1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/places/tests/queries/test_querySerialization.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,818 @@ 1.4 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim:set ts=2 sw=2 sts=2 et: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +/** 1.11 + * Tests Places query serialization. Associated bug is 1.12 + * https://bugzilla.mozilla.org/show_bug.cgi?id=370197 1.13 + * 1.14 + * The simple idea behind this test is to try out different combinations of 1.15 + * query switches and ensure that queries are the same before serialization 1.16 + * as they are after de-serialization. 1.17 + * 1.18 + * In the code below, "switch" refers to a query option -- "option" in a broad 1.19 + * sense, not nsINavHistoryQueryOptions specifically (which is why we refer to 1.20 + * them as switches, not options). Both nsINavHistoryQuery and 1.21 + * nsINavHistoryQueryOptions allow you to specify switches that affect query 1.22 + * strings. nsINavHistoryQuery instances have attributes hasBeginTime, 1.23 + * hasEndTime, hasSearchTerms, and so on. nsINavHistoryQueryOptions instances 1.24 + * have attributes sortingMode, resultType, excludeItems, etc. 1.25 + * 1.26 + * Ideally we would like to test all 2^N subsets of switches, where N is the 1.27 + * total number of switches; switches might interact in erroneous or other ways 1.28 + * we do not expect. However, since N is large (21 at this time), that's 1.29 + * impractical for a single test in a suite. 1.30 + * 1.31 + * Instead we choose all possible subsets of a certain, smaller size. In fact 1.32 + * we begin by choosing CHOOSE_HOW_MANY_SWITCHES_LO and ramp up to 1.33 + * CHOOSE_HOW_MANY_SWITCHES_HI. 1.34 + * 1.35 + * There are two more wrinkles. First, for some switches we'd like to be able to 1.36 + * test multiple values. For example, it seems like a good idea to test both an 1.37 + * empty string and a non-empty string for switch nsINavHistoryQuery.searchTerms. 1.38 + * When switches have more than one value for a test run, we use the Cartesian 1.39 + * product of their values to generate all possible combinations of values. 1.40 + * 1.41 + * Second, we need to also test serialization of multiple nsINavHistoryQuery 1.42 + * objects at once. To do this, we remember the previous NUM_MULTIPLE_QUERIES 1.43 + * queries we tested individually and then serialize them together. We do this 1.44 + * each time we test an individual query. Thus the set of queries we test 1.45 + * together loses one query and gains another each time. 1.46 + * 1.47 + * To summarize, here's how this test works: 1.48 + * 1.49 + * - For n = CHOOSE_HOW_MANY_SWITCHES_LO to CHOOSE_HOW_MANY_SWITCHES_HI: 1.50 + * - From the total set of switches choose all possible subsets of size n. 1.51 + * For each of those subsets s: 1.52 + * - Collect the test runs of each switch in subset s and take their 1.53 + * Cartesian product. For each sequence in the product: 1.54 + * - Create nsINavHistoryQuery and nsINavHistoryQueryOptions objects 1.55 + * with the chosen switches and test run values. 1.56 + * - Serialize the query. 1.57 + * - De-serialize and ensure that the de-serialized query objects equal 1.58 + * the originals. 1.59 + * - For each of the previous NUM_MULTIPLE_QUERIES 1.60 + * nsINavHistoryQueryOptions objects o we created: 1.61 + * - Serialize the previous NUM_MULTIPLE_QUERIES nsINavHistoryQuery 1.62 + * objects together with o. 1.63 + * - De-serialize and ensure that the de-serialized query objects 1.64 + * equal the originals. 1.65 + */ 1.66 + 1.67 +const CHOOSE_HOW_MANY_SWITCHES_LO = 1; 1.68 +const CHOOSE_HOW_MANY_SWITCHES_HI = 2; 1.69 + 1.70 +const NUM_MULTIPLE_QUERIES = 2; 1.71 + 1.72 +// The switches are represented by objects below, in arrays querySwitches and 1.73 +// queryOptionSwitches. Use them to set up test runs. 1.74 +// 1.75 +// Some switches have special properties (where noted), but all switches must 1.76 +// have the following properties: 1.77 +// 1.78 +// matches: A function that takes two nsINavHistoryQuery objects (in the case 1.79 +// of nsINavHistoryQuery switches) or two nsINavHistoryQueryOptions 1.80 +// objects (for nsINavHistoryQueryOptions switches) and returns true 1.81 +// if the values of the switch in the two objects are equal. This is 1.82 +// the foundation of how we determine if two queries are equal. 1.83 +// runs: An array of functions. Each function takes an nsINavHistoryQuery 1.84 +// object and an nsINavHistoryQueryOptions object. The functions 1.85 +// should set the attributes of one of the two objects as appropriate 1.86 +// to their switches. This is how switch values are set for each test 1.87 +// run. 1.88 +// 1.89 +// The following properties are optional: 1.90 +// 1.91 +// desc: An informational string to print out during runs when the switch 1.92 +// is chosen. Hopefully helpful if the test fails. 1.93 + 1.94 +// nsINavHistoryQuery switches 1.95 +const querySwitches = [ 1.96 + // hasBeginTime 1.97 + { 1.98 + // flag and subswitches are used by the flagSwitchMatches function. Several 1.99 + // of the nsINavHistoryQuery switches (like this one) are really guard flags 1.100 + // that indicate if other "subswitches" are enabled. 1.101 + flag: "hasBeginTime", 1.102 + subswitches: ["beginTime", "beginTimeReference", "absoluteBeginTime"], 1.103 + desc: "nsINavHistoryQuery.hasBeginTime", 1.104 + matches: flagSwitchMatches, 1.105 + runs: [ 1.106 + function (aQuery, aQueryOptions) { 1.107 + aQuery.beginTime = Date.now() * 1000; 1.108 + aQuery.beginTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_EPOCH; 1.109 + }, 1.110 + function (aQuery, aQueryOptions) { 1.111 + aQuery.beginTime = Date.now() * 1000; 1.112 + aQuery.beginTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_TODAY; 1.113 + } 1.114 + ] 1.115 + }, 1.116 + // hasEndTime 1.117 + { 1.118 + flag: "hasEndTime", 1.119 + subswitches: ["endTime", "endTimeReference", "absoluteEndTime"], 1.120 + desc: "nsINavHistoryQuery.hasEndTime", 1.121 + matches: flagSwitchMatches, 1.122 + runs: [ 1.123 + function (aQuery, aQueryOptions) { 1.124 + aQuery.endTime = Date.now() * 1000; 1.125 + aQuery.endTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_EPOCH; 1.126 + }, 1.127 + function (aQuery, aQueryOptions) { 1.128 + aQuery.endTime = Date.now() * 1000; 1.129 + aQuery.endTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_TODAY; 1.130 + } 1.131 + ] 1.132 + }, 1.133 + // hasSearchTerms 1.134 + { 1.135 + flag: "hasSearchTerms", 1.136 + subswitches: ["searchTerms"], 1.137 + desc: "nsINavHistoryQuery.hasSearchTerms", 1.138 + matches: flagSwitchMatches, 1.139 + runs: [ 1.140 + function (aQuery, aQueryOptions) { 1.141 + aQuery.searchTerms = "shrimp and white wine"; 1.142 + }, 1.143 + function (aQuery, aQueryOptions) { 1.144 + aQuery.searchTerms = ""; 1.145 + } 1.146 + ] 1.147 + }, 1.148 + // hasDomain 1.149 + { 1.150 + flag: "hasDomain", 1.151 + subswitches: ["domain", "domainIsHost"], 1.152 + desc: "nsINavHistoryQuery.hasDomain", 1.153 + matches: flagSwitchMatches, 1.154 + runs: [ 1.155 + function (aQuery, aQueryOptions) { 1.156 + aQuery.domain = "mozilla.com"; 1.157 + aQuery.domainIsHost = false; 1.158 + }, 1.159 + function (aQuery, aQueryOptions) { 1.160 + aQuery.domain = "www.mozilla.com"; 1.161 + aQuery.domainIsHost = true; 1.162 + }, 1.163 + function (aQuery, aQueryOptions) { 1.164 + aQuery.domain = ""; 1.165 + } 1.166 + ] 1.167 + }, 1.168 + // hasUri 1.169 + { 1.170 + flag: "hasUri", 1.171 + subswitches: ["uri", "uriIsPrefix"], 1.172 + desc: "nsINavHistoryQuery.hasUri", 1.173 + matches: flagSwitchMatches, 1.174 + runs: [ 1.175 + function (aQuery, aQueryOptions) { 1.176 + aQuery.uri = uri("http://mozilla.com"); 1.177 + aQuery.uriIsPrefix = false; 1.178 + }, 1.179 + function (aQuery, aQueryOptions) { 1.180 + aQuery.uri = uri("http://mozilla.com"); 1.181 + aQuery.uriIsPrefix = true; 1.182 + } 1.183 + ] 1.184 + }, 1.185 + // hasAnnotation 1.186 + { 1.187 + flag: "hasAnnotation", 1.188 + subswitches: ["annotation", "annotationIsNot"], 1.189 + desc: "nsINavHistoryQuery.hasAnnotation", 1.190 + matches: flagSwitchMatches, 1.191 + runs: [ 1.192 + function (aQuery, aQueryOptions) { 1.193 + aQuery.annotation = "bookmarks/toolbarFolder"; 1.194 + aQuery.annotationIsNot = false; 1.195 + }, 1.196 + function (aQuery, aQueryOptions) { 1.197 + aQuery.annotation = "bookmarks/toolbarFolder"; 1.198 + aQuery.annotationIsNot = true; 1.199 + } 1.200 + ] 1.201 + }, 1.202 + // minVisits 1.203 + { 1.204 + // property is used by function simplePropertyMatches. 1.205 + property: "minVisits", 1.206 + desc: "nsINavHistoryQuery.minVisits", 1.207 + matches: simplePropertyMatches, 1.208 + runs: [ 1.209 + function (aQuery, aQueryOptions) { 1.210 + aQuery.minVisits = 0x7fffffff; // 2^31 - 1 1.211 + } 1.212 + ] 1.213 + }, 1.214 + // maxVisits 1.215 + { 1.216 + property: "maxVisits", 1.217 + desc: "nsINavHistoryQuery.maxVisits", 1.218 + matches: simplePropertyMatches, 1.219 + runs: [ 1.220 + function (aQuery, aQueryOptions) { 1.221 + aQuery.maxVisits = 0x7fffffff; // 2^31 - 1 1.222 + } 1.223 + ] 1.224 + }, 1.225 + // onlyBookmarked 1.226 + { 1.227 + property: "onlyBookmarked", 1.228 + desc: "nsINavHistoryQuery.onlyBookmarked", 1.229 + matches: simplePropertyMatches, 1.230 + runs: [ 1.231 + function (aQuery, aQueryOptions) { 1.232 + aQuery.onlyBookmarked = true; 1.233 + } 1.234 + ] 1.235 + }, 1.236 + // getFolders 1.237 + { 1.238 + desc: "nsINavHistoryQuery.getFolders", 1.239 + matches: function (aQuery1, aQuery2) { 1.240 + var q1Folders = aQuery1.getFolders(); 1.241 + var q2Folders = aQuery2.getFolders(); 1.242 + if (q1Folders.length !== q2Folders.length) 1.243 + return false; 1.244 + for (let i = 0; i < q1Folders.length; i++) { 1.245 + if (q2Folders.indexOf(q1Folders[i]) < 0) 1.246 + return false; 1.247 + } 1.248 + for (let i = 0; i < q2Folders.length; i++) { 1.249 + if (q1Folders.indexOf(q2Folders[i]) < 0) 1.250 + return false; 1.251 + } 1.252 + return true; 1.253 + }, 1.254 + runs: [ 1.255 + function (aQuery, aQueryOptions) { 1.256 + aQuery.setFolders([], 0); 1.257 + }, 1.258 + function (aQuery, aQueryOptions) { 1.259 + aQuery.setFolders([PlacesUtils.placesRootId], 1); 1.260 + }, 1.261 + function (aQuery, aQueryOptions) { 1.262 + aQuery.setFolders([PlacesUtils.placesRootId, PlacesUtils.tagsFolderId], 2); 1.263 + } 1.264 + ] 1.265 + }, 1.266 + // tags 1.267 + { 1.268 + desc: "nsINavHistoryQuery.getTags", 1.269 + matches: function (aQuery1, aQuery2) { 1.270 + if (aQuery1.tagsAreNot !== aQuery2.tagsAreNot) 1.271 + return false; 1.272 + var q1Tags = aQuery1.tags; 1.273 + var q2Tags = aQuery2.tags; 1.274 + if (q1Tags.length !== q2Tags.length) 1.275 + return false; 1.276 + for (let i = 0; i < q1Tags.length; i++) { 1.277 + if (q2Tags.indexOf(q1Tags[i]) < 0) 1.278 + return false; 1.279 + } 1.280 + for (let i = 0; i < q2Tags.length; i++) { 1.281 + if (q1Tags.indexOf(q2Tags[i]) < 0) 1.282 + return false; 1.283 + } 1.284 + return true; 1.285 + }, 1.286 + runs: [ 1.287 + function (aQuery, aQueryOptions) { 1.288 + aQuery.tags = []; 1.289 + }, 1.290 + function (aQuery, aQueryOptions) { 1.291 + aQuery.tags = [""]; 1.292 + }, 1.293 + function (aQuery, aQueryOptions) { 1.294 + aQuery.tags = [ 1.295 + "foo", 1.296 + "七難", 1.297 + "", 1.298 + "いっぱいおっぱい", 1.299 + "Abracadabra", 1.300 + "123", 1.301 + "Here's a pretty long tag name with some = signs and 1 2 3s and spaces oh jeez will it work I hope so!", 1.302 + "アスキーでございません", 1.303 + "あいうえお", 1.304 + ]; 1.305 + }, 1.306 + function (aQuery, aQueryOptions) { 1.307 + aQuery.tags = [ 1.308 + "foo", 1.309 + "七難", 1.310 + "", 1.311 + "いっぱいおっぱい", 1.312 + "Abracadabra", 1.313 + "123", 1.314 + "Here's a pretty long tag name with some = signs and 1 2 3s and spaces oh jeez will it work I hope so!", 1.315 + "アスキーでございません", 1.316 + "あいうえお", 1.317 + ]; 1.318 + aQuery.tagsAreNot = true; 1.319 + } 1.320 + ] 1.321 + }, 1.322 + // transitions 1.323 + { 1.324 + desc: "tests nsINavHistoryQuery.getTransitions", 1.325 + matches: function (aQuery1, aQuery2) { 1.326 + var q1Trans = aQuery1.getTransitions(); 1.327 + var q2Trans = aQuery2.getTransitions(); 1.328 + if (q1Trans.length !== q2Trans.length) 1.329 + return false; 1.330 + for (let i = 0; i < q1Trans.length; i++) { 1.331 + if (q2Trans.indexOf(q1Trans[i]) < 0) 1.332 + return false; 1.333 + } 1.334 + for (let i = 0; i < q2Trans.length; i++) { 1.335 + if (q1Trans.indexOf(q2Trans[i]) < 0) 1.336 + return false; 1.337 + } 1.338 + return true; 1.339 + }, 1.340 + runs: [ 1.341 + function (aQuery, aQueryOptions) { 1.342 + aQuery.setTransitions([], 0); 1.343 + }, 1.344 + function (aQuery, aQueryOptions) { 1.345 + aQuery.setTransitions([Ci.nsINavHistoryService.TRANSITION_DOWNLOAD], 1.346 + 1); 1.347 + }, 1.348 + function (aQuery, aQueryOptions) { 1.349 + aQuery.setTransitions([Ci.nsINavHistoryService.TRANSITION_TYPED, 1.350 + Ci.nsINavHistoryService.TRANSITION_BOOKMARK], 2); 1.351 + } 1.352 + ] 1.353 + }, 1.354 +]; 1.355 + 1.356 +// nsINavHistoryQueryOptions switches 1.357 +const queryOptionSwitches = [ 1.358 + // sortingMode 1.359 + { 1.360 + desc: "nsINavHistoryQueryOptions.sortingMode", 1.361 + matches: function (aOptions1, aOptions2) { 1.362 + if (aOptions1.sortingMode === aOptions2.sortingMode) { 1.363 + switch (aOptions1.sortingMode) { 1.364 + case aOptions1.SORT_BY_ANNOTATION_ASCENDING: 1.365 + case aOptions1.SORT_BY_ANNOTATION_DESCENDING: 1.366 + return aOptions1.sortingAnnotation === aOptions2.sortingAnnotation; 1.367 + break; 1.368 + } 1.369 + return true; 1.370 + } 1.371 + return false; 1.372 + }, 1.373 + runs: [ 1.374 + function (aQuery, aQueryOptions) { 1.375 + aQueryOptions.sortingMode = aQueryOptions.SORT_BY_DATE_ASCENDING; 1.376 + }, 1.377 + function (aQuery, aQueryOptions) { 1.378 + aQueryOptions.sortingMode = aQueryOptions.SORT_BY_ANNOTATION_ASCENDING; 1.379 + aQueryOptions.sortingAnnotation = "bookmarks/toolbarFolder"; 1.380 + } 1.381 + ] 1.382 + }, 1.383 + // resultType 1.384 + { 1.385 + // property is used by function simplePropertyMatches. 1.386 + property: "resultType", 1.387 + desc: "nsINavHistoryQueryOptions.resultType", 1.388 + matches: simplePropertyMatches, 1.389 + runs: [ 1.390 + function (aQuery, aQueryOptions) { 1.391 + aQueryOptions.resultType = aQueryOptions.RESULTS_AS_URI; 1.392 + }, 1.393 + function (aQuery, aQueryOptions) { 1.394 + aQueryOptions.resultType = aQueryOptions.RESULTS_AS_FULL_VISIT; 1.395 + } 1.396 + ] 1.397 + }, 1.398 + // excludeItems 1.399 + { 1.400 + property: "excludeItems", 1.401 + desc: "nsINavHistoryQueryOptions.excludeItems", 1.402 + matches: simplePropertyMatches, 1.403 + runs: [ 1.404 + function (aQuery, aQueryOptions) { 1.405 + aQueryOptions.excludeItems = true; 1.406 + } 1.407 + ] 1.408 + }, 1.409 + // excludeQueries 1.410 + { 1.411 + property: "excludeQueries", 1.412 + desc: "nsINavHistoryQueryOptions.excludeQueries", 1.413 + matches: simplePropertyMatches, 1.414 + runs: [ 1.415 + function (aQuery, aQueryOptions) { 1.416 + aQueryOptions.excludeQueries = true; 1.417 + } 1.418 + ] 1.419 + }, 1.420 + // excludeReadOnlyFolders 1.421 + { 1.422 + property: "excludeReadOnlyFolders", 1.423 + desc: "nsINavHistoryQueryOptions.excludeReadOnlyFolders", 1.424 + matches: simplePropertyMatches, 1.425 + runs: [ 1.426 + function (aQuery, aQueryOptions) { 1.427 + aQueryOptions.excludeReadOnlyFolders = true; 1.428 + } 1.429 + ] 1.430 + }, 1.431 + // expandQueries 1.432 + { 1.433 + property: "expandQueries", 1.434 + desc: "nsINavHistoryQueryOptions.expandQueries", 1.435 + matches: simplePropertyMatches, 1.436 + runs: [ 1.437 + function (aQuery, aQueryOptions) { 1.438 + aQueryOptions.expandQueries = true; 1.439 + } 1.440 + ] 1.441 + }, 1.442 + // includeHidden 1.443 + { 1.444 + property: "includeHidden", 1.445 + desc: "nsINavHistoryQueryOptions.includeHidden", 1.446 + matches: simplePropertyMatches, 1.447 + runs: [ 1.448 + function (aQuery, aQueryOptions) { 1.449 + aQueryOptions.includeHidden = true; 1.450 + } 1.451 + ] 1.452 + }, 1.453 + // maxResults 1.454 + { 1.455 + property: "maxResults", 1.456 + desc: "nsINavHistoryQueryOptions.maxResults", 1.457 + matches: simplePropertyMatches, 1.458 + runs: [ 1.459 + function (aQuery, aQueryOptions) { 1.460 + aQueryOptions.maxResults = 0xffffffff; // 2^32 - 1 1.461 + } 1.462 + ] 1.463 + }, 1.464 + // queryType 1.465 + { 1.466 + property: "queryType", 1.467 + desc: "nsINavHistoryQueryOptions.queryType", 1.468 + matches: simplePropertyMatches, 1.469 + runs: [ 1.470 + function (aQuery, aQueryOptions) { 1.471 + aQueryOptions.queryType = aQueryOptions.QUERY_TYPE_HISTORY; 1.472 + }, 1.473 + function (aQuery, aQueryOptions) { 1.474 + aQueryOptions.queryType = aQueryOptions.QUERY_TYPE_UNIFIED; 1.475 + } 1.476 + ] 1.477 + }, 1.478 +]; 1.479 + 1.480 +/////////////////////////////////////////////////////////////////////////////// 1.481 + 1.482 +/** 1.483 + * Enumerates all the sequences of the cartesian product of the arrays contained 1.484 + * in aSequences. Examples: 1.485 + * 1.486 + * cartProd([[1, 2, 3], ["a", "b"]], callback); 1.487 + * // callback is called 3 * 2 = 6 times with the following arrays: 1.488 + * // [1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"] 1.489 + * 1.490 + * cartProd([["a"], [1, 2, 3], ["X", "Y"]], callback); 1.491 + * // callback is called 1 * 3 * 2 = 6 times with the following arrays: 1.492 + * // ["a", 1, "X"], ["a", 1, "Y"], ["a", 2, "X"], ["a", 2, "Y"], 1.493 + * // ["a", 3, "X"], ["a", 3, "Y"] 1.494 + * 1.495 + * cartProd([[1], [2], [3], [4]], callback); 1.496 + * // callback is called 1 * 1 * 1 * 1 = 1 time with the following array: 1.497 + * // [1, 2, 3, 4] 1.498 + * 1.499 + * cartProd([], callback); 1.500 + * // callback is 0 times 1.501 + * 1.502 + * cartProd([[1, 2, 3, 4]], callback); 1.503 + * // callback is called 4 times with the following arrays: 1.504 + * // [1], [2], [3], [4] 1.505 + * 1.506 + * @param aSequences 1.507 + * an array that contains an arbitrary number of arrays 1.508 + * @param aCallback 1.509 + * a function that is passed each sequence of the product as it's 1.510 + * computed 1.511 + * @return the total number of sequences in the product 1.512 + */ 1.513 +function cartProd(aSequences, aCallback) 1.514 +{ 1.515 + if (aSequences.length === 0) 1.516 + return 0; 1.517 + 1.518 + // For each sequence in aSequences, we maintain a pointer (an array index, 1.519 + // really) to the element we're currently enumerating in that sequence 1.520 + var seqEltPtrs = aSequences.map(function (i) 0); 1.521 + 1.522 + var numProds = 0; 1.523 + var done = false; 1.524 + while (!done) { 1.525 + numProds++; 1.526 + 1.527 + // prod = sequence in product we're currently enumerating 1.528 + let prod = []; 1.529 + for (let i = 0; i < aSequences.length; i++) { 1.530 + prod.push(aSequences[i][seqEltPtrs[i]]); 1.531 + } 1.532 + aCallback(prod); 1.533 + 1.534 + // The next sequence in the product differs from the current one by just a 1.535 + // single element. Determine which element that is. We advance the 1.536 + // "rightmost" element pointer to the "right" by one. If we move past the 1.537 + // end of that pointer's sequence, reset the pointer to the first element 1.538 + // in its sequence and then try the sequence to the "left", and so on. 1.539 + 1.540 + // seqPtr = index of rightmost input sequence whose element pointer is not 1.541 + // past the end of the sequence 1.542 + let seqPtr = aSequences.length - 1; 1.543 + while (!done) { 1.544 + // Advance the rightmost element pointer. 1.545 + seqEltPtrs[seqPtr]++; 1.546 + 1.547 + // The rightmost element pointer is past the end of its sequence. 1.548 + if (seqEltPtrs[seqPtr] >= aSequences[seqPtr].length) { 1.549 + seqEltPtrs[seqPtr] = 0; 1.550 + seqPtr--; 1.551 + 1.552 + // All element pointers are past the ends of their sequences. 1.553 + if (seqPtr < 0) 1.554 + done = true; 1.555 + } 1.556 + else break; 1.557 + } 1.558 + } 1.559 + return numProds; 1.560 +} 1.561 + 1.562 +/** 1.563 + * Enumerates all the subsets in aSet of size aHowMany. There are 1.564 + * C(aSet.length, aHowMany) such subsets. aCallback will be passed each subset 1.565 + * as it is generated. Note that aSet and the subsets enumerated are -- even 1.566 + * though they're arrays -- not sequences; the ordering of their elements is not 1.567 + * important. Example: 1.568 + * 1.569 + * choose([1, 2, 3, 4], 2, callback); 1.570 + * // callback is called C(4, 2) = 6 times with the following sets (arrays): 1.571 + * // [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4] 1.572 + * 1.573 + * @param aSet 1.574 + * an array from which to choose elements, aSet.length > 0 1.575 + * @param aHowMany 1.576 + * the number of elements to choose, > 0 and <= aSet.length 1.577 + * @return the total number of sets chosen 1.578 + */ 1.579 +function choose(aSet, aHowMany, aCallback) 1.580 +{ 1.581 + // ptrs = indices of the elements in aSet we're currently choosing 1.582 + var ptrs = []; 1.583 + for (let i = 0; i < aHowMany; i++) { 1.584 + ptrs.push(i); 1.585 + } 1.586 + 1.587 + var numFound = 0; 1.588 + var done = false; 1.589 + while (!done) { 1.590 + numFound++; 1.591 + aCallback(ptrs.map(function (p) aSet[p])); 1.592 + 1.593 + // The next subset to be chosen differs from the current one by just a 1.594 + // single element. Determine which element that is. Advance the "rightmost" 1.595 + // pointer to the "right" by one. If we move past the end of set, move the 1.596 + // next non-adjacent rightmost pointer to the right by one, and reset all 1.597 + // succeeding pointers so that they're adjacent to it. When all pointers 1.598 + // are clustered all the way to the right, we're done. 1.599 + 1.600 + // Advance the rightmost pointer. 1.601 + ptrs[ptrs.length - 1]++; 1.602 + 1.603 + // The rightmost pointer has gone past the end of set. 1.604 + if (ptrs[ptrs.length - 1] >= aSet.length) { 1.605 + // Find the next rightmost pointer that is not adjacent to the current one. 1.606 + let si = aSet.length - 2; // aSet index 1.607 + let pi = ptrs.length - 2; // ptrs index 1.608 + while (pi >= 0 && ptrs[pi] === si) { 1.609 + pi--; 1.610 + si--; 1.611 + } 1.612 + 1.613 + // All pointers are adjacent and clustered all the way to the right. 1.614 + if (pi < 0) 1.615 + done = true; 1.616 + else { 1.617 + // pi = index of rightmost pointer with a gap between it and its 1.618 + // succeeding pointer. Move it right and reset all succeeding pointers 1.619 + // so that they're adjacent to it. 1.620 + ptrs[pi]++; 1.621 + for (let i = 0; i < ptrs.length - pi - 1; i++) { 1.622 + ptrs[i + pi + 1] = ptrs[pi] + i + 1; 1.623 + } 1.624 + } 1.625 + } 1.626 + } 1.627 + return numFound; 1.628 +} 1.629 + 1.630 +/** 1.631 + * Convenience function for nsINavHistoryQuery switches that act as flags. This 1.632 + * is attached to switch objects. See querySwitches array above. 1.633 + * 1.634 + * @param aQuery1 1.635 + * an nsINavHistoryQuery object 1.636 + * @param aQuery2 1.637 + * another nsINavHistoryQuery object 1.638 + * @return true if this switch is the same in both aQuery1 and aQuery2 1.639 + */ 1.640 +function flagSwitchMatches(aQuery1, aQuery2) 1.641 +{ 1.642 + if (aQuery1[this.flag] && aQuery2[this.flag]) { 1.643 + for (let p in this.subswitches) { 1.644 + if (p in aQuery1 && p in aQuery2) { 1.645 + if (aQuery1[p] instanceof Ci.nsIURI) { 1.646 + if (!aQuery1[p].equals(aQuery2[p])) 1.647 + return false; 1.648 + } 1.649 + else if (aQuery1[p] !== aQuery2[p]) 1.650 + return false; 1.651 + } 1.652 + } 1.653 + } 1.654 + else if (aQuery1[this.flag] || aQuery2[this.flag]) 1.655 + return false; 1.656 + 1.657 + return true; 1.658 +} 1.659 + 1.660 +/** 1.661 + * Tests if aObj1 and aObj2 are equal. This function is general and may be used 1.662 + * for either nsINavHistoryQuery or nsINavHistoryQueryOptions objects. aSwitches 1.663 + * determines which set of switches is used for comparison. Pass in either 1.664 + * querySwitches or queryOptionSwitches. 1.665 + * 1.666 + * @param aSwitches 1.667 + * determines which set of switches applies to aObj1 and aObj2, either 1.668 + * querySwitches or queryOptionSwitches 1.669 + * @param aObj1 1.670 + * an nsINavHistoryQuery or nsINavHistoryQueryOptions object 1.671 + * @param aObj2 1.672 + * another nsINavHistoryQuery or nsINavHistoryQueryOptions object 1.673 + * @return true if aObj1 and aObj2 are equal 1.674 + */ 1.675 +function queryObjsEqual(aSwitches, aObj1, aObj2) 1.676 +{ 1.677 + for (let i = 0; i < aSwitches.length; i++) { 1.678 + if (!aSwitches[i].matches(aObj1, aObj2)) 1.679 + return false; 1.680 + } 1.681 + return true; 1.682 +} 1.683 + 1.684 +/** 1.685 + * This drives the test runs. See the comment at the top of this file. 1.686 + * 1.687 + * @param aHowManyLo 1.688 + * the size of the switch subsets to start with 1.689 + * @param aHowManyHi 1.690 + * the size of the switch subsets to end with (inclusive) 1.691 + */ 1.692 +function runQuerySequences(aHowManyLo, aHowManyHi) 1.693 +{ 1.694 + var allSwitches = querySwitches.concat(queryOptionSwitches); 1.695 + var prevQueries = []; 1.696 + var prevOpts = []; 1.697 + 1.698 + // Choose aHowManyLo switches up to aHowManyHi switches. 1.699 + for (let howMany = aHowManyLo; howMany <= aHowManyHi; howMany++) { 1.700 + let numIters = 0; 1.701 + print("CHOOSING " + howMany + " SWITCHES"); 1.702 + 1.703 + // Choose all subsets of size howMany from allSwitches. 1.704 + choose(allSwitches, howMany, function (chosenSwitches) { 1.705 + print(numIters); 1.706 + numIters++; 1.707 + 1.708 + // Collect the runs. 1.709 + // runs = [ [runs from switch 1], ..., [runs from switch howMany] ] 1.710 + var runs = chosenSwitches.map(function (s) { 1.711 + if (s.desc) 1.712 + print(" " + s.desc); 1.713 + return s.runs; 1.714 + }); 1.715 + 1.716 + // cartProd(runs) => [ 1.717 + // [switch 1 run 1, switch 2 run 1, ..., switch howMany run 1 ], 1.718 + // ..., 1.719 + // [switch 1 run 1, switch 2 run 1, ..., switch howMany run N ], 1.720 + // ..., ..., 1.721 + // [switch 1 run N, switch 2 run N, ..., switch howMany run 1 ], 1.722 + // ..., 1.723 + // [switch 1 run N, switch 2 run N, ..., switch howMany run N ], 1.724 + // ] 1.725 + cartProd(runs, function (runSet) { 1.726 + // Create a new query, apply the switches in runSet, and test it. 1.727 + var query = PlacesUtils.history.getNewQuery(); 1.728 + var opts = PlacesUtils.history.getNewQueryOptions(); 1.729 + for (let i = 0; i < runSet.length; i++) { 1.730 + runSet[i](query, opts); 1.731 + } 1.732 + serializeDeserialize([query], opts); 1.733 + 1.734 + // Test the previous NUM_MULTIPLE_QUERIES queries together. 1.735 + prevQueries.push(query); 1.736 + prevOpts.push(opts); 1.737 + if (prevQueries.length >= NUM_MULTIPLE_QUERIES) { 1.738 + // We can serialize multiple nsINavHistoryQuery objects together but 1.739 + // only one nsINavHistoryQueryOptions object with them. So, test each 1.740 + // of the previous NUM_MULTIPLE_QUERIES nsINavHistoryQueryOptions. 1.741 + for (let i = 0; i < prevOpts.length; i++) { 1.742 + serializeDeserialize(prevQueries, prevOpts[i]); 1.743 + } 1.744 + prevQueries.shift(); 1.745 + prevOpts.shift(); 1.746 + } 1.747 + }); 1.748 + }); 1.749 + } 1.750 + print("\n"); 1.751 +} 1.752 + 1.753 +/** 1.754 + * Serializes the nsINavHistoryQuery objects in aQueryArr and the 1.755 + * nsINavHistoryQueryOptions object aQueryOptions, de-serializes the 1.756 + * serialization, and ensures (using do_check_* functions) that the 1.757 + * de-serialized objects equal the originals. 1.758 + * 1.759 + * @param aQueryArr 1.760 + * an array containing nsINavHistoryQuery objects 1.761 + * @param aQueryOptions 1.762 + * an nsINavHistoryQueryOptions object 1.763 + */ 1.764 +function serializeDeserialize(aQueryArr, aQueryOptions) 1.765 +{ 1.766 + var queryStr = PlacesUtils.history.queriesToQueryString(aQueryArr, 1.767 + aQueryArr.length, 1.768 + aQueryOptions); 1.769 + print(" " + queryStr); 1.770 + var queryArr2 = {}; 1.771 + var opts2 = {}; 1.772 + PlacesUtils.history.queryStringToQueries(queryStr, queryArr2, {}, opts2); 1.773 + queryArr2 = queryArr2.value; 1.774 + opts2 = opts2.value; 1.775 + 1.776 + // The two sets of queries cannot be the same if their lengths differ. 1.777 + do_check_eq(aQueryArr.length, queryArr2.length); 1.778 + 1.779 + // Although the query serialization code as it is written now practically 1.780 + // ensures that queries appear in the query string in the same order they 1.781 + // appear in both the array to be serialized and the array resulting from 1.782 + // de-serialization, the interface does not guarantee any ordering. So, for 1.783 + // each query in aQueryArr, find its equivalent in queryArr2 and delete it 1.784 + // from queryArr2. If queryArr2 is empty after looping through aQueryArr, 1.785 + // the two sets of queries are equal. 1.786 + for (let i = 0; i < aQueryArr.length; i++) { 1.787 + let j = 0; 1.788 + for (; j < queryArr2.length; j++) { 1.789 + if (queryObjsEqual(querySwitches, aQueryArr[i], queryArr2[j])) 1.790 + break; 1.791 + } 1.792 + if (j < queryArr2.length) 1.793 + queryArr2.splice(j, 1); 1.794 + } 1.795 + do_check_eq(queryArr2.length, 0); 1.796 + 1.797 + // Finally check the query options objects. 1.798 + do_check_true(queryObjsEqual(queryOptionSwitches, aQueryOptions, opts2)); 1.799 +} 1.800 + 1.801 +/** 1.802 + * Convenience function for switches that have simple values. This is attached 1.803 + * to switch objects. See querySwitches and queryOptionSwitches arrays above. 1.804 + * 1.805 + * @param aObj1 1.806 + * an nsINavHistoryQuery or nsINavHistoryQueryOptions object 1.807 + * @param aObj2 1.808 + * another nsINavHistoryQuery or nsINavHistoryQueryOptions object 1.809 + * @return true if this switch is the same in both aObj1 and aObj2 1.810 + */ 1.811 +function simplePropertyMatches(aObj1, aObj2) 1.812 +{ 1.813 + return aObj1[this.property] === aObj2[this.property]; 1.814 +} 1.815 + 1.816 +/////////////////////////////////////////////////////////////////////////////// 1.817 + 1.818 +function run_test() 1.819 +{ 1.820 + runQuerySequences(CHOOSE_HOW_MANY_SWITCHES_LO, CHOOSE_HOW_MANY_SWITCHES_HI); 1.821 +}