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

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

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

mercurial