Bug 983360 - Telemetry experiments: add monitoring data to Telemetry payload. r=bsmedberg

This commit is contained in:
Georg Fritzsche 2014-03-24 09:58:57 +01:00
Родитель 863af40e1e
Коммит 6dc6f7ec05
6 изменённых файлов: 496 добавлений и 39 удалений

Просмотреть файл

@ -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]