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

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

mercurial