|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
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. |
|
29 |
|
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 ]; |
|
49 |
|
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 ]; |
|
65 |
|
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 ]; |
|
94 |
|
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 ]; |
|
116 |
|
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 = []; |
|
125 |
|
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); |
|
129 |
|
130 for (let i = 0; i < SIZE_OF_RANDOM_SET; i++) { |
|
131 let completion = {}; |
|
132 |
|
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); |
|
144 |
|
145 hashPrefixes.push(prefix); |
|
146 completion.hash = hash; |
|
147 |
|
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); |
|
153 |
|
154 completion.chunkId = rand.nextNum(16); |
|
155 } |
|
156 |
|
157 completionSet.push(completion); |
|
158 } |
|
159 |
|
160 completionSets.push(completionSet); |
|
161 } |
|
162 |
|
163 let completionSets = [basicCompletionSet, falseCompletionSet, |
|
164 dupedCompletionSet, multipleResponsesCompletionSet]; |
|
165 let currentCompletionSet = -1; |
|
166 let finishedCompletions = 0; |
|
167 |
|
168 const SERVER_PORT = 8080; |
|
169 const SERVER_PATH = "/hash-completer"; |
|
170 let server; |
|
171 |
|
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; |
|
176 |
|
177 let completer = Cc["@mozilla.org/url-classifier/hashcompleter;1"]. |
|
178 getService(Ci.nsIUrlClassifierHashCompleter); |
|
179 |
|
180 function run_test() { |
|
181 addRandomCompletionSet(); |
|
182 |
|
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(); |
|
200 |
|
201 server = new HttpServer(); |
|
202 server.registerPathHandler(SERVER_PATH, hashCompleterServer); |
|
203 |
|
204 const SERVER_PORT = 8080; |
|
205 server.start(SERVER_PORT); |
|
206 |
|
207 completer.gethashUrl = "http://localhost:" + SERVER_PORT + SERVER_PATH; |
|
208 |
|
209 runNextCompletion(); |
|
210 } |
|
211 |
|
212 function doneCompletionSet() { |
|
213 do_check_eq(finishedCompletions, completionSets[currentCompletionSet].length); |
|
214 |
|
215 for each (let completion in completionSets[currentCompletionSet]) |
|
216 do_check_true(completion._finished); |
|
217 |
|
218 runNextCompletion(); |
|
219 } |
|
220 |
|
221 function runNextCompletion() { |
|
222 currentCompletionSet++; |
|
223 finishedCompletions = 0; |
|
224 |
|
225 if (currentCompletionSet >= completionSets.length) { |
|
226 finish(); |
|
227 return; |
|
228 } |
|
229 |
|
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 } |
|
236 |
|
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); |
|
242 |
|
243 let len = stream.available(); |
|
244 let data = wrapperStream.readBytes(len); |
|
245 |
|
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 = ""; |
|
250 |
|
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); |
|
258 |
|
259 if (completion.multipleCompletions) |
|
260 responseText += completion.completions.map(responseForCompletion).join(""); |
|
261 else |
|
262 responseText += responseForCompletion(completion); |
|
263 } |
|
264 } |
|
265 |
|
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 } |
|
273 |
|
274 |
|
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); |
|
281 |
|
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); |
|
288 |
|
289 completion._completed = true; |
|
290 |
|
291 if (this._completion.completions.every(function(x) x._completed)) |
|
292 this._completed = true; |
|
293 |
|
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); |
|
303 |
|
304 this._completed = true; |
|
305 } |
|
306 }, |
|
307 |
|
308 completionFinished: function completionFinished(status) { |
|
309 do_check_eq(!!this._completion.expectCompletion, !!this._completed); |
|
310 this._completion._finished = true; |
|
311 |
|
312 finishedCompletions++; |
|
313 if (finishedCompletions == completionSets[currentCompletionSet].length) |
|
314 doneCompletionSet(); |
|
315 }, |
|
316 }; |
|
317 |
|
318 function finish() { |
|
319 server.stop(function() { |
|
320 do_test_finished(); |
|
321 }); |
|
322 } |