Merge mozilla-central to mozilla-inbound
@ -585,9 +585,6 @@
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
@BINPATH@/defaults/pref/services-sync.js
|
||||
#endif
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
@BINPATH@/defaults/pref/healthreport-prefs.js
|
||||
#endif
|
||||
|
||||
; [Layout Engine Resources]
|
||||
; Style Sheets, Graphics and other Resources used by the layout engine.
|
||||
|
@ -29,7 +29,6 @@ MOZ_SERVICES_AITC=1
|
||||
MOZ_SERVICES_COMMON=1
|
||||
MOZ_SERVICES_CRYPTO=1
|
||||
MOZ_SERVICES_METRICS=1
|
||||
MOZ_SERVICES_NOTIFICATIONS=1
|
||||
MOZ_SERVICES_SYNC=1
|
||||
MOZ_APP_VERSION=$FIREFOX_VERSION
|
||||
MOZ_EXTENSIONS_DEFAULT=" gio"
|
||||
|
@ -31,6 +31,8 @@ function test()
|
||||
gDebugger = gPane.panelWin;
|
||||
resumed = true;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
executeSoon(startTest);
|
||||
@ -47,12 +49,10 @@ function test()
|
||||
executeSoon(startTest);
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
function startTest()
|
||||
{
|
||||
if (scriptShown && framesAdded && resumed && !testStarted) {
|
||||
window.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
testStarted = true;
|
||||
Services.tm.currentThread.dispatch({ run: performTest }, 0);
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ function test()
|
||||
addAndCheckExpressions(total, index, "", true);
|
||||
|
||||
for (let i = 0; i < string.length; i++) {
|
||||
EventUtils.sendChar(string[i]);
|
||||
EventUtils.sendChar(string[i], gDebugger);
|
||||
}
|
||||
|
||||
gDebugger.editor.focus();
|
||||
|
@ -126,7 +126,7 @@ function test()
|
||||
});
|
||||
executeSoon(function() {
|
||||
gWatch.addExpression("a = 5");
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
});
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ function test()
|
||||
});
|
||||
executeSoon(function() {
|
||||
gWatch.addExpression("encodeURI(\"\\\")");
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
});
|
||||
}
|
||||
|
||||
@ -150,7 +150,7 @@ function test()
|
||||
})
|
||||
executeSoon(function() {
|
||||
gWatch.addExpression("decodeURI(\"\\\")");
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
});
|
||||
}
|
||||
|
||||
@ -162,7 +162,7 @@ function test()
|
||||
});
|
||||
executeSoon(function() {
|
||||
gWatch.addExpression("?");
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
});
|
||||
}
|
||||
|
||||
@ -174,7 +174,7 @@ function test()
|
||||
});
|
||||
executeSoon(function() {
|
||||
gWatch.addExpression("a");
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,8 @@ function test()
|
||||
gDebugger = gPane.panelWin;
|
||||
resumed = true;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
executeSoon(startTest);
|
||||
@ -46,13 +48,11 @@ function test()
|
||||
executeSoon(startTest);
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
function startTest()
|
||||
{
|
||||
if (scriptShown && framesAdded && resumed && !testStarted) {
|
||||
testStarted = true;
|
||||
window.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
Services.tm.currentThread.dispatch({ run: performTest }, 0);
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,8 @@ function test()
|
||||
gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
|
||||
resumed = true;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
executeSoon(startTest);
|
||||
@ -52,12 +54,10 @@ function test()
|
||||
executeSoon(startTest);
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
function startTest()
|
||||
{
|
||||
if (scriptShown && framesAdded && resumed && !testStarted) {
|
||||
window.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
testStarted = true;
|
||||
Services.tm.currentThread.dispatch({ run: addBreakpoints }, 0);
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ function test()
|
||||
gBreakpoints = gDebugger.DebuggerController.Breakpoints;
|
||||
gBreakpointsPane = gDebugger.DebuggerView.Breakpoints;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
gDebugger.DebuggerView.togglePanes({ visible: true, animated: false });
|
||||
resumed = true;
|
||||
|
||||
@ -56,12 +58,10 @@ function test()
|
||||
executeSoon(startTest);
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
function startTest()
|
||||
{
|
||||
if (scriptShown && framesAdded && resumed && !testStarted) {
|
||||
window.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
testStarted = true;
|
||||
Services.tm.currentThread.dispatch({ run: performTest }, 0);
|
||||
}
|
||||
@ -166,7 +166,7 @@ function test()
|
||||
function modBreakpoint3()
|
||||
{
|
||||
write("bamboocha");
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
|
||||
waitForBreakpoint(14, function() {
|
||||
waitForCaretPos(13, function() {
|
||||
@ -568,7 +568,7 @@ function test()
|
||||
gBreakpointsPane._cbTextbox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,15 @@ function test()
|
||||
gPane = aPane;
|
||||
gDebugger = gPane.panelWin;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("browser_dbg_stack") != -1) {
|
||||
scriptShown = true;
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
runTest();
|
||||
@ -30,15 +39,6 @@ function test()
|
||||
gDebuggee.simpleCall();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("browser_dbg_stack") != -1) {
|
||||
scriptShown = true;
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
function runTest()
|
||||
{
|
||||
if (scriptShown && framesAdded) {
|
||||
|
@ -22,6 +22,15 @@ function test()
|
||||
gPane = aPane;
|
||||
gDebugger = gPane.panelWin;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("browser_dbg_stack") != -1) {
|
||||
scriptShown = true;
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
runTest();
|
||||
@ -30,15 +39,6 @@ function test()
|
||||
gDebuggee.simpleCall();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("browser_dbg_stack") != -1) {
|
||||
scriptShown = true;
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
function runTest()
|
||||
{
|
||||
if (scriptShown && framesAdded) {
|
||||
|
@ -154,8 +154,8 @@ function testModification(aVar, aTest, aCallback, aNewValue, aNewResult, aArgRes
|
||||
aLocalScopeIndex = 1, aDeletionFlag = null)
|
||||
{
|
||||
function makeChangesAndExitInputMode() {
|
||||
EventUtils.sendString(aNewValue);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendString(aNewValue, gDebugger);
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
}
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "dblclick" },
|
||||
|
@ -65,8 +65,8 @@ function testFrameEval() {
|
||||
|
||||
function testModification(aVar, aCallback, aNewValue, aNewResult) {
|
||||
function makeChangesAndExitInputMode() {
|
||||
EventUtils.sendString(aNewValue);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendString(aNewValue, gDebugger);
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
}
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "click" },
|
||||
|
@ -502,7 +502,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -444,7 +444,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ function testVariablesFiltering()
|
||||
is(oneItem.expanded, false,
|
||||
"The one item in the inner scope should not be expanded");
|
||||
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
is(oneItem.expanded, true,
|
||||
"The one item in the inner scope should now be expanded");
|
||||
}
|
||||
@ -108,7 +108,7 @@ function testVariablesFiltering()
|
||||
is(twoItem.expanded, false,
|
||||
"The two item in the inner scope should not be expanded");
|
||||
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
is(twoItem.expanded, true,
|
||||
"The two item in the inner scope should now be expanded");
|
||||
}
|
||||
@ -285,7 +285,7 @@ function write(text) {
|
||||
|
||||
function backspace(times) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
EventUtils.sendKey("BACK_SPACE")
|
||||
EventUtils.sendKey("BACK_SPACE", gDebugger)
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,7 +293,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ function testVariablesFiltering()
|
||||
test2: function()
|
||||
{
|
||||
assertExpansion(2, [true, false, false, false, false]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test3: function()
|
||||
{
|
||||
@ -55,7 +55,7 @@ function testVariablesFiltering()
|
||||
},
|
||||
test5: function() {
|
||||
assertExpansion(5, [true, true, true, true, true]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test6: function() {
|
||||
assertExpansion(6, [true, true, true, true, true]);
|
||||
@ -67,7 +67,7 @@ function testVariablesFiltering()
|
||||
},
|
||||
test8: function() {
|
||||
assertExpansion(8, [true, true, true, true, true]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test9: function() {
|
||||
assertExpansion(9, [true, true, true, true, true]);
|
||||
@ -87,7 +87,7 @@ function testVariablesFiltering()
|
||||
},
|
||||
test12: function() {
|
||||
assertExpansion(12, [false, false, false, false, false]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test13: function() {
|
||||
assertExpansion(13, [false, false, false, false, false]);
|
||||
@ -99,7 +99,7 @@ function testVariablesFiltering()
|
||||
},
|
||||
test15: function() {
|
||||
assertExpansion(15, [true, true, true, true, true]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test16: function() {
|
||||
assertExpansion(16, [true, true, true, true, true]);
|
||||
@ -111,7 +111,7 @@ function testVariablesFiltering()
|
||||
},
|
||||
test18: function() {
|
||||
assertExpansion(18, [true, true, true, true, true]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test19: function() {
|
||||
assertExpansion(19, [true, true, true, true, true]);
|
||||
@ -227,7 +227,7 @@ function write(text) {
|
||||
|
||||
function backspace(times) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
EventUtils.sendKey("BACK_SPACE")
|
||||
EventUtils.sendKey("BACK_SPACE", gDebugger)
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,7 +235,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ function testVariablesFiltering()
|
||||
test2: function()
|
||||
{
|
||||
assertExpansion(2, [true, false, false, false, false]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test3: function()
|
||||
{
|
||||
@ -55,7 +55,7 @@ function testVariablesFiltering()
|
||||
},
|
||||
test5: function() {
|
||||
assertExpansion(5, [true, true, true, true, true]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test6: function() {
|
||||
assertExpansion(6, [true, true, true, true, true]);
|
||||
@ -67,7 +67,7 @@ function testVariablesFiltering()
|
||||
},
|
||||
test8: function() {
|
||||
assertExpansion(8, [true, true, true, true, true]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test9: function() {
|
||||
assertExpansion(9, [true, true, true, true, true]);
|
||||
@ -87,7 +87,7 @@ function testVariablesFiltering()
|
||||
},
|
||||
test12: function() {
|
||||
assertExpansion(12, [false, false, false, false, false]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test13: function() {
|
||||
assertExpansion(13, [false, false, false, false, false]);
|
||||
@ -99,7 +99,7 @@ function testVariablesFiltering()
|
||||
},
|
||||
test15: function() {
|
||||
assertExpansion(15, [true, true, true, true, true]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test16: function() {
|
||||
assertExpansion(16, [true, true, true, true, true]);
|
||||
@ -111,7 +111,7 @@ function testVariablesFiltering()
|
||||
},
|
||||
test18: function() {
|
||||
assertExpansion(18, [true, true, true, true, true]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
},
|
||||
test19: function() {
|
||||
assertExpansion(19, [true, true, true, true, true]);
|
||||
@ -232,7 +232,7 @@ function write(text) {
|
||||
|
||||
function backspace(times) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
EventUtils.sendKey("BACK_SPACE")
|
||||
EventUtils.sendKey("BACK_SPACE", gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,7 +240,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ function testVariablesFiltering()
|
||||
"The arguments pseudoarray in the testScope should not be expanded");
|
||||
|
||||
assertExpansion(1, [true, true, true, true, true]);
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
aCallback();
|
||||
},
|
||||
test3: function(aCallback)
|
||||
@ -302,7 +302,7 @@ function write(text) {
|
||||
|
||||
function backspace(times) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
EventUtils.sendKey("BACK_SPACE")
|
||||
EventUtils.sendKey("BACK_SPACE", gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
@ -310,7 +310,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,8 @@ function test()
|
||||
gView = gDebugger.DebuggerView;
|
||||
resumed = true;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
gView.Sources.preferredSource = EXAMPLE_URL + expectedScript;
|
||||
startTest();
|
||||
});
|
||||
@ -42,12 +44,10 @@ function test()
|
||||
startTest();
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
function startTest()
|
||||
{
|
||||
if (expectedScriptShown && resumed && !testStarted) {
|
||||
window.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
testStarted = true;
|
||||
Services.tm.currentThread.dispatch({ run: performTest }, 0);
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ function test()
|
||||
gView = gDebugger.DebuggerView;
|
||||
resumed = true;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
startTest();
|
||||
});
|
||||
|
||||
@ -55,13 +57,11 @@ function test()
|
||||
testScriptShown();
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
function startTest()
|
||||
{
|
||||
if (expectedScriptShown && resumed && !testStarted) {
|
||||
window.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
window.addEventListener("Debugger:SourceShown", onUlteriorScriptShown);
|
||||
gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
gDebugger.addEventListener("Debugger:SourceShown", onUlteriorScriptShown);
|
||||
testStarted = true;
|
||||
Services.tm.currentThread.dispatch({ run: performTest }, 0);
|
||||
}
|
||||
@ -70,7 +70,7 @@ function test()
|
||||
function finishTest()
|
||||
{
|
||||
if (expectedScriptShown && resumed && testStarted) {
|
||||
window.removeEventListener("Debugger:SourceShown", onUlteriorScriptShown);
|
||||
gDebugger.removeEventListener("Debugger:SourceShown", onUlteriorScriptShown);
|
||||
closeDebuggerAndFinish();
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,8 @@ function test()
|
||||
gDebugger = gPane.panelWin;
|
||||
resumed = true;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
executeSoon(startTest);
|
||||
@ -44,12 +46,10 @@ function test()
|
||||
executeSoon(startTest);
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
function startTest()
|
||||
{
|
||||
if (scriptShown && framesAdded && resumed && !testStarted) {
|
||||
window.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
testStarted = true;
|
||||
Services.tm.currentThread.dispatch({ run: testScriptsDisplay }, 0);
|
||||
}
|
||||
@ -87,10 +87,10 @@ function testScriptsDisplay() {
|
||||
is(gDebugger.editor.getDebugLocation(), 5,
|
||||
"editor debugger location is correct.");
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-01.js") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
testSwitchPaused();
|
||||
}
|
||||
});
|
||||
@ -110,10 +110,10 @@ function testSwitchPaused()
|
||||
"editor debugger location has been cleared.");
|
||||
|
||||
gDebugger.DebuggerController.activeThread.resume(function() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
testSwitchRunning();
|
||||
}
|
||||
});
|
||||
|
@ -26,6 +26,12 @@ function test()
|
||||
gPane = aPane;
|
||||
gDebugger = gPane.panelWin;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
scriptShown = true;
|
||||
runTest();
|
||||
});
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
runTest();
|
||||
@ -34,12 +40,6 @@ function test()
|
||||
gDebuggee.simpleCall();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
scriptShown = true;
|
||||
runTest();
|
||||
});
|
||||
|
||||
function runTest()
|
||||
{
|
||||
if (scriptShown && framesAdded) {
|
||||
@ -63,35 +63,35 @@ function testScriptSearching() {
|
||||
gEditor.getCaretPosition().col == 0,
|
||||
"The editor didn't jump to the correct line.");
|
||||
|
||||
EventUtils.synthesizeKey("g", { metaKey: true });
|
||||
EventUtils.synthesizeKey("g", { metaKey: true }, gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 12 &&
|
||||
gEditor.getCaretPosition().col == 0,
|
||||
"The editor didn't jump to the correct line after Meta+G");
|
||||
|
||||
EventUtils.synthesizeKey("n", { ctrlKey: true });
|
||||
EventUtils.synthesizeKey("n", { ctrlKey: true }, gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 13 &&
|
||||
gEditor.getCaretPosition().col == 0,
|
||||
"The editor didn't jump to the correct line after Ctrl+N");
|
||||
|
||||
EventUtils.synthesizeKey("G", { metaKey: true, shiftKey: true });
|
||||
EventUtils.synthesizeKey("G", { metaKey: true, shiftKey: true }, gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 12 &&
|
||||
gEditor.getCaretPosition().col == 0,
|
||||
"The editor didn't jump to the correct line after Meta+Shift+G");
|
||||
|
||||
EventUtils.synthesizeKey("p", { ctrlKey: true });
|
||||
EventUtils.synthesizeKey("p", { ctrlKey: true }, gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 11 &&
|
||||
gEditor.getCaretPosition().col == 0,
|
||||
"The editor didn't jump to the correct line after Ctrl+P");
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
EventUtils.sendKey("DOWN");
|
||||
EventUtils.sendKey("DOWN", gDebugger);
|
||||
}
|
||||
ok(gEditor.getCaretPosition().line == 32 &&
|
||||
gEditor.getCaretPosition().col == 0,
|
||||
"The editor didn't jump to the correct line after multiple DOWN keys");
|
||||
|
||||
for (let i = 0; i < 100; i++) {
|
||||
EventUtils.sendKey("UP");
|
||||
EventUtils.sendKey("UP", gDebugger);
|
||||
}
|
||||
ok(gEditor.getCaretPosition().line == 0 &&
|
||||
gEditor.getCaretPosition().col == 0,
|
||||
@ -104,27 +104,27 @@ function testScriptSearching() {
|
||||
gEditor.getCaretPosition().col == 44 + token.length,
|
||||
"The editor didn't jump to the correct token. (1)");
|
||||
|
||||
EventUtils.sendKey("DOWN");
|
||||
EventUtils.sendKey("DOWN", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 8 &&
|
||||
gEditor.getCaretPosition().col == 2 + token.length,
|
||||
"The editor didn't jump to the correct token. (2)");
|
||||
|
||||
EventUtils.sendKey("DOWN");
|
||||
EventUtils.sendKey("DOWN", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 12 &&
|
||||
gEditor.getCaretPosition().col == 8 + token.length,
|
||||
"The editor didn't jump to the correct token. (3)");
|
||||
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 19 &&
|
||||
gEditor.getCaretPosition().col == 4 + token.length,
|
||||
"The editor didn't jump to the correct token. (4)");
|
||||
|
||||
EventUtils.sendKey("ENTER");
|
||||
EventUtils.sendKey("ENTER", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 2 &&
|
||||
gEditor.getCaretPosition().col == 44 + token.length,
|
||||
"The editor didn't jump to the correct token. (5)");
|
||||
|
||||
EventUtils.sendKey("UP");
|
||||
EventUtils.sendKey("UP", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 19 &&
|
||||
gEditor.getCaretPosition().col == 4 + token.length,
|
||||
"The editor didn't jump to the correct token. (5.1)");
|
||||
@ -227,14 +227,14 @@ function testScriptSearching() {
|
||||
"The menulist should not display a notice that matches are found.");
|
||||
|
||||
clear();
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 2 &&
|
||||
gEditor.getCaretPosition().col == 44 + token.length,
|
||||
"The editor shouldn't jump to another token. (12.2)");
|
||||
isnot(gMenulist.getAttribute("label"), noMatchingScripts,
|
||||
"The menulist should not display a notice that matches are found.");
|
||||
|
||||
EventUtils.sendKey("ENTER");
|
||||
EventUtils.sendKey("ENTER", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 2 &&
|
||||
gEditor.getCaretPosition().col == 44 + token.length,
|
||||
"The editor shouldn't jump to another token. (12.3)");
|
||||
@ -252,27 +252,27 @@ function testScriptSearching() {
|
||||
gEditor.getCaretPosition().col == 44 + token.length,
|
||||
"The editor didn't jump to the correct token. (14)");
|
||||
|
||||
EventUtils.sendKey("DOWN");
|
||||
EventUtils.sendKey("DOWN", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 8 &&
|
||||
gEditor.getCaretPosition().col == 2 + token.length,
|
||||
"The editor didn't jump to the correct token. (15)");
|
||||
|
||||
EventUtils.sendKey("DOWN");
|
||||
EventUtils.sendKey("DOWN", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 12 &&
|
||||
gEditor.getCaretPosition().col == 8 + token.length,
|
||||
"The editor didn't jump to the correct token. (16)");
|
||||
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 19 &&
|
||||
gEditor.getCaretPosition().col == 4 + token.length,
|
||||
"The editor didn't jump to the correct token. (17)");
|
||||
|
||||
EventUtils.sendKey("ENTER");
|
||||
EventUtils.sendKey("ENTER", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 2 &&
|
||||
gEditor.getCaretPosition().col == 44 + token.length,
|
||||
"The editor didn't jump to the correct token. (18)");
|
||||
|
||||
EventUtils.sendKey("UP");
|
||||
EventUtils.sendKey("UP", gDebugger);
|
||||
ok(gEditor.getCaretPosition().line == 19 &&
|
||||
gEditor.getCaretPosition().col == 4 + token.length,
|
||||
"The editor didn't jump to the correct token. (18.1)");
|
||||
@ -305,7 +305,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
}
|
||||
|
@ -28,6 +28,15 @@ function test()
|
||||
gPane = aPane;
|
||||
gDebugger = gPane.panelWin;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
runTest();
|
||||
@ -36,15 +45,6 @@ function test()
|
||||
gDebuggee.firstCall();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
function runTest()
|
||||
{
|
||||
if (scriptShown && framesAdded) {
|
||||
@ -65,13 +65,13 @@ function testScriptSearching() {
|
||||
}
|
||||
|
||||
function firstSearch() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
info("Current script url:\n" + aEvent.detail.url + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-01.js") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
executeSoon(function() {
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
@ -91,13 +91,13 @@ function firstSearch() {
|
||||
function secondSearch() {
|
||||
let token = "deb";
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
info("Current script url:\n" + aEvent.detail.url + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
executeSoon(function() {
|
||||
append("#" + token);
|
||||
@ -117,13 +117,13 @@ function secondSearch() {
|
||||
}
|
||||
|
||||
function waitForFirstScript() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
info("Current script url:\n" + aEvent.detail.url + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-01.js") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
executeSoon(function() {
|
||||
thirdSearch();
|
||||
@ -136,13 +136,13 @@ function waitForFirstScript() {
|
||||
function thirdSearch() {
|
||||
let token = "deb";
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
info("Current script url:\n" + aEvent.detail.url + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
executeSoon(function() {
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
@ -167,7 +167,7 @@ function fourthSearch(i, string, token) {
|
||||
"The editor didn't remain at the correct token. (4)");
|
||||
|
||||
if (string[i]) {
|
||||
EventUtils.sendChar(string[i]);
|
||||
EventUtils.sendChar(string[i], gDebugger);
|
||||
fourthSearch(i + 1, string, token);
|
||||
return;
|
||||
}
|
||||
@ -245,7 +245,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
}
|
||||
|
@ -30,6 +30,15 @@ function test()
|
||||
gDebugger = gPane.panelWin;
|
||||
gDebugger.SourceResults.prototype.alwaysExpand = false;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
runTest();
|
||||
@ -38,15 +47,6 @@ function test()
|
||||
gDebuggee.firstCall();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
function runTest()
|
||||
{
|
||||
if (scriptShown && framesAdded) {
|
||||
@ -74,8 +74,8 @@ function firstSearch() {
|
||||
is(gSearchView._splitter.hidden, true,
|
||||
"The global search pane splitter shouldn't be visible yet.");
|
||||
|
||||
window.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -190,8 +190,8 @@ function secondSearch() {
|
||||
is(gSearchView._splitter.hidden, false,
|
||||
"The global search pane splitter should be visible from the previous search.");
|
||||
|
||||
window.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -311,7 +311,7 @@ function write(text) {
|
||||
|
||||
function backspace(times) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
EventUtils.sendKey("BACK_SPACE")
|
||||
EventUtils.sendKey("BACK_SPACE", gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
@ -319,7 +319,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
}
|
||||
|
@ -30,6 +30,15 @@ function test()
|
||||
gDebugger = gPane.panelWin;
|
||||
gDebugger.SourceResults.prototype.alwaysExpand = false;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
runTest();
|
||||
@ -38,15 +47,6 @@ function test()
|
||||
gDebuggee.firstCall();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
function runTest()
|
||||
{
|
||||
if (scriptShown && framesAdded) {
|
||||
@ -74,8 +74,8 @@ function doSearch() {
|
||||
is(gSearchView._splitter.hidden, true,
|
||||
"The global search pane splitter shouldn't be visible yet.");
|
||||
|
||||
window.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -108,8 +108,8 @@ function doSearch() {
|
||||
}
|
||||
|
||||
function doFirstJump() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + aEvent.detail.url + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -130,13 +130,13 @@ function doFirstJump() {
|
||||
}
|
||||
});
|
||||
executeSoon(function() {
|
||||
EventUtils.sendKey("DOWN");
|
||||
EventUtils.sendKey("DOWN", gDebugger);
|
||||
});
|
||||
}
|
||||
|
||||
function doSecondJump() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + aEvent.detail.url + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -157,13 +157,13 @@ function doSecondJump() {
|
||||
}
|
||||
});
|
||||
executeSoon(function() {
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
});
|
||||
}
|
||||
|
||||
function doWrapAroundJump() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + aEvent.detail.url + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -184,13 +184,13 @@ function doWrapAroundJump() {
|
||||
}
|
||||
});
|
||||
executeSoon(function() {
|
||||
EventUtils.sendKey("ENTER");
|
||||
EventUtils.sendKey("ENTER", gDebugger);
|
||||
});
|
||||
}
|
||||
|
||||
function doBackwardsWrapAroundJump() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + aEvent.detail.url + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -211,13 +211,13 @@ function doBackwardsWrapAroundJump() {
|
||||
}
|
||||
});
|
||||
executeSoon(function() {
|
||||
EventUtils.sendKey("UP");
|
||||
EventUtils.sendKey("UP", gDebugger);
|
||||
});
|
||||
}
|
||||
|
||||
function testSearchTokenEmpty() {
|
||||
window.addEventListener("Debugger:GlobalSearch:TokenEmpty", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:TokenEmpty", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -259,7 +259,7 @@ function write(text) {
|
||||
|
||||
function backspace(times) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
EventUtils.sendKey("BACK_SPACE")
|
||||
EventUtils.sendKey("BACK_SPACE", gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,7 +267,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
}
|
||||
|
@ -30,6 +30,15 @@ function test()
|
||||
gDebugger = gPane.panelWin;
|
||||
gDebugger.SourceResults.prototype.alwaysExpand = false;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
runTest();
|
||||
@ -38,15 +47,6 @@ function test()
|
||||
gDebuggee.firstCall();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
function runTest()
|
||||
{
|
||||
if (scriptShown && framesAdded) {
|
||||
@ -74,8 +74,8 @@ function doSearch() {
|
||||
is(gSearchView._splitter.hidden, true,
|
||||
"The global search pane splitter shouldn't be visible yet.");
|
||||
|
||||
window.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -118,8 +118,8 @@ function testLocationChange()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:GlobalSearch:ViewCleared", function _onViewCleared(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onViewCleared);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:ViewCleared", function _onViewCleared(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onViewCleared);
|
||||
|
||||
is(gSearchView._container._list.childNodes.length, 0,
|
||||
"The global search pane shouldn't have any child nodes after a page navigation.");
|
||||
@ -132,8 +132,8 @@ function testLocationChange()
|
||||
_maybeFinish();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:GlobalSearch:CacheCleared", function _onCacheCleared(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onCacheCleared);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:CacheCleared", function _onCacheCleared(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onCacheCleared);
|
||||
|
||||
is(gSearchView._cache.size, 0,
|
||||
"The scripts sources cache for global searching should be cleared after a page navigation.")
|
||||
@ -159,7 +159,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
}
|
||||
|
@ -30,6 +30,15 @@ function test()
|
||||
gDebugger = gPane.panelWin;
|
||||
gDebugger.SourceResults.prototype.alwaysExpand = false;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
runTest();
|
||||
@ -38,15 +47,6 @@ function test()
|
||||
gDebuggee.firstCall();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
function runTest()
|
||||
{
|
||||
if (scriptShown && framesAdded) {
|
||||
@ -67,8 +67,8 @@ function testScriptSearching() {
|
||||
}
|
||||
|
||||
function doSearch() {
|
||||
window.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -94,8 +94,8 @@ function doSearch() {
|
||||
}
|
||||
|
||||
function testSearchMatchNotFound() {
|
||||
window.addEventListener("Debugger:GlobalSearch:MatchNotFound", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:MatchNotFound", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -132,7 +132,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
}
|
||||
|
@ -31,6 +31,15 @@ function test()
|
||||
gDebugger = gPane.panelWin;
|
||||
gDebugger.SourceResults.prototype.alwaysExpand = false;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
runTest();
|
||||
@ -39,15 +48,6 @@ function test()
|
||||
gDebuggee.firstCall();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
function runTest()
|
||||
{
|
||||
if (scriptShown && framesAdded) {
|
||||
@ -68,8 +68,8 @@ function testScriptSearching() {
|
||||
}
|
||||
|
||||
function doSearch() {
|
||||
window.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -170,8 +170,8 @@ function testClickLineToJump(scriptResults, callbacks) {
|
||||
is(firstHeaderItem.instance.expanded, true,
|
||||
"The first script results should be expanded after direct function call.");
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + aEvent.detail.url + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -205,8 +205,8 @@ function testClickMatchToJump(scriptResults, callbacks) {
|
||||
is(secondHeaderItem.instance.expanded, true,
|
||||
"The second script results should be expanded after direct function call.");
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + aEvent.detail.url + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -246,7 +246,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
}
|
||||
|
@ -30,6 +30,15 @@ function test()
|
||||
gDebugger = gPane.panelWin;
|
||||
gDebugger.SourceResults.prototype.alwaysExpand = false;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
runTest();
|
||||
@ -38,15 +47,6 @@ function test()
|
||||
gDebuggee.firstCall();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
function runTest()
|
||||
{
|
||||
if (scriptShown && framesAdded) {
|
||||
@ -70,8 +70,8 @@ function doSearch() {
|
||||
is(gSearchView._container._parent.hidden, true,
|
||||
"The global search pane shouldn't be visible yet.");
|
||||
|
||||
window.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -94,8 +94,8 @@ function testFocusLost()
|
||||
is(gSearchView._container._parent.hidden, false,
|
||||
"The global search pane should be visible after a search.");
|
||||
|
||||
window.addEventListener("Debugger:GlobalSearch:ViewCleared", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:ViewCleared", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -117,8 +117,8 @@ function reshowSearch() {
|
||||
is(gSearchView._container._parent.hidden, true,
|
||||
"The global search pane shouldn't be visible after the search was stopped.");
|
||||
|
||||
window.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:MatchFound", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -141,8 +141,8 @@ function testEscape()
|
||||
is(gSearchView._container._parent.hidden, false,
|
||||
"The global search pane should be visible after a re-search.");
|
||||
|
||||
window.addEventListener("Debugger:GlobalSearch:ViewCleared", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("Debugger:GlobalSearch:ViewCleared", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -180,19 +180,19 @@ function write(text) {
|
||||
|
||||
function sendEnter() {
|
||||
gSearchBox.focus();
|
||||
EventUtils.sendKey("ENTER");
|
||||
EventUtils.sendKey("ENTER", gDebugger);
|
||||
}
|
||||
|
||||
function sendEscape() {
|
||||
gSearchBox.focus();
|
||||
EventUtils.sendKey("ESCAPE");
|
||||
EventUtils.sendKey("ESCAPE", gDebugger);
|
||||
}
|
||||
|
||||
function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
}
|
||||
|
@ -25,12 +25,13 @@ function test()
|
||||
gDebuggee = aDebuggee;
|
||||
gPane = aPane;
|
||||
gDebugger = gPane.panelWin;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
Services.tm.currentThread.dispatch({ run: testScriptSearching }, 0);
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
Services.tm.currentThread.dispatch({ run: testScriptSearching }, 0);
|
||||
});
|
||||
}
|
||||
|
||||
function testScriptSearching() {
|
||||
@ -44,8 +45,8 @@ function testScriptSearching() {
|
||||
}
|
||||
|
||||
function firstSearch() {
|
||||
window.addEventListener("popupshown", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("popupshown", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -107,13 +108,13 @@ function secondSearch() {
|
||||
let popupshown = false;
|
||||
let proceeded = false;
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent1(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent1);
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent1(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent1);
|
||||
sourceshown = true;
|
||||
executeSoon(proceed);
|
||||
});
|
||||
window.addEventListener("popupshown", function _onEvent2(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent2);
|
||||
gDebugger.addEventListener("popupshown", function _onEvent2(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent2);
|
||||
popupshown = true;
|
||||
executeSoon(proceed);
|
||||
});
|
||||
@ -184,13 +185,13 @@ function thirdSearch() {
|
||||
let popupshown = false;
|
||||
let proceeded = false;
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent1(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent1);
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent1(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent1);
|
||||
sourceshown = true;
|
||||
executeSoon(proceed);
|
||||
});
|
||||
window.addEventListener("popupshown", function _onEvent2(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent2);
|
||||
gDebugger.addEventListener("popupshown", function _onEvent2(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent2);
|
||||
popupshown = true;
|
||||
executeSoon(proceed);
|
||||
});
|
||||
@ -257,7 +258,7 @@ function thirdSearch() {
|
||||
}
|
||||
|
||||
function goDown() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -275,7 +276,7 @@ function goDown() {
|
||||
|
||||
let url = gScripts.selectedValue;
|
||||
if (url.indexOf("test-editor-mode") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
executeSoon(function() {
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
@ -291,11 +292,11 @@ function goDown() {
|
||||
ok(false, "How did you get here?");
|
||||
}
|
||||
});
|
||||
EventUtils.sendKey("DOWN");
|
||||
EventUtils.sendKey("DOWN", gDebugger);
|
||||
}
|
||||
|
||||
function goDownAgain() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -313,7 +314,7 @@ function goDownAgain() {
|
||||
|
||||
let url = gScripts.selectedValue;
|
||||
if (url.indexOf("test-script-switching-01.js") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
executeSoon(function() {
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
@ -329,11 +330,11 @@ function goDownAgain() {
|
||||
ok(false, "How did you get here?");
|
||||
}
|
||||
});
|
||||
EventUtils.synthesizeKey("g", { metaKey: true });
|
||||
EventUtils.synthesizeKey("g", { metaKey: true }, gDebugger);
|
||||
}
|
||||
|
||||
function goDownAndWrap() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -351,7 +352,7 @@ function goDownAndWrap() {
|
||||
|
||||
let url = gScripts.selectedValue;
|
||||
if (url.indexOf("update-editor-mode.html") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
executeSoon(function() {
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
@ -367,11 +368,11 @@ function goDownAndWrap() {
|
||||
ok(false, "How did you get here?");
|
||||
}
|
||||
});
|
||||
EventUtils.synthesizeKey("n", { ctrlKey: true });
|
||||
EventUtils.synthesizeKey("n", { ctrlKey: true }, gDebugger);
|
||||
}
|
||||
|
||||
function goUpAndWrap() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -389,7 +390,7 @@ function goUpAndWrap() {
|
||||
|
||||
let url = gScripts.selectedValue;
|
||||
if (url.indexOf("test-script-switching-01.js") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
executeSoon(function() {
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
@ -405,11 +406,11 @@ function goUpAndWrap() {
|
||||
ok(false, "How did you get here?");
|
||||
}
|
||||
});
|
||||
EventUtils.sendKey("UP");
|
||||
EventUtils.sendKey("UP", gDebugger);
|
||||
}
|
||||
|
||||
function clickAndSwitch() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -427,7 +428,7 @@ function clickAndSwitch() {
|
||||
|
||||
let url = gScripts.selectedValue;
|
||||
if (url.indexOf("update-editor-mode.html") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
executeSoon(function() {
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
@ -447,7 +448,7 @@ function clickAndSwitch() {
|
||||
}
|
||||
|
||||
function clickAndSwitchAgain() {
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
|
||||
@ -465,7 +466,7 @@ function clickAndSwitchAgain() {
|
||||
|
||||
let url = gScripts.selectedValue;
|
||||
if (url.indexOf("test-script-switching-01.js") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
executeSoon(function() {
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
@ -485,8 +486,8 @@ function clickAndSwitchAgain() {
|
||||
}
|
||||
|
||||
function switchFocusWithEscape() {
|
||||
window.addEventListener("popuphidden", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("popuphidden", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
@ -508,12 +509,12 @@ function switchFocusWithEscape() {
|
||||
ok(false, "How did you get here?");
|
||||
}
|
||||
});
|
||||
EventUtils.sendKey("ESCAPE");
|
||||
EventUtils.sendKey("ESCAPE", gDebugger);
|
||||
}
|
||||
|
||||
function focusAgainAfterEscape() {
|
||||
window.addEventListener("popupshown", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("popupshown", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
@ -539,8 +540,8 @@ function focusAgainAfterEscape() {
|
||||
}
|
||||
|
||||
function switchFocusWithReturn() {
|
||||
window.addEventListener("popuphidden", function _onEvent(aEvent) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.addEventListener("popuphidden", function _onEvent(aEvent) {
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
|
||||
info("Current script url:\n" + gScripts.selectedValue + "\n");
|
||||
info("Debugger editor text:\n" + gEditor.getText() + "\n");
|
||||
@ -562,7 +563,7 @@ function switchFocusWithReturn() {
|
||||
ok(false, "How did you get here?");
|
||||
}
|
||||
});
|
||||
EventUtils.sendKey("RETURN");
|
||||
EventUtils.sendKey("RETURN", gDebugger);
|
||||
}
|
||||
|
||||
function clear() {
|
||||
@ -577,7 +578,7 @@ function write(text) {
|
||||
|
||||
function backspace(times) {
|
||||
for (let i = 0; i < times; i++) {
|
||||
EventUtils.sendKey("BACK_SPACE")
|
||||
EventUtils.sendKey("BACK_SPACE", gDebugger);
|
||||
}
|
||||
}
|
||||
|
||||
@ -585,7 +586,7 @@ function append(text) {
|
||||
gSearchBox.focus();
|
||||
|
||||
for (let i = 0; i < text.length; i++) {
|
||||
EventUtils.sendChar(text[i]);
|
||||
EventUtils.sendChar(text[i], gDebugger);
|
||||
}
|
||||
info("Editor caret position: " + gEditor.getCaretPosition().toSource() + "\n");
|
||||
}
|
||||
|
@ -24,6 +24,15 @@ function test() {
|
||||
gPane = aPane;
|
||||
gDebugger = gPane.panelWin;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
runTest();
|
||||
@ -32,15 +41,6 @@ function test() {
|
||||
gDebuggee.firstCall();
|
||||
});
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("-02.js") != -1) {
|
||||
scriptShown = true;
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
runTest();
|
||||
}
|
||||
});
|
||||
|
||||
function runTest()
|
||||
{
|
||||
if (scriptShown && framesAdded) {
|
||||
|
@ -33,6 +33,8 @@ function test()
|
||||
gScripts = gDebugger.DebuggerView.Sources._container;
|
||||
resumed = true;
|
||||
|
||||
gDebugger.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
gDebugger.DebuggerController.activeThread.addOneTimeListener("framesadded", function() {
|
||||
framesAdded = true;
|
||||
executeSoon(startTest);
|
||||
@ -48,12 +50,10 @@ function test()
|
||||
executeSoon(startTest);
|
||||
}
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", onScriptShown);
|
||||
|
||||
function startTest()
|
||||
{
|
||||
if (scriptShown && framesAdded && resumed && !testStarted) {
|
||||
window.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
gDebugger.removeEventListener("Debugger:SourceShown", onScriptShown);
|
||||
testStarted = true;
|
||||
Services.tm.currentThread.dispatch({ run: testScriptsDisplay }, 0);
|
||||
}
|
||||
@ -73,10 +73,10 @@ function testScriptsDisplay() {
|
||||
ok(gDebugger.editor.getText().search(/debugger/) != -1,
|
||||
"The correct script was loaded initially.");
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("switching-01.js") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
testSwitchPaused1();
|
||||
}
|
||||
});
|
||||
@ -102,10 +102,10 @@ function testSwitchPaused1()
|
||||
is(gDebugger.editor.getMode(), SourceEditor.MODES.JAVASCRIPT,
|
||||
"Found the expected editor mode.");
|
||||
|
||||
window.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
gDebugger.addEventListener("Debugger:SourceShown", function _onEvent(aEvent) {
|
||||
let url = aEvent.detail.url;
|
||||
if (url.indexOf("update-editor-mode") != -1) {
|
||||
window.removeEventListener(aEvent.type, _onEvent);
|
||||
gDebugger.removeEventListener(aEvent.type, _onEvent);
|
||||
testSwitchPaused2();
|
||||
}
|
||||
});
|
||||
|
@ -149,6 +149,7 @@ let profilerDefinition = {
|
||||
killswitch: "devtools.profiler.enabled",
|
||||
url: "chrome://browser/content/profiler.xul",
|
||||
label: l10n("profiler.label", profilerStrings),
|
||||
icon: "chrome://browser/skin/devtools/tool-profiler.png",
|
||||
tooltip: l10n("profiler.tooltip", profilerStrings),
|
||||
|
||||
isTargetSupported: function (target) {
|
||||
|
@ -383,8 +383,10 @@ StyleEditorChrome.prototype = {
|
||||
} else {
|
||||
// If a line or column was specified we move the caret appropriately.
|
||||
aEditor.sourceEditor.setCaretPosition(line - 1, col - 1);
|
||||
self._styleSheetToSelect = null;
|
||||
this._styleSheetToSelect = null;
|
||||
}
|
||||
} else {
|
||||
this._styleSheetToSelect = null;
|
||||
}
|
||||
|
||||
this._view.activeSummary = summary;
|
||||
|
@ -473,9 +473,6 @@
|
||||
@BINPATH@/components/HealthReportComponents.manifest
|
||||
@BINPATH@/components/HealthReportService.js
|
||||
#endif
|
||||
#ifdef MOZ_SERVICES_NOTIFICATIONS
|
||||
@BINPATH@/components/NotificationsComponents.manifest
|
||||
#endif
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
@BINPATH@/components/SyncComponents.manifest
|
||||
@BINPATH@/components/Weave.js
|
||||
@ -596,9 +593,6 @@
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
@BINPATH@/defaults/pref/services-sync.js
|
||||
#endif
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
@BINPATH@/defaults/pref/healthreport-prefs.js
|
||||
#endif
|
||||
|
||||
; [Layout Engine Resources]
|
||||
; Style Sheets, Graphics and other Resources used by the layout engine.
|
||||
|
@ -900,6 +900,7 @@ xpicleanup@BIN_SUFFIX@
|
||||
components/interfaces.manifest
|
||||
components/jsconsole-clhandler.js
|
||||
components/NetworkGeolocationProvider.js
|
||||
components/NotificationsComponents.manifest
|
||||
components/nsBadCertHandler.js
|
||||
components/nsBlocklistService.js
|
||||
components/nsBrowserContentHandler.js
|
||||
@ -1056,6 +1057,7 @@ xpicleanup@BIN_SUFFIX@
|
||||
modules/services-common/rest.js
|
||||
modules/services-common/stringbundle.js
|
||||
modules/services-common/utils.js
|
||||
modules/services-notifications/service.js
|
||||
modules/services-sync/auth.js
|
||||
modules/services-sync/base_records/collection.js
|
||||
modules/services-sync/base_records/crypto.js
|
||||
|
Before Width: | Height: | Size: 487 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 536 B After Width: | Height: | Size: 1.4 KiB |
BIN
browser/themes/gnomestripe/devtools/tool-profiler.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
@ -67,7 +67,7 @@
|
||||
.command-button:hover {
|
||||
background-color: hsla(206,37%,4%,.2);
|
||||
}
|
||||
.command-button:hover:active {
|
||||
.command-button:hover:active, .command-button[checked=true]:not(:hover) {
|
||||
background-color: hsla(206,37%,4%,.4);
|
||||
}
|
||||
|
||||
|
@ -182,6 +182,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/tool-debugger.png (devtools/tool-debugger.png)
|
||||
skin/classic/browser/devtools/tool-inspector.png (devtools/tool-inspector.png)
|
||||
skin/classic/browser/devtools/tool-styleeditor.png (devtools/tool-styleeditor.png)
|
||||
skin/classic/browser/devtools/tool-profiler.png (devtools/tool-profiler.png)
|
||||
skin/classic/browser/devtools/close.png (devtools/close.png)
|
||||
skin/classic/browser/devtools/undock.png (devtools/undock.png)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
|
Before Width: | Height: | Size: 487 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 536 B After Width: | Height: | Size: 1.4 KiB |
BIN
browser/themes/pinstripe/devtools/tool-profiler.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
@ -54,7 +54,7 @@
|
||||
.command-button:hover {
|
||||
background-color: hsla(206,37%,4%,.2);
|
||||
}
|
||||
.command-button:hover:active {
|
||||
.command-button:hover:active, .command-button[checked=true]:not(:hover) {
|
||||
background-color: hsla(206,37%,4%,.4);
|
||||
}
|
||||
|
||||
|
@ -253,6 +253,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/tool-debugger.png (devtools/tool-debugger.png)
|
||||
skin/classic/browser/devtools/tool-inspector.png (devtools/tool-inspector.png)
|
||||
skin/classic/browser/devtools/tool-styleeditor.png (devtools/tool-styleeditor.png)
|
||||
skin/classic/browser/devtools/tool-profiler.png (devtools/tool-profiler.png)
|
||||
skin/classic/browser/devtools/close.png (devtools/close.png)
|
||||
skin/classic/browser/devtools/undock.png (devtools/undock.png)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
|
Before Width: | Height: | Size: 487 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 536 B After Width: | Height: | Size: 1.4 KiB |
BIN
browser/themes/winstripe/devtools/tool-profiler.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
@ -70,7 +70,7 @@
|
||||
.command-button:hover {
|
||||
background-color: hsla(206,37%,4%,.2);
|
||||
}
|
||||
.command-button:hover:active {
|
||||
.command-button:hover:active, .command-button[checked=true]:not(:hover) {
|
||||
background-color: hsla(206,37%,4%,.4);
|
||||
}
|
||||
|
||||
|
@ -208,6 +208,7 @@ browser.jar:
|
||||
skin/classic/browser/devtools/tool-debugger.png (devtools/tool-debugger.png)
|
||||
skin/classic/browser/devtools/tool-inspector.png (devtools/tool-inspector.png)
|
||||
skin/classic/browser/devtools/tool-styleeditor.png (devtools/tool-styleeditor.png)
|
||||
skin/classic/browser/devtools/tool-profiler.png (devtools/tool-profiler.png)
|
||||
skin/classic/browser/devtools/close.png (devtools/close.png)
|
||||
skin/classic/browser/devtools/undock.png (devtools/undock.png)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
@ -429,6 +430,7 @@ browser.jar:
|
||||
skin/classic/aero/browser/devtools/tool-debugger.png (devtools/tool-debugger.png)
|
||||
skin/classic/aero/browser/devtools/tool-inspector.png (devtools/tool-inspector.png)
|
||||
skin/classic/aero/browser/devtools/tool-styleeditor.png (devtools/tool-styleeditor.png)
|
||||
skin/classic/aero/browser/devtools/tool-profiler.png (devtools/tool-profiler.png)
|
||||
skin/classic/aero/browser/devtools/close.png (devtools/close.png)
|
||||
skin/classic/aero/browser/devtools/undock.png (devtools/undock.png)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
|
@ -426,11 +426,6 @@
|
||||
@BINPATH@/defaults/autoconfig/prefcalls.js
|
||||
@BINPATH@/defaults/profile/prefs.js
|
||||
|
||||
; Services (gre) prefs
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
@BINPATH@/defaults/pref/healthreport-prefs.js
|
||||
#endif
|
||||
|
||||
; [Layout Engine Resources]
|
||||
; Style Sheets, Graphics and other Resources used by the layout engine.
|
||||
@BINPATH@/res/EditorOverride.css
|
||||
|
@ -510,9 +510,6 @@
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
@BINPATH@/defaults/pref/services-sync.js
|
||||
#endif
|
||||
#ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
@BINPATH@/defaults/pref/healthreport-prefs.js
|
||||
#endif
|
||||
|
||||
; [Layout Engine Resources]
|
||||
; Style Sheets, Graphics and other Resources used by the layout engine.
|
||||
|
@ -40,7 +40,12 @@ GARBAGE += $(addprefix $(DIST)/bin/defaults/pref/, \
|
||||
|
||||
GARBAGE += greprefs.js
|
||||
|
||||
GREPREF_FILES = $(topsrcdir)/netwerk/base/public/security-prefs.js $(srcdir)/init/all.js
|
||||
# TODO bug 813259 external files should be defined near their location in the source tree.
|
||||
grepref_files = $(topsrcdir)/netwerk/base/public/security-prefs.js $(srcdir)/init/all.js
|
||||
|
||||
ifdef MOZ_SERVICES_HEALTHREPORT
|
||||
grepref_files += $(topsrcdir)/services/healthreport/healthreport-prefs.js
|
||||
endif
|
||||
|
||||
# Optimizer bug with GCC 3.2.2 on OS/2
|
||||
ifeq ($(OS_ARCH), OS2)
|
||||
@ -51,7 +56,7 @@ nsPrefService.$(OBJ_SUFFIX): nsPrefService.cpp
|
||||
endif
|
||||
|
||||
|
||||
greprefs.js: $(GREPREF_FILES)
|
||||
greprefs.js: $(grepref_files)
|
||||
$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(PREF_PPFLAGS) $(DEFINES) $(ACDEFINES) $(XULPPFLAGS) $^ > $@
|
||||
|
||||
libs:: greprefs.js
|
||||
|
@ -25,10 +25,6 @@ ifdef MOZ_SERVICES_METRICS
|
||||
PARALLEL_DIRS += metrics
|
||||
endif
|
||||
|
||||
ifdef MOZ_SERVICES_NOTIFICATIONS
|
||||
PARALLEL_DIRS += notifications
|
||||
endif
|
||||
|
||||
ifdef MOZ_SERVICES_SYNC
|
||||
PARALLEL_DIRS += sync
|
||||
endif
|
||||
|
@ -9,7 +9,10 @@ component {e354c59b-b252-4040-b6dd-b71864e3e35c} HealthReportService.js
|
||||
contract @mozilla.org/healthreport/service;1 {e354c59b-b252-4040-b6dd-b71864e3e35c}
|
||||
category app-startup HealthReportService service,@mozilla.org/healthreport/service;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66}
|
||||
|
||||
category healthreport-js-provider AddonsProvider resource://gre/modules/services/healthreport/providers.jsm
|
||||
category healthreport-js-provider AppInfoProvider resource://gre/modules/services/healthreport/providers.jsm
|
||||
category healthreport-js-provider CrashesProvider resource://gre/modules/services/healthreport/providers.jsm
|
||||
category healthreport-js-provider SysInfoProvider resource://gre/modules/services/healthreport/providers.jsm
|
||||
category healthreport-js-provider ProfileMetadataProvider resource://gre/modules/services/healthreport/profile.jsm
|
||||
category healthreport-js-provider SessionsProvider resource://gre/modules/services/healthreport/providers.jsm
|
||||
|
||||
|
@ -10,25 +10,46 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-common/preferences.js");
|
||||
|
||||
|
||||
const INITIAL_STARTUP_DELAY_MSEC = 10 * 1000;
|
||||
const BRANCH = "healthreport.";
|
||||
const JS_PROVIDERS_CATEGORY = "healthreport-js-provider";
|
||||
|
||||
const DEFAULT_LOAD_DELAY_MSEC = 10 * 1000;
|
||||
|
||||
/**
|
||||
* The Firefox Health Report XPCOM service.
|
||||
*
|
||||
* This instantiates an instance of HealthReporter (assuming it is enabled)
|
||||
* and starts it upon application startup.
|
||||
* External consumers will be interested in the "reporter" property of this
|
||||
* service. This property is a `HealthReporter` instance that powers the
|
||||
* service. The property may be null if the Health Report service is not
|
||||
* enabled.
|
||||
*
|
||||
* One can obtain a reference to the underlying HealthReporter instance by
|
||||
* accessing .reporter. If this property is null, the reporter isn't running
|
||||
* yet or has been disabled.
|
||||
* EXAMPLE USAGE
|
||||
* =============
|
||||
*
|
||||
* let reporter = Cc["@mozilla.org/healthreport/service;1"]
|
||||
* .getService(Ci.nsISupports)
|
||||
* .wrappedJSObject
|
||||
* .reporter;
|
||||
*
|
||||
* if (reporter.haveRemoteData) {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* IMPLEMENTATION NOTES
|
||||
* ====================
|
||||
*
|
||||
* In order to not adversely impact application start time, the `HealthReporter`
|
||||
* instance is not initialized until a few seconds after "final-ui-startup."
|
||||
* The exact delay is configurable via preferences so it can be adjusted with
|
||||
* a hotfix extension if the default value is ever problematic.
|
||||
*
|
||||
* Shutdown of the `HealthReporter` instance is handled completely within the
|
||||
* instance (it registers observers on initialization). See the notes on that
|
||||
* type for more.
|
||||
*/
|
||||
this.HealthReportService = function HealthReportService() {
|
||||
this.wrappedJSObject = this;
|
||||
|
||||
this.prefs = new Preferences(BRANCH);
|
||||
this._prefs = new Preferences(BRANCH);
|
||||
|
||||
this._reporter = null;
|
||||
}
|
||||
|
||||
@ -40,7 +61,7 @@ HealthReportService.prototype = {
|
||||
|
||||
observe: function observe(subject, topic, data) {
|
||||
// If the background service is disabled, don't do anything.
|
||||
if (!this.prefs.get("serviceEnabled", true)) {
|
||||
if (!this._prefs.get("service.enabled", true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -54,7 +75,9 @@ HealthReportService.prototype = {
|
||||
|
||||
case "final-ui-startup":
|
||||
os.removeObserver(this, "final-ui-startup");
|
||||
os.addObserver(this, "quit-application", true);
|
||||
|
||||
let delayInterval = this._prefs.get("service.loadDelayMsec") ||
|
||||
DEFAULT_LOAD_DELAY_MSEC;
|
||||
|
||||
// Delay service loading a little more so things have an opportunity
|
||||
// to cool down first.
|
||||
@ -66,25 +89,21 @@ HealthReportService.prototype = {
|
||||
let reporter = this.reporter;
|
||||
delete this.timer;
|
||||
}.bind(this),
|
||||
}, INITIAL_STARTUP_DELAY_MSEC, this.timer.TYPE_ONE_SHOT);
|
||||
}, delayInterval, this.timer.TYPE_ONE_SHOT);
|
||||
|
||||
break;
|
||||
|
||||
case "quit-application-granted":
|
||||
if (this.reporter) {
|
||||
this.reporter.stop();
|
||||
}
|
||||
|
||||
os.removeObserver(this, "quit-application");
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The HealthReporter instance associated with this service.
|
||||
*
|
||||
* If the service is disabled, this will return null.
|
||||
*
|
||||
* The obtained instance may not be fully initialized.
|
||||
*/
|
||||
get reporter() {
|
||||
if (!this.prefs.get("serviceEnabled", true)) {
|
||||
if (!this._prefs.get("service.enabled", true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -92,18 +111,19 @@ HealthReportService.prototype = {
|
||||
return this._reporter;
|
||||
}
|
||||
|
||||
// Lazy import so application startup isn't adversely affected.
|
||||
let ns = {};
|
||||
Cu.import("resource://services-common/log4moz.js", ns);
|
||||
// Lazy import so application startup isn't adversely affected.
|
||||
Cu.import("resource://gre/modules/Task.jsm", ns);
|
||||
Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm", ns);
|
||||
Cu.import("resource://services-common/log4moz.js", ns);
|
||||
|
||||
// How many times will we rewrite this code before rolling it up into a
|
||||
// generic module? See also bug 451283.
|
||||
const LOGGERS = [
|
||||
"Metrics",
|
||||
"Services.HealthReport",
|
||||
"Services.Metrics",
|
||||
"Services.BagheeraClient",
|
||||
"Sqlite.Connection.healthreport",
|
||||
];
|
||||
|
||||
let prefs = new Preferences(BRANCH + "logging.");
|
||||
@ -118,9 +138,8 @@ HealthReportService.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
// The reporter initializes in the background.
|
||||
this._reporter = new ns.HealthReporter(BRANCH);
|
||||
this._reporter.registerProvidersFromCategoryManager(JS_PROVIDERS_CATEGORY);
|
||||
this._reporter.start();
|
||||
|
||||
return this._reporter;
|
||||
},
|
||||
|
@ -18,6 +18,7 @@ modules := \
|
||||
|
||||
testing_modules := \
|
||||
mocks.jsm \
|
||||
utils.jsm \
|
||||
$(NULL)
|
||||
|
||||
TEST_DIRS += tests
|
||||
@ -34,6 +35,4 @@ EXTRA_COMPONENTS := \
|
||||
HealthReportService.js \
|
||||
$(NULL)
|
||||
|
||||
PREF_JS_EXPORTS := healthreport-prefs.js
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
pref("healthreport.documentServerURI", "https://data.mozilla.com/");
|
||||
pref("healthreport.documentServerNamespace", "metrics");
|
||||
pref("healthreport.serviceEnabled", false);
|
||||
pref("healthreport.logging.consoleEnabled", true);
|
||||
pref("healthreport.logging.consoleLevel", "Warn");
|
||||
pref("healthreport.policy.currentDaySubmissionFailureCount", 0);
|
||||
@ -19,4 +18,7 @@ pref("healthreport.policy.lastDataSubmissionFailureTime", "0");
|
||||
pref("healthreport.policy.lastDataSubmissionRequestedTime", "0");
|
||||
pref("healthreport.policy.lastDataSubmissionSuccessfulTime", "0");
|
||||
pref("healthreport.policy.nextDataSubmissionTime", "0");
|
||||
pref("healthreport.service.enabled", true);
|
||||
pref("healthreport.service.loadDelayMsec", 10000);
|
||||
pref("healthreport.service.providerCategories", "healthreport-js-provider");
|
||||
|
||||
|
@ -8,24 +8,33 @@ this.EXPORTED_SYMBOLS = ["HealthReporter"];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://services-common/async.js");
|
||||
Cu.import("resource://services-common/bagheeraclient.js");
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
Cu.import("resource://services-common/observers.js");
|
||||
Cu.import("resource://services-common/preferences.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/services/healthreport/policy.jsm");
|
||||
Cu.import("resource://gre/modules/services/metrics/collector.jsm");
|
||||
|
||||
|
||||
// Oldest year to allow in date preferences. This module was implemented in
|
||||
// 2012 and no dates older than that should be encountered.
|
||||
const OLDEST_ALLOWED_YEAR = 2012;
|
||||
|
||||
const DAYS_IN_PAYLOAD = 180;
|
||||
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
const DEFAULT_DATABASE_NAME = "healthreport.sqlite";
|
||||
|
||||
|
||||
/**
|
||||
* Coordinates collection and submission of metrics.
|
||||
* Coordinates collection and submission of health report metrics.
|
||||
*
|
||||
* This is the main type for Firefox Health Report. It glues all the
|
||||
* lower-level components (such as collection and submission) together.
|
||||
@ -39,23 +48,50 @@ const OLDEST_ALLOWED_YEAR = 2012;
|
||||
* this type and *the* Firefox Health Report (e.g. the policy). This could
|
||||
* be abstracted if needed.
|
||||
*
|
||||
* IMPLEMENTATION NOTES
|
||||
* ====================
|
||||
*
|
||||
* Initialization and shutdown are somewhat complicated and worth explaining
|
||||
* in extra detail.
|
||||
*
|
||||
* The complexity is driven by the requirements of SQLite connection management.
|
||||
* Once you have a SQLite connection, it isn't enough to just let the
|
||||
* application shut down. If there is an open connection or if there are
|
||||
* outstanding SQL statements come XPCOM shutdown time, Storage will assert.
|
||||
* On debug builds you will crash. On release builds you will get a shutdown
|
||||
* hang. This must be avoided!
|
||||
*
|
||||
* During initialization, the second we create a SQLite connection (via
|
||||
* Metrics.Storage) we register observers for application shutdown. The
|
||||
* "quit-application" notification initiates our shutdown procedure. The
|
||||
* subsequent "profile-do-change" notification ensures it has completed.
|
||||
*
|
||||
* The handler for "profile-do-change" may result in event loop spinning. This
|
||||
* is because of race conditions between our shutdown code and application
|
||||
* shutdown.
|
||||
*
|
||||
* All of our shutdown routines are async. There is the potential that these
|
||||
* async functions will not complete before XPCOM shutdown. If they don't
|
||||
* finish in time, we could get assertions in Storage. Our solution is to
|
||||
* initiate storage early in the shutdown cycle ("quit-application").
|
||||
* Hopefully all the async operations have completed by the time we reach
|
||||
* "profile-do-change." If so, great. If not, we spin the event loop until
|
||||
* they have completed, avoiding potential race conditions.
|
||||
*
|
||||
* @param branch
|
||||
* (string) The preferences branch to use for state storage. The value
|
||||
* must end with a period (.).
|
||||
*/
|
||||
this.HealthReporter = function HealthReporter(branch) {
|
||||
function HealthReporter(branch) {
|
||||
if (!branch.endsWith(".")) {
|
||||
throw new Error("Branch argument must end with a period (.): " + branch);
|
||||
throw new Error("Branch must end with a period (.): " + branch);
|
||||
}
|
||||
|
||||
this._log = Log4Moz.repository.getLogger("Services.HealthReport.HealthReporter");
|
||||
this._log.info("Initializing health reporter instance against " + branch);
|
||||
|
||||
this._prefs = new Preferences(branch);
|
||||
|
||||
let policyBranch = new Preferences(branch + "policy.");
|
||||
this._policy = new HealthReportPolicy(policyBranch, this);
|
||||
this._collector = new MetricsCollector();
|
||||
|
||||
if (!this.serverURI) {
|
||||
throw new Error("No server URI defined. Did you forget to define the pref?");
|
||||
}
|
||||
@ -63,9 +99,42 @@ this.HealthReporter = function HealthReporter(branch) {
|
||||
if (!this.serverNamespace) {
|
||||
throw new Error("No server namespace defined. Did you forget a pref?");
|
||||
}
|
||||
|
||||
this._dbName = this._prefs.get("dbName") || DEFAULT_DATABASE_NAME;
|
||||
|
||||
let policyBranch = new Preferences(branch + "policy.");
|
||||
this._policy = new HealthReportPolicy(policyBranch, this);
|
||||
|
||||
this._storage = null;
|
||||
this._storageInProgress = false;
|
||||
this._collector = null;
|
||||
this._collectorInProgress = false;
|
||||
this._initialized = false;
|
||||
this._initializeHadError = false;
|
||||
this._initializedDeferred = Promise.defer();
|
||||
this._shutdownRequested = false;
|
||||
this._shutdownInitiated = false;
|
||||
this._shutdownComplete = false;
|
||||
this._shutdownCompleteCallback = null;
|
||||
|
||||
this._ensureDirectoryExists(this._stateDir)
|
||||
.then(this._onStateDirCreated.bind(this),
|
||||
this._onInitError.bind(this));
|
||||
|
||||
}
|
||||
|
||||
HealthReporter.prototype = {
|
||||
HealthReporter.prototype = Object.freeze({
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||
|
||||
/**
|
||||
* Whether the service is fully initialized and running.
|
||||
*
|
||||
* If this is false, it is not safe to call most functions.
|
||||
*/
|
||||
get initialized() {
|
||||
return this._initialized;
|
||||
},
|
||||
|
||||
/**
|
||||
* When we last successfully submitted data to the server.
|
||||
*
|
||||
@ -146,48 +215,268 @@ HealthReporter.prototype = {
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
haveRemoteData: function haveRemoteData() {
|
||||
haveRemoteData: function () {
|
||||
return !!this.lastSubmitID;
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform post-construction initialization and start background activity.
|
||||
*
|
||||
* If this isn't called, no data upload will occur.
|
||||
*
|
||||
* This returns a promise that will be fulfilled when all initialization
|
||||
* activity is completed. It is not safe for this instance to perform
|
||||
* additional actions until this promise has been resolved.
|
||||
*/
|
||||
start: function start() {
|
||||
let onExists = function onExists() {
|
||||
this._policy.startPolling();
|
||||
this._log.info("HealthReporter started.");
|
||||
//----------------------------------------------------
|
||||
// SERVICE CONTROL FUNCTIONS
|
||||
//
|
||||
// You shouldn't need to call any of these externally.
|
||||
//----------------------------------------------------
|
||||
|
||||
return Promise.resolve();
|
||||
}.bind(this);
|
||||
_onInitError: function (error) {
|
||||
this._log.error("Error during initialization: " +
|
||||
CommonUtils.exceptionStr(error));
|
||||
this._initializeHadError = true;
|
||||
this._initiateShutdown();
|
||||
this._initializedDeferred.reject(error);
|
||||
|
||||
return this._ensureDirectoryExists(this._stateDir)
|
||||
.then(onExists);
|
||||
// FUTURE consider poisoning prototype's functions so calls fail with a
|
||||
// useful error message.
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop background functionality.
|
||||
*/
|
||||
stop: function stop() {
|
||||
_onStateDirCreated: function () {
|
||||
// As soon as we have could storage, we need to register cleanup or
|
||||
// else bad things happen on shutdown.
|
||||
Services.obs.addObserver(this, "quit-application", false);
|
||||
Services.obs.addObserver(this, "profile-before-change", false);
|
||||
|
||||
this._storageInProgress = true;
|
||||
Metrics.Storage(this._dbName).then(this._onStorageCreated.bind(this),
|
||||
this._onInitError.bind(this));
|
||||
},
|
||||
|
||||
// Called when storage has been opened.
|
||||
_onStorageCreated: function (storage) {
|
||||
this._log.info("Storage initialized.");
|
||||
this._storage = storage;
|
||||
this._storageInProgress = false;
|
||||
|
||||
if (this._shutdownRequested) {
|
||||
this._initiateShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
Task.spawn(this._initializeCollector.bind(this))
|
||||
.then(this._onCollectorInitialized.bind(this),
|
||||
this._onInitError.bind(this));
|
||||
},
|
||||
|
||||
_initializeCollector: function () {
|
||||
if (this._collector) {
|
||||
throw new Error("Collector has already been initialized.");
|
||||
}
|
||||
|
||||
this._log.info("Initializing collector.");
|
||||
this._collector = new Metrics.Collector(this._storage);
|
||||
this._collectorInProgress = true;
|
||||
|
||||
let catString = this._prefs.get("service.providerCategories") || "";
|
||||
if (catString.length) {
|
||||
for (let category of catString.split(",")) {
|
||||
yield this.registerProvidersFromCategoryManager(category);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onCollectorInitialized: function () {
|
||||
this._log.debug("Collector initialized.");
|
||||
this._collectorInProgress = false;
|
||||
|
||||
if (this._shutdownRequested) {
|
||||
this._initiateShutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
this._policy.startPolling();
|
||||
this._log.info("HealthReporter started.");
|
||||
this._initialized = true;
|
||||
Services.obs.addObserver(this, "idle-daily", false);
|
||||
this._initializedDeferred.resolve(this);
|
||||
},
|
||||
|
||||
// nsIObserver to handle shutdown.
|
||||
observe: function (subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "quit-application":
|
||||
Services.obs.removeObserver(this, "quit-application");
|
||||
this._initiateShutdown();
|
||||
break;
|
||||
|
||||
case "profile-before-change":
|
||||
Services.obs.removeObserver(this, "profile-before-change");
|
||||
this._waitForShutdown();
|
||||
break;
|
||||
|
||||
case "idle-daily":
|
||||
this._performDailyMaintenance();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_initiateShutdown: function () {
|
||||
// Ensure we only begin the main shutdown sequence once.
|
||||
if (this._shutdownInitiated) {
|
||||
this._log.warn("Shutdown has already been initiated. No-op.");
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.info("Request to shut down.");
|
||||
|
||||
this._initialized = false;
|
||||
this._shutdownRequested = true;
|
||||
|
||||
// Safe to call multiple times.
|
||||
this._policy.stopPolling();
|
||||
|
||||
if (this._collectorInProgress) {
|
||||
this._log.warn("Collector is in progress of initializing. Waiting to finish.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If storage is in the process of initializing, we need to wait for it
|
||||
// to finish before continuing. The initialization process will call us
|
||||
// again once storage has initialized.
|
||||
if (this._storageInProgress) {
|
||||
this._log.warn("Storage is in progress of initializing. Waiting to finish.");
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.warn("Initiating main shutdown procedure.");
|
||||
|
||||
// Everything from here must only be performed once or else race conditions
|
||||
// could occur.
|
||||
this._shutdownInitiated = true;
|
||||
|
||||
if (this._initialized) {
|
||||
Services.obs.removeObserver(this, "idle-daily");
|
||||
}
|
||||
|
||||
// If we have collectors, we need to shut down providers.
|
||||
if (this._collector) {
|
||||
let onShutdown = this._onCollectorShutdown.bind(this);
|
||||
Task.spawn(this._shutdownCollector.bind(this))
|
||||
.then(onShutdown, onShutdown);
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.warn("Don't have collector. Proceeding to storage shutdown.");
|
||||
this._shutdownStorage();
|
||||
},
|
||||
|
||||
_shutdownCollector: function () {
|
||||
this._log.info("Shutting down collector.");
|
||||
for (let provider of this._collector.providers) {
|
||||
try {
|
||||
yield provider.shutdown();
|
||||
} catch (ex) {
|
||||
this._log.warn("Error when shutting down provider: " +
|
||||
CommonUtils.exceptionStr(ex));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onCollectorShutdown: function () {
|
||||
this._log.info("Collector shut down.");
|
||||
this._collector = null;
|
||||
this._shutdownStorage();
|
||||
},
|
||||
|
||||
_shutdownStorage: function () {
|
||||
if (!this._storage) {
|
||||
this._onShutdownComplete();
|
||||
}
|
||||
|
||||
this._log.info("Shutting down storage.");
|
||||
let onClose = this._onStorageClose.bind(this);
|
||||
this._storage.close().then(onClose, onClose);
|
||||
},
|
||||
|
||||
_onStorageClose: function (error) {
|
||||
this._log.info("Storage has been closed.");
|
||||
|
||||
if (error) {
|
||||
this._log.warn("Error when closing storage: " +
|
||||
CommonUtils.exceptionStr(error));
|
||||
}
|
||||
|
||||
this._storage = null;
|
||||
this._onShutdownComplete();
|
||||
},
|
||||
|
||||
_onShutdownComplete: function () {
|
||||
this._log.warn("Shutdown complete.");
|
||||
this._shutdownComplete = true;
|
||||
|
||||
if (this._shutdownCompleteCallback) {
|
||||
this._shutdownCompleteCallback();
|
||||
}
|
||||
},
|
||||
|
||||
_waitForShutdown: function () {
|
||||
if (this._shutdownComplete) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._shutdownCompleteCallback = Async.makeSpinningCallback();
|
||||
this._shutdownCompleteCallback.wait();
|
||||
this._shutdownCompleteCallback = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a `MetricsProvider` with this instance.
|
||||
* Convenience method to shut down the instance.
|
||||
*
|
||||
* This should *not* be called outside of tests.
|
||||
*/
|
||||
_shutdown: function () {
|
||||
this._initiateShutdown();
|
||||
this._waitForShutdown();
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a promise that is resolved once the service has been initialized.
|
||||
*/
|
||||
onInit: function () {
|
||||
if (this._initializeHadError) {
|
||||
throw new Error("Service failed to initialize.");
|
||||
}
|
||||
|
||||
if (this._initialized) {
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
|
||||
return this._initializedDeferred.promise;
|
||||
},
|
||||
|
||||
_performDailyMaintenance: function () {
|
||||
this._log.info("Request to perform daily maintenance.");
|
||||
|
||||
if (!this._initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
let now = new Date();
|
||||
let cutoff = new Date(now.getTime() - MILLISECONDS_PER_DAY * (DAYS_IN_PAYLOAD - 1));
|
||||
|
||||
// The operation is enqueued and put in a transaction by the storage module.
|
||||
this._storage.pruneDataBefore(cutoff);
|
||||
},
|
||||
|
||||
//--------------------
|
||||
// Provider Management
|
||||
//--------------------
|
||||
|
||||
/**
|
||||
* Register a `Metrics.Provider` with this instance.
|
||||
*
|
||||
* This needs to be called or no data will be collected. See also
|
||||
* registerProvidersFromCategoryManager`.
|
||||
*
|
||||
* @param provider
|
||||
* (MetricsProvider) The provider to register for collection.
|
||||
* (Metrics.Provider) The provider to register for collection.
|
||||
*/
|
||||
registerProvider: function registerProvider(provider) {
|
||||
registerProvider: function (provider) {
|
||||
return this._collector.registerProvider(provider);
|
||||
},
|
||||
|
||||
@ -198,7 +487,7 @@ HealthReporter.prototype = {
|
||||
* providers.
|
||||
*
|
||||
* Category entries are essentially JS modules and the name of the symbol
|
||||
* within that module that is a `MetricsProvider` instance.
|
||||
* within that module that is a `Metrics.Provider` instance.
|
||||
*
|
||||
* The category entry name is the name of the JS type for the provider. The
|
||||
* value is the resource:// URI to import which makes this type available.
|
||||
@ -213,18 +502,18 @@ HealthReporter.prototype = {
|
||||
*
|
||||
* Then to load them:
|
||||
*
|
||||
* let reporter = new HealthReporter("healthreport.");
|
||||
* let reporter = getHealthReporter("healthreport.");
|
||||
* reporter.registerProvidersFromCategoryManager("healthreport-js-provider");
|
||||
*
|
||||
* @param category
|
||||
* (string) Name of category to query and load from.
|
||||
*/
|
||||
registerProvidersFromCategoryManager:
|
||||
function registerProvidersFromCategoryManager(category) {
|
||||
|
||||
registerProvidersFromCategoryManager: function (category) {
|
||||
this._log.info("Registering providers from category: " + category);
|
||||
let cm = Cc["@mozilla.org/categorymanager;1"]
|
||||
.getService(Ci.nsICategoryManager);
|
||||
|
||||
let promises = [];
|
||||
let enumerator = cm.enumerateCategory(category);
|
||||
while (enumerator.hasMoreElements()) {
|
||||
let entry = enumerator.getNext()
|
||||
@ -240,20 +529,26 @@ HealthReporter.prototype = {
|
||||
Cu.import(uri, ns);
|
||||
|
||||
let provider = new ns[entry]();
|
||||
this.registerProvider(provider);
|
||||
promises.push(this.registerProvider(provider));
|
||||
} catch (ex) {
|
||||
this._log.warn("Error registering provider from category manager: " +
|
||||
entry + "; " + CommonUtils.exceptionStr(ex));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return Task.spawn(function wait() {
|
||||
for (let promise of promises) {
|
||||
yield promise;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Collect all measurements for all registered providers.
|
||||
*/
|
||||
collectMeasurements: function collectMeasurements() {
|
||||
return this._collector.collectConstantMeasurements();
|
||||
collectMeasurements: function () {
|
||||
return this._collector.collectConstantData();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -264,7 +559,7 @@ HealthReporter.prototype = {
|
||||
* @param reason
|
||||
* (string) Why data submission is being disabled.
|
||||
*/
|
||||
recordPolicyRejection: function recordPolicyRejection(reason) {
|
||||
recordPolicyRejection: function (reason) {
|
||||
this._policy.recordUserRejection(reason);
|
||||
},
|
||||
|
||||
@ -276,7 +571,7 @@ HealthReporter.prototype = {
|
||||
* @param reason
|
||||
* (string) Why data submission is being enabled.
|
||||
*/
|
||||
recordPolicyAcceptance: function recordPolicyAcceptance(reason) {
|
||||
recordPolicyAcceptance: function (reason) {
|
||||
this._policy.recordUserAcceptance(reason);
|
||||
},
|
||||
|
||||
@ -306,7 +601,7 @@ HealthReporter.prototype = {
|
||||
* callers should poll haveRemoteData() to determine when remote data is
|
||||
* deleted.
|
||||
*/
|
||||
requestDeleteRemoteData: function requestDeleteRemoteData(reason) {
|
||||
requestDeleteRemoteData: function (reason) {
|
||||
if (!this.lastSubmitID) {
|
||||
return;
|
||||
}
|
||||
@ -314,26 +609,110 @@ HealthReporter.prototype = {
|
||||
return this._policy.deleteRemoteData(reason);
|
||||
},
|
||||
|
||||
getJSONPayload: function getJSONPayload() {
|
||||
getJSONPayload: function () {
|
||||
return Task.spawn(this._getJSONPayload.bind(this, this._now()));
|
||||
},
|
||||
|
||||
_getJSONPayload: function (now) {
|
||||
let pingDateString = this._formatDate(now);
|
||||
this._log.info("Producing JSON payload for " + pingDateString);
|
||||
|
||||
let o = {
|
||||
version: 1,
|
||||
thisPingDate: this._formatDate(this._now()),
|
||||
providers: {},
|
||||
thisPingDate: pingDateString,
|
||||
data: {last: {}, days: {}},
|
||||
};
|
||||
|
||||
let outputDataDays = o.data.days;
|
||||
|
||||
// We need to be careful that data in errors does not leak potentially
|
||||
// private information.
|
||||
// FUTURE ask Privacy if we can put exception stacks in here.
|
||||
let errors = [];
|
||||
|
||||
let lastPingDate = this.lastPingDate;
|
||||
if (lastPingDate.getTime() > 0) {
|
||||
o.lastPingDate = this._formatDate(lastPingDate);
|
||||
}
|
||||
|
||||
for (let [name, provider] of this._collector.collectionResults) {
|
||||
o.providers[name] = provider;
|
||||
for (let provider of this._collector.providers) {
|
||||
let providerName = provider.name;
|
||||
|
||||
let providerEntry = {
|
||||
measurements: {},
|
||||
};
|
||||
|
||||
for (let [measurementKey, measurement] of provider.measurements) {
|
||||
let name = providerName + "." + measurement.name + "." + measurement.version;
|
||||
|
||||
let serializer;
|
||||
try {
|
||||
serializer = measurement.serializer(measurement.SERIALIZE_JSON);
|
||||
} catch (ex) {
|
||||
this._log.warn("Error obtaining serializer for measurement: " + name +
|
||||
": " + CommonUtils.exceptionStr(ex));
|
||||
errors.push("Could not obtain serializer: " + name);
|
||||
continue;
|
||||
}
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = yield this._storage.getMeasurementValues(measurement.id);
|
||||
} catch (ex) {
|
||||
this._log.warn("Error obtaining data for measurement: " +
|
||||
name + ": " + CommonUtils.exceptionStr(ex));
|
||||
errors.push("Could not obtain data: " + name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data.singular.size) {
|
||||
try {
|
||||
o.data.last[name] = serializer.singular(data.singular);
|
||||
} catch (ex) {
|
||||
this._log.warn("Error serializing data: " + CommonUtils.exceptionStr(ex));
|
||||
errors.push("Error serializing singular: " + name);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let dataDays = data.days;
|
||||
for (let i = 0; i < DAYS_IN_PAYLOAD; i++) {
|
||||
let date = new Date(now.getTime() - i * MILLISECONDS_PER_DAY);
|
||||
if (!dataDays.hasDay(date)) {
|
||||
continue;
|
||||
}
|
||||
let dateFormatted = this._formatDate(date);
|
||||
|
||||
try {
|
||||
let serialized = serializer.daily(dataDays.getDay(date));
|
||||
if (!serialized) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(dateFormatted in outputDataDays)) {
|
||||
outputDataDays[dateFormatted] = {};
|
||||
}
|
||||
|
||||
outputDataDays[dateFormatted][name] = serialized;
|
||||
} catch (ex) {
|
||||
this._log.warn("Error populating data for day: " +
|
||||
CommonUtils.exceptionStr(ex));
|
||||
errors.push("Could not serialize day: " + name +
|
||||
" ( " + dateFormatted + ")");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(o);
|
||||
if (errors.length) {
|
||||
o.errors = errors;
|
||||
}
|
||||
|
||||
throw new Task.Result(JSON.stringify(o));
|
||||
},
|
||||
|
||||
_onBagheeraResult: function _onBagheeraResult(request, isDelete, result) {
|
||||
_onBagheeraResult: function (request, isDelete, result) {
|
||||
this._log.debug("Received Bagheera result.");
|
||||
|
||||
let promise = Promise.resolve(null);
|
||||
@ -362,36 +741,34 @@ HealthReporter.prototype = {
|
||||
return promise;
|
||||
},
|
||||
|
||||
_onSubmitDataRequestFailure: function _onSubmitDataRequestFailure(error) {
|
||||
_onSubmitDataRequestFailure: function (error) {
|
||||
this._log.error("Error processing request to submit data: " +
|
||||
CommonUtils.exceptionStr(error));
|
||||
},
|
||||
|
||||
_formatDate: function _formatDate(date) {
|
||||
_formatDate: function (date) {
|
||||
// Why, oh, why doesn't JS have a strftime() equivalent?
|
||||
return date.toISOString().substr(0, 10);
|
||||
},
|
||||
|
||||
|
||||
_uploadData: function _uploadData(request) {
|
||||
_uploadData: function (request) {
|
||||
let id = CommonUtils.generateUUID();
|
||||
|
||||
this._log.info("Uploading data to server: " + this.serverURI + " " +
|
||||
this.serverNamespace + ":" + id);
|
||||
let client = new BagheeraClient(this.serverURI);
|
||||
|
||||
let payload = this.getJSONPayload();
|
||||
|
||||
return this._saveLastPayload(payload)
|
||||
.then(client.uploadJSON.bind(client,
|
||||
this.serverNamespace,
|
||||
id,
|
||||
payload,
|
||||
this.lastSubmitID))
|
||||
.then(this._onBagheeraResult.bind(this, request, false));
|
||||
return Task.spawn(function doUpload() {
|
||||
let payload = yield this.getJSONPayload();
|
||||
yield this._saveLastPayload(payload);
|
||||
let result = yield client.uploadJSON(this.serverNamespace, id, payload,
|
||||
this.lastSubmitID);
|
||||
yield this._onBagheeraResult(request, false, result);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_deleteRemoteData: function _deleteRemoteData(request) {
|
||||
_deleteRemoteData: function (request) {
|
||||
if (!this.lastSubmitID) {
|
||||
this._log.info("Received request to delete remote data but no data stored.");
|
||||
request.onNoDataAvailable();
|
||||
@ -419,7 +796,7 @@ HealthReporter.prototype = {
|
||||
return OS.Path.join(profD, "healthreport");
|
||||
},
|
||||
|
||||
_ensureDirectoryExists: function _ensureDirectoryExists(path) {
|
||||
_ensureDirectoryExists: function (path) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
OS.File.makeDir(path).then(
|
||||
@ -443,7 +820,7 @@ HealthReporter.prototype = {
|
||||
return OS.Path.join(this._stateDir, "lastpayload.json");
|
||||
},
|
||||
|
||||
_saveLastPayload: function _saveLastPayload(payload) {
|
||||
_saveLastPayload: function (payload) {
|
||||
let path = this._lastPayloadPath;
|
||||
let pathTmp = path + ".tmp";
|
||||
|
||||
@ -462,7 +839,7 @@ HealthReporter.prototype = {
|
||||
*
|
||||
* @return Promise<object>
|
||||
*/
|
||||
getLastPayload: function getLoadPayload() {
|
||||
getLastPayload: function () {
|
||||
let path = this._lastPayloadPath;
|
||||
|
||||
return OS.File.read(path).then(
|
||||
@ -486,26 +863,24 @@ HealthReporter.prototype = {
|
||||
// HealthReportPolicy listeners
|
||||
//-----------------------------
|
||||
|
||||
onRequestDataUpload: function onRequestDataSubmission(request) {
|
||||
onRequestDataUpload: function (request) {
|
||||
this.collectMeasurements()
|
||||
.then(this._uploadData.bind(this, request),
|
||||
this._onSubmitDataRequestFailure.bind(this));
|
||||
},
|
||||
|
||||
onNotifyDataPolicy: function onNotifyDataPolicy(request) {
|
||||
onNotifyDataPolicy: function (request) {
|
||||
// This isn't very loosely coupled. We may want to have this call
|
||||
// registered listeners instead.
|
||||
Observers.notify("healthreport:notify-data-policy:request", request);
|
||||
},
|
||||
|
||||
onRequestRemoteDelete: function onRequestRemoteDelete(request) {
|
||||
onRequestRemoteDelete: function (request) {
|
||||
this._deleteRemoteData(request);
|
||||
},
|
||||
|
||||
//------------------------------------
|
||||
// End of HealthReportPolicy listeners
|
||||
//------------------------------------
|
||||
};
|
||||
|
||||
Object.freeze(HealthReporter.prototype);
|
||||
});
|
||||
|
||||
|
259
services/healthreport/modules-testing/utils.jsm
Normal file
@ -0,0 +1,259 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"getAppInfo",
|
||||
"updateAppInfo",
|
||||
"makeFakeAppDir",
|
||||
"createFakeCrash",
|
||||
"InspectedHealthReporter",
|
||||
];
|
||||
|
||||
|
||||
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm");
|
||||
|
||||
|
||||
let APP_INFO = {
|
||||
vendor: "Mozilla",
|
||||
name: "xpcshell",
|
||||
ID: "xpcshell@tests.mozilla.org",
|
||||
version: "1",
|
||||
appBuildID: "20121107",
|
||||
platformVersion: "p-ver",
|
||||
platformBuildID: "20121106",
|
||||
inSafeMode: false,
|
||||
logConsoleErrors: true,
|
||||
OS: "XPCShell",
|
||||
XPCOMABI: "noarch-spidermonkey",
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo, Ci.nsIXULRuntime]),
|
||||
invalidateCachesOnRestart: function() {},
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Obtain a reference to the current object used to define XULAppInfo.
|
||||
*/
|
||||
this.getAppInfo = function () { return APP_INFO; }
|
||||
|
||||
/**
|
||||
* Update the current application info.
|
||||
*
|
||||
* If the argument is defined, it will be the object used. Else, APP_INFO is
|
||||
* used.
|
||||
*
|
||||
* To change the current XULAppInfo, simply call this function. If there was
|
||||
* a previously registered app info object, it will be unloaded and replaced.
|
||||
*/
|
||||
this.updateAppInfo = function (obj) {
|
||||
obj = obj || APP_INFO;
|
||||
APP_INFO = obj;
|
||||
|
||||
let id = Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}");
|
||||
let cid = "@mozilla.org/xre/app-info;1";
|
||||
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
|
||||
// Unregister an existing factory if one exists.
|
||||
try {
|
||||
let existing = Components.manager.getClassObjectByContractID(cid, Ci.nsIFactory);
|
||||
registrar.unregisterFactory(id, existing);
|
||||
} catch (ex) {}
|
||||
|
||||
let factory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer != null) {
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
|
||||
return obj.QueryInterface(iid);
|
||||
},
|
||||
};
|
||||
|
||||
registrar.registerFactory(id, "XULAppInfo", cid, factory);
|
||||
};
|
||||
|
||||
// Reference needed in order for fake app dir provider to be active.
|
||||
let gFakeAppDirectoryProvider;
|
||||
|
||||
/**
|
||||
* Installs a fake UAppData directory.
|
||||
*
|
||||
* This is needed by tests because a UAppData directory typically isn't
|
||||
* present in the test environment.
|
||||
*
|
||||
* This function is suitable for use in different components. If we ever
|
||||
* establish a central location for convenient test helpers, this should
|
||||
* go there.
|
||||
*
|
||||
* We create the new UAppData directory under the profile's directory
|
||||
* because the profile directory is automatically cleaned as part of
|
||||
* test shutdown.
|
||||
*
|
||||
* This returns a promise that will be resolved once the new directory
|
||||
* is created and installed.
|
||||
*/
|
||||
this.makeFakeAppDir = function () {
|
||||
let dirMode = OS.Constants.libc.S_IRWXU;
|
||||
let dirService = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIProperties);
|
||||
let baseFile = dirService.get("ProfD", Ci.nsIFile);
|
||||
let appD = baseFile.clone();
|
||||
appD.append("UAppData");
|
||||
|
||||
if (gFakeAppDirectoryProvider) {
|
||||
return Promise.resolve(appD.path);
|
||||
}
|
||||
|
||||
function makeDir(f) {
|
||||
if (f.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dump("Creating directory: " + f.path + "\n");
|
||||
f.create(Ci.nsIFile.DIRECTORY_TYPE, dirMode);
|
||||
}
|
||||
|
||||
makeDir(appD);
|
||||
|
||||
let reportsD = appD.clone();
|
||||
reportsD.append("Crash Reports");
|
||||
|
||||
let pendingD = reportsD.clone();
|
||||
pendingD.append("pending");
|
||||
let submittedD = reportsD.clone();
|
||||
submittedD.append("submitted");
|
||||
|
||||
makeDir(reportsD);
|
||||
makeDir(pendingD);
|
||||
makeDir(submittedD);
|
||||
|
||||
let provider = {
|
||||
getFile: function (prop, persistent) {
|
||||
persistent.value = true;
|
||||
if (prop == "UAppData") {
|
||||
return appD.clone();
|
||||
}
|
||||
|
||||
throw Cr.NS_ERROR_FAILURE;
|
||||
},
|
||||
|
||||
QueryInterace: function (iid) {
|
||||
if (iid.equals(Ci.nsIDirectoryServiceProvider) ||
|
||||
iid.equals(Ci.nsISupports)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
};
|
||||
|
||||
// Register the new provider.
|
||||
dirService.QueryInterface(Ci.nsIDirectoryService)
|
||||
.registerProvider(provider);
|
||||
|
||||
// And undefine the old one.
|
||||
try {
|
||||
dirService.undefine("UAppData");
|
||||
} catch (ex) {};
|
||||
|
||||
gFakeAppDirectoryProvider = provider;
|
||||
|
||||
dump("Successfully installed fake UAppDir\n");
|
||||
return Promise.resolve(appD.path);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates a fake crash in the Crash Reports directory.
|
||||
*
|
||||
* Currently, we just create a dummy file. A more robust implementation would
|
||||
* create something that actually resembles a crash report file.
|
||||
*
|
||||
* This is very similar to code in crashreporter/tests/browser/head.js.
|
||||
*
|
||||
* FUTURE consolidate code in a shared JSM.
|
||||
*/
|
||||
this.createFakeCrash = function (submitted=false, date=new Date()) {
|
||||
let id = CommonUtils.generateUUID();
|
||||
let filename;
|
||||
|
||||
let paths = ["Crash Reports"];
|
||||
let mode;
|
||||
|
||||
if (submitted) {
|
||||
paths.push("submitted");
|
||||
filename = "bp-" + id + ".txt";
|
||||
mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR |
|
||||
OS.Constants.libc.S_IRGRP | OS.Constants.libc.S_IROTH;
|
||||
} else {
|
||||
paths.push("pending");
|
||||
filename = id + ".dmp";
|
||||
mode = OS.Constants.libc.S_IRUSR | OS.Constants.libc.S_IWUSR;
|
||||
}
|
||||
|
||||
paths.push(filename);
|
||||
|
||||
let file = FileUtils.getFile("UAppData", paths, true);
|
||||
file.create(file.NORMAL_FILE_TYPE, mode);
|
||||
file.lastModifiedTime = date.getTime();
|
||||
dump("Created fake crash: " + id + "\n");
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A HealthReporter that is probed with various callbacks and counters.
|
||||
*
|
||||
* The purpose of this type is to aid testing of startup and shutdown.
|
||||
*/
|
||||
this.InspectedHealthReporter = function (branch) {
|
||||
HealthReporter.call(this, branch);
|
||||
|
||||
this.onStorageCreated = null;
|
||||
this.onCollectorInitialized = null;
|
||||
this.collectorShutdownCount = 0;
|
||||
this.storageCloseCount = 0;
|
||||
}
|
||||
|
||||
InspectedHealthReporter.prototype = {
|
||||
__proto__: HealthReporter.prototype,
|
||||
|
||||
_onStorageCreated: function (storage) {
|
||||
if (this.onStorageCreated) {
|
||||
this.onStorageCreated(storage);
|
||||
}
|
||||
|
||||
return HealthReporter.prototype._onStorageCreated.call(this, storage);
|
||||
},
|
||||
|
||||
_onCollectorInitialized: function () {
|
||||
if (this.onCollectorInitialized) {
|
||||
this.onCollectorInitialized();
|
||||
}
|
||||
|
||||
return HealthReporter.prototype._onCollectorInitialized.call(this);
|
||||
},
|
||||
|
||||
_onCollectorShutdown: function () {
|
||||
this.collectorShutdownCount++;
|
||||
|
||||
return HealthReporter.prototype._onCollectorShutdown.call(this);
|
||||
},
|
||||
|
||||
_onStorageClose: function () {
|
||||
this.storageCloseCount++;
|
||||
|
||||
return HealthReporter.prototype._onStorageClose.call(this);
|
||||
},
|
||||
};
|
||||
|
@ -11,24 +11,26 @@ this.EXPORTED_SYMBOLS = [
|
||||
|
||||
const {utils: Cu, classes: Cc, interfaces: Ci} = Components;
|
||||
|
||||
const DEFAULT_PROFILE_MEASUREMENT_NAME = "org.mozilla.profile";
|
||||
const DEFAULT_PROFILE_MEASUREMENT_NAME = "age";
|
||||
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
const REQUIRED_UINT32_TYPE = {type: "TYPE_UINT32"};
|
||||
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/osfile.jsm")
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
// Profile creation time access.
|
||||
// This is separate from the provider to simplify testing and enable extraction
|
||||
// to a shared location in the future.
|
||||
function ProfileCreationTimeAccessor(profile) {
|
||||
function ProfileCreationTimeAccessor(profile, log) {
|
||||
this.profilePath = profile || OS.Constants.Path.profileDir;
|
||||
if (!this.profilePath) {
|
||||
throw new Error("No profile directory.");
|
||||
}
|
||||
this._log = log || {"debug": function (s) { dump(s + "\n"); }};
|
||||
}
|
||||
ProfileCreationTimeAccessor.prototype = {
|
||||
/**
|
||||
@ -115,31 +117,34 @@ ProfileCreationTimeAccessor.prototype = {
|
||||
* and returning its creation timestamp.
|
||||
*/
|
||||
getOldestProfileTimestamp: function () {
|
||||
let self = this;
|
||||
let oldest = Date.now() + 1000;
|
||||
let iterator = new OS.File.DirectoryIterator(this.profilePath);
|
||||
dump("Iterating over profile " + this.profilePath);
|
||||
self._log.debug("Iterating over profile " + this.profilePath);
|
||||
if (!iterator) {
|
||||
throw new Error("Unable to fetch oldest profile entry: no profile iterator.");
|
||||
}
|
||||
|
||||
function onEntry(entry) {
|
||||
if ("winLastWriteDate" in entry) {
|
||||
// Under Windows, additional information allow us to sort files immediately
|
||||
// without having to perform additional I/O.
|
||||
let timestamp = entry.winCreationDate.getTime();
|
||||
if (timestamp < oldest) {
|
||||
oldest = timestamp;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Under other OSes, we need to call OS.File.stat.
|
||||
function onStatSuccess(info) {
|
||||
let date = info.creationDate;
|
||||
let timestamp = date.getTime();
|
||||
dump("CREATION DATE: " + entry.path + " = " + date);
|
||||
if (timestamp < oldest) {
|
||||
oldest = timestamp;
|
||||
// OS.File doesn't seem to be behaving. See Bug 827148.
|
||||
// Let's do the best we can. This whole function is defensive.
|
||||
let date = info.winBirthDate || info.macBirthDate;
|
||||
if (!date || !date.getTime()) {
|
||||
// OS.File will only return file creation times of any kind on Mac
|
||||
// and Windows, where birthTime is defined.
|
||||
// That means we're unable to function on Linux, so we use mtime
|
||||
// instead.
|
||||
self._log.debug("No birth date. Using mtime.");
|
||||
date = info.lastModificationDate;
|
||||
}
|
||||
|
||||
if (date) {
|
||||
let timestamp = date.getTime();
|
||||
self._log.debug("Using date: " + entry.path + " = " + date);
|
||||
if (timestamp < oldest) {
|
||||
oldest = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
return OS.File.stat(entry.path)
|
||||
@ -165,15 +170,18 @@ dump("Iterating over profile " + this.profilePath);
|
||||
/**
|
||||
* Measurements pertaining to the user's profile.
|
||||
*/
|
||||
function ProfileMetadataMeasurement(name=DEFAULT_PROFILE_MEASUREMENT_NAME) {
|
||||
MetricsMeasurement.call(this, name, 1);
|
||||
function ProfileMetadataMeasurement() {
|
||||
Metrics.Measurement.call(this);
|
||||
}
|
||||
ProfileMetadataMeasurement.prototype = {
|
||||
__proto__: MetricsMeasurement.prototype,
|
||||
__proto__: Metrics.Measurement.prototype,
|
||||
|
||||
fields: {
|
||||
name: DEFAULT_PROFILE_MEASUREMENT_NAME,
|
||||
version: 1,
|
||||
|
||||
configureStorage: function () {
|
||||
// Profile creation date. Number of days since Unix epoch.
|
||||
"profileCreation": REQUIRED_UINT32_TYPE,
|
||||
return this.registerStorageField("profileCreation", this.storage.FIELD_LAST_NUMERIC);
|
||||
},
|
||||
};
|
||||
|
||||
@ -188,41 +196,35 @@ function truncate(msec) {
|
||||
}
|
||||
|
||||
/**
|
||||
* A MetricsProvider for profile metadata, such as profile creation time.
|
||||
* A Metrics.Provider for profile metadata, such as profile creation time.
|
||||
*/
|
||||
function ProfileMetadataProvider(name="ProfileMetadataProvider") {
|
||||
MetricsProvider.call(this, name);
|
||||
function ProfileMetadataProvider() {
|
||||
Metrics.Provider.call(this);
|
||||
}
|
||||
ProfileMetadataProvider.prototype = {
|
||||
__proto__: MetricsProvider.prototype,
|
||||
__proto__: Metrics.Provider.prototype,
|
||||
|
||||
name: "org.mozilla.profile",
|
||||
|
||||
measurementTypes: [ProfileMetadataMeasurement],
|
||||
|
||||
getProfileCreationDays: function () {
|
||||
let accessor = new ProfileCreationTimeAccessor();
|
||||
let accessor = new ProfileCreationTimeAccessor(null, this._log);
|
||||
|
||||
return accessor.created
|
||||
.then(truncate);
|
||||
},
|
||||
|
||||
collectConstantMeasurements: function () {
|
||||
let result = this.createResult();
|
||||
result.expectMeasurement("org.mozilla.profile");
|
||||
result.populate = this._populateConstants.bind(this);
|
||||
return result;
|
||||
},
|
||||
collectConstantData: function () {
|
||||
let m = this.getMeasurement(DEFAULT_PROFILE_MEASUREMENT_NAME, 1);
|
||||
|
||||
_populateConstants: function (result) {
|
||||
let name = DEFAULT_PROFILE_MEASUREMENT_NAME;
|
||||
result.addMeasurement(new ProfileMetadataMeasurement(name));
|
||||
function onSuccess(days) {
|
||||
result.setValue(name, "profileCreation", days);
|
||||
result.finish();
|
||||
}
|
||||
function onFailure(ex) {
|
||||
result.addError(ex);
|
||||
result.finish();
|
||||
}
|
||||
return this.getProfileCreationDays()
|
||||
.then(onSuccess, onFailure);
|
||||
return Task.spawn(function collectConstant() {
|
||||
let createdDays = yield this.getProfileCreationDays();
|
||||
|
||||
yield this.enqueueStorageOperation(function storeDays() {
|
||||
return m.setLastNumeric("profileCreation", createdDays);
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -14,3 +14,24 @@ do_get_profile();
|
||||
ns.initTestLogging();
|
||||
}).call(this);
|
||||
|
||||
(function createAppInfo() {
|
||||
let ns = {};
|
||||
Components.utils.import("resource://testing-common/services/healthreport/utils.jsm", ns);
|
||||
ns.updateAppInfo();
|
||||
}).call(this);
|
||||
|
||||
// The hack, it burns. This could go away if extensions code exposed its
|
||||
// test environment setup functions as a testing-only JSM. See similar
|
||||
// code in Sync's head_helpers.js.
|
||||
let gGlobalScope = this;
|
||||
function loadAddonManager() {
|
||||
let ns = {};
|
||||
Components.utils.import("resource://gre/modules/Services.jsm", ns);
|
||||
let head = "../../../../toolkit/mozapps/extensions/test/xpcshell/head_addons.js";
|
||||
let file = do_get_file(head);
|
||||
let uri = ns.Services.io.newFileURI(file);
|
||||
ns.Services.scriptloader.loadSubScript(uri.spec, gGlobalScope);
|
||||
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
|
||||
startupManager();
|
||||
}
|
||||
|
||||
|
@ -10,13 +10,17 @@ Cu.import("resource://services-common/preferences.js");
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm");
|
||||
Cu.import("resource://gre/modules/services/healthreport/policy.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://testing-common/services-common/bagheeraserver.js");
|
||||
Cu.import("resource://testing-common/services/metrics/mocks.jsm");
|
||||
Cu.import("resource://testing-common/services/healthreport/utils.jsm");
|
||||
|
||||
|
||||
const SERVER_HOSTNAME = "localhost";
|
||||
const SERVER_PORT = 8080;
|
||||
const SERVER_URI = "http://" + SERVER_HOSTNAME + ":" + SERVER_PORT;
|
||||
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
|
||||
function defineNow(policy, now) {
|
||||
@ -29,33 +33,49 @@ function defineNow(policy, now) {
|
||||
});
|
||||
}
|
||||
|
||||
function getReporter(name, uri=SERVER_URI) {
|
||||
function getJustReporter(name, uri=SERVER_URI, inspected=false) {
|
||||
let branch = "healthreport.testing. " + name + ".";
|
||||
|
||||
let prefs = new Preferences(branch);
|
||||
prefs.set("documentServerURI", uri);
|
||||
prefs.set("dbName", name);
|
||||
|
||||
return new HealthReporter(branch);
|
||||
let type = inspected ? InspectedHealthReporter : HealthReporter;
|
||||
return new type(branch);
|
||||
}
|
||||
|
||||
function getReporter(name, uri, inspected) {
|
||||
let reporter = getJustReporter(name, uri, inspected);
|
||||
return reporter.onInit();
|
||||
}
|
||||
|
||||
function getReporterAndServer(name, namespace="test") {
|
||||
let reporter = getReporter(name, SERVER_URI);
|
||||
reporter.serverNamespace = namespace;
|
||||
return Task.spawn(function get() {
|
||||
let reporter = yield getReporter(name, SERVER_URI);
|
||||
reporter.serverNamespace = namespace;
|
||||
|
||||
let server = new BagheeraServer(SERVER_URI);
|
||||
server.createNamespace(namespace);
|
||||
let server = new BagheeraServer(SERVER_URI);
|
||||
server.createNamespace(namespace);
|
||||
|
||||
server.start(SERVER_PORT);
|
||||
server.start(SERVER_PORT);
|
||||
|
||||
return [reporter, server];
|
||||
throw new Task.Result([reporter, server]);
|
||||
});
|
||||
}
|
||||
|
||||
function shutdownServer(server) {
|
||||
let deferred = Promise.defer();
|
||||
server.stop(deferred.resolve.bind(deferred));
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_constructor() {
|
||||
let reporter = getReporter("constructor");
|
||||
add_task(function test_constructor() {
|
||||
let reporter = yield getReporter("constructor");
|
||||
|
||||
do_check_eq(reporter.lastPingDate.getTime(), 0);
|
||||
do_check_null(reporter.lastSubmitID);
|
||||
@ -70,16 +90,54 @@ add_test(function test_constructor() {
|
||||
new HealthReporter("foo.bar");
|
||||
} catch (ex) {
|
||||
failed = true;
|
||||
do_check_true(ex.message.startsWith("Branch argument must end"));
|
||||
do_check_true(ex.message.startsWith("Branch must end"));
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_register_providers_from_category_manager() {
|
||||
add_task(function test_shutdown_normal() {
|
||||
let reporter = yield getReporter("shutdown_normal");
|
||||
|
||||
// We can't send "quit-application" notification because the xpcshell runner
|
||||
// will shut down!
|
||||
reporter._initiateShutdown();
|
||||
reporter._waitForShutdown();
|
||||
});
|
||||
|
||||
add_task(function test_shutdown_storage_in_progress() {
|
||||
let reporter = yield getJustReporter("shutdown_storage_in_progress", SERVER_URI, true);
|
||||
|
||||
reporter.onStorageCreated = function () {
|
||||
print("Faking shutdown during storage initialization.");
|
||||
reporter._initiateShutdown();
|
||||
};
|
||||
|
||||
reporter._waitForShutdown();
|
||||
do_check_eq(reporter.collectorShutdownCount, 0);
|
||||
do_check_eq(reporter.storageCloseCount, 1);
|
||||
});
|
||||
|
||||
// Ensure that a shutdown triggered while collector is initializing results in
|
||||
// shutdown and storage closure.
|
||||
add_task(function test_shutdown_collector_in_progress() {
|
||||
let reporter = yield getJustReporter("shutdown_collect_in_progress", SERVER_URI, true);
|
||||
|
||||
reporter.onCollectorInitialized = function () {
|
||||
print("Faking shutdown during collector initialization.");
|
||||
reporter._initiateShutdown();
|
||||
};
|
||||
|
||||
// This will hang if shutdown logic is busted.
|
||||
reporter._waitForShutdown();
|
||||
do_check_eq(reporter.collectorShutdownCount, 1);
|
||||
do_check_eq(reporter.storageCloseCount, 1);
|
||||
});
|
||||
|
||||
add_task(function test_register_providers_from_category_manager() {
|
||||
const category = "healthreporter-js-modules";
|
||||
|
||||
let cm = Cc["@mozilla.org/categorymanager;1"]
|
||||
@ -88,113 +146,140 @@ add_test(function test_register_providers_from_category_manager() {
|
||||
"resource://testing-common/services/metrics/mocks.jsm",
|
||||
false, true);
|
||||
|
||||
let reporter = getReporter("category_manager");
|
||||
do_check_eq(reporter._collector._providers.length, 0);
|
||||
reporter.registerProvidersFromCategoryManager(category);
|
||||
do_check_eq(reporter._collector._providers.length, 1);
|
||||
let reporter = yield getReporter("category_manager");
|
||||
do_check_eq(reporter._collector._providers.size, 0);
|
||||
yield reporter.registerProvidersFromCategoryManager(category);
|
||||
do_check_eq(reporter._collector._providers.size, 1);
|
||||
|
||||
run_next_test();
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_start() {
|
||||
let reporter = getReporter("start");
|
||||
reporter.start().then(function onStarted() {
|
||||
reporter.stop();
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_json_payload_simple() {
|
||||
let reporter = getReporter("json_payload_simple");
|
||||
add_task(function test_json_payload_simple() {
|
||||
let reporter = yield getReporter("json_payload_simple");
|
||||
|
||||
let now = new Date();
|
||||
let payload = reporter.getJSONPayload();
|
||||
let payload = yield reporter.getJSONPayload();
|
||||
let original = JSON.parse(payload);
|
||||
|
||||
do_check_eq(original.version, 1);
|
||||
do_check_eq(original.thisPingDate, reporter._formatDate(now));
|
||||
do_check_eq(Object.keys(original.providers).length, 0);
|
||||
do_check_eq(Object.keys(original.data.last).length, 0);
|
||||
do_check_eq(Object.keys(original.data.days).length, 0);
|
||||
|
||||
reporter.lastPingDate = new Date(now.getTime() - 24 * 60 * 60 * 1000 - 10);
|
||||
|
||||
original = JSON.parse(reporter.getJSONPayload());
|
||||
original = JSON.parse(yield reporter.getJSONPayload());
|
||||
do_check_eq(original.lastPingDate, reporter._formatDate(reporter.lastPingDate));
|
||||
|
||||
// This could fail if we cross UTC day boundaries at the exact instance the
|
||||
// test is executed. Let's tempt fate.
|
||||
do_check_eq(original.thisPingDate, reporter._formatDate(now));
|
||||
|
||||
run_next_test();
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_json_payload_dummy_provider() {
|
||||
let reporter = getReporter("json_payload_dummy_provider");
|
||||
add_task(function test_json_payload_dummy_provider() {
|
||||
let reporter = yield getReporter("json_payload_dummy_provider");
|
||||
|
||||
reporter.registerProvider(new DummyProvider());
|
||||
reporter.collectMeasurements().then(function onResult() {
|
||||
let o = JSON.parse(reporter.getJSONPayload());
|
||||
yield reporter.registerProvider(new DummyProvider());
|
||||
yield reporter.collectMeasurements();
|
||||
let payload = yield reporter.getJSONPayload();
|
||||
print(payload);
|
||||
let o = JSON.parse(payload);
|
||||
|
||||
do_check_eq(Object.keys(o.providers).length, 1);
|
||||
do_check_true("DummyProvider" in o.providers);
|
||||
do_check_true("measurements" in o.providers.DummyProvider);
|
||||
do_check_true("DummyMeasurement" in o.providers.DummyProvider.measurements);
|
||||
do_check_eq(Object.keys(o.data.last).length, 1);
|
||||
do_check_true("DummyProvider.DummyMeasurement.1" in o.data.last);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_notify_policy_observers() {
|
||||
let reporter = getReporter("notify_policy_observers");
|
||||
add_task(function test_json_payload_multiple_days() {
|
||||
let reporter = yield getReporter("json_payload_multiple_days");
|
||||
let provider = new DummyProvider();
|
||||
yield reporter.registerProvider(provider);
|
||||
|
||||
Observers.add("healthreport:notify-data-policy:request",
|
||||
function onObserver(subject, data) {
|
||||
Observers.remove("healthreport:notify-data-policy:request", onObserver);
|
||||
let now = new Date();
|
||||
let m = provider.getMeasurement("DummyMeasurement", 1);
|
||||
for (let i = 0; i < 200; i++) {
|
||||
let date = new Date(now.getTime() - i * MILLISECONDS_PER_DAY);
|
||||
yield m.incrementDailyCounter("daily-counter", date);
|
||||
yield m.addDailyDiscreteNumeric("daily-discrete-numeric", i, date);
|
||||
yield m.addDailyDiscreteNumeric("daily-discrete-numeric", i + 100, date);
|
||||
yield m.addDailyDiscreteText("daily-discrete-text", "" + i, date);
|
||||
yield m.addDailyDiscreteText("daily-discrete-text", "" + (i + 50), date);
|
||||
yield m.setDailyLastNumeric("daily-last-numeric", date.getTime(), date);
|
||||
}
|
||||
|
||||
do_check_true("foo" in subject);
|
||||
let payload = yield reporter.getJSONPayload();
|
||||
print(payload);
|
||||
let o = JSON.parse(payload);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
do_check_eq(Object.keys(o.data.days).length, 180);
|
||||
let today = reporter._formatDate(now);
|
||||
do_check_true(today in o.data.days);
|
||||
|
||||
reporter.onNotifyDataPolicy({foo: "bar"});
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_data_submission_transport_failure() {
|
||||
let reporter = getReporter("data_submission_transport_failure");
|
||||
add_task(function test_idle_daily() {
|
||||
let reporter = yield getReporter("idle_daily");
|
||||
let provider = new DummyProvider();
|
||||
yield reporter.registerProvider(provider);
|
||||
|
||||
let now = new Date();
|
||||
let m = provider.getMeasurement("DummyMeasurement", 1);
|
||||
for (let i = 0; i < 200; i++) {
|
||||
let date = new Date(now.getTime() - i * MILLISECONDS_PER_DAY);
|
||||
yield m.incrementDailyCounter("daily-counter", date);
|
||||
}
|
||||
|
||||
let values = yield m.getValues();
|
||||
do_check_eq(values.days.size, 200);
|
||||
|
||||
Services.obs.notifyObservers(null, "idle-daily", null);
|
||||
|
||||
let values = yield m.getValues();
|
||||
do_check_eq(values.days.size, 180);
|
||||
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_task(function test_data_submission_transport_failure() {
|
||||
let reporter = yield getReporter("data_submission_transport_failure");
|
||||
reporter.serverURI = "http://localhost:8080/";
|
||||
reporter.serverNamespace = "test00";
|
||||
|
||||
let deferred = Promise.defer();
|
||||
deferred.promise.then(function onResult(request) {
|
||||
do_check_eq(request.state, request.SUBMISSION_FAILURE_SOFT);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
let request = new DataSubmissionRequest(deferred, new Date(Date.now + 30000));
|
||||
reporter.onRequestDataUpload(request);
|
||||
|
||||
yield deferred.promise;
|
||||
do_check_eq(request.state, request.SUBMISSION_FAILURE_SOFT);
|
||||
|
||||
reporter._shutdown();
|
||||
});
|
||||
|
||||
add_test(function test_data_submission_success() {
|
||||
let [reporter, server] = getReporterAndServer("data_submission_success");
|
||||
add_task(function test_data_submission_success() {
|
||||
let [reporter, server] = yield getReporterAndServer("data_submission_success");
|
||||
|
||||
do_check_eq(reporter.lastPingDate.getTime(), 0);
|
||||
do_check_false(reporter.haveRemoteData());
|
||||
|
||||
let deferred = Promise.defer();
|
||||
deferred.promise.then(function onResult(request) {
|
||||
do_check_eq(request.state, request.SUBMISSION_SUCCESS);
|
||||
do_check_neq(reporter.lastPingDate.getTime(), 0);
|
||||
do_check_true(reporter.haveRemoteData());
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
|
||||
let request = new DataSubmissionRequest(deferred, new Date());
|
||||
reporter.onRequestDataUpload(request);
|
||||
yield deferred.promise;
|
||||
do_check_eq(request.state, request.SUBMISSION_SUCCESS);
|
||||
do_check_true(reporter.lastPingDate.getTime() > 0);
|
||||
do_check_true(reporter.haveRemoteData());
|
||||
|
||||
reporter._shutdown();
|
||||
yield shutdownServer(server);
|
||||
});
|
||||
|
||||
add_test(function test_recurring_daily_pings() {
|
||||
let [reporter, server] = getReporterAndServer("recurring_daily_pings");
|
||||
add_task(function test_recurring_daily_pings() {
|
||||
let [reporter, server] = yield getReporterAndServer("recurring_daily_pings");
|
||||
reporter.registerProvider(new DummyProvider());
|
||||
|
||||
let policy = reporter._policy;
|
||||
@ -204,55 +289,52 @@ add_test(function test_recurring_daily_pings() {
|
||||
defineNow(policy, policy.nextDataSubmissionDate);
|
||||
let promise = policy.checkStateAndTrigger();
|
||||
do_check_neq(promise, null);
|
||||
yield promise;
|
||||
|
||||
promise.then(function onUploadComplete() {
|
||||
let lastID = reporter.lastSubmitID;
|
||||
let lastID = reporter.lastSubmitID;
|
||||
do_check_neq(lastID, null);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, lastID));
|
||||
|
||||
do_check_neq(lastID, null);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, lastID));
|
||||
// Skip forward to next scheduled submission time.
|
||||
defineNow(policy, policy.nextDataSubmissionDate);
|
||||
promise = policy.checkStateAndTrigger();
|
||||
do_check_neq(promise, null);
|
||||
yield promise;
|
||||
do_check_neq(reporter.lastSubmitID, lastID);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, reporter.lastSubmitID));
|
||||
do_check_false(server.hasDocument(reporter.serverNamespace, lastID));
|
||||
|
||||
// Skip forward to next scheduled submission time.
|
||||
defineNow(policy, policy.nextDataSubmissionDate);
|
||||
let promise = policy.checkStateAndTrigger();
|
||||
do_check_neq(promise, null);
|
||||
promise.then(function onSecondUploadCOmplete() {
|
||||
do_check_neq(reporter.lastSubmitID, lastID);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, reporter.lastSubmitID));
|
||||
do_check_false(server.hasDocument(reporter.serverNamespace, lastID));
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
reporter._shutdown();
|
||||
yield shutdownServer(server);
|
||||
});
|
||||
|
||||
add_test(function test_request_remote_data_deletion() {
|
||||
let [reporter, server] = getReporterAndServer("request_remote_data_deletion");
|
||||
add_task(function test_request_remote_data_deletion() {
|
||||
let [reporter, server] = yield getReporterAndServer("request_remote_data_deletion");
|
||||
|
||||
let policy = reporter._policy;
|
||||
defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000));
|
||||
policy.recordUserAcceptance();
|
||||
defineNow(policy, policy.nextDataSubmissionDate);
|
||||
policy.checkStateAndTrigger().then(function onUploadComplete() {
|
||||
let id = reporter.lastSubmitID;
|
||||
do_check_neq(id, null);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, id));
|
||||
yield policy.checkStateAndTrigger();
|
||||
let id = reporter.lastSubmitID;
|
||||
do_check_neq(id, null);
|
||||
do_check_true(server.hasDocument(reporter.serverNamespace, id));
|
||||
|
||||
defineNow(policy, policy._futureDate(10 * 1000));
|
||||
defineNow(policy, policy._futureDate(10 * 1000));
|
||||
|
||||
let promise = reporter.requestDeleteRemoteData();
|
||||
do_check_neq(promise, null);
|
||||
promise.then(function onDeleteComplete() {
|
||||
do_check_null(reporter.lastSubmitID);
|
||||
do_check_false(reporter.haveRemoteData());
|
||||
do_check_false(server.hasDocument(reporter.serverNamespace, id));
|
||||
let promise = reporter.requestDeleteRemoteData();
|
||||
do_check_neq(promise, null);
|
||||
yield promise;
|
||||
do_check_null(reporter.lastSubmitID);
|
||||
do_check_false(reporter.haveRemoteData());
|
||||
do_check_false(server.hasDocument(reporter.serverNamespace, id));
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
reporter._shutdown();
|
||||
yield shutdownServer(server);
|
||||
});
|
||||
|
||||
add_test(function test_policy_accept_reject() {
|
||||
let [reporter, server] = getReporterAndServer("policy_accept_reject");
|
||||
add_task(function test_policy_accept_reject() {
|
||||
let [reporter, server] = yield getReporterAndServer("policy_accept_reject");
|
||||
|
||||
do_check_false(reporter.dataSubmissionPolicyAccepted);
|
||||
do_check_false(reporter.willUploadData);
|
||||
@ -265,21 +347,22 @@ add_test(function test_policy_accept_reject() {
|
||||
do_check_false(reporter.dataSubmissionPolicyAccepted);
|
||||
do_check_false(reporter.willUploadData);
|
||||
|
||||
server.stop(run_next_test);
|
||||
reporter._shutdown();
|
||||
yield shutdownServer(server);
|
||||
});
|
||||
|
||||
|
||||
add_test(function test_upload_save_payload() {
|
||||
let [reporter, server] = getReporterAndServer("upload_save_payload");
|
||||
add_task(function test_upload_save_payload() {
|
||||
let [reporter, server] = yield getReporterAndServer("upload_save_payload");
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let request = new DataSubmissionRequest(deferred, new Date(), false);
|
||||
|
||||
reporter._uploadData(request).then(function onUpload() {
|
||||
reporter.getLastPayload().then(function onJSON(json) {
|
||||
do_check_true("thisPingDate" in json);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
yield reporter._uploadData(request);
|
||||
let json = yield reporter.getLastPayload();
|
||||
do_check_true("thisPingDate" in json);
|
||||
|
||||
reporter._shutdown();
|
||||
yield shutdownServer(server);
|
||||
});
|
||||
|
||||
|
@ -13,12 +13,14 @@ let profile_creation_lower = Date.now() - MILLISECONDS_PER_DAY;
|
||||
do_get_profile();
|
||||
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/services/healthreport/profile.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
|
||||
function MockProfileMetadataProvider(name="MockProfileMetadataProvider") {
|
||||
ProfileMetadataProvider.call(this, name);
|
||||
this.name = name;
|
||||
ProfileMetadataProvider.call(this);
|
||||
}
|
||||
MockProfileMetadataProvider.prototype = {
|
||||
__proto__: ProfileMetadataProvider.prototype,
|
||||
@ -33,15 +35,6 @@ function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
/**
|
||||
* Treat the provided function as a generator of promises,
|
||||
* suitable for use with Task.spawn. Success runs next test;
|
||||
* failure throws.
|
||||
*/
|
||||
function testTask(promiseFunction) {
|
||||
Task.spawn(promiseFunction).then(run_next_test, do_throw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that OS.File works in our environment.
|
||||
* This test can go once there are xpcshell tests for OS.File.
|
||||
@ -84,22 +77,17 @@ add_test(function test_time_accessor_no_file() {
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_time_accessor_named_file() {
|
||||
add_task(function test_time_accessor_named_file() {
|
||||
let acc = getAccessor();
|
||||
|
||||
testTask(function () {
|
||||
// There should be no file yet.
|
||||
yield acc.writeTimes({created: 12345}, "test.json");
|
||||
yield acc.readTimes("test.json")
|
||||
.then(function onSuccess(json) {
|
||||
print("Read: " + JSON.stringify(json));
|
||||
do_check_eq(12345, json.created);
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
// There should be no file yet.
|
||||
yield acc.writeTimes({created: 12345}, "test.json");
|
||||
let json = yield acc.readTimes("test.json")
|
||||
print("Read: " + JSON.stringify(json));
|
||||
do_check_eq(12345, json.created);
|
||||
});
|
||||
|
||||
add_test(function test_time_accessor_creates_file() {
|
||||
add_task(function test_time_accessor_creates_file() {
|
||||
let lower = profile_creation_lower;
|
||||
|
||||
// Ensure that provided contents are merged, and existing
|
||||
@ -109,42 +97,32 @@ add_test(function test_time_accessor_creates_file() {
|
||||
let existing = {abc: "123", easy: "abc"};
|
||||
let expected;
|
||||
|
||||
testTask(function () {
|
||||
yield acc.computeAndPersistTimes(existing, "test2.json")
|
||||
.then(function onSuccess(created) {
|
||||
let upper = Date.now() + 1000;
|
||||
print(lower + " < " + created + " <= " + upper);
|
||||
do_check_true(lower < created);
|
||||
do_check_true(upper >= created);
|
||||
expected = created;
|
||||
});
|
||||
yield acc.readTimes("test2.json")
|
||||
.then(function onSuccess(json) {
|
||||
print("Read: " + JSON.stringify(json));
|
||||
do_check_eq("123", json.abc);
|
||||
do_check_eq("abc", json.easy);
|
||||
do_check_eq(expected, json.created);
|
||||
});
|
||||
});
|
||||
let created = yield acc.computeAndPersistTimes(existing, "test2.json")
|
||||
let upper = Date.now() + 1000;
|
||||
print(lower + " < " + created + " <= " + upper);
|
||||
do_check_true(lower < created);
|
||||
do_check_true(upper >= created);
|
||||
expected = created;
|
||||
|
||||
let json = yield acc.readTimes("test2.json")
|
||||
print("Read: " + JSON.stringify(json));
|
||||
do_check_eq("123", json.abc);
|
||||
do_check_eq("abc", json.easy);
|
||||
do_check_eq(expected, json.created);
|
||||
});
|
||||
|
||||
add_test(function test_time_accessor_all() {
|
||||
add_task(function test_time_accessor_all() {
|
||||
let lower = profile_creation_lower;
|
||||
let acc = getAccessor();
|
||||
let expected;
|
||||
testTask(function () {
|
||||
yield acc.created
|
||||
.then(function onSuccess(created) {
|
||||
let upper = Date.now() + 1000;
|
||||
do_check_true(lower < created);
|
||||
do_check_true(upper >= created);
|
||||
expected = created;
|
||||
});
|
||||
yield acc.created
|
||||
.then(function onSuccess(again) {
|
||||
do_check_eq(expected, again);
|
||||
});
|
||||
});
|
||||
let created = yield acc.created
|
||||
let upper = Date.now() + 1000;
|
||||
do_check_true(lower < created);
|
||||
do_check_true(upper >= created);
|
||||
expected = created;
|
||||
|
||||
let again = yield acc.created
|
||||
do_check_eq(expected, again);
|
||||
});
|
||||
|
||||
add_test(function test_constructor() {
|
||||
@ -171,43 +149,48 @@ add_test(function test_profile_files() {
|
||||
|
||||
// A generic test helper. We use this with both real
|
||||
// and mock providers in these tests.
|
||||
function test_collect_constant(provider, valueTest) {
|
||||
let result = provider.collectConstantMeasurements();
|
||||
do_check_true(result instanceof MetricsCollectionResult);
|
||||
function test_collect_constant(provider) {
|
||||
return Task.spawn(function () {
|
||||
yield provider.collectConstantData();
|
||||
|
||||
result.onFinished(function onFinished() {
|
||||
do_check_eq(result.expectedMeasurements.size, 1);
|
||||
do_check_true(result.expectedMeasurements.has("org.mozilla.profile"));
|
||||
let m = result.measurements.get("org.mozilla.profile");
|
||||
do_check_true(!!m);
|
||||
valueTest(m.getValue("profileCreation"));
|
||||
let m = provider.getMeasurement("age", 1);
|
||||
do_check_neq(m, null);
|
||||
let values = yield m.getValues();
|
||||
do_check_eq(values.singular.size, 1);
|
||||
do_check_true(values.singular.has("profileCreation"));
|
||||
|
||||
run_next_test();
|
||||
throw new Task.Result(values.singular.get("profileCreation")[1]);
|
||||
});
|
||||
|
||||
result.populate(result);
|
||||
}
|
||||
|
||||
add_test(function test_collect_constant_mock() {
|
||||
add_task(function test_collect_constant_mock() {
|
||||
let storage = yield Metrics.Storage("collect_constant_mock");
|
||||
let provider = new MockProfileMetadataProvider();
|
||||
function valueTest(v) {
|
||||
do_check_eq(v, 1234);
|
||||
}
|
||||
test_collect_constant(provider, valueTest);
|
||||
yield provider.init(storage);
|
||||
|
||||
let v = yield test_collect_constant(provider);
|
||||
do_check_eq(v, 1234);
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_test(function test_collect_constant_real() {
|
||||
add_task(function test_collect_constant_real() {
|
||||
let provider = new ProfileMetadataProvider();
|
||||
function valueTest(v) {
|
||||
let ms = v * MILLISECONDS_PER_DAY;
|
||||
let lower = profile_creation_lower;
|
||||
let upper = Date.now() + 1000;
|
||||
print("Day: " + v);
|
||||
print("msec: " + ms);
|
||||
print("Lower: " + lower);
|
||||
print("Upper: " + upper);
|
||||
do_check_true(lower <= ms);
|
||||
do_check_true(upper >= ms);
|
||||
}
|
||||
test_collect_constant(provider, valueTest);
|
||||
let storage = yield Metrics.Storage("collect_constant_real");
|
||||
yield provider.init(storage);
|
||||
|
||||
let v = yield test_collect_constant(provider);
|
||||
|
||||
let ms = v * MILLISECONDS_PER_DAY;
|
||||
let lower = profile_creation_lower;
|
||||
let upper = Date.now() + 1000;
|
||||
print("Day: " + v);
|
||||
print("msec: " + ms);
|
||||
print("Lower: " + lower);
|
||||
print("Upper: " + upper);
|
||||
do_check_true(lower <= ms);
|
||||
do_check_true(upper >= ms);
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
|
119
services/healthreport/tests/xpcshell/test_provider_addons.js
Normal file
@ -0,0 +1,119 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
|
||||
|
||||
|
||||
function run_test() {
|
||||
loadAddonManager();
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_constructor() {
|
||||
let provider = new AddonsProvider();
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(function test_init() {
|
||||
let storage = yield Metrics.Storage("init");
|
||||
let provider = new AddonsProvider();
|
||||
yield provider.init(storage);
|
||||
yield provider.shutdown();
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
function monkeypatchAddons(provider, addons) {
|
||||
if (!Array.isArray(addons)) {
|
||||
throw new Error("Must define array of addon objects.");
|
||||
}
|
||||
|
||||
Object.defineProperty(provider, "_createDataStructure", {
|
||||
value: function _createDataStructure() {
|
||||
return AddonsProvider.prototype._createDataStructure.call(provider, addons);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function test_collect() {
|
||||
let storage = yield Metrics.Storage("collect");
|
||||
let provider = new AddonsProvider();
|
||||
yield provider.init(storage);
|
||||
|
||||
let now = new Date();
|
||||
|
||||
// FUTURE install add-on via AddonManager and don't use monkeypatching.
|
||||
let addons = [
|
||||
{
|
||||
id: "addon0",
|
||||
userDisabled: false,
|
||||
appDisabled: false,
|
||||
version: "1",
|
||||
type: "extension",
|
||||
scope: 1,
|
||||
foreignInstall: false,
|
||||
hasBinaryComponents: false,
|
||||
installDate: now,
|
||||
updateDate: now,
|
||||
},
|
||||
{
|
||||
id: "addon1",
|
||||
userDisabled: false,
|
||||
appDisabled: false,
|
||||
version: "2",
|
||||
type: "plugin",
|
||||
scope: 1,
|
||||
foreignInstall: false,
|
||||
hasBinaryComponents: false,
|
||||
installDate: now,
|
||||
updateDate: now,
|
||||
},
|
||||
];
|
||||
|
||||
monkeypatchAddons(provider, addons);
|
||||
|
||||
yield provider.collectConstantData();
|
||||
|
||||
let active = provider.getMeasurement("active", 1);
|
||||
let data = yield active.getValues();
|
||||
|
||||
do_check_eq(data.days.size, 0);
|
||||
do_check_eq(data.singular.size, 1);
|
||||
do_check_true(data.singular.has("addons"));
|
||||
|
||||
let json = data.singular.get("addons")[1];
|
||||
let value = JSON.parse(json);
|
||||
do_check_eq(typeof(value), "object");
|
||||
do_check_eq(Object.keys(value).length, 2);
|
||||
do_check_true("addon0" in value);
|
||||
do_check_true("addon1" in value);
|
||||
|
||||
let serializer = active.serializer(active.SERIALIZE_JSON);
|
||||
let serialized = serializer.singular(data.singular);
|
||||
do_check_eq(typeof(serialized), "object");
|
||||
do_check_eq(Object.keys(serialized).length, 2);
|
||||
do_check_true("addon0" in serialized);
|
||||
do_check_true("addon1" in serialized);
|
||||
|
||||
let counts = provider.getMeasurement("counts", 1);
|
||||
data = yield counts.getValues();
|
||||
do_check_eq(data.days.size, 1);
|
||||
do_check_eq(data.singular.size, 0);
|
||||
do_check_true(data.days.hasDay(now));
|
||||
|
||||
value = data.days.getDay(now);
|
||||
do_check_eq(value.size, 2);
|
||||
do_check_eq(value.get("extension"), 1);
|
||||
do_check_eq(value.get("plugin"), 1);
|
||||
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
});
|
||||
|
@ -5,42 +5,13 @@
|
||||
|
||||
const {interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
Cu.import("resource://testing-common/services/healthreport/utils.jsm");
|
||||
|
||||
|
||||
function run_test() {
|
||||
let appInfo = {
|
||||
vendor: "Mozilla",
|
||||
name: "xpcshell",
|
||||
ID: "xpcshell@tests.mozilla.org",
|
||||
version: "1",
|
||||
appBuildID: "20121107",
|
||||
platformVersion: "p-ver",
|
||||
platformBuildID: "20121106",
|
||||
inSafeMode: false,
|
||||
logConsoleErrors: true,
|
||||
OS: "XPCShell",
|
||||
XPCOMABI: "noarch-spidermonkey",
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo, Ci.nsIXULRuntime]),
|
||||
invalidateCachesOnRestart: function() {},
|
||||
};
|
||||
|
||||
let factory = {
|
||||
createInstance: function createInstance(outer, iid) {
|
||||
if (outer != null) {
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
|
||||
return appInfo.QueryInterface(iid);
|
||||
},
|
||||
};
|
||||
|
||||
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"),
|
||||
"XULAppInfo", "@mozilla.org/xre/app-info;1",
|
||||
factory);
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
@ -50,32 +21,93 @@ add_test(function test_constructor() {
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_collect_smoketest() {
|
||||
add_task(function test_collect_smoketest() {
|
||||
let storage = yield Metrics.Storage("collect_smoketest");
|
||||
let provider = new AppInfoProvider();
|
||||
yield provider.init(storage);
|
||||
|
||||
let result = provider.collectConstantMeasurements();
|
||||
do_check_true(result instanceof MetricsCollectionResult);
|
||||
let now = new Date();
|
||||
yield provider.collectConstantData();
|
||||
|
||||
result.onFinished(function onFinished() {
|
||||
do_check_eq(result.expectedMeasurements.size, 1);
|
||||
do_check_true(result.expectedMeasurements.has("appinfo"));
|
||||
do_check_eq(result.measurements.size, 1);
|
||||
do_check_true(result.measurements.has("appinfo"));
|
||||
do_check_eq(result.errors.length, 0);
|
||||
let m = provider.getMeasurement("appinfo", 1);
|
||||
let data = yield storage.getMeasurementValues(m.id);
|
||||
let serializer = m.serializer(m.SERIALIZE_JSON);
|
||||
let d = serializer.singular(data.singular);
|
||||
|
||||
let ai = result.measurements.get("appinfo");
|
||||
do_check_eq(ai.getValue("vendor"), "Mozilla");
|
||||
do_check_eq(ai.getValue("name"), "xpcshell");
|
||||
do_check_eq(ai.getValue("id"), "xpcshell@tests.mozilla.org");
|
||||
do_check_eq(ai.getValue("version"), "1");
|
||||
do_check_eq(ai.getValue("appBuildID"), "20121107");
|
||||
do_check_eq(ai.getValue("platformVersion"), "p-ver");
|
||||
do_check_eq(ai.getValue("platformBuildID"), "20121106");
|
||||
do_check_eq(ai.getValue("os"), "XPCShell");
|
||||
do_check_eq(ai.getValue("xpcomabi"), "noarch-spidermonkey");
|
||||
do_check_eq(d.vendor, "Mozilla");
|
||||
do_check_eq(d.name, "xpcshell");
|
||||
do_check_eq(d.id, "xpcshell@tests.mozilla.org");
|
||||
do_check_eq(d.version, "1");
|
||||
do_check_eq(d.appBuildID, "20121107");
|
||||
do_check_eq(d.platformVersion, "p-ver");
|
||||
do_check_eq(d.platformBuildID, "20121106");
|
||||
do_check_eq(d.os, "XPCShell");
|
||||
do_check_eq(d.xpcomabi, "noarch-spidermonkey");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
do_check_eq(data.days.size, 1);
|
||||
do_check_true(data.days.hasDay(now));
|
||||
let day = data.days.getDay(now);
|
||||
do_check_eq(day.size, 1);
|
||||
do_check_true(day.has("isDefaultBrowser"));
|
||||
|
||||
result.populate(result);
|
||||
// TODO Bug 827189 Actually test this properly. On some local builds, this
|
||||
// is always -1 (the service throws). On buildbot, it seems to always be 0.
|
||||
do_check_neq(day.get("isDefaultBrowser"), 1);
|
||||
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_task(function test_record_version() {
|
||||
let storage = yield Metrics.Storage("record_version");
|
||||
|
||||
let provider = new AppInfoProvider();
|
||||
let now = new Date();
|
||||
yield provider.init(storage);
|
||||
|
||||
// The provider records information on startup.
|
||||
let m = provider.getMeasurement("versions", 1);
|
||||
let data = yield m.getValues();
|
||||
|
||||
do_check_true(data.days.hasDay(now));
|
||||
let day = data.days.getDay(now);
|
||||
do_check_eq(day.size, 1);
|
||||
do_check_true(day.has("version"));
|
||||
let value = day.get("version");
|
||||
do_check_true(Array.isArray(value));
|
||||
do_check_eq(value.length, 1);
|
||||
let ai = getAppInfo();
|
||||
do_check_eq(value, ai.version);
|
||||
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_task(function test_record_version_change() {
|
||||
let storage = yield Metrics.Storage("record_version_change");
|
||||
|
||||
let provider = new AppInfoProvider();
|
||||
let now = new Date();
|
||||
yield provider.init(storage);
|
||||
yield provider.shutdown();
|
||||
|
||||
let ai = getAppInfo();
|
||||
ai.version = "2";
|
||||
updateAppInfo(ai);
|
||||
|
||||
provider = new AppInfoProvider();
|
||||
yield provider.init(storage);
|
||||
|
||||
// There should be 2 records in the versions history.
|
||||
let m = provider.getMeasurement("versions", 1);
|
||||
let data = yield m.getValues();
|
||||
do_check_true(data.days.hasDay(now));
|
||||
let day = data.days.getDay(now);
|
||||
let value = day.get("version");
|
||||
do_check_true(Array.isArray(value));
|
||||
do_check_eq(value.length, 2);
|
||||
do_check_eq(value[1], "2");
|
||||
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
});
|
||||
|
146
services/healthreport/tests/xpcshell/test_provider_crashes.js
Normal file
@ -0,0 +1,146 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
|
||||
Cu.import("resource://testing-common/services/healthreport/utils.jsm");
|
||||
|
||||
|
||||
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
|
||||
function run_test() {
|
||||
makeFakeAppDir().then(run_next_test, do_throw);
|
||||
}
|
||||
|
||||
let gPending = {};
|
||||
let gSubmitted = {};
|
||||
|
||||
add_task(function test_directory_service() {
|
||||
let d = new CrashDirectoryService();
|
||||
|
||||
let entries = yield d.getPendingFiles();
|
||||
do_check_eq(typeof(entries), "object");
|
||||
do_check_eq(Object.keys(entries).length, 0);
|
||||
|
||||
entries = yield d.getSubmittedFiles();
|
||||
do_check_eq(typeof(entries), "object");
|
||||
do_check_eq(Object.keys(entries).length, 0);
|
||||
|
||||
let now = new Date();
|
||||
|
||||
// We lose granularity when writing to filesystem.
|
||||
now.setUTCMilliseconds(0);
|
||||
let dates = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
dates.push(new Date(now.getTime() - i * MILLISECONDS_PER_DAY));
|
||||
}
|
||||
|
||||
let pending = {};
|
||||
let submitted = {};
|
||||
for (let date of dates) {
|
||||
pending[createFakeCrash(false, date)] = date;
|
||||
submitted[createFakeCrash(true, date)] = date;
|
||||
}
|
||||
|
||||
entries = yield d.getPendingFiles();
|
||||
do_check_eq(Object.keys(entries).length, Object.keys(pending).length);
|
||||
for (let id in pending) {
|
||||
let filename = id + ".dmp";
|
||||
do_check_true(filename in entries);
|
||||
do_check_eq(entries[filename].modified.getTime(), pending[id].getTime());
|
||||
}
|
||||
|
||||
entries = yield d.getSubmittedFiles();
|
||||
do_check_eq(Object.keys(entries).length, Object.keys(submitted).length);
|
||||
for (let id in submitted) {
|
||||
let filename = "bp-" + id + ".txt";
|
||||
do_check_true(filename in entries);
|
||||
do_check_eq(entries[filename].modified.getTime(), submitted[id].getTime());
|
||||
}
|
||||
|
||||
gPending = pending;
|
||||
gSubmitted = submitted;
|
||||
});
|
||||
|
||||
add_test(function test_constructor() {
|
||||
let provider = new CrashesProvider();
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(function test_init() {
|
||||
let storage = yield Metrics.Storage("init");
|
||||
let provider = new CrashesProvider();
|
||||
yield provider.init(storage);
|
||||
yield provider.shutdown();
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_task(function test_collect() {
|
||||
let storage = yield Metrics.Storage("collect");
|
||||
let provider = new CrashesProvider();
|
||||
yield provider.init(storage);
|
||||
|
||||
// FUTURE Don't rely on state from previous test.
|
||||
yield provider.collectConstantData();
|
||||
|
||||
let m = provider.getMeasurement("crashes", 1);
|
||||
let values = yield m.getValues();
|
||||
do_check_eq(values.days.size, Object.keys(gPending).length);
|
||||
for each (let date in gPending) {
|
||||
do_check_true(values.days.hasDay(date));
|
||||
|
||||
let value = values.days.getDay(date);
|
||||
do_check_true(value.has("pending"));
|
||||
do_check_true(value.has("submitted"));
|
||||
do_check_eq(value.get("pending"), 1);
|
||||
do_check_eq(value.get("submitted"), 1);
|
||||
}
|
||||
|
||||
let currentState = yield provider.getState("lastCheck");
|
||||
do_check_eq(typeof(currentState), "string");
|
||||
do_check_true(currentState.length > 0);
|
||||
let lastState = currentState;
|
||||
|
||||
// If we collect again, we should get no new data.
|
||||
yield provider.collectConstantData();
|
||||
values = yield m.getValues();
|
||||
for each (let date in gPending) {
|
||||
let day = values.days.getDay(date);
|
||||
do_check_eq(day.get("pending"), 1);
|
||||
do_check_eq(day.get("submitted"), 1);
|
||||
}
|
||||
|
||||
currentState = yield provider.getState("lastCheck");
|
||||
do_check_neq(currentState, lastState);
|
||||
do_check_true(currentState > lastState);
|
||||
|
||||
let now = new Date();
|
||||
let tomorrow = new Date(now.getTime() + MILLISECONDS_PER_DAY);
|
||||
let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
|
||||
|
||||
let yesterdayID = createFakeCrash(false, yesterday);
|
||||
let tomorrowID = createFakeCrash(false, tomorrow);
|
||||
|
||||
yield provider.collectConstantData();
|
||||
values = yield m.getValues();
|
||||
do_check_eq(values.days.size, 11);
|
||||
do_check_eq(values.days.getDay(tomorrow).get("pending"), 1);
|
||||
|
||||
for each (let date in gPending) {
|
||||
let day = values.days.getDay(date);
|
||||
do_check_eq(day.get("pending"), 1);
|
||||
do_check_eq(day.get("submitted"), 1);
|
||||
}
|
||||
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
});
|
||||
|
208
services/healthreport/tests/xpcshell/test_provider_sessions.js
Normal file
@ -0,0 +1,208 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
|
||||
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_constructor() {
|
||||
let provider = new SessionsProvider();
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(function test_init() {
|
||||
let storage = yield Metrics.Storage("init");
|
||||
let provider = new SessionsProvider();
|
||||
yield provider.init(storage);
|
||||
yield provider.shutdown();
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
function getProvider(name, now=new Date()) {
|
||||
return Task.spawn(function () {
|
||||
let storage = yield Metrics.Storage(name);
|
||||
let provider = new SessionsProvider();
|
||||
monkeypatchStartupInfo(provider, now);
|
||||
yield provider.init(storage);
|
||||
|
||||
throw new Task.Result([provider, storage]);
|
||||
});
|
||||
}
|
||||
|
||||
function monkeypatchStartupInfo(provider, start=new Date(), offset=500) {
|
||||
Object.defineProperty(provider, "_getStartupInfo", {
|
||||
value: function _getStartupInfo() {
|
||||
return {
|
||||
process: start,
|
||||
main: new Date(start.getTime() + offset),
|
||||
firstPaint: new Date(start.getTime() + 2 * offset),
|
||||
sessionRestored: new Date(start.getTime() + 3 * offset),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function test_record_current_on_init() {
|
||||
let [provider, storage] = yield getProvider("record_current_on_init");
|
||||
|
||||
let now = new Date();
|
||||
|
||||
let current = provider.getMeasurement("current", 1);
|
||||
let values = yield current.getValues();
|
||||
let fields = values.singular;
|
||||
do_check_eq(fields.size, 6);
|
||||
do_check_eq(fields.get("main")[1], 500);
|
||||
do_check_eq(fields.get("firstPaint")[1], 1000);
|
||||
do_check_eq(fields.get("sessionRestored")[1], 1500);
|
||||
do_check_eq(fields.get("startDay")[1], provider._dateToDays(now));
|
||||
do_check_eq(fields.get("activeTime")[1], 0);
|
||||
do_check_eq(fields.get("totalTime")[1], 0);
|
||||
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_task(function test_current_moved_on_shutdown() {
|
||||
let [provider, storage] = yield getProvider("current_moved_on_shutdown");
|
||||
let now = new Date();
|
||||
|
||||
let previous = provider.getMeasurement("previous", 1);
|
||||
|
||||
yield provider.shutdown();
|
||||
|
||||
// This relies on undocumented behavior of the underlying measurement not
|
||||
// being invalidated on provider shutdown. If this breaks, we should rewrite
|
||||
// the test and not hold up implementation changes.
|
||||
let values = yield previous.getValues();
|
||||
do_check_eq(values.days.size, 1);
|
||||
do_check_true(values.days.hasDay(now));
|
||||
let fields = values.days.getDay(now);
|
||||
|
||||
// 3 startup + 2 clean.
|
||||
do_check_eq(fields.size, 5);
|
||||
for (let field of ["cleanActiveTime", "cleanTotalTime", "main", "firstPaint", "sessionRestored"]) {
|
||||
do_check_true(fields.has(field));
|
||||
do_check_true(Array.isArray(fields.get(field)));
|
||||
do_check_eq(fields.get(field).length, 1);
|
||||
}
|
||||
|
||||
do_check_eq(fields.get("main")[0], 500);
|
||||
do_check_eq(fields.get("firstPaint")[0], 1000);
|
||||
do_check_eq(fields.get("sessionRestored")[0], 1500);
|
||||
do_check_true(fields.get("cleanActiveTime")[0] > 0);
|
||||
do_check_true(fields.get("cleanTotalTime")[0] > 0);
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_task(function test_detect_abort() {
|
||||
let [provider, storage] = yield getProvider("detect_abort");
|
||||
|
||||
let now = new Date();
|
||||
|
||||
let m = provider.getMeasurement("current", 1);
|
||||
let original = yield m.getValues().singular;
|
||||
|
||||
let provider2 = new SessionsProvider();
|
||||
monkeypatchStartupInfo(provider2);
|
||||
yield provider2.init(storage);
|
||||
|
||||
let previous = provider.getMeasurement("previous", 1);
|
||||
let values = yield previous.getValues();
|
||||
do_check_true(values.days.hasDay(now));
|
||||
let day = values.days.getDay(now);
|
||||
do_check_eq(day.size, 5);
|
||||
do_check_true(day.has("abortedActiveTime"));
|
||||
do_check_true(day.has("abortedTotalTime"));
|
||||
do_check_eq(day.get("abortedActiveTime")[0], 0);
|
||||
do_check_eq(day.get("abortedTotalTime")[0], 0);
|
||||
|
||||
yield provider.shutdown();
|
||||
yield provider2.shutdown();
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
// This isn't a perfect test because we only simulate the observer
|
||||
// notifications. We should probably supplement this with a mochitest.
|
||||
add_task(function test_record_browser_activity() {
|
||||
let [provider, storage] = yield getProvider("record_browser_activity");
|
||||
|
||||
function waitOnDB () {
|
||||
return provider.enqueueStorageOperation(function () {
|
||||
return storage._connection.execute("SELECT 1");
|
||||
});
|
||||
}
|
||||
|
||||
let current = provider.getMeasurement("current", 1);
|
||||
|
||||
Services.obs.notifyObservers(null, "user-interaction-active", null);
|
||||
yield waitOnDB();
|
||||
|
||||
let values = yield current.getValues();
|
||||
let fields = values.singular;
|
||||
let activeTime = fields.get("activeTime")[1];
|
||||
let totalTime = fields.get("totalTime")[1];
|
||||
|
||||
do_check_eq(activeTime, totalTime);
|
||||
do_check_true(activeTime > 0);
|
||||
|
||||
// Another active should have similar effects.
|
||||
Services.obs.notifyObservers(null, "user-interaction-active", null);
|
||||
yield waitOnDB();
|
||||
|
||||
values = yield current.getValues();
|
||||
fields = values.singular;
|
||||
|
||||
do_check_true(fields.get("activeTime")[1] > activeTime);
|
||||
activeTime = fields.get("activeTime")[1];
|
||||
totalTime = fields.get("totalTime")[1];
|
||||
do_check_eq(activeTime, totalTime);
|
||||
|
||||
// Now send inactive. We should increment total time but not active.
|
||||
Services.obs.notifyObservers(null, "user-interaction-inactive", null);
|
||||
yield waitOnDB();
|
||||
values = yield current.getValues();
|
||||
fields = values.singular;
|
||||
do_check_eq(fields.get("activeTime")[1], activeTime);
|
||||
totalTime = fields.get("totalTime")[1];
|
||||
do_check_true(totalTime > activeTime);
|
||||
|
||||
// If we send an active again, this should be counted as inactive.
|
||||
Services.obs.notifyObservers(null, "user-interaction-active", null);
|
||||
yield waitOnDB();
|
||||
values = yield current.getValues();
|
||||
fields = values.singular;
|
||||
|
||||
do_check_eq(fields.get("activeTime")[1], activeTime);
|
||||
do_check_true(fields.get("totalTime")[1] > totalTime);
|
||||
do_check_neq(fields.get("activeTime")[1], fields.get("totalTime")[1]);
|
||||
activeTime = fields.get("activeTime")[1];
|
||||
totalTime = fields.get("totalTime")[1];
|
||||
|
||||
// Another active should increment active this time.
|
||||
Services.obs.notifyObservers(null, "user-interaction-active", null);
|
||||
yield waitOnDB();
|
||||
values = yield current.getValues();
|
||||
fields = values.singular;
|
||||
do_check_true(fields.get("activeTime")[1] > activeTime);
|
||||
do_check_true(fields.get("totalTime")[1] > totalTime);
|
||||
|
||||
yield provider.shutdown();
|
||||
yield storage.close();
|
||||
});
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
const {interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
|
||||
|
||||
function run_test() {
|
||||
@ -20,26 +20,21 @@ add_test(function test_constructor() {
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_collect_smoketest() {
|
||||
add_task(function test_collect_smoketest() {
|
||||
let storage = yield Metrics.Storage("collect_smoketest");
|
||||
let provider = new SysInfoProvider();
|
||||
yield provider.init(storage);
|
||||
|
||||
let result = provider.collectConstantMeasurements();
|
||||
do_check_true(result instanceof MetricsCollectionResult);
|
||||
yield provider.collectConstantData();
|
||||
|
||||
result.onFinished(function onFinished() {
|
||||
do_check_eq(result.expectedMeasurements.size, 1);
|
||||
do_check_true(result.expectedMeasurements.has("sysinfo"));
|
||||
do_check_eq(result.measurements.size, 1);
|
||||
do_check_true(result.measurements.has("sysinfo"));
|
||||
do_check_eq(result.errors.length, 0);
|
||||
let m = provider.getMeasurement("sysinfo", 1);
|
||||
let data = yield storage.getMeasurementValues(m.id);
|
||||
let serializer = m.serializer(m.SERIALIZE_JSON);
|
||||
let d = serializer.singular(data.singular);
|
||||
|
||||
let si = result.measurements.get("sysinfo");
|
||||
do_check_true(si.getValue("cpuCount") > 0);
|
||||
do_check_neq(si.getValue("name"), null);
|
||||
do_check_true(d.cpuCount > 0);
|
||||
do_check_neq(d.name, null);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
result.populate(result);
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
|
@ -6,6 +6,9 @@ tail =
|
||||
[test_profile.js]
|
||||
[test_policy.js]
|
||||
[test_healthreporter.js]
|
||||
[test_provider_addons.js]
|
||||
[test_provider_appinfo.js]
|
||||
[test_provider_crashes.js]
|
||||
[test_provider_sysinfo.js]
|
||||
[test_provider_sessions.js]
|
||||
|
||||
|
@ -10,7 +10,6 @@ add_makefiles "
|
||||
services/crypto/component/Makefile
|
||||
services/healthreport/Makefile
|
||||
services/metrics/Makefile
|
||||
services/notifications/Makefile
|
||||
services/sync/Makefile
|
||||
services/sync/locales/Makefile
|
||||
"
|
||||
@ -22,7 +21,6 @@ if [ "$ENABLE_TESTS" ]; then
|
||||
services/crypto/tests/Makefile
|
||||
services/healthreport/tests/Makefile
|
||||
services/metrics/tests/Makefile
|
||||
services/notifications/tests/Makefile
|
||||
services/sync/tests/Makefile
|
||||
"
|
||||
fi
|
||||
|
@ -12,12 +12,17 @@ include $(DEPTH)/config/autoconf.mk
|
||||
modules := \
|
||||
collector.jsm \
|
||||
dataprovider.jsm \
|
||||
storage.jsm \
|
||||
$(NULL)
|
||||
|
||||
testing_modules := \
|
||||
mocks.jsm \
|
||||
$(NULL)
|
||||
|
||||
# We install Metrics.jsm into the "main" JSM repository and the rest in
|
||||
# services. External consumers should only go through Metrics.jsm.
|
||||
EXTRA_JS_MODULES := Metrics.jsm
|
||||
|
||||
TEST_DIRS += tests
|
||||
|
||||
MODULES_FILES := $(modules)
|
||||
|
24
services/metrics/Metrics.jsm
Normal file
@ -0,0 +1,24 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Metrics"];
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/services/metrics/collector.jsm");
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
Cu.import("resource://gre/modules/services/metrics/storage.jsm");
|
||||
|
||||
|
||||
this.Metrics = {
|
||||
Collector: Collector,
|
||||
Measurement: Measurement,
|
||||
Provider: Provider,
|
||||
Storage: MetricsStorageBackend,
|
||||
dateToDays: dateToDays,
|
||||
daysToDate: daysToDate,
|
||||
};
|
||||
|
@ -4,11 +4,12 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["MetricsCollector"];
|
||||
this.EXPORTED_SYMBOLS = ["Collector"];
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
@ -17,44 +18,111 @@ Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
/**
|
||||
* Handles and coordinates the collection of metrics data from providers.
|
||||
*
|
||||
* This provides an interface for managing `MetricsProvider` instances. It
|
||||
* This provides an interface for managing `Metrics.Provider` instances. It
|
||||
* provides APIs for bulk collection of data.
|
||||
*/
|
||||
this.MetricsCollector = function MetricsCollector() {
|
||||
this._log = Log4Moz.repository.getLogger("Metrics.MetricsCollector");
|
||||
this.Collector = function (storage) {
|
||||
this._log = Log4Moz.repository.getLogger("Services.Metrics.Collector");
|
||||
|
||||
this._providers = [];
|
||||
this.collectionResults = new Map();
|
||||
this._providers = new Map();
|
||||
this._storage = storage;
|
||||
|
||||
this._providerInitQueue = [];
|
||||
this._providerInitializing = false;
|
||||
this.providerErrors = new Map();
|
||||
}
|
||||
|
||||
MetricsCollector.prototype = {
|
||||
Collector.prototype = Object.freeze({
|
||||
get providers() {
|
||||
let providers = [];
|
||||
for (let [name, entry] of this._providers) {
|
||||
providers.push(entry.provider);
|
||||
}
|
||||
|
||||
return providers;
|
||||
},
|
||||
|
||||
/**
|
||||
* Registers a `MetricsProvider` with this collector.
|
||||
*
|
||||
* Once a `MetricsProvider` is registered, data will be collected from it
|
||||
* whenever we collect data.
|
||||
*
|
||||
* The returned value is a promise that will be resolved once registration
|
||||
* is complete.
|
||||
*
|
||||
* Providers are initialized as part of registration by calling
|
||||
* provider.init().
|
||||
*
|
||||
* @param provider
|
||||
* (MetricsProvider) The provider instance to register.
|
||||
* (Metrics.Provider) The provider instance to register.
|
||||
*
|
||||
* @return Promise<null>
|
||||
*/
|
||||
registerProvider: function registerProvider(provider) {
|
||||
if (!(provider instanceof MetricsProvider)) {
|
||||
throw new Error("argument must be a MetricsProvider instance.");
|
||||
registerProvider: function (provider) {
|
||||
if (!(provider instanceof Provider)) {
|
||||
throw new Error("Argument must be a Provider instance.");
|
||||
}
|
||||
|
||||
for (let p of this._providers) {
|
||||
if (p.provider == provider) {
|
||||
return;
|
||||
}
|
||||
if (this._providers.has(provider.name)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this._providers.push({
|
||||
provider: provider,
|
||||
constantsCollected: false,
|
||||
});
|
||||
let deferred = Promise.defer();
|
||||
this._providerInitQueue.push([provider, deferred]);
|
||||
|
||||
if (this._providerInitQueue.length == 1) {
|
||||
this._popAndInitProvider();
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
_popAndInitProvider: function () {
|
||||
if (!this._providerInitQueue.length || this._providerInitializing) {
|
||||
return;
|
||||
}
|
||||
|
||||
let [provider, deferred] = this._providerInitQueue.pop();
|
||||
this._providerInitializing = true;
|
||||
|
||||
this._log.info("Initializing provider with storage: " + provider.name);
|
||||
let initPromise;
|
||||
try {
|
||||
initPromise = provider.init(this._storage);
|
||||
} catch (ex) {
|
||||
this._log.warn("Provider failed to initialize: " +
|
||||
CommonUtils.exceptionStr(ex));
|
||||
this._providerInitializing = false;
|
||||
deferred.reject(ex);
|
||||
this._popAndInitProvider();
|
||||
return;
|
||||
}
|
||||
|
||||
initPromise.then(
|
||||
function onSuccess(result) {
|
||||
this._log.info("Provider finished initialization: " + provider.name);
|
||||
this._providerInitializing = false;
|
||||
|
||||
this._providers.set(provider.name, {
|
||||
provider: provider,
|
||||
constantsCollected: false,
|
||||
});
|
||||
|
||||
this.providerErrors.set(provider.name, []);
|
||||
|
||||
deferred.resolve(result);
|
||||
this._popAndInitProvider();
|
||||
}.bind(this),
|
||||
function onError(error) {
|
||||
this._log.warn("Provider initialization failed: " +
|
||||
CommonUtils.exceptionStr(error));
|
||||
this._providerInitializing = false;
|
||||
deferred.reject(error);
|
||||
this._popAndInitProvider();
|
||||
}.bind(this)
|
||||
);
|
||||
|
||||
this.providerErrors.set(provider.name, []);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -63,55 +131,41 @@ MetricsCollector.prototype = {
|
||||
* Returns a Promise that will be fulfilled once all data providers have
|
||||
* provided their constant data. A side-effect of this promise fulfillment
|
||||
* is that the collector is populated with the obtained collection results.
|
||||
* The resolved value to the promise is this `MetricsCollector` instance.
|
||||
* The resolved value to the promise is this `Collector` instance.
|
||||
*/
|
||||
collectConstantMeasurements: function collectConstantMeasurements() {
|
||||
collectConstantData: function () {
|
||||
let promises = [];
|
||||
|
||||
for (let provider of this._providers) {
|
||||
let name = provider.provider.name;
|
||||
|
||||
if (provider.constantsCollected) {
|
||||
for (let [name, entry] of this._providers) {
|
||||
if (entry.constantsCollected) {
|
||||
this._log.trace("Provider has already provided constant data: " +
|
||||
name);
|
||||
continue;
|
||||
}
|
||||
|
||||
let result;
|
||||
let collectPromise;
|
||||
try {
|
||||
result = provider.provider.collectConstantMeasurements();
|
||||
collectPromise = entry.provider.collectConstantData();
|
||||
} catch (ex) {
|
||||
this._log.warn("Exception when calling " + name +
|
||||
".collectConstantMeasurements: " +
|
||||
".collectConstantData: " +
|
||||
CommonUtils.exceptionStr(ex));
|
||||
this.providerErrors.get(name).push(ex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
this._log.trace("Provider does not provide constant data: " + name);
|
||||
continue;
|
||||
if (!collectPromise) {
|
||||
throw new Error("Provider does not return a promise from " +
|
||||
"collectConstantData():" + name);
|
||||
}
|
||||
|
||||
try {
|
||||
this._log.debug("Populating constant measurements: " + name);
|
||||
result.populate(result);
|
||||
} catch (ex) {
|
||||
this._log.warn("Exception when calling " + name + ".populate(): " +
|
||||
CommonUtils.exceptionStr(ex));
|
||||
result.addError(ex);
|
||||
promises.push(Promise.resolve(result));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Chain an invisible promise that updates state.
|
||||
let promise = result.onFinished(function onFinished(result) {
|
||||
provider.constantsCollected = true;
|
||||
let promise = collectPromise.then(function onCollected(result) {
|
||||
entry.constantsCollected = true;
|
||||
|
||||
return Promise.resolve(result);
|
||||
});
|
||||
|
||||
promises.push(promise);
|
||||
promises.push([name, promise]);
|
||||
}
|
||||
|
||||
return this._handleCollectionPromises(promises);
|
||||
@ -122,8 +176,11 @@ MetricsCollector.prototype = {
|
||||
*
|
||||
* This consumes the data resolved by the promises and returns a new promise
|
||||
* that will be resolved once all promises have been resolved.
|
||||
*
|
||||
* The promise is resolved even if one of the underlying collection
|
||||
* promises is rejected.
|
||||
*/
|
||||
_handleCollectionPromises: function _handleCollectionPromises(promises) {
|
||||
_handleCollectionPromises: function (promises) {
|
||||
if (!promises.length) {
|
||||
return Promise.resolve(this);
|
||||
}
|
||||
@ -131,36 +188,24 @@ MetricsCollector.prototype = {
|
||||
let deferred = Promise.defer();
|
||||
let finishedCount = 0;
|
||||
|
||||
let onResult = function onResult(result) {
|
||||
try {
|
||||
this._log.debug("Got result for " + result.name);
|
||||
|
||||
if (this.collectionResults.has(result.name)) {
|
||||
this.collectionResults.get(result.name).aggregate(result);
|
||||
} else {
|
||||
this.collectionResults.set(result.name, result);
|
||||
}
|
||||
} finally {
|
||||
finishedCount++;
|
||||
if (finishedCount >= promises.length) {
|
||||
deferred.resolve(this);
|
||||
}
|
||||
let onComplete = function () {
|
||||
finishedCount++;
|
||||
if (finishedCount >= promises.length) {
|
||||
deferred.resolve(this);
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
let onError = function onError(error) {
|
||||
this._log.warn("Error when handling result: " +
|
||||
CommonUtils.exceptionStr(error));
|
||||
deferred.reject(error);
|
||||
}.bind(this);
|
||||
|
||||
for (let promise of promises) {
|
||||
promise.then(onResult, onError);
|
||||
for (let [name, promise] of promises) {
|
||||
let onError = function (error) {
|
||||
this._log.warn("Collection promise was rejected: " +
|
||||
CommonUtils.exceptionStr(error));
|
||||
this.providerErrors.get(name).push(error);
|
||||
onComplete();
|
||||
}.bind(this);
|
||||
promise.then(onComplete, onError);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
};
|
||||
|
||||
Object.freeze(MetricsCollector.prototype);
|
||||
});
|
||||
|
||||
|
@ -5,489 +5,531 @@
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"MetricsCollectionResult",
|
||||
"MetricsMeasurement",
|
||||
"MetricsProvider",
|
||||
"Measurement",
|
||||
"Provider",
|
||||
];
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
|
||||
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
|
||||
/**
|
||||
* Represents a measurement of data.
|
||||
* Represents a collection of related pieces/fields of data.
|
||||
*
|
||||
* This is how data is recorded and represented. Each instance of this type
|
||||
* represents a related set of data.
|
||||
* This is an abstract base type. Providers implement child types that
|
||||
* implement core functions such as `registerStorage`.
|
||||
*
|
||||
* Each data set has some basic metadata associated with it. This includes a
|
||||
* name and version.
|
||||
* This type provides the primary interface for storing, retrieving, and
|
||||
* serializing data.
|
||||
*
|
||||
* This type is meant to be an abstract base type. Child types should define
|
||||
* a `fields` property which is a mapping of field names to metadata describing
|
||||
* that field. This field constitutes the "schema" of the measurement/type.
|
||||
* Each derived type must define a `name` and `version` property. These must be
|
||||
* a string name and integer version, respectively. The `name` is used to
|
||||
* identify the measurement within a `Provider`. The version is to denote the
|
||||
* behavior of the `Measurement` and the composition of its fields over time.
|
||||
* When a new field is added or the behavior of an existing field changes
|
||||
* (perhaps the method for storing it has changed), the version should be
|
||||
* incremented.
|
||||
*
|
||||
* Data is added to instances by calling `setValue()`. Values are validated
|
||||
* against the schema at add time.
|
||||
* Each measurement consists of a set of named fields. Each field is primarily
|
||||
* identified by a string name, which must be unique within the measurement.
|
||||
*
|
||||
* Field Specification
|
||||
* ===================
|
||||
* For fields backed by the SQLite metrics storage backend, fields must have a
|
||||
* strongly defined type. Valid types include daily counters, daily discrete
|
||||
* text values, etc. See `MetricsStorageSqliteBackend.FIELD_*`.
|
||||
*
|
||||
* The `fields` property is a mapping of string field names to a mapping of
|
||||
* metadata describing the field. This mapping can have the following
|
||||
* properties:
|
||||
*
|
||||
* type -- A string corresponding to the TYPE_* property name describing a
|
||||
* field type. The TYPE_* properties are defined on this type. e.g.
|
||||
* "TYPE_STRING".
|
||||
*
|
||||
* optional -- If true, this field is optional. If omitted, the field is
|
||||
* required.
|
||||
*
|
||||
* @param name
|
||||
* (string) Name of this data set.
|
||||
* @param version
|
||||
* (Number) Integer version of the data in this set.
|
||||
* FUTURE: provide hook points for measurements to supplement with custom
|
||||
* storage needs.
|
||||
*/
|
||||
this.MetricsMeasurement = function MetricsMeasurement(name, version) {
|
||||
if (!this.fields) {
|
||||
throw new Error("fields not defined on instance. You are likely using " +
|
||||
"this type incorrectly.");
|
||||
this.Measurement = function () {
|
||||
if (!this.name) {
|
||||
throw new Error("Measurement must have a name.");
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
throw new Error("Must define a name for this measurement.");
|
||||
if (!this.version) {
|
||||
throw new Error("Measurement must have a version.");
|
||||
}
|
||||
|
||||
if (!version) {
|
||||
throw new Error("Must define a version for this measurement.");
|
||||
if (!Number.isInteger(this.version)) {
|
||||
throw new Error("Measurement's version must be an integer: " + this.version);
|
||||
}
|
||||
|
||||
if (!Number.isInteger(version)) {
|
||||
throw new Error("version must be an integer: " + version);
|
||||
}
|
||||
this._log = Log4Moz.repository.getLogger("Services.Metrics.Measurement." + this.name);
|
||||
|
||||
this.name = name;
|
||||
this.version = version;
|
||||
this.id = null;
|
||||
this.storage = null;
|
||||
this._fieldsByName = new Map();
|
||||
|
||||
this.values = new Map();
|
||||
}
|
||||
|
||||
MetricsMeasurement.prototype = {
|
||||
/**
|
||||
* An unsigned integer field stored in 32 bits.
|
||||
*
|
||||
* This holds values from 0 to 2^32 - 1.
|
||||
*/
|
||||
TYPE_UINT32: {
|
||||
validate: function validate(value) {
|
||||
if (!Number.isInteger(value)) {
|
||||
throw new Error("UINT32 field expects an integer. Got " + value);
|
||||
}
|
||||
|
||||
if (value < 0) {
|
||||
throw new Error("UINT32 field expects a positive integer. Got " + value);
|
||||
}
|
||||
|
||||
if (value >= 0xffffffff) {
|
||||
throw new Error("Value is too large to fit within 32 bits: " + value);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* A string field.
|
||||
*
|
||||
* Values must be valid UTF-8 strings.
|
||||
*/
|
||||
TYPE_STRING: {
|
||||
validate: function validate(value) {
|
||||
if (typeof(value) != "string") {
|
||||
throw new Error("STRING field expects a string. Got " + typeof(value));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the value of a field.
|
||||
*
|
||||
* This is ultimately how fields are set. All field sets should go through
|
||||
* this function.
|
||||
*
|
||||
* Values are validated when they are set. If the value passed does not
|
||||
* validate against the field's specification, an Error will be thrown.
|
||||
*
|
||||
* @param name
|
||||
* (string) The name of the field whose value to set.
|
||||
* @param value
|
||||
* The value to set the field to.
|
||||
*/
|
||||
setValue: function setValue(name, value) {
|
||||
if (!this.fields[name]) {
|
||||
throw new Error("Attempting to set unknown field: " + name);
|
||||
}
|
||||
|
||||
let type = this.fields[name].type;
|
||||
|
||||
if (!(type in this)) {
|
||||
throw new Error("Unknown field type: " + type);
|
||||
}
|
||||
|
||||
this[type].validate(value);
|
||||
this.values.set(name, value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain the value of a named field.
|
||||
*
|
||||
* @param name
|
||||
* (string) The name of the field to retrieve.
|
||||
*/
|
||||
getValue: function getValue(name) {
|
||||
return this.values.get(name);
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate that this instance is in conformance with the specification.
|
||||
*
|
||||
* This ensures all required fields are present. Field value validation
|
||||
* occurs when individual fields are set.
|
||||
*/
|
||||
validate: function validate() {
|
||||
for (let field in this.fields) {
|
||||
let spec = this.fields[field];
|
||||
|
||||
if (!spec.optional && !(field in this.values)) {
|
||||
throw new Error("Required field not defined: " + field);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
toJSON: function toJSON() {
|
||||
let fields = {};
|
||||
for (let [k, v] of this.values) {
|
||||
fields[k] = v;
|
||||
}
|
||||
|
||||
return {
|
||||
name: this.name,
|
||||
version: this.version,
|
||||
fields: fields,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
Object.freeze(MetricsMeasurement.prototype);
|
||||
|
||||
|
||||
/**
|
||||
* Entity which provides metrics data for recording.
|
||||
*
|
||||
* This essentially provides an interface that different systems must implement
|
||||
* to provide collected metrics data.
|
||||
*
|
||||
* This type consists of various collect* functions. These functions are called
|
||||
* by the metrics collector at different points during the application's
|
||||
* lifetime. These functions return a `MetricsCollectionResult` instance.
|
||||
* This type behaves a lot like a promise. It has a `onFinished()` that can chain
|
||||
* deferred events until after the result is populated.
|
||||
*
|
||||
* Implementations of collect* functions should call `createResult()` to create
|
||||
* a new `MetricsCollectionResult` instance. They should then register
|
||||
* expected measurements with this instance, define a `populate` function on
|
||||
* it, then return the instance.
|
||||
*
|
||||
* It is important for the collect* functions to just create the empty
|
||||
* `MetricsCollectionResult` and nothing more. This is to enable the callee
|
||||
* to handle errors gracefully. If the collect* function were to raise, the
|
||||
* callee may not receive a `MetricsCollectionResult` instance and it would not
|
||||
* know what data is missing.
|
||||
*
|
||||
* See the documentation for `MetricsCollectionResult` for details on how
|
||||
* to perform population.
|
||||
*
|
||||
* Receivers of created `MetricsCollectionResult` instances should wait
|
||||
* until population has finished. They can do this by chaining on to the
|
||||
* promise inside that instance by calling `onFinished()`.
|
||||
*
|
||||
* The collect* functions can return null to signify that they will never
|
||||
* provide any data. This is the default implementation. An implemented
|
||||
* collect* function should *never* return null. Instead, it should return
|
||||
* a `MetricsCollectionResult` with expected measurements that has finished
|
||||
* populating (i.e. an empty result).
|
||||
*
|
||||
* @param name
|
||||
* (string) The name of this provider.
|
||||
*/
|
||||
this.MetricsProvider = function MetricsProvider(name) {
|
||||
if (!name) {
|
||||
throw new Error("MetricsProvider must have a name.");
|
||||
}
|
||||
|
||||
if (typeof(name) != "string") {
|
||||
throw new Error("name must be a string. Got: " + typeof(name));
|
||||
}
|
||||
|
||||
this._log = Log4Moz.repository.getLogger("Services.Metrics.MetricsProvider");
|
||||
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
MetricsProvider.prototype = {
|
||||
/**
|
||||
* Collects constant measurements.
|
||||
*
|
||||
* Constant measurements are data that doesn't change during the lifetime of
|
||||
* the application/process. The metrics collector only needs to call this
|
||||
* once per `MetricsProvider` instance per process lifetime.
|
||||
*/
|
||||
collectConstantMeasurements: function collectConstantMeasurements() {
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a new `MetricsCollectionResult` tied to this provider.
|
||||
*/
|
||||
createResult: function createResult() {
|
||||
return new MetricsCollectionResult(this.name);
|
||||
},
|
||||
};
|
||||
|
||||
Object.freeze(MetricsProvider.prototype);
|
||||
|
||||
|
||||
/**
|
||||
* Holds the result of metrics collection.
|
||||
*
|
||||
* This is the type eventually returned by the MetricsProvider.collect*
|
||||
* functions. It holds all results and any state/errors that occurred while
|
||||
* collecting.
|
||||
*
|
||||
* This type is essentially a container for `MetricsMeasurement` instances that
|
||||
* provides some smarts useful for capturing state.
|
||||
*
|
||||
* The first things consumers of new instances should do is define the set of
|
||||
* expected measurements this result will contain via `expectMeasurement`. If
|
||||
* population of this instance is aborted or times out, downstream consumers
|
||||
* will know there is missing data.
|
||||
*
|
||||
* Next, they should define the `populate` property to a function that
|
||||
* populates the instance.
|
||||
*
|
||||
* The `populate` function implementation should add empty `MetricsMeasurement`
|
||||
* instances to the result via `addMeasurement`. Then, it should populate these
|
||||
* measurements via `setValue`.
|
||||
*
|
||||
* It is preferred to populate via this type instead of directly on
|
||||
* `MetricsMeasurement` instances so errors with data population can be
|
||||
* captured and reported.
|
||||
*
|
||||
* Once population has finished, `finish()` must be called.
|
||||
*
|
||||
* @param name
|
||||
* (string) The name of the provider this result came from.
|
||||
*/
|
||||
this.MetricsCollectionResult = function MetricsCollectionResult(name) {
|
||||
if (!name || typeof(name) != "string") {
|
||||
throw new Error("Must provide name argument to MetricsCollectionResult.");
|
||||
}
|
||||
|
||||
this._log = Log4Moz.repository.getLogger("Services.Metrics.MetricsCollectionResult");
|
||||
|
||||
this.name = name;
|
||||
|
||||
this.measurements = new Map();
|
||||
this.expectedMeasurements = new Set();
|
||||
this.errors = [];
|
||||
|
||||
this.populate = function populate() {
|
||||
throw new Error("populate() must be defined on MetricsCollectionResult " +
|
||||
"instance.");
|
||||
this._serializers = {};
|
||||
this._serializers[this.SERIALIZE_JSON] = {
|
||||
singular: this._serializeJSONSingular.bind(this),
|
||||
daily: this._serializeJSONDay.bind(this),
|
||||
};
|
||||
|
||||
this._deferred = Promise.defer();
|
||||
}
|
||||
|
||||
MetricsCollectionResult.prototype = {
|
||||
/**
|
||||
* The Set of `MetricsMeasurement` names currently missing from this result.
|
||||
*/
|
||||
get missingMeasurements() {
|
||||
let missing = new Set();
|
||||
Measurement.prototype = Object.freeze({
|
||||
SERIALIZE_JSON: "json",
|
||||
|
||||
for (let name of this.expectedMeasurements) {
|
||||
if (this.measurements.has(name)) {
|
||||
/**
|
||||
* Configures the storage backend so that it can store this measurement.
|
||||
*
|
||||
* Implementations must return a promise which is resolved when storage has
|
||||
* been configured.
|
||||
*
|
||||
* Most implementations will typically call into this.registerStorageField()
|
||||
* to configure fields in storage.
|
||||
*
|
||||
* FUTURE: Provide method for upgrading from older measurement versions.
|
||||
*/
|
||||
configureStorage: function () {
|
||||
throw new Error("configureStorage() must be implemented.");
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain a serializer for this measurement.
|
||||
*
|
||||
* Implementations should return an object with the following keys:
|
||||
*
|
||||
* singular -- Serializer for singular data.
|
||||
* daily -- Serializer for daily data.
|
||||
*
|
||||
* Each item is a function that takes a single argument: the data to
|
||||
* serialize. The passed data is a subset of that returned from
|
||||
* this.getValues(). For "singular," data.singular is passed. For "daily",
|
||||
* data.days.get(<day>) is passed.
|
||||
*
|
||||
* This function receives a single argument: the serialization format we
|
||||
* are requesting. This is one of the SERIALIZE_* constants on this base type.
|
||||
*
|
||||
* For SERIALIZE_JSON, the function should return an object that
|
||||
* JSON.stringify() knows how to handle. This could be an anonymous object or
|
||||
* array or any object with a property named `toJSON` whose value is a
|
||||
* function. The returned object will be added to a larger document
|
||||
* containing the results of all `serialize` calls.
|
||||
*
|
||||
* The default implementation knows how to serialize built-in types using
|
||||
* very simple logic. If small encoding size is a goal, the default
|
||||
* implementation may not be suitable. If an unknown field type is
|
||||
* encountered, the default implementation will error.
|
||||
*
|
||||
* @param format
|
||||
* (string) A SERIALIZE_* constant defining what serialization format
|
||||
* to use.
|
||||
*/
|
||||
serializer: function (format) {
|
||||
if (!(format in this._serializers)) {
|
||||
throw new Error("Don't know how to serialize format: " + format);
|
||||
}
|
||||
|
||||
return this._serializers[format];
|
||||
},
|
||||
|
||||
hasField: function (name) {
|
||||
return this._fieldsByName.has(name);
|
||||
},
|
||||
|
||||
fieldID: function (name) {
|
||||
let entry = this._fieldsByName.get(name);
|
||||
|
||||
if (!entry) {
|
||||
throw new Error("Unknown field: " + name);
|
||||
}
|
||||
|
||||
return entry[0];
|
||||
},
|
||||
|
||||
fieldType: function (name) {
|
||||
let entry = this._fieldsByName.get(name);
|
||||
|
||||
if (!entry) {
|
||||
throw new Error("Unknown field: " + name);
|
||||
}
|
||||
|
||||
return entry[1];
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a named field with storage that's attached to this measurement.
|
||||
*
|
||||
* This is typically called during `configureStorage`. The `Measurement`
|
||||
* implementation passes the field name and its type (one of the
|
||||
* storage.FIELD_* constants). The storage backend then allocates space
|
||||
* for this named field. A side-effect of calling this is that the field's
|
||||
* storage ID is stored in this._fieldsByName and subsequent calls to the
|
||||
* storage modifiers below will know how to reference this field in the
|
||||
* storage backend.
|
||||
*
|
||||
* @param name
|
||||
* (string) The name of the field being registered.
|
||||
* @param type
|
||||
* (string) A field type name. This is typically one of the
|
||||
* storage.FIELD_* constants. It could also be a custom type
|
||||
* (presumably registered by this measurement or provider).
|
||||
*/
|
||||
registerStorageField: function (name, type) {
|
||||
this._log.debug("Registering field: " + name + " " + type);
|
||||
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let self = this;
|
||||
this.storage.registerField(this.id, name, type).then(
|
||||
function onSuccess(id) {
|
||||
self._fieldsByName.set(name, [id, type]);
|
||||
deferred.resolve();
|
||||
}, deferred.reject);
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
incrementDailyCounter: function (field, date=new Date()) {
|
||||
return this.storage.incrementDailyCounterFromFieldID(this.fieldID(field),
|
||||
date);
|
||||
},
|
||||
|
||||
addDailyDiscreteNumeric: function (field, value, date=new Date()) {
|
||||
return this.storage.addDailyDiscreteNumericFromFieldID(
|
||||
this.fieldID(field), value, date);
|
||||
},
|
||||
|
||||
addDailyDiscreteText: function (field, value, date=new Date()) {
|
||||
return this.storage.addDailyDiscreteTextFromFieldID(
|
||||
this.fieldID(field), value, date);
|
||||
},
|
||||
|
||||
setLastNumeric: function (field, value, date=new Date()) {
|
||||
return this.storage.setLastNumericFromFieldID(this.fieldID(field), value,
|
||||
date);
|
||||
},
|
||||
|
||||
setLastText: function (field, value, date=new Date()) {
|
||||
return this.storage.setLastTextFromFieldID(this.fieldID(field), value,
|
||||
date);
|
||||
},
|
||||
|
||||
setDailyLastNumeric: function (field, value, date=new Date()) {
|
||||
return this.storage.setDailyLastNumericFromFieldID(this.fieldID(field),
|
||||
value, date);
|
||||
},
|
||||
|
||||
setDailyLastText: function (field, value, date=new Date()) {
|
||||
return this.storage.setDailyLastTextFromFieldID(this.fieldID(field),
|
||||
value, date);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain all values stored for this measurement.
|
||||
*
|
||||
* The default implementation obtains all known types from storage. If the
|
||||
* measurement provides custom types or stores values somewhere other than
|
||||
* storage, it should define its own implementation.
|
||||
*
|
||||
* This returns a promise that resolves to a data structure which is
|
||||
* understood by the measurement's serialize() function.
|
||||
*/
|
||||
getValues: function () {
|
||||
return this.storage.getMeasurementValues(this.id);
|
||||
},
|
||||
|
||||
deleteLastNumeric: function (field) {
|
||||
return this.storage.deleteLastNumericFromFieldID(this.fieldID(field));
|
||||
},
|
||||
|
||||
deleteLastText: function (field) {
|
||||
return this.storage.deleteLastTextFromFieldID(this.fieldID(field));
|
||||
},
|
||||
|
||||
_serializeJSONSingular: function (data) {
|
||||
let result = {};
|
||||
|
||||
for (let [field, data] of data) {
|
||||
// There could be legacy fields in storage we no longer care about.
|
||||
if (!this._fieldsByName.has(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
missing.add(name);
|
||||
let type = this.fieldType(field);
|
||||
|
||||
switch (type) {
|
||||
case this.storage.FIELD_LAST_NUMERIC:
|
||||
case this.storage.FIELD_LAST_TEXT:
|
||||
result[field] = data[1];
|
||||
break;
|
||||
|
||||
case this.storage.FIELD_DAILY_COUNTER:
|
||||
case this.storage.FIELD_DAILY_DISCRETE_NUMERIC:
|
||||
case this.storage.FIELD_DAILY_DISCRETE_TEXT:
|
||||
case this.storage.FIELD_DAILY_LAST_NUMERIC:
|
||||
case this.storage.FIELD_DAILY_LAST_TEXT:
|
||||
continue;
|
||||
|
||||
default:
|
||||
throw new Error("Unknown field type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
return missing;
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Record that this result is expected to provide a named measurement.
|
||||
*
|
||||
* This function should be called ASAP on new `MetricsCollectionResult`
|
||||
* instances. It defines expectations about what data should be present.
|
||||
*
|
||||
* @param name
|
||||
* (string) The name of the measurement this result should contain.
|
||||
*/
|
||||
expectMeasurement: function expectMeasurement(name) {
|
||||
this.expectedMeasurements.add(name);
|
||||
_serializeJSONDay: function (data) {
|
||||
let result = {};
|
||||
|
||||
for (let [field, data] of data) {
|
||||
if (!this._fieldsByName.has(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let type = this.fieldType(field);
|
||||
|
||||
switch (type) {
|
||||
case this.storage.FIELD_DAILY_COUNTER:
|
||||
case this.storage.FIELD_DAILY_DISCRETE_NUMERIC:
|
||||
case this.storage.FIELD_DAILY_DISCRETE_TEXT:
|
||||
case this.storage.FIELD_DAILY_LAST_NUMERIC:
|
||||
case this.storage.FIELD_DAILY_LAST_TEXT:
|
||||
result[field] = data;
|
||||
break;
|
||||
|
||||
case this.storage.FIELD_LAST_NUMERIC:
|
||||
case this.storage.FIELD_LAST_TEXT:
|
||||
continue;
|
||||
|
||||
default:
|
||||
throw new Error("Unknown field type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* An entity that emits data.
|
||||
*
|
||||
* A `Provider` consists of a string name (must be globally unique among all
|
||||
* known providers) and a set of `Measurement` instances.
|
||||
*
|
||||
* The main role of a `Provider` is to produce metrics data and to store said
|
||||
* data in the storage backend.
|
||||
*
|
||||
* Metrics data collection is initiated either by a collector calling a
|
||||
* `collect*` function on `Provider` instances or by the `Provider` registering
|
||||
* to some external event and then reacting whenever they occur.
|
||||
*
|
||||
* `Provider` implementations interface directly with a storage backend. For
|
||||
* common stored values (daily counters, daily discrete values, etc),
|
||||
* implementations should interface with storage via the various helper
|
||||
* functions on the `Measurement` instances. For custom stored value types,
|
||||
* implementations will interact directly with the low-level storage APIs.
|
||||
*
|
||||
* Because multiple providers exist and could be responding to separate
|
||||
* external events simultaneously and because not all operations performed by
|
||||
* storage can safely be performed in parallel, writing directly to storage at
|
||||
* event time is dangerous. Therefore, interactions with storage must be
|
||||
* deferred until it is safe to perform them.
|
||||
*
|
||||
* This typically looks something like:
|
||||
*
|
||||
* // This gets called when an external event worthy of recording metrics
|
||||
* // occurs. The function receives a numeric value associated with the event.
|
||||
* function onExternalEvent (value) {
|
||||
* let now = new Date();
|
||||
* let m = this.getMeasurement("foo", 1);
|
||||
*
|
||||
* this.enqueueStorageOperation(function storeExternalEvent() {
|
||||
*
|
||||
* // We interface with storage via the `Measurement` helper functions.
|
||||
* // These each return a promise that will be resolved when the
|
||||
* // operation finishes. We rely on behavior of storage where operations
|
||||
* // are executed single threaded and sequentially. Therefore, we only
|
||||
* // need to return the final promise.
|
||||
* m.incrementDailyCounter("foo", now);
|
||||
* return m.addDailyDiscreteNumericValue("my_value", value, now);
|
||||
* }.bind(this));
|
||||
*
|
||||
* }
|
||||
*
|
||||
*
|
||||
* `Provider` is an abstract base class. Implementations must define a few
|
||||
* properties:
|
||||
*
|
||||
* name
|
||||
* The `name` property should be a string defining the provider's name. The
|
||||
* name must be globally unique for the application. The name is used as an
|
||||
* identifier to distinguish providers from each other.
|
||||
*
|
||||
* measurementTypes
|
||||
* This must be an array of `Measurement`-derived types. Note that elements
|
||||
* in the array are the type functions, not instances. Instances of the
|
||||
* `Measurement` are created at run-time by the `Provider` and are bound
|
||||
* to the provider and to a specific storage backend.
|
||||
*/
|
||||
this.Provider = function () {
|
||||
if (!this.name) {
|
||||
throw new Error("Provider must define a name.");
|
||||
}
|
||||
|
||||
if (!Array.isArray(this.measurementTypes)) {
|
||||
throw new Error("Provider must define measurement types.");
|
||||
}
|
||||
|
||||
this._log = Log4Moz.repository.getLogger("Services.Metrics.Provider." + this.name);
|
||||
|
||||
this.measurements = null;
|
||||
this.storage = null;
|
||||
}
|
||||
|
||||
Provider.prototype = Object.freeze({
|
||||
/**
|
||||
* Add a `MetricsMeasurement` to this result.
|
||||
* Obtain a `Measurement` from its name and version.
|
||||
*
|
||||
* If the measurement is not found, an Error is thrown.
|
||||
*/
|
||||
addMeasurement: function addMeasurement(data) {
|
||||
if (!(data instanceof MetricsMeasurement)) {
|
||||
throw new Error("addMeasurement expects a MetricsMeasurement instance.");
|
||||
getMeasurement: function (name, version) {
|
||||
if (!Number.isInteger(version)) {
|
||||
throw new Error("getMeasurement expects an integer version. Got: " + version);
|
||||
}
|
||||
|
||||
if (!this.expectedMeasurements.has(data.name)) {
|
||||
throw new Error("Not expecting this measurement: " + data.name);
|
||||
}
|
||||
let m = this.measurements.get([name, version].join(":"));
|
||||
|
||||
if (this.measurements.has(data.name)) {
|
||||
throw new Error("Measurement of this name already present: " + data.name);
|
||||
}
|
||||
|
||||
this.measurements.set(data.name, data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the value of a field in a registered measurement instance.
|
||||
*
|
||||
* This is a convenience function to set a field on a measurement. If an
|
||||
* error occurs, it will record that error in the errors container.
|
||||
*
|
||||
* Attempting to set a value on a measurement that does not exist results
|
||||
* in an Error being thrown. Attempting a bad assignment on an existing
|
||||
* measurement will not throw unless `rethrow` is true.
|
||||
*
|
||||
* @param name
|
||||
* (string) The `MetricsMeasurement` on which to set the value.
|
||||
* @param field
|
||||
* (string) The field we are setting.
|
||||
* @param value
|
||||
* The value being set.
|
||||
* @param rethrow
|
||||
* (bool) Whether to rethrow any errors encountered.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the assignment was successful.
|
||||
*/
|
||||
setValue: function setValue(name, field, value, rethrow=false) {
|
||||
let m = this.measurements.get(name);
|
||||
if (!m) {
|
||||
throw new Error("Attempting to operate on an undefined measurement: " +
|
||||
name);
|
||||
throw new Error("Unknown measurement: " + name + " v" + version);
|
||||
}
|
||||
|
||||
try {
|
||||
m.setValue(field, value);
|
||||
return true;
|
||||
} catch (ex) {
|
||||
this.addError(ex);
|
||||
return m;
|
||||
},
|
||||
|
||||
if (rethrow) {
|
||||
throw ex;
|
||||
init: function (storage) {
|
||||
if (this.storage !== null) {
|
||||
throw new Error("Provider() not called. Did the sub-type forget to call it?");
|
||||
}
|
||||
|
||||
if (this.storage) {
|
||||
throw new Error("Provider has already been initialized.");
|
||||
}
|
||||
|
||||
this.measurements = new Map();
|
||||
this.storage = storage;
|
||||
|
||||
let self = this;
|
||||
return Task.spawn(function init() {
|
||||
for (let measurementType of self.measurementTypes) {
|
||||
let measurement = new measurementType();
|
||||
|
||||
measurement.provider = self;
|
||||
measurement.storage = self.storage;
|
||||
|
||||
let id = yield storage.registerMeasurement(self.name, measurement.name,
|
||||
measurement.version);
|
||||
|
||||
measurement.id = id;
|
||||
|
||||
yield measurement.configureStorage();
|
||||
|
||||
self.measurements.set([measurement.name, measurement.version].join(":"),
|
||||
measurement);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
},
|
||||
let promise = self.onInit();
|
||||
|
||||
/**
|
||||
* Record an error that was encountered when populating this result.
|
||||
*/
|
||||
addError: function addError(error) {
|
||||
this.errors.push(error);
|
||||
},
|
||||
|
||||
/**
|
||||
* Aggregate another MetricsCollectionResult into this one.
|
||||
*
|
||||
* Instances can only be aggregated together if they belong to the same
|
||||
* provider (they have the same name).
|
||||
*/
|
||||
aggregate: function aggregate(other) {
|
||||
if (!(other instanceof MetricsCollectionResult)) {
|
||||
throw new Error("aggregate expects a MetricsCollectionResult instance.");
|
||||
}
|
||||
|
||||
if (this.name != other.name) {
|
||||
throw new Error("Can only aggregate MetricsCollectionResult from " +
|
||||
"the same provider. " + this.name + " != " + other.name);
|
||||
}
|
||||
|
||||
for (let name of other.expectedMeasurements) {
|
||||
this.expectedMeasurements.add(name);
|
||||
}
|
||||
|
||||
for (let [name, m] of other.measurements) {
|
||||
if (this.measurements.has(name)) {
|
||||
throw new Error("Incoming result has same measurement as us: " + name);
|
||||
if (!promise || typeof(promise.then) != "function") {
|
||||
throw new Error("onInit() does not return a promise.");
|
||||
}
|
||||
|
||||
this.measurements.set(name, m);
|
||||
}
|
||||
|
||||
this.errors = this.errors.concat(other.errors);
|
||||
yield promise;
|
||||
});
|
||||
},
|
||||
|
||||
toJSON: function toJSON() {
|
||||
let o = {
|
||||
measurements: {},
|
||||
missing: [],
|
||||
errors: [],
|
||||
};
|
||||
shutdown: function () {
|
||||
let promise = this.onShutdown();
|
||||
|
||||
for (let [name, value] of this.measurements) {
|
||||
o.measurements[name] = value;
|
||||
if (!promise || typeof(promise.then) != "function") {
|
||||
throw new Error("onShutdown implementation does not return a promise.");
|
||||
}
|
||||
|
||||
for (let missing of this.missingMeasurements) {
|
||||
o.missing.push(missing);
|
||||
}
|
||||
|
||||
for (let error of this.errors) {
|
||||
if (error.message) {
|
||||
o.errors.push(error.message);
|
||||
} else {
|
||||
o.errors.push(error);
|
||||
}
|
||||
}
|
||||
|
||||
return o;
|
||||
return promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Signal that population of the result has finished.
|
||||
* Hook point for implementations to perform initialization activity.
|
||||
*
|
||||
* This will resolve the internal promise.
|
||||
* If a `Provider` instance needs to register observers, etc, it should
|
||||
* implement this function.
|
||||
*
|
||||
* Implementations should return a promise which is resolved when
|
||||
* initialization activities have completed.
|
||||
*/
|
||||
finish: function finish() {
|
||||
this._deferred.resolve(this);
|
||||
onInit: function () {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Chain deferred behavior until after the result has finished population.
|
||||
* Hook point for shutdown of instances.
|
||||
*
|
||||
* This is a wrapped around the internal promise's `then`.
|
||||
* This is the opposite of `onInit`. If a `Provider` needs to unregister
|
||||
* observers, etc, this is where it should do it.
|
||||
*
|
||||
* We can't call this "then" because the core promise library will get
|
||||
* confused.
|
||||
* Implementations should return a promise which is resolved when
|
||||
* shutdown activities have completed.
|
||||
*/
|
||||
onFinished: function onFinished(onFulfill, onError) {
|
||||
return this._deferred.promise.then(onFulfill, onError);
|
||||
onShutdown: function () {
|
||||
return Promise.resolve();
|
||||
},
|
||||
};
|
||||
|
||||
Object.freeze(MetricsCollectionResult.prototype);
|
||||
/**
|
||||
* Collects data that doesn't change during the application's lifetime.
|
||||
*
|
||||
* Implementations should return a promise that resolves when all data has
|
||||
* been collected and storage operations have been finished.
|
||||
*/
|
||||
collectConstantData: function () {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
/**
|
||||
* Queue a deferred storage operation.
|
||||
*
|
||||
* Deferred storage operations are the preferred method for providers to
|
||||
* interact with storage. When collected data is to be added to storage,
|
||||
* the provider creates a function that performs the necessary storage
|
||||
* interactions and then passes that function to this function. Pending
|
||||
* storage operations will be executed sequentially by a coordinator.
|
||||
*
|
||||
* The passed function should return a promise which will be resolved upon
|
||||
* completion of storage interaction.
|
||||
*/
|
||||
enqueueStorageOperation: function (func) {
|
||||
return this.storage.enqueueOperation(func);
|
||||
},
|
||||
|
||||
getState: function (key) {
|
||||
let name = this.name;
|
||||
let storage = this.storage;
|
||||
return storage.enqueueOperation(function get() {
|
||||
return storage.getProviderState(name, key);
|
||||
});
|
||||
},
|
||||
|
||||
setState: function (key, value) {
|
||||
let name = this.name;
|
||||
let storage = this.storage;
|
||||
return storage.enqueueOperation(function set() {
|
||||
return storage.setProviderState(name, key, value);
|
||||
});
|
||||
},
|
||||
|
||||
_dateToDays: function (date) {
|
||||
return Math.floor(date.getTime() / MILLISECONDS_PER_DAY);
|
||||
},
|
||||
|
||||
_daysToDate: function (days) {
|
||||
return new Date(days * MILLISECONDS_PER_DAY);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -11,64 +11,79 @@ this.EXPORTED_SYMBOLS = [
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
this.DummyMeasurement = function DummyMeasurement(name="DummyMeasurement") {
|
||||
MetricsMeasurement.call(this, name, 2);
|
||||
this.name = name;
|
||||
|
||||
Metrics.Measurement.call(this);
|
||||
}
|
||||
|
||||
DummyMeasurement.prototype = {
|
||||
__proto__: MetricsMeasurement.prototype,
|
||||
__proto__: Metrics.Measurement.prototype,
|
||||
|
||||
fields: {
|
||||
"string": {
|
||||
type: "TYPE_STRING",
|
||||
},
|
||||
version: 1,
|
||||
|
||||
"uint32": {
|
||||
type: "TYPE_UINT32",
|
||||
optional: true,
|
||||
},
|
||||
configureStorage: function () {
|
||||
let self = this;
|
||||
return Task.spawn(function configureStorage() {
|
||||
yield self.registerStorageField("daily-counter", self.storage.FIELD_DAILY_COUNTER);
|
||||
yield self.registerStorageField("daily-discrete-numeric", self.storage.FIELD_DAILY_DISCRETE_NUMERIC);
|
||||
yield self.registerStorageField("daily-discrete-text", self.storage.FIELD_DAILY_DISCRETE_TEXT);
|
||||
yield self.registerStorageField("daily-last-numeric", self.storage.FIELD_DAILY_LAST_NUMERIC);
|
||||
yield self.registerStorageField("daily-last-text", self.storage.FIELD_DAILY_LAST_TEXT);
|
||||
yield self.registerStorageField("last-numeric", self.storage.FIELD_LAST_NUMERIC);
|
||||
yield self.registerStorageField("last-text", self.storage.FIELD_LAST_TEXT);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
this.DummyProvider = function DummyProvider(name="DummyProvider") {
|
||||
MetricsProvider.call(this, name);
|
||||
this.name = name;
|
||||
|
||||
this.measurementTypes = [DummyMeasurement];
|
||||
|
||||
Metrics.Provider.call(this);
|
||||
|
||||
this.constantMeasurementName = "DummyMeasurement";
|
||||
this.collectConstantCount = 0;
|
||||
this.throwDuringCollectConstantMeasurements = null;
|
||||
this.throwDuringCollectConstantData = null;
|
||||
this.throwDuringConstantPopulate = null;
|
||||
}
|
||||
DummyProvider.prototype = {
|
||||
__proto__: MetricsProvider.prototype,
|
||||
|
||||
collectConstantMeasurements: function collectConstantMeasurements() {
|
||||
this.havePushedMeasurements = true;
|
||||
}
|
||||
|
||||
DummyProvider.prototype = {
|
||||
__proto__: Metrics.Provider.prototype,
|
||||
|
||||
collectConstantData: function () {
|
||||
this.collectConstantCount++;
|
||||
|
||||
let result = this.createResult();
|
||||
result.expectMeasurement(this.constantMeasurementName);
|
||||
|
||||
result.populate = this._populateConstantResult.bind(this);
|
||||
|
||||
if (this.throwDuringCollectConstantMeasurements) {
|
||||
throw new Error(this.throwDuringCollectConstantMeasurements);
|
||||
if (this.throwDuringCollectConstantData) {
|
||||
throw new Error(this.throwDuringCollectConstantData);
|
||||
}
|
||||
|
||||
return result;
|
||||
return this.enqueueStorageOperation(function doStorage() {
|
||||
if (this.throwDuringConstantPopulate) {
|
||||
throw new Error(this.throwDuringConstantPopulate);
|
||||
}
|
||||
|
||||
let m = this.getMeasurement("DummyMeasurement", 1);
|
||||
let now = new Date();
|
||||
m.incrementDailyCounter("daily-counter", now);
|
||||
m.addDailyDiscreteNumeric("daily-discrete-numeric", 1, now);
|
||||
m.addDailyDiscreteNumeric("daily-discrete-numeric", 2, now);
|
||||
m.addDailyDiscreteText("daily-discrete-text", "foo", now);
|
||||
m.addDailyDiscreteText("daily-discrete-text", "bar", now);
|
||||
m.setDailyLastNumeric("daily-last-numeric", 3, now);
|
||||
m.setDailyLastText("daily-last-text", "biz", now);
|
||||
m.setLastNumeric("last-numeric", 4, now);
|
||||
return m.setLastText("last-text", "bazfoo", now);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_populateConstantResult: function _populateConstantResult(result) {
|
||||
if (this.throwDuringConstantPopulate) {
|
||||
throw new Error(this.throwDuringConstantPopulate);
|
||||
}
|
||||
|
||||
this._log.debug("Populating constant measurement in DummyProvider.");
|
||||
result.addMeasurement(new DummyMeasurement(this.constantMeasurementName));
|
||||
|
||||
result.setValue(this.constantMeasurementName, "string", "foo");
|
||||
result.setValue(this.constantMeasurementName, "uint32", 24);
|
||||
|
||||
result.finish();
|
||||
},
|
||||
};
|
||||
|
||||
|
2028
services/metrics/storage.jsm
Normal file
@ -4,10 +4,12 @@
|
||||
"use strict";
|
||||
|
||||
(function initMetricsTestingInfrastructure() {
|
||||
do_get_profile();
|
||||
|
||||
let ns = {};
|
||||
Components.utils.import("resource://testing-common/services-common/logging.js",
|
||||
ns);
|
||||
|
||||
ns.initTestLogging();
|
||||
ns.initTestLogging("Trace");
|
||||
}).call(this);
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
const modules = [
|
||||
"collector.jsm",
|
||||
"dataprovider.jsm",
|
||||
"storage.jsm",
|
||||
];
|
||||
|
||||
const test_modules = [
|
||||
@ -22,5 +23,7 @@ function run_test() {
|
||||
let resource = "resource://testing-common/services/metrics/" + m;
|
||||
Components.utils.import(resource, {});
|
||||
}
|
||||
|
||||
Components.utils.import("resource://gre/modules/Metrics.jsm", {});
|
||||
}
|
||||
|
||||
|
@ -1,243 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
Cu.import("resource://testing-common/services/metrics/mocks.jsm");
|
||||
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
};
|
||||
|
||||
add_test(function test_constructor() {
|
||||
let result = new MetricsCollectionResult("foo");
|
||||
do_check_eq(result.name, "foo");
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
new MetricsCollectionResult();
|
||||
} catch (ex) {
|
||||
do_check_true(ex.message.startsWith("Must provide name argument to Metrics"));
|
||||
failed = true;
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
try {
|
||||
result.populate();
|
||||
} catch (ex) {
|
||||
do_check_true(ex.message.startsWith("populate() must be defined"));
|
||||
failed = true;
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_expected_measurements() {
|
||||
let result = new MetricsCollectionResult("foo");
|
||||
do_check_eq(result.missingMeasurements.size0);
|
||||
|
||||
result.expectMeasurement("foo");
|
||||
result.expectMeasurement("bar");
|
||||
do_check_eq(result.missingMeasurements.size, 2);
|
||||
do_check_true(result.missingMeasurements.has("foo"));
|
||||
do_check_true(result.missingMeasurements.has("bar"));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_missing_measurements() {
|
||||
let result = new MetricsCollectionResult("foo");
|
||||
|
||||
let missing = result.missingMeasurements;
|
||||
do_check_eq(missing.size, 0);
|
||||
|
||||
result.expectMeasurement("DummyMeasurement");
|
||||
result.expectMeasurement("b");
|
||||
|
||||
missing = result.missingMeasurements;
|
||||
do_check_eq(missing.size, 2);
|
||||
do_check_true(missing.has("DummyMeasurement"));
|
||||
do_check_true(missing.has("b"));
|
||||
|
||||
result.addMeasurement(new DummyMeasurement());
|
||||
missing = result.missingMeasurements;
|
||||
do_check_eq(missing.size, 1);
|
||||
do_check_true(missing.has("b"));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_add_measurement() {
|
||||
let result = new MetricsCollectionResult("add_measurement");
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
result.addMeasurement(new DummyMeasurement());
|
||||
} catch (ex) {
|
||||
do_check_true(ex.message.startsWith("Not expecting this measurement"));
|
||||
failed = true;
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
result.expectMeasurement("foo");
|
||||
result.addMeasurement(new DummyMeasurement("foo"));
|
||||
|
||||
do_check_eq(result.measurements.size, 1);
|
||||
do_check_true(result.measurements.has("foo"));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_set_value() {
|
||||
let result = new MetricsCollectionResult("set_value");
|
||||
result.expectMeasurement("DummyMeasurement");
|
||||
result.addMeasurement(new DummyMeasurement());
|
||||
|
||||
do_check_true(result.setValue("DummyMeasurement", "string", "hello world"));
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
result.setValue("unknown", "irrelevant", "irrelevant");
|
||||
} catch (ex) {
|
||||
do_check_true(ex.message.startsWith("Attempting to operate on an undefined measurement"));
|
||||
failed = true;
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
do_check_eq(result.errors.length, 0);
|
||||
do_check_false(result.setValue("DummyMeasurement", "string", 42));
|
||||
do_check_eq(result.errors.length, 1);
|
||||
|
||||
try {
|
||||
result.setValue("DummyMeasurement", "string", 42, true);
|
||||
} catch (ex) {
|
||||
failed = true;
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_aggregate_bad_argument() {
|
||||
let result = new MetricsCollectionResult("bad_argument");
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
result.aggregate(null);
|
||||
} catch (ex) {
|
||||
do_check_true(ex.message.startsWith("aggregate expects a MetricsCollection"));
|
||||
failed = true;
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
try {
|
||||
let result2 = new MetricsCollectionResult("bad_argument2");
|
||||
result.aggregate(result2);
|
||||
} catch (ex) {
|
||||
do_check_true(ex.message.startsWith("Can only aggregate"));
|
||||
failed = true;
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_aggregate_side_effects() {
|
||||
let result1 = new MetricsCollectionResult("aggregate");
|
||||
let result2 = new MetricsCollectionResult("aggregate");
|
||||
|
||||
result1.expectMeasurement("dummy1");
|
||||
result1.expectMeasurement("foo");
|
||||
|
||||
result2.expectMeasurement("dummy2");
|
||||
result2.expectMeasurement("bar");
|
||||
|
||||
result1.addMeasurement(new DummyMeasurement("dummy1"));
|
||||
result1.setValue("dummy1", "invalid", "invalid");
|
||||
|
||||
result2.addMeasurement(new DummyMeasurement("dummy2"));
|
||||
result2.setValue("dummy2", "another", "invalid");
|
||||
|
||||
result1.aggregate(result2);
|
||||
|
||||
do_check_eq(result1.expectedMeasurements.size, 4);
|
||||
do_check_true(result1.expectedMeasurements.has("bar"));
|
||||
|
||||
do_check_eq(result1.measurements.size, 2);
|
||||
do_check_true(result1.measurements.has("dummy1"));
|
||||
do_check_true(result1.measurements.has("dummy2"));
|
||||
|
||||
do_check_eq(result1.missingMeasurements.size, 2);
|
||||
do_check_true(result1.missingMeasurements.has("bar"));
|
||||
|
||||
do_check_eq(result1.errors.length, 2);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_json() {
|
||||
let result = new MetricsCollectionResult("json");
|
||||
result.expectMeasurement("dummy1");
|
||||
result.expectMeasurement("dummy2");
|
||||
result.expectMeasurement("missing1");
|
||||
result.expectMeasurement("missing2");
|
||||
|
||||
result.addMeasurement(new DummyMeasurement("dummy1"));
|
||||
result.addMeasurement(new DummyMeasurement("dummy2"));
|
||||
|
||||
result.setValue("dummy1", "string", "hello world");
|
||||
result.setValue("dummy2", "uint32", 42);
|
||||
result.setValue("dummy1", "invalid", "irrelevant");
|
||||
|
||||
let json = JSON.parse(JSON.stringify(result));
|
||||
|
||||
do_check_eq(Object.keys(json).length, 3);
|
||||
do_check_true("measurements" in json);
|
||||
do_check_true("missing" in json);
|
||||
do_check_true("errors" in json);
|
||||
|
||||
do_check_eq(Object.keys(json.measurements).length, 2);
|
||||
do_check_true("dummy1" in json.measurements);
|
||||
do_check_true("dummy2" in json.measurements);
|
||||
|
||||
do_check_eq(json.missing.length, 2);
|
||||
let missing = new Set(json.missing);
|
||||
do_check_true(missing.has("missing1"));
|
||||
do_check_true(missing.has("missing2"));
|
||||
|
||||
do_check_eq(json.errors.length, 1);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_finish() {
|
||||
let result = new MetricsCollectionResult("finish");
|
||||
|
||||
result.onFinished(function onFinished(result2) {
|
||||
do_check_eq(result, result2);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
result.finish();
|
||||
});
|
||||
|
@ -5,158 +5,123 @@
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/services/metrics/collector.jsm");
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://testing-common/services/metrics/mocks.jsm");
|
||||
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
};
|
||||
|
||||
add_test(function test_constructor() {
|
||||
let collector = new MetricsCollector();
|
||||
add_task(function test_constructor() {
|
||||
let storage = yield Metrics.Storage("constructor");
|
||||
let collector = new Metrics.Collector(storage);
|
||||
|
||||
run_next_test();
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_test(function test_register_provider() {
|
||||
let collector = new MetricsCollector();
|
||||
add_task(function test_register_provider() {
|
||||
let storage = yield Metrics.Storage("register_provider");
|
||||
|
||||
let collector = new Metrics.Collector(storage);
|
||||
let dummy = new DummyProvider();
|
||||
|
||||
collector.registerProvider(dummy);
|
||||
do_check_eq(collector._providers.length, 1);
|
||||
collector.registerProvider(dummy);
|
||||
do_check_eq(collector._providers.length, 1);
|
||||
yield collector.registerProvider(dummy);
|
||||
do_check_eq(collector._providers.size, 1);
|
||||
yield collector.registerProvider(dummy);
|
||||
do_check_eq(collector._providers.size, 1);
|
||||
do_check_eq(collector.providerErrors.size, 1);
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
collector.registerProvider({});
|
||||
} catch (ex) {
|
||||
do_check_true(ex.message.startsWith("argument must be a MetricsProvider"));
|
||||
do_check_true(ex.message.startsWith("Argument must be a Provider"));
|
||||
failed = true;
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_test(function test_collect_constant_measurements() {
|
||||
let collector = new MetricsCollector();
|
||||
add_task(function test_collect_constant_data() {
|
||||
let storage = yield Metrics.Storage("collect_constant_data");
|
||||
let collector = new Metrics.Collector(storage);
|
||||
let provider = new DummyProvider();
|
||||
collector.registerProvider(provider);
|
||||
yield collector.registerProvider(provider);
|
||||
|
||||
do_check_eq(provider.collectConstantCount, 0);
|
||||
|
||||
collector.collectConstantMeasurements().then(function onResult() {
|
||||
do_check_eq(provider.collectConstantCount, 1);
|
||||
do_check_eq(collector.collectionResults.size, 1);
|
||||
do_check_true(collector.collectionResults.has("DummyProvider"));
|
||||
yield collector.collectConstantData();
|
||||
do_check_eq(provider.collectConstantCount, 1);
|
||||
|
||||
let result = collector.collectionResults.get("DummyProvider");
|
||||
do_check_true(result instanceof MetricsCollectionResult);
|
||||
do_check_true(collector._providers.get("DummyProvider").constantsCollected);
|
||||
do_check_eq(collector.providerErrors.get("DummyProvider").length, 0);
|
||||
|
||||
do_check_true(collector._providers[0].constantsCollected);
|
||||
do_check_eq(collector.providerErrors.get("DummyProvider").length, 0);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_test(function test_collect_constant_throws() {
|
||||
let collector = new MetricsCollector();
|
||||
add_task(function test_collect_constant_throws() {
|
||||
let storage = yield Metrics.Storage("collect_constant_throws");
|
||||
let collector = new Metrics.Collector(storage);
|
||||
let provider = new DummyProvider();
|
||||
provider.throwDuringCollectConstantMeasurements = "Fake error during collect";
|
||||
collector.registerProvider(provider);
|
||||
provider.throwDuringCollectConstantData = "Fake error during collect";
|
||||
yield collector.registerProvider(provider);
|
||||
|
||||
collector.collectConstantMeasurements().then(function onResult() {
|
||||
do_check_eq(collector.providerErrors.get("DummyProvider").length, 1);
|
||||
do_check_eq(collector.providerErrors.get("DummyProvider")[0].message,
|
||||
provider.throwDuringCollectConstantMeasurements);
|
||||
yield collector.collectConstantData();
|
||||
do_check_true(collector.providerErrors.has(provider.name));
|
||||
let errors = collector.providerErrors.get(provider.name);
|
||||
do_check_eq(errors.length, 1);
|
||||
do_check_eq(errors[0].message, provider.throwDuringCollectConstantData);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_test(function test_collect_constant_populate_throws() {
|
||||
let collector = new MetricsCollector();
|
||||
add_task(function test_collect_constant_populate_throws() {
|
||||
let storage = yield Metrics.Storage("collect_constant_populate_throws");
|
||||
let collector = new Metrics.Collector(storage);
|
||||
let provider = new DummyProvider();
|
||||
provider.throwDuringConstantPopulate = "Fake error during constant populate";
|
||||
collector.registerProvider(provider);
|
||||
yield collector.registerProvider(provider);
|
||||
|
||||
collector.collectConstantMeasurements().then(function onResult() {
|
||||
do_check_eq(collector.collectionResults.size, 1);
|
||||
do_check_true(collector.collectionResults.has("DummyProvider"));
|
||||
yield collector.collectConstantData();
|
||||
|
||||
let result = collector.collectionResults.get("DummyProvider");
|
||||
do_check_eq(result.errors.length, 1);
|
||||
do_check_eq(result.errors[0].message, provider.throwDuringConstantPopulate);
|
||||
let errors = collector.providerErrors.get(provider.name);
|
||||
do_check_eq(errors.length, 1);
|
||||
do_check_eq(errors[0].message, provider.throwDuringConstantPopulate);
|
||||
do_check_false(collector._providers.get(provider.name).constantsCollected);
|
||||
|
||||
do_check_false(collector._providers[0].constantsCollected);
|
||||
do_check_eq(collector.providerErrors.get("DummyProvider").length, 0);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_test(function test_collect_constant_onetime() {
|
||||
let collector = new MetricsCollector();
|
||||
add_task(function test_collect_constant_onetime() {
|
||||
let storage = yield Metrics.Storage("collect_constant_onetime");
|
||||
let collector = new Metrics.Collector(storage);
|
||||
let provider = new DummyProvider();
|
||||
collector.registerProvider(provider);
|
||||
yield collector.registerProvider(provider);
|
||||
|
||||
collector.collectConstantMeasurements().then(function onResult() {
|
||||
do_check_eq(provider.collectConstantCount, 1);
|
||||
yield collector.collectConstantData();
|
||||
do_check_eq(provider.collectConstantCount, 1);
|
||||
|
||||
collector.collectConstantMeasurements().then(function onResult() {
|
||||
do_check_eq(provider.collectConstantCount, 1);
|
||||
yield collector.collectConstantData();
|
||||
do_check_eq(provider.collectConstantCount, 1);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_test(function test_collect_multiple() {
|
||||
let collector = new MetricsCollector();
|
||||
add_task(function test_collect_multiple() {
|
||||
let storage = yield Metrics.Storage("collect_multiple");
|
||||
let collector = new Metrics.Collector(storage);
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
collector.registerProvider(new DummyProvider("provider" + i));
|
||||
yield collector.registerProvider(new DummyProvider("provider" + i));
|
||||
}
|
||||
|
||||
do_check_eq(collector._providers.length, 10);
|
||||
do_check_eq(collector._providers.size, 10);
|
||||
|
||||
collector.collectConstantMeasurements().then(function onResult(innerCollector) {
|
||||
do_check_eq(collector, innerCollector);
|
||||
do_check_eq(collector.collectionResults.size, 10);
|
||||
yield collector.collectConstantData();
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_collect_aggregate() {
|
||||
let collector = new MetricsCollector();
|
||||
|
||||
let dummy1 = new DummyProvider();
|
||||
dummy1.constantMeasurementName = "measurement1";
|
||||
|
||||
let dummy2 = new DummyProvider();
|
||||
dummy2.constantMeasurementName = "measurement2";
|
||||
|
||||
collector.registerProvider(dummy1);
|
||||
collector.registerProvider(dummy2);
|
||||
do_check_eq(collector._providers.length, 2);
|
||||
|
||||
collector.collectConstantMeasurements().then(function onResult() {
|
||||
do_check_eq(collector.collectionResults.size, 1);
|
||||
|
||||
let measurements = collector.collectionResults.get("DummyProvider").measurements;
|
||||
do_check_eq(measurements.size, 2);
|
||||
do_check_true(measurements.has("measurement1"));
|
||||
do_check_true(measurements.has("measurement2"));
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
|
@ -1,115 +0,0 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://testing-common/services/metrics/mocks.jsm");
|
||||
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
};
|
||||
|
||||
add_test(function test_constructor() {
|
||||
let m = new DummyMeasurement();
|
||||
do_check_eq(m.name, "DummyMeasurement");
|
||||
do_check_eq(m.version, 2);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_add_string() {
|
||||
let m = new DummyMeasurement();
|
||||
|
||||
m.setValue("string", "hello world");
|
||||
do_check_eq(m.getValue("string"), "hello world");
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
m.setValue("string", 46);
|
||||
} catch (ex) {
|
||||
do_check_true(ex.message.startsWith("STRING field expects a string"));
|
||||
failed = true;
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_add_uint32() {
|
||||
let m = new DummyMeasurement();
|
||||
|
||||
m.setValue("uint32", 52342);
|
||||
do_check_eq(m.getValue("uint32"), 52342);
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
m.setValue("uint32", -1);
|
||||
} catch (ex) {
|
||||
failed = true;
|
||||
do_check_true(ex.message.startsWith("UINT32 field expects a positive"));
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
try {
|
||||
m.setValue("uint32", "foo");
|
||||
} catch (ex) {
|
||||
failed = true;
|
||||
do_check_true(ex.message.startsWith("UINT32 field expects an integer"));
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
try {
|
||||
m.setValue("uint32", Math.pow(2, 32));
|
||||
} catch (ex) {
|
||||
failed = true;
|
||||
do_check_true(ex.message.startsWith("Value is too large"));
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_validate() {
|
||||
let m = new DummyMeasurement();
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
m.validate();
|
||||
} catch (ex) {
|
||||
failed = true;
|
||||
do_check_true(ex.message.startsWith("Required field not defined"));
|
||||
} finally {
|
||||
do_check_true(failed);
|
||||
failed = false;
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_toJSON() {
|
||||
let m = new DummyMeasurement();
|
||||
|
||||
m.setValue("string", "foo bar");
|
||||
|
||||
let json = JSON.parse(JSON.stringify(m));
|
||||
do_check_eq(Object.keys(json).length, 3);
|
||||
do_check_eq(json.name, "DummyMeasurement");
|
||||
do_check_eq(json.version, 2);
|
||||
do_check_true("fields" in json);
|
||||
|
||||
do_check_eq(Object.keys(json.fields).length, 1);
|
||||
do_check_eq(json.fields.string, "foo bar");
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
@ -5,22 +5,36 @@
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://testing-common/services/metrics/mocks.jsm");
|
||||
|
||||
|
||||
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
|
||||
function getProvider(storageName) {
|
||||
return Task.spawn(function () {
|
||||
let provider = new DummyProvider();
|
||||
let storage = yield Metrics.Storage(storageName);
|
||||
|
||||
yield provider.init(storage);
|
||||
|
||||
throw new Task.Result(provider);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
};
|
||||
|
||||
add_test(function test_constructor() {
|
||||
let provider = new MetricsProvider("foo");
|
||||
|
||||
let failed = false;
|
||||
try {
|
||||
new MetricsProvider();
|
||||
new Metrics.Provider();
|
||||
} catch(ex) {
|
||||
do_check_true(ex.message.startsWith("MetricsProvider must have a name"));
|
||||
do_check_true(ex.message.startsWith("Provider must define a name"));
|
||||
failed = true;
|
||||
}
|
||||
finally {
|
||||
@ -30,35 +44,231 @@ add_test(function test_constructor() {
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_default_collectors() {
|
||||
let provider = new MetricsProvider("foo");
|
||||
add_task(function test_init() {
|
||||
let provider = new DummyProvider();
|
||||
let storage = yield Metrics.Storage("init");
|
||||
|
||||
for (let property in MetricsProvider.prototype) {
|
||||
yield provider.init(storage);
|
||||
|
||||
let m = provider.getMeasurement("DummyMeasurement", 1);
|
||||
do_check_true(m instanceof Metrics.Measurement);
|
||||
do_check_eq(m.id, 1);
|
||||
do_check_eq(m._fieldsByName.size, 7);
|
||||
do_check_true(m.hasField("daily-counter"));
|
||||
do_check_false(m.hasField("does-not-exist"));
|
||||
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_task(function test_default_collectors() {
|
||||
let provider = new DummyProvider();
|
||||
let storage = yield Metrics.Storage("default_collectors");
|
||||
yield provider.init(storage);
|
||||
|
||||
for (let property in Metrics.Provider.prototype) {
|
||||
if (!property.startsWith("collect")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let result = provider[property]();
|
||||
do_check_null(result);
|
||||
do_check_neq(result, null);
|
||||
do_check_eq(typeof(result.then), "function");
|
||||
}
|
||||
|
||||
run_next_test();
|
||||
yield storage.close();
|
||||
});
|
||||
|
||||
add_test(function test_collect_constant_synchronous() {
|
||||
let provider = new DummyProvider();
|
||||
add_task(function test_measurement_storage_basic() {
|
||||
let provider = yield getProvider("measurement_storage_basic");
|
||||
let m = provider.getMeasurement("DummyMeasurement", 1);
|
||||
|
||||
let result = provider.collectConstantMeasurements();
|
||||
do_check_true(result instanceof MetricsCollectionResult);
|
||||
result.populate(result);
|
||||
let now = new Date();
|
||||
let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
|
||||
|
||||
result.onFinished(function onResult(res2) {
|
||||
do_check_eq(result, res2);
|
||||
// Daily counter.
|
||||
let counterID = m.fieldID("daily-counter");
|
||||
yield m.incrementDailyCounter("daily-counter", now);
|
||||
yield m.incrementDailyCounter("daily-counter", now);
|
||||
yield m.incrementDailyCounter("daily-counter", yesterday);
|
||||
let count = yield provider.storage.getDailyCounterCountFromFieldID(counterID, now);
|
||||
do_check_eq(count, 2);
|
||||
|
||||
let m = result.measurements.get("DummyMeasurement");
|
||||
do_check_eq(m.getValue("uint32"), 24);
|
||||
count = yield provider.storage.getDailyCounterCountFromFieldID(counterID, yesterday);
|
||||
do_check_eq(count, 1);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
// Daily discrete numeric.
|
||||
let dailyDiscreteNumericID = m.fieldID("daily-discrete-numeric");
|
||||
yield m.addDailyDiscreteNumeric("daily-discrete-numeric", 5, now);
|
||||
yield m.addDailyDiscreteNumeric("daily-discrete-numeric", 6, now);
|
||||
yield m.addDailyDiscreteNumeric("daily-discrete-numeric", 7, yesterday);
|
||||
|
||||
let values = yield provider.storage.getDailyDiscreteNumericFromFieldID(
|
||||
dailyDiscreteNumericID, now);
|
||||
|
||||
do_check_eq(values.size, 1);
|
||||
do_check_true(values.hasDay(now));
|
||||
let actual = values.getDay(now);
|
||||
do_check_eq(actual.length, 2);
|
||||
do_check_eq(actual[0], 5);
|
||||
do_check_eq(actual[1], 6);
|
||||
|
||||
values = yield provider.storage.getDailyDiscreteNumericFromFieldID(
|
||||
dailyDiscreteNumericID, yesterday);
|
||||
|
||||
do_check_eq(values.size, 1);
|
||||
do_check_true(values.hasDay(yesterday));
|
||||
do_check_eq(values.getDay(yesterday)[0], 7);
|
||||
|
||||
// Daily discrete text.
|
||||
let dailyDiscreteTextID = m.fieldID("daily-discrete-text");
|
||||
yield m.addDailyDiscreteText("daily-discrete-text", "foo", now);
|
||||
yield m.addDailyDiscreteText("daily-discrete-text", "bar", now);
|
||||
yield m.addDailyDiscreteText("daily-discrete-text", "biz", yesterday);
|
||||
|
||||
values = yield provider.storage.getDailyDiscreteTextFromFieldID(
|
||||
dailyDiscreteTextID, now);
|
||||
|
||||
do_check_eq(values.size, 1);
|
||||
do_check_true(values.hasDay(now));
|
||||
actual = values.getDay(now);
|
||||
do_check_eq(actual.length, 2);
|
||||
do_check_eq(actual[0], "foo");
|
||||
do_check_eq(actual[1], "bar");
|
||||
|
||||
values = yield provider.storage.getDailyDiscreteTextFromFieldID(
|
||||
dailyDiscreteTextID, yesterday);
|
||||
do_check_true(values.hasDay(yesterday));
|
||||
do_check_eq(values.getDay(yesterday)[0], "biz");
|
||||
|
||||
// Daily last numeric.
|
||||
let lastDailyNumericID = m.fieldID("daily-last-numeric");
|
||||
yield m.setDailyLastNumeric("daily-last-numeric", 5, now);
|
||||
yield m.setDailyLastNumeric("daily-last-numeric", 6, yesterday);
|
||||
|
||||
let result = yield provider.storage.getDailyLastNumericFromFieldID(
|
||||
lastDailyNumericID, now);
|
||||
do_check_eq(result.size, 1);
|
||||
do_check_true(result.hasDay(now));
|
||||
do_check_eq(result.getDay(now), 5);
|
||||
|
||||
result = yield provider.storage.getDailyLastNumericFromFieldID(
|
||||
lastDailyNumericID, yesterday);
|
||||
do_check_true(result.hasDay(yesterday));
|
||||
do_check_eq(result.getDay(yesterday), 6);
|
||||
|
||||
yield m.setDailyLastNumeric("daily-last-numeric", 7, now);
|
||||
result = yield provider.storage.getDailyLastNumericFromFieldID(
|
||||
lastDailyNumericID, now);
|
||||
do_check_eq(result.getDay(now), 7);
|
||||
|
||||
// Daily last text.
|
||||
let lastDailyTextID = m.fieldID("daily-last-text");
|
||||
yield m.setDailyLastText("daily-last-text", "foo", now);
|
||||
yield m.setDailyLastText("daily-last-text", "bar", yesterday);
|
||||
|
||||
result = yield provider.storage.getDailyLastTextFromFieldID(
|
||||
lastDailyTextID, now);
|
||||
do_check_eq(result.size, 1);
|
||||
do_check_true(result.hasDay(now));
|
||||
do_check_eq(result.getDay(now), "foo");
|
||||
|
||||
result = yield provider.storage.getDailyLastTextFromFieldID(
|
||||
lastDailyTextID, yesterday);
|
||||
do_check_true(result.hasDay(yesterday));
|
||||
do_check_eq(result.getDay(yesterday), "bar");
|
||||
|
||||
yield m.setDailyLastText("daily-last-text", "biz", now);
|
||||
result = yield provider.storage.getDailyLastTextFromFieldID(
|
||||
lastDailyTextID, now);
|
||||
do_check_eq(result.getDay(now), "biz");
|
||||
|
||||
// Last numeric.
|
||||
let lastNumericID = m.fieldID("last-numeric");
|
||||
yield m.setLastNumeric("last-numeric", 1, now);
|
||||
result = yield provider.storage.getLastNumericFromFieldID(lastNumericID);
|
||||
do_check_eq(result[1], 1);
|
||||
do_check_true(result[0].getTime() < now.getTime());
|
||||
do_check_true(result[0].getTime() > yesterday.getTime());
|
||||
|
||||
yield m.setLastNumeric("last-numeric", 2, now);
|
||||
result = yield provider.storage.getLastNumericFromFieldID(lastNumericID);
|
||||
do_check_eq(result[1], 2);
|
||||
|
||||
// Last text.
|
||||
let lastTextID = m.fieldID("last-text");
|
||||
yield m.setLastText("last-text", "foo", now);
|
||||
result = yield provider.storage.getLastTextFromFieldID(lastTextID);
|
||||
do_check_eq(result[1], "foo");
|
||||
do_check_true(result[0].getTime() < now.getTime());
|
||||
do_check_true(result[0].getTime() > yesterday.getTime());
|
||||
|
||||
yield m.setLastText("last-text", "bar", now);
|
||||
result = yield provider.storage.getLastTextFromFieldID(lastTextID);
|
||||
do_check_eq(result[1], "bar");
|
||||
|
||||
yield provider.storage.close();
|
||||
});
|
||||
|
||||
add_task(function test_serialize_json_default() {
|
||||
let provider = yield getProvider("serialize_json_default");
|
||||
|
||||
let now = new Date();
|
||||
let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
|
||||
|
||||
let m = provider.getMeasurement("DummyMeasurement", 1);
|
||||
|
||||
m.incrementDailyCounter("daily-counter", now);
|
||||
m.incrementDailyCounter("daily-counter", now);
|
||||
m.incrementDailyCounter("daily-counter", yesterday);
|
||||
|
||||
m.addDailyDiscreteNumeric("daily-discrete-numeric", 1, now);
|
||||
m.addDailyDiscreteNumeric("daily-discrete-numeric", 2, now);
|
||||
m.addDailyDiscreteNumeric("daily-discrete-numeric", 3, yesterday);
|
||||
|
||||
m.addDailyDiscreteText("daily-discrete-text", "foo", now);
|
||||
m.addDailyDiscreteText("daily-discrete-text", "bar", now);
|
||||
m.addDailyDiscreteText("daily-discrete-text", "baz", yesterday);
|
||||
|
||||
m.setDailyLastNumeric("daily-last-numeric", 4, now);
|
||||
m.setDailyLastNumeric("daily-last-numeric", 5, yesterday);
|
||||
|
||||
m.setDailyLastText("daily-last-text", "apple", now);
|
||||
m.setDailyLastText("daily-last-text", "orange", yesterday);
|
||||
|
||||
m.setLastNumeric("last-numeric", 6, now);
|
||||
yield m.setLastText("last-text", "hello", now);
|
||||
|
||||
let data = yield provider.storage.getMeasurementValues(m.id);
|
||||
|
||||
let serializer = m.serializer(m.SERIALIZE_JSON);
|
||||
let formatted = serializer.singular(data.singular);
|
||||
|
||||
do_check_eq(Object.keys(formatted).length, 2);
|
||||
do_check_true("last-numeric" in formatted);
|
||||
do_check_true("last-text" in formatted);
|
||||
do_check_eq(formatted["last-numeric"], 6);
|
||||
do_check_eq(formatted["last-text"], "hello");
|
||||
|
||||
formatted = serializer.daily(data.days.getDay(now));
|
||||
do_check_eq(Object.keys(formatted).length, 5);
|
||||
do_check_eq(formatted["daily-counter"], 2);
|
||||
|
||||
do_check_true(Array.isArray(formatted["daily-discrete-numeric"]));
|
||||
do_check_eq(formatted["daily-discrete-numeric"].length, 2);
|
||||
do_check_eq(formatted["daily-discrete-numeric"][0], 1);
|
||||
do_check_eq(formatted["daily-discrete-numeric"][1], 2);
|
||||
|
||||
do_check_true(Array.isArray(formatted["daily-discrete-text"]));
|
||||
do_check_eq(formatted["daily-discrete-text"].length, 2);
|
||||
do_check_eq(formatted["daily-discrete-text"][0], "foo");
|
||||
do_check_eq(formatted["daily-discrete-text"][1], "bar");
|
||||
|
||||
do_check_eq(formatted["daily-last-numeric"], 4);
|
||||
do_check_eq(formatted["daily-last-text"], "apple");
|
||||
|
||||
formatted = serializer.daily(data.days.getDay(yesterday));
|
||||
do_check_eq(formatted["daily-last-numeric"], 5);
|
||||
do_check_eq(formatted["daily-last-text"], "orange");
|
||||
|
||||
yield provider.storage.close();
|
||||
});
|
||||
|
721
services/metrics/tests/xpcshell/test_metrics_storage.js
Normal file
@ -0,0 +1,721 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/commonjs/promise/core.js");
|
||||
Cu.import("resource://gre/modules/Metrics.jsm");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
|
||||
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
|
||||
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_days_date_conversion() {
|
||||
let toDays = Metrics.dateToDays;
|
||||
let toDate = Metrics.daysToDate;
|
||||
|
||||
let d = new Date(0);
|
||||
do_check_eq(toDays(d), 0);
|
||||
|
||||
d = new Date(MILLISECONDS_PER_DAY);
|
||||
do_check_eq(toDays(d), 1);
|
||||
|
||||
d = new Date(MILLISECONDS_PER_DAY - 1);
|
||||
do_check_eq(toDays(d), 0);
|
||||
|
||||
d = new Date("1970-12-31T23:59:59.999Z");
|
||||
do_check_eq(toDays(d), 364);
|
||||
|
||||
d = new Date("1971-01-01T00:00:00Z");
|
||||
do_check_eq(toDays(d), 365);
|
||||
|
||||
d = toDate(0);
|
||||
do_check_eq(d.getTime(), 0);
|
||||
|
||||
d = toDate(1);
|
||||
do_check_eq(d.getTime(), MILLISECONDS_PER_DAY);
|
||||
|
||||
d = toDate(365);
|
||||
do_check_eq(d.getUTCFullYear(), 1971);
|
||||
do_check_eq(d.getUTCMonth(), 0);
|
||||
do_check_eq(d.getUTCDate(), 1);
|
||||
do_check_eq(d.getUTCHours(), 0);
|
||||
do_check_eq(d.getUTCMinutes(), 0);
|
||||
do_check_eq(d.getUTCSeconds(), 0);
|
||||
do_check_eq(d.getUTCMilliseconds(), 0);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(function test_get_sqlite_backend() {
|
||||
let backend = yield Metrics.Storage("get_sqlite_backend.sqlite");
|
||||
|
||||
do_check_neq(backend._connection, null);
|
||||
|
||||
yield backend.close();
|
||||
do_check_null(backend._connection);
|
||||
});
|
||||
|
||||
add_task(function test_reconnect() {
|
||||
let backend = yield Metrics.Storage("reconnect");
|
||||
yield backend.close();
|
||||
|
||||
let backend2 = yield Metrics.Storage("reconnect");
|
||||
yield backend2.close();
|
||||
});
|
||||
|
||||
add_task(function test_future_schema_errors() {
|
||||
let backend = yield Metrics.Storage("future_schema_errors");
|
||||
backend._connection.schemaVersion = 2;
|
||||
yield backend.close();
|
||||
|
||||
let backend2;
|
||||
let failed = false;
|
||||
try {
|
||||
backend2 = yield Metrics.Storage("future_schema_errors");
|
||||
} catch (ex) {
|
||||
failed = true;
|
||||
do_check_true(ex.message.startsWith("Unknown database schema"));
|
||||
}
|
||||
|
||||
do_check_null(backend2);
|
||||
do_check_true(failed);
|
||||
});
|
||||
|
||||
add_task(function test_measurement_registration() {
|
||||
let backend = yield Metrics.Storage("measurement_registration");
|
||||
|
||||
do_check_false(backend.hasProvider("foo"));
|
||||
do_check_false(backend.hasMeasurement("foo", "bar", 1));
|
||||
|
||||
let id = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
do_check_eq(id, 1);
|
||||
|
||||
do_check_true(backend.hasProvider("foo"));
|
||||
do_check_true(backend.hasMeasurement("foo", "bar", 1));
|
||||
do_check_eq(backend.measurementID("foo", "bar", 1), id);
|
||||
do_check_false(backend.hasMeasurement("foo", "bar", 2));
|
||||
|
||||
let id2 = yield backend.registerMeasurement("foo", "bar", 2);
|
||||
do_check_eq(id2, 2);
|
||||
do_check_true(backend.hasMeasurement("foo", "bar", 2));
|
||||
do_check_eq(backend.measurementID("foo", "bar", 2), id2);
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_field_registration_basic() {
|
||||
let backend = yield Metrics.Storage("field_registration_basic");
|
||||
|
||||
do_check_false(backend.hasField("foo", "bar", 1, "baz"));
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
do_check_false(backend.hasField("foo", "bar", 1, "baz"));
|
||||
do_check_false(backend.hasFieldFromMeasurement(mID, "baz"));
|
||||
|
||||
let bazID = yield backend.registerField(mID, "baz",
|
||||
backend.FIELD_DAILY_COUNTER);
|
||||
do_check_true(backend.hasField("foo", "bar", 1, "baz"));
|
||||
do_check_true(backend.hasFieldFromMeasurement(mID, "baz"));
|
||||
|
||||
let bar2ID = yield backend.registerMeasurement("foo", "bar2", 1);
|
||||
|
||||
yield backend.registerField(bar2ID, "baz",
|
||||
backend.FIELD_DAILY_DISCRETE_NUMERIC);
|
||||
|
||||
do_check_true(backend.hasField("foo", "bar2", 1, "baz"));
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
// Ensure changes types of fields results in fatal error.
|
||||
add_task(function test_field_registration_changed_type() {
|
||||
let backend = yield Metrics.Storage("field_registration_changed_type");
|
||||
|
||||
let mID = yield backend.registerMeasurement("bar", "bar", 1);
|
||||
|
||||
let id = yield backend.registerField(mID, "baz",
|
||||
backend.FIELD_DAILY_COUNTER);
|
||||
|
||||
let caught = false;
|
||||
try {
|
||||
yield backend.registerField(mID, "baz",
|
||||
backend.FIELD_DAILY_DISCRETE_NUMERIC);
|
||||
} catch (ex) {
|
||||
caught = true;
|
||||
do_check_true(ex.message.startsWith("Field already defined with different type"));
|
||||
}
|
||||
|
||||
do_check_true(caught);
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_field_registration_repopulation() {
|
||||
let backend = yield Metrics.Storage("field_registration_repopulation");
|
||||
|
||||
let mID1 = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
let mID2 = yield backend.registerMeasurement("foo", "bar", 2);
|
||||
let mID3 = yield backend.registerMeasurement("foo", "biz", 1);
|
||||
let mID4 = yield backend.registerMeasurement("baz", "foo", 1);
|
||||
|
||||
let fID1 = yield backend.registerField(mID1, "foo", backend.FIELD_DAILY_COUNTER);
|
||||
let fID2 = yield backend.registerField(mID1, "bar", backend.FIELD_DAILY_DISCRETE_NUMERIC);
|
||||
let fID3 = yield backend.registerField(mID4, "foo", backend.FIELD_LAST_TEXT);
|
||||
|
||||
yield backend.close();
|
||||
|
||||
backend = yield Metrics.Storage("field_registration_repopulation");
|
||||
|
||||
do_check_true(backend.hasProvider("foo"));
|
||||
do_check_true(backend.hasProvider("baz"));
|
||||
do_check_true(backend.hasMeasurement("foo", "bar", 1));
|
||||
do_check_eq(backend.measurementID("foo", "bar", 1), mID1);
|
||||
do_check_true(backend.hasMeasurement("foo", "bar", 2));
|
||||
do_check_eq(backend.measurementID("foo", "bar", 2), mID2);
|
||||
do_check_true(backend.hasMeasurement("foo", "biz", 1));
|
||||
do_check_eq(backend.measurementID("foo", "biz", 1), mID3);
|
||||
do_check_true(backend.hasMeasurement("baz", "foo", 1));
|
||||
do_check_eq(backend.measurementID("baz", "foo", 1), mID4);
|
||||
|
||||
do_check_true(backend.hasField("foo", "bar", 1, "foo"));
|
||||
do_check_eq(backend.fieldID("foo", "bar", 1, "foo"), fID1);
|
||||
do_check_true(backend.hasField("foo", "bar", 1, "bar"));
|
||||
do_check_eq(backend.fieldID("foo", "bar", 1, "bar"), fID2);
|
||||
do_check_true(backend.hasField("baz", "foo", 1, "foo"));
|
||||
do_check_eq(backend.fieldID("baz", "foo", 1, "foo"), fID3);
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_enqueue_operation_many() {
|
||||
let backend = yield Metrics.Storage("enqueue_operation_many");
|
||||
|
||||
let promises = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
promises.push(backend.registerMeasurement("foo", "bar" + i, 1));
|
||||
}
|
||||
|
||||
for (let promise of promises) {
|
||||
yield promise;
|
||||
}
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
// If the operation did not return a promise, everything should still execute.
|
||||
add_task(function test_enqueue_operation_no_return_promise() {
|
||||
let backend = yield Metrics.Storage("enqueue_operation_no_return_promise");
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
let fID = yield backend.registerField(mID, "baz", backend.FIELD_DAILY_COUNTER);
|
||||
let now = new Date();
|
||||
|
||||
let promises = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(backend.enqueueOperation(function op() {
|
||||
backend.incrementDailyCounterFromFieldID(fID, now);
|
||||
}));
|
||||
}
|
||||
|
||||
let deferred = Promise.defer();
|
||||
|
||||
let finished = 0;
|
||||
for (let promise of promises) {
|
||||
promise.then(
|
||||
do_throw.bind(this, "Unexpected resolve."),
|
||||
function onError() {
|
||||
finished++;
|
||||
|
||||
if (finished == promises.length) {
|
||||
backend.getDailyCounterCountFromFieldID(fID, now).then(function onCount(count) {
|
||||
// There should not be a race condition here because storage
|
||||
// serializes all statements. So, for the getDailyCounterCount
|
||||
// query to finish means that all counter update statements must
|
||||
// have completed.
|
||||
do_check_eq(count, promises.length);
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
yield deferred.promise;
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
// If an operation throws, subsequent operations should still execute.
|
||||
add_task(function test_enqueue_operation_throw_exception() {
|
||||
let backend = yield Metrics.Storage("enqueue_operation_rejected_promise");
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
let fID = yield backend.registerField(mID, "baz", backend.FIELD_DAILY_COUNTER);
|
||||
let now = new Date();
|
||||
|
||||
let deferred = Promise.defer();
|
||||
backend.enqueueOperation(function bad() {
|
||||
throw new Error("I failed.");
|
||||
}).then(do_throw, function onError(error) {
|
||||
do_check_true(error.message.contains("I failed."));
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
let promise = backend.enqueueOperation(function () {
|
||||
return backend.incrementDailyCounterFromFieldID(fID, now);
|
||||
});
|
||||
|
||||
yield deferred.promise;
|
||||
yield promise;
|
||||
|
||||
let count = yield backend.getDailyCounterCountFromFieldID(fID, now);
|
||||
do_check_eq(count, 1);
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
// If an operation rejects, subsequent operations should still execute.
|
||||
add_task(function test_enqueue_operation_reject_promise() {
|
||||
let backend = yield Metrics.Storage("enqueue_operation_reject_promise");
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
let fID = yield backend.registerField(mID, "baz", backend.FIELD_DAILY_COUNTER);
|
||||
let now = new Date();
|
||||
|
||||
let deferred = Promise.defer();
|
||||
backend.enqueueOperation(function reject() {
|
||||
let d = Promise.defer();
|
||||
|
||||
CommonUtils.nextTick(function nextTick() {
|
||||
d.reject("I failed.");
|
||||
});
|
||||
|
||||
return d.promise;
|
||||
}).then(do_throw, function onError(error) {
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
let promise = backend.enqueueOperation(function () {
|
||||
return backend.incrementDailyCounterFromFieldID(fID, now);
|
||||
});
|
||||
|
||||
yield deferred.promise;
|
||||
yield promise;
|
||||
|
||||
let count = yield backend.getDailyCounterCountFromFieldID(fID, now);
|
||||
do_check_eq(count, 1);
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_increment_daily_counter_basic() {
|
||||
let backend = yield Metrics.Storage("increment_daily_counter_basic");
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
|
||||
let fieldID = yield backend.registerField(mID, "baz",
|
||||
backend.FIELD_DAILY_COUNTER);
|
||||
|
||||
let now = new Date();
|
||||
yield backend.incrementDailyCounterFromFieldID(fieldID, now);
|
||||
|
||||
let count = yield backend.getDailyCounterCountFromFieldID(fieldID, now);
|
||||
do_check_eq(count, 1);
|
||||
|
||||
yield backend.incrementDailyCounterFromFieldID(fieldID, now);
|
||||
count = yield backend.getDailyCounterCountFromFieldID(fieldID, now);
|
||||
do_check_eq(count, 2);
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_increment_daily_counter_multiple_days() {
|
||||
let backend = yield Metrics.Storage("increment_daily_counter_multiple_days");
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
let fieldID = yield backend.registerField(mID, "baz",
|
||||
backend.FIELD_DAILY_COUNTER);
|
||||
|
||||
let days = [];
|
||||
let now = Date.now();
|
||||
for (let i = 0; i < 100; i++) {
|
||||
days.push(new Date(now - i * MILLISECONDS_PER_DAY));
|
||||
}
|
||||
|
||||
for (let day of days) {
|
||||
yield backend.incrementDailyCounterFromFieldID(fieldID, day);
|
||||
}
|
||||
|
||||
let result = yield backend.getDailyCounterCountsFromFieldID(fieldID);
|
||||
do_check_eq(result.size, 100);
|
||||
for (let day of days) {
|
||||
do_check_true(result.hasDay(day));
|
||||
do_check_eq(result.getDay(day), 1);
|
||||
}
|
||||
|
||||
let fields = yield backend.getMeasurementDailyCountersFromMeasurementID(mID);
|
||||
do_check_eq(fields.size, 1);
|
||||
do_check_true(fields.has("baz"));
|
||||
do_check_eq(fields.get("baz").size, 100);
|
||||
|
||||
for (let day of days) {
|
||||
do_check_true(fields.get("baz").hasDay(day));
|
||||
do_check_eq(fields.get("baz").getDay(day), 1);
|
||||
}
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_last_values() {
|
||||
let backend = yield Metrics.Storage("set_last");
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
let numberID = yield backend.registerField(mID, "number",
|
||||
backend.FIELD_LAST_NUMERIC);
|
||||
let textID = yield backend.registerField(mID, "text",
|
||||
backend.FIELD_LAST_TEXT);
|
||||
let now = new Date();
|
||||
let nowDay = new Date(Math.floor(now.getTime() / MILLISECONDS_PER_DAY) * MILLISECONDS_PER_DAY);
|
||||
|
||||
yield backend.setLastNumericFromFieldID(numberID, 42, now);
|
||||
yield backend.setLastTextFromFieldID(textID, "hello world", now);
|
||||
|
||||
let result = yield backend.getLastNumericFromFieldID(numberID);
|
||||
do_check_true(Array.isArray(result));
|
||||
do_check_eq(result[0].getTime(), nowDay.getTime());
|
||||
do_check_eq(typeof(result[1]), "number");
|
||||
do_check_eq(result[1], 42);
|
||||
|
||||
result = yield backend.getLastTextFromFieldID(textID);
|
||||
do_check_true(Array.isArray(result));
|
||||
do_check_eq(result[0].getTime(), nowDay.getTime());
|
||||
do_check_eq(typeof(result[1]), "string");
|
||||
do_check_eq(result[1], "hello world");
|
||||
|
||||
let missingID = yield backend.registerField(mID, "missing",
|
||||
backend.FIELD_LAST_NUMERIC);
|
||||
do_check_null(yield backend.getLastNumericFromFieldID(missingID));
|
||||
|
||||
let fields = yield backend.getMeasurementLastValuesFromMeasurementID(mID);
|
||||
do_check_eq(fields.size, 2);
|
||||
do_check_true(fields.has("number"));
|
||||
do_check_true(fields.has("text"));
|
||||
do_check_eq(fields.get("number")[1], 42);
|
||||
do_check_eq(fields.get("text")[1], "hello world");
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_discrete_values_basic() {
|
||||
let backend = yield Metrics.Storage("discrete_values_basic");
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
let numericID = yield backend.registerField(mID, "numeric",
|
||||
backend.FIELD_DAILY_DISCRETE_NUMERIC);
|
||||
let textID = yield backend.registerField(mID, "text",
|
||||
backend.FIELD_DAILY_DISCRETE_TEXT);
|
||||
|
||||
let now = new Date();
|
||||
let expectedNumeric = [];
|
||||
let expectedText = [];
|
||||
for (let i = 0; i < 100; i++) {
|
||||
expectedNumeric.push(i);
|
||||
expectedText.push("value" + i);
|
||||
yield backend.addDailyDiscreteNumericFromFieldID(numericID, i, now);
|
||||
yield backend.addDailyDiscreteTextFromFieldID(textID, "value" + i, now);
|
||||
}
|
||||
|
||||
let values = yield backend.getDailyDiscreteNumericFromFieldID(numericID);
|
||||
do_check_eq(values.size, 1);
|
||||
do_check_true(values.hasDay(now));
|
||||
do_check_true(Array.isArray(values.getDay(now)));
|
||||
do_check_eq(values.getDay(now).length, expectedNumeric.length);
|
||||
|
||||
for (let i = 0; i < expectedNumeric.length; i++) {
|
||||
do_check_eq(values.getDay(now)[i], expectedNumeric[i]);
|
||||
}
|
||||
|
||||
values = yield backend.getDailyDiscreteTextFromFieldID(textID);
|
||||
do_check_eq(values.size, 1);
|
||||
do_check_true(values.hasDay(now));
|
||||
do_check_true(Array.isArray(values.getDay(now)));
|
||||
do_check_eq(values.getDay(now).length, expectedText.length);
|
||||
|
||||
for (let i = 0; i < expectedText.length; i++) {
|
||||
do_check_eq(values.getDay(now)[i], expectedText[i]);
|
||||
}
|
||||
|
||||
let fields = yield backend.getMeasurementDailyDiscreteValuesFromMeasurementID(mID);
|
||||
do_check_eq(fields.size, 2);
|
||||
do_check_true(fields.has("numeric"));
|
||||
do_check_true(fields.has("text"));
|
||||
|
||||
let numeric = fields.get("numeric");
|
||||
let text = fields.get("text");
|
||||
do_check_true(numeric.hasDay(now));
|
||||
do_check_true(text.hasDay(now));
|
||||
do_check_eq(numeric.getDay(now).length, expectedNumeric.length);
|
||||
do_check_eq(text.getDay(now).length, expectedText.length);
|
||||
|
||||
for (let i = 0; i < expectedNumeric.length; i++) {
|
||||
do_check_eq(numeric.getDay(now)[i], expectedNumeric[i]);
|
||||
}
|
||||
|
||||
for (let i = 0; i < expectedText.length; i++) {
|
||||
do_check_eq(text.getDay(now)[i], expectedText[i]);
|
||||
}
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_discrete_values_multiple_days() {
|
||||
let backend = yield Metrics.Storage("discrete_values_multiple_days");
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
let id = yield backend.registerField(mID, "baz",
|
||||
backend.FIELD_DAILY_DISCRETE_NUMERIC);
|
||||
|
||||
let now = new Date();
|
||||
let dates = [];
|
||||
for (let i = 0; i < 50; i++) {
|
||||
let date = new Date(now.getTime() + i * MILLISECONDS_PER_DAY);
|
||||
dates.push(date);
|
||||
|
||||
yield backend.addDailyDiscreteNumericFromFieldID(id, i, date);
|
||||
}
|
||||
|
||||
let values = yield backend.getDailyDiscreteNumericFromFieldID(id);
|
||||
do_check_eq(values.size, 50);
|
||||
|
||||
let i = 0;
|
||||
for (let date of dates) {
|
||||
do_check_true(values.hasDay(date));
|
||||
do_check_eq(values.getDay(date)[0], i);
|
||||
i++;
|
||||
}
|
||||
|
||||
let fields = yield backend.getMeasurementDailyDiscreteValuesFromMeasurementID(mID);
|
||||
do_check_eq(fields.size, 1);
|
||||
do_check_true(fields.has("baz"));
|
||||
let baz = fields.get("baz");
|
||||
do_check_eq(baz.size, 50);
|
||||
i = 0;
|
||||
for (let date of dates) {
|
||||
do_check_true(baz.hasDay(date));
|
||||
do_check_eq(baz.getDay(date).length, 1);
|
||||
do_check_eq(baz.getDay(date)[0], i);
|
||||
i++;
|
||||
}
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_daily_last_values() {
|
||||
let backend = yield Metrics.Storage("daily_last_values");
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
let numericID = yield backend.registerField(mID, "numeric",
|
||||
backend.FIELD_DAILY_LAST_NUMERIC);
|
||||
let textID = yield backend.registerField(mID, "text",
|
||||
backend.FIELD_DAILY_LAST_TEXT);
|
||||
|
||||
let now = new Date();
|
||||
let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
|
||||
let dayBefore = new Date(yesterday.getTime() - MILLISECONDS_PER_DAY);
|
||||
|
||||
yield backend.setDailyLastNumericFromFieldID(numericID, 1, yesterday);
|
||||
yield backend.setDailyLastNumericFromFieldID(numericID, 2, now);
|
||||
yield backend.setDailyLastNumericFromFieldID(numericID, 3, dayBefore);
|
||||
yield backend.setDailyLastTextFromFieldID(textID, "foo", now);
|
||||
yield backend.setDailyLastTextFromFieldID(textID, "bar", yesterday);
|
||||
yield backend.setDailyLastTextFromFieldID(textID, "baz", dayBefore);
|
||||
|
||||
let days = yield backend.getDailyLastNumericFromFieldID(numericID);
|
||||
do_check_eq(days.size, 3);
|
||||
do_check_eq(days.getDay(yesterday), 1);
|
||||
do_check_eq(days.getDay(now), 2);
|
||||
do_check_eq(days.getDay(dayBefore), 3);
|
||||
|
||||
days = yield backend.getDailyLastTextFromFieldID(textID);
|
||||
do_check_eq(days.size, 3);
|
||||
do_check_eq(days.getDay(now), "foo");
|
||||
do_check_eq(days.getDay(yesterday), "bar");
|
||||
do_check_eq(days.getDay(dayBefore), "baz");
|
||||
|
||||
yield backend.setDailyLastNumericFromFieldID(numericID, 4, yesterday);
|
||||
days = yield backend.getDailyLastNumericFromFieldID(numericID);
|
||||
do_check_eq(days.getDay(yesterday), 4);
|
||||
|
||||
yield backend.setDailyLastTextFromFieldID(textID, "biz", yesterday);
|
||||
days = yield backend.getDailyLastTextFromFieldID(textID);
|
||||
do_check_eq(days.getDay(yesterday), "biz");
|
||||
|
||||
days = yield backend.getDailyLastNumericFromFieldID(numericID, yesterday);
|
||||
do_check_eq(days.size, 1);
|
||||
do_check_eq(days.getDay(yesterday), 4);
|
||||
|
||||
days = yield backend.getDailyLastTextFromFieldID(textID, yesterday);
|
||||
do_check_eq(days.size, 1);
|
||||
do_check_eq(days.getDay(yesterday), "biz");
|
||||
|
||||
let fields = yield backend.getMeasurementDailyLastValuesFromMeasurementID(mID);
|
||||
do_check_eq(fields.size, 2);
|
||||
do_check_true(fields.has("numeric"));
|
||||
do_check_true(fields.has("text"));
|
||||
let numeric = fields.get("numeric");
|
||||
let text = fields.get("text");
|
||||
do_check_true(numeric.hasDay(yesterday));
|
||||
do_check_true(numeric.hasDay(dayBefore));
|
||||
do_check_true(numeric.hasDay(now));
|
||||
do_check_true(text.hasDay(yesterday));
|
||||
do_check_true(text.hasDay(dayBefore));
|
||||
do_check_true(text.hasDay(now));
|
||||
do_check_eq(numeric.getDay(yesterday), 4);
|
||||
do_check_eq(text.getDay(yesterday), "biz");
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_prune_data_before() {
|
||||
let backend = yield Metrics.Storage("prune_data_before");
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
|
||||
let counterID = yield backend.registerField(mID, "baz",
|
||||
backend.FIELD_DAILY_COUNTER);
|
||||
let text1ID = yield backend.registerField(mID, "one_text_1",
|
||||
backend.FIELD_LAST_TEXT);
|
||||
let text2ID = yield backend.registerField(mID, "one_text_2",
|
||||
backend.FIELD_LAST_TEXT);
|
||||
let numeric1ID = yield backend.registerField(mID, "one_numeric_1",
|
||||
backend.FIELD_LAST_NUMERIC);
|
||||
let numeric2ID = yield backend.registerField(mID, "one_numeric_2",
|
||||
backend.FIELD_LAST_NUMERIC);
|
||||
let text3ID = yield backend.registerField(mID, "daily_last_text_1",
|
||||
backend.FIELD_DAILY_LAST_TEXT);
|
||||
let text4ID = yield backend.registerField(mID, "daily_last_text_2",
|
||||
backend.FIELD_DAILY_LAST_TEXT);
|
||||
let numeric3ID = yield backend.registerField(mID, "daily_last_numeric_1",
|
||||
backend.FIELD_DAILY_LAST_NUMERIC);
|
||||
let numeric4ID = yield backend.registerField(mID, "daily_last_numeric_2",
|
||||
backend.FIELD_DAILY_LAST_NUMERIC);
|
||||
|
||||
let now = new Date();
|
||||
let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
|
||||
let dayBefore = new Date(yesterday.getTime() - MILLISECONDS_PER_DAY);
|
||||
|
||||
yield backend.incrementDailyCounterFromFieldID(counterID, now);
|
||||
yield backend.incrementDailyCounterFromFieldID(counterID, yesterday);
|
||||
yield backend.incrementDailyCounterFromFieldID(counterID, dayBefore);
|
||||
yield backend.setLastTextFromFieldID(text1ID, "hello", dayBefore);
|
||||
yield backend.setLastTextFromFieldID(text2ID, "world", yesterday);
|
||||
yield backend.setLastNumericFromFieldID(numeric1ID, 42, dayBefore);
|
||||
yield backend.setLastNumericFromFieldID(numeric2ID, 43, yesterday);
|
||||
yield backend.setDailyLastTextFromFieldID(text3ID, "foo", dayBefore);
|
||||
yield backend.setDailyLastTextFromFieldID(text3ID, "bar", yesterday);
|
||||
yield backend.setDailyLastTextFromFieldID(text4ID, "hello", dayBefore);
|
||||
yield backend.setDailyLastTextFromFieldID(text4ID, "world", yesterday);
|
||||
yield backend.setDailyLastNumericFromFieldID(numeric3ID, 40, dayBefore);
|
||||
yield backend.setDailyLastNumericFromFieldID(numeric3ID, 41, yesterday);
|
||||
yield backend.setDailyLastNumericFromFieldID(numeric4ID, 42, dayBefore);
|
||||
yield backend.setDailyLastNumericFromFieldID(numeric4ID, 43, yesterday);
|
||||
|
||||
let days = yield backend.getDailyCounterCountsFromFieldID(counterID);
|
||||
do_check_eq(days.size, 3);
|
||||
|
||||
yield backend.pruneDataBefore(yesterday);
|
||||
days = yield backend.getDailyCounterCountsFromFieldID(counterID);
|
||||
do_check_eq(days.size, 2);
|
||||
do_check_false(days.hasDay(dayBefore));
|
||||
|
||||
do_check_null(yield backend.getLastTextFromFieldID(text1ID));
|
||||
do_check_null(yield backend.getLastNumericFromFieldID(numeric1ID));
|
||||
|
||||
let result = yield backend.getLastTextFromFieldID(text2ID);
|
||||
do_check_true(Array.isArray(result));
|
||||
do_check_eq(result[1], "world");
|
||||
|
||||
result = yield backend.getLastNumericFromFieldID(numeric2ID);
|
||||
do_check_true(Array.isArray(result));
|
||||
do_check_eq(result[1], 43);
|
||||
|
||||
result = yield backend.getDailyLastNumericFromFieldID(numeric3ID);
|
||||
do_check_eq(result.size, 1);
|
||||
do_check_true(result.hasDay(yesterday));
|
||||
|
||||
result = yield backend.getDailyLastTextFromFieldID(text3ID);
|
||||
do_check_eq(result.size, 1);
|
||||
do_check_true(result.hasDay(yesterday));
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_provider_state() {
|
||||
let backend = yield Metrics.Storage("provider_state");
|
||||
|
||||
yield backend.registerMeasurement("foo", "bar", 1);
|
||||
yield backend.setProviderState("foo", "apple", "orange");
|
||||
let value = yield backend.getProviderState("foo", "apple");
|
||||
do_check_eq(value, "orange");
|
||||
|
||||
yield backend.setProviderState("foo", "apple", "pear");
|
||||
value = yield backend.getProviderState("foo", "apple");
|
||||
do_check_eq(value, "pear");
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
||||
add_task(function test_get_measurement_values() {
|
||||
let backend = yield Metrics.Storage("get_measurement_values");
|
||||
|
||||
let mID = yield backend.registerMeasurement("foo", "bar", 1);
|
||||
let id1 = yield backend.registerField(mID, "id1", backend.FIELD_DAILY_COUNTER);
|
||||
let id2 = yield backend.registerField(mID, "id2", backend.FIELD_DAILY_DISCRETE_NUMERIC);
|
||||
let id3 = yield backend.registerField(mID, "id3", backend.FIELD_DAILY_DISCRETE_TEXT);
|
||||
let id4 = yield backend.registerField(mID, "id4", backend.FIELD_DAILY_LAST_NUMERIC);
|
||||
let id5 = yield backend.registerField(mID, "id5", backend.FIELD_DAILY_LAST_TEXT);
|
||||
let id6 = yield backend.registerField(mID, "id6", backend.FIELD_LAST_NUMERIC);
|
||||
let id7 = yield backend.registerField(mID, "id7", backend.FIELD_LAST_TEXT);
|
||||
|
||||
let now = new Date();
|
||||
let yesterday = new Date(now.getTime() - MILLISECONDS_PER_DAY);
|
||||
|
||||
yield backend.incrementDailyCounterFromFieldID(id1, now);
|
||||
yield backend.addDailyDiscreteNumericFromFieldID(id2, 3, now);
|
||||
yield backend.addDailyDiscreteNumericFromFieldID(id2, 4, now);
|
||||
yield backend.addDailyDiscreteNumericFromFieldID(id2, 5, yesterday);
|
||||
yield backend.addDailyDiscreteNumericFromFieldID(id2, 6, yesterday);
|
||||
yield backend.addDailyDiscreteTextFromFieldID(id3, "1", now);
|
||||
yield backend.addDailyDiscreteTextFromFieldID(id3, "2", now);
|
||||
yield backend.addDailyDiscreteTextFromFieldID(id3, "3", yesterday);
|
||||
yield backend.addDailyDiscreteTextFromFieldID(id3, "4", yesterday);
|
||||
yield backend.setDailyLastNumericFromFieldID(id4, 1, now);
|
||||
yield backend.setDailyLastNumericFromFieldID(id4, 2, yesterday);
|
||||
yield backend.setDailyLastTextFromFieldID(id5, "foo", now);
|
||||
yield backend.setDailyLastTextFromFieldID(id5, "bar", yesterday);
|
||||
yield backend.setLastNumericFromFieldID(id6, 42, now);
|
||||
yield backend.setLastTextFromFieldID(id7, "foo", now);
|
||||
|
||||
let fields = yield backend.getMeasurementValues(mID);
|
||||
do_check_eq(Object.keys(fields).length, 2);
|
||||
do_check_true("days" in fields);
|
||||
do_check_true("singular" in fields);
|
||||
do_check_eq(fields.days.size, 2);
|
||||
do_check_true(fields.days.hasDay(now));
|
||||
do_check_true(fields.days.hasDay(yesterday));
|
||||
do_check_eq(fields.days.getDay(now).size, 5);
|
||||
do_check_eq(fields.days.getDay(yesterday).size, 4);
|
||||
do_check_eq(fields.days.getDay(now).get("id3")[0], 1);
|
||||
do_check_eq(fields.days.getDay(yesterday).get("id4"), 2);
|
||||
do_check_eq(fields.singular.size, 2);
|
||||
do_check_eq(fields.singular.get("id6")[1], 42);
|
||||
do_check_eq(fields.singular.get("id7")[1], "foo");
|
||||
|
||||
yield backend.close();
|
||||
});
|
||||
|
@ -3,7 +3,6 @@ head = head.js
|
||||
tail =
|
||||
|
||||
[test_load_modules.js]
|
||||
[test_metrics_collection_result.js]
|
||||
[test_metrics_measurement.js]
|
||||
[test_metrics_provider.js]
|
||||
[test_metrics_collector.js]
|
||||
[test_metrics_storage.js]
|
||||
|
@ -1,28 +0,0 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEPTH := @DEPTH@
|
||||
topsrcdir := @top_srcdir@
|
||||
srcdir := @srcdir@
|
||||
VPATH := @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
EXTRA_COMPONENTS := \
|
||||
NotificationsComponents.manifest \
|
||||
$(NULL)
|
||||
|
||||
PREF_JS_EXPORTS := $(srcdir)/services-notifications.js
|
||||
|
||||
modules := \
|
||||
service.js \
|
||||
$(NULL)
|
||||
|
||||
NOTIFICATION_MODULE_FILES := $(modules)
|
||||
NOTIFICATION_MODULE_DEST = $(FINAL_TARGET)/modules/services-notifications
|
||||
INSTALL_TARGETS += NOTIFICATION_MODULE
|
||||
|
||||
TEST_DIRS += tests
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
@ -1,2 +0,0 @@
|
||||
# Register resource aliases
|
||||
resource services-notifications resource://gre/modules/services-notifications/
|
@ -1,11 +0,0 @@
|
||||
Here lies most of the moving parts for push notifcations in the browser. DOM
|
||||
and UI bindings will live elsewhere; these files deal with talking to the API,
|
||||
storing messages, and creating persistent connections to the notification
|
||||
server.
|
||||
|
||||
Structure:
|
||||
|
||||
services.js::Service
|
||||
This is a singleton that manages API calls and message storage. It's an
|
||||
instance of the NotificationSvc class. Messages and state are persisted to a
|
||||
JSON file on disk.
|
@ -1,28 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Service"];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://services-common/preferences.js");
|
||||
|
||||
const PREFS_BRANCH = "services.notifications.";
|
||||
|
||||
|
||||
function NotificationSvc() {
|
||||
this.ready = false;
|
||||
this.prefs = new Preferences(PREFS_BRANCH);
|
||||
}
|
||||
NotificationSvc.prototype = {
|
||||
|
||||
get serverURL() this.prefs.get("serverURL"),
|
||||
|
||||
onStartup: function onStartup() {
|
||||
this.ready = true;
|
||||
}
|
||||
};
|
||||
|
||||
this.Service = new NotificationSvc();
|
||||
Service.onStartup();
|
@ -1 +0,0 @@
|
||||
pref("services.notifications.serverURL", "https://notifications.mozilla.org/");
|
@ -1,3 +0,0 @@
|
||||
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
let _ = function() print(Array.slice(arguments).join(" "));
|
@ -1,8 +0,0 @@
|
||||
// Check that everything is getting hooked together properly.
|
||||
function run_test() {
|
||||
_("When imported, Service.onStartup is called.");
|
||||
Cu.import("resource://services-notifications/service.js");
|
||||
|
||||
do_check_eq(Service.serverURL, "https://notifications.mozilla.org/");
|
||||
do_check_eq(Service.ready, true);
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
[DEFAULT]
|
||||
head = head_helpers.js
|
||||
tail =
|
||||
|
||||
[test_service_start.js]
|
@ -1512,5 +1512,4 @@ BookmarksTracker.prototype = {
|
||||
onEndUpdateBatch: function () {},
|
||||
onItemVisited: function () {},
|
||||
onBeforeItemRemoved: function () {},
|
||||
batching: function () {},
|
||||
};
|
||||
|
@ -426,5 +426,4 @@ HistoryTracker.prototype = {
|
||||
onPageChanged: function () {},
|
||||
onTitleChanged: function () {},
|
||||
onBeforeDeleteURI: function () {},
|
||||
batching: function () {},
|
||||
};
|
||||
|
@ -56,6 +56,7 @@ skip-if = os == "android"
|
||||
[include:toolkit/forgetaboutsite/test/unit/xpcshell.ini]
|
||||
[include:toolkit/content/tests/unit/xpcshell.ini]
|
||||
[include:toolkit/identity/tests/unit/xpcshell.ini]
|
||||
[include:toolkit/modules/tests/xpcshell/xpcshell.ini]
|
||||
[include:toolkit/mozapps/downloads/tests/unit/xpcshell.ini]
|
||||
[include:toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini]
|
||||
[include:toolkit/mozapps/extensions/test/xpcshell-unpack/xpcshell.ini]
|
||||
@ -89,7 +90,6 @@ skip-if = os == "android"
|
||||
[include:services/crypto/components/tests/unit/xpcshell.ini]
|
||||
[include:services/healthreport/tests/xpcshell/xpcshell.ini]
|
||||
[include:services/metrics/tests/xpcshell/xpcshell.ini]
|
||||
[include:services/notifications/tests/unit/xpcshell.ini]
|
||||
[include:services/sync/tests/unit/xpcshell.ini]
|
||||
# Bug 676978: tests hang on Android
|
||||
skip-if = os == "android"
|
||||
|
@ -17,6 +17,7 @@ PARALLEL_DIRS = \
|
||||
forgetaboutsite \
|
||||
identity \
|
||||
locales \
|
||||
modules \
|
||||
mozapps/downloads \
|
||||
mozapps/extensions \
|
||||
mozapps/handling \
|
||||
|
18
toolkit/modules/Makefile.in
Normal file
@ -0,0 +1,18 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
TEST_DIRS += tests
|
||||
|
||||
EXTRA_JS_MODULES := \
|
||||
Sqlite.jsm \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|