Sat, 03 Jan 2015 20:18:00 +0100
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 | } |