зеркало из https://github.com/mozilla/gecko-dev.git
Bug 983360 - Telemetry experiments: add monitoring data to Telemetry payload. r=bsmedberg
This commit is contained in:
Родитель
863af40e1e
Коммит
6dc6f7ec05
|
@ -28,6 +28,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
|
|||
"resource://gre/modules/AddonManager.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryPing",
|
||||
"resource://gre/modules/TelemetryPing.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog",
|
||||
"resource://gre/modules/TelemetryLog.jsm");
|
||||
// CertUtils.jsm doesn't expose a single "CertUtils" object like a normal .jsm
|
||||
// would.
|
||||
XPCOMUtils.defineLazyGetter(this, "CertUtils",
|
||||
|
@ -62,6 +64,27 @@ const PREF_BRANCH_TELEMETRY = "toolkit.telemetry.";
|
|||
const PREF_TELEMETRY_ENABLED = "enabled";
|
||||
const PREF_TELEMETRY_PRERELEASE = "enabledPreRelease";
|
||||
|
||||
const TELEMETRY_LOG = {
|
||||
// log(key, [kind, experimentId, details])
|
||||
ACTIVATION_KEY: "EXPERIMENT_ACTIVATION",
|
||||
ACTIVATION: {
|
||||
ACTIVATED: "ACTIVATED", // successfully activated
|
||||
INSTALL_FAILURE: "INSTALL_FAILURE", // failed to install the extension
|
||||
REJECTED: "REJECTED", // experiment was rejected because of it's conditions,
|
||||
// provides details on which
|
||||
},
|
||||
|
||||
// log(key, [kind, experimentId, optionalDetails...])
|
||||
TERMINATION_KEY: "EXPERIMENT_TERMINATION",
|
||||
TERMINATION: {
|
||||
USERDISABLED: "USERDISABLED", // the user disabled this experiment
|
||||
FROM_API: "FROM_API", // the experiment disabled itself
|
||||
EXPIRED: "EXPIRED", // experiment expired e.g. by exceeding the end-date
|
||||
RECHECK: "RECHECK", // disabled after re-evaluating conditions,
|
||||
// provides details on which
|
||||
},
|
||||
};
|
||||
|
||||
const gPrefs = new Preferences(PREF_BRANCH);
|
||||
const gPrefsTelemetry = new Preferences(PREF_BRANCH_TELEMETRY);
|
||||
let gExperimentsEnabled = false;
|
||||
|
@ -737,11 +760,12 @@ Experiments.Experiments.prototype = {
|
|||
// Make sure we keep experiments that are or were running.
|
||||
// We remove them after KEEP_HISTORY_N_DAYS.
|
||||
for (let [id, entry] of this._experiments) {
|
||||
if (experiments.has(id) || !entry.startDate || !entry.shouldDiscard()) {
|
||||
if (experiments.has(id) || !entry.startDate || entry.shouldDiscard()) {
|
||||
gLogger.trace("Experiments::updateExperiments() - discarding entry for " + id);
|
||||
continue;
|
||||
}
|
||||
|
||||
experiments.set(id, experiment);
|
||||
experiments.set(id, entry);
|
||||
}
|
||||
|
||||
this._experiments = experiments;
|
||||
|
@ -805,16 +829,25 @@ Experiments.Experiments.prototype = {
|
|||
}
|
||||
|
||||
if (!experiment.enabled) {
|
||||
return Promise.reject();
|
||||
let message = "experiment is not enabled";
|
||||
gLogger.debug("Experiments::disableExperiment() - " + message);
|
||||
return Promise.reject(new Error(message));
|
||||
}
|
||||
|
||||
return Task.spawn(function* Experiments_disableExperimentTaskOuter() {
|
||||
gLogger.trace("Experiments_disableExperimentTaskOuter");
|
||||
// We need to wait on possible previous disable tasks.
|
||||
yield this._pendingTasks.disableExperiment;
|
||||
|
||||
let disableTask = Task.spawn(function* Experiments_disableExperimentTaskInner() {
|
||||
yield this._pendingTasksDone([this._pendingTasks.disableExperiment]);
|
||||
yield experiment.stop(userDisabled);
|
||||
|
||||
let terminationKind = TELEMETRY_LOG.TERMINATION.USERDISABLED;
|
||||
if (!userDisabled) {
|
||||
terminationKind = TELEMETRY_LOG.TERMINATION.FROM_API;
|
||||
}
|
||||
|
||||
yield experiment.stop(terminationKind);
|
||||
Services.obs.notifyObservers(null, OBSERVER_TOPIC, null);
|
||||
this._pendingTasks.disableExperiment = null;
|
||||
}.bind(this));
|
||||
|
@ -839,6 +872,7 @@ Experiments.Experiments.prototype = {
|
|||
this._checkForShutdown();
|
||||
let activeExperiment = this._getActiveExperiment();
|
||||
let activeChanged = false;
|
||||
let now = this._policy.now();
|
||||
|
||||
if (activeExperiment) {
|
||||
let wasStopped = yield activeExperiment.maybeStop();
|
||||
|
@ -865,14 +899,23 @@ Experiments.Experiments.prototype = {
|
|||
if (!activeExperiment) {
|
||||
for (let [id, experiment] of this._experiments) {
|
||||
let applicable;
|
||||
let reason = null;
|
||||
yield experiment.isApplicable().then(
|
||||
result => applicable = result,
|
||||
reason => {
|
||||
result => {
|
||||
applicable = false;
|
||||
// TODO telemetry: experiment rejection reason
|
||||
reason = result;
|
||||
}
|
||||
);
|
||||
|
||||
if (!applicable && reason && reason[0] != "was-active") {
|
||||
// Report this from here to avoid over-reporting.
|
||||
let desc = TELEMETRY_LOG.ACTIVATION;
|
||||
let data = [TELEMETRY_LOG.ACTIVATION.REJECTED, id];
|
||||
data = data.concat(reason);
|
||||
TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY, data);
|
||||
}
|
||||
|
||||
if (applicable) {
|
||||
gLogger.debug("Experiments::evaluateExperiments() - activating experiment " + id);
|
||||
try {
|
||||
|
@ -1173,7 +1216,7 @@ Experiments.ExperimentEntry.prototype = {
|
|||
// Not applicable if it already ran.
|
||||
|
||||
if (!this.enabled && this._endDate) {
|
||||
return Promise.reject("was already active");
|
||||
return Promise.reject(["was-active"]);
|
||||
}
|
||||
|
||||
// Define and run the condition checks.
|
||||
|
@ -1222,7 +1265,7 @@ Experiments.ExperimentEntry.prototype = {
|
|||
if (!result) {
|
||||
gLogger.debug("ExperimentEntry::isApplicable() - id="
|
||||
+ data.id + " - test '" + check.name + "' failed");
|
||||
return Promise.reject(check.name);
|
||||
return Promise.reject([check.name]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1256,7 +1299,7 @@ Experiments.ExperimentEntry.prototype = {
|
|||
Cu.evalInSandbox(jsfilter, sandbox);
|
||||
} catch (e) {
|
||||
gLogger.error("ExperimentEntry::runFilterFunction() - failed to eval jsfilter: " + e.message);
|
||||
throw "jsfilter:evalFailure";
|
||||
throw ["jsfilter-evalfailed"];
|
||||
}
|
||||
|
||||
// You can't insert arbitrarily complex objects into a sandbox, so
|
||||
|
@ -1272,13 +1315,17 @@ Experiments.ExperimentEntry.prototype = {
|
|||
catch (e) {
|
||||
gLogger.debug("ExperimentEntry::runFilterFunction() - filter function failed: "
|
||||
+ e.message + ", " + e.stack);
|
||||
throw "jsfilter:rejected " + e.message;
|
||||
throw ["jsfilter-threw", e.message];
|
||||
}
|
||||
finally {
|
||||
Cu.nukeSandbox(sandbox);
|
||||
}
|
||||
|
||||
throw new Task.Result(result);
|
||||
if (!result) {
|
||||
throw ["jsfilter-false"];
|
||||
}
|
||||
|
||||
throw new Task.Result(true);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
|
@ -1297,7 +1344,8 @@ Experiments.ExperimentEntry.prototype = {
|
|||
gLogger.error("ExperimentEntry::start() - " + message);
|
||||
this._failedStart = true;
|
||||
|
||||
// TODO telemetry: install failure
|
||||
TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
|
||||
[TELEMETRY_LOG.ACTIVATION.INSTALL_FAILURE, this.id]);
|
||||
|
||||
deferred.reject(new Error(message));
|
||||
};
|
||||
|
@ -1328,7 +1376,8 @@ Experiments.ExperimentEntry.prototype = {
|
|||
this._startDate = this._policy.now();
|
||||
this._enabled = true;
|
||||
|
||||
// TODO telemetry: install success
|
||||
TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
|
||||
[TELEMETRY_LOG.ACTIVATION.ACTIVATED, this.id]);
|
||||
|
||||
deferred.resolve();
|
||||
},
|
||||
|
@ -1352,11 +1401,13 @@ Experiments.ExperimentEntry.prototype = {
|
|||
|
||||
/*
|
||||
* Stop running the experiment if it is active.
|
||||
* @param userDisabled (optional) Whether this is disabled by user action, defaults to false.
|
||||
* @param terminationKind (optional) The termination kind, e.g. USERDISABLED or EXPIRED.
|
||||
* @param terminationReason (optional) The termination reason details for
|
||||
* termination kind RECHECK.
|
||||
* @return Promise<> Resolved when the operation is complete.
|
||||
*/
|
||||
stop: function (userDisabled=false) {
|
||||
gLogger.trace("ExperimentEntry::stop() - id=" + this.id + ", userDisabled=" + userDisabled);
|
||||
stop: function (terminationKind, terminationReason) {
|
||||
gLogger.trace("ExperimentEntry::stop() - id=" + this.id + ", terminationKind=" + terminationKind);
|
||||
if (!this._enabled) {
|
||||
gLogger.warning("ExperimentEntry::stop() - experiment not enabled: " + id);
|
||||
return Promise.reject();
|
||||
|
@ -1386,6 +1437,7 @@ Experiments.ExperimentEntry.prototype = {
|
|||
}
|
||||
|
||||
updateDates();
|
||||
this._logTermination(terminationKind, terminationReason);
|
||||
|
||||
AddonManager.removeAddonListener(listener);
|
||||
deferred.resolve();
|
||||
|
@ -1397,13 +1449,29 @@ Experiments.ExperimentEntry.prototype = {
|
|||
AddonManager.addAddonListener(listener);
|
||||
|
||||
addon.uninstall();
|
||||
|
||||
// TODO telemetry: experiment disabling, differentiate by userDisabled
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
_logTermination: function (terminationKind, terminationReason) {
|
||||
if (terminationKind === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(terminationKind in TELEMETRY_LOG.TERMINATION)) {
|
||||
gLogger.warn("ExperimentEntry::stop() - unknown terminationKind " + terminationKind);
|
||||
return;
|
||||
}
|
||||
|
||||
let data = [terminationKind, this.id];
|
||||
if (terminationReason) {
|
||||
data = data.concat(terminationReason);
|
||||
}
|
||||
|
||||
TelemetryLog.log(TELEMETRY_LOG.TERMINATION_KEY, data);
|
||||
},
|
||||
|
||||
/*
|
||||
* Stop if experiment stop criteria are met.
|
||||
* @return Promise<boolean> Resolved when done stopping or checking,
|
||||
|
@ -1413,11 +1481,17 @@ Experiments.ExperimentEntry.prototype = {
|
|||
gLogger.trace("ExperimentEntry::maybeStop()");
|
||||
|
||||
return Task.spawn(function ExperimentEntry_maybeStop_task() {
|
||||
let shouldStop = yield this._shouldStop();
|
||||
if (shouldStop) {
|
||||
yield this.stop();
|
||||
let result = yield this._shouldStop();
|
||||
if (result.shouldStop) {
|
||||
let expireReasons = ["endTime", "maxActiveSeconds"];
|
||||
if (expireReasons.indexOf(result.reason[0]) != -1) {
|
||||
yield this.stop(TELEMETRY_LOG.TERMINATION.EXPIRED);
|
||||
} else {
|
||||
yield this.stop(TELEMETRY_LOG.TERMINATION.RECHECK, result.reason);
|
||||
}
|
||||
}
|
||||
throw new Task.Result(shouldStop);
|
||||
|
||||
throw new Task.Result(result.shouldStop);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
|
@ -1427,13 +1501,13 @@ Experiments.ExperimentEntry.prototype = {
|
|||
let maxActiveSec = data.maxActiveSeconds || 0;
|
||||
|
||||
if (!this._enabled) {
|
||||
return Promise.resolve(false);
|
||||
return Promise.resolve({shouldStop: false});
|
||||
}
|
||||
|
||||
let deferred = Promise.defer();
|
||||
this.isApplicable().then(
|
||||
() => deferred.resolve(false),
|
||||
() => deferred.resolve(true)
|
||||
() => deferred.resolve({shouldStop: false}),
|
||||
reason => deferred.resolve({shouldStop: true, reason: reason})
|
||||
);
|
||||
|
||||
return deferred.promise;
|
||||
|
|
|
@ -25,6 +25,9 @@ const EXPERIMENT2_ID = "test-experiment-2@tests.mozilla.org"
|
|||
const EXPERIMENT2_XPI_SHA1 = "sha1:81877991ec70360fb48db84c34a9b2da7aa41d6a";
|
||||
const EXPERIMENT2_XPI_NAME = "experiment-2.xpi";
|
||||
|
||||
const EXPERIMENT3_ID = "test-experiment-3@tests.mozilla.org";
|
||||
const EXPERIMENT4_ID = "test-experiment-4@tests.mozilla.org";
|
||||
|
||||
const FAKE_EXPERIMENTS_1 = [
|
||||
{
|
||||
id: "id1",
|
||||
|
|
|
@ -118,15 +118,15 @@ add_task(function* test_startStop() {
|
|||
yield experiment.start();
|
||||
Assert.equal(experiment.enabled, true, "Experiment should now be enabled.");
|
||||
|
||||
let shouldStop = yield experiment._shouldStop();
|
||||
Assert.equal(shouldStop, false, "shouldStop should be false.");
|
||||
let result = yield experiment._shouldStop();
|
||||
Assert.equal(result.shouldStop, false, "shouldStop should be false.");
|
||||
let maybeStop = yield experiment.maybeStop();
|
||||
Assert.equal(maybeStop, false, "Experiment should not have been stopped.");
|
||||
Assert.equal(experiment.enabled, true, "Experiment should be enabled.");
|
||||
|
||||
defineNow(gPolicy, futureDate(endDate, MS_IN_ONE_DAY));
|
||||
shouldStop = yield experiment._shouldStop();
|
||||
Assert.equal(shouldStop, true, "shouldStop should now be true.");
|
||||
result = yield experiment._shouldStop();
|
||||
Assert.equal(result.shouldStop, true, "shouldStop should now be true.");
|
||||
maybeStop = yield experiment.maybeStop();
|
||||
Assert.equal(maybeStop, true, "Experiment should have been stopped.");
|
||||
Assert.equal(experiment.enabled, false, "Experiment should be disabled.");
|
||||
|
|
|
@ -74,6 +74,20 @@ add_task(function* test_setup() {
|
|||
let experiments = new Experiments.Experiments();
|
||||
});
|
||||
|
||||
function arraysEqual(a, b) {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let i=0; i<a.length; ++i) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This function exists solely to be .toSource()d
|
||||
const sanityFilter = function filter(c) {
|
||||
if (c.telemetryPayload === undefined) {
|
||||
|
@ -94,18 +108,18 @@ const sanityFilter = function filter(c) {
|
|||
add_task(function* test_simpleFields() {
|
||||
let testData = [
|
||||
// "expected applicable?", failure reason or null, manifest data
|
||||
[false, "os", {os: ["42", "abcdef"]}],
|
||||
[false, ["os"], {os: ["42", "abcdef"]}],
|
||||
[true, null, {os: [gAppInfo.OS, "plan9"]}],
|
||||
[false, "buildIDs", {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID + "-invalid"]}],
|
||||
[false, ["buildIDs"], {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID + "-invalid"]}],
|
||||
[true, null, {buildIDs: ["not-a-build-id", gAppInfo.platformBuildID]}],
|
||||
[true, null, {jsfilter: "function filter(c) { return true; }"}],
|
||||
[false, null, {jsfilter: "function filter(c) { return false; }"}],
|
||||
[false, ["jsfilter-false"], {jsfilter: "function filter(c) { return false; }"}],
|
||||
[true, null, {jsfilter: "function filter(c) { return 123; }"}], // truthy
|
||||
[false, null, {jsfilter: "function filter(c) { return ''; }"}], // falsy
|
||||
[false, null, {jsfilter: "function filter(c) { var a = []; }"}], // undefined
|
||||
[false, "jsfilter:rejected some error", {jsfilter: "function filter(c) { throw new Error('some error'); }"}],
|
||||
[false, "jsfilter:evalFailure", {jsfilter: "123, this won't work"}],
|
||||
[true, {jsfilter: "var filter = " + sanityFilter.toSource()}],
|
||||
[false, ["jsfilter-false"], {jsfilter: "function filter(c) { return ''; }"}], // falsy
|
||||
[false, ["jsfilter-false"], {jsfilter: "function filter(c) { var a = []; }"}], // undefined
|
||||
[false, ["jsfilter-threw", "some error"], {jsfilter: "function filter(c) { throw new Error('some error'); }"}],
|
||||
[false, ["jsfilter-evalfailed"], {jsfilter: "123, this won't work"}],
|
||||
[true, null, {jsfilter: "var filter = " + sanityFilter.toSource()}],
|
||||
];
|
||||
|
||||
for (let i=0; i<testData.length; ++i) {
|
||||
|
@ -124,8 +138,13 @@ add_task(function* test_simpleFields() {
|
|||
Assert.equal(applicable, entry[0],
|
||||
"Experiment entry applicability should match for test "
|
||||
+ i + ": " + JSON.stringify(entry[2]));
|
||||
if (!applicable) {
|
||||
Assert.equal(reason, entry[1], "Experiment rejection reason should match for test " + i);
|
||||
|
||||
let expectedReason = entry[1];
|
||||
if (!applicable && expectedReason) {
|
||||
Assert.ok(arraysEqual(reason, expectedReason),
|
||||
"Experiment rejection reasons should match for test " + i + ". "
|
||||
+ "Got " + JSON.stringify(reason) + ", expected "
|
||||
+ JSON.stringify(expectedReason));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,360 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://testing-common/httpd.js");
|
||||
Cu.import("resource://gre/modules/TelemetryLog.jsm");
|
||||
Cu.import("resource://gre/modules/TelemetryPing.jsm");
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
|
||||
|
||||
const FILE_MANIFEST = "experiments.manifest";
|
||||
const PREF_EXPERIMENTS_ENABLED = "experiments.enabled";
|
||||
const PREF_LOGGING_LEVEL = "experiments.logging.level";
|
||||
const PREF_LOGGING_DUMP = "experiments.logging.dump";
|
||||
const PREF_MANIFEST_URI = "experiments.manifest.uri";
|
||||
const PREF_FETCHINTERVAL = "experiments.manifest.fetchIntervalSeconds";
|
||||
|
||||
const MANIFEST_HANDLER = "manifests/handler";
|
||||
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
|
||||
|
||||
|
||||
let gProfileDir = null;
|
||||
let gHttpServer = null;
|
||||
let gHttpRoot = null;
|
||||
let gDataRoot = null;
|
||||
let gReporter = null;
|
||||
let gPolicy = null;
|
||||
let gManifestObject = null;
|
||||
let gManifestHandlerURI = null;
|
||||
|
||||
const TLOG = {
|
||||
// log(key, [kind, experimentId, details])
|
||||
ACTIVATION_KEY: "EXPERIMENT_ACTIVATION",
|
||||
ACTIVATION: {
|
||||
ACTIVATED: "ACTIVATED",
|
||||
INSTALL_FAILURE: "INSTALL_FAILURE",
|
||||
REJECTED: "REJECTED",
|
||||
},
|
||||
|
||||
// log(key, [kind, experimentId, optionalDetails...])
|
||||
TERMINATION_KEY: "EXPERIMENT_TERMINATION",
|
||||
TERMINATION: {
|
||||
USERDISABLED: "USERDISABLED",
|
||||
FROM_API: "FROM_API",
|
||||
EXPIRED: "EXPIRED",
|
||||
RECHECK: "RECHECK",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
let gGlobalScope = this;
|
||||
function loadAddonManager() {
|
||||
let ns = {};
|
||||
Cu.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();
|
||||
}
|
||||
|
||||
function checkEvent(event, id, data)
|
||||
{
|
||||
do_print("Checking message " + id);
|
||||
Assert.equal(event[0], id, "id should match");
|
||||
Assert.ok(event[1] > 0, "timestamp should be greater than 0");
|
||||
|
||||
if (data === undefined) {
|
||||
Assert.equal(event.length, 2, "event array should have 2 entries");
|
||||
} else {
|
||||
Assert.equal(event.length, data.length + 2, "event entry count should match expected count");
|
||||
for (var i = 0; i < data.length; ++i) {
|
||||
Assert.equal(typeof(event[i + 2]), "string", "event entry should be a string");
|
||||
Assert.equal(event[i + 2], data[i], "event entry should match expected entry");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function* test_setup() {
|
||||
loadAddonManager();
|
||||
gProfileDir = do_get_profile();
|
||||
|
||||
gHttpServer = new HttpServer();
|
||||
gHttpServer.start(-1);
|
||||
let port = gHttpServer.identity.primaryPort;
|
||||
gHttpRoot = "http://localhost:" + port + "/";
|
||||
gDataRoot = gHttpRoot + "data/";
|
||||
gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER;
|
||||
gHttpServer.registerDirectory("/data/", do_get_cwd());
|
||||
gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => {
|
||||
response.setStatusLine(null, 200, "OK");
|
||||
response.write(JSON.stringify(gManifestObject));
|
||||
response.processAsync();
|
||||
response.finish();
|
||||
});
|
||||
do_register_cleanup(() => gHttpServer.stop(() => {}));
|
||||
|
||||
disableCertificateChecks();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
|
||||
Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0);
|
||||
Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true);
|
||||
Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI);
|
||||
Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0);
|
||||
|
||||
gReporter = yield getReporter("json_payload_simple");
|
||||
yield gReporter.collectMeasurements();
|
||||
let payload = yield gReporter.getJSONPayload(true);
|
||||
|
||||
gPolicy = new Experiments.Policy();
|
||||
let dummyTimer = { cancel: () => {}, clear: () => {} };
|
||||
patchPolicy(gPolicy, {
|
||||
updatechannel: () => "nightly",
|
||||
healthReportPayload: () => Promise.resolve(payload),
|
||||
oneshotTimer: (callback, timeout, thisObj, name) => dummyTimer,
|
||||
});
|
||||
|
||||
yield removeCacheFile();
|
||||
});
|
||||
|
||||
// Test basic starting and stopping of experiments.
|
||||
|
||||
add_task(function* test_telemetryBasics() {
|
||||
const OBSERVER_TOPIC = "experiments-changed";
|
||||
let observerFireCount = 0;
|
||||
let expectedLogLength = 0;
|
||||
|
||||
// Dates the following tests are based on.
|
||||
|
||||
let baseDate = new Date(2014, 5, 1, 12);
|
||||
let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY);
|
||||
let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
|
||||
let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY);
|
||||
let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY);
|
||||
|
||||
// The manifest data we test with.
|
||||
|
||||
gManifestObject = {
|
||||
"version": 1,
|
||||
experiments: [
|
||||
{
|
||||
id: EXPERIMENT1_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
|
||||
xpiHash: EXPERIMENT1_XPI_SHA1,
|
||||
startTime: dateToSeconds(startDate1),
|
||||
endTime: dateToSeconds(endDate1),
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
{
|
||||
id: EXPERIMENT2_ID,
|
||||
xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME,
|
||||
xpiHash: EXPERIMENT2_XPI_SHA1,
|
||||
startTime: dateToSeconds(startDate2),
|
||||
endTime: dateToSeconds(endDate2),
|
||||
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
|
||||
appName: ["XPCShell"],
|
||||
channel: ["nightly"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Data to compare the result of Experiments.getExperiments() against.
|
||||
|
||||
let experimentListData = [
|
||||
{
|
||||
id: EXPERIMENT2_ID,
|
||||
name: "Test experiment 2",
|
||||
description: "And yet another experiment that experiments experimentally.",
|
||||
},
|
||||
{
|
||||
id: EXPERIMENT1_ID,
|
||||
name: EXPERIMENT1_NAME,
|
||||
description: "Yet another experiment that experiments experimentally.",
|
||||
},
|
||||
];
|
||||
|
||||
let experiments = new Experiments.Experiments(gPolicy);
|
||||
|
||||
// Trigger update, clock set to before any activation.
|
||||
// Use updateManifest() to provide for coverage of that path.
|
||||
|
||||
let now = baseDate;
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.updateManifest();
|
||||
let list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 0, "Experiment list should be empty.");
|
||||
|
||||
expectedLogLength += 2;
|
||||
let log = TelemetryPing.getPayload().log;
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-2], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.REJECTED, EXPERIMENT1_ID, "startTime"]);
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]);
|
||||
|
||||
// Trigger update, clock set for experiment 1 to start.
|
||||
|
||||
now = futureDate(startDate1, 5 * MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryPing.getPayload().log;
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT1_ID]);
|
||||
|
||||
// Trigger update, clock set for experiment 1 to stop.
|
||||
|
||||
now = futureDate(endDate1, 1000);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entry.");
|
||||
|
||||
expectedLogLength += 2;
|
||||
log = TelemetryPing.getPayload().log;
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-2], TLOG.TERMINATION_KEY,
|
||||
[TLOG.TERMINATION.EXPIRED, EXPERIMENT1_ID]);
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]);
|
||||
|
||||
// Trigger update, clock set for experiment 2 to start with invalid hash.
|
||||
|
||||
now = startDate2;
|
||||
defineNow(gPolicy, now);
|
||||
gManifestObject.experiments[1].xpiHash = "sha1:0000000000000000000000000000000000000000";
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 1, "Experiment list should have 1 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryPing.getPayload().log;
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.INSTALL_FAILURE, EXPERIMENT2_ID]);
|
||||
|
||||
// Trigger update, clock set for experiment 2 to properly start now.
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
gManifestObject.experiments[1].xpiHash = EXPERIMENT2_XPI_SHA1;
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryPing.getPayload().log;
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT2_ID]);
|
||||
|
||||
// Fake user-disable of an experiment.
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.disableExperiment(EXPERIMENT2_ID);
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 2, "Experiment list should have 2 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryPing.getPayload().log;
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
|
||||
[TLOG.TERMINATION.USERDISABLED, EXPERIMENT2_ID]);
|
||||
|
||||
// Trigger update with experiment 1a ready to start.
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
gManifestObject.experiments[0].id = EXPERIMENT3_ID;
|
||||
gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY));
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryPing.getPayload().log;
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT3_ID]);
|
||||
|
||||
// Trigger non-user-disable of an experiment via the API
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
|
||||
yield experiments.disableExperiment(EXPERIMENT3_ID, false);
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 3, "Experiment list should have 3 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryPing.getPayload().log;
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
|
||||
[TLOG.TERMINATION.FROM_API, EXPERIMENT3_ID]);
|
||||
|
||||
// Trigger update with experiment 1a ready to start.
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
gManifestObject.experiments[0].id = EXPERIMENT4_ID;
|
||||
gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY));
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 4, "Experiment list should have 4 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryPing.getPayload().log;
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY,
|
||||
[TLOG.ACTIVATION.ACTIVATED, EXPERIMENT4_ID]);
|
||||
|
||||
// Trigger experiment termination by something other than expiry via the manifest.
|
||||
|
||||
now = futureDate(now, MS_IN_ONE_DAY);
|
||||
defineNow(gPolicy, now);
|
||||
gManifestObject.experiments[0].os = "Plan9";
|
||||
|
||||
yield experiments.updateManifest();
|
||||
list = yield experiments.getExperiments();
|
||||
Assert.equal(list.length, 4, "Experiment list should have 4 entries.");
|
||||
|
||||
expectedLogLength += 1;
|
||||
log = TelemetryPing.getPayload().log;
|
||||
Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries.");
|
||||
checkEvent(log[log.length-1], TLOG.TERMINATION_KEY,
|
||||
[TLOG.TERMINATION.RECHECK, EXPERIMENT4_ID, "os"]);
|
||||
|
||||
// Cleanup.
|
||||
|
||||
yield experiments.uninit();
|
||||
yield removeCacheFile();
|
||||
});
|
||||
|
||||
add_task(function* shutdown() {
|
||||
yield gReporter._shutdown();
|
||||
yield removeCacheFile();
|
||||
});
|
|
@ -12,4 +12,5 @@ support-files =
|
|||
[test_api.js]
|
||||
[test_conditions.js]
|
||||
[test_fetch.js]
|
||||
[test_telemetry.js]
|
||||
[test_healthreport.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче