diff --git a/browser/app/profile/extensions/testpilot@labs.mozilla.com/modules/remote-experiment-loader.js b/browser/app/profile/extensions/testpilot@labs.mozilla.com/modules/remote-experiment-loader.js index cdfdacf49d69..2824736e2eab 100644 --- a/browser/app/profile/extensions/testpilot@labs.mozilla.com/modules/remote-experiment-loader.js +++ b/browser/app/profile/extensions/testpilot@labs.mozilla.com/modules/remote-experiment-loader.js @@ -176,8 +176,6 @@ exports.RemoteExperimentLoader.prototype = { _init: function(logRepo, fileGetterFunction) { this._logger = logRepo.getLogger("TestPilot.Loader"); this._expLogger = logRepo.getLogger("TestPilot.RemoteCode"); - this._studyResults = []; - this._legacyStudies = []; let prefs = require("preferences-service"); this._baseUrl = prefs.get(BASE_URL_PREF, ""); if (fileGetterFunction != undefined) { @@ -187,7 +185,6 @@ exports.RemoteExperimentLoader.prototype = { } this._logger.trace("About to instantiate jar store."); this._jarStore = new JarStore(); - this._experimentFileNames = []; let self = this; this._logger.trace("About to instantiate cuddlefish loader."); this._refreshLoader(); @@ -217,6 +214,11 @@ exports.RemoteExperimentLoader.prototype = { [self._jarStore, Cuddlefish.parentLoader.fs]), console: this._expLogger }); + + // Clear all of our lists of studies/surveys/results when refreshing loader + this._studyResults = []; + this._legacyStudies = []; + this._experimentFileNames = []; }, getLocalizedStudyInfo: function(studiesIndex) { @@ -260,13 +262,35 @@ exports.RemoteExperimentLoader.prototype = { this._studyResults = data.results; this._legacyStudies = data.legacy; - /* Go through each record indicated in index.json for our locale; - * download the specified .jar file (replacing any version on disk) + + /* Look in the "maintain_experiments" section of the index file. Experiments + * listed here should be run IF we alrady have the code present on disk, but should + * not be downloaded if we don't already have them. + */ + if (data.maintain_experiments) { + this._logger.trace(data.maintain_experiments.length + " files to maintain.\n"); + for each (let studyFile in data.maintain_experiments) { + this._experimentFileNames.push(studyFile); + } + } + + /* Look in the "new_experiments" section of the index file for new jar files + * to download. Go through each record indicated in index.json, look up the + * .jar file specified for our locale, and download it (replacing any version + * on disk) */ let jarFiles = this.getLocalizedStudyInfo(data.new_experiments); let numFilesToDload = jarFiles.length; + this._logger.trace(numFilesToDload + " files to download.\n"); let self = this; + if (numFilesToDload == 0) { + this._logger.trace("Num files to download is 0, bailing\n"); + // nothing has changed --> callback false + callback(false); + return; + } + for each (let j in jarFiles) { let filename = j.jarfile; let hash = j.hash; @@ -310,6 +334,13 @@ exports.RemoteExperimentLoader.prototype = { this._studyResults = data.results; this._legacyStudies = data.legacy; + // Studies to be maintained: + if (data.maintain_experiments) { + for each (let studyFile in data.maintain_experiments) { + this._experimentFileNames.push(studyFile); + } + } + // Read names of experiment modules from index. let jarFiles = this.getLocalizedStudyInfo(data.new_experiments); for each (let j in jarFiles) { @@ -447,9 +478,11 @@ exports.RemoteExperimentLoader.prototype = { let url = resolveUrl(self._baseUrl, indexFileName); self._fileGetter(url, function onDone(data) { if (data) { + self._logger.trace("Index file updated on server.\n"); self._executeFreshIndexFile(data, callback); // cache index file contents so we can read them later if we can't get online. self._cacheIndexFile(data); + // executeFreshIndexFile will call the callback. } else { self._logger.info("Could not download index.json, using cached version."); let data = self._loadCachedIndexFile(); @@ -501,4 +534,3 @@ exports.RemoteExperimentLoader.prototype = { // TODO but once the study is expired, should delete the jar for it and // just load the LegacyStudy version. - diff --git a/browser/app/profile/extensions/testpilot@labs.mozilla.com/tests/test_data_store.js b/browser/app/profile/extensions/testpilot@labs.mozilla.com/tests/test_data_store.js index 7e1ca748b1ad..d8f17805066a 100644 --- a/browser/app/profile/extensions/testpilot@labs.mozilla.com/tests/test_data_store.js +++ b/browser/app/profile/extensions/testpilot@labs.mozilla.com/tests/test_data_store.js @@ -5,6 +5,15 @@ var Ci = Components.interfaces; var testsRun = 0; var testsPassed = 0; +function base64decode(data) { + // base64 encode/decode is built into window object, but we're in a module so we + // need to fetch a window reference first: + let wm = Cc["@mozilla.org/appshell/window-mediator;1"]. + getService(Ci.nsIWindowMediator); + let window = wm.getMostRecentWindow("navigator:browser"); + return window.atob(data); +} + function cheapAssertEqual(a, b, errorMsg) { testsRun += 1; if (a == b) { @@ -249,7 +258,7 @@ function testRemoteLoader() { } }; - let remoteLoader = new remoteLoaderModule.RemoteExperimentLoader(getFileFunc); + let remoteLoader = new remoteLoaderModule.RemoteExperimentLoader(stubLogger, getFileFunc); remoteLoader.checkForUpdates(function(success) { if (success) { @@ -291,6 +300,14 @@ function testRemotelyLoadTabsExperiment() { // (~/testpilot/website/testcases/tab-open-close/tabs_experiment.js) } +let stubLogger = { + getLogger: function() { return {trace: function(str) {dump("Trace: " + str +"\n");}, + warn: function(str) {dump("Warn: " + str +"\n");}, + info: function(str) {dump("Info: " + str +"\n");}, + debug: function(str) {dump("Debug: " + str +"\n");}};} +}; + + function testRemoteLoaderIndexCache() { var Cuddlefish = {}; Cu.import("resource://testpilot/modules/lib/cuddlefish.js", @@ -303,17 +320,17 @@ function testRemoteLoaderIndexCache() { callback(null); }; - let stubLogger = { - getLogger: function() { return {trace: function() {}, - warn: function() {}, - info: function() {}, - debug: function() {}};} - }; - let remoteLoader = new remoteLoaderModule.RemoteExperimentLoader(stubLogger, getFileFunc); let data = "Foo bar baz quux"; remoteLoader._cacheIndexFile(data); cheapAssertEqual(remoteLoader._loadCachedIndexFile(), data); + + // Clean up by killing the file so we don't break Test Pilot so bad + dump("Killing the mungled index file.\n"); + let indexFile = remoteLoader.cachedIndexNsiFile; + if (indexFile.exists()) { + indexFile.remove(false); + } } @@ -532,13 +549,17 @@ function testRecurringStudyStateChange() { function runAllTests() { - testTheDataStore(); - testFirefoxVersionCheck(); - testStringSanitizer(); + testsRun = 0; + testsPassed = 0; + + //testTheDataStore(); + //testFirefoxVersionCheck(); + //testStringSanitizer(); //testTheCuddlefishPreferencesFilesystem(); //testRemoteLoader(); - testRemoteLoaderIndexCache(); - testRecurringStudyStateChange(); + //testRemoteLoaderIndexCache(); + //testRecurringStudyStateChange(); + testKillSwitch(); dump("TESTING COMPLETE. " + testsPassed + " out of " + testsRun + " tests passed."); } @@ -551,4 +572,123 @@ function runAllTests() { // Test that every observer that is installed gets uninstalled. -// Test that the observer is writing to the data store correctly. \ No newline at end of file +// Test that the observer is writing to the data store correctly. + +function testKillSwitch() { + var Cuddlefish = {}; + Cu.import("resource://testpilot/modules/lib/cuddlefish.js", + Cuddlefish); + let cfl = new Cuddlefish.Loader({rootPaths: ["resource://testpilot/modules/", + "resource://testpilot/modules/lib/"]}); + let remoteLoaderModule = cfl.require("remote-experiment-loader"); + + var indexJson1 = '{"new_experiments": [{"default": {"name": "Foo Study", ' + + '"jarfile": "foo.jar", "studyfile": "foo.js",' + + '"hash": "19f32c805f93697e1b180a782bc7a8c7575f32138008d2617e9843a9bde14b38"}}]}'; + + var indexJson2 = '{"new_experiments": [], "maintain_experiments": ["foo.js"]}'; + var indexJson3 = '{"new_experiments": []}'; + + var indexJson = indexJson1; + + var theRemoteFile = "exports.foo = function(x, y) { return x * y; }"; + + let getFileFunc = function(url, callback) { + if (url.indexOf("index.json") > -1 || url.indexOf("index-dev.json") > -1) { + if (indexJson != "") { + callback(indexJson); + } else { + callback(null); + } + } else if (url.indexOf("foo.jar") > -1) { + // Binary file (got this by base64encoding foo.jar): + callback(base64decode( + 'UEsDBBQACAAIAFd2Pj4AAAAAAAAAAAAAAAAJAAQATUVUQS1JTkYv/soAAAMAUEsHCAAAAAACAAAAAAAA' + + 'AFBLAwQUAAgACABXdj4+AAAAAAAAAAAAAAAAFAAAAE1FVEEtSU5GL01BTklGRVNULk1G803My0xLLS7R' + + 'DUstKs7Mz7NSMNQz4OVyLkpNLElN0XWqBAmY6RnEGxkpaDgWFOSkKnjmJetp8nLxcgEAUEsHCPlAfs88' + + 'AAAAPAAAAFBLAwQUAAgACAA1dj4+AAAAAAAAAAAAAAAABgAAAGZvby5qc0utKMgvKinWS8vPV7BVSCvN' + + 'Sy7JzM/TqNBRqNRUqFYoSi0pLcpTqFDQUqi0Vqi1BgBQSwcIGwhXqzEAAAAvAAAAUEsBAhQAFAAIAAgA' + + 'V3Y+PgAAAAACAAAAAAAAAAkABAAAAAAAAAAAAAAAAAAAAE1FVEEtSU5GL/7KAABQSwECFAAUAAgACABX' + + 'dj4++UB+zzwAAAA8AAAAFAAAAAAAAAAAAAAAAAA9AAAATUVUQS1JTkYvTUFOSUZFU1QuTUZQSwECFAAU' + + 'AAgACAA1dj4+GwhXqzEAAAAvAAAABgAAAAAAAAAAAAAAAAC7AAAAZm9vLmpzUEsFBgAAAAADAAMAsQAA' + + 'ACABAAAAAA==')); + } else { + callback(null); + } + }; + + let remoteLoader = new remoteLoaderModule.RemoteExperimentLoader(stubLogger, getFileFunc); + + function clearIndexFileCache() { + // TODO this needs to go in a teardown + dump("Killing the mungled index file.\n"); + let indexFile = remoteLoader.cachedIndexNsiFile; + if (indexFile.exists()) { + indexFile.remove(false); + } + } + + dump("Preparing To Test Kill Switch!!!\n"); + remoteLoader.checkForUpdates(function(hasChanges) { + if (hasChanges) { + dump("Testing that foo study is loaded.\n"); + let exp = remoteLoader.getExperiments(); + let count = 0; + for (let i in exp) { + count += 1; + } + cheapAssertEqual(count, 1, "Supposed to find one file."); + let fooModule = exp["foo.js"]; + cheapAssertEqual(fooModule.foo(3, 4), 12, "Supposed to load the foo module.\n"); + } else { + cheapAssertFail("checkForUpdates is supposed to find changes."); + } + + /* Now we change the index file and call checkForUpdates again... test that: + * a. with indexJson2, the Foo Study continues to run. + * b. with indexJson3, the Foo Study stops immediately. + * */ + clearIndexFileCache(); + indexJson = indexJson2; + dump("Preparing to check for updates 2nd time.\n"); + remoteLoader.checkForUpdates( function(hasChanges) { + dump("in the callback for check for updates 2nd time.\n"); + if (hasChanges) { + dump("checkforUpdates 2nd time - has changes.\n"); + } else { + dump("checkforUpdates 2nd time - does not have changes.\n"); + } + + dump("Testing that foo study is loaded. (2nd time)\n"); + let exp = remoteLoader.getExperiments(); + let count = 0; + for (let i in exp) { + count += 1; + } + cheapAssertEqual(count, 1, "Supposed to find one file (2nd time)."); + let fooModule = exp["foo.js"]; + cheapAssertEqual(fooModule.foo(3, 4), 12, "Supposed to load the foo module (2).\n"); + + + clearIndexFileCache(); + indexJson = indexJson3; + remoteLoader.checkForUpdates( function(hasChanges) { + if (hasChanges) { + dump("checkForUpdates 3rd time - has changes.\n"); + } else { + dump("checkForUpdates 3rd time - has no changes.\n"); + } + + dump("Testing that foo study is NOT loaded. (3rd time)\n"); + let exp = remoteLoader.getExperiments(); + let count = 0; + for (let i in exp) { + count += 1; + } + cheapAssertEqual(count, 0, "File should be killed!\n"); + + clearIndexFileCache(); + }); + }); + }); +} \ No newline at end of file