toolkit/components/places/tests/queries/test_querySerialization.js

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:b8b87fac0b04
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /**
8 * Tests Places query serialization. Associated bug is
9 * https://bugzilla.mozilla.org/show_bug.cgi?id=370197
10 *
11 * The simple idea behind this test is to try out different combinations of
12 * query switches and ensure that queries are the same before serialization
13 * as they are after de-serialization.
14 *
15 * In the code below, "switch" refers to a query option -- "option" in a broad
16 * sense, not nsINavHistoryQueryOptions specifically (which is why we refer to
17 * them as switches, not options). Both nsINavHistoryQuery and
18 * nsINavHistoryQueryOptions allow you to specify switches that affect query
19 * strings. nsINavHistoryQuery instances have attributes hasBeginTime,
20 * hasEndTime, hasSearchTerms, and so on. nsINavHistoryQueryOptions instances
21 * have attributes sortingMode, resultType, excludeItems, etc.
22 *
23 * Ideally we would like to test all 2^N subsets of switches, where N is the
24 * total number of switches; switches might interact in erroneous or other ways
25 * we do not expect. However, since N is large (21 at this time), that's
26 * impractical for a single test in a suite.
27 *
28 * Instead we choose all possible subsets of a certain, smaller size. In fact
29 * we begin by choosing CHOOSE_HOW_MANY_SWITCHES_LO and ramp up to
30 * CHOOSE_HOW_MANY_SWITCHES_HI.
31 *
32 * There are two more wrinkles. First, for some switches we'd like to be able to
33 * test multiple values. For example, it seems like a good idea to test both an
34 * empty string and a non-empty string for switch nsINavHistoryQuery.searchTerms.
35 * When switches have more than one value for a test run, we use the Cartesian
36 * product of their values to generate all possible combinations of values.
37 *
38 * Second, we need to also test serialization of multiple nsINavHistoryQuery
39 * objects at once. To do this, we remember the previous NUM_MULTIPLE_QUERIES
40 * queries we tested individually and then serialize them together. We do this
41 * each time we test an individual query. Thus the set of queries we test
42 * together loses one query and gains another each time.
43 *
44 * To summarize, here's how this test works:
45 *
46 * - For n = CHOOSE_HOW_MANY_SWITCHES_LO to CHOOSE_HOW_MANY_SWITCHES_HI:
47 * - From the total set of switches choose all possible subsets of size n.
48 * For each of those subsets s:
49 * - Collect the test runs of each switch in subset s and take their
50 * Cartesian product. For each sequence in the product:
51 * - Create nsINavHistoryQuery and nsINavHistoryQueryOptions objects
52 * with the chosen switches and test run values.
53 * - Serialize the query.
54 * - De-serialize and ensure that the de-serialized query objects equal
55 * the originals.
56 * - For each of the previous NUM_MULTIPLE_QUERIES
57 * nsINavHistoryQueryOptions objects o we created:
58 * - Serialize the previous NUM_MULTIPLE_QUERIES nsINavHistoryQuery
59 * objects together with o.
60 * - De-serialize and ensure that the de-serialized query objects
61 * equal the originals.
62 */
63
64 const CHOOSE_HOW_MANY_SWITCHES_LO = 1;
65 const CHOOSE_HOW_MANY_SWITCHES_HI = 2;
66
67 const NUM_MULTIPLE_QUERIES = 2;
68
69 // The switches are represented by objects below, in arrays querySwitches and
70 // queryOptionSwitches. Use them to set up test runs.
71 //
72 // Some switches have special properties (where noted), but all switches must
73 // have the following properties:
74 //
75 // matches: A function that takes two nsINavHistoryQuery objects (in the case
76 // of nsINavHistoryQuery switches) or two nsINavHistoryQueryOptions
77 // objects (for nsINavHistoryQueryOptions switches) and returns true
78 // if the values of the switch in the two objects are equal. This is
79 // the foundation of how we determine if two queries are equal.
80 // runs: An array of functions. Each function takes an nsINavHistoryQuery
81 // object and an nsINavHistoryQueryOptions object. The functions
82 // should set the attributes of one of the two objects as appropriate
83 // to their switches. This is how switch values are set for each test
84 // run.
85 //
86 // The following properties are optional:
87 //
88 // desc: An informational string to print out during runs when the switch
89 // is chosen. Hopefully helpful if the test fails.
90
91 // nsINavHistoryQuery switches
92 const querySwitches = [
93 // hasBeginTime
94 {
95 // flag and subswitches are used by the flagSwitchMatches function. Several
96 // of the nsINavHistoryQuery switches (like this one) are really guard flags
97 // that indicate if other "subswitches" are enabled.
98 flag: "hasBeginTime",
99 subswitches: ["beginTime", "beginTimeReference", "absoluteBeginTime"],
100 desc: "nsINavHistoryQuery.hasBeginTime",
101 matches: flagSwitchMatches,
102 runs: [
103 function (aQuery, aQueryOptions) {
104 aQuery.beginTime = Date.now() * 1000;
105 aQuery.beginTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_EPOCH;
106 },
107 function (aQuery, aQueryOptions) {
108 aQuery.beginTime = Date.now() * 1000;
109 aQuery.beginTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_TODAY;
110 }
111 ]
112 },
113 // hasEndTime
114 {
115 flag: "hasEndTime",
116 subswitches: ["endTime", "endTimeReference", "absoluteEndTime"],
117 desc: "nsINavHistoryQuery.hasEndTime",
118 matches: flagSwitchMatches,
119 runs: [
120 function (aQuery, aQueryOptions) {
121 aQuery.endTime = Date.now() * 1000;
122 aQuery.endTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_EPOCH;
123 },
124 function (aQuery, aQueryOptions) {
125 aQuery.endTime = Date.now() * 1000;
126 aQuery.endTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_TODAY;
127 }
128 ]
129 },
130 // hasSearchTerms
131 {
132 flag: "hasSearchTerms",
133 subswitches: ["searchTerms"],
134 desc: "nsINavHistoryQuery.hasSearchTerms",
135 matches: flagSwitchMatches,
136 runs: [
137 function (aQuery, aQueryOptions) {
138 aQuery.searchTerms = "shrimp and white wine";
139 },
140 function (aQuery, aQueryOptions) {
141 aQuery.searchTerms = "";
142 }
143 ]
144 },
145 // hasDomain
146 {
147 flag: "hasDomain",
148 subswitches: ["domain", "domainIsHost"],
149 desc: "nsINavHistoryQuery.hasDomain",
150 matches: flagSwitchMatches,
151 runs: [
152 function (aQuery, aQueryOptions) {
153 aQuery.domain = "mozilla.com";
154 aQuery.domainIsHost = false;
155 },
156 function (aQuery, aQueryOptions) {
157 aQuery.domain = "www.mozilla.com";
158 aQuery.domainIsHost = true;
159 },
160 function (aQuery, aQueryOptions) {
161 aQuery.domain = "";
162 }
163 ]
164 },
165 // hasUri
166 {
167 flag: "hasUri",
168 subswitches: ["uri", "uriIsPrefix"],
169 desc: "nsINavHistoryQuery.hasUri",
170 matches: flagSwitchMatches,
171 runs: [
172 function (aQuery, aQueryOptions) {
173 aQuery.uri = uri("http://mozilla.com");
174 aQuery.uriIsPrefix = false;
175 },
176 function (aQuery, aQueryOptions) {
177 aQuery.uri = uri("http://mozilla.com");
178 aQuery.uriIsPrefix = true;
179 }
180 ]
181 },
182 // hasAnnotation
183 {
184 flag: "hasAnnotation",
185 subswitches: ["annotation", "annotationIsNot"],
186 desc: "nsINavHistoryQuery.hasAnnotation",
187 matches: flagSwitchMatches,
188 runs: [
189 function (aQuery, aQueryOptions) {
190 aQuery.annotation = "bookmarks/toolbarFolder";
191 aQuery.annotationIsNot = false;
192 },
193 function (aQuery, aQueryOptions) {
194 aQuery.annotation = "bookmarks/toolbarFolder";
195 aQuery.annotationIsNot = true;
196 }
197 ]
198 },
199 // minVisits
200 {
201 // property is used by function simplePropertyMatches.
202 property: "minVisits",
203 desc: "nsINavHistoryQuery.minVisits",
204 matches: simplePropertyMatches,
205 runs: [
206 function (aQuery, aQueryOptions) {
207 aQuery.minVisits = 0x7fffffff; // 2^31 - 1
208 }
209 ]
210 },
211 // maxVisits
212 {
213 property: "maxVisits",
214 desc: "nsINavHistoryQuery.maxVisits",
215 matches: simplePropertyMatches,
216 runs: [
217 function (aQuery, aQueryOptions) {
218 aQuery.maxVisits = 0x7fffffff; // 2^31 - 1
219 }
220 ]
221 },
222 // onlyBookmarked
223 {
224 property: "onlyBookmarked",
225 desc: "nsINavHistoryQuery.onlyBookmarked",
226 matches: simplePropertyMatches,
227 runs: [
228 function (aQuery, aQueryOptions) {
229 aQuery.onlyBookmarked = true;
230 }
231 ]
232 },
233 // getFolders
234 {
235 desc: "nsINavHistoryQuery.getFolders",
236 matches: function (aQuery1, aQuery2) {
237 var q1Folders = aQuery1.getFolders();
238 var q2Folders = aQuery2.getFolders();
239 if (q1Folders.length !== q2Folders.length)
240 return false;
241 for (let i = 0; i < q1Folders.length; i++) {
242 if (q2Folders.indexOf(q1Folders[i]) < 0)
243 return false;
244 }
245 for (let i = 0; i < q2Folders.length; i++) {
246 if (q1Folders.indexOf(q2Folders[i]) < 0)
247 return false;
248 }
249 return true;
250 },
251 runs: [
252 function (aQuery, aQueryOptions) {
253 aQuery.setFolders([], 0);
254 },
255 function (aQuery, aQueryOptions) {
256 aQuery.setFolders([PlacesUtils.placesRootId], 1);
257 },
258 function (aQuery, aQueryOptions) {
259 aQuery.setFolders([PlacesUtils.placesRootId, PlacesUtils.tagsFolderId], 2);
260 }
261 ]
262 },
263 // tags
264 {
265 desc: "nsINavHistoryQuery.getTags",
266 matches: function (aQuery1, aQuery2) {
267 if (aQuery1.tagsAreNot !== aQuery2.tagsAreNot)
268 return false;
269 var q1Tags = aQuery1.tags;
270 var q2Tags = aQuery2.tags;
271 if (q1Tags.length !== q2Tags.length)
272 return false;
273 for (let i = 0; i < q1Tags.length; i++) {
274 if (q2Tags.indexOf(q1Tags[i]) < 0)
275 return false;
276 }
277 for (let i = 0; i < q2Tags.length; i++) {
278 if (q1Tags.indexOf(q2Tags[i]) < 0)
279 return false;
280 }
281 return true;
282 },
283 runs: [
284 function (aQuery, aQueryOptions) {
285 aQuery.tags = [];
286 },
287 function (aQuery, aQueryOptions) {
288 aQuery.tags = [""];
289 },
290 function (aQuery, aQueryOptions) {
291 aQuery.tags = [
292 "foo",
293 "七難",
294 "",
295 "いっぱいおっぱい",
296 "Abracadabra",
297 "123",
298 "Here's a pretty long tag name with some = signs and 1 2 3s and spaces oh jeez will it work I hope so!",
299 "アスキーでございません",
300 "あいうえお",
301 ];
302 },
303 function (aQuery, aQueryOptions) {
304 aQuery.tags = [
305 "foo",
306 "七難",
307 "",
308 "いっぱいおっぱい",
309 "Abracadabra",
310 "123",
311 "Here's a pretty long tag name with some = signs and 1 2 3s and spaces oh jeez will it work I hope so!",
312 "アスキーでございません",
313 "あいうえお",
314 ];
315 aQuery.tagsAreNot = true;
316 }
317 ]
318 },
319 // transitions
320 {
321 desc: "tests nsINavHistoryQuery.getTransitions",
322 matches: function (aQuery1, aQuery2) {
323 var q1Trans = aQuery1.getTransitions();
324 var q2Trans = aQuery2.getTransitions();
325 if (q1Trans.length !== q2Trans.length)
326 return false;
327 for (let i = 0; i < q1Trans.length; i++) {
328 if (q2Trans.indexOf(q1Trans[i]) < 0)
329 return false;
330 }
331 for (let i = 0; i < q2Trans.length; i++) {
332 if (q1Trans.indexOf(q2Trans[i]) < 0)
333 return false;
334 }
335 return true;
336 },
337 runs: [
338 function (aQuery, aQueryOptions) {
339 aQuery.setTransitions([], 0);
340 },
341 function (aQuery, aQueryOptions) {
342 aQuery.setTransitions([Ci.nsINavHistoryService.TRANSITION_DOWNLOAD],
343 1);
344 },
345 function (aQuery, aQueryOptions) {
346 aQuery.setTransitions([Ci.nsINavHistoryService.TRANSITION_TYPED,
347 Ci.nsINavHistoryService.TRANSITION_BOOKMARK], 2);
348 }
349 ]
350 },
351 ];
352
353 // nsINavHistoryQueryOptions switches
354 const queryOptionSwitches = [
355 // sortingMode
356 {
357 desc: "nsINavHistoryQueryOptions.sortingMode",
358 matches: function (aOptions1, aOptions2) {
359 if (aOptions1.sortingMode === aOptions2.sortingMode) {
360 switch (aOptions1.sortingMode) {
361 case aOptions1.SORT_BY_ANNOTATION_ASCENDING:
362 case aOptions1.SORT_BY_ANNOTATION_DESCENDING:
363 return aOptions1.sortingAnnotation === aOptions2.sortingAnnotation;
364 break;
365 }
366 return true;
367 }
368 return false;
369 },
370 runs: [
371 function (aQuery, aQueryOptions) {
372 aQueryOptions.sortingMode = aQueryOptions.SORT_BY_DATE_ASCENDING;
373 },
374 function (aQuery, aQueryOptions) {
375 aQueryOptions.sortingMode = aQueryOptions.SORT_BY_ANNOTATION_ASCENDING;
376 aQueryOptions.sortingAnnotation = "bookmarks/toolbarFolder";
377 }
378 ]
379 },
380 // resultType
381 {
382 // property is used by function simplePropertyMatches.
383 property: "resultType",
384 desc: "nsINavHistoryQueryOptions.resultType",
385 matches: simplePropertyMatches,
386 runs: [
387 function (aQuery, aQueryOptions) {
388 aQueryOptions.resultType = aQueryOptions.RESULTS_AS_URI;
389 },
390 function (aQuery, aQueryOptions) {
391 aQueryOptions.resultType = aQueryOptions.RESULTS_AS_FULL_VISIT;
392 }
393 ]
394 },
395 // excludeItems
396 {
397 property: "excludeItems",
398 desc: "nsINavHistoryQueryOptions.excludeItems",
399 matches: simplePropertyMatches,
400 runs: [
401 function (aQuery, aQueryOptions) {
402 aQueryOptions.excludeItems = true;
403 }
404 ]
405 },
406 // excludeQueries
407 {
408 property: "excludeQueries",
409 desc: "nsINavHistoryQueryOptions.excludeQueries",
410 matches: simplePropertyMatches,
411 runs: [
412 function (aQuery, aQueryOptions) {
413 aQueryOptions.excludeQueries = true;
414 }
415 ]
416 },
417 // excludeReadOnlyFolders
418 {
419 property: "excludeReadOnlyFolders",
420 desc: "nsINavHistoryQueryOptions.excludeReadOnlyFolders",
421 matches: simplePropertyMatches,
422 runs: [
423 function (aQuery, aQueryOptions) {
424 aQueryOptions.excludeReadOnlyFolders = true;
425 }
426 ]
427 },
428 // expandQueries
429 {
430 property: "expandQueries",
431 desc: "nsINavHistoryQueryOptions.expandQueries",
432 matches: simplePropertyMatches,
433 runs: [
434 function (aQuery, aQueryOptions) {
435 aQueryOptions.expandQueries = true;
436 }
437 ]
438 },
439 // includeHidden
440 {
441 property: "includeHidden",
442 desc: "nsINavHistoryQueryOptions.includeHidden",
443 matches: simplePropertyMatches,
444 runs: [
445 function (aQuery, aQueryOptions) {
446 aQueryOptions.includeHidden = true;
447 }
448 ]
449 },
450 // maxResults
451 {
452 property: "maxResults",
453 desc: "nsINavHistoryQueryOptions.maxResults",
454 matches: simplePropertyMatches,
455 runs: [
456 function (aQuery, aQueryOptions) {
457 aQueryOptions.maxResults = 0xffffffff; // 2^32 - 1
458 }
459 ]
460 },
461 // queryType
462 {
463 property: "queryType",
464 desc: "nsINavHistoryQueryOptions.queryType",
465 matches: simplePropertyMatches,
466 runs: [
467 function (aQuery, aQueryOptions) {
468 aQueryOptions.queryType = aQueryOptions.QUERY_TYPE_HISTORY;
469 },
470 function (aQuery, aQueryOptions) {
471 aQueryOptions.queryType = aQueryOptions.QUERY_TYPE_UNIFIED;
472 }
473 ]
474 },
475 ];
476
477 ///////////////////////////////////////////////////////////////////////////////
478
479 /**
480 * Enumerates all the sequences of the cartesian product of the arrays contained
481 * in aSequences. Examples:
482 *
483 * cartProd([[1, 2, 3], ["a", "b"]], callback);
484 * // callback is called 3 * 2 = 6 times with the following arrays:
485 * // [1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]
486 *
487 * cartProd([["a"], [1, 2, 3], ["X", "Y"]], callback);
488 * // callback is called 1 * 3 * 2 = 6 times with the following arrays:
489 * // ["a", 1, "X"], ["a", 1, "Y"], ["a", 2, "X"], ["a", 2, "Y"],
490 * // ["a", 3, "X"], ["a", 3, "Y"]
491 *
492 * cartProd([[1], [2], [3], [4]], callback);
493 * // callback is called 1 * 1 * 1 * 1 = 1 time with the following array:
494 * // [1, 2, 3, 4]
495 *
496 * cartProd([], callback);
497 * // callback is 0 times
498 *
499 * cartProd([[1, 2, 3, 4]], callback);
500 * // callback is called 4 times with the following arrays:
501 * // [1], [2], [3], [4]
502 *
503 * @param aSequences
504 * an array that contains an arbitrary number of arrays
505 * @param aCallback
506 * a function that is passed each sequence of the product as it's
507 * computed
508 * @return the total number of sequences in the product
509 */
510 function cartProd(aSequences, aCallback)
511 {
512 if (aSequences.length === 0)
513 return 0;
514
515 // For each sequence in aSequences, we maintain a pointer (an array index,
516 // really) to the element we're currently enumerating in that sequence
517 var seqEltPtrs = aSequences.map(function (i) 0);
518
519 var numProds = 0;
520 var done = false;
521 while (!done) {
522 numProds++;
523
524 // prod = sequence in product we're currently enumerating
525 let prod = [];
526 for (let i = 0; i < aSequences.length; i++) {
527 prod.push(aSequences[i][seqEltPtrs[i]]);
528 }
529 aCallback(prod);
530
531 // The next sequence in the product differs from the current one by just a
532 // single element. Determine which element that is. We advance the
533 // "rightmost" element pointer to the "right" by one. If we move past the
534 // end of that pointer's sequence, reset the pointer to the first element
535 // in its sequence and then try the sequence to the "left", and so on.
536
537 // seqPtr = index of rightmost input sequence whose element pointer is not
538 // past the end of the sequence
539 let seqPtr = aSequences.length - 1;
540 while (!done) {
541 // Advance the rightmost element pointer.
542 seqEltPtrs[seqPtr]++;
543
544 // The rightmost element pointer is past the end of its sequence.
545 if (seqEltPtrs[seqPtr] >= aSequences[seqPtr].length) {
546 seqEltPtrs[seqPtr] = 0;
547 seqPtr--;
548
549 // All element pointers are past the ends of their sequences.
550 if (seqPtr < 0)
551 done = true;
552 }
553 else break;
554 }
555 }
556 return numProds;
557 }
558
559 /**
560 * Enumerates all the subsets in aSet of size aHowMany. There are
561 * C(aSet.length, aHowMany) such subsets. aCallback will be passed each subset
562 * as it is generated. Note that aSet and the subsets enumerated are -- even
563 * though they're arrays -- not sequences; the ordering of their elements is not
564 * important. Example:
565 *
566 * choose([1, 2, 3, 4], 2, callback);
567 * // callback is called C(4, 2) = 6 times with the following sets (arrays):
568 * // [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]
569 *
570 * @param aSet
571 * an array from which to choose elements, aSet.length > 0
572 * @param aHowMany
573 * the number of elements to choose, > 0 and <= aSet.length
574 * @return the total number of sets chosen
575 */
576 function choose(aSet, aHowMany, aCallback)
577 {
578 // ptrs = indices of the elements in aSet we're currently choosing
579 var ptrs = [];
580 for (let i = 0; i < aHowMany; i++) {
581 ptrs.push(i);
582 }
583
584 var numFound = 0;
585 var done = false;
586 while (!done) {
587 numFound++;
588 aCallback(ptrs.map(function (p) aSet[p]));
589
590 // The next subset to be chosen differs from the current one by just a
591 // single element. Determine which element that is. Advance the "rightmost"
592 // pointer to the "right" by one. If we move past the end of set, move the
593 // next non-adjacent rightmost pointer to the right by one, and reset all
594 // succeeding pointers so that they're adjacent to it. When all pointers
595 // are clustered all the way to the right, we're done.
596
597 // Advance the rightmost pointer.
598 ptrs[ptrs.length - 1]++;
599
600 // The rightmost pointer has gone past the end of set.
601 if (ptrs[ptrs.length - 1] >= aSet.length) {
602 // Find the next rightmost pointer that is not adjacent to the current one.
603 let si = aSet.length - 2; // aSet index
604 let pi = ptrs.length - 2; // ptrs index
605 while (pi >= 0 && ptrs[pi] === si) {
606 pi--;
607 si--;
608 }
609
610 // All pointers are adjacent and clustered all the way to the right.
611 if (pi < 0)
612 done = true;
613 else {
614 // pi = index of rightmost pointer with a gap between it and its
615 // succeeding pointer. Move it right and reset all succeeding pointers
616 // so that they're adjacent to it.
617 ptrs[pi]++;
618 for (let i = 0; i < ptrs.length - pi - 1; i++) {
619 ptrs[i + pi + 1] = ptrs[pi] + i + 1;
620 }
621 }
622 }
623 }
624 return numFound;
625 }
626
627 /**
628 * Convenience function for nsINavHistoryQuery switches that act as flags. This
629 * is attached to switch objects. See querySwitches array above.
630 *
631 * @param aQuery1
632 * an nsINavHistoryQuery object
633 * @param aQuery2
634 * another nsINavHistoryQuery object
635 * @return true if this switch is the same in both aQuery1 and aQuery2
636 */
637 function flagSwitchMatches(aQuery1, aQuery2)
638 {
639 if (aQuery1[this.flag] && aQuery2[this.flag]) {
640 for (let p in this.subswitches) {
641 if (p in aQuery1 && p in aQuery2) {
642 if (aQuery1[p] instanceof Ci.nsIURI) {
643 if (!aQuery1[p].equals(aQuery2[p]))
644 return false;
645 }
646 else if (aQuery1[p] !== aQuery2[p])
647 return false;
648 }
649 }
650 }
651 else if (aQuery1[this.flag] || aQuery2[this.flag])
652 return false;
653
654 return true;
655 }
656
657 /**
658 * Tests if aObj1 and aObj2 are equal. This function is general and may be used
659 * for either nsINavHistoryQuery or nsINavHistoryQueryOptions objects. aSwitches
660 * determines which set of switches is used for comparison. Pass in either
661 * querySwitches or queryOptionSwitches.
662 *
663 * @param aSwitches
664 * determines which set of switches applies to aObj1 and aObj2, either
665 * querySwitches or queryOptionSwitches
666 * @param aObj1
667 * an nsINavHistoryQuery or nsINavHistoryQueryOptions object
668 * @param aObj2
669 * another nsINavHistoryQuery or nsINavHistoryQueryOptions object
670 * @return true if aObj1 and aObj2 are equal
671 */
672 function queryObjsEqual(aSwitches, aObj1, aObj2)
673 {
674 for (let i = 0; i < aSwitches.length; i++) {
675 if (!aSwitches[i].matches(aObj1, aObj2))
676 return false;
677 }
678 return true;
679 }
680
681 /**
682 * This drives the test runs. See the comment at the top of this file.
683 *
684 * @param aHowManyLo
685 * the size of the switch subsets to start with
686 * @param aHowManyHi
687 * the size of the switch subsets to end with (inclusive)
688 */
689 function runQuerySequences(aHowManyLo, aHowManyHi)
690 {
691 var allSwitches = querySwitches.concat(queryOptionSwitches);
692 var prevQueries = [];
693 var prevOpts = [];
694
695 // Choose aHowManyLo switches up to aHowManyHi switches.
696 for (let howMany = aHowManyLo; howMany <= aHowManyHi; howMany++) {
697 let numIters = 0;
698 print("CHOOSING " + howMany + " SWITCHES");
699
700 // Choose all subsets of size howMany from allSwitches.
701 choose(allSwitches, howMany, function (chosenSwitches) {
702 print(numIters);
703 numIters++;
704
705 // Collect the runs.
706 // runs = [ [runs from switch 1], ..., [runs from switch howMany] ]
707 var runs = chosenSwitches.map(function (s) {
708 if (s.desc)
709 print(" " + s.desc);
710 return s.runs;
711 });
712
713 // cartProd(runs) => [
714 // [switch 1 run 1, switch 2 run 1, ..., switch howMany run 1 ],
715 // ...,
716 // [switch 1 run 1, switch 2 run 1, ..., switch howMany run N ],
717 // ..., ...,
718 // [switch 1 run N, switch 2 run N, ..., switch howMany run 1 ],
719 // ...,
720 // [switch 1 run N, switch 2 run N, ..., switch howMany run N ],
721 // ]
722 cartProd(runs, function (runSet) {
723 // Create a new query, apply the switches in runSet, and test it.
724 var query = PlacesUtils.history.getNewQuery();
725 var opts = PlacesUtils.history.getNewQueryOptions();
726 for (let i = 0; i < runSet.length; i++) {
727 runSet[i](query, opts);
728 }
729 serializeDeserialize([query], opts);
730
731 // Test the previous NUM_MULTIPLE_QUERIES queries together.
732 prevQueries.push(query);
733 prevOpts.push(opts);
734 if (prevQueries.length >= NUM_MULTIPLE_QUERIES) {
735 // We can serialize multiple nsINavHistoryQuery objects together but
736 // only one nsINavHistoryQueryOptions object with them. So, test each
737 // of the previous NUM_MULTIPLE_QUERIES nsINavHistoryQueryOptions.
738 for (let i = 0; i < prevOpts.length; i++) {
739 serializeDeserialize(prevQueries, prevOpts[i]);
740 }
741 prevQueries.shift();
742 prevOpts.shift();
743 }
744 });
745 });
746 }
747 print("\n");
748 }
749
750 /**
751 * Serializes the nsINavHistoryQuery objects in aQueryArr and the
752 * nsINavHistoryQueryOptions object aQueryOptions, de-serializes the
753 * serialization, and ensures (using do_check_* functions) that the
754 * de-serialized objects equal the originals.
755 *
756 * @param aQueryArr
757 * an array containing nsINavHistoryQuery objects
758 * @param aQueryOptions
759 * an nsINavHistoryQueryOptions object
760 */
761 function serializeDeserialize(aQueryArr, aQueryOptions)
762 {
763 var queryStr = PlacesUtils.history.queriesToQueryString(aQueryArr,
764 aQueryArr.length,
765 aQueryOptions);
766 print(" " + queryStr);
767 var queryArr2 = {};
768 var opts2 = {};
769 PlacesUtils.history.queryStringToQueries(queryStr, queryArr2, {}, opts2);
770 queryArr2 = queryArr2.value;
771 opts2 = opts2.value;
772
773 // The two sets of queries cannot be the same if their lengths differ.
774 do_check_eq(aQueryArr.length, queryArr2.length);
775
776 // Although the query serialization code as it is written now practically
777 // ensures that queries appear in the query string in the same order they
778 // appear in both the array to be serialized and the array resulting from
779 // de-serialization, the interface does not guarantee any ordering. So, for
780 // each query in aQueryArr, find its equivalent in queryArr2 and delete it
781 // from queryArr2. If queryArr2 is empty after looping through aQueryArr,
782 // the two sets of queries are equal.
783 for (let i = 0; i < aQueryArr.length; i++) {
784 let j = 0;
785 for (; j < queryArr2.length; j++) {
786 if (queryObjsEqual(querySwitches, aQueryArr[i], queryArr2[j]))
787 break;
788 }
789 if (j < queryArr2.length)
790 queryArr2.splice(j, 1);
791 }
792 do_check_eq(queryArr2.length, 0);
793
794 // Finally check the query options objects.
795 do_check_true(queryObjsEqual(queryOptionSwitches, aQueryOptions, opts2));
796 }
797
798 /**
799 * Convenience function for switches that have simple values. This is attached
800 * to switch objects. See querySwitches and queryOptionSwitches arrays above.
801 *
802 * @param aObj1
803 * an nsINavHistoryQuery or nsINavHistoryQueryOptions object
804 * @param aObj2
805 * another nsINavHistoryQuery or nsINavHistoryQueryOptions object
806 * @return true if this switch is the same in both aObj1 and aObj2
807 */
808 function simplePropertyMatches(aObj1, aObj2)
809 {
810 return aObj1[this.property] === aObj2[this.property];
811 }
812
813 ///////////////////////////////////////////////////////////////////////////////
814
815 function run_test()
816 {
817 runQuerySequences(CHOOSE_HOW_MANY_SWITCHES_LO, CHOOSE_HOW_MANY_SWITCHES_HI);
818 }

mercurial