Source code

Revision control

Copy as Markdown

Other Tools

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Generates backup fixtures with test data for compatibility testing.
This is not part of the regular test suite - run manually before bumping SCHEMA_VERSION
to create fixtures of the current version.
To generate fixtures:
1. Ensure you're on the commit BEFORE bumping SCHEMA_VERSION
2. Add entry to VERSION_CONFIG in compat_config.py
3. Run: ./mach test browser/components/backup/tests/marionette/compat-files/test_generate_backup_fixture.py --headless
4. Legacy and selectable fixtures will be saved to backups/
5. Add the new fixtures to manifest.toml support-files
6. Add test methods to test_compatibility.py
"""
import os
import shutil
import sys
import tempfile
from pathlib import Path
import mozfile
sys.path.append(os.fspath(Path(__file__).parents[1]))
from backup_test_base import BackupTestBase
from compat_config import VERSION_CONFIG
class GenerateTestBackup(BackupTestBase):
"""Generate test backup fixtures for compatibility testing."""
def test_generate_current_version_backup(self):
"""Auto-detect schema version and generate both fixture types."""
version = self.run_code(
"""
const { ArchiveUtils } = ChromeUtils.importESModule(
"resource:///modules/backup/ArchiveUtils.sys.mjs"
);
return ArchiveUtils.SCHEMA_VERSION;
"""
)
self.logger.info(f"Detected SCHEMA_VERSION: {version}")
self.assertIn(
version,
VERSION_CONFIG,
f"VERSION_CONFIG has no entry for schema version {version}. "
f"Add one to compat_config.py before generating fixtures.",
)
self._generate_legacy_fixture(version)
self.marionette.restart(in_app=False, clean=True)
self.marionette.set_context("chrome")
self._generate_selectable_fixture(version)
def _generate_legacy_fixture(self, version):
"""Generate a legacy (non-selectable-profile) backup fixture."""
config = VERSION_CONFIG[version]
self.logger.info(f"Generating legacy fixture for v{version}")
self._add_common_test_data(version)
for extra in config["extra_data_legacy"]:
add_method = getattr(self, f"_add_{extra}_data")
add_method(version)
self.marionette.restart()
self.marionette.set_context("chrome")
archive_path = self._create_encrypted_backup(config["recovery_password"])
self.logger.info(f"Created legacy backup at: {archive_path}")
output_path = self._save_fixture(archive_path, f"v{version}_legacy_backup.html")
self.assertTrue(
os.path.exists(output_path), f"Legacy fixture should exist at {output_path}"
)
self.logger.info(f"Legacy fixture saved: {output_path}")
def _generate_selectable_fixture(self, version):
"""Generate a selectable-profile backup fixture."""
config = VERSION_CONFIG[version]
self.logger.info(f"Generating selectable fixture for v{version}")
profile_name = self.register_profile_and_restart()
self._cleanups.append({"profile_name": profile_name})
self.setup_selectable_profile()
self._add_common_test_data(version)
for extra in config["extra_data_legacy"]:
add_method = getattr(self, f"_add_{extra}_data")
add_method(version)
for extra in config["extra_data_selectable"]:
add_method = getattr(self, f"_add_{extra}_data")
add_method(version)
self.marionette.restart(clean=False, in_app=True)
self.marionette.set_context("chrome")
self.marionette.enforce_gecko_prefs({
"browser.backup.enabled": True,
"browser.backup.log": True,
"browser.backup.archive.enabled": True,
"browser.backup.restore.enabled": True,
"browser.backup.profiles.force-enable": True,
})
archive_path = self._create_encrypted_backup(config["recovery_password"])
self.logger.info(f"Created selectable backup at: {archive_path}")
output_path = self._save_fixture(
archive_path, f"v{version}_selectable_backup.html"
)
self.assertTrue(
os.path.exists(output_path),
f"Selectable fixture should exist at {output_path}",
)
self.logger.info(f"Selectable fixture saved: {output_path}")
def _add_selectable_profile_metadata_data(self, version):
self.set_selectable_profile_metadata(f"V{version} Test Profile", "book")
def _add_common_test_data(self, version):
prefix = f"v{version}-test"
self.marionette.execute_async_script(
"""
const { PlacesUtils } = ChromeUtils.importESModule(
"resource://gre/modules/PlacesUtils.sys.mjs"
);
let [prefix, version, outerResolve] = arguments;
(async () => {
Services.logins.removeAllLogins();
const nsLoginInfo = new Components.Constructor(
"@mozilla.org/login-manager/loginInfo;1",
Ci.nsILoginInfo,
"init"
);
const login = new nsLoginInfo(
null,
`${prefix}-user`,
`${prefix}-password`,
"user",
"pass"
);
await Services.logins.addLoginAsync(login);
await PlacesUtils.bookmarks.eraseEverything();
await PlacesUtils.bookmarks.insert({
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
title: `V${version} Compatibility Test Bookmark`,
url: Services.io.newURI(`https://${prefix}.example.com/bookmark`),
});
await PlacesUtils.history.clear();
await PlacesUtils.history.insertMany([{
visits: [{ transition: PlacesUtils.history.TRANSITION_LINK }],
}]);
Services.prefs.setBoolPref(`test.v${version}.compatibility.pref`, true);
})().then(outerResolve);
""",
script_args=[prefix, version],
)
def _create_encrypted_backup(self, recovery_password):
dest = os.path.join(tempfile.gettempdir(), "backup-fixture-dest")
return self.run_async(
"""
const { OSKeyStore } = ChromeUtils.importESModule(
"resource://gre/modules/OSKeyStore.sys.mjs"
);
const { BackupService } = ChromeUtils.importESModule(
"resource:///modules/backup/BackupService.sys.mjs"
);
let [destPath, recoveryCode] = arguments;
OSKeyStore.STORE_LABEL = "test-" + Math.random().toString(36).substr(2);
let bs = BackupService.init();
bs.setParentDirPath(destPath);
await bs.enableEncryption(recoveryCode);
let result = await bs.createBackup();
if (!result) {
throw new Error(
"createBackup returned null; archiveEnabledStatus: " +
JSON.stringify(bs.archiveEnabledStatus)
);
}
await OSKeyStore.cleanup();
return result.archivePath;
""",
script_args=[dest, recovery_password],
)
def _save_fixture(self, archive_path, filename):
test_dir = os.path.dirname(__file__)
backups_dir = os.path.join(test_dir, "backups")
os.makedirs(backups_dir, exist_ok=True)
output_path = os.path.join(backups_dir, filename)
shutil.copy(archive_path, output_path)
mozfile.remove(archive_path)
return output_path