test_qm_first_initialization_attempt.js (22600B)
1 /** 2 * Any copyright is dedicated to the Public Domain. 3 * http://creativecommons.org/publicdomain/zero/1.0/ 4 */ 5 6 const { AppConstants } = ChromeUtils.importESModule( 7 "resource://gre/modules/AppConstants.sys.mjs" 8 ); 9 const { TelemetryTestUtils } = ChromeUtils.importESModule( 10 "resource://testing-common/TelemetryTestUtils.sys.mjs" 11 ); 12 13 const storageDirName = "storage"; 14 const storageFileName = "storage.sqlite"; 15 const indexedDBDirName = "indexedDB"; 16 const persistentStorageDirName = "storage/persistent"; 17 const histogramName = "QM_FIRST_INITIALIZATION_ATTEMPT"; 18 19 const testcases = [ 20 { 21 mainKey: "Storage", 22 async setup(expectedInitResult) { 23 if (!expectedInitResult) { 24 // Make the database unusable by creating it as a directory (not a 25 // file). 26 const storageFile = getRelativeFile(storageFileName); 27 storageFile.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); 28 } 29 }, 30 initFunction: init, 31 expectedSnapshots: { 32 initFailure: { 33 // mainKey 34 Storage: { 35 values: [1, 0], 36 }, 37 }, 38 initFailureThenSuccess: { 39 // mainKey 40 Storage: { 41 values: [1, 1, 0], 42 }, 43 }, 44 }, 45 }, 46 { 47 mainKey: "TemporaryStorage", 48 async setup(expectedInitResult) { 49 // We need to initialize storage before populating the repositories. If 50 // we don't do that, the storage directory created for the repositories 51 // would trigger storage upgrades (from version 0 to current version). 52 let request = init(); 53 await requestFinished(request); 54 55 populateRepository("temporary"); 56 populateRepository("default"); 57 58 if (!expectedInitResult) { 59 makeRepositoryUnusable("temporary"); 60 makeRepositoryUnusable("default"); 61 } 62 }, 63 initFunction: initTemporaryStorage, 64 getExpectedSnapshots() { 65 const expectedSnapshotsInNightly = { 66 initFailure: { 67 Storage: { 68 values: [0, 1, 0], 69 }, 70 TemporaryRepository: { 71 values: [1, 0], 72 }, 73 DefaultRepository: { 74 values: [1, 0], 75 }, 76 // mainKey 77 TemporaryStorage: { 78 values: [1, 0], 79 }, 80 }, 81 initFailureThenSuccess: { 82 Storage: { 83 values: [0, 2, 0], 84 }, 85 TemporaryRepository: { 86 values: [1, 1, 0], 87 }, 88 DefaultRepository: { 89 values: [1, 1, 0], 90 }, 91 // mainKey 92 TemporaryStorage: { 93 values: [1, 1, 0], 94 }, 95 }, 96 }; 97 98 const expectedSnapshotsInOthers = { 99 initFailure: { 100 Storage: { 101 values: [0, 1, 0], 102 }, 103 TemporaryRepository: { 104 values: [1, 0], 105 }, 106 // mainKey 107 TemporaryStorage: { 108 values: [1, 0], 109 }, 110 }, 111 initFailureThenSuccess: { 112 Storage: { 113 values: [0, 2, 0], 114 }, 115 TemporaryRepository: { 116 values: [1, 1, 0], 117 }, 118 DefaultRepository: { 119 values: [0, 1, 0], 120 }, 121 // mainKey 122 TemporaryStorage: { 123 values: [1, 1, 0], 124 }, 125 }, 126 }; 127 128 return AppConstants.NIGHTLY_BUILD 129 ? expectedSnapshotsInNightly 130 : expectedSnapshotsInOthers; 131 }, 132 }, 133 { 134 mainKey: "DefaultRepository", 135 async setup(expectedInitResult) { 136 // See the comment for "TemporaryStorage". 137 let request = init(); 138 await requestFinished(request); 139 140 populateRepository("default"); 141 142 if (!expectedInitResult) { 143 makeRepositoryUnusable("default"); 144 } 145 }, 146 initFunction: initTemporaryStorage, 147 expectedSnapshots: { 148 initFailure: { 149 Storage: { 150 values: [0, 1, 0], 151 }, 152 TemporaryRepository: { 153 values: [0, 1, 0], 154 }, 155 // mainKey 156 DefaultRepository: { 157 values: [1, 0], 158 }, 159 TemporaryStorage: { 160 values: [1, 0], 161 }, 162 }, 163 initFailureThenSuccess: { 164 Storage: { 165 values: [0, 2, 0], 166 }, 167 TemporaryRepository: { 168 values: [0, 2, 0], 169 }, 170 // mainKey 171 DefaultRepository: { 172 values: [1, 1, 0], 173 }, 174 TemporaryStorage: { 175 values: [1, 1, 0], 176 }, 177 }, 178 }, 179 }, 180 { 181 mainKey: "TemporaryRepository", 182 async setup(expectedInitResult) { 183 // See the comment for "TemporaryStorage". 184 let request = init(); 185 await requestFinished(request); 186 187 populateRepository("temporary"); 188 189 if (!expectedInitResult) { 190 makeRepositoryUnusable("temporary"); 191 } 192 }, 193 initFunction: initTemporaryStorage, 194 getExpectedSnapshots() { 195 const expectedSnapshotsInNightly = { 196 initFailure: { 197 Storage: { 198 values: [0, 1, 0], 199 }, 200 // mainKey 201 TemporaryRepository: { 202 values: [1, 0], 203 }, 204 DefaultRepository: { 205 values: [0, 1, 0], 206 }, 207 TemporaryStorage: { 208 values: [1, 0], 209 }, 210 }, 211 initFailureThenSuccess: { 212 Storage: { 213 values: [0, 2, 0], 214 }, 215 // mainKey 216 TemporaryRepository: { 217 values: [1, 1, 0], 218 }, 219 DefaultRepository: { 220 values: [0, 2, 0], 221 }, 222 TemporaryStorage: { 223 values: [1, 1, 0], 224 }, 225 }, 226 }; 227 228 const expectedSnapshotsInOthers = { 229 initFailure: { 230 Storage: { 231 values: [0, 1, 0], 232 }, 233 // mainKey 234 TemporaryRepository: { 235 values: [1, 0], 236 }, 237 TemporaryStorage: { 238 values: [1, 0], 239 }, 240 }, 241 initFailureThenSuccess: { 242 Storage: { 243 values: [0, 2, 0], 244 }, 245 // mainKey 246 TemporaryRepository: { 247 values: [1, 1, 0], 248 }, 249 DefaultRepository: { 250 values: [0, 1, 0], 251 }, 252 TemporaryStorage: { 253 values: [1, 1, 0], 254 }, 255 }, 256 }; 257 258 return AppConstants.NIGHTLY_BUILD 259 ? expectedSnapshotsInNightly 260 : expectedSnapshotsInOthers; 261 }, 262 }, 263 { 264 mainKey: "UpgradeStorageFrom0_0To1_0", 265 async setup(expectedInitResult) { 266 // storage used prior FF 49 (storage version 0.0) 267 installPackage("version0_0_profile"); 268 269 if (!expectedInitResult) { 270 installPackage("version0_0_make_it_unusable"); 271 } 272 }, 273 initFunction: init, 274 expectedSnapshots: { 275 initFailure: { 276 // mainKey 277 UpgradeStorageFrom0_0To1_0: { 278 values: [1, 0], 279 }, 280 Storage: { 281 values: [1, 0], 282 }, 283 }, 284 initFailureThenSuccess: { 285 // mainKey 286 UpgradeStorageFrom0_0To1_0: { 287 values: [1, 1, 0], 288 }, 289 UpgradeStorageFrom1_0To2_0: { 290 values: [0, 1, 0], 291 }, 292 UpgradeStorageFrom2_0To2_1: { 293 values: [0, 1, 0], 294 }, 295 UpgradeStorageFrom2_1To2_2: { 296 values: [0, 1, 0], 297 }, 298 UpgradeStorageFrom2_2To2_3: { 299 values: [0, 1, 0], 300 }, 301 Storage: { 302 values: [1, 1, 0], 303 }, 304 }, 305 }, 306 }, 307 { 308 mainKey: "UpgradeStorageFrom1_0To2_0", 309 async setup(expectedInitResult) { 310 // storage used by FF 49-54 (storage version 1.0) 311 installPackage("version1_0_profile"); 312 313 if (!expectedInitResult) { 314 installPackage("version1_0_make_it_unusable"); 315 } 316 }, 317 initFunction: init, 318 expectedSnapshots: { 319 initFailure: { 320 // mainKey 321 UpgradeStorageFrom1_0To2_0: { 322 values: [1, 0], 323 }, 324 Storage: { 325 values: [1, 0], 326 }, 327 }, 328 initFailureThenSuccess: { 329 // mainKey 330 UpgradeStorageFrom1_0To2_0: { 331 values: [1, 1, 0], 332 }, 333 UpgradeStorageFrom2_0To2_1: { 334 values: [0, 1, 0], 335 }, 336 UpgradeStorageFrom2_1To2_2: { 337 values: [0, 1, 0], 338 }, 339 UpgradeStorageFrom2_2To2_3: { 340 values: [0, 1, 0], 341 }, 342 Storage: { 343 values: [1, 1, 0], 344 }, 345 }, 346 }, 347 }, 348 { 349 mainKey: "UpgradeStorageFrom2_0To2_1", 350 async setup(expectedInitResult) { 351 // storage used by FF 55-56 (storage version 2.0) 352 installPackage("version2_0_profile"); 353 354 if (!expectedInitResult) { 355 installPackage("version2_0_make_it_unusable"); 356 } 357 }, 358 initFunction: init, 359 expectedSnapshots: { 360 initFailure: { 361 // mainKey 362 UpgradeStorageFrom2_0To2_1: { 363 values: [1, 0], 364 }, 365 Storage: { 366 values: [1, 0], 367 }, 368 }, 369 initFailureThenSuccess: { 370 // mainKey 371 UpgradeStorageFrom2_0To2_1: { 372 values: [1, 1, 0], 373 }, 374 UpgradeStorageFrom2_1To2_2: { 375 values: [0, 1, 0], 376 }, 377 UpgradeStorageFrom2_2To2_3: { 378 values: [0, 1, 0], 379 }, 380 Storage: { 381 values: [1, 1, 0], 382 }, 383 }, 384 }, 385 }, 386 { 387 mainKey: "UpgradeStorageFrom2_1To2_2", 388 async setup(expectedInitResult) { 389 // storage used by FF 57-67 (storage version 2.1) 390 installPackage("version2_1_profile"); 391 392 if (!expectedInitResult) { 393 installPackage("version2_1_make_it_unusable"); 394 } 395 }, 396 initFunction: init, 397 expectedSnapshots: { 398 initFailure: { 399 // mainKey 400 UpgradeStorageFrom2_1To2_2: { 401 values: [1, 0], 402 }, 403 Storage: { 404 values: [1, 0], 405 }, 406 }, 407 initFailureThenSuccess: { 408 // mainKey 409 UpgradeStorageFrom2_1To2_2: { 410 values: [1, 1, 0], 411 }, 412 UpgradeStorageFrom2_2To2_3: { 413 values: [0, 1, 0], 414 }, 415 Storage: { 416 values: [1, 1, 0], 417 }, 418 }, 419 }, 420 }, 421 { 422 mainKey: "UpgradeStorageFrom2_2To2_3", 423 async setup(expectedInitResult) { 424 // storage used by FF 68-69 (storage version 2.2) 425 installPackage("version2_2_profile"); 426 427 if (!expectedInitResult) { 428 installPackage( 429 "version2_2_make_it_unusable", 430 /* allowFileOverwrites */ true 431 ); 432 } 433 }, 434 initFunction: init, 435 expectedSnapshots: { 436 initFailure: { 437 // mainKey 438 UpgradeStorageFrom2_2To2_3: { 439 values: [1, 0], 440 }, 441 Storage: { 442 values: [1, 0], 443 }, 444 }, 445 initFailureThenSuccess: { 446 // mainKey 447 UpgradeStorageFrom2_2To2_3: { 448 values: [1, 1, 0], 449 }, 450 Storage: { 451 values: [1, 1, 0], 452 }, 453 }, 454 }, 455 }, 456 { 457 mainKey: "UpgradeFromIndexedDBDirectory", 458 async setup(expectedInitResult) { 459 const indexedDBDir = getRelativeFile(indexedDBDirName); 460 indexedDBDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); 461 462 if (!expectedInitResult) { 463 // "indexedDB" directory will be moved under "storage" directory and at 464 // the same time renamed to "persistent". Create a storage file to cause 465 // the moves to fail. 466 const storageFile = getRelativeFile(storageDirName); 467 storageFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); 468 } 469 }, 470 initFunction: init, 471 expectedSnapshots: { 472 initFailure: { 473 // mainKey 474 UpgradeFromIndexedDBDirectory: { 475 values: [1, 0], 476 }, 477 Storage: { 478 values: [1, 0], 479 }, 480 }, 481 initFailureThenSuccess: { 482 // mainKey 483 UpgradeFromIndexedDBDirectory: { 484 values: [1, 1, 0], 485 }, 486 UpgradeFromPersistentStorageDirectory: { 487 values: [0, 1, 0], 488 }, 489 UpgradeStorageFrom0_0To1_0: { 490 values: [0, 1, 0], 491 }, 492 UpgradeStorageFrom1_0To2_0: { 493 values: [0, 1, 0], 494 }, 495 UpgradeStorageFrom2_0To2_1: { 496 values: [0, 1, 0], 497 }, 498 UpgradeStorageFrom2_1To2_2: { 499 values: [0, 1, 0], 500 }, 501 UpgradeStorageFrom2_2To2_3: { 502 values: [0, 1, 0], 503 }, 504 Storage: { 505 values: [1, 1, 0], 506 }, 507 }, 508 }, 509 }, 510 { 511 mainKey: "UpgradeFromPersistentStorageDirectory", 512 async setup(expectedInitResult) { 513 const persistentStorageDir = getRelativeFile(persistentStorageDirName); 514 persistentStorageDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); 515 516 if (!expectedInitResult) { 517 // Create a metadata directory to break creating or upgrading directory 518 // metadata files. 519 const metadataDir = getRelativeFile( 520 "storage/persistent/https+++bad.example.com/.metadata" 521 ); 522 metadataDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); 523 } 524 }, 525 initFunction: init, 526 expectedSnapshots: { 527 initFailure: { 528 // mainKey 529 UpgradeFromPersistentStorageDirectory: { 530 values: [1, 0], 531 }, 532 Storage: { 533 values: [1, 0], 534 }, 535 }, 536 initFailureThenSuccess: { 537 // mainKey 538 UpgradeFromPersistentStorageDirectory: { 539 values: [1, 1, 0], 540 }, 541 UpgradeStorageFrom0_0To1_0: { 542 values: [0, 1, 0], 543 }, 544 UpgradeStorageFrom1_0To2_0: { 545 values: [0, 1, 0], 546 }, 547 UpgradeStorageFrom2_0To2_1: { 548 values: [0, 1, 0], 549 }, 550 UpgradeStorageFrom2_1To2_2: { 551 values: [0, 1, 0], 552 }, 553 UpgradeStorageFrom2_2To2_3: { 554 values: [0, 1, 0], 555 }, 556 Storage: { 557 values: [1, 1, 0], 558 }, 559 }, 560 }, 561 }, 562 { 563 mainKey: "PersistentOrigin", 564 async setup(expectedInitResult) { 565 // We need to initialize storage before creating the origin files. If we 566 // don't do that, the storage directory created for the origin files 567 // would trigger storage upgrades (from version 0 to current version). 568 let request = init(); 569 await requestFinished(request); 570 571 if (!expectedInitResult) { 572 const originFiles = [ 573 getRelativeFile("storage/permanent/https+++example.com"), 574 getRelativeFile("storage/permanent/https+++example1.com"), 575 getRelativeFile("storage/default/https+++example2.com"), 576 ]; 577 578 for (const originFile of originFiles) { 579 originFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); 580 } 581 } 582 583 request = initTemporaryStorage(); 584 await requestFinished(request); 585 }, 586 initFunctions: [ 587 { 588 name: initPersistentOrigin, 589 args: [getPrincipal("https://example.com")], 590 }, 591 { 592 name: initPersistentOrigin, 593 args: [getPrincipal("https://example1.com")], 594 }, 595 { 596 name: initTemporaryOrigin, 597 args: [ 598 "default", 599 getPrincipal("https://example2.com"), 600 /* createIfNonExistent */ true, 601 ], 602 }, 603 ], 604 expectedSnapshots: { 605 initFailure: { 606 Storage: { 607 values: [0, 1, 0], 608 }, 609 TemporaryRepository: { 610 values: [0, 1, 0], 611 }, 612 DefaultRepository: { 613 values: [0, 1, 0], 614 }, 615 TemporaryStorage: { 616 values: [0, 1, 0], 617 }, 618 // mainKey 619 PersistentOrigin: { 620 values: [2, 0], 621 }, 622 TemporaryOrigin: { 623 values: [1, 0], 624 }, 625 }, 626 initFailureThenSuccess: { 627 Storage: { 628 values: [0, 2, 0], 629 }, 630 TemporaryRepository: { 631 values: [0, 2, 0], 632 }, 633 DefaultRepository: { 634 values: [0, 2, 0], 635 }, 636 TemporaryStorage: { 637 values: [0, 2, 0], 638 }, 639 // mainKey 640 PersistentOrigin: { 641 values: [2, 2, 0], 642 }, 643 TemporaryOrigin: { 644 values: [1, 1, 0], 645 }, 646 }, 647 }, 648 }, 649 { 650 mainKey: "TemporaryOrigin", 651 async setup(expectedInitResult) { 652 // See the comment for "PersistentOrigin". 653 let request = init(); 654 await requestFinished(request); 655 656 if (!expectedInitResult) { 657 const originFiles = [ 658 getRelativeFile("storage/temporary/https+++example.com"), 659 getRelativeFile("storage/default/https+++example.com"), 660 getRelativeFile("storage/default/https+++example1.com"), 661 getRelativeFile("storage/permanent/https+++example2.com"), 662 ]; 663 664 for (const originFile of originFiles) { 665 originFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); 666 } 667 } 668 669 request = initTemporaryStorage(); 670 await requestFinished(request); 671 }, 672 initFunctions: [ 673 { 674 name: initTemporaryOrigin, 675 args: [ 676 "temporary", 677 getPrincipal("https://example.com"), 678 /* createIfNonExistent */ true, 679 ], 680 }, 681 { 682 name: initTemporaryOrigin, 683 args: [ 684 "default", 685 getPrincipal("https://example.com"), 686 /* createIfNonExistent */ true, 687 ], 688 }, 689 { 690 name: initTemporaryOrigin, 691 args: [ 692 "default", 693 getPrincipal("https://example1.com"), 694 /* createIfNonExistent */ true, 695 ], 696 }, 697 { 698 name: initPersistentOrigin, 699 args: [getPrincipal("https://example2.com")], 700 }, 701 ], 702 // Only the first result of EnsureTemporaryOriginIsInitialized per origin 703 // should be reported. Thus, only the results for (temporary, example.com), 704 // and (default, example1.com) should be reported. 705 expectedSnapshots: { 706 initFailure: { 707 Storage: { 708 values: [0, 1, 0], 709 }, 710 TemporaryRepository: { 711 values: [0, 1, 0], 712 }, 713 DefaultRepository: { 714 values: [0, 1, 0], 715 }, 716 TemporaryStorage: { 717 values: [0, 1, 0], 718 }, 719 PersistentOrigin: { 720 values: [1, 0], 721 }, 722 // mainKey 723 TemporaryOrigin: { 724 values: [2, 0], 725 }, 726 }, 727 initFailureThenSuccess: { 728 Storage: { 729 values: [0, 2, 0], 730 }, 731 TemporaryRepository: { 732 values: [0, 2, 0], 733 }, 734 DefaultRepository: { 735 values: [0, 2, 0], 736 }, 737 TemporaryStorage: { 738 values: [0, 2, 0], 739 }, 740 PersistentOrigin: { 741 values: [1, 1, 0], 742 }, 743 // mainKey 744 TemporaryOrigin: { 745 values: [2, 2, 0], 746 }, 747 }, 748 }, 749 }, 750 ]; 751 752 loadScript("dom/quota/test/xpcshell/common/utils.js"); 753 754 function verifyHistogram(histogram, mainKey, expectedSnapshot) { 755 const snapshot = histogram.snapshot(); 756 757 ok( 758 mainKey in snapshot, 759 `The histogram ${histogram.name()} must contain the main key ${mainKey}` 760 ); 761 762 const keys = Object.keys(snapshot); 763 764 is( 765 keys.length, 766 Object.keys(expectedSnapshot).length, 767 `The number of keys must match the expected number of keys for ` + 768 `${histogram.name()}` 769 ); 770 771 for (const key of keys) { 772 ok( 773 key in expectedSnapshot, 774 `The key ${key} must match the expected keys for ${histogram.name()}` 775 ); 776 777 const values = Object.entries(snapshot[key].values); 778 const expectedValues = expectedSnapshot[key].values; 779 780 is( 781 values.length, 782 expectedValues.length, 783 `The number of values should match the expected number of values for ` + 784 `${histogram.name()}` 785 ); 786 787 for (let [i, val] of values) { 788 is( 789 val, 790 expectedValues[i], 791 `Expected counts should match for ${histogram.name()} at index ${i}` 792 ); 793 } 794 } 795 } 796 797 async function testSteps() { 798 let request; 799 for (const testcase of testcases) { 800 const mainKey = testcase.mainKey; 801 802 info(`Verifying ${histogramName} histogram for the main key ${mainKey}`); 803 804 const histogram = 805 TelemetryTestUtils.getAndClearKeyedHistogram(histogramName); 806 807 for (const expectedInitResult of [false, true]) { 808 info( 809 `Verifying the histogram when the initialization ` + 810 `${expectedInitResult ? "failed and then succeeds" : "fails"}` 811 ); 812 813 await testcase.setup(expectedInitResult); 814 815 const msg = `Should ${expectedInitResult ? "not " : ""} have thrown`; 816 817 // Call the initialization function twice, so we can verify below that 818 // only the first initialization attempt has been reported. 819 for (let i = 0; i < 2; ++i) { 820 let initFunctions; 821 822 if (testcase.initFunctions) { 823 initFunctions = testcase.initFunctions; 824 } else { 825 initFunctions = [ 826 { 827 name: testcase.initFunction, 828 args: [], 829 }, 830 ]; 831 } 832 833 for (const initFunction of initFunctions) { 834 request = initFunction.name(...initFunction.args); 835 try { 836 await requestFinished(request); 837 ok(expectedInitResult, msg); 838 } catch (ex) { 839 ok(!expectedInitResult, msg); 840 } 841 } 842 } 843 844 const expectedSnapshots = testcase.getExpectedSnapshots 845 ? testcase.getExpectedSnapshots() 846 : testcase.expectedSnapshots; 847 848 const expectedSnapshot = expectedInitResult 849 ? expectedSnapshots.initFailureThenSuccess 850 : expectedSnapshots.initFailure; 851 852 verifyHistogram(histogram, mainKey, expectedSnapshot); 853 854 // The first initialization attempt has been reported in the histogram 855 // and any new attemps wouldn't be reported if we didn't reset or clear 856 // the storage here. We need a clean profile for the next iteration 857 // anyway. 858 // However, the clear storage operation needs initialized storage, so 859 // clearing can fail if the storage is unusable and it can also increase 860 // some of the telemetry counters. Instead of calling clear, we can just 861 // call reset and clear profile manually. 862 request = reset(); 863 await requestFinished(request); 864 865 const indexedDBDir = getRelativeFile(indexedDBDirName); 866 if (indexedDBDir.exists()) { 867 indexedDBDir.remove(false); 868 } 869 870 const storageDir = getRelativeFile(storageDirName); 871 if (storageDir.exists()) { 872 storageDir.remove(true); 873 } 874 875 const storageFile = getRelativeFile(storageFileName); 876 if (storageFile.exists()) { 877 // It could be a non empty directory, so remove it recursively. 878 storageFile.remove(true); 879 } 880 } 881 } 882 }