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 +}