toolkit/components/url-classifier/tests/unit/test_hashcompleter.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* Any copyright is dedicated to the Public Domain.
     2    http://creativecommons.org/publicdomain/zero/1.0/ */
     4 // This test ensures that the nsIUrlClassifierHashCompleter works as expected
     5 // and simulates an HTTP server to provide completions.
     6 //
     7 // In order to test completions, each group of completions sent as one request
     8 // to the HTTP server is called a completion set. There is currently not
     9 // support for multiple requests being sent to the server at once, in this test.
    10 // This tests makes a request for each element of |completionSets|, waits for
    11 // a response and then moves to the next element.
    12 // Each element of |completionSets| is an array of completions, and each
    13 // completion is an object with the properties:
    14 //   hash: complete hash for the completion. Automatically right-padded
    15 //         to be COMPLETE_LENGTH.
    16 //   expectCompletion: boolean indicating whether the server should respond
    17 //                     with a full hash.
    18 //   table: name of the table that the hash corresponds to. Only needs to be set
    19 //          if a completion is expected.
    20 //   chunkId: positive integer corresponding to the chunk that the hash belongs
    21 //            to. Only needs to be set if a completion is expected.
    22 //   multipleCompletions: boolean indicating whether the server should respond
    23 //                        with more than one full hash. If this is set to true
    24 //                        then |expectCompletion| must also be set to true and
    25 //                        |hash| must have the same prefix as all |completions|.
    26 //   completions: an array of completions (objects with a hash, table and
    27 //                chunkId property as described above). This property is only
    28 //                used when |multipleCompletions| is set to true.
    30 // Basic prefixes with 2/3 completions.
    31 let basicCompletionSet = [
    32   {
    33     hash: "abcdefgh",
    34     expectCompletion: true,
    35     table: "test",
    36     chunkId: 1234,
    37   },
    38   {
    39     hash: "1234",
    40     expectCompletion: false,
    41   },
    42   {
    43     hash: "\u0000\u0000\u000012312",
    44     expectCompletion: true,
    45     table: "test",
    46     chunkId: 1234,
    47   }
    48 ];
    50 // 3 prefixes with 0 completions to test HashCompleter handling a 204 status.
    51 let falseCompletionSet = [
    52   {
    53     hash: "1234",
    54     expectCompletion: false,
    55   },
    56   {
    57     hash: "",
    58     expectCompletion: false,
    59   },
    60   {
    61     hash: "abc",
    62     expectCompletion: false,
    63   }
    64 ];
    66 // The current implementation (as of Mar 2011) sometimes sends duplicate
    67 // entries to HashCompleter and even expects responses for duplicated entries.
    68 let dupedCompletionSet = [
    69   {
    70     hash: "1234",
    71     expectCompletion: true,
    72     table: "test",
    73     chunkId: 1,
    74   },
    75   {
    76     hash: "5678",
    77     expectCompletion: false,
    78     table: "test2",
    79     chunkId: 2,
    80   },
    81   {
    82     hash: "1234",
    83     expectCompletion: true,
    84     table: "test",
    85     chunkId: 1,
    86   },
    87   {
    88     hash: "5678",
    89     expectCompletion: false,
    90     table: "test2",
    91     chunkId: 2
    92   }
    93 ];
    95 // It is possible for a hash completion request to return with multiple
    96 // completions, the HashCompleter should return all of these.
    97 let multipleResponsesCompletionSet = [
    98   {
    99     hash: "1234",
   100     expectCompletion: true,
   101     multipleCompletions: true,
   102     completions: [
   103       {
   104         hash: "123456",
   105         table: "test1",
   106         chunkId: 3,
   107       },
   108       {
   109         hash: "123478",
   110         table: "test2",
   111         chunkId: 4,
   112       }
   113     ],
   114   }
   115 ];
   117 // The fifth completion set is added at runtime by addRandomCompletionSet.
   118 // Each completion in the set only has one response and its purpose is to
   119 // provide an easy way to test the HashCompleter handling an arbitrarily large
   120 // completion set (determined by SIZE_OF_RANDOM_SET).
   121 const SIZE_OF_RANDOM_SET = 16;
   122 function addRandomCompletionSet() {
   123   let completionSet = [];
   124   let hashPrefixes = [];
   126   let seed = Math.floor(Math.random() * Math.pow(2, 32));
   127   dump("Using seed of " + seed + " for random completion set.\n");
   128   let rand = new LFSRgenerator(seed);
   130   for (let i = 0; i < SIZE_OF_RANDOM_SET; i++) {
   131     let completion = {};
   133     // Generate a random 256 bit hash. First we get a random number and then
   134     // convert it to a string.
   135     let hash;
   136     let prefix;
   137     do {
   138       hash = "";
   139       let length = 1 + rand.nextNum(5);
   140       for (let i = 0; i < length; i++)
   141         hash += String.fromCharCode(rand.nextNum(8));
   142       prefix = hash.substring(0,4);
   143     } while (hashPrefixes.indexOf(prefix) != -1);
   145     hashPrefixes.push(prefix);
   146     completion.hash = hash;
   148     completion.expectCompletion = rand.nextNum(1) == 1;
   149     if (completion.expectCompletion) {
   150       // Generate a random alpha-numeric string of length at most 6 for the
   151       // table name.
   152       completion.table = (rand.nextNum(31)).toString(36);
   154       completion.chunkId = rand.nextNum(16);
   155     }
   157     completionSet.push(completion);
   158   }
   160   completionSets.push(completionSet);
   161 }
   163 let completionSets = [basicCompletionSet, falseCompletionSet,
   164                       dupedCompletionSet, multipleResponsesCompletionSet];
   165 let currentCompletionSet = -1;
   166 let finishedCompletions = 0;
   168 const SERVER_PORT = 8080;
   169 const SERVER_PATH = "/hash-completer";
   170 let server;
   172 // Completion hashes are automatically right-padded with null chars to have a
   173 // length of COMPLETE_LENGTH.
   174 // Taken from nsUrlClassifierDBService.h
   175 const COMPLETE_LENGTH = 32;
   177 let completer = Cc["@mozilla.org/url-classifier/hashcompleter;1"].
   178                   getService(Ci.nsIUrlClassifierHashCompleter);
   180 function run_test() {
   181   addRandomCompletionSet();
   183   // Fix up the completions before running the test.
   184   for each (let completionSet in completionSets) {
   185     for each (let completion in completionSet) {
   186       // Pad the right of each |hash| so that the length is COMPLETE_LENGTH.
   187       if (completion.multipleCompletions) {
   188         for each (let responseCompletion in completion.completions) {
   189           let numChars = COMPLETE_LENGTH - responseCompletion.hash.length;
   190           responseCompletion.hash += (new Array(numChars + 1)).join("\u0000");
   191         }
   192       }
   193       else {
   194         let numChars = COMPLETE_LENGTH - completion.hash.length;
   195         completion.hash += (new Array(numChars + 1)).join("\u0000");
   196       }
   197     }
   198   }
   199   do_test_pending();
   201   server = new HttpServer();
   202   server.registerPathHandler(SERVER_PATH, hashCompleterServer);
   204   const SERVER_PORT = 8080;
   205   server.start(SERVER_PORT);
   207   completer.gethashUrl = "http://localhost:" + SERVER_PORT + SERVER_PATH;
   209   runNextCompletion();
   210 }
   212 function doneCompletionSet() {
   213   do_check_eq(finishedCompletions, completionSets[currentCompletionSet].length);
   215   for each (let completion in completionSets[currentCompletionSet])
   216     do_check_true(completion._finished);
   218   runNextCompletion();
   219 }
   221 function runNextCompletion() {
   222   currentCompletionSet++;
   223   finishedCompletions = 0;
   225   if (currentCompletionSet >= completionSets.length) {
   226     finish();
   227     return;
   228   }
   230   dump("Now on completion set index " + currentCompletionSet + "\n");
   231   for each (let completion in completionSets[currentCompletionSet]) {
   232     completer.complete(completion.hash.substring(0,4),
   233                        (new callback(completion)));
   234   }
   235 }
   237 function hashCompleterServer(aRequest, aResponse) {
   238   let stream = aRequest.bodyInputStream;
   239   let wrapperStream = Cc["@mozilla.org/binaryinputstream;1"].
   240                         createInstance(Ci.nsIBinaryInputStream);
   241   wrapperStream.setInputStream(stream);
   243   let len = stream.available();
   244   let data = wrapperStream.readBytes(len);
   246   // To avoid a response with duplicate hash completions, we keep track of all
   247   // completed hash prefixes so far.
   248   let completedHashes = [];
   249   let responseText = "";
   251   function responseForCompletion(x) {
   252     return x.table + ":" + x.chunkId + ":" + x.hash.length + "\n" + x.hash;
   253   }
   254   for each (let completion in completionSets[currentCompletionSet]) {
   255     if (completion.expectCompletion &&
   256         (completedHashes.indexOf(completion.hash) == -1)) {
   257       completedHashes.push(completion.hash);
   259       if (completion.multipleCompletions)
   260         responseText += completion.completions.map(responseForCompletion).join("");
   261       else
   262         responseText += responseForCompletion(completion);
   263     }
   264   }
   266   // As per the spec, a server should response with a 204 if there are no
   267   // full-length hashes that match the prefixes.
   268   if (responseText)
   269     aResponse.write(responseText);
   270   else
   271     aResponse.setStatusLine(null, 204, null);
   272 }
   275 function callback(completion) {
   276   this._completion = completion;
   277 }
   278 callback.prototype = {
   279   completion: function completion(hash, table, chunkId, trusted) {
   280     do_check_true(this._completion.expectCompletion);
   282     if (this._completion.multipleCompletions) {
   283       for each (let completion in this._completion.completions) {
   284         if (completion.hash == hash) {
   285           do_check_eq(JSON.stringify(hash), JSON.stringify(completion.hash));
   286           do_check_eq(table, completion.table);
   287           do_check_eq(chunkId, completion.chunkId);
   289           completion._completed = true;
   291           if (this._completion.completions.every(function(x) x._completed))
   292             this._completed = true;
   294           break;
   295         }
   296       }
   297     }
   298     else {
   299       // Hashes are not actually strings and can contain arbitrary data.
   300       do_check_eq(JSON.stringify(hash), JSON.stringify(this._completion.hash));
   301       do_check_eq(table, this._completion.table);
   302       do_check_eq(chunkId, this._completion.chunkId);
   304       this._completed = true;
   305     }
   306   },
   308   completionFinished: function completionFinished(status) {
   309     do_check_eq(!!this._completion.expectCompletion, !!this._completed);
   310     this._completion._finished = true;
   312     finishedCompletions++;
   313     if (finishedCompletions == completionSets[currentCompletionSet].length)
   314       doneCompletionSet();
   315   },
   316 };
   318 function finish() {
   319   server.stop(function() {
   320     do_test_finished();
   321   });
   322 }

mercurial