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

852 lines
28 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Places unit test code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Drew Willcoxon <adw@mozilla.com> (Original Author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* Tests Places query serialization. Associated bug is
* https://bugzilla.mozilla.org/show_bug.cgi?id=370197
*
* The simple idea behind this test is to try out different combinations of
* query switches and ensure that queries are the same before serialization
* as they are after de-serialization.
*
* In the code below, "switch" refers to a query option -- "option" in a broad
* sense, not nsINavHistoryQueryOptions specifically (which is why we refer to
* them as switches, not options). Both nsINavHistoryQuery and
* nsINavHistoryQueryOptions allow you to specify switches that affect query
* strings. nsINavHistoryQuery instances have attributes hasBeginTime,
* hasEndTime, hasSearchTerms, and so on. nsINavHistoryQueryOptions instances
* have attributes sortingMode, resultType, excludeItems, etc.
*
* Ideally we would like to test all 2^N subsets of switches, where N is the
* total number of switches; switches might interact in erroneous or other ways
* we do not expect. However, since N is large (21 at this time), that's
* impractical for a single test in a suite.
*
* Instead we choose all possible subsets of a certain, smaller size. In fact
* we begin by choosing CHOOSE_HOW_MANY_SWITCHES_LO and ramp up to
* CHOOSE_HOW_MANY_SWITCHES_HI.
*
* There are two more wrinkles. First, for some switches we'd like to be able to
* test multiple values. For example, it seems like a good idea to test both an
* empty string and a non-empty string for switch nsINavHistoryQuery.searchTerms.
* When switches have more than one value for a test run, we use the Cartesian
* product of their values to generate all possible combinations of values.
*
* Second, we need to also test serialization of multiple nsINavHistoryQuery
* objects at once. To do this, we remember the previous NUM_MULTIPLE_QUERIES
* queries we tested individually and then serialize them together. We do this
* each time we test an individual query. Thus the set of queries we test
* together loses one query and gains another each time.
*
* To summarize, here's how this test works:
*
* - For n = CHOOSE_HOW_MANY_SWITCHES_LO to CHOOSE_HOW_MANY_SWITCHES_HI:
* - From the total set of switches choose all possible subsets of size n.
* For each of those subsets s:
* - Collect the test runs of each switch in subset s and take their
* Cartesian product. For each sequence in the product:
* - Create nsINavHistoryQuery and nsINavHistoryQueryOptions objects
* with the chosen switches and test run values.
* - Serialize the query.
* - De-serialize and ensure that the de-serialized query objects equal
* the originals.
* - For each of the previous NUM_MULTIPLE_QUERIES
* nsINavHistoryQueryOptions objects o we created:
* - Serialize the previous NUM_MULTIPLE_QUERIES nsINavHistoryQuery
* objects together with o.
* - De-serialize and ensure that the de-serialized query objects
* equal the originals.
*/
const CHOOSE_HOW_MANY_SWITCHES_LO = 1;
const CHOOSE_HOW_MANY_SWITCHES_HI = 2;
const NUM_MULTIPLE_QUERIES = 2;
// The switches are represented by objects below, in arrays querySwitches and
// queryOptionSwitches. Use them to set up test runs.
//
// Some switches have special properties (where noted), but all switches must
// have the following properties:
//
// matches: A function that takes two nsINavHistoryQuery objects (in the case
// of nsINavHistoryQuery switches) or two nsINavHistoryQueryOptions
// objects (for nsINavHistoryQueryOptions switches) and returns true
// if the values of the switch in the two objects are equal. This is
// the foundation of how we determine if two queries are equal.
// runs: An array of functions. Each function takes an nsINavHistoryQuery
// object and an nsINavHistoryQueryOptions object. The functions
// should set the attributes of one of the two objects as appropriate
// to their switches. This is how switch values are set for each test
// run.
//
// The following properties are optional:
//
// desc: An informational string to print out during runs when the switch
// is chosen. Hopefully helpful if the test fails.
// nsINavHistoryQuery switches
const querySwitches = [
// hasBeginTime
{
// flag and subswitches are used by the flagSwitchMatches function. Several
// of the nsINavHistoryQuery switches (like this one) are really guard flags
// that indicate if other "subswitches" are enabled.
flag: "hasBeginTime",
subswitches: ["beginTime", "beginTimeReference", "absoluteBeginTime"],
desc: "nsINavHistoryQuery.hasBeginTime",
matches: flagSwitchMatches,
runs: [
function (aQuery, aQueryOptions) {
aQuery.beginTime = Date.now() * 1000;
aQuery.beginTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_EPOCH;
},
function (aQuery, aQueryOptions) {
aQuery.beginTime = Date.now() * 1000;
aQuery.beginTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_TODAY;
}
]
},
// hasEndTime
{
flag: "hasEndTime",
subswitches: ["endTime", "endTimeReference", "absoluteEndTime"],
desc: "nsINavHistoryQuery.hasEndTime",
matches: flagSwitchMatches,
runs: [
function (aQuery, aQueryOptions) {
aQuery.endTime = Date.now() * 1000;
aQuery.endTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_EPOCH;
},
function (aQuery, aQueryOptions) {
aQuery.endTime = Date.now() * 1000;
aQuery.endTimeReference = Ci.nsINavHistoryQuery.TIME_RELATIVE_TODAY;
}
]
},
// hasSearchTerms
{
flag: "hasSearchTerms",
subswitches: ["searchTerms"],
desc: "nsINavHistoryQuery.hasSearchTerms",
matches: flagSwitchMatches,
runs: [
function (aQuery, aQueryOptions) {
aQuery.searchTerms = "shrimp and white wine";
},
function (aQuery, aQueryOptions) {
aQuery.searchTerms = "";
}
]
},
// hasDomain
{
flag: "hasDomain",
subswitches: ["domain", "domainIsHost"],
desc: "nsINavHistoryQuery.hasDomain",
matches: flagSwitchMatches,
runs: [
function (aQuery, aQueryOptions) {
aQuery.domain = "mozilla.com";
aQuery.domainIsHost = false;
},
function (aQuery, aQueryOptions) {
aQuery.domain = "www.mozilla.com";
aQuery.domainIsHost = true;
},
function (aQuery, aQueryOptions) {
aQuery.domain = "";
}
]
},
// hasUri
{
flag: "hasUri",
subswitches: ["uri", "uriIsPrefix"],
desc: "nsINavHistoryQuery.hasUri",
matches: flagSwitchMatches,
runs: [
function (aQuery, aQueryOptions) {
aQuery.uri = uri("http://mozilla.com");
aQuery.uriIsPrefix = false;
},
function (aQuery, aQueryOptions) {
aQuery.uri = uri("http://mozilla.com");
aQuery.uriIsPrefix = true;
}
]
},
// hasAnnotation
{
flag: "hasAnnotation",
subswitches: ["annotation", "annotationIsNot"],
desc: "nsINavHistoryQuery.hasAnnotation",
matches: flagSwitchMatches,
runs: [
function (aQuery, aQueryOptions) {
aQuery.annotation = "bookmarks/toolbarFolder";
aQuery.annotationIsNot = false;
},
function (aQuery, aQueryOptions) {
aQuery.annotation = "bookmarks/toolbarFolder";
aQuery.annotationIsNot = true;
}
]
},
// minVisits
{
// property is used by function simplePropertyMatches.
property: "minVisits",
desc: "nsINavHistoryQuery.minVisits",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQuery.minVisits = 0x7fffffff; // 2^31 - 1
}
]
},
// maxVisits
{
property: "maxVisits",
desc: "nsINavHistoryQuery.maxVisits",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQuery.maxVisits = 0x7fffffff; // 2^31 - 1
}
]
},
// onlyBookmarked
{
property: "onlyBookmarked",
desc: "nsINavHistoryQuery.onlyBookmarked",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQuery.onlyBookmarked = true;
}
]
},
// getFolders
{
desc: "nsINavHistoryQuery.getFolders",
matches: function (aQuery1, aQuery2) {
var q1Folders = aQuery1.getFolders();
var q2Folders = aQuery2.getFolders();
if (q1Folders.length !== q2Folders.length)
return false;
for (let i = 0; i < q1Folders.length; i++) {
if (q2Folders.indexOf(q1Folders[i]) < 0)
return false;
}
for (let i = 0; i < q2Folders.length; i++) {
if (q1Folders.indexOf(q2Folders[i]) < 0)
return false;
}
return true;
},
runs: [
function (aQuery, aQueryOptions) {
aQuery.setFolders([], 0);
},
function (aQuery, aQueryOptions) {
aQuery.setFolders([bmsvc.placesRoot], 1);
},
function (aQuery, aQueryOptions) {
aQuery.setFolders([bmsvc.placesRoot, bmsvc.tagsFolder], 2);
}
]
},
// tags
{
desc: "nsINavHistoryQuery.getTags",
matches: function (aQuery1, aQuery2) {
if (aQuery1.tagsAreNot !== aQuery2.tagsAreNot)
return false;
var q1Tags = aQuery1.tags;
var q2Tags = aQuery2.tags;
if (q1Tags.length !== q2Tags.length)
return false;
for (let i = 0; i < q1Tags.length; i++) {
if (q2Tags.indexOf(q1Tags[i]) < 0)
return false;
}
for (let i = 0; i < q2Tags.length; i++) {
if (q1Tags.indexOf(q2Tags[i]) < 0)
return false;
}
return true;
},
runs: [
function (aQuery, aQueryOptions) {
aQuery.tags = [];
},
function (aQuery, aQueryOptions) {
aQuery.tags = [""];
},
function (aQuery, aQueryOptions) {
aQuery.tags = [
"foo",
"七難",
"",
"いっぱいおっぱい",
"Abracadabra",
"",
"Here's a pretty long tag name with some = signs and 1 2 3s and spaces oh jeez will it work I hope so!",
"アスキーでございません",
"あいうえお",
];
},
function (aQuery, aQueryOptions) {
aQuery.tags = [
"foo",
"七難",
"",
"いっぱいおっぱい",
"Abracadabra",
"",
"Here's a pretty long tag name with some = signs and 1 2 3s and spaces oh jeez will it work I hope so!",
"アスキーでございません",
"あいうえお",
];
aQuery.tagsAreNot = true;
}
]
},
];
// nsINavHistoryQueryOptions switches
const queryOptionSwitches = [
// sortingMode
{
desc: "nsINavHistoryQueryOptions.sortingMode",
matches: function (aOptions1, aOptions2) {
if (aOptions1.sortingMode === aOptions2.sortingMode) {
switch (aOptions1.sortingMode) {
case aOptions1.SORT_BY_ANNOTATION_ASCENDING:
case aOptions1.SORT_BY_ANNOTATION_DESCENDING:
return aOptions1.sortingAnnotation === aOptions2.sortingAnnotation;
break;
}
return true;
}
return false;
},
runs: [
function (aQuery, aQueryOptions) {
aQueryOptions.sortingMode = aQueryOptions.SORT_BY_DATE_ASCENDING;
},
function (aQuery, aQueryOptions) {
aQueryOptions.sortingMode = aQueryOptions.SORT_BY_ANNOTATION_ASCENDING;
aQueryOptions.sortingAnnotation = "bookmarks/toolbarFolder";
}
]
},
// resultType
{
// property is used by function simplePropertyMatches.
property: "resultType",
desc: "nsINavHistoryQueryOptions.resultType",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQueryOptions.resultType = aQueryOptions.RESULTS_AS_URI;
},
function (aQuery, aQueryOptions) {
aQueryOptions.resultType = aQueryOptions.RESULTS_AS_FULL_VISIT;
}
]
},
// excludeItems
{
property: "excludeItems",
desc: "nsINavHistoryQueryOptions.excludeItems",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQueryOptions.excludeItems = true;
}
]
},
// excludeQueries
{
property: "excludeQueries",
desc: "nsINavHistoryQueryOptions.excludeQueries",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQueryOptions.excludeQueries = true;
}
]
},
// excludeReadOnlyFolders
{
property: "excludeReadOnlyFolders",
desc: "nsINavHistoryQueryOptions.excludeReadOnlyFolders",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQueryOptions.excludeReadOnlyFolders = true;
}
]
},
// excludeItemIfParentHasAnnotation
{
property: "excludeItemIfParentHasAnnotation",
desc: "nsINavHistoryQueryOptions.excludeItemIfParentHasAnnotation",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQueryOptions.excludeItemIfParentHasAnnotation =
"bookmarks/toolbarFolder";
},
function (aQuery, aQueryOptions) {
aQueryOptions.excludeItemIfParentHasAnnotation = "";
}
]
},
// expandQueries
{
property: "expandQueries",
desc: "nsINavHistoryQueryOptions.expandQueries",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQueryOptions.expandQueries = true;
}
]
},
// includeHidden
{
property: "includeHidden",
desc: "nsINavHistoryQueryOptions.includeHidden",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQueryOptions.includeHidden = true;
}
]
},
// maxResults
{
property: "maxResults",
desc: "nsINavHistoryQueryOptions.maxResults",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQueryOptions.maxResults = 0xffffffff; // 2^32 - 1
}
]
},
// queryType
{
property: "queryType",
desc: "nsINavHistoryQueryOptions.queryType",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQueryOptions.queryType = aQueryOptions.QUERY_TYPE_HISTORY;
},
function (aQuery, aQueryOptions) {
aQueryOptions.queryType = aQueryOptions.QUERY_TYPE_UNIFIED;
}
]
},
// redirectsMode
{
property: "redirectsMode",
desc: "nsINavHistoryQueryOptions.redirectsMode",
matches: simplePropertyMatches,
runs: [
function (aQuery, aQueryOptions) {
aQueryOptions.redirectsMode = aQueryOptions.REDIRECTS_MODE_ALL;
},
function (aQuery, aQueryOptions) {
aQueryOptions.redirectsMode = aQueryOptions.REDIRECTS_MODE_TARGET;
},
function (aQuery, aQueryOptions) {
aQueryOptions.redirectsMode = aQueryOptions.REDIRECTS_MODE_SOURCE;
}
]
},
];
///////////////////////////////////////////////////////////////////////////////
/**
* Enumerates all the sequences of the cartesian product of the arrays contained
* in aSequences. Examples:
*
* cartProd([[1, 2, 3], ["a", "b"]], callback);
* // callback is called 3 * 2 = 6 times with the following arrays:
* // [1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]
*
* cartProd([["a"], [1, 2, 3], ["X", "Y"]], callback);
* // callback is called 1 * 3 * 2 = 6 times with the following arrays:
* // ["a", 1, "X"], ["a", 1, "Y"], ["a", 2, "X"], ["a", 2, "Y"],
* // ["a", 3, "X"], ["a", 3, "Y"]
*
* cartProd([[1], [2], [3], [4]], callback);
* // callback is called 1 * 1 * 1 * 1 = 1 time with the following array:
* // [1, 2, 3, 4]
*
* cartProd([], callback);
* // callback is 0 times
*
* cartProd([[1, 2, 3, 4]], callback);
* // callback is called 4 times with the following arrays:
* // [1], [2], [3], [4]
*
* @param aSequences
* an array that contains an arbitrary number of arrays
* @param aCallback
* a function that is passed each sequence of the product as it's
* computed
* @return the total number of sequences in the product
*/
function cartProd(aSequences, aCallback)
{
if (aSequences.length === 0)
return 0;
// For each sequence in aSequences, we maintain a pointer (an array index,
// really) to the element we're currently enumerating in that sequence
var seqEltPtrs = aSequences.map(function (i) 0);
var numProds = 0;
var done = false;
while (!done) {
numProds++;
// prod = sequence in product we're currently enumerating
let prod = [];
for (let i = 0; i < aSequences.length; i++) {
prod.push(aSequences[i][seqEltPtrs[i]]);
}
aCallback(prod);
// The next sequence in the product differs from the current one by just a
// single element. Determine which element that is. We advance the
// "rightmost" element pointer to the "right" by one. If we move past the
// end of that pointer's sequence, reset the pointer to the first element
// in its sequence and then try the sequence to the "left", and so on.
// seqPtr = index of rightmost input sequence whose element pointer is not
// past the end of the sequence
let seqPtr = aSequences.length - 1;
while (!done) {
// Advance the rightmost element pointer.
seqEltPtrs[seqPtr]++;
// The rightmost element pointer is past the end of its sequence.
if (seqEltPtrs[seqPtr] >= aSequences[seqPtr].length) {
seqEltPtrs[seqPtr] = 0;
seqPtr--;
// All element pointers are past the ends of their sequences.
if (seqPtr < 0)
done = true;
}
else break;
}
}
return numProds;
}
/**
* Enumerates all the subsets in aSet of size aHowMany. There are
* C(aSet.length, aHowMany) such subsets. aCallback will be passed each subset
* as it is generated. Note that aSet and the subsets enumerated are -- even
* though they're arrays -- not sequences; the ordering of their elements is not
* important. Example:
*
* choose([1, 2, 3, 4], 2, callback);
* // callback is called C(4, 2) = 6 times with the following sets (arrays):
* // [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]
*
* @param aSet
* an array from which to choose elements, aSet.length > 0
* @param aHowMany
* the number of elements to choose, > 0 and <= aSet.length
* @return the total number of sets chosen
*/
function choose(aSet, aHowMany, aCallback)
{
// ptrs = indices of the elements in aSet we're currently choosing
var ptrs = [];
for (let i = 0; i < aHowMany; i++) {
ptrs.push(i);
}
var numFound = 0;
var done = false;
while (!done) {
numFound++;
aCallback(ptrs.map(function (p) aSet[p]));
// The next subset to be chosen differs from the current one by just a
// single element. Determine which element that is. Advance the "rightmost"
// pointer to the "right" by one. If we move past the end of set, move the
// next non-adjacent rightmost pointer to the right by one, and reset all
// succeeding pointers so that they're adjacent to it. When all pointers
// are clustered all the way to the right, we're done.
// Advance the rightmost pointer.
ptrs[ptrs.length - 1]++;
// The rightmost pointer has gone past the end of set.
if (ptrs[ptrs.length - 1] >= aSet.length) {
// Find the next rightmost pointer that is not adjacent to the current one.
let si = aSet.length - 2; // aSet index
let pi = ptrs.length - 2; // ptrs index
while (pi >= 0 && ptrs[pi] === si) {
pi--;
si--;
}
// All pointers are adjacent and clustered all the way to the right.
if (pi < 0)
done = true;
else {
// pi = index of rightmost pointer with a gap between it and its
// succeeding pointer. Move it right and reset all succeeding pointers
// so that they're adjacent to it.
ptrs[pi]++;
for (let i = 0; i < ptrs.length - pi - 1; i++) {
ptrs[i + pi + 1] = ptrs[pi] + i + 1;
}
}
}
}
return numFound;
}
/**
* Convenience function for nsINavHistoryQuery switches that act as flags. This
* is attached to switch objects. See querySwitches array above.
*
* @param aQuery1
* an nsINavHistoryQuery object
* @param aQuery2
* another nsINavHistoryQuery object
* @return true if this switch is the same in both aQuery1 and aQuery2
*/
function flagSwitchMatches(aQuery1, aQuery2)
{
if (aQuery1[this.flag] && aQuery2[this.flag]) {
for (let p in this.subswitches) {
if (p in aQuery1 && p in aQuery2) {
if (aQuery1[p] instanceof Ci.nsIURI) {
if (!aQuery1[p].equals(aQuery2[p]))
return false;
}
else if (aQuery1[p] !== aQuery2[p])
return false;
}
}
}
else if (aQuery1[this.flag] || aQuery2[this.flag])
return false;
return true;
}
/**
* Tests if aObj1 and aObj2 are equal. This function is general and may be used
* for either nsINavHistoryQuery or nsINavHistoryQueryOptions objects. aSwitches
* determines which set of switches is used for comparison. Pass in either
* querySwitches or queryOptionSwitches.
*
* @param aSwitches
* determines which set of switches applies to aObj1 and aObj2, either
* querySwitches or queryOptionSwitches
* @param aObj1
* an nsINavHistoryQuery or nsINavHistoryQueryOptions object
* @param aObj2
* another nsINavHistoryQuery or nsINavHistoryQueryOptions object
* @return true if aObj1 and aObj2 are equal
*/
function queryObjsEqual(aSwitches, aObj1, aObj2)
{
for (let i = 0; i < aSwitches.length; i++) {
if (!aSwitches[i].matches(aObj1, aObj2))
return false;
}
return true;
}
/**
* This drives the test runs. See the comment at the top of this file.
*
* @param aHowManyLo
* the size of the switch subsets to start with
* @param aHowManyHi
* the size of the switch subsets to end with (inclusive)
*/
function runQuerySequences(aHowManyLo, aHowManyHi)
{
var allSwitches = querySwitches.concat(queryOptionSwitches);
var prevQueries = [];
var prevOpts = [];
// Choose aHowManyLo switches up to aHowManyHi switches.
for (let howMany = aHowManyLo; howMany <= aHowManyHi; howMany++) {
let numIters = 0;
print("CHOOSING " + howMany + " SWITCHES");
// Choose all subsets of size howMany from allSwitches.
choose(allSwitches, howMany, function (chosenSwitches) {
print(numIters);
numIters++;
// Collect the runs.
// runs = [ [runs from switch 1], ..., [runs from switch howMany] ]
var runs = chosenSwitches.map(function (s) {
if (s.desc)
print(" " + s.desc);
return s.runs;
});
// cartProd(runs) => [
// [switch 1 run 1, switch 2 run 1, ..., switch howMany run 1 ],
// ...,
// [switch 1 run 1, switch 2 run 1, ..., switch howMany run N ],
// ..., ...,
// [switch 1 run N, switch 2 run N, ..., switch howMany run 1 ],
// ...,
// [switch 1 run N, switch 2 run N, ..., switch howMany run N ],
// ]
cartProd(runs, function (runSet) {
// Create a new query, apply the switches in runSet, and test it.
var query = histsvc.getNewQuery();
var opts = histsvc.getNewQueryOptions();
for (let i = 0; i < runSet.length; i++) {
runSet[i](query, opts);
}
serializeDeserialize([query], opts);
// Test the previous NUM_MULTIPLE_QUERIES queries together.
prevQueries.push(query);
prevOpts.push(opts);
if (prevQueries.length >= NUM_MULTIPLE_QUERIES) {
// We can serialize multiple nsINavHistoryQuery objects together but
// only one nsINavHistoryQueryOptions object with them. So, test each
// of the previous NUM_MULTIPLE_QUERIES nsINavHistoryQueryOptions.
for (let i = 0; i < prevOpts.length; i++) {
serializeDeserialize(prevQueries, prevOpts[i]);
}
prevQueries.shift();
prevOpts.shift();
}
});
});
}
print("\n");
}
/**
* Serializes the nsINavHistoryQuery objects in aQueryArr and the
* nsINavHistoryQueryOptions object aQueryOptions, de-serializes the
* serialization, and ensures (using do_check_* functions) that the
* de-serialized objects equal the originals.
*
* @param aQueryArr
* an array containing nsINavHistoryQuery objects
* @param aQueryOptions
* an nsINavHistoryQueryOptions object
*/
function serializeDeserialize(aQueryArr, aQueryOptions)
{
var queryStr = histsvc.queriesToQueryString(aQueryArr,
aQueryArr.length,
aQueryOptions);
print(" " + queryStr);
var queryArr2 = {};
var opts2 = {};
histsvc.queryStringToQueries(queryStr, queryArr2, {}, opts2);
queryArr2 = queryArr2.value;
opts2 = opts2.value;
// The two sets of queries cannot be the same if their lengths differ.
do_check_eq(aQueryArr.length, queryArr2.length);
// Although the query serialization code as it is written now practically
// ensures that queries appear in the query string in the same order they
// appear in both the array to be serialized and the array resulting from
// de-serialization, the interface does not guarantee any ordering. So, for
// each query in aQueryArr, find its equivalent in queryArr2 and delete it
// from queryArr2. If queryArr2 is empty after looping through aQueryArr,
// the two sets of queries are equal.
for (let i = 0; i < aQueryArr.length; i++) {
let j = 0;
for (; j < queryArr2.length; j++) {
if (queryObjsEqual(querySwitches, aQueryArr[i], queryArr2[j]))
break;
}
if (j < queryArr2.length)
queryArr2.splice(j, 1);
}
do_check_eq(queryArr2.length, 0);
// Finally check the query options objects.
do_check_true(queryObjsEqual(queryOptionSwitches, aQueryOptions, opts2));
}
/**
* Convenience function for switches that have simple values. This is attached
* to switch objects. See querySwitches and queryOptionSwitches arrays above.
*
* @param aObj1
* an nsINavHistoryQuery or nsINavHistoryQueryOptions object
* @param aObj2
* another nsINavHistoryQuery or nsINavHistoryQueryOptions object
* @return true if this switch is the same in both aObj1 and aObj2
*/
function simplePropertyMatches(aObj1, aObj2)
{
return aObj1[this.property] === aObj2[this.property];
}
///////////////////////////////////////////////////////////////////////////////
function run_test()
{
runQuerySequences(CHOOSE_HOW_MANY_SWITCHES_LO, CHOOSE_HOW_MANY_SWITCHES_HI);
}