diff options
Diffstat (limited to 'browser/experiments/test/xpcshell/test_cache.js')
-rw-r--r-- | browser/experiments/test/xpcshell/test_cache.js | 399 |
1 files changed, 399 insertions, 0 deletions
diff --git a/browser/experiments/test/xpcshell/test_cache.js b/browser/experiments/test/xpcshell/test_cache.js new file mode 100644 index 000000000..4f2bce881 --- /dev/null +++ b/browser/experiments/test/xpcshell/test_cache.js @@ -0,0 +1,399 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Cu.import("resource://testing-common/httpd.js"); +XPCOMUtils.defineLazyModuleGetter(this, "Experiments", + "resource:///modules/experiments/Experiments.jsm"); + +const MANIFEST_HANDLER = "manifests/handler"; + +const SEC_IN_ONE_DAY = 24 * 60 * 60; +const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; + +var gHttpServer = null; +var gHttpRoot = null; +var gDataRoot = null; +var gPolicy = null; +var gManifestObject = null; +var gManifestHandlerURI = null; + +function run_test() { + run_next_test(); +} + +add_task(function* test_setup() { + loadAddonManager(); + yield removeCacheFile(); + + 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(() => {})); + + 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); + + gPolicy = new Experiments.Policy(); + patchPolicy(gPolicy, { + updatechannel: () => "nightly", + oneshotTimer: (callback, timeout, thisObj, name) => {}, + }); +}); + +function checkExperimentListsEqual(list, list2) { + Assert.equal(list.length, list2.length, "Lists should have the same length.") + + for (let i=0; i<list.length; ++i) { + for (let k of Object.keys(list[i])) { + Assert.equal(list[i][k], list2[i][k], + "Field '" + k + "' should match for list entry " + i + "."); + } + } +} + +function checkExperimentSerializations(experimentEntryIterator) { + for (let experiment of experimentEntryIterator) { + let experiment2 = new Experiments.ExperimentEntry(gPolicy); + let jsonStr = JSON.stringify(experiment.toJSON()); + Assert.ok(experiment2.initFromCacheData(JSON.parse(jsonStr)), + "Should have initialized successfully from JSON serialization."); + Assert.equal(JSON.stringify(experiment), JSON.stringify(experiment2), + "Object stringifications should match."); + } +} + +function validateCache(cachedExperiments, experimentIds) { + let cachedExperimentIds = new Set(cachedExperiments); + Assert.equal(cachedExperimentIds.size, experimentIds.length, + "The number of cached experiments does not match with the provided list"); + for (let id of experimentIds) { + Assert.ok(cachedExperimentIds.has(id), "The cache must contain the experiment with id " + id); + } +} + +// Set up an experiments instance and check if it is properly restored from cache. + +add_task(function* test_cache() { + // The manifest data we test with. + + gManifestObject = { + "version": 1, + experiments: [ + { + id: EXPERIMENT1_ID, + xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, + xpiHash: EXPERIMENT1_XPI_SHA1, + maxActiveSeconds: 10 * SEC_IN_ONE_DAY, + appName: ["XPCShell"], + channel: ["nightly"], + }, + { + id: EXPERIMENT2_ID, + xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, + xpiHash: EXPERIMENT2_XPI_SHA1, + maxActiveSeconds: 10 * SEC_IN_ONE_DAY, + appName: ["XPCShell"], + channel: ["nightly"], + }, + { + id: EXPERIMENT3_ID, + xpiURL: "https://inval.id/foo.xpi", + xpiHash: "sha1:0000000000000000000000000000000000000000", + maxActiveSeconds: 10 * SEC_IN_ONE_DAY, + appName: ["XPCShell"], + channel: ["nightly"], + }, + ], + }; + + // Setup dates for the experiments. + + let baseDate = new Date(2014, 5, 1, 12); + let startDates = []; + let endDates = []; + + for (let i=0; i<gManifestObject.experiments.length; ++i) { + let experiment = gManifestObject.experiments[i]; + startDates.push(futureDate(baseDate, (50 + (150 * i)) * MS_IN_ONE_DAY)); + endDates .push(futureDate(startDates[i], 50 * MS_IN_ONE_DAY)); + experiment.startTime = dateToSeconds(startDates[i]); + experiment.endTime = dateToSeconds(endDates[i]); + } + + // 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.", + }, + ]; + + // Trigger update & re-init, clock set to before any activation. + + let now = baseDate; + defineNow(gPolicy, now); + + let experiments = new Experiments.Experiments(gPolicy); + yield experiments.updateManifest(); + let list = yield experiments.getExperiments(); + Assert.equal(list.length, 0, "Experiment list should be empty."); + checkExperimentSerializations(experiments._experiments.values()); + + yield promiseRestartManager(); + experiments = new Experiments.Experiments(gPolicy); + + yield experiments._run(); + list = yield experiments.getExperiments(); + Assert.equal(list.length, 0, "Experiment list should be empty."); + checkExperimentSerializations(experiments._experiments.values()); + + // Re-init, clock set for experiment 1 to start. + + now = futureDate(startDates[0], 5 * MS_IN_ONE_DAY); + defineNow(gPolicy, now); + + yield promiseRestartManager(); + experiments = new Experiments.Experiments(gPolicy); + yield experiments._run(); + + list = yield experiments.getExperiments(); + Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); + + experimentListData[1].active = true; + experimentListData[1].endDate = now.getTime() + 10 * MS_IN_ONE_DAY; + checkExperimentListsEqual(experimentListData.slice(1), list); + checkExperimentSerializations(experiments._experiments.values()); + + let branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID); + Assert.strictEqual(branch, null); + + yield experiments.setExperimentBranch(EXPERIMENT1_ID, "testbranch"); + branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID); + Assert.strictEqual(branch, "testbranch"); + + // Re-init, clock set for experiment 1 to stop. + + now = futureDate(now, 20 * MS_IN_ONE_DAY); + defineNow(gPolicy, now); + + yield promiseRestartManager(); + experiments = new Experiments.Experiments(gPolicy); + yield experiments._run(); + + list = yield experiments.getExperiments(); + Assert.equal(list.length, 1, "Experiment list should have 1 entry."); + + experimentListData[1].active = false; + experimentListData[1].endDate = now.getTime(); + checkExperimentListsEqual(experimentListData.slice(1), list); + checkExperimentSerializations(experiments._experiments.values()); + + branch = yield experiments.getExperimentBranch(EXPERIMENT1_ID); + Assert.strictEqual(branch, "testbranch"); + + // Re-init, clock set for experiment 2 to start. + + now = futureDate(startDates[1], 20 * MS_IN_ONE_DAY); + defineNow(gPolicy, now); + + yield promiseRestartManager(); + experiments = new Experiments.Experiments(gPolicy); + yield experiments._run(); + + list = yield experiments.getExperiments(); + Assert.equal(list.length, 2, "Experiment list should have 2 entries."); + + experimentListData[0].active = true; + experimentListData[0].endDate = now.getTime() + 10 * MS_IN_ONE_DAY; + checkExperimentListsEqual(experimentListData, list); + checkExperimentSerializations(experiments._experiments.values()); + + // Re-init, clock set for experiment 2 to stop. + + now = futureDate(now, 20 * MS_IN_ONE_DAY); + defineNow(gPolicy, now); + + yield promiseRestartManager(); + experiments = new Experiments.Experiments(gPolicy); + yield experiments._run(); + + list = yield experiments.getExperiments(); + Assert.equal(list.length, 2, "Experiment list should have 2 entries."); + + experimentListData[0].active = false; + experimentListData[0].endDate = now.getTime(); + checkExperimentListsEqual(experimentListData, list); + checkExperimentSerializations(experiments._experiments.values()); + + // Cleanup. + + yield experiments._toggleExperimentsEnabled(false); + yield promiseRestartManager(); + yield removeCacheFile(); +}); + +add_task(function* test_expiration() { + // The manifest data we test with. + gManifestObject = { + "version": 1, + experiments: [ + { + id: EXPERIMENT1_ID, + xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, + xpiHash: EXPERIMENT1_XPI_SHA1, + maxActiveSeconds: 10 * SEC_IN_ONE_DAY, + appName: ["XPCShell"], + channel: ["nightly"], + }, + { + id: EXPERIMENT2_ID, + xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, + xpiHash: EXPERIMENT2_XPI_SHA1, + maxActiveSeconds: 50 * SEC_IN_ONE_DAY, + appName: ["XPCShell"], + channel: ["nightly"], + }, + // The 3rd experiment will never run, so it's ok to use experiment's 2 data. + { + id: EXPERIMENT3_ID, + xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, + xpiHash: EXPERIMENT2_XPI_SHA1, + 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.", + }, + ]; + + // Setup dates for the experiments. + let baseDate = new Date(2014, 5, 1, 12); + let startDates = []; + let endDates = []; + + for (let i=0; i<gManifestObject.experiments.length; ++i) { + let experiment = gManifestObject.experiments[i]; + // Spread out experiments in time so that one experiment can end and expire while + // the next is still running. + startDates.push(futureDate(baseDate, (50 + (200 * i)) * MS_IN_ONE_DAY)); + endDates .push(futureDate(startDates[i], 50 * MS_IN_ONE_DAY)); + experiment.startTime = dateToSeconds(startDates[i]); + experiment.endTime = dateToSeconds(endDates[i]); + } + + let now = null; + let experiments = null; + + let setDateAndRestartExperiments = new Task.async(function* (newDate) { + now = newDate; + defineNow(gPolicy, now); + + yield promiseRestartManager(); + experiments = new Experiments.Experiments(gPolicy); + yield experiments._run(); + }); + + // Trigger update & re-init, clock set to before any activation. + now = baseDate; + defineNow(gPolicy, now); + + experiments = new Experiments.Experiments(gPolicy); + yield experiments.updateManifest(); + let list = yield experiments.getExperiments(); + Assert.equal(list.length, 0, "Experiment list should be empty."); + + // Re-init, clock set for experiment 1 to start... + yield setDateAndRestartExperiments(startDates[0]); + list = yield experiments.getExperiments(); + Assert.equal(list.length, 1, "The first experiment should have started."); + + // ... init again, and set the clock so that the first experiment ends. + yield setDateAndRestartExperiments(endDates[0]); + + // The experiment just ended, it should still be in the cache, but marked + // as finished. + list = yield experiments.getExperiments(); + Assert.equal(list.length, 1, "Experiment list should have 1 entry."); + + experimentListData[1].active = false; + experimentListData[1].endDate = now.getTime(); + checkExperimentListsEqual(experimentListData.slice(1), list); + validateCache([...experiments._experiments.keys()], [EXPERIMENT1_ID, EXPERIMENT2_ID, EXPERIMENT3_ID]); + + // Start the second experiment. + yield setDateAndRestartExperiments(startDates[1]); + + // The experiments cache should contain the finished experiment and the + // one that's still running. + list = yield experiments.getExperiments(); + Assert.equal(list.length, 2, "Experiment list should have 2 entries."); + + experimentListData[0].active = true; + experimentListData[0].endDate = now.getTime() + 50 * MS_IN_ONE_DAY; + checkExperimentListsEqual(experimentListData, list); + + // Move the clock in the future, just 31 days after the start date of the second experiment, + // so that the cache for the first experiment expires and the second experiment is still running. + yield setDateAndRestartExperiments(futureDate(startDates[1], 31 * MS_IN_ONE_DAY)); + validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID, EXPERIMENT3_ID]); + + // Make sure that the expired experiment is not reported anymore. + let history = yield experiments.getExperiments(); + Assert.equal(history.length, 1, "Experiments older than 180 days must be removed from the cache."); + + // Test that we don't write expired experiments in the cache. + yield setDateAndRestartExperiments(now); + validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID, EXPERIMENT3_ID]); + + // The first experiment should be expired and not in the cache, it ended more than + // 180 days ago. We should see the one still running in the cache. + history = yield experiments.getExperiments(); + Assert.equal(history.length, 1, "Expired experiments must not be saved to cache."); + checkExperimentListsEqual(experimentListData.slice(0, 1), history); + + // Test that experiments that are cached locally but never ran are removed from cache + // when they are removed from the manifest (this is cached data, not really history). + gManifestObject["experiments"] = gManifestObject["experiments"].slice(1, 1); + yield experiments.updateManifest(); + validateCache([...experiments._experiments.keys()], [EXPERIMENT2_ID]); + + // Cleanup. + yield experiments._toggleExperimentsEnabled(false); + yield promiseRestartManager(); + yield removeCacheFile(); +}); |