Bug 1447499 - Refactor test helper PreferenceExperiments.withMockExperiment to work like AddonStudies.withStudies. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D5609

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Michael Cooper 2018-09-13 19:52:54 +00:00
Родитель bf250afcd0
Коммит d15f0d0fa0
4 изменённых файлов: 211 добавлений и 217 удалений

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

@ -61,7 +61,7 @@ var AboutPages = {
this.aboutStudies.registerParentListeners(); this.aboutStudies.registerParentListeners();
CleanupManager.addCleanupHandler(() => { CleanupManager.addCleanupHandler(() => {
// Stop loading processs scripts and notify existing scripts to clean up. // Stop loading process scripts and notify existing scripts to clean up.
Services.ppmm.broadcastAsyncMessage("Shield:ShuttingDown"); Services.ppmm.broadcastAsyncMessage("Shield:ShuttingDown");
Services.mm.broadcastAsyncMessage("Shield:ShuttingDown"); Services.mm.broadcastAsyncMessage("Shield:ShuttingDown");

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

@ -90,14 +90,14 @@ const PreferenceBranchType = {
/** /**
* Asynchronously load the JSON file that stores experiment status in the profile. * Asynchronously load the JSON file that stores experiment status in the profile.
*/ */
let storePromise; let gStorePromise;
function ensureStorage() { function ensureStorage() {
if (storePromise === undefined) { if (gStorePromise === undefined) {
const path = OS.Path.join(OS.Constants.Path.profileDir, EXPERIMENT_FILE); const path = OS.Path.join(OS.Constants.Path.profileDir, EXPERIMENT_FILE);
const storage = new JSONFile({path}); const storage = new JSONFile({path});
storePromise = storage.load().then(() => storage); gStorePromise = storage.load().then(() => storage);
} }
return storePromise; return gStorePromise;
} }
const log = LogManager.getLogger("preference-experiments"); const log = LogManager.getLogger("preference-experiments");
@ -248,23 +248,30 @@ var PreferenceExperiments = {
* Test wrapper that temporarily replaces the stored experiment data with fake * Test wrapper that temporarily replaces the stored experiment data with fake
* data for testing. * data for testing.
*/ */
withMockExperiments(testFunction) { withMockExperiments(mockExperiments = []) {
return async function inner(...args) { return function wrapper(testFunction) {
const oldPromise = storePromise; return async function wrappedTestFunction(...args) {
const mockExperiments = {}; const data = {};
storePromise = Promise.resolve({
data: mockExperiments, for (const exp of mockExperiments) {
saveSoon() { }, data[exp.name] = exp;
}); }
const oldObservers = experimentObservers;
experimentObservers = new Map(); const oldPromise = gStorePromise;
try { gStorePromise = Promise.resolve({
await testFunction(...args, mockExperiments); data,
} finally { saveSoon() { },
storePromise = oldPromise; });
PreferenceExperiments.stopAllObservers(); const oldObservers = experimentObservers;
experimentObservers = oldObservers; experimentObservers = new Map();
} try {
await testFunction(...args, mockExperiments);
} finally {
gStorePromise = oldPromise;
PreferenceExperiments.stopAllObservers();
experimentObservers = oldObservers;
}
};
}; };
}, },

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

@ -173,7 +173,7 @@ decorate_task(
decorate_task( decorate_task(
withSandboxManager(Assert), withSandboxManager(Assert),
withMockPreferences, withMockPreferences,
PreferenceExperiments.withMockExperiments, PreferenceExperiments.withMockExperiments(),
async function testPreferenceStudies(sandboxManager) { async function testPreferenceStudies(sandboxManager) {
const driver = new NormandyDriver(sandboxManager); const driver = new NormandyDriver(sandboxManager);
sandboxManager.cloneIntoGlobal("driver", driver, {cloneFunctions: true}); sandboxManager.cloneIntoGlobal("driver", driver, {cloneFunctions: true});

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

@ -28,9 +28,8 @@ function experimentFactory(attrs) {
// clearAllExperimentStorage // clearAllExperimentStorage
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({ name: "test" })]),
async function(experiments) { async function(experiments) {
experiments.test = experimentFactory({name: "test"});
ok(await PreferenceExperiments.has("test"), "Mock experiment is detected."); ok(await PreferenceExperiments.has("test"), "Mock experiment is detected.");
await PreferenceExperiments.clearAllExperimentStorage(); await PreferenceExperiments.clearAllExperimentStorage();
ok( ok(
@ -42,10 +41,9 @@ decorate_task(
// start should throw if an experiment with the given name already exists // start should throw if an experiment with the given name already exists
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({ name: "test" })]),
withSendEventStub, withSendEventStub,
async function(experiments, sendEventStub) { async function(experiments, sendEventStub) {
experiments.test = experimentFactory({name: "test"});
await Assert.rejects( await Assert.rejects(
PreferenceExperiments.start({ PreferenceExperiments.start({
name: "test", name: "test",
@ -69,10 +67,9 @@ decorate_task(
// start should throw if an experiment for the given preference is active // start should throw if an experiment for the given preference is active
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({ name: "test", preferenceName: "fake.preference" })]),
withSendEventStub, withSendEventStub,
async function(experiments, sendEventStub) { async function(experiments, sendEventStub) {
experiments.test = experimentFactory({name: "test", preferenceName: "fake.preference"});
await Assert.rejects( await Assert.rejects(
PreferenceExperiments.start({ PreferenceExperiments.start({
name: "different", name: "different",
@ -96,7 +93,7 @@ decorate_task(
// start should throw if an invalid preferenceBranchType is given // start should throw if an invalid preferenceBranchType is given
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
withSendEventStub, withSendEventStub,
async function(experiments, sendEventStub) { async function(experiments, sendEventStub) {
await Assert.rejects( await Assert.rejects(
@ -123,7 +120,7 @@ decorate_task(
// start should save experiment data, modify the preference, and register a // start should save experiment data, modify the preference, and register a
// watcher. // watcher.
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
withMockPreferences, withMockPreferences,
withStub(PreferenceExperiments, "startObserver"), withStub(PreferenceExperiments, "startObserver"),
withSendEventStub, withSendEventStub,
@ -139,7 +136,7 @@ decorate_task(
preferenceBranchType: "default", preferenceBranchType: "default",
preferenceType: "string", preferenceType: "string",
}); });
ok("test" in experiments, "start saved the experiment"); ok(await PreferenceExperiments.get("test"), "start saved the experiment");
ok( ok(
startObserverStub.calledWith("test", "fake.preference", "string", "newvalue"), startObserverStub.calledWith("test", "fake.preference", "string", "newvalue"),
"start registered an observer", "start registered an observer",
@ -156,7 +153,8 @@ decorate_task(
preferenceBranchType: "default", preferenceBranchType: "default",
}; };
const experiment = {}; const experiment = {};
Object.keys(expectedExperiment).forEach(key => experiment[key] = experiments.test[key]); const actualExperiment = await PreferenceExperiments.get("test");
Object.keys(expectedExperiment).forEach(key => experiment[key] = actualExperiment[key]);
Assert.deepEqual(experiment, expectedExperiment, "start saved the experiment"); Assert.deepEqual(experiment, expectedExperiment, "start saved the experiment");
is( is(
@ -179,7 +177,7 @@ decorate_task(
// start should modify the user preference for the user branch type // start should modify the user preference for the user branch type
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
withMockPreferences, withMockPreferences,
withStub(PreferenceExperiments, "startObserver"), withStub(PreferenceExperiments, "startObserver"),
async function(experiments, mockPreferences, startObserver) { async function(experiments, mockPreferences, startObserver) {
@ -211,7 +209,8 @@ decorate_task(
}; };
const experiment = {}; const experiment = {};
Object.keys(expectedExperiment).forEach(key => experiment[key] = experiments.test[key]); const actualExperiment = await PreferenceExperiments.get("test");
Object.keys(expectedExperiment).forEach(key => experiment[key] = actualExperiment[key]);
Assert.deepEqual(experiment, expectedExperiment, "start saved the experiment"); Assert.deepEqual(experiment, expectedExperiment, "start saved the experiment");
Assert.notEqual( Assert.notEqual(
@ -254,7 +253,7 @@ decorate_task(
// startObserver should throw if an observer for the experiment is already // startObserver should throw if an observer for the experiment is already
// active. // active.
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
async function() { async function() {
PreferenceExperiments.startObserver("test", "fake.preference", "string", "newvalue"); PreferenceExperiments.startObserver("test", "fake.preference", "string", "newvalue");
Assert.throws( Assert.throws(
@ -269,7 +268,7 @@ decorate_task(
// startObserver should register an observer that calls stop when a preference // startObserver should register an observer that calls stop when a preference
// changes from its experimental value. // changes from its experimental value.
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
withMockPreferences, withMockPreferences,
async function(mockExperiments, mockPreferences) { async function(mockExperiments, mockPreferences) {
const tests = [ const tests = [
@ -300,7 +299,7 @@ decorate_task(
); );
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
async function testHasObserver() { async function testHasObserver() {
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentValue"); PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentValue");
@ -316,7 +315,7 @@ decorate_task(
// stopObserver should throw if there is no observer active for it to stop. // stopObserver should throw if there is no observer active for it to stop.
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
async function() { async function() {
Assert.throws( Assert.throws(
() => PreferenceExperiments.stopObserver("neveractive", "another.fake", "othervalue"), () => PreferenceExperiments.stopObserver("neveractive", "another.fake", "othervalue"),
@ -328,7 +327,7 @@ decorate_task(
// stopObserver should cancel an active observer. // stopObserver should cancel an active observer.
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
withMockPreferences, withMockPreferences,
async function(mockExperiments, mockPreferences) { async function(mockExperiments, mockPreferences) {
const stop = sinon.stub(PreferenceExperiments, "stop"); const stop = sinon.stub(PreferenceExperiments, "stop");
@ -357,7 +356,7 @@ decorate_task(
// stopAllObservers // stopAllObservers
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
withMockPreferences, withMockPreferences,
async function(mockExperiments, mockPreferences) { async function(mockExperiments, mockPreferences) {
const stop = sinon.stub(PreferenceExperiments, "stop"); const stop = sinon.stub(PreferenceExperiments, "stop");
@ -390,7 +389,7 @@ decorate_task(
// markLastSeen should throw if it can't find a matching experiment // markLastSeen should throw if it can't find a matching experiment
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
async function() { async function() {
await Assert.rejects( await Assert.rejects(
PreferenceExperiments.markLastSeen("neveractive"), PreferenceExperiments.markLastSeen("neveractive"),
@ -401,14 +400,13 @@ decorate_task(
); );
// markLastSeen should update the lastSeen date // markLastSeen should update the lastSeen date
const oldDate = new Date(1988, 10, 1).toJSON();
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({ name: "test", lastSeen: oldDate })]),
async function(experiments) { async function([experiment]) {
const oldDate = new Date(1988, 10, 1).toJSON();
experiments.test = experimentFactory({name: "test", lastSeen: oldDate});
await PreferenceExperiments.markLastSeen("test"); await PreferenceExperiments.markLastSeen("test");
Assert.notEqual( Assert.notEqual(
experiments.test.lastSeen, experiment.lastSeen,
oldDate, oldDate,
"markLastSeen updated the experiment lastSeen date", "markLastSeen updated the experiment lastSeen date",
); );
@ -417,7 +415,7 @@ decorate_task(
// stop should throw if an experiment with the given name doesn't exist // stop should throw if an experiment with the given name doesn't exist
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
withSendEventStub, withSendEventStub,
async function(experiments, sendEventStub) { async function(experiments, sendEventStub) {
await Assert.rejects( await Assert.rejects(
@ -436,10 +434,9 @@ decorate_task(
// stop should throw if the experiment is already expired // stop should throw if the experiment is already expired
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({ name: "test", expired: true })]),
withSendEventStub, withSendEventStub,
async function(experiments, sendEventStub) { async function(experiments, sendEventStub) {
experiments.test = experimentFactory({name: "test", expired: true});
await Assert.rejects( await Assert.rejects(
PreferenceExperiments.stop("test"), PreferenceExperiments.stop("test"),
/already expired/, /already expired/,
@ -457,7 +454,18 @@ decorate_task(
// stop should mark the experiment as expired, stop its observer, and revert the // stop should mark the experiment as expired, stop its observer, and revert the
// preference value. // preference value.
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([
experimentFactory({
name: "test",
expired: false,
branch: "fakebranch",
preferenceName: "fake.preference",
preferenceValue: "experimentvalue",
preferenceType: "string",
previousPreferenceValue: "oldvalue",
preferenceBranchType: "default",
}),
]),
withMockPreferences, withMockPreferences,
withSpy(PreferenceExperiments, "stopObserver"), withSpy(PreferenceExperiments, "stopObserver"),
withSendEventStub, withSendEventStub,
@ -468,21 +476,12 @@ decorate_task(
mockPreferences.set(`${startupPrefs}.fake.preference`, "experimentvalue", "user"); mockPreferences.set(`${startupPrefs}.fake.preference`, "experimentvalue", "user");
mockPreferences.set("fake.preference", "experimentvalue", "default"); mockPreferences.set("fake.preference", "experimentvalue", "default");
experiments.test = experimentFactory({
name: "test",
expired: false,
branch: "fakebranch",
preferenceName: "fake.preference",
preferenceValue: "experimentvalue",
preferenceType: "string",
previousPreferenceValue: "oldvalue",
preferenceBranchType: "default",
});
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue"); PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
await PreferenceExperiments.stop("test", {reason: "test-reason"}); await PreferenceExperiments.stop("test", {reason: "test-reason"});
ok(stopObserverSpy.calledWith("test"), "stop removed an observer"); ok(stopObserverSpy.calledWith("test"), "stop removed an observer");
is(experiments.test.expired, true, "stop marked the experiment as expired"); const experiment = await PreferenceExperiments.get("test");
is(experiment.expired, true, "stop marked the experiment as expired");
is( is(
DefaultPreferences.get("fake.preference"), DefaultPreferences.get("fake.preference"),
"oldvalue", "oldvalue",
@ -495,7 +494,7 @@ decorate_task(
Assert.deepEqual( Assert.deepEqual(
sendEventStub.args, sendEventStub.args,
[["unenroll", "preference_study", experiments.test.name, { [["unenroll", "preference_study", "test", {
didResetValue: "true", didResetValue: "true",
reason: "test-reason", reason: "test-reason",
branch: "fakebranch", branch: "fakebranch",
@ -509,7 +508,15 @@ decorate_task(
// stop should also support user pref experiments // stop should also support user pref experiments
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({
name: "test",
expired: false,
preferenceName: "fake.preference",
preferenceValue: "experimentvalue",
preferenceType: "string",
previousPreferenceValue: "oldvalue",
preferenceBranchType: "user",
})]),
withMockPreferences, withMockPreferences,
withStub(PreferenceExperiments, "stopObserver"), withStub(PreferenceExperiments, "stopObserver"),
withStub(PreferenceExperiments, "hasObserver"), withStub(PreferenceExperiments, "hasObserver"),
@ -517,20 +524,12 @@ decorate_task(
hasObserver.returns(true); hasObserver.returns(true);
mockPreferences.set("fake.preference", "experimentvalue", "user"); mockPreferences.set("fake.preference", "experimentvalue", "user");
experiments.test = experimentFactory({
name: "test",
expired: false,
preferenceName: "fake.preference",
preferenceValue: "experimentvalue",
preferenceType: "string",
previousPreferenceValue: "oldvalue",
preferenceBranchType: "user",
});
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue"); PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
await PreferenceExperiments.stop("test"); await PreferenceExperiments.stop("test");
ok(stopObserver.calledWith("test"), "stop removed an observer"); ok(stopObserver.calledWith("test"), "stop removed an observer");
is(experiments.test.expired, true, "stop marked the experiment as expired"); const experiment = await PreferenceExperiments.get("test");
is(experiment.expired, true, "stop marked the experiment as expired");
is( is(
Preferences.get("fake.preference"), Preferences.get("fake.preference"),
"oldvalue", "oldvalue",
@ -543,20 +542,19 @@ decorate_task(
// stop should remove a preference that had no value prior to an experiment for user prefs // stop should remove a preference that had no value prior to an experiment for user prefs
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({
name: "test",
expired: false,
preferenceName: "fake.preference",
preferenceValue: "experimentvalue",
preferenceType: "string",
previousPreferenceValue: null,
preferenceBranchType: "user",
})]),
withMockPreferences, withMockPreferences,
async function(experiments, mockPreferences) { async function(experiments, mockPreferences) {
const stopObserver = sinon.stub(PreferenceExperiments, "stopObserver"); const stopObserver = sinon.stub(PreferenceExperiments, "stopObserver");
mockPreferences.set("fake.preference", "experimentvalue", "user"); mockPreferences.set("fake.preference", "experimentvalue", "user");
experiments.test = experimentFactory({
name: "test",
expired: false,
preferenceName: "fake.preference",
preferenceValue: "experimentvalue",
preferenceType: "string",
previousPreferenceValue: null,
preferenceBranchType: "user",
});
await PreferenceExperiments.stop("test"); await PreferenceExperiments.stop("test");
ok( ok(
@ -570,22 +568,21 @@ decorate_task(
// stop should not modify a preference if resetValue is false // stop should not modify a preference if resetValue is false
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({
name: "test",
expired: false,
branch: "fakebranch",
preferenceName: "fake.preference",
preferenceValue: "experimentvalue",
preferenceType: "string",
previousPreferenceValue: "oldvalue",
preferenceBranchType: "default",
})]),
withMockPreferences, withMockPreferences,
withStub(PreferenceExperiments, "stopObserver"), withStub(PreferenceExperiments, "stopObserver"),
withSendEventStub, withSendEventStub,
async function testStopReset(experiments, mockPreferences, stopObserverStub, sendEventStub) { async function testStopReset(experiments, mockPreferences, stopObserverStub, sendEventStub) {
mockPreferences.set("fake.preference", "customvalue", "default"); mockPreferences.set("fake.preference", "customvalue", "default");
experiments.test = experimentFactory({
name: "test",
expired: false,
branch: "fakebranch",
preferenceName: "fake.preference",
preferenceValue: "experimentvalue",
preferenceType: "string",
previousPreferenceValue: "oldvalue",
preferenceBranchType: "default",
});
await PreferenceExperiments.stop("test", {reason: "test-reason", resetValue: false}); await PreferenceExperiments.stop("test", {reason: "test-reason", resetValue: false});
is( is(
@ -595,7 +592,7 @@ decorate_task(
); );
Assert.deepEqual( Assert.deepEqual(
sendEventStub.args, sendEventStub.args,
[["unenroll", "preference_study", experiments.test.name, { [["unenroll", "preference_study", "test", {
didResetValue: "false", didResetValue: "false",
reason: "test-reason", reason: "test-reason",
branch: "fakebranch", branch: "fakebranch",
@ -607,7 +604,7 @@ decorate_task(
// get should throw if no experiment exists with the given name // get should throw if no experiment exists with the given name
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
async function() { async function() {
await Assert.rejects( await Assert.rejects(
PreferenceExperiments.get("neverexisted"), PreferenceExperiments.get("neverexisted"),
@ -619,29 +616,25 @@ decorate_task(
// get // get
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({ name: "test" })]),
async function(experiments) { async function(experiments) {
const experiment = experimentFactory({name: "test"}); const experiment = await PreferenceExperiments.get("test");
experiments.test = experiment; is(experiment.name, "test", "get fetches the correct experiment");
const fetchedExperiment = await PreferenceExperiments.get("test");
Assert.deepEqual(fetchedExperiment, experiment, "get fetches the correct experiment");
// Modifying the fetched experiment must not edit the data source. // Modifying the fetched experiment must not edit the data source.
fetchedExperiment.name = "othername"; experiment.name = "othername";
is(experiments.test.name, "test", "get returns a copy of the experiment"); const refetched = await PreferenceExperiments.get("test");
is(refetched.name, "test", "get returns a copy of the experiment");
} }
); );
// get all // get all
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([
async function testGetAll(experiments) { experimentFactory({ name: "experiment1", disabled: false }),
const experiment1 = experimentFactory({name: "experiment1"}); experimentFactory({ name: "experiment2", disabled: true }),
const experiment2 = experimentFactory({name: "experiment2", disabled: true}); ]),
experiments.experiment1 = experiment1; async function testGetAll([experiment1, experiment2]) {
experiments.experiment2 = experiment2;
const fetchedExperiments = await PreferenceExperiments.getAll(); const fetchedExperiments = await PreferenceExperiments.getAll();
is(fetchedExperiments.length, 2, "getAll returns a list of all stored experiments"); is(fetchedExperiments.length, 2, "getAll returns a list of all stored experiments");
Assert.deepEqual( Assert.deepEqual(
@ -663,28 +656,29 @@ decorate_task(
// get all active // get all active
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([
withMockPreferences, experimentFactory({
async function testGetAllActive(experiments) {
experiments.active = experimentFactory({
name: "active", name: "active",
expired: false, expired: false,
}); }),
experiments.inactive = experimentFactory({ experimentFactory({
name: "inactive", name: "inactive",
expired: true, expired: true,
}); }),
]),
const activeExperiments = await PreferenceExperiments.getAllActive(); withMockPreferences,
async function testGetAllActive([activeExperiment, inactiveExperiment]) {
let allActiveExperiments = await PreferenceExperiments.getAllActive();
Assert.deepEqual( Assert.deepEqual(
activeExperiments, allActiveExperiments,
[experiments.active], [activeExperiment],
"getAllActive only returns active experiments", "getAllActive only returns active experiments",
); );
activeExperiments[0].name = "newfakename"; allActiveExperiments[0].name = "newfakename";
allActiveExperiments = await PreferenceExperiments.getAllActive();
Assert.notEqual( Assert.notEqual(
experiments.active.name, allActiveExperiments,
"newfakename", "newfakename",
"getAllActive returns copies of stored experiments", "getAllActive returns copies of stored experiments",
); );
@ -693,9 +687,8 @@ decorate_task(
// has // has
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({ name: "test" })]),
async function(experiments) { async function(experiments) {
experiments.test = experimentFactory({name: "test"});
ok(await PreferenceExperiments.has("test"), "has returned true for a stored experiment"); ok(await PreferenceExperiments.has("test"), "has returned true for a stored experiment");
ok(!(await PreferenceExperiments.has("missing")), "has returned false for a missing experiment"); ok(!(await PreferenceExperiments.has("missing")), "has returned false for a missing experiment");
} }
@ -703,25 +696,22 @@ decorate_task(
// init should register telemetry experiments // init should register telemetry experiments
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({
name: "test",
branch: "branch",
preferenceName: "fake.pref",
preferenceValue: "experiment value",
expired: false,
preferenceBranchType: "default",
})]),
withMockPreferences, withMockPreferences,
withStub(TelemetryEnvironment, "setExperimentActive"), withStub(TelemetryEnvironment, "setExperimentActive"),
withStub(PreferenceExperiments, "startObserver"), withStub(PreferenceExperiments, "startObserver"),
async function testInit(experiments, mockPreferences, setActiveStub, startObserverStub) { async function testInit(experiments, mockPreferences, setActiveStub, startObserverStub) {
mockPreferences.set("fake.pref", "experiment value"); mockPreferences.set("fake.pref", "experiment value");
experiments.test = experimentFactory({
name: "test",
branch: "branch",
preferenceName: "fake.pref",
preferenceValue: "experiment value",
expired: false,
preferenceBranchType: "default",
});
await PreferenceExperiments.init(); await PreferenceExperiments.init();
ok( ok(
setActiveStub.calledWith("test", "branch", {type: "normandy-exp"}), setActiveStub.calledWith("test", "branch", { type: "normandy-exp" }),
"Experiment is registered by init", "Experiment is registered by init",
); );
}, },
@ -729,24 +719,21 @@ decorate_task(
// init should use the provided experiment type // init should use the provided experiment type
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({
name: "test",
branch: "branch",
preferenceName: "fake.pref",
preferenceValue: "experiment value",
experimentType: "pref-test",
})]),
withMockPreferences, withMockPreferences,
withStub(TelemetryEnvironment, "setExperimentActive"), withStub(TelemetryEnvironment, "setExperimentActive"),
withStub(PreferenceExperiments, "startObserver"), withStub(PreferenceExperiments, "startObserver"),
async function testInit(experiments, mockPreferences, setActiveStub, startObserverStub) { async function testInit(experiments, mockPreferences, setActiveStub, startObserverStub) {
mockPreferences.set("fake.pref", "experiment value"); mockPreferences.set("fake.pref", "experiment value");
experiments.test = experimentFactory({
name: "test",
branch: "branch",
preferenceName: "fake.pref",
preferenceValue: "experiment value",
experimentType: "pref-test",
});
await PreferenceExperiments.init(); await PreferenceExperiments.init();
ok( ok(
setActiveStub.calledWith("test", "branch", {type: "normandy-pref-test"}), setActiveStub.calledWith("test", "branch", { type: "normandy-pref-test" }),
"init should use the provided experiment type", "init should use the provided experiment type",
); );
}, },
@ -754,7 +741,7 @@ decorate_task(
// starting and stopping experiments should register in telemetry // starting and stopping experiments should register in telemetry
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
withStub(TelemetryEnvironment, "setExperimentActive"), withStub(TelemetryEnvironment, "setExperimentActive"),
withStub(TelemetryEnvironment, "setExperimentInactive"), withStub(TelemetryEnvironment, "setExperimentInactive"),
withSendEventStub, withSendEventStub,
@ -770,10 +757,10 @@ decorate_task(
Assert.deepEqual( Assert.deepEqual(
setActiveStub.getCall(0).args, setActiveStub.getCall(0).args,
["test", "branch", {type: "normandy-exp"}], ["test", "branch", { type: "normandy-exp" }],
"Experiment is registered by start()", "Experiment is registered by start()",
); );
await PreferenceExperiments.stop("test", {reason: "test-reason"}); await PreferenceExperiments.stop("test", { reason: "test-reason" });
Assert.deepEqual(setInactiveStub.args, [["test"]], "Experiment is unregistered by stop()"); Assert.deepEqual(setInactiveStub.args, [["test"]], "Experiment is unregistered by stop()");
Assert.deepEqual( Assert.deepEqual(
@ -796,7 +783,7 @@ decorate_task(
// starting experiments should use the provided experiment type // starting experiments should use the provided experiment type
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
withStub(TelemetryEnvironment, "setExperimentActive"), withStub(TelemetryEnvironment, "setExperimentActive"),
withStub(TelemetryEnvironment, "setExperimentInactive"), withStub(TelemetryEnvironment, "setExperimentInactive"),
withSendEventStub, withSendEventStub,
@ -813,7 +800,7 @@ decorate_task(
Assert.deepEqual( Assert.deepEqual(
setActiveStub.getCall(0).args, setActiveStub.getCall(0).args,
["test", "branch", {type: "normandy-pref-test"}], ["test", "branch", { type: "normandy-pref-test" }],
"start() should register the experiment with the provided type", "start() should register the experiment with the provided type",
); );
@ -834,10 +821,9 @@ decorate_task(
// Experiments shouldn't be recorded by init() in telemetry if they are expired // Experiments shouldn't be recorded by init() in telemetry if they are expired
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({ name: "expired", branch: "branch", expired: true })]),
withStub(TelemetryEnvironment, "setExperimentActive"), withStub(TelemetryEnvironment, "setExperimentActive"),
async function testInitTelemetryExpired(experiments, setActiveStub) { async function testInitTelemetryExpired(experiments, setActiveStub) {
experiments.experiment1 = experimentFactory({name: "expired", branch: "branch", expired: true});
await PreferenceExperiments.init(); await PreferenceExperiments.init();
ok(!setActiveStub.called, "Expired experiment is not registered by init"); ok(!setActiveStub.called, "Expired experiment is not registered by init");
}, },
@ -845,17 +831,16 @@ decorate_task(
// Experiments should end if the preference has been changed when init() is called // Experiments should end if the preference has been changed when init() is called
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({
name: "test",
preferenceName: "fake.preference",
preferenceValue: "experiment value",
})]),
withMockPreferences, withMockPreferences,
withStub(PreferenceExperiments, "stop"), withStub(PreferenceExperiments, "stop"),
async function testInitChanges(experiments, mockPreferences, stopStub) { async function testInitChanges(experiments, mockPreferences, stopStub) {
mockPreferences.set("fake.preference", "experiment value", "default"); mockPreferences.set("fake.preference", "experiment value", "default");
experiments.test = experimentFactory({ mockPreferences.set("fake.preference", "changed value", "user");
name: "test",
preferenceName: "fake.preference",
preferenceValue: "experiment value",
});
mockPreferences.set("fake.preference", "changed value");
await PreferenceExperiments.init(); await PreferenceExperiments.init();
is(Preferences.get("fake.preference"), "changed value", "Preference value was not changed"); is(Preferences.get("fake.preference"), "changed value", "Preference value was not changed");
@ -873,7 +858,11 @@ decorate_task(
// init should register an observer for experiments // init should register an observer for experiments
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({
name: "test",
preferenceName: "fake.preference",
preferenceValue: "experiment value",
})]),
withMockPreferences, withMockPreferences,
withStub(PreferenceExperiments, "startObserver"), withStub(PreferenceExperiments, "startObserver"),
withStub(PreferenceExperiments, "stop"), withStub(PreferenceExperiments, "stop"),
@ -882,11 +871,6 @@ decorate_task(
stop.throws("Stop should not be called"); stop.throws("Stop should not be called");
mockPreferences.set("fake.preference", "experiment value", "default"); mockPreferences.set("fake.preference", "experiment value", "default");
is(Preferences.get("fake.preference"), "experiment value", "pref shouldn't have a user value"); is(Preferences.get("fake.preference"), "experiment value", "pref shouldn't have a user value");
experiments.test = experimentFactory({
name: "test",
preferenceName: "fake.preference",
preferenceValue: "experiment value",
});
await PreferenceExperiments.init(); await PreferenceExperiments.init();
ok(startObserver.calledOnce, "init should register an observer"); ok(startObserver.calledOnce, "init should register an observer");
@ -900,21 +884,24 @@ decorate_task(
// saveStartupPrefs // saveStartupPrefs
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([
experimentFactory({
name: "char",
preferenceName: `fake.char`,
preferenceValue: "string",
}),
experimentFactory({
name: "int",
preferenceName: `fake.int`,
preferenceValue: 2,
}),
experimentFactory({
name: "bool",
preferenceName: `fake.bool`,
preferenceValue: true,
}),
]),
async function testSaveStartupPrefs(experiments) { async function testSaveStartupPrefs(experiments) {
const experimentPrefs = {
char: "string",
int: 2,
bool: true,
};
for (const [key, value] of Object.entries(experimentPrefs)) {
experiments[key] = experimentFactory({
preferenceName: `fake.${key}`,
preferenceValue: value,
});
}
Services.prefs.deleteBranch(startupPrefs); Services.prefs.deleteBranch(startupPrefs);
Services.prefs.setBoolPref(`${startupPrefs}.fake.old`, true); Services.prefs.setBoolPref(`${startupPrefs}.fake.old`, true);
await PreferenceExperiments.saveStartupPrefs(); await PreferenceExperiments.saveStartupPrefs();
@ -942,13 +929,12 @@ decorate_task(
// saveStartupPrefs errors for invalid pref type // saveStartupPrefs errors for invalid pref type
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({
name: "test",
preferenceName: "fake.invalidValue",
preferenceValue: new Date(),
})]),
async function testSaveStartupPrefsError(experiments) { async function testSaveStartupPrefsError(experiments) {
experiments.test = experimentFactory({
preferenceName: "fake.invalidValue",
preferenceValue: new Date(),
});
await Assert.rejects( await Assert.rejects(
PreferenceExperiments.saveStartupPrefs(), PreferenceExperiments.saveStartupPrefs(),
/invalid preference type/i, /invalid preference type/i,
@ -959,19 +945,21 @@ decorate_task(
// saveStartupPrefs should not store values for user-branch recipes // saveStartupPrefs should not store values for user-branch recipes
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([
async function testSaveStartupPrefsUserBranch(experiments) { experimentFactory({
experiments.defaultBranchRecipe = experimentFactory({ name: "defaultBranchRecipe",
preferenceName: "fake.default", preferenceName: "fake.default",
preferenceValue: "experiment value", preferenceValue: "experiment value",
branch: "default", branch: "default",
}); }),
experiments.userBranchRecipe = experimentFactory({ experimentFactory({
name: "userBranchRecipe",
preferenceName: "fake.user", preferenceName: "fake.user",
preferenceValue: "experiment value", preferenceValue: "experiment value",
branch: "user", branch: "user",
}); }),
]),
async function testSaveStartupPrefsUserBranch(experiments) {
await PreferenceExperiments.saveStartupPrefs(); await PreferenceExperiments.saveStartupPrefs();
is( is(
@ -991,7 +979,7 @@ decorate_task(
// test that default branch prefs restore to the right value if the default pref changes // test that default branch prefs restore to the right value if the default pref changes
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
withMockPreferences, withMockPreferences,
withStub(PreferenceExperiments, "startObserver"), withStub(PreferenceExperiments, "startObserver"),
withStub(PreferenceExperiments, "stopObserver"), withStub(PreferenceExperiments, "stopObserver"),
@ -1039,7 +1027,7 @@ decorate_task(
// test that default branch prefs restore to the right value if the preference is removed // test that default branch prefs restore to the right value if the preference is removed
decorate_task( decorate_task(
withMockExperiments, withMockExperiments(),
withMockPreferences, withMockPreferences,
withStub(PreferenceExperiments, "startObserver"), withStub(PreferenceExperiments, "startObserver"),
withStub(PreferenceExperiments, "stopObserver"), withStub(PreferenceExperiments, "stopObserver"),
@ -1086,13 +1074,12 @@ decorate_task(
// stop should pass "unknown" to telemetry event for `reason` if none is specified // stop should pass "unknown" to telemetry event for `reason` if none is specified
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([experimentFactory({ name: "test", preferenceName: "fake.preference" })]),
withMockPreferences, withMockPreferences,
withStub(PreferenceExperiments, "stopObserver"), withStub(PreferenceExperiments, "stopObserver"),
withSendEventStub, withSendEventStub,
async function testStopUnknownReason(experiments, mockPreferences, stopObserverStub, sendEventStub) { async function testStopUnknownReason(experiments, mockPreferences, stopObserverStub, sendEventStub) {
mockPreferences.set("fake.preference", "default value", "default"); mockPreferences.set("fake.preference", "default value", "default");
experiments.test = experimentFactory({ name: "test", preferenceName: "fake.preference" });
await PreferenceExperiments.stop("test"); await PreferenceExperiments.stop("test");
is( is(
sendEventStub.getCall(0).args[3].reason, sendEventStub.getCall(0).args[3].reason,
@ -1104,14 +1091,16 @@ decorate_task(
// stop should pass along the value for resetValue to Telemetry Events as didResetValue // stop should pass along the value for resetValue to Telemetry Events as didResetValue
decorate_task( decorate_task(
withMockExperiments, withMockExperiments([
experimentFactory({ name: "test1", preferenceName: "fake.preference1" }),
experimentFactory({ name: "test2", preferenceName: "fake.preference2" }),
]),
withMockPreferences, withMockPreferences,
withStub(PreferenceExperiments, "stopObserver"), withStub(PreferenceExperiments, "stopObserver"),
withSendEventStub, withSendEventStub,
async function testStopResetValue(experiments, mockPreferences, stopObserverStub, sendEventStub) { async function testStopResetValue(experiments, mockPreferences, stopObserverStub, sendEventStub) {
mockPreferences.set("fake.preference1", "default value", "default"); mockPreferences.set("fake.preference1", "default value", "default");
experiments.test1 = experimentFactory({ name: "test1", preferenceName: "fake.preference1" }); await PreferenceExperiments.stop("test1", { resetValue: true });
await PreferenceExperiments.stop("test1", {resetValue: true});
is(sendEventStub.callCount, 1); is(sendEventStub.callCount, 1);
is( is(
sendEventStub.getCall(0).args[3].didResetValue, sendEventStub.getCall(0).args[3].didResetValue,
@ -1120,8 +1109,7 @@ decorate_task(
); );
mockPreferences.set("fake.preference2", "default value", "default"); mockPreferences.set("fake.preference2", "default value", "default");
experiments.test2 = experimentFactory({ name: "test2", preferenceName: "fake.preference2" }); await PreferenceExperiments.stop("test2", { resetValue: false });
await PreferenceExperiments.stop("test2", {resetValue: false});
is(sendEventStub.callCount, 2); is(sendEventStub.callCount, 2);
is( is(
sendEventStub.getCall(1).args[3].didResetValue, sendEventStub.getCall(1).args[3].didResetValue,
@ -1136,20 +1124,19 @@ decorate_task(
decorate_task( decorate_task(
withMockPreferences, withMockPreferences,
withSendEventStub, withSendEventStub,
withMockExperiments, withMockExperiments([experimentFactory({
name: "test",
expired: false,
branch: "fakebranch",
preferenceName: "fake.preference",
preferenceValue: "experimentvalue",
preferenceType: "string",
previousPreferenceValue: "oldvalue",
preferenceBranchType: "default",
})]),
async function testPrefChangeEventTelemetry(mockPreferences, sendEventStub, mockExperiments) { async function testPrefChangeEventTelemetry(mockPreferences, sendEventStub, mockExperiments) {
is(Preferences.get("fake.preference"), null, "preference should start unset"); is(Preferences.get("fake.preference"), null, "preference should start unset");
mockPreferences.set("fake.preference", "oldvalue", "default"); mockPreferences.set("fake.preference", "oldvalue", "default");
mockExperiments.test = experimentFactory({
name: "test",
expired: false,
branch: "fakebranch",
preferenceName: "fake.preference",
preferenceValue: "experimentvalue",
preferenceType: "string",
previousPreferenceValue: "oldvalue",
preferenceBranchType: "default",
});
PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue"); PreferenceExperiments.startObserver("test", "fake.preference", "string", "experimentvalue");
// setting the preference on the user branch should trigger the observer to stop the experiment // setting the preference on the user branch should trigger the observer to stop the experiment