Thu, 22 Jan 2015 13:21:57 +0100
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 }