| Index: chrome/content/tests/synchronizer.js |
| =================================================================== |
| rename from chrome/content/tests/test_synchronizer.html |
| rename to chrome/content/tests/synchronizer.js |
| --- a/chrome/content/tests/test_synchronizer.html |
| +++ b/chrome/content/tests/synchronizer.js |
| @@ -1,857 +1,745 @@ |
| -<!DOCTYPE HTML> |
| -<html> |
| -<head> |
| - <title>Subscription synchronizer tests</title> |
| +(function() |
| +{ |
| + let testRunner = null; |
| + let server = null; |
| + let randomResult = 0.5; |
| - <link rel="stylesheet" type="text/css" href="/content/tests/SimpleTest/test.css" /> |
| + const MILLIS_IN_SECOND = 1000; |
| + const MILLIS_IN_MINUTE = 60 * MILLIS_IN_SECOND; |
| + const MILLIS_IN_HOUR = 60 * MILLIS_IN_MINUTE; |
| + const MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR; |
| - <script type="text/javascript" src="/content/MochiKit/MochiKit.js"></script> |
| - <script type="application/x-javascript;version=1.7" src="../httpd.js"></script> |
| - <script type="text/javascript; version=1.7" src="/content/tests/SimpleTest/specialpowersAPI.js"></script> |
| - <script type="text/javascript; version=1.7" src="/content/tests/SimpleTest/SpecialPowersObserverAPI.js"></script> |
| - <script type="text/javascript; version=1.7" src="/content/tests/SimpleTest/ChromePowers.js"></script> |
| - <script type="text/javascript" src="/content/tests/SimpleTest/SimpleTest.js"></script> |
| + module("Synchronizer", { |
| + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), |
| - <script type="application/x-javascript;version=1.7" src="common.js"></script> |
| + setup: function() |
| + { |
| + testRunner = this; |
| -</head> |
| -<body> |
| - <p id="display"></p> |
| - <div id="content" style="display: none"> |
| + prepareFilterComponents.call(this); |
| + preparePrefs.call(this); |
| - </div> |
| + let SynchronizerGlobal = Cu.getGlobalForObject(Synchronizer); |
| + let SynchronizerModule = getModuleGlobal("synchronizer"); |
| + let DownloaderGlobal = Cu.getGlobalForObject(SynchronizerModule.downloader); |
| - <pre id="test"> |
| - <script type="application/x-javascript;version=1.7"> |
| - let {Synchronizer} = require("synchronizer"); |
| - let SynchronizerGlobal = Cu.getGlobalForObject(Synchronizer); |
| - prepareFilterComponents(); |
| - preparePrefs(); |
| + server = new nsHttpServer(); |
| + server.start(1234); |
| - let currentTime = 20000 * 24 * 60 * 60 * 1000; |
| - let startTime = 0; |
| - let scheduledTasks = []; |
| + let currentTime = 100000 * MILLIS_IN_HOUR; |
| + let startTime = currentTime; |
| + let scheduledTasks = []; |
| - let oldRandom = SynchronizerGlobal.Math.random; |
| - let oldNow = SynchronizerGlobal.Date.now; |
| - SynchronizerGlobal.Date.now = function() |
| - { |
| - return currentTime; |
| - }; |
| - Date.now = SynchronizerGlobal.Date.now; // Override for httpd Date header |
| + // Replace Date.now() function |
| + this._origNow = SynchronizerGlobal.Date.now; |
| + SynchronizerGlobal.Date.now = DownloaderGlobal.Date.now = function() currentTime; |
| - let outstandingRequests = 0; |
| + // Replace Math.random() function |
| + this._origRandom = DownloaderGlobal.Math.random; |
| + DownloaderGlobal.Math.random = function() randomResult; |
| - function runScheduledTasks(maxHours, noExecution) |
| - { |
| - startTime = currentTime; |
| - let maxTime = maxHours * 60 * 60 * 1000; |
| - let endTime = currentTime + maxTime; |
| - while (true) |
| - { |
| - let nextTask = null; |
| - for each (let task in scheduledTasks) |
| - { |
| - if (!nextTask || nextTask.nextExecution > task.nextExecution) |
| - nextTask = task; |
| - } |
| - if (!nextTask || nextTask.nextExecution > endTime) |
| - break; |
| - |
| - currentTime = nextTask.nextExecution; |
| - if (!noExecution) |
| - nextTask.handler(); |
| - |
| - // Let all asynchronous actions finish |
| - let thread = Services.tm.currentThread; |
| - let loopStartTime = Date.now(); |
| - |
| - while (outstandingRequests > 0 || thread.hasPendingEvents()) |
| - { |
| - thread.processNextEvent(true); |
| - |
| - if (Date.now() - loopStartTime > 5000) |
| - { |
| - ok(false, "Synchronizer stuck downloading subscriptions"); |
| - return; |
| - } |
| - } |
| - |
| - if (nextTask.type == Components.interfaces.nsITimer.TYPE_ONE_SHOT) |
| - scheduledTasks = scheduledTasks.filter(function(task) task != nextTask); |
| - else |
| - nextTask.nextExecution = currentTime + nextTask.delay; |
| - } |
| - |
| - currentTime = endTime; |
| - } |
| - |
| - Prefs.subscriptions_fallbackerrors = 7; |
| - Prefs.subscriptions_fallbackurl = "http://127.0.0.1:1234/fallback?%SUBSCRIPTION%&%URL%&%CHANNELSTATUS%&%RESPONSESTATUS%"; |
| - |
| - { |
| - let timer = {__proto__: SynchronizerGlobal.timer}; |
| + // Replace global timer variable |
| + let timer = {__proto__: SynchronizerModule.downloader._timer, delay: 0.1 * MILLIS_IN_HOUR}; |
| let callback = timer.callback; |
| timer.handler = function() { callback.notify(timer); }; |
| timer.nextExecution = currentTime + timer.delay; |
| + scheduledTasks.push(timer); |
| + SynchronizerModule.downloader._timer.cancel(); |
| + SynchronizerModule.downloader._timer = timer; |
| - scheduledTasks.push(timer); |
| + // Register observer to track outstanding requests |
| + this._outstandingRequests = 0; |
| + Services.obs.addObserver(this, "http-on-modify-request", true); |
| + Services.obs.addObserver(this, "http-on-examine-response", true); |
| - SynchronizerGlobal.timer.cancel(); |
| - SynchronizerGlobal.timer = timer; |
| + this.runScheduledTasks = function(maxHours, initial, skip) |
| + { |
| + if (typeof maxHours != "number") |
| + throw new Error("Numerical parameter expected"); |
| + if (typeof initial != "number") |
| + initial = 0; |
| + if (typeof skip != "number") |
| + skip = 0; |
| + |
| + startTime = currentTime; |
| + if (initial >= 0) |
| + { |
| + this._runScheduledTasks(initial); |
| + maxHours -= initial; |
| + } |
| + if (skip) |
| + { |
| + this._skipTasks(skip); |
| + maxHours -= skip; |
| + } |
| + this._runScheduledTasks(maxHours); |
| + } |
| + |
| + this._runScheduledTasks = function(maxHours) |
| + { |
| + let endTime = currentTime + maxHours * MILLIS_IN_HOUR; |
| + while (true) |
| + { |
| + let nextTask = null; |
| + for each (let task in scheduledTasks) |
| + { |
| + if (!nextTask || nextTask.nextExecution > task.nextExecution) |
| + nextTask = task; |
| + } |
| + if (!nextTask || nextTask.nextExecution > endTime) |
| + break; |
| + |
| + currentTime = nextTask.nextExecution; |
| + nextTask.handler(); |
| + |
| + // Let all asynchronous actions finish |
| + let thread = Services.tm.currentThread; |
| + let loopStartTime = Date.now(); |
| + |
| + while (this._outstandingRequests > 0 || thread.hasPendingEvents()) |
| + { |
| + thread.processNextEvent(true); |
| + |
| + if (Date.now() - loopStartTime > 5000) |
| + throw new Error("Synchronizer stuck downloading subscriptions"); |
| + } |
| + |
| + if (nextTask.type == Components.interfaces.nsITimer.TYPE_ONE_SHOT) |
| + scheduledTasks = scheduledTasks.filter(function(task) task != nextTask); |
| + else |
| + nextTask.nextExecution = currentTime + nextTask.delay; |
| + } |
| + |
| + currentTime = endTime; |
| + } |
| + |
| + this._skipTasks = function(hours) |
| + { |
| + let newTasks = []; |
| + currentTime += hours * MILLIS_IN_HOUR; |
| + for each (let task in scheduledTasks) |
| + { |
| + if (task.nextExecution >= currentTime) |
| + newTasks.push(task); |
| + else if (task.type != Components.interfaces.nsITimer.TYPE_ONE_SHOT) |
| + { |
| + task.nextExecution = currentTime; |
| + newTasks.push(task); |
| + } |
| + } |
| + scheduledTasks = newTasks; |
| + } |
| + |
| + this.getTimeOffset = function() (currentTime - startTime) / MILLIS_IN_HOUR; |
| + |
| + this.__defineGetter__("currentTime", function() currentTime); |
| + }, |
| + |
| + observe: function(subject, topic, data) |
| + { |
| + let orig = this._outstandingRequests; |
| + if (topic == "http-on-modify-request") |
| + this._outstandingRequests++; |
| + else if (topic == "http-on-examine-response") |
| + this._outstandingRequests--; |
| + }, |
| + |
| + teardown: function() |
| + { |
| + restoreFilterComponents.call(this); |
| + restorePrefs.call(this); |
| + |
| + stop(); |
| + server.stop(function() |
| + { |
| + server = null; |
| + start(); |
| + }); |
| + |
| + if (this._origNow) |
| + { |
| + let SynchronizerGlobal = Cu.getGlobalForObject(Synchronizer); |
| + let SynchronizerModule = getModuleGlobal("synchronizer"); |
| + let DownloaderGlobal = Cu.getGlobalForObject(SynchronizerModule.downloader); |
| + SynchronizerGlobal.Date.now = DownloaderGlobal.Date.now = this._origNow; |
| + delete this._origNow; |
| + } |
| + |
| + if (this._origRandom) |
| + { |
| + let SynchronizerModule = getModuleGlobal("synchronizer"); |
| + let DownloaderGlobal = Cu.getGlobalForObject(SynchronizerModule.downloader); |
| + DownloaderGlobal.Math.random = this._origRandom; |
| + delete this._origRandom; |
| + } |
| + |
| + Synchronizer.init(); |
| + } |
| + }); |
| + |
| + function resetSubscription(subscription) |
| + { |
| + FilterStorage.updateSubscriptionFilters(subscription, []); |
| + subscription.lastCheck = subscription.lastDownload = |
| + subscription.version = subscription.lastSuccess = |
| + subscription.expires = subscription.softExpiration = 0; |
| + subscription.title = ""; |
| + subscription.homepage = null; |
| + subscription.errors = 0; |
| + subscription.downloadStatus = null; |
| + subscription.requiredVersion = null; |
| + } |
| + |
| + test("Downloads of one subscription", function() |
| + { |
| + // Always use average download interval |
| + randomResult = 0.5; |
| + |
| + let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + let requests = []; |
| + function handler(metadata, response) |
| + { |
| + requests.push([testRunner.getTimeOffset(), metadata.method, metadata.path]); |
| + |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| + |
| + let result = "[Adblock]\n! ExPiREs: 1day\nfoo\nbar"; |
| + response.bodyOutputStream.write(result, result.length); |
| } |
| - // Track requests initiated by Synchronizer object by hooking its |
| - // XMLHttpRequest constructor. |
| - let oldXMLHttp = SynchronizerGlobal.XMLHttpRequest; |
| - SynchronizerGlobal.XMLHttpRequest = function() |
| + server.registerPathHandler("/subscription", handler); |
| + |
| + testRunner.runScheduledTasks(50); |
| + deepEqual(requests, [ |
| + [0.1, "GET", "/subscription"], |
| + [24.1, "GET", "/subscription"], |
| + [48.1, "GET", "/subscription"], |
| + ], "Requests after 50 hours"); |
| + }); |
| + |
| + test("Downloads of two subscriptions", function() |
| + { |
| + // Always use average download interval |
| + randomResult = 0.5; |
| + |
| + let subscription1 = Subscription.fromURL("http://127.0.0.1:1234/subscription1"); |
| + FilterStorage.addSubscription(subscription1); |
| + |
| + let subscription2 = Subscription.fromURL("http://127.0.0.1:1234/subscription2"); |
| + subscription2.expires = |
| + subscription2.softExpiration = |
| + (testRunner.currentTime + 2 * MILLIS_IN_HOUR) / MILLIS_IN_SECOND; |
| + FilterStorage.addSubscription(subscription2); |
| + |
| + let requests = []; |
| + function handler(metadata, response) |
| { |
| - let inner = new oldXMLHttp(); |
| + requests.push([testRunner.getTimeOffset(), metadata.method, metadata.path]); |
| - return { |
| - __proto__: inner, |
| - send: function() |
| - { |
| - outstandingRequests++; |
| - function finished() |
| - { |
| - outstandingRequests--; |
| - } |
| - inner.addEventListener("load", finished, false); |
| - inner.addEventListener("error", finished, false); |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| - inner.send.apply(inner, arguments); |
| - } |
| + let result = "[Adblock]\n! ExPiREs: 1day\nfoo\nbar"; |
| + response.bodyOutputStream.write(result, result.length); |
| + } |
| + |
| + server.registerPathHandler("/subscription1", handler); |
| + server.registerPathHandler("/subscription2", handler); |
| + |
| + testRunner.runScheduledTasks(55); |
| + deepEqual(requests, [ |
| + [0.1, "GET", "/subscription1"], |
| + [2.1, "GET", "/subscription2"], |
| + [24.1, "GET", "/subscription1"], |
| + [26.1, "GET", "/subscription2"], |
| + [48.1, "GET", "/subscription1"], |
| + [50.1, "GET", "/subscription2"], |
| + ], "Requests after 55 hours"); |
| + }); |
| + |
| + test("Download result, various subscription headers", function() |
| + { |
| + // Always use average download interval |
| + randomResult = 0.5; |
| + |
| + let test; |
| + let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + function handler(metadata, response) |
| + { |
| + response.setStatusLine("1.1", "200", "OK"); |
| + |
| + // Wrong content type shouldn't matter |
| + response.setHeader("Content-Type", "text/xml"); |
| + |
| + let result = test.header + "\n!Expires: 8 hours\nfoo\n!bar\n\n@@bas\n#bam"; |
| + response.bodyOutputStream.write(result, result.length); |
| + } |
| + server.registerPathHandler("/subscription", handler); |
| + |
| + let tests = [ |
| + {header: "[Adblock]", downloadStatus: "synchronize_ok", requiredVersion: null}, |
| + {header: "[Adblock Plus]", downloadStatus: "synchronize_ok", requiredVersion: null}, |
| + {header: "(something)[Adblock]", downloadStatus: "synchronize_ok", requiredVersion: null}, |
| + {header: "[Adblock Plus 0.0.1]", downloadStatus: "synchronize_ok", requiredVersion: "0.0.1"}, |
| + {header: "[Adblock Plus 99.9]", downloadStatus: "synchronize_ok", requiredVersion: "99.9"}, |
| + {header: "[Foo]", downloadStatus: "synchronize_invalid_data", requiredVersion: null} |
| + ]; |
| + for each (test in tests) |
| + { |
| + resetSubscription(subscription) |
| + testRunner.runScheduledTasks(2); |
| + |
| + equal(subscription.downloadStatus, test.downloadStatus, "Download status for " + test.header) |
| + equal(subscription.requiredVersion, test.requiredVersion, "Required version for " + test.header) |
| + |
| + if (test.downloadStatus == "synchronize_ok") |
| + { |
| + deepEqual(subscription.filters, [ |
| + Filter.fromText("foo"), |
| + Filter.fromText("!bar"), |
| + Filter.fromText("@@bas"), |
| + Filter.fromText("#bam"), |
| + ], "Resulting subscription filters for " + test.header); |
| + } |
| + else |
| + { |
| + deepEqual(subscription.filters, [ |
| + ], "Resulting subscription filters for " + test.header); |
| } |
| } |
| + }) |
| - // Make sure to restore everything when this document unloads |
| - window.addEventListener("unload", function() |
| + test("Automatic updates disabled", function() |
| + { |
| + Prefs.subscriptions_autoupdate = false; |
| + |
| + let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + let requests = 0; |
| + function handler(metadata, response) |
| { |
| - SynchronizerGlobal.Date.now = oldNow; |
| - SynchronizerGlobal.XMLHttpRequest = oldXMLHttp; |
| - SynchronizerGlobal.Math.random = oldRandom; |
| - Synchronizer.startup(); |
| - }, false); |
| + requests++; |
| + throw new Error("Unexpected request"); |
| + } |
| - let server = new nsHttpServer(); |
| + server.registerPathHandler("/subscription", handler); |
| + testRunner.runScheduledTasks(50); |
| + equal(requests, 0, "Request count"); |
| + }); |
| + |
| + test("Expiration time", function() |
| + { |
| + let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + let test; |
| let requests = []; |
| + function handler(metadata, response) |
| + { |
| + requests.push(testRunner.getTimeOffset()); |
| - let subscription1 = Subscription.fromURL("http://127.0.0.1:1234/subscription1"); |
| - let subscription2 = Subscription.fromURL("http://127.0.0.1:1234/subscription2"); |
| - let subscription3 = Subscription.fromURL("http://127.0.0.1:1234/subscription3"); |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| - let subscriptionStatus = [200, "OK"]; |
| - let subscriptionExtraHeaders = null; |
| - let subscriptionBody = "[Adblock]\nfoo\nbar"; |
| - function getSubscription(metadata, response) |
| + let result = "[Adblock]\nfoo\n!Expires: " + test.expiration + "\nbar"; |
| + response.bodyOutputStream.write(result, result.length); |
| + } |
| + server.registerPathHandler("/subscription", handler); |
| + |
| + let tests = [ |
| + { |
| + expiration: "default", |
| + randomResult: 0.5, |
| + requests: [0.1, 5 * 24 + 0.1] |
| + }, |
| + { |
| + expiration: "1 hours", // Minimal expiration interval |
| + randomResult: 0.5, |
| + requests: [0.1, 1.1, 2.1, 3.1] |
| + }, |
| + { |
| + expiration: "26 hours", |
| + randomResult: 0.5, |
| + requests: [0.1, 26.1] |
| + }, |
| + { |
| + expiration: "2 days", |
| + randomResult: 0.5, |
| + requests: [0.1, 48.1] |
| + }, |
| + { |
| + expiration: "20 days", // Too large, will be corrected |
| + randomResult: 0.5, |
| + requests: [0.1, 14 * 24 + 0.1] |
| + }, |
| + { |
| + expiration: "35 hours", |
| + randomResult: 0, // Changes interval by factor 0.8 |
| + requests: [0.1, 28.1] |
| + }, |
| + { |
| + expiration: "35 hours", |
| + randomResult: 1, // Changes interval by factor 1.2 |
| + requests: [0.1, 42.1] |
| + }, |
| + { |
| + expiration: "35 hours", |
| + randomResult: 0.25, // Changes interval by factor 0.9 |
| + requests: [0.1, 32.1] |
| + }, |
| + { |
| + expiration: "40 hours", |
| + randomResult: 0.5, |
| + skipAfter: 5.1, |
| + skip: 10, // Short break should not increase soft expiration |
| + requests: [0.1, 40.1] |
| + }, |
| + { |
| + expiration: "40 hours", |
| + randomResult: 0.5, |
| + skipAfter: 5.1, |
| + skip: 30, // Long break should increase soft expiration |
| + requests: [0.1, 70.1] |
| + }, |
| + { |
| + expiration: "40 hours", |
| + randomResult: 0.5, |
| + skipAfter: 5.1, |
| + skip: 80, // Hitting hard expiration, immediate download |
| + requests: [0.1, 85.1] |
| + } |
| + ] |
| + |
| + for each (test in tests) |
| { |
| - requests.push((currentTime - startTime) / 3600000 + ": " + metadata.method + " " + metadata.path); |
| + requests = []; |
| + randomResult = test.randomResult; |
| + resetSubscription(subscription); |
| - response.setStatusLine("1.1", subscriptionStatus[0], subscriptionStatus[1]); |
| - // Return wrong MIME type, client should be able to handle it |
| - response.setHeader("Content-Type", "text/xml"); |
| + let maxHours = Math.round(Math.max.apply(null, test.requests)) + 1; |
| + testRunner.runScheduledTasks(maxHours, test.skipAfter, test.skip); |
| - if (subscriptionExtraHeaders) |
| - { |
| - for each (let [header, value] in subscriptionExtraHeaders(metadata)) |
| - response.setHeader(header, value); |
| - } |
| + let randomAddendum = (randomResult == 0.5 ? "" : " with Math.random() returning " + randomResult); |
| + let skipAddendum = (typeof test.skip != "number" ? "" : " skipping " + test.skip + " hours after " + test.skipAfter + " hours"); |
| + deepEqual(requests, test.requests, "Requests for \"" + test.expiration + "\"" + randomAddendum + skipAddendum); |
| + } |
| + }); |
| + |
| + test("Checksum verification", function() |
| + { |
| + // Always use average download interval |
| + randomResult = 0.5; |
| + |
| + let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + let testName, subscriptionBody, expectedResult; |
| + let tests = [ |
| + ["Correct checksum", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\nfoo\nbar\n", true], |
| + ["Wrong checksum", "[Adblock]\n! Checksum: wrongggny6Fn24b7JHsq/A\nfoo\nbar\n", false], |
| + ["Empty lines ignored", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\n\nfoo\n\nbar\n\n", true], |
| + ["CR LF line breaks treated like LR", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\nfoo\r\nbar\r\n", true], |
| + ["CR line breaks treated like LR", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\nfoo\rbar\r", true], |
| + ["Trailing line break not ignored", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\nfoo\nbar", false], |
| + ["Line breaks between lines not ignored", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\nfoobar", false], |
| + ["Lines with spaces not ignored", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\n \nfoo\n\nbar\n", false], |
| + ["Extra content in checksum line is part of the checksum", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A foobar\nfoo\nbar\n", false], |
| + ["= symbols after checksum are ignored", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A===\nfoo\nbar\n", true], |
| + ["Header line is part of the checksum", "[Adblock Plus]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\nfoo\nbar\n", false], |
| + ["Special comments are part of the checksum", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\n! Expires: 1\nfoo\nbar\n", false], |
| + ]; |
| + |
| + function handler(metadata, response) |
| + { |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| response.bodyOutputStream.write(subscriptionBody, subscriptionBody.length); |
| } |
| + server.registerPathHandler("/subscription", handler); |
| - let redirectPermanent = null; |
| - let redirectURL = null; |
| - let redirectExtraHeaders = null; |
| - function redirectHandler(metadata, response) |
| + for each ([testName, subscriptionBody, expectedResult] in tests) |
| { |
| - response.setStatusLine("1.1", redirectPermanent ? 301 : 302, redirectPermanent ? "Moved Permanently" : "Moved Temporarily"); |
| - response.setHeader("Location", redirectURL); |
| + resetSubscription(subscription); |
| + testRunner.runScheduledTasks(2); |
| + equal(subscription.downloadStatus, expectedResult ? "synchronize_ok" : "synchronize_checksum_mismatch", testName); |
| + } |
| + }); |
| - if (redirectExtraHeaders) |
| - { |
| - for each (let [header, value] in redirectExtraHeaders(metadata)) |
| - response.setHeader(header, value); |
| - } |
| + test("Special comments", function() |
| + { |
| + // Always use average download interval |
| + randomResult = 0.5; |
| + |
| + let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + let comment, check; |
| + let tests = [ |
| + ["! Homepage: http://example.com/", function() equal(subscription.homepage, "http://example.com/", "Valid homepage comment")], |
| + ["! Homepage: ssh://example.com/", function() equal(subscription.homepage, null, "Invalid homepage comment")], |
| + ["! Title: foo", function() |
| + { |
| + equal(subscription.title, "foo", "Title comment"); |
| + equal(subscription.fixedTitle, true, "Fixed title"); |
| + }], |
| + ["! Version: 1234", function() equal(subscription.version, 1234, "Version comment")] |
| + ]; |
| + |
| + function handler(metadata, response) |
| + { |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| + |
| + let result = "[Adblock]\n" + comment + "\nfoo\nbar"; |
| + response.bodyOutputStream.write(result, result.length); |
| } |
| - function commentRedirectHandler(metadata, response) |
| + server.registerPathHandler("/subscription", handler); |
| + |
| + for each([comment, check] in tests) |
| { |
| - getSubscription(metadata, response); |
| + resetSubscription(subscription); |
| + testRunner.runScheduledTasks(2); |
| + check(); |
| + deepEqual(subscription.filters, [Filter.fromText("foo"), Filter.fromText("bar")], "Special comment not added to filters"); |
| + } |
| + }); |
| - if (redirectExtraHeaders) |
| - { |
| - for each (let [header, value] in redirectExtraHeaders(metadata)) |
| - response.setHeader(header, value); |
| - } |
| + test("Redirects", function() |
| + { |
| + // Always use average download interval |
| + randomResult = 0.5; |
| - var comment = "\n! Redirect: " + redirectURL; |
| - response.bodyOutputStream.write(comment, comment.length); |
| + let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + function redirect_handler(metadata, response) |
| + { |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| + |
| + let result = "[Adblock]\nfoo\n!Redirect: http://127.0.0.1:1234/redirected\nbar"; |
| + response.bodyOutputStream.write(result, result.length); |
| } |
| + server.registerPathHandler("/subscription", redirect_handler); |
| - var fallbackResult = ""; |
| - function fallbackHandler(metadata, response) |
| + testRunner.runScheduledTasks(30); |
| + equal(FilterStorage.subscriptions[0], subscription, "Invalid redirect ignored"); |
| + equal(subscription.downloadStatus, "synchronize_connection_error", "Connection error recorded"); |
| + equal(subscription.errors, 2, "Number of download errors"); |
| + |
| + let requests = []; |
| + function handler(metadata, response) |
| { |
| - requests.push((currentTime - startTime) / 3600000 + ": " + metadata.method + " " + metadata.path + " " + decodeURIComponent(metadata.queryString)); |
| + requests.push(testRunner.getTimeOffset()); |
| - response.setStatusLine("1.1", 200, "OK"); |
| - // Return wrong MIME type, client should be able to handle it |
| - response.setHeader("Content-Type", "application/x-foo-bar"); |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| - if (subscriptionExtraHeaders) |
| - { |
| - for each (let [header, value] in subscriptionExtraHeaders()) |
| - response.setHeader(header, value); |
| - } |
| + let result = "[Adblock]\nfoo\n! Expires: 8 hours\nbar"; |
| + response.bodyOutputStream.write(result, result.length); |
| + } |
| + server.registerPathHandler("/redirected", handler); |
| - response.bodyOutputStream.write(fallbackResult, fallbackResult.length); |
| + resetSubscription(subscription); |
| + testRunner.runScheduledTasks(15); |
| + equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/redirected", "Redirect followed"); |
| + deepEqual(requests, [0.1, 8.1], "Resulting requests"); |
| + |
| + server.registerPathHandler("/redirected", function(metadata, response) |
| + { |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| + |
| + let result = "[Adblock]\nfoo\n!Redirect: http://127.0.0.1:1234/subscription\nbar"; |
| + response.bodyOutputStream.write(result, result.length); |
| + }) |
| + |
| + let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + resetSubscription(subscription); |
| + FilterStorage.removeSubscription(FilterStorage.subscriptions[0]); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + testRunner.runScheduledTasks(2); |
| + equal(FilterStorage.subscriptions[0], subscription, "Redirect not followed on redirect loop"); |
| + equal(subscription.downloadStatus, "synchronize_connection_error", "Download status after redirect loop"); |
| + }); |
| + |
| + test("Fallback", function() |
| + { |
| + // Always use average download interval |
| + randomResult = 0.5; |
| + |
| + Prefs.subscriptions_fallbackerrors = 3; |
| + Prefs.subscriptions_fallbackurl = "http://127.0.0.1:1234/fallback?%SUBSCRIPTION%&%CHANNELSTATUS%&%RESPONSESTATUS%"; |
| + |
| + let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + // No valid response from fallback |
| + |
| + let requests = []; |
| + function handler(metadata, response) |
| + { |
| + requests.push(testRunner.getTimeOffset()); |
| + |
| + response.setStatusLine("1.1", "404", "Not found"); |
| } |
| + server.registerPathHandler("/subscription", handler); |
| - function compareRequests(test, expected) |
| + testRunner.runScheduledTasks(100); |
| + deepEqual(requests, [0.1, 24.1, 48.1, 72.1, 96.1], "Continue trying if the fallback doesn't respond"); |
| + |
| + // Fallback giving "Gone" response |
| + |
| + resetSubscription(subscription); |
| + requests = []; |
| + fallbackParams = null; |
| + server.registerPathHandler("/fallback", function(metadata, response) |
| { |
| - is(requests.join("\n"), expected.join("\n"), test); |
| - requests = []; |
| + response.setStatusLine("1.1", "200", "OK"); |
| + fallbackParams = decodeURIComponent(metadata.queryString); |
| + |
| + let result = "410 Gone"; |
| + response.bodyOutputStream.write(result, result.length); |
| + }); |
| + |
| + testRunner.runScheduledTasks(100); |
| + deepEqual(requests, [0.1, 24.1, 48.1], "Stop trying if the fallback responds with Gone"); |
| + equal(fallbackParams, "http://127.0.0.1:1234/subscription&0&404", "Fallback arguments"); |
| + |
| + // Fallback redirecting to a missing file |
| + |
| + subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + resetSubscription(subscription); |
| + FilterStorage.removeSubscription(FilterStorage.subscriptions[0]); |
| + FilterStorage.addSubscription(subscription); |
| + requests = []; |
| + |
| + server.registerPathHandler("/fallback", function(metadata, response) |
| + { |
| + response.setStatusLine("1.1", "200", "OK"); |
| + |
| + let result = "301 http://127.0.0.1:1234/redirected"; |
| + response.bodyOutputStream.write(result, result.length); |
| + }); |
| + testRunner.runScheduledTasks(100); |
| + equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/subscription", "Ignore invalid redirect from fallback"); |
| + deepEqual(requests, [0.1, 24.1, 48.1, 72.1, 96.1], "Requests not affected by invalid redirect"); |
| + |
| + // Fallback redirecting to an existing file |
| + |
| + resetSubscription(subscription); |
| + requests = []; |
| + let redirectedRequests = []; |
| + server.registerPathHandler("/redirected", function(metadata, response) |
| + { |
| + redirectedRequests.push(testRunner.getTimeOffset()); |
| + |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| + |
| + let result = "[Adblock]\n!Expires: 1day\nfoo\nbar"; |
| + response.bodyOutputStream.write(result, result.length); |
| + }); |
| + |
| + testRunner.runScheduledTasks(100); |
| + equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/redirected", "Valid redirect from fallback is followed"); |
| + deepEqual(requests, [0.1, 24.1, 48.1], "Stop polling original URL after a valid redirect from fallback"); |
| + deepEqual(redirectedRequests, [48.1, 72.1, 96.1], "Request new URL after a valid redirect from fallback"); |
| + |
| + // Checksum mismatch |
| + |
| + function handler2(metadata, response) |
| + { |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| + |
| + let result = "[Adblock]\n! Checksum: wrong\nfoo\nbar"; |
| + response.bodyOutputStream.write(result, result.length); |
| } |
| + server.registerPathHandler("/subscription", handler2); |
| - function compareFilters(test, expected, expectedStatus, expectedVersion) |
| + subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + resetSubscription(subscription); |
| + FilterStorage.removeSubscription(FilterStorage.subscriptions[0]); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + testRunner.runScheduledTasks(100); |
| + equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/redirected", "Wrong checksum produces fallback request"); |
| + |
| + // Redirect loop |
| + |
| + server.registerPathHandler("/subscription", function(metadata, response) |
| { |
| - let result = subscription1.filters.map(function(filter) filter.text).join("\n"); |
| - is(result, expected, test); |
| - is(subscription1.downloadStatus, expectedStatus, "Subscription status after previous test"); |
| - is(subscription1.requiredVersion, expectedVersion, "Required version after previous test"); |
| - requests = []; |
| - } |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| - function resetSubscriptions() |
| + let result = "[Adblock]\n! Redirect: http://127.0.0.1:1234/subscription2"; |
| + response.bodyOutputStream.write(result, result.length); |
| + }); |
| + server.registerPathHandler("/subscription2", function(metadata, response) |
| { |
| - FilterStorage.removeSubscription(subscription1); |
| - FilterStorage.removeSubscription(subscription2); |
| - FilterStorage.removeSubscription(subscription3); |
| - FilterStorage.addSubscription(subscription1); |
| - FilterStorage.addSubscription(subscription2); |
| - subscription2.autoDownload = false; |
| - } |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| - function compareSubscriptions(test, expectedSubscriptions) |
| + let result = "[Adblock]\n! Redirect: http://127.0.0.1:1234/subscription"; |
| + response.bodyOutputStream.write(result, result.length); |
| + }); |
| + |
| + subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + resetSubscription(subscription); |
| + FilterStorage.removeSubscription(FilterStorage.subscriptions[0]); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + testRunner.runScheduledTasks(100); |
| + equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/redirected", "Fallback can still redirect even after a redirect loop"); |
| + }); |
| + |
| + test("State fields", function() |
| + { |
| + // Always use average download interval |
| + randomResult = 0.5; |
| + |
| + let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); |
| + FilterStorage.addSubscription(subscription); |
| + |
| + server.registerPathHandler("/subscription", function successHandler(metadata, response) |
| { |
| - let result = FilterStorage.subscriptions.map(function(subscription) subscription.url).join("\n"); |
| - let expected = expectedSubscriptions.map(function(subscription) subscription.url).join("\n"); |
| - is(result, expected, test); |
| - requests = []; |
| - resetSubscriptions(); |
| - } |
| + response.setStatusLine("1.1", "200", "OK"); |
| + response.setHeader("Content-Type", "text/plain"); |
| - function runTests() |
| + let result = "[Adblock]\n! Expires: 2 hours\nfoo\nbar"; |
| + response.bodyOutputStream.write(result, result.length); |
| + }); |
| + |
| + let startTime = testRunner.currentTime; |
| + testRunner.runScheduledTasks(2); |
| + |
| + equal(subscription.downloadStatus, "synchronize_ok", "downloadStatus after successful download"); |
| + equal(subscription.lastDownload * MILLIS_IN_SECOND, startTime + 0.1 * MILLIS_IN_HOUR, "lastDownload after successful download"); |
| + equal(subscription.lastSuccess * MILLIS_IN_SECOND, startTime + 0.1 * MILLIS_IN_HOUR, "lastSuccess after successful download"); |
| + equal(subscription.lastCheck * MILLIS_IN_SECOND, startTime + 1.1 * MILLIS_IN_HOUR, "lastCheck after successful download"); |
| + equal(subscription.errors, 0, "errors after successful download"); |
| + |
| + server.registerPathHandler("/subscription", function errorHandler(metadata, response) |
| { |
| - is(typeof Synchronizer, "object", "typeof Synchronizer"); |
| + response.setStatusLine("1.1", "404", "Not Found"); |
| + }); |
| - server.registerPathHandler("/subscription1", getSubscription); |
| - server.registerPathHandler("/subscription2", getSubscription); |
| - server.registerPathHandler("/subscription3", getSubscription); |
| - server.registerPathHandler("/fallback", fallbackHandler); |
| + testRunner.runScheduledTasks(2); |
| - FilterStorage.addSubscription(subscription1); |
| - |
| - subscription2.autoDownload = false; |
| - FilterStorage.addSubscription(subscription2); |
| - |
| - // |
| - // General subscription download testing |
| - // |
| - |
| - SynchronizerGlobal.Math.random = function() 0.5; |
| - |
| - runScheduledTasks(50); |
| - compareRequests("Downloads of one subscription (50 hours)", [ |
| - "0.1: GET /subscription1", |
| - "24.1: GET /subscription1", |
| - "48.1: GET /subscription1" |
| - ]); |
| - |
| - subscription2.autoDownload = true; |
| - runScheduledTasks(70); |
| - compareRequests("Downloads with second subscription switched on (48 hours)", [ |
| - "0.1: GET /subscription2", |
| - "22.1: GET /subscription1", |
| - "24.1: GET /subscription2", |
| - "46.1: GET /subscription1", |
| - "48.1: GET /subscription2" |
| - ]); |
| - subscription2.autoDownload = false; |
| - |
| - // |
| - // Header variations testing |
| - // |
| - |
| - subscriptionBody = "[Adblock]\nfoo\n!bar\n\n\n@@bas\n#bam"; |
| - runScheduledTasks(24); |
| - compareFilters("Filters of downloaded subscription", "foo\n!bar\n@@bas\n#bam", "synchronize_ok", null); |
| - |
| - subscriptionBody = "[Adblock Plus]\nfoo2\n!bar2\n@@bas2\n#bam2"; |
| - runScheduledTasks(24); |
| - compareFilters("Filters of downloaded subscription with [Adblock Plus] header", "foo2\n!bar2\n@@bas2\n#bam2", "synchronize_ok", null); |
| - |
| - subscriptionBody = "[Adblock Plus 0.0.1]\nfoo3\n!bar3\n@@bas3\n#bam3"; |
| - runScheduledTasks(24); |
| - compareFilters("Filters of downloaded subscription with [Adblock Plus 0.0.1] header", "foo3\n!bar3\n@@bas3\n#bam3", "synchronize_ok", "0.0.1"); |
| - |
| - subscriptionBody = "(something)[Adblock]\nfoo4\n!bar4\n@@bas4\n#bam4"; |
| - runScheduledTasks(24); |
| - compareFilters("Filters of downloaded subscription with (something)[Adblock] header", "foo4\n!bar4\n@@bas4\n#bam4", "synchronize_ok", null); |
| - |
| - subscriptionBody = "[Foo]\nthis should not be accepted"; |
| - runScheduledTasks(24); |
| - compareFilters("Filters of downloaded subscription with [Foo] header", "foo4\n!bar4\n@@bas4\n#bam4", "synchronize_invalid_data", null); |
| - |
| - subscriptionBody = "[Adblock Plus 99.9]\nsome_new_syntax"; |
| - runScheduledTasks(24); |
| - compareFilters("Filters of downloaded subscription with [Adblock Plus 99.9] header", "some_new_syntax", "synchronize_ok", "99.9"); |
| - |
| - // |
| - // Expiration testing |
| - // |
| - |
| - // Expiration time too small - should be changed into 24 hours |
| - subscriptionBody = "[Adblock]\n! Expires after 1 hour\nfoo"; |
| - runScheduledTasks(36); |
| - compareRequests("Expiration comment with less than default update interval (25 hours)", [ |
| - "0.1: GET /subscription1", |
| - "24.1: GET /subscription1" |
| - ]); |
| - |
| - subscriptionBody = "[Adblock]\n! Expires after 26 hours\nfoo"; |
| - runScheduledTasks(48); |
| - compareRequests("Downloads with 'Expires after 26 hours' comment (48 hours)", [ |
| - "12.1: GET /subscription1", |
| - "38.1: GET /subscription1" |
| - ]); |
| - |
| - subscriptionBody = "[Adblock]\n! Expires: 2 days\nfoo"; |
| - runScheduledTasks(70); |
| - compareRequests("Downloads with 'Expires: 2 days' comment (70 hours)", [ |
| - "16.1: GET /subscription1", |
| - "64.1: GET /subscription1" |
| - ]); |
| - |
| - subscriptionBody = "[Adblock]\nfoo"; |
| - subscriptionExtraHeaders = function() [["Expires", new Date(currentTime + 30 * 60 * 60 * 1000).toGMTString()]]; |
| - runScheduledTasks(80); |
| - compareRequests("Downloads with 'Expires: +30h' HTTP header (80 hours)", [ |
| - "42.1: GET /subscription1", |
| - "72.1: GET /subscription1" |
| - ]); |
| - |
| - // Expiration time too small, should be changed into 24 hours |
| - subscriptionExtraHeaders = function() [["Expires", new Date(currentTime + 20 * 60 * 60 * 1000).toGMTString()]]; |
| - runScheduledTasks(48); |
| - compareRequests("Expiration header with less than default update interval (48 hours)", [ |
| - "22.1: GET /subscription1", |
| - "46.1: GET /subscription1" |
| - ]); |
| - |
| - // Expiration time too large, should be changed into 14 days |
| - subscriptionExtraHeaders = function() [["Expires", new Date(currentTime + 504 * 60 * 60 * 1000).toGMTString()]]; |
| - runScheduledTasks(692); |
| - compareRequests("Expiration header more than two weeks in future (692 hours)", [ |
| - "22.1: GET /subscription1", |
| - "358.1: GET /subscription1" |
| - ]); |
| - |
| - // Soft expiration interval should be randomized - random returning 0 means factor 0.8 |
| - subscriptionExtraHeaders = function() [["Expires", new Date(currentTime + 35 * 60 * 60 * 1000).toGMTString()]]; |
| - SynchronizerGlobal.Math.random = function() 0; |
| - runScheduledTasks(56); |
| - compareRequests("Soft expiration should be multiplied with 0.8 if Math.random() returns 0 (48 hours)", [ |
| - "2.1: GET /subscription1", |
| - "30.1: GET /subscription1" |
| - ]); |
| - |
| - // Soft expiration interval should be randomized - random returning 0.9 means factor 1.16 |
| - subscriptionExtraHeaders = function() [["Expires", new Date(currentTime + 35 * 60 * 60 * 1000).toGMTString()]]; |
| - SynchronizerGlobal.Math.random = function() 0.9; |
| - runScheduledTasks(82); |
| - compareRequests("Soft expiration should be multiplied with 1.16 if Math.random() returns 0.9 (82 hours)", [ |
| - "2.1: GET /subscription1", |
| - "43.1: GET /subscription1" |
| - ]); |
| - SynchronizerGlobal.Math.random = function() 0.5; |
| - |
| - // Soft expiration interval should increase if the user is off-line more than a day |
| - subscriptionExtraHeaders = function() [["Expires", new Date(currentTime + 35 * 60 * 60 * 1000).toGMTString()]]; |
| - runScheduledTasks(4); |
| - requests = []; |
| - runScheduledTasks(26, true); // Skip the next 26 hours |
| - runScheduledTasks(104); |
| - compareRequests("Soft expiration interval should increase if user is offline for more than a day (104 hours)", [ |
| - "34.1: GET /subscription1", |
| - "69.1: GET /subscription1" |
| - ]); |
| - |
| - // Soft expiration interval should *not* increase if the user was off-line for a short period |
| - subscriptionExtraHeaders = function() [["Expires", new Date(currentTime + 35 * 60 * 60 * 1000).toGMTString()]]; |
| - runScheduledTasks(2); |
| - requests = []; |
| - runScheduledTasks(10, true); // Skip the next 10 hours |
| - runScheduledTasks(93); |
| - compareRequests("Soft expiration interval should not increase if user is offline for a few hours (93 hours)", [ |
| - "23.1: GET /subscription1", |
| - "58.1: GET /subscription1" |
| - ]); |
| - |
| - // Hard expiration interval: if the user was away too long the download should happen immediately |
| - subscriptionExtraHeaders = function() [["Expires", new Date(currentTime + 35 * 60 * 60 * 1000).toGMTString()]]; |
| - runScheduledTasks(4); |
| - requests = []; |
| - runScheduledTasks(80, true); // Skip the next 80 hours, more than twice the expiration time |
| - runScheduledTasks(70); |
| - compareRequests("Download should happen immediately if hard expiration interval is hit (70 hours)", [ |
| - "0.1: GET /subscription1", |
| - "35.1: GET /subscription1" |
| - ]); |
| - |
| - subscriptionExtraHeaders = null; |
| - |
| - // |
| - // Redirect testing |
| - // |
| - |
| - server.registerPathHandler("/subscription1", commentRedirectHandler); |
| - |
| - redirectURL = subscription2.url; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after comment redirect to /subscription2", [subscription2]); |
| - |
| - redirectURL = subscription2.url.replace("subscription2", "invalid_url"); |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after redirect to /invalid_url", [subscription1, subscription2]); |
| - |
| - server.registerPathHandler("/subscription1", redirectHandler); |
| - |
| - redirectURL = subscription2.url; |
| - redirectPermanent = false; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after temporary redirect to /subscription2", [subscription1, subscription2]); |
| - |
| - redirectPermanent = true; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after permanent redirect to /subscription2", [subscription2]); |
| - |
| - redirectURL = subscription3.url; |
| - redirectPermanent = false; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after temporary redirect to /subscription3", [subscription1, subscription2]); |
| - |
| - redirectPermanent = true; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after permanent redirect to /subscription3", [subscription2, subscription3]); |
| - |
| - redirectURL = subscription2.url.replace("subscription2", "invalid_url"); |
| - redirectPermanent = false; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after temporary redirect to /invalid_url", [subscription1, subscription2]); |
| - |
| - redirectPermanent = true; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after permanent redirect to /invalid_url", [subscription1, subscription2]); |
| - |
| - server.registerPathHandler("/subscription3", redirectHandler); |
| - |
| - server.registerPathHandler("/subscription1", function redirectHandler(metadata, response) |
| - { |
| - response.setStatusLine("1.1", 302, "Moved Temporarily"); |
| - response.setHeader("Location", subscription3.url); |
| - }); |
| - |
| - redirectURL = subscription2.url; |
| - redirectPermanent = false; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after temporary redirect to /subscription3 followed by temporary redirect to /subscription2", [subscription1, subscription2]); |
| - |
| - redirectPermanent = true; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after temporary redirect to /subscription3 followed by permanent redirect to /subscription2", [subscription1, subscription2]); |
| - |
| - redirectURL = subscription2.url.replace("subscription2", "invalid_url");; |
| - redirectPermanent = false; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after temporary redirect to /subscription3 followed by temporary redirect to /invalid_url", [subscription1, subscription2]); |
| - |
| - redirectPermanent = true; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after temporary redirect to /subscription3 followed by permanent redirect to /invalid_url", [subscription1, subscription2]); |
| - |
| - server.registerPathHandler("/subscription1", function redirectHandler(metadata, response) |
| - { |
| - response.setStatusLine("1.1", 301, "Moved Permanently"); |
| - response.setHeader("Location", subscription3.url); |
| - }); |
| - |
| - redirectURL = subscription2.url; |
| - redirectPermanent = false; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after permanent redirect to /subscription3 followed by temporary redirect to /subscription2", [subscription2, subscription3]); |
| - |
| - redirectPermanent = true; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after permanent redirect to /subscription3 followed by permanent redirect to /subscription2", [subscription2]); |
| - |
| - redirectURL = subscription2.url.replace("subscription2", "invalid_url");; |
| - redirectPermanent = false; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after permanent redirect to /subscription3 followed by temporary redirect to /invalid_url", [subscription1, subscription2]); |
| - |
| - redirectPermanent = true; |
| - runScheduledTasks(48); |
| - compareSubscriptions("Subscriptions after permanent redirect to /subscription3 followed by permanent redirect to /invalid_url", [subscription1, subscription2]); |
| - |
| - server.registerPathHandler("/subscription1", getSubscription); |
| - server.registerPathHandler("/subscription3", getSubscription); |
| - |
| - // |
| - // Behavior on errors |
| - // |
| - |
| - runScheduledTasks(48); // reset error counters |
| - requests = []; |
| - |
| - subscriptionStatus = [404, "Not Found"]; |
| - runScheduledTasks(72); |
| - compareRequests("Requests after 404 error (72 hours)", [ |
| - "0.1: GET /subscription1", |
| - "24.1: GET /subscription1", |
| - "48.1: GET /subscription1" |
| - ]); |
| - |
| - subscriptionStatus = [200, "OK"]; |
| - subscriptionBody = "Not a valid subscription"; |
| - runScheduledTasks(72); |
| - compareRequests("Requests for invalid subscription (72 hours)", [ |
| - "0.1: GET /subscription1", |
| - "24.1: GET /subscription1", |
| - "48.1: GET /subscription1" |
| - ]); |
| - |
| - server.registerPathHandler("/subscription1", function(metadata, response) |
| - { |
| - getSubscription(metadata, response); |
| - response.setStatusLine("1.1", "404", "Not found"); |
| - }); |
| - subscriptionBody = "[Adblock]\nfoo\nbar"; |
| - runScheduledTasks(216); |
| - compareRequests("Requests with fallback calls (216 hours)", [ |
| - "0.1: GET /subscription1", |
| - "0.1: GET /fallback " + subscription1.url + "&" + subscription1.url + "&0&404", |
| - "24.1: GET /subscription1", |
| - "48.1: GET /subscription1", |
| - "72.1: GET /subscription1", |
| - "96.1: GET /subscription1", |
| - "120.1: GET /subscription1", |
| - "144.1: GET /subscription1", |
| - "168.1: GET /subscription1", |
| - "168.1: GET /fallback " + subscription1.url + "&" + subscription1.url + "&0&404", |
| - "192.1: GET /subscription1" |
| - ]); |
| - |
| - fallbackResult = "410 Gone"; |
| - runScheduledTasks(216); |
| - compareRequests("Requests with fallback returning 410 Gone (216 hours)", [ |
| - "0.1: GET /subscription1", |
| - "24.1: GET /subscription1", |
| - "48.1: GET /subscription1", |
| - "72.1: GET /subscription1", |
| - "96.1: GET /subscription1", |
| - "120.1: GET /subscription1", |
| - "120.1: GET /fallback " + subscription1.url + "&" + subscription1.url + "&0&404", |
| - ]); |
| - subscription1.autoDownload = true; |
| - |
| - fallbackResult = "301 " + subscription2.url; |
| - runScheduledTasks(216); |
| - compareRequests("Requests with fallback redirecting to /subscription2 (216 hours)", [ |
| - "0.1: GET /subscription1", |
| - "24.1: GET /subscription1", |
| - "48.1: GET /subscription1", |
| - "72.1: GET /subscription1", |
| - "96.1: GET /subscription1", |
| - "120.1: GET /subscription1", |
| - "144.1: GET /subscription1", |
| - "144.1: GET /fallback " + subscription1.url + "&" + subscription1.url + "&0&404", |
| - "168.1: GET /subscription2", |
| - "192.1: GET /subscription2" |
| - ]); |
| - compareSubscriptions("Subscriptions after test above", [subscription2]); |
| - subscription1.autoDownload = true; |
| - |
| - fallbackResult = "301 " + subscription3.url; |
| - runScheduledTasks(216); |
| - compareRequests("Requests with fallback redirecting to /subscription3 (216 hours)", [ |
| - "0.1: GET /subscription1", |
| - "24.1: GET /subscription1", |
| - "48.1: GET /subscription1", |
| - "72.1: GET /subscription1", |
| - "96.1: GET /subscription1", |
| - "120.1: GET /subscription1", |
| - "144.1: GET /subscription1", |
| - "144.1: GET /fallback " + subscription1.url + "&" + subscription1.url + "&0&404", |
| - "168.1: GET /subscription3", |
| - "192.1: GET /subscription3" |
| - ]); |
| - compareSubscriptions("Subscriptions after test above", [subscription2, subscription3]); |
| - subscription1.autoDownload = true; |
| - |
| - fallbackResult = "301 " + subscription2.url.replace("subscription2", "invalid_url"); |
| - runScheduledTasks(384); |
| - compareRequests("Requests with fallback redirecting to /invalid_url (384 hours)", [ |
| - "0.1: GET /subscription1", |
| - "24.1: GET /subscription1", |
| - "48.1: GET /subscription1", |
| - "72.1: GET /subscription1", |
| - "96.1: GET /subscription1", |
| - "120.1: GET /subscription1", |
| - "144.1: GET /subscription1", |
| - "144.1: GET /fallback " + subscription1.url + "&" + subscription1.url + "&0&404", |
| - "192.1: GET /subscription1", |
| - "216.1: GET /subscription1", |
| - "240.1: GET /subscription1", |
| - "264.1: GET /subscription1", |
| - "288.1: GET /subscription1", |
| - "312.1: GET /subscription1", |
| - "312.1: GET /fallback " + subscription1.url + "&" + subscription1.url + "&0&404", |
| - "360.1: GET /subscription1" |
| - ]); |
| - compareSubscriptions("Subscriptions after test above", [subscription1, subscription2]); |
| - subscription1.autoDownload = true; |
| - |
| - server.registerPathHandler("/subscription1", getSubscription); |
| - fallbackResult = ""; |
| - |
| - // |
| - // Checksum verification |
| - // |
| - |
| - subscriptionBody = "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\nfoo\nbar\n"; |
| - |
| - runScheduledTasks(48); |
| - is(subscription1.downloadStatus, "synchronize_ok", "Subscription download with correct checksum succeeded"); |
| - |
| - subscriptionBody = subscriptionBody.replace(/Checksum: /, "$&wrong"); |
| - runScheduledTasks(48); |
| - is(subscription1.downloadStatus, "synchronize_checksum_mismatch", "Subscription download with wrong checksum failed"); |
| - subscriptionBody = subscriptionBody.replace(/wrong/, ""); |
| - |
| - subscriptionBody = subscriptionBody.replace(/\n/g, "\n\n"); |
| - runScheduledTasks(48); |
| - is(subscription1.downloadStatus, "synchronize_ok", "Empty lines are ignored for checksum validation"); |
| - subscriptionBody = subscriptionBody.replace(/\n\n/g, "\n"); |
| - |
| - subscriptionBody = subscriptionBody.replace(/\n/g, "\n \n"); |
| - runScheduledTasks(48); |
| - is(subscription1.downloadStatus, "synchronize_checksum_mismatch", "Lines with spaces are not ignored for checksum validation"); |
| - subscriptionBody = subscriptionBody.replace(/\n \n/g, "\n"); |
| - |
| - subscriptionBody = subscriptionBody.replace(/(Checksum[^\r\n]*)/, "extra1 $& extra2"); |
| - runScheduledTasks(48); |
| - is(subscription1.downloadStatus, "synchronize_ok", "Extra content in checksum line is ignored"); |
| - subscriptionBody = subscriptionBody.replace(/extra1 /, "").replace(/ extra2/, ""); |
| - |
| - subscriptionBody = subscriptionBody.replace(/\n/g, "\r\n"); |
| - runScheduledTasks(48); |
| - is(subscription1.downloadStatus, "synchronize_ok", "LF symbols are ignored for checksum validation"); |
| - subscriptionBody = subscriptionBody.replace(/\r\n/g, "\n"); |
| - |
| - subscriptionBody = subscriptionBody.replace(/\n/g, "\r"); |
| - runScheduledTasks(48); |
| - is(subscription1.downloadStatus, "synchronize_ok", "CR symbols are relevant for checksum validation"); |
| - subscriptionBody = subscriptionBody.replace(/\r/g, "\n"); |
| - |
| - subscriptionBody = subscriptionBody.replace(/(Checksum[^\r\n]*)/, "$&extra"); |
| - runScheduledTasks(48); |
| - is(subscription1.downloadStatus, "synchronize_checksum_mismatch", "Extra symbols in the checksum are interpreted as part of the checksum"); |
| - subscriptionBody = subscriptionBody.replace(/extra/, ""); |
| - |
| - subscriptionBody = subscriptionBody.replace(/(Checksum[^\r\n]*)/, "$&==="); |
| - runScheduledTasks(48); |
| - is(subscription1.downloadStatus, "synchronize_ok", "= symbols after checksum are ignored"); |
| - subscriptionBody = subscriptionBody.replace(/===/, ""); |
| - |
| - requests = []; |
| - subscriptionBody = subscriptionBody.replace(/Checksum: /, "$&wrong"); |
| - runScheduledTasks(216); |
| - compareRequests("Requests with checksum failures shouldn't trigger fallback URL (27 hours)", [ |
| - "0.1: GET /subscription1", |
| - "24.1: GET /subscription1", |
| - "48.1: GET /subscription1", |
| - "72.1: GET /subscription1", |
| - "96.1: GET /subscription1", |
| - "120.1: GET /subscription1", |
| - "144.1: GET /subscription1", |
| - "168.1: GET /subscription1", |
| - "192.1: GET /subscription1", |
| - ]); |
| - subscriptionBody = subscriptionBody.replace(/wrong/, ""); |
| - |
| - // |
| - // Alternative download locations |
| - // |
| - |
| - subscriptionBody = "[Adblock]\nfoo\nbar\n"; |
| - let alternativeLocations = subscription2.url + ";q=0.5," + subscription3.url + ";q=2"; |
| - subscriptionExtraHeaders = function() [["X-Alternative-Locations", alternativeLocations]]; |
| - |
| - runScheduledTasks(48); |
| - is(subscription1.downloadStatus, "synchronize_ok", "= symbols after checksum are ignored"); |
| - is(subscription1.alternativeLocations, alternativeLocations, "Alternative locations header processed on download"); |
| - |
| - requests = []; |
| - SynchronizerGlobal.Math.random = function() 0; |
| - runScheduledTasks(72); |
| - compareRequests("Base URL should be chosen if Math.random() returns 0", [ |
| - "0.1: GET /subscription1", |
| - "24.1: GET /subscription1", |
| - "48.1: GET /subscription1", |
| - ]); |
| - |
| - requests = []; |
| - SynchronizerGlobal.Math.random = function() 0.28; |
| - runScheduledTasks(72); |
| - compareRequests("Base URL should be chosen if Math.random() returns 0.28", [ |
| - "0.1: GET /subscription1", |
| - "24.1: GET /subscription1", |
| - "48.1: GET /subscription1", |
| - ]); |
| - |
| - requests = []; |
| - SynchronizerGlobal.Math.random = function() 0.29; |
| - runScheduledTasks(72); |
| - compareRequests("First alternative should be chosen if Math.random() returns 0.29", [ |
| - "0.1: GET /subscription2", |
| - "24.1: GET /subscription2", |
| - "48.1: GET /subscription2", |
| - ]); |
| - |
| - requests = []; |
| - SynchronizerGlobal.Math.random = function() 0.42; |
| - runScheduledTasks(72); |
| - compareRequests("First alternative should be chosen if Math.random() returns 0.42", [ |
| - "0.1: GET /subscription2", |
| - "24.1: GET /subscription2", |
| - "48.1: GET /subscription2", |
| - ]); |
| - |
| - requests = []; |
| - SynchronizerGlobal.Math.random = function() 0.43; |
| - runScheduledTasks(72); |
| - compareRequests("Second alternative should be chosen if Math.random() returns 0.43", [ |
| - "0.1: GET /subscription3", |
| - "24.1: GET /subscription3", |
| - "48.1: GET /subscription3", |
| - ]); |
| - |
| - requests = []; |
| - SynchronizerGlobal.Math.random = function() 0.99; // Note: side-effect is increasing soft expiration interval to 29 hours |
| - runScheduledTasks(87); |
| - compareRequests("Second alternative should be chosen if Math.random() returns 0.99", [ |
| - "0.1: GET /subscription3", |
| - "29.1: GET /subscription3", |
| - "58.1: GET /subscription3", |
| - ]); |
| - |
| - subscriptionStatus = [404, "Not Found"]; |
| - SynchronizerGlobal.Math.random = function() 0; |
| - runScheduledTasks(24); |
| - is(subscription1.alternativeLocations, alternativeLocations, "Alternative locations shouldn't be reset on download failure for base URL"); |
| - |
| - SynchronizerGlobal.Math.random = function() 0.99; |
| - runScheduledTasks(24); |
| - is(subscription1.alternativeLocations, null, "Alternative locations should be reset on download failure for alternative URL"); |
| - |
| - requests = []; |
| - subscriptionStatus = [200, "OK"]; |
| - SynchronizerGlobal.Math.random = function() 0.99; // Note: side-effect is increasing soft expiration interval to 29 hours |
| - runScheduledTasks(87); |
| - compareRequests("Alternative locations should be used again once the base URL returns a new list", [ |
| - "0.1: GET /subscription1", |
| - "29.1: GET /subscription3", |
| - "58.1: GET /subscription3", |
| - ]); |
| - |
| - server.registerPathHandler("/subscription1", commentRedirectHandler); |
| - redirectURL = subscription2.url; |
| - SynchronizerGlobal.Math.random = function() 0; |
| - runScheduledTasks(24); |
| - is(subscription1.nextURL, subscription2.url, "Redirect comment accepted from base URL"); |
| - subscription1.nextURL = null; |
| - server.registerPathHandler("/subscription1", getSubscription); |
| - |
| - server.registerPathHandler("/subscription3", commentRedirectHandler); |
| - redirectURL = subscription2.url; |
| - SynchronizerGlobal.Math.random = function() 0.99; |
| - runScheduledTasks(29); |
| - is(subscription1.nextURL, null, "Redirect comment ignored from alternative URL"); |
| - |
| - server.registerPathHandler("/subscription3", redirectHandler); |
| - redirectURL = subscription2.url; |
| - SynchronizerGlobal.Math.random = function() 0.99; |
| - redirectPermanent = true; |
| - runScheduledTasks(29); |
| - compareSubscriptions("Subscriptions after redirect from alternative URL", [subscription1, subscription2]); |
| - server.registerPathHandler("/subscription3", getSubscription); |
| - |
| - server.registerPathHandler("/subscription1", redirectHandler); |
| - redirectURL = subscription2.url; |
| - SynchronizerGlobal.Math.random = function() 0; |
| - redirectPermanent = true; |
| - runScheduledTasks(24); |
| - compareSubscriptions("Subscriptions after redirect from base URL", [subscription2]); |
| - server.registerPathHandler("/subscription1", getSubscription); |
| - |
| - subscriptionExtraHeaders = redirectExtraHeaders = |
| - function(metadata) [["X-Alternative-Locations", metadata.path == "/subscription1" ? subscription2.url : subscription1.url]]; |
| - server.registerPathHandler("/subscription1", redirectHandler); |
| - redirectURL = subscription2.url; |
| - SynchronizerGlobal.Math.random = function() 0; |
| - redirectPermanent = false; |
| - runScheduledTasks(24); |
| - is(subscription1.alternativeLocations, subscription2.url, "Alternative locations not taken over from redirect target on temporary redirect"); |
| - resetSubscriptions(); |
| - server.registerPathHandler("/subscription1", getSubscription); |
| - |
| - server.registerPathHandler("/subscription1", redirectHandler); |
| - redirectURL = subscription2.url; |
| - SynchronizerGlobal.Math.random = function() 0; |
| - redirectPermanent = true; |
| - runScheduledTasks(24); |
| - is(subscription1.alternativeLocations, subscription1.url, "Alternative locations taken over from redirect target on permanent redirect"); |
| - resetSubscriptions(); |
| - server.registerPathHandler("/subscription1", getSubscription); |
| - |
| - subscriptionExtraHeaders = null; |
| - redirectExtraHeaders = null; |
| - |
| - // @TODO: If-Modified-Since |
| - } |
| - |
| - SimpleTest.waitForExplicitFinish(); |
| - addLoadEvent(function() |
| - { |
| - try |
| - { |
| - server.start(1234); |
| - runTests(); |
| - } |
| - catch (e) |
| - { |
| - ok(false, e); |
| - throw e; |
| - } |
| - finally |
| - { |
| - server.stop(); |
| - SimpleTest.finish(); |
| - } |
| - }); |
| - </script> |
| - </pre> |
| -</body> |
| -</html> |
| + equal(subscription.downloadStatus, "synchronize_connection_error", "downloadStatus after download error"); |
| + equal(subscription.lastDownload * MILLIS_IN_SECOND, startTime + 2.1 * MILLIS_IN_HOUR, "lastDownload after download error"); |
| + equal(subscription.lastSuccess * MILLIS_IN_SECOND, startTime + 0.1 * MILLIS_IN_HOUR, "lastSuccess after download error"); |
| + equal(subscription.lastCheck * MILLIS_IN_SECOND, startTime + 3.1 * MILLIS_IN_HOUR, "lastCheck after download error"); |
| + equal(subscription.errors, 1, "errors after download error"); |
| + }); |
| +})(); |