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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/toolkit/components/url-classifier/tests/unit/test_hashcompleter.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,322 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 +   http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +
     1.7 +// This test ensures that the nsIUrlClassifierHashCompleter works as expected
     1.8 +// and simulates an HTTP server to provide completions.
     1.9 +//
    1.10 +// In order to test completions, each group of completions sent as one request
    1.11 +// to the HTTP server is called a completion set. There is currently not
    1.12 +// support for multiple requests being sent to the server at once, in this test.
    1.13 +// This tests makes a request for each element of |completionSets|, waits for
    1.14 +// a response and then moves to the next element.
    1.15 +// Each element of |completionSets| is an array of completions, and each
    1.16 +// completion is an object with the properties:
    1.17 +//   hash: complete hash for the completion. Automatically right-padded
    1.18 +//         to be COMPLETE_LENGTH.
    1.19 +//   expectCompletion: boolean indicating whether the server should respond
    1.20 +//                     with a full hash.
    1.21 +//   table: name of the table that the hash corresponds to. Only needs to be set
    1.22 +//          if a completion is expected.
    1.23 +//   chunkId: positive integer corresponding to the chunk that the hash belongs
    1.24 +//            to. Only needs to be set if a completion is expected.
    1.25 +//   multipleCompletions: boolean indicating whether the server should respond
    1.26 +//                        with more than one full hash. If this is set to true
    1.27 +//                        then |expectCompletion| must also be set to true and
    1.28 +//                        |hash| must have the same prefix as all |completions|.
    1.29 +//   completions: an array of completions (objects with a hash, table and
    1.30 +//                chunkId property as described above). This property is only
    1.31 +//                used when |multipleCompletions| is set to true.
    1.32 +
    1.33 +// Basic prefixes with 2/3 completions.
    1.34 +let basicCompletionSet = [
    1.35 +  {
    1.36 +    hash: "abcdefgh",
    1.37 +    expectCompletion: true,
    1.38 +    table: "test",
    1.39 +    chunkId: 1234,
    1.40 +  },
    1.41 +  {
    1.42 +    hash: "1234",
    1.43 +    expectCompletion: false,
    1.44 +  },
    1.45 +  {
    1.46 +    hash: "\u0000\u0000\u000012312",
    1.47 +    expectCompletion: true,
    1.48 +    table: "test",
    1.49 +    chunkId: 1234,
    1.50 +  }
    1.51 +];
    1.52 +
    1.53 +// 3 prefixes with 0 completions to test HashCompleter handling a 204 status.
    1.54 +let falseCompletionSet = [
    1.55 +  {
    1.56 +    hash: "1234",
    1.57 +    expectCompletion: false,
    1.58 +  },
    1.59 +  {
    1.60 +    hash: "",
    1.61 +    expectCompletion: false,
    1.62 +  },
    1.63 +  {
    1.64 +    hash: "abc",
    1.65 +    expectCompletion: false,
    1.66 +  }
    1.67 +];
    1.68 +
    1.69 +// The current implementation (as of Mar 2011) sometimes sends duplicate
    1.70 +// entries to HashCompleter and even expects responses for duplicated entries.
    1.71 +let dupedCompletionSet = [
    1.72 +  {
    1.73 +    hash: "1234",
    1.74 +    expectCompletion: true,
    1.75 +    table: "test",
    1.76 +    chunkId: 1,
    1.77 +  },
    1.78 +  {
    1.79 +    hash: "5678",
    1.80 +    expectCompletion: false,
    1.81 +    table: "test2",
    1.82 +    chunkId: 2,
    1.83 +  },
    1.84 +  {
    1.85 +    hash: "1234",
    1.86 +    expectCompletion: true,
    1.87 +    table: "test",
    1.88 +    chunkId: 1,
    1.89 +  },
    1.90 +  {
    1.91 +    hash: "5678",
    1.92 +    expectCompletion: false,
    1.93 +    table: "test2",
    1.94 +    chunkId: 2
    1.95 +  }
    1.96 +];
    1.97 +
    1.98 +// It is possible for a hash completion request to return with multiple
    1.99 +// completions, the HashCompleter should return all of these.
   1.100 +let multipleResponsesCompletionSet = [
   1.101 +  {
   1.102 +    hash: "1234",
   1.103 +    expectCompletion: true,
   1.104 +    multipleCompletions: true,
   1.105 +    completions: [
   1.106 +      {
   1.107 +        hash: "123456",
   1.108 +        table: "test1",
   1.109 +        chunkId: 3,
   1.110 +      },
   1.111 +      {
   1.112 +        hash: "123478",
   1.113 +        table: "test2",
   1.114 +        chunkId: 4,
   1.115 +      }
   1.116 +    ],
   1.117 +  }
   1.118 +];
   1.119 +
   1.120 +// The fifth completion set is added at runtime by addRandomCompletionSet.
   1.121 +// Each completion in the set only has one response and its purpose is to
   1.122 +// provide an easy way to test the HashCompleter handling an arbitrarily large
   1.123 +// completion set (determined by SIZE_OF_RANDOM_SET).
   1.124 +const SIZE_OF_RANDOM_SET = 16;
   1.125 +function addRandomCompletionSet() {
   1.126 +  let completionSet = [];
   1.127 +  let hashPrefixes = [];
   1.128 +
   1.129 +  let seed = Math.floor(Math.random() * Math.pow(2, 32));
   1.130 +  dump("Using seed of " + seed + " for random completion set.\n");
   1.131 +  let rand = new LFSRgenerator(seed);
   1.132 +
   1.133 +  for (let i = 0; i < SIZE_OF_RANDOM_SET; i++) {
   1.134 +    let completion = {};
   1.135 +
   1.136 +    // Generate a random 256 bit hash. First we get a random number and then
   1.137 +    // convert it to a string.
   1.138 +    let hash;
   1.139 +    let prefix;
   1.140 +    do {
   1.141 +      hash = "";
   1.142 +      let length = 1 + rand.nextNum(5);
   1.143 +      for (let i = 0; i < length; i++)
   1.144 +        hash += String.fromCharCode(rand.nextNum(8));
   1.145 +      prefix = hash.substring(0,4);
   1.146 +    } while (hashPrefixes.indexOf(prefix) != -1);
   1.147 +
   1.148 +    hashPrefixes.push(prefix);
   1.149 +    completion.hash = hash;
   1.150 +
   1.151 +    completion.expectCompletion = rand.nextNum(1) == 1;
   1.152 +    if (completion.expectCompletion) {
   1.153 +      // Generate a random alpha-numeric string of length at most 6 for the
   1.154 +      // table name.
   1.155 +      completion.table = (rand.nextNum(31)).toString(36);
   1.156 +
   1.157 +      completion.chunkId = rand.nextNum(16);
   1.158 +    }
   1.159 +
   1.160 +    completionSet.push(completion);
   1.161 +  }
   1.162 +
   1.163 +  completionSets.push(completionSet);
   1.164 +}
   1.165 +
   1.166 +let completionSets = [basicCompletionSet, falseCompletionSet,
   1.167 +                      dupedCompletionSet, multipleResponsesCompletionSet];
   1.168 +let currentCompletionSet = -1;
   1.169 +let finishedCompletions = 0;
   1.170 +
   1.171 +const SERVER_PORT = 8080;
   1.172 +const SERVER_PATH = "/hash-completer";
   1.173 +let server;
   1.174 +
   1.175 +// Completion hashes are automatically right-padded with null chars to have a
   1.176 +// length of COMPLETE_LENGTH.
   1.177 +// Taken from nsUrlClassifierDBService.h
   1.178 +const COMPLETE_LENGTH = 32;
   1.179 +
   1.180 +let completer = Cc["@mozilla.org/url-classifier/hashcompleter;1"].
   1.181 +                  getService(Ci.nsIUrlClassifierHashCompleter);
   1.182 +
   1.183 +function run_test() {
   1.184 +  addRandomCompletionSet();
   1.185 +
   1.186 +  // Fix up the completions before running the test.
   1.187 +  for each (let completionSet in completionSets) {
   1.188 +    for each (let completion in completionSet) {
   1.189 +      // Pad the right of each |hash| so that the length is COMPLETE_LENGTH.
   1.190 +      if (completion.multipleCompletions) {
   1.191 +        for each (let responseCompletion in completion.completions) {
   1.192 +          let numChars = COMPLETE_LENGTH - responseCompletion.hash.length;
   1.193 +          responseCompletion.hash += (new Array(numChars + 1)).join("\u0000");
   1.194 +        }
   1.195 +      }
   1.196 +      else {
   1.197 +        let numChars = COMPLETE_LENGTH - completion.hash.length;
   1.198 +        completion.hash += (new Array(numChars + 1)).join("\u0000");
   1.199 +      }
   1.200 +    }
   1.201 +  }
   1.202 +  do_test_pending();
   1.203 +
   1.204 +  server = new HttpServer();
   1.205 +  server.registerPathHandler(SERVER_PATH, hashCompleterServer);
   1.206 +
   1.207 +  const SERVER_PORT = 8080;
   1.208 +  server.start(SERVER_PORT);
   1.209 +
   1.210 +  completer.gethashUrl = "http://localhost:" + SERVER_PORT + SERVER_PATH;
   1.211 +
   1.212 +  runNextCompletion();
   1.213 +}
   1.214 +
   1.215 +function doneCompletionSet() {
   1.216 +  do_check_eq(finishedCompletions, completionSets[currentCompletionSet].length);
   1.217 +
   1.218 +  for each (let completion in completionSets[currentCompletionSet])
   1.219 +    do_check_true(completion._finished);
   1.220 +
   1.221 +  runNextCompletion();
   1.222 +}
   1.223 +
   1.224 +function runNextCompletion() {
   1.225 +  currentCompletionSet++;
   1.226 +  finishedCompletions = 0;
   1.227 +
   1.228 +  if (currentCompletionSet >= completionSets.length) {
   1.229 +    finish();
   1.230 +    return;
   1.231 +  }
   1.232 +
   1.233 +  dump("Now on completion set index " + currentCompletionSet + "\n");
   1.234 +  for each (let completion in completionSets[currentCompletionSet]) {
   1.235 +    completer.complete(completion.hash.substring(0,4),
   1.236 +                       (new callback(completion)));
   1.237 +  }
   1.238 +}
   1.239 +
   1.240 +function hashCompleterServer(aRequest, aResponse) {
   1.241 +  let stream = aRequest.bodyInputStream;
   1.242 +  let wrapperStream = Cc["@mozilla.org/binaryinputstream;1"].
   1.243 +                        createInstance(Ci.nsIBinaryInputStream);
   1.244 +  wrapperStream.setInputStream(stream);
   1.245 +
   1.246 +  let len = stream.available();
   1.247 +  let data = wrapperStream.readBytes(len);
   1.248 +
   1.249 +  // To avoid a response with duplicate hash completions, we keep track of all
   1.250 +  // completed hash prefixes so far.
   1.251 +  let completedHashes = [];
   1.252 +  let responseText = "";
   1.253 +
   1.254 +  function responseForCompletion(x) {
   1.255 +    return x.table + ":" + x.chunkId + ":" + x.hash.length + "\n" + x.hash;
   1.256 +  }
   1.257 +  for each (let completion in completionSets[currentCompletionSet]) {
   1.258 +    if (completion.expectCompletion &&
   1.259 +        (completedHashes.indexOf(completion.hash) == -1)) {
   1.260 +      completedHashes.push(completion.hash);
   1.261 +
   1.262 +      if (completion.multipleCompletions)
   1.263 +        responseText += completion.completions.map(responseForCompletion).join("");
   1.264 +      else
   1.265 +        responseText += responseForCompletion(completion);
   1.266 +    }
   1.267 +  }
   1.268 +
   1.269 +  // As per the spec, a server should response with a 204 if there are no
   1.270 +  // full-length hashes that match the prefixes.
   1.271 +  if (responseText)
   1.272 +    aResponse.write(responseText);
   1.273 +  else
   1.274 +    aResponse.setStatusLine(null, 204, null);
   1.275 +}
   1.276 +
   1.277 +
   1.278 +function callback(completion) {
   1.279 +  this._completion = completion;
   1.280 +}
   1.281 +callback.prototype = {
   1.282 +  completion: function completion(hash, table, chunkId, trusted) {
   1.283 +    do_check_true(this._completion.expectCompletion);
   1.284 +
   1.285 +    if (this._completion.multipleCompletions) {
   1.286 +      for each (let completion in this._completion.completions) {
   1.287 +        if (completion.hash == hash) {
   1.288 +          do_check_eq(JSON.stringify(hash), JSON.stringify(completion.hash));
   1.289 +          do_check_eq(table, completion.table);
   1.290 +          do_check_eq(chunkId, completion.chunkId);
   1.291 +
   1.292 +          completion._completed = true;
   1.293 +
   1.294 +          if (this._completion.completions.every(function(x) x._completed))
   1.295 +            this._completed = true;
   1.296 +
   1.297 +          break;
   1.298 +        }
   1.299 +      }
   1.300 +    }
   1.301 +    else {
   1.302 +      // Hashes are not actually strings and can contain arbitrary data.
   1.303 +      do_check_eq(JSON.stringify(hash), JSON.stringify(this._completion.hash));
   1.304 +      do_check_eq(table, this._completion.table);
   1.305 +      do_check_eq(chunkId, this._completion.chunkId);
   1.306 +
   1.307 +      this._completed = true;
   1.308 +    }
   1.309 +  },
   1.310 +
   1.311 +  completionFinished: function completionFinished(status) {
   1.312 +    do_check_eq(!!this._completion.expectCompletion, !!this._completed);
   1.313 +    this._completion._finished = true;
   1.314 +
   1.315 +    finishedCompletions++;
   1.316 +    if (finishedCompletions == completionSets[currentCompletionSet].length)
   1.317 +      doneCompletionSet();
   1.318 +  },
   1.319 +};
   1.320 +
   1.321 +function finish() {
   1.322 +  server.stop(function() {
   1.323 +    do_test_finished();
   1.324 +  });
   1.325 +}

mercurial