Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android'
- Manifest: browser/components/backup/tests/xpcshell/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
const { AddonsBackupResource } = ChromeUtils.importESModule(
"resource:///modules/backup/AddonsBackupResource.sys.mjs"
);
/**
* Tests that we can measure the size of all the addons & extensions data.
*/
add_task(async function test_measure() {
Services.fog.testResetFOG();
Services.telemetry.clearScalars();
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON = 250;
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE = 500;
const EXPECTED_KILOBYTES_FOR_STORAGE_SYNC = 50;
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_A = 600;
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_B = 400;
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C = 150;
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_DIRECTORY = 1000;
const EXPECTED_KILOBYTES_FOR_EXTENSION_DATA = 100;
const EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE = 200;
let tempDir = PathUtils.tempDir;
// Create extensions json files (all the same size).
const extensionsFilePath = PathUtils.join(tempDir, "extensions.json");
await createKilobyteSizedFile(
extensionsFilePath,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
);
const extensionSettingsFilePath = PathUtils.join(
tempDir,
"extension-settings.json"
);
await createKilobyteSizedFile(
extensionSettingsFilePath,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
);
const extensionsPrefsFilePath = PathUtils.join(
tempDir,
"extension-preferences.json"
);
await createKilobyteSizedFile(
extensionsPrefsFilePath,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
);
const addonStartupFilePath = PathUtils.join(tempDir, "addonStartup.json.lz4");
await createKilobyteSizedFile(
addonStartupFilePath,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON
);
// Create the extension store permissions data file.
let extensionStorePermissionsDataSize = PathUtils.join(
tempDir,
"extension-store-permissions",
"data.safe.bin"
);
await createKilobyteSizedFile(
extensionStorePermissionsDataSize,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE
);
// Create the storage sync database file.
let storageSyncPath = PathUtils.join(tempDir, "storage-sync-v2.sqlite");
await createKilobyteSizedFile(
storageSyncPath,
EXPECTED_KILOBYTES_FOR_STORAGE_SYNC
);
// Create the extensions directory with XPI files.
let extensionsXPIAPath = PathUtils.join(
tempDir,
"extensions",
"extension-b.xpi"
);
let extensionsXPIBPath = PathUtils.join(
tempDir,
"extensions",
"extension-a.xpi"
);
await createKilobyteSizedFile(
extensionsXPIAPath,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_A
);
await createKilobyteSizedFile(
extensionsXPIBPath,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_B
);
// Should be ignored.
let extensionsXPIStagedPath = PathUtils.join(
tempDir,
"extensions",
"staged",
"staged-test-extension.xpi"
);
let extensionsXPITrashPath = PathUtils.join(
tempDir,
"extensions",
"trash",
"trashed-test-extension.xpi"
);
let extensionsXPIUnpackedPath = PathUtils.join(
tempDir,
"extensions",
"unpacked-extension.xpi",
"manifest.json"
);
await createKilobyteSizedFile(
extensionsXPIStagedPath,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C
);
await createKilobyteSizedFile(
extensionsXPITrashPath,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C
);
await createKilobyteSizedFile(
extensionsXPIUnpackedPath,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_XPI_C
);
// Create the browser extension data directory.
let browserExtensionDataPath = PathUtils.join(
tempDir,
"browser-extension-data",
"test-file"
);
await createKilobyteSizedFile(
browserExtensionDataPath,
EXPECTED_KILOBYTES_FOR_EXTENSION_DATA
);
// Create the extensions storage directory.
let extensionsStoragePath = PathUtils.join(
tempDir,
"storage",
"default",
"moz-extension+++test-extension-id",
"idb",
"data.sqlite"
);
// Other storage files that should not be counted.
let otherStoragePath = PathUtils.join(
tempDir,
"storage",
"default",
"https+++accounts.firefox.com",
"ls",
"data.sqlite"
);
await createKilobyteSizedFile(
extensionsStoragePath,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE
);
await createKilobyteSizedFile(
otherStoragePath,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE
);
// Measure all the extensions data.
let extensionsBackupResource = new AddonsBackupResource();
await extensionsBackupResource.measure(tempDir);
let extensionsJsonSizeMeasurement =
Glean.browserBackup.extensionsJsonSize.testGetValue();
Assert.equal(
extensionsJsonSizeMeasurement,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_JSON * 4, // There are 4 equally sized files.
"Should have collected the correct measurement of the total size of all extensions JSON files"
);
let extensionStorePermissionsDataSizeMeasurement =
Glean.browserBackup.extensionStorePermissionsDataSize.testGetValue();
Assert.equal(
extensionStorePermissionsDataSizeMeasurement,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORE,
"Should have collected the correct measurement of the size of the extension store permissions data"
);
let storageSyncSizeMeasurement =
Glean.browserBackup.storageSyncSize.testGetValue();
Assert.equal(
storageSyncSizeMeasurement,
EXPECTED_KILOBYTES_FOR_STORAGE_SYNC,
"Should have collected the correct measurement of the size of the storage sync database"
);
let extensionsXPIDirectorySizeMeasurement =
Glean.browserBackup.extensionsXpiDirectorySize.testGetValue();
Assert.equal(
extensionsXPIDirectorySizeMeasurement,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_DIRECTORY,
"Should have collected the correct measurement of the size 2 equally sized XPI files in the extensions directory"
);
let browserExtensionDataSizeMeasurement =
Glean.browserBackup.browserExtensionDataSize.testGetValue();
Assert.equal(
browserExtensionDataSizeMeasurement,
EXPECTED_KILOBYTES_FOR_EXTENSION_DATA,
"Should have collected the correct measurement of the size of the browser extension data directory"
);
let extensionsStorageSizeMeasurement =
Glean.browserBackup.extensionsStorageSize.testGetValue();
Assert.equal(
extensionsStorageSizeMeasurement,
EXPECTED_KILOBYTES_FOR_EXTENSIONS_STORAGE,
"Should have collected the correct measurement of all the extensions storage"
);
// Compare glean vs telemetry measurements
let scalars = TelemetryTestUtils.getProcessScalars("parent", false, false);
TelemetryTestUtils.assertScalar(
scalars,
"browser.backup.extensions_json_size",
extensionsJsonSizeMeasurement,
"Glean and telemetry measurements for extensions JSON should be equal"
);
TelemetryTestUtils.assertScalar(
scalars,
"browser.backup.extension_store_permissions_data_size",
extensionStorePermissionsDataSizeMeasurement,
"Glean and telemetry measurements for extension store permissions data should be equal"
);
TelemetryTestUtils.assertScalar(
scalars,
"browser.backup.storage_sync_size",
storageSyncSizeMeasurement,
"Glean and telemetry measurements for storage sync database should be equal"
);
TelemetryTestUtils.assertScalar(
scalars,
"browser.backup.extensions_xpi_directory_size",
extensionsXPIDirectorySizeMeasurement,
"Glean and telemetry measurements for extensions directory should be equal"
);
TelemetryTestUtils.assertScalar(
scalars,
"browser.backup.browser_extension_data_size",
browserExtensionDataSizeMeasurement,
"Glean and telemetry measurements for browser extension data should be equal"
);
TelemetryTestUtils.assertScalar(
scalars,
"browser.backup.extensions_storage_size",
extensionsStorageSizeMeasurement,
"Glean and telemetry measurements for extensions storage should be equal"
);
await maybeRemovePath(tempDir);
});
/**
* Tests that we can handle the extension store permissions data
* and moz-extension IndexedDB databases not existing.
*/
add_task(async function test_measure_missing_data() {
Services.fog.testResetFOG();
let tempDir = PathUtils.tempDir;
let extensionsBackupResource = new AddonsBackupResource();
await extensionsBackupResource.measure(tempDir);
let extensionStorePermissionsDataSizeMeasurement =
Glean.browserBackup.extensionStorePermissionsDataSize.testGetValue();
Assert.equal(
extensionStorePermissionsDataSizeMeasurement,
null,
"Should NOT have collected a measurement for the missing permissions data"
);
let extensionsStorageSizeMeasurement =
Glean.browserBackup.extensionsStorageSize.testGetValue();
Assert.equal(
extensionsStorageSizeMeasurement,
null,
"Should NOT have collected a measurement for the missing storage data"
);
});
/**
* Test that the backup method correctly copies items from the profile directory
* into the staging directory.
*/
add_task(async function test_backup() {
let sandbox = sinon.createSandbox();
let addonsBackupResource = new AddonsBackupResource();
let sourcePath = await IOUtils.createUniqueDirectory(
PathUtils.tempDir,
"AddonsBackupResource-source-test"
);
let stagingPath = await IOUtils.createUniqueDirectory(
PathUtils.tempDir,
"AddonsBackupResource-staging-test"
);
const simpleCopyFiles = [
{ path: "extensions.json" },
{ path: "extension-settings.json" },
{ path: "extension-preferences.json" },
{ path: "addonStartup.json.lz4" },
{
path: [
"browser-extension-data",
"{11aa1234-f111-1234-abcd-a9b8c7654d32}",
],
},
{ path: ["extension-store-permissions", "data.safe.bin"] },
{ path: ["extensions", "{11aa1234-f111-1234-abcd-a9b8c7654d32}.xpi"] },
];
await createTestFiles(sourcePath, simpleCopyFiles);
const junkFiles = [{ path: ["extensions", "junk"] }];
await createTestFiles(sourcePath, junkFiles);
// Create a fake storage-sync-v2 database file. We don't expect this to
// be copied to the staging directory in this test due to our stubbing
// of the backup method, so we don't include it in `simpleCopyFiles`.
await createTestFiles(sourcePath, [{ path: "storage-sync-v2.sqlite" }]);
let fakeConnection = {
backup: sandbox.stub().resolves(true),
close: sandbox.stub().resolves(true),
};
sandbox.stub(Sqlite, "openConnection").returns(fakeConnection);
let manifestEntry = await addonsBackupResource.backup(
stagingPath,
sourcePath
);
Assert.equal(
manifestEntry,
null,
"AddonsBackupResource.backup should return null as its ManifestEntry"
);
await assertFilesExist(stagingPath, simpleCopyFiles);
let junkFile = PathUtils.join(stagingPath, "extensions", "junk");
Assert.equal(
await IOUtils.exists(junkFile),
false,
`${junkFile} should not exist in the staging folder`
);
// Make sure storage-sync-v2 database is backed up.
Assert.ok(
fakeConnection.backup.calledOnce,
"Called backup the expected number of times for all connections"
);
Assert.ok(
fakeConnection.backup.calledWith(
PathUtils.join(stagingPath, "storage-sync-v2.sqlite")
),
"Called backup on the storage-sync-v2 Sqlite connection"
);
await maybeRemovePath(stagingPath);
await maybeRemovePath(sourcePath);
sandbox.restore();
});
/**
* Test that the recover method correctly copies items from the recovery
* directory into the destination profile directory.
*/
add_task(async function test_recover() {
let addonsBackupResource = new AddonsBackupResource();
let recoveryPath = await IOUtils.createUniqueDirectory(
PathUtils.tempDir,
"addonsBackupResource-recovery-test"
);
let destProfilePath = await IOUtils.createUniqueDirectory(
PathUtils.tempDir,
"addonsBackupResource-test-profile"
);
const files = [
{ path: "extensions.json" },
{ path: "extension-settings.json" },
{ path: "extension-preferences.json" },
{ path: "addonStartup.json.lz4" },
{ path: "storage-sync-v2.sqlite" },
{ path: ["browser-extension-data", "addon@darkreader.org.xpi", "data"] },
{ path: ["extensions", "addon@darkreader.org.xpi"] },
{ path: ["extension-store-permissions", "data.safe.bin"] },
];
await createTestFiles(recoveryPath, files);
// The backup method is expected to have returned a null ManifestEntry
let postRecoveryEntry = await addonsBackupResource.recover(
null /* manifestEntry */,
recoveryPath,
destProfilePath
);
Assert.equal(
postRecoveryEntry,
null,
"AddonsBackupResource.recover should return null as its post " +
"recovery entry"
);
await assertFilesExist(destProfilePath, files);
await maybeRemovePath(recoveryPath);
await maybeRemovePath(destProfilePath);
});