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.

     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/. */
     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  */
    64 const CHOOSE_HOW_MANY_SWITCHES_LO = 1;
    65 const CHOOSE_HOW_MANY_SWITCHES_HI = 2;
    67 const NUM_MULTIPLE_QUERIES        = 2;
    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.
    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 ];
   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 ];
   477 ///////////////////////////////////////////////////////////////////////////////
   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;
   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);
   519   var numProds = 0;
   520   var done = false;
   521   while (!done) {
   522     numProds++;
   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);
   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.
   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]++;
   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--;
   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 }
   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   }
   584   var numFound = 0;
   585   var done = false;
   586   while (!done) {
   587     numFound++;
   588     aCallback(ptrs.map(function (p) aSet[p]));
   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.
   597     // Advance the rightmost pointer.
   598     ptrs[ptrs.length - 1]++;
   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       }
   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 }
   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;
   654   return true;
   655 }
   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 }
   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 = [];
   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");
   700     // Choose all subsets of size howMany from allSwitches.
   701     choose(allSwitches, howMany, function (chosenSwitches) {
   702       print(numIters);
   703       numIters++;
   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       });
   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);
   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 }
   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;
   773   // The two sets of queries cannot be the same if their lengths differ.
   774   do_check_eq(aQueryArr.length, queryArr2.length);
   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);
   794   // Finally check the query options objects.
   795   do_check_true(queryObjsEqual(queryOptionSwitches, aQueryOptions, opts2));
   796 }
   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 }
   813 ///////////////////////////////////////////////////////////////////////////////
   815 function run_test()
   816 {
   817   runQuerySequences(CHOOSE_HOW_MANY_SWITCHES_LO, CHOOSE_HOW_MANY_SWITCHES_HI);
   818 }

mercurial