| LEFT | RIGHT | 
|    1 (function() |    1 (function() | 
|    2 { |    2 { | 
|    3   let testRunner = null; |    3   let testRunner = null; | 
|    4   let server = null; |    4   let server = null; | 
|    5   let randomResult = 0.5; |    5   let randomResult = 0.5; | 
|    6  |    6  | 
|    7   const MILLIS_IN_SECOND = 1000; |    7   const MILLIS_IN_SECOND = 1000; | 
|    8   const MILLIS_IN_MINUTE = 60 * MILLIS_IN_SECOND; |    8   const MILLIS_IN_MINUTE = 60 * MILLIS_IN_SECOND; | 
|    9   const MILLIS_IN_HOUR = 60 * MILLIS_IN_MINUTE; |    9   const MILLIS_IN_HOUR = 60 * MILLIS_IN_MINUTE; | 
|   10   const MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR; |   10   const MILLIS_IN_DAY = 24 * MILLIS_IN_HOUR; | 
|   11  |   11  | 
|   12   module("Synchronizer", { |   12   module("Synchronizer", { | 
|   13     QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakRef
     erence]), |   13     QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakRef
     erence]), | 
|   14  |   14  | 
|   15     setup: function() |   15     setup: function() | 
|   16     { |   16     { | 
|   17       testRunner = this; |   17       testRunner = this; | 
|   18  |   18  | 
|   19       prepareFilterComponents.call(this); |   19       prepareFilterComponents.call(this); | 
|   20       preparePrefs.call(this); |   20       preparePrefs.call(this); | 
|   21  |   21  | 
|   22       let SynchronizerGlobal = Cu.getGlobalForObject(Synchronizer); |   22       let SynchronizerGlobal = Cu.getGlobalForObject(Synchronizer); | 
|   23       let SynchronizerModule = getModuleGlobal("synchronizer"); |   23       let SynchronizerModule = getModuleGlobal("synchronizer"); | 
 |   24       let DownloaderGlobal = Cu.getGlobalForObject(SynchronizerModule.downloader
     ); | 
|   24  |   25  | 
|   25       server = new nsHttpServer(); |   26       server = new nsHttpServer(); | 
|   26       server.start(1234); |   27       server.start(1234); | 
|   27  |   28  | 
|   28       let currentTime = 100000 * MILLIS_IN_HOUR; |   29       let currentTime = 100000 * MILLIS_IN_HOUR; | 
|   29       let startTime = currentTime; |   30       let startTime = currentTime; | 
|   30       let scheduledTasks = []; |   31       let scheduledTasks = []; | 
|   31  |   32  | 
|   32       // Replace Date.now() function |   33       // Replace Date.now() function | 
|   33       this._origNow = SynchronizerGlobal.Date.now; |   34       this._origNow = SynchronizerGlobal.Date.now; | 
|   34       SynchronizerGlobal.Date.now = function() currentTime; |   35       SynchronizerGlobal.Date.now = DownloaderGlobal.Date.now = function() curre
     ntTime; | 
|   35  |   36  | 
|   36       // Replace Math.random() function |   37       // Replace Math.random() function | 
|   37       this._origRandom = SynchronizerGlobal.Math.random; |   38       this._origRandom = DownloaderGlobal.Math.random; | 
|   38       SynchronizerGlobal.Math.random = function() randomResult; |   39       DownloaderGlobal.Math.random = function() randomResult; | 
|   39  |   40  | 
|   40       // Replace global timer variable |   41       // Replace global timer variable | 
|   41       let timer = {__proto__: SynchronizerModule.timer, delay: 0.1 * MILLIS_IN_H
     OUR}; |   42       let timer = {__proto__: SynchronizerModule.downloader._timer, delay: 0.1 *
      MILLIS_IN_HOUR}; | 
|   42       let callback = timer.callback; |   43       let callback = timer.callback; | 
|   43       timer.handler = function() { callback.notify(timer); }; |   44       timer.handler = function() { callback.notify(timer); }; | 
|   44       timer.nextExecution = currentTime + timer.delay; |   45       timer.nextExecution = currentTime + timer.delay; | 
|   45       scheduledTasks.push(timer); |   46       scheduledTasks.push(timer); | 
|   46       SynchronizerModule.timer.cancel(); |   47       SynchronizerModule.downloader._timer.cancel(); | 
|   47       SynchronizerModule.timer = timer; |   48       SynchronizerModule.downloader._timer = timer; | 
|   48  |   49  | 
|   49       // Register observer to track outstanding requests |   50       // Register observer to track outstanding requests | 
|   50       this._outstandingRequests = 0; |   51       this._outstandingRequests = 0; | 
|   51       Services.obs.addObserver(this, "http-on-modify-request", true); |   52       Services.obs.addObserver(this, "http-on-modify-request", true); | 
|   52       Services.obs.addObserver(this, "http-on-examine-response", true); |   53       Services.obs.addObserver(this, "http-on-examine-response", true); | 
|   53  |   54  | 
|   54       this.runScheduledTasks = function(maxHours, initial, skip) |   55       this.runScheduledTasks = function(maxHours, initial, skip) | 
|   55       { |   56       { | 
|   56         if (typeof maxHours != "number") |   57         if (typeof maxHours != "number") | 
|   57           throw new Error("Numerical parameter expected"); |   58           throw new Error("Numerical parameter expected"); | 
|   58         if (typeof initial != "number") |   59         if (typeof initial != "number") | 
|   59           initial = 0; |   60           initial = 0; | 
|   60         if (typeof skip != "number") |   61         if (typeof skip != "number") | 
|   61           skip = 0; |   62           skip = 0; | 
|   62  |   63  | 
|   63         startTime = currentTime; |   64         startTime = currentTime; | 
|   64         if (initial >= 0) |   65         if (initial >= 0) | 
|   65         { |   66         { | 
|   66           this._runScheduledTasks(initial); |   67           this._runScheduledTasks(initial); | 
|   67           maxHours -= initial; |   68           maxHours -= initial; | 
|   68         } |   69         } | 
|   69         if (skip) |   70         if (skip) | 
|   70         { |   71         { | 
|   71           this._skipTasks(skip); |   72           this._skipTasks(skip); | 
|   72           maxHours -= initial; |   73           maxHours -= skip; | 
|   73         } |   74         } | 
|   74         this._runScheduledTasks(maxHours); |   75         this._runScheduledTasks(maxHours); | 
|   75       } |   76       } | 
|   76  |   77  | 
|   77       this._runScheduledTasks = function(maxHours) |   78       this._runScheduledTasks = function(maxHours) | 
|   78       { |   79       { | 
|   79         let endTime = currentTime + maxHours * MILLIS_IN_HOUR; |   80         let endTime = currentTime + maxHours * MILLIS_IN_HOUR; | 
|   80         while (true) |   81         while (true) | 
|   81         { |   82         { | 
|   82           let nextTask = null; |   83           let nextTask = null; | 
| (...skipping 25 matching lines...) Expand all  Loading... | 
|  108           else |  109           else | 
|  109             nextTask.nextExecution = currentTime + nextTask.delay; |  110             nextTask.nextExecution = currentTime + nextTask.delay; | 
|  110         } |  111         } | 
|  111  |  112  | 
|  112         currentTime = endTime; |  113         currentTime = endTime; | 
|  113       } |  114       } | 
|  114  |  115  | 
|  115       this._skipTasks = function(hours) |  116       this._skipTasks = function(hours) | 
|  116       { |  117       { | 
|  117         let newTasks = []; |  118         let newTasks = []; | 
|  118         let endTime = currentTime + hours * MILLIS_IN_HOUR; |  119         currentTime += hours * MILLIS_IN_HOUR; | 
|  119         for each (let task in scheduledTasks) |  120         for each (let task in scheduledTasks) | 
|  120         { |  121         { | 
|  121           if (task.nextExecution >= endTime) |  122           if (task.nextExecution >= currentTime) | 
|  122             newTasks.push(task); |  123             newTasks.push(task); | 
|  123           else if (task.type != Components.interfaces.nsITimer.TYPE_ONE_SHOT) |  124           else if (task.type != Components.interfaces.nsITimer.TYPE_ONE_SHOT) | 
|  124           { |  125           { | 
|  125             task.nextExecution = endTime; |  126             task.nextExecution = currentTime; | 
|  126             newTasks.push(task); |  127             newTasks.push(task); | 
|  127           } |  128           } | 
|  128         } |  129         } | 
|  129         scheduledTasks = newTasks; |  130         scheduledTasks = newTasks; | 
|  130       } |  131       } | 
|  131  |  132  | 
|  132       this.getTimeOffset = function() (currentTime - startTime) / MILLIS_IN_HOUR
     ; |  133       this.getTimeOffset = function() (currentTime - startTime) / MILLIS_IN_HOUR
     ; | 
|  133  |  134  | 
|  134       this.__defineGetter__("currentTime", function() currentTime); |  135       this.__defineGetter__("currentTime", function() currentTime); | 
|  135     }, |  136     }, | 
| (...skipping 15 matching lines...) Expand all  Loading... | 
|  151       stop(); |  152       stop(); | 
|  152       server.stop(function() |  153       server.stop(function() | 
|  153       { |  154       { | 
|  154         server = null; |  155         server = null; | 
|  155         start(); |  156         start(); | 
|  156       }); |  157       }); | 
|  157  |  158  | 
|  158       if (this._origNow) |  159       if (this._origNow) | 
|  159       { |  160       { | 
|  160         let SynchronizerGlobal = Cu.getGlobalForObject(Synchronizer); |  161         let SynchronizerGlobal = Cu.getGlobalForObject(Synchronizer); | 
|  161         SynchronizerGlobal.Date.now = this._origNow; |  162         let SynchronizerModule = getModuleGlobal("synchronizer"); | 
 |  163         let DownloaderGlobal = Cu.getGlobalForObject(SynchronizerModule.download
     er); | 
 |  164         SynchronizerGlobal.Date.now = DownloaderGlobal.Date.now = this._origNow; | 
|  162         delete this._origNow; |  165         delete this._origNow; | 
|  163       } |  166       } | 
|  164  |  167  | 
|  165       if (this._origRandom) |  168       if (this._origRandom) | 
|  166       { |  169       { | 
|  167         let SynchronizerGlobal = Cu.getGlobalForObject(Synchronizer); |  170         let SynchronizerModule = getModuleGlobal("synchronizer"); | 
|  168         SynchronizerGlobal.Math.random = this._origRandom; |  171         let DownloaderGlobal = Cu.getGlobalForObject(SynchronizerModule.download
     er); | 
 |  172         DownloaderGlobal.Math.random = this._origRandom; | 
|  169         delete this._origRandom; |  173         delete this._origRandom; | 
|  170       } |  174       } | 
|  171  |  175  | 
|  172       Synchronizer.init(); |  176       Synchronizer.init(); | 
|  173     } |  177     } | 
|  174   }); |  178   }); | 
|  175  |  179  | 
|  176   function resetSubscription(subscription) |  180   function resetSubscription(subscription) | 
|  177   { |  181   { | 
|  178     FilterStorage.updateSubscriptionFilters(subscription, []); |  182     FilterStorage.updateSubscriptionFilters(subscription, []); | 
|  179     subscription.lastCheck =  subscription.lastDownload = |  183     subscription.lastCheck =  subscription.lastDownload = | 
|  180       subscription.lastSuccess = subscription.expires = |  184       subscription.version = subscription.lastSuccess = | 
|  181       subscription.softExpiration = 0; |  185       subscription.expires = subscription.softExpiration = 0; | 
 |  186     subscription.title = ""; | 
 |  187     subscription.homepage = null; | 
|  182     subscription.errors = 0; |  188     subscription.errors = 0; | 
|  183     subscription.downloadStatus = null; |  189     subscription.downloadStatus = null; | 
|  184     subscription.requiredVersion = null; |  190     subscription.requiredVersion = null; | 
|  185     subscription.nextURL = null; |  | 
|  186   } |  191   } | 
|  187  |  192  | 
|  188   test("Downloads of one subscription", function() |  193   test("Downloads of one subscription", function() | 
|  189   { |  194   { | 
|  190     // Always use average download interval |  195     // Always use average download interval | 
|  191     randomResult = 0.5; |  196     randomResult = 0.5; | 
|  192  |  197  | 
|  193     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); |  198     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); | 
|  194     FilterStorage.addSubscription(subscription); |  199     FilterStorage.addSubscription(subscription); | 
|  195  |  200  | 
|  196     let requests = []; |  201     let requests = []; | 
|  197     function handler(metadata, response) |  202     function handler(metadata, response) | 
|  198     { |  203     { | 
|  199       requests.push([testRunner.getTimeOffset(), metadata.method, metadata.path]
     ); |  204       requests.push([testRunner.getTimeOffset(), metadata.method, metadata.path]
     ); | 
|  200  |  205  | 
|  201       response.setStatusLine("1.1", "200", "OK"); |  206       response.setStatusLine("1.1", "200", "OK"); | 
|  202       response.setHeader("Content-Type", "text/plain"); |  207       response.setHeader("Content-Type", "text/plain"); | 
|  203  |  208  | 
|  204       let result = "[Adblock]\nfoo\nbar"; |  209       let result = "[Adblock]\n! ExPiREs: 1day\nfoo\nbar"; | 
|  205       response.bodyOutputStream.write(result, result.length); |  210       response.bodyOutputStream.write(result, result.length); | 
|  206     } |  211     } | 
|  207  |  212  | 
|  208     server.registerPathHandler("/subscription", handler); |  213     server.registerPathHandler("/subscription", handler); | 
|  209  |  214  | 
|  210     testRunner.runScheduledTasks(50); |  215     testRunner.runScheduledTasks(50); | 
|  211     deepEqual(requests, [ |  216     deepEqual(requests, [ | 
|  212       [0.1, "GET", "/subscription"], |  217       [0.1, "GET", "/subscription"], | 
|  213       [24.1, "GET", "/subscription"], |  218       [24.1, "GET", "/subscription"], | 
|  214       [48.1, "GET", "/subscription"], |  219       [48.1, "GET", "/subscription"], | 
| (...skipping 15 matching lines...) Expand all  Loading... | 
|  230     FilterStorage.addSubscription(subscription2); |  235     FilterStorage.addSubscription(subscription2); | 
|  231  |  236  | 
|  232     let requests = []; |  237     let requests = []; | 
|  233     function handler(metadata, response) |  238     function handler(metadata, response) | 
|  234     { |  239     { | 
|  235       requests.push([testRunner.getTimeOffset(), metadata.method, metadata.path]
     ); |  240       requests.push([testRunner.getTimeOffset(), metadata.method, metadata.path]
     ); | 
|  236  |  241  | 
|  237       response.setStatusLine("1.1", "200", "OK"); |  242       response.setStatusLine("1.1", "200", "OK"); | 
|  238       response.setHeader("Content-Type", "text/plain"); |  243       response.setHeader("Content-Type", "text/plain"); | 
|  239  |  244  | 
|  240       let result = "[Adblock]\nfoo\nbar"; |  245       let result = "[Adblock]\n! ExPiREs: 1day\nfoo\nbar"; | 
|  241       response.bodyOutputStream.write(result, result.length); |  246       response.bodyOutputStream.write(result, result.length); | 
|  242     } |  247     } | 
|  243  |  248  | 
|  244     server.registerPathHandler("/subscription1", handler); |  249     server.registerPathHandler("/subscription1", handler); | 
|  245     server.registerPathHandler("/subscription2", handler); |  250     server.registerPathHandler("/subscription2", handler); | 
|  246  |  251  | 
|  247     testRunner.runScheduledTasks(55); |  252     testRunner.runScheduledTasks(55); | 
|  248     deepEqual(requests, [ |  253     deepEqual(requests, [ | 
|  249       [0.1, "GET", "/subscription1"], |  254       [0.1, "GET", "/subscription1"], | 
|  250       [2.1, "GET", "/subscription2"], |  255       [2.1, "GET", "/subscription2"], | 
| (...skipping 13 matching lines...) Expand all  Loading... | 
|  264     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); |  269     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); | 
|  265     FilterStorage.addSubscription(subscription); |  270     FilterStorage.addSubscription(subscription); | 
|  266  |  271  | 
|  267     function handler(metadata, response) |  272     function handler(metadata, response) | 
|  268     { |  273     { | 
|  269       response.setStatusLine("1.1", "200", "OK"); |  274       response.setStatusLine("1.1", "200", "OK"); | 
|  270  |  275  | 
|  271       // Wrong content type shouldn't matter |  276       // Wrong content type shouldn't matter | 
|  272       response.setHeader("Content-Type", "text/xml"); |  277       response.setHeader("Content-Type", "text/xml"); | 
|  273  |  278  | 
|  274       let result = test.header + "\nfoo\n!bar\n\n@@bas\n#bam"; |  279       let result = test.header + "\n!Expires: 8 hours\nfoo\n!bar\n\n@@bas\n#bam"
     ; | 
|  275       response.bodyOutputStream.write(result, result.length); |  280       response.bodyOutputStream.write(result, result.length); | 
|  276     } |  281     } | 
|  277     server.registerPathHandler("/subscription", handler); |  282     server.registerPathHandler("/subscription", handler); | 
|  278  |  283  | 
|  279     let tests = [ |  284     let tests = [ | 
|  280       {header: "[Adblock]", downloadStatus: "synchronize_ok", requiredVersion: n
     ull}, |  285       {header: "[Adblock]", downloadStatus: "synchronize_ok", requiredVersion: n
     ull}, | 
|  281       {header: "[Adblock Plus]", downloadStatus: "synchronize_ok", requiredVersi
     on: null}, |  286       {header: "[Adblock Plus]", downloadStatus: "synchronize_ok", requiredVersi
     on: null}, | 
|  282       {header: "(something)[Adblock]", downloadStatus: "synchronize_ok", require
     dVersion: null}, |  287       {header: "(something)[Adblock]", downloadStatus: "synchronize_ok", require
     dVersion: null}, | 
|  283       {header: "[Adblock Plus 0.0.1]", downloadStatus: "synchronize_ok", require
     dVersion: "0.0.1"}, |  288       {header: "[Adblock Plus 0.0.1]", downloadStatus: "synchronize_ok", require
     dVersion: "0.0.1"}, | 
|  284       {header: "[Adblock Plus 99.9]", downloadStatus: "synchronize_ok", required
     Version: "99.9"}, |  289       {header: "[Adblock Plus 99.9]", downloadStatus: "synchronize_ok", required
     Version: "99.9"}, | 
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  343       response.setStatusLine("1.1", "200", "OK"); |  348       response.setStatusLine("1.1", "200", "OK"); | 
|  344       response.setHeader("Content-Type", "text/plain"); |  349       response.setHeader("Content-Type", "text/plain"); | 
|  345  |  350  | 
|  346       let result = "[Adblock]\nfoo\n!Expires: " + test.expiration + "\nbar"; |  351       let result = "[Adblock]\nfoo\n!Expires: " + test.expiration + "\nbar"; | 
|  347       response.bodyOutputStream.write(result, result.length); |  352       response.bodyOutputStream.write(result, result.length); | 
|  348     } |  353     } | 
|  349     server.registerPathHandler("/subscription", handler); |  354     server.registerPathHandler("/subscription", handler); | 
|  350  |  355  | 
|  351     let tests = [ |  356     let tests = [ | 
|  352       { |  357       { | 
|  353         expiration: "1 hour",   // Too small, will be corrected |  358         expiration: "default", | 
|  354         randomResult: 0.5, |  359         randomResult: 0.5, | 
|  355         requests: [0.1, 24.1] |  360         requests: [0.1, 5 * 24 + 0.1] | 
 |  361       }, | 
 |  362       { | 
 |  363         expiration: "1 hours",  // Minimal expiration interval | 
 |  364         randomResult: 0.5, | 
 |  365         requests: [0.1, 1.1, 2.1, 3.1] | 
|  356       }, |  366       }, | 
|  357       { |  367       { | 
|  358         expiration: "26 hours", |  368         expiration: "26 hours", | 
|  359         randomResult: 0.5, |  369         randomResult: 0.5, | 
|  360         requests: [0.1, 26.1] |  370         requests: [0.1, 26.1] | 
|  361       }, |  371       }, | 
|  362       { |  372       { | 
|  363         expiration: "2 days", |  373         expiration: "2 days", | 
|  364         randomResult: 0.5, |  374         randomResult: 0.5, | 
|  365         requests: [0.1, 48.1] |  375         requests: [0.1, 48.1] | 
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  406         requests: [0.1, 85.1] |  416         requests: [0.1, 85.1] | 
|  407       } |  417       } | 
|  408     ] |  418     ] | 
|  409  |  419  | 
|  410     for each (test in tests) |  420     for each (test in tests) | 
|  411     { |  421     { | 
|  412       requests = []; |  422       requests = []; | 
|  413       randomResult = test.randomResult; |  423       randomResult = test.randomResult; | 
|  414       resetSubscription(subscription); |  424       resetSubscription(subscription); | 
|  415  |  425  | 
|  416       let maxHours = Math.round(Math.max.apply(null, test.requests)) + 12; |  426       let maxHours = Math.round(Math.max.apply(null, test.requests)) + 1; | 
|  417       testRunner.runScheduledTasks(maxHours, test.skipAfter, test.skip); |  427       testRunner.runScheduledTasks(maxHours, test.skipAfter, test.skip); | 
|  418  |  428  | 
|  419       let randomAddendum = (randomResult == 0.5 ? "" : " with Math.random() retu
     rning " + randomResult); |  429       let randomAddendum = (randomResult == 0.5 ? "" : " with Math.random() retu
     rning " + randomResult); | 
|  420       let skipAddendum = (typeof test.skip != "number" ? "" : " skipping " + tes
     t.skip + " hours after " + test.skipAfter + " hours"); |  430       let skipAddendum = (typeof test.skip != "number" ? "" : " skipping " + tes
     t.skip + " hours after " + test.skipAfter + " hours"); | 
|  421       deepEqual(requests, test.requests, "Requests for \"" + test.expiration + "
     \"" + randomAddendum + skipAddendum); |  431       deepEqual(requests, test.requests, "Requests for \"" + test.expiration + "
     \"" + randomAddendum + skipAddendum); | 
|  422  |  432     } | 
|  423       if (typeof test.skip == "number") |  433   }); | 
|  424       { |  434  | 
|  425         // Ensure that next time synchronizer triggers at time offset 0.1 again |  435   test("Checksum verification", function() | 
|  426         testRunner.runScheduledTasks(0.1); |  436   { | 
|  427       } |  437     // Always use average download interval | 
 |  438     randomResult = 0.5; | 
 |  439  | 
 |  440     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); | 
 |  441     FilterStorage.addSubscription(subscription); | 
 |  442  | 
 |  443     let testName, subscriptionBody, expectedResult; | 
 |  444     let tests = [ | 
 |  445       ["Correct checksum", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\nfoo\n
     bar\n", true], | 
 |  446       ["Wrong checksum", "[Adblock]\n! Checksum: wrongggny6Fn24b7JHsq/A\nfoo\nba
     r\n", false], | 
 |  447       ["Empty lines ignored", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7JHsq/A\n\n
     foo\n\nbar\n\n", true], | 
 |  448       ["CR LF line breaks treated like LR", "[Adblock]\n! Checksum: e/JCmqXny6Fn
     24b7JHsq/A\nfoo\r\nbar\r\n", true], | 
 |  449       ["CR line breaks treated like LR", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b
     7JHsq/A\nfoo\rbar\r", true], | 
 |  450       ["Trailing line break not ignored", "[Adblock]\n! Checksum: e/JCmqXny6Fn24
     b7JHsq/A\nfoo\nbar", false], | 
 |  451       ["Line breaks between lines not ignored", "[Adblock]\n! Checksum: e/JCmqXn
     y6Fn24b7JHsq/A\nfoobar", false], | 
 |  452       ["Lines with spaces not ignored", "[Adblock]\n! Checksum: e/JCmqXny6Fn24b7
     JHsq/A\n \nfoo\n\nbar\n", false], | 
 |  453       ["Extra content in checksum line is part of the checksum", "[Adblock]\n! C
     hecksum: e/JCmqXny6Fn24b7JHsq/A foobar\nfoo\nbar\n", false], | 
 |  454       ["= symbols after checksum are ignored", "[Adblock]\n! Checksum: e/JCmqXny
     6Fn24b7JHsq/A===\nfoo\nbar\n", true], | 
 |  455       ["Header line is part of the checksum", "[Adblock Plus]\n! Checksum: e/JCm
     qXny6Fn24b7JHsq/A\nfoo\nbar\n", false], | 
 |  456       ["Special comments are part of the checksum", "[Adblock]\n! Checksum: e/JC
     mqXny6Fn24b7JHsq/A\n! Expires: 1\nfoo\nbar\n", false], | 
 |  457     ]; | 
 |  458  | 
 |  459     function handler(metadata, response) | 
 |  460     { | 
 |  461       response.setStatusLine("1.1", "200", "OK"); | 
 |  462       response.setHeader("Content-Type", "text/plain"); | 
 |  463  | 
 |  464       response.bodyOutputStream.write(subscriptionBody, subscriptionBody.length)
     ; | 
 |  465     } | 
 |  466     server.registerPathHandler("/subscription", handler); | 
 |  467  | 
 |  468     for each ([testName, subscriptionBody, expectedResult] in tests) | 
 |  469     { | 
 |  470       resetSubscription(subscription); | 
 |  471       testRunner.runScheduledTasks(2); | 
 |  472       equal(subscription.downloadStatus, expectedResult ? "synchronize_ok" : "sy
     nchronize_checksum_mismatch", testName); | 
 |  473     } | 
 |  474   }); | 
 |  475  | 
 |  476   test("Special comments", function() | 
 |  477   { | 
 |  478     // Always use average download interval | 
 |  479     randomResult = 0.5; | 
 |  480  | 
 |  481     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); | 
 |  482     FilterStorage.addSubscription(subscription); | 
 |  483  | 
 |  484     let comment, check; | 
 |  485     let tests = [ | 
 |  486       ["! Homepage: http://example.com/", function() equal(subscription.homepage
     , "http://example.com/", "Valid homepage comment")], | 
 |  487       ["! Homepage: ssh://example.com/", function() equal(subscription.homepage,
      null, "Invalid homepage comment")], | 
 |  488       ["! Title: foo", function() | 
 |  489         { | 
 |  490           equal(subscription.title, "foo", "Title comment"); | 
 |  491           equal(subscription.fixedTitle, true, "Fixed title"); | 
 |  492         }], | 
 |  493       ["! Version: 1234", function() equal(subscription.version, 1234, "Version 
     comment")] | 
 |  494     ]; | 
 |  495  | 
 |  496     function handler(metadata, response) | 
 |  497     { | 
 |  498       response.setStatusLine("1.1", "200", "OK"); | 
 |  499       response.setHeader("Content-Type", "text/plain"); | 
 |  500  | 
 |  501       let result = "[Adblock]\n" + comment + "\nfoo\nbar"; | 
 |  502       response.bodyOutputStream.write(result, result.length); | 
 |  503     } | 
 |  504     server.registerPathHandler("/subscription", handler); | 
 |  505  | 
 |  506     for each([comment, check] in tests) | 
 |  507     { | 
 |  508       resetSubscription(subscription); | 
 |  509       testRunner.runScheduledTasks(2); | 
 |  510       check(); | 
 |  511       deepEqual(subscription.filters, [Filter.fromText("foo"), Filter.fromText("
     bar")], "Special comment not added to filters"); | 
|  428     } |  512     } | 
|  429   }); |  513   }); | 
|  430  |  514  | 
|  431   test("Redirects", function() |  515   test("Redirects", function() | 
|  432   { |  516   { | 
 |  517     // Always use average download interval | 
 |  518     randomResult = 0.5; | 
 |  519  | 
|  433     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); |  520     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); | 
|  434     FilterStorage.addSubscription(subscription); |  521     FilterStorage.addSubscription(subscription); | 
|  435  |  522  | 
|  436     function redirect_handler(metadata, response) |  523     function redirect_handler(metadata, response) | 
|  437     { |  524     { | 
|  438       response.setStatusLine("1.1", "200", "OK"); |  525       response.setStatusLine("1.1", "200", "OK"); | 
|  439       response.setHeader("Content-Type", "text/plain"); |  526       response.setHeader("Content-Type", "text/plain"); | 
|  440  |  527  | 
|  441       let result = "[Adblock]\nfoo\n!Redirect: http://127.0.0.1:1234/redirected\
     nbar"; |  528       let result = "[Adblock]\nfoo\n!Redirect: http://127.0.0.1:1234/redirected\
     nbar"; | 
|  442       response.bodyOutputStream.write(result, result.length); |  529       response.bodyOutputStream.write(result, result.length); | 
|  443     } |  530     } | 
|  444     server.registerPathHandler("/subscription", redirect_handler); |  531     server.registerPathHandler("/subscription", redirect_handler); | 
|  445  |  532  | 
|  446     testRunner.runScheduledTasks(50); |  533     testRunner.runScheduledTasks(30); | 
|  447     equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/subscriptio
     n", "Invalid redirect ignored"); |  534     equal(FilterStorage.subscriptions[0], subscription, "Invalid redirect ignore
     d"); | 
 |  535     equal(subscription.downloadStatus, "synchronize_connection_error", "Connecti
     on error recorded"); | 
 |  536     equal(subscription.errors, 2, "Number of download errors"); | 
|  448  |  537  | 
|  449     let requests = []; |  538     let requests = []; | 
|  450     function handler(metadata, response) |  539     function handler(metadata, response) | 
|  451     { |  540     { | 
|  452       requests.push(testRunner.getTimeOffset()); |  541       requests.push(testRunner.getTimeOffset()); | 
|  453  |  542  | 
|  454       response.setStatusLine("1.1", "200", "OK"); |  543       response.setStatusLine("1.1", "200", "OK"); | 
|  455       response.setHeader("Content-Type", "text/plain"); |  544       response.setHeader("Content-Type", "text/plain"); | 
|  456  |  545  | 
|  457       let result = "[Adblock]\nfoo\nbar"; |  546       let result = "[Adblock]\nfoo\n! Expires: 8 hours\nbar"; | 
|  458       response.bodyOutputStream.write(result, result.length); |  547       response.bodyOutputStream.write(result, result.length); | 
|  459     } |  548     } | 
|  460     server.registerPathHandler("/redirected", handler); |  549     server.registerPathHandler("/redirected", handler); | 
|  461  |  550  | 
|  462     resetSubscription(subscription); |  551     resetSubscription(subscription); | 
|  463     testRunner.runScheduledTasks(50); |  552     testRunner.runScheduledTasks(15); | 
|  464     equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/redirected"
     , "Redirect followed"); |  553     equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/redirected"
     , "Redirect followed"); | 
|  465     deepEqual(requests, [24.1, 48.1], "Resulting requests"); |  554     deepEqual(requests, [0.1, 8.1], "Resulting requests"); | 
 |  555  | 
 |  556     server.registerPathHandler("/redirected", function(metadata, response) | 
 |  557     { | 
 |  558       response.setStatusLine("1.1", "200", "OK"); | 
 |  559       response.setHeader("Content-Type", "text/plain"); | 
 |  560  | 
 |  561       let result = "[Adblock]\nfoo\n!Redirect: http://127.0.0.1:1234/subscriptio
     n\nbar"; | 
 |  562       response.bodyOutputStream.write(result, result.length); | 
 |  563     }) | 
 |  564  | 
 |  565     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); | 
 |  566     resetSubscription(subscription); | 
 |  567     FilterStorage.removeSubscription(FilterStorage.subscriptions[0]); | 
 |  568     FilterStorage.addSubscription(subscription); | 
 |  569  | 
 |  570     testRunner.runScheduledTasks(2); | 
 |  571     equal(FilterStorage.subscriptions[0], subscription, "Redirect not followed o
     n redirect loop"); | 
 |  572     equal(subscription.downloadStatus, "synchronize_connection_error", "Download
      status after redirect loop"); | 
|  466   }); |  573   }); | 
|  467  |  574  | 
|  468   test("Fallback", function() |  575   test("Fallback", function() | 
|  469   { |  576   { | 
 |  577     // Always use average download interval | 
 |  578     randomResult = 0.5; | 
 |  579  | 
|  470     Prefs.subscriptions_fallbackerrors = 3; |  580     Prefs.subscriptions_fallbackerrors = 3; | 
|  471     Prefs.subscriptions_fallbackurl = "http://127.0.0.1:1234/fallback?%URL%&%CHA
     NNELSTATUS%&%RESPONSESTATUS%"; |  581     Prefs.subscriptions_fallbackurl = "http://127.0.0.1:1234/fallback?%SUBSCRIPT
     ION%&%CHANNELSTATUS%&%RESPONSESTATUS%"; | 
|  472  |  582  | 
|  473     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); |  583     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); | 
|  474     FilterStorage.addSubscription(subscription); |  584     FilterStorage.addSubscription(subscription); | 
 |  585  | 
 |  586     // No valid response from fallback | 
|  475  |  587  | 
|  476     let requests = []; |  588     let requests = []; | 
|  477     function handler(metadata, response) |  589     function handler(metadata, response) | 
|  478     { |  590     { | 
|  479       requests.push(testRunner.getTimeOffset()); |  591       requests.push(testRunner.getTimeOffset()); | 
|  480  |  592  | 
|  481       response.setStatusLine("1.1", "404", "Not found"); |  593       response.setStatusLine("1.1", "404", "Not found"); | 
|  482     } |  594     } | 
|  483     server.registerPathHandler("/subscription", handler); |  595     server.registerPathHandler("/subscription", handler); | 
|  484  |  596  | 
|  485     testRunner.runScheduledTasks(100); |  597     testRunner.runScheduledTasks(100); | 
|  486     deepEqual(requests, [0.1, 24.1, 48.1, 72.1, 96.1], "Continue trying if the f
     allback doesn't respond"); |  598     deepEqual(requests, [0.1, 24.1, 48.1, 72.1, 96.1], "Continue trying if the f
     allback doesn't respond"); | 
 |  599  | 
 |  600     // Fallback giving "Gone" response | 
|  487  |  601  | 
|  488     resetSubscription(subscription); |  602     resetSubscription(subscription); | 
|  489     requests = []; |  603     requests = []; | 
|  490     fallbackParams = null; |  604     fallbackParams = null; | 
|  491     server.registerPathHandler("/fallback", function(metadata, response) |  605     server.registerPathHandler("/fallback", function(metadata, response) | 
|  492     { |  606     { | 
|  493       response.setStatusLine("1.1", "200", "OK"); |  607       response.setStatusLine("1.1", "200", "OK"); | 
|  494       fallbackParams = decodeURIComponent(metadata.queryString); |  608       fallbackParams = decodeURIComponent(metadata.queryString); | 
|  495  |  609  | 
|  496       let result = "410 Gone"; |  610       let result = "410 Gone"; | 
|  497       response.bodyOutputStream.write(result, result.length); |  611       response.bodyOutputStream.write(result, result.length); | 
|  498     }); |  612     }); | 
|  499  |  613  | 
|  500     testRunner.runScheduledTasks(100); |  614     testRunner.runScheduledTasks(100); | 
|  501     deepEqual(requests, [0.1, 24.1, 48.1], "Stop trying if the fallback responds
      with Gone"); |  615     deepEqual(requests, [0.1, 24.1, 48.1], "Stop trying if the fallback responds
      with Gone"); | 
|  502     equal(fallbackParams, "http://127.0.0.1:1234/subscription&0&404"); |  616     equal(fallbackParams, "http://127.0.0.1:1234/subscription&0&404", "Fallback 
     arguments"); | 
|  503  |  617  | 
 |  618     // Fallback redirecting to a missing file | 
 |  619  | 
 |  620     subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); | 
|  504     resetSubscription(subscription); |  621     resetSubscription(subscription); | 
|  505     FilterStorage.removeSubscription(FilterStorage.subscriptions[0]); |  622     FilterStorage.removeSubscription(FilterStorage.subscriptions[0]); | 
|  506     FilterStorage.addSubscription(subscription); |  623     FilterStorage.addSubscription(subscription); | 
|  507     requests = []; |  624     requests = []; | 
|  508  |  625  | 
|  509     server.registerPathHandler("/fallback", function(metadata, response) |  626     server.registerPathHandler("/fallback", function(metadata, response) | 
|  510     { |  627     { | 
|  511       response.setStatusLine("1.1", "200", "OK"); |  628       response.setStatusLine("1.1", "200", "OK"); | 
|  512  |  629  | 
|  513       let result = "301 http://127.0.0.1:1234/redirected"; |  630       let result = "301 http://127.0.0.1:1234/redirected"; | 
|  514       response.bodyOutputStream.write(result, result.length); |  631       response.bodyOutputStream.write(result, result.length); | 
|  515     }); |  632     }); | 
|  516     testRunner.runScheduledTasks(100); |  633     testRunner.runScheduledTasks(100); | 
|  517     equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/subscriptio
     n"); |  634     equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/subscriptio
     n", "Ignore invalid redirect from fallback"); | 
|  518     deepEqual(requests, [0.1, 24.1, 48.1, 96.1], "Come back after invalid redire
     ct from fallback"); |  635     deepEqual(requests, [0.1, 24.1, 48.1, 72.1, 96.1], "Requests not affected by
      invalid redirect"); | 
 |  636  | 
 |  637     // Fallback redirecting to an existing file | 
|  519  |  638  | 
|  520     resetSubscription(subscription); |  639     resetSubscription(subscription); | 
|  521     requests = []; |  640     requests = []; | 
|  522     let redirectedRequests = []; |  641     let redirectedRequests = []; | 
|  523     server.registerPathHandler("/redirected", function(metadata, response) |  642     server.registerPathHandler("/redirected", function(metadata, response) | 
|  524     { |  643     { | 
|  525       redirectedRequests.push(testRunner.getTimeOffset()); |  644       redirectedRequests.push(testRunner.getTimeOffset()); | 
|  526  |  645  | 
|  527       response.setStatusLine("1.1", "200", "OK"); |  646       response.setStatusLine("1.1", "200", "OK"); | 
|  528       response.setHeader("Content-Type", "text/plain"); |  647       response.setHeader("Content-Type", "text/plain"); | 
|  529  |  648  | 
|  530       let result = "[Adblock]\nfoo\nbar"; |  649       let result = "[Adblock]\n!Expires: 1day\nfoo\nbar"; | 
|  531       response.bodyOutputStream.write(result, result.length); |  650       response.bodyOutputStream.write(result, result.length); | 
|  532     }); |  651     }); | 
|  533  |  652  | 
|  534     testRunner.runScheduledTasks(100); |  653     testRunner.runScheduledTasks(100); | 
|  535     equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/redirected"
     ); |  654     equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/redirected"
     , "Valid redirect from fallback is followed"); | 
|  536     deepEqual(requests, [0.1, 24.1, 48.1], "Stop polling original URL after a va
     lid redirect from fallback"); |  655     deepEqual(requests, [0.1, 24.1, 48.1], "Stop polling original URL after a va
     lid redirect from fallback"); | 
|  537     deepEqual(redirectedRequests, [72.1, 96.1], "Request new URL after a valid r
     edirect from fallback"); |  656     deepEqual(redirectedRequests, [48.1, 72.1, 96.1], "Request new URL after a v
     alid redirect from fallback"); | 
|  538   }); |  657  | 
|  539  |  658     // Checksum mismatch | 
|  540   // TODO: Checksum verification |  659  | 
 |  660     function handler2(metadata, response) | 
 |  661     { | 
 |  662       response.setStatusLine("1.1", "200", "OK"); | 
 |  663       response.setHeader("Content-Type", "text/plain"); | 
 |  664  | 
 |  665       let result = "[Adblock]\n! Checksum: wrong\nfoo\nbar"; | 
 |  666       response.bodyOutputStream.write(result, result.length); | 
 |  667     } | 
 |  668     server.registerPathHandler("/subscription", handler2); | 
 |  669  | 
 |  670     subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); | 
 |  671     resetSubscription(subscription); | 
 |  672     FilterStorage.removeSubscription(FilterStorage.subscriptions[0]); | 
 |  673     FilterStorage.addSubscription(subscription); | 
 |  674  | 
 |  675     testRunner.runScheduledTasks(100); | 
 |  676     equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/redirected"
     , "Wrong checksum produces fallback request"); | 
 |  677  | 
 |  678     // Redirect loop | 
 |  679  | 
 |  680     server.registerPathHandler("/subscription", function(metadata, response) | 
 |  681     { | 
 |  682       response.setStatusLine("1.1", "200", "OK"); | 
 |  683       response.setHeader("Content-Type", "text/plain"); | 
 |  684  | 
 |  685       let result = "[Adblock]\n! Redirect: http://127.0.0.1:1234/subscription2"; | 
 |  686       response.bodyOutputStream.write(result, result.length); | 
 |  687     }); | 
 |  688     server.registerPathHandler("/subscription2", function(metadata, response) | 
 |  689     { | 
 |  690       response.setStatusLine("1.1", "200", "OK"); | 
 |  691       response.setHeader("Content-Type", "text/plain"); | 
 |  692  | 
 |  693       let result = "[Adblock]\n! Redirect: http://127.0.0.1:1234/subscription"; | 
 |  694       response.bodyOutputStream.write(result, result.length); | 
 |  695     }); | 
 |  696  | 
 |  697     subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"); | 
 |  698     resetSubscription(subscription); | 
 |  699     FilterStorage.removeSubscription(FilterStorage.subscriptions[0]); | 
 |  700     FilterStorage.addSubscription(subscription); | 
 |  701  | 
 |  702     testRunner.runScheduledTasks(100); | 
 |  703     equal(FilterStorage.subscriptions[0].url, "http://127.0.0.1:1234/redirected"
     , "Fallback can still redirect even after a redirect loop"); | 
 |  704   }); | 
 |  705  | 
 |  706   test("State fields", function() | 
 |  707   { | 
 |  708     // Always use average download interval | 
 |  709     randomResult = 0.5; | 
 |  710  | 
 |  711     let subscription = Subscription.fromURL("http://127.0.0.1:1234/subscription"
     ); | 
 |  712     FilterStorage.addSubscription(subscription); | 
 |  713  | 
 |  714     server.registerPathHandler("/subscription", function successHandler(metadata
     , response) | 
 |  715     { | 
 |  716       response.setStatusLine("1.1", "200", "OK"); | 
 |  717       response.setHeader("Content-Type", "text/plain"); | 
 |  718  | 
 |  719       let result = "[Adblock]\n! Expires: 2 hours\nfoo\nbar"; | 
 |  720       response.bodyOutputStream.write(result, result.length); | 
 |  721     }); | 
 |  722  | 
 |  723     let startTime = testRunner.currentTime; | 
 |  724     testRunner.runScheduledTasks(2); | 
 |  725  | 
 |  726     equal(subscription.downloadStatus, "synchronize_ok", "downloadStatus after s
     uccessful download"); | 
 |  727     equal(subscription.lastDownload * MILLIS_IN_SECOND, startTime + 0.1 * MILLIS
     _IN_HOUR, "lastDownload after successful download"); | 
 |  728     equal(subscription.lastSuccess * MILLIS_IN_SECOND, startTime + 0.1 * MILLIS_
     IN_HOUR, "lastSuccess after successful download"); | 
 |  729     equal(subscription.lastCheck * MILLIS_IN_SECOND, startTime + 1.1 * MILLIS_IN
     _HOUR, "lastCheck after successful download"); | 
 |  730     equal(subscription.errors, 0, "errors after successful download"); | 
 |  731  | 
 |  732     server.registerPathHandler("/subscription", function errorHandler(metadata, 
     response) | 
 |  733     { | 
 |  734       response.setStatusLine("1.1", "404", "Not Found"); | 
 |  735     }); | 
 |  736  | 
 |  737     testRunner.runScheduledTasks(2); | 
 |  738  | 
 |  739     equal(subscription.downloadStatus, "synchronize_connection_error", "download
     Status after download error"); | 
 |  740     equal(subscription.lastDownload * MILLIS_IN_SECOND, startTime + 2.1 * MILLIS
     _IN_HOUR, "lastDownload after download error"); | 
 |  741     equal(subscription.lastSuccess * MILLIS_IN_SECOND, startTime + 0.1 * MILLIS_
     IN_HOUR, "lastSuccess after download error"); | 
 |  742     equal(subscription.lastCheck * MILLIS_IN_SECOND, startTime + 3.1 * MILLIS_IN
     _HOUR, "lastCheck after download error"); | 
 |  743     equal(subscription.errors, 1, "errors after download error"); | 
 |  744   }); | 
|  541 })(); |  745 })(); | 
| LEFT | RIGHT |