Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
/* Any copyright is dedicated to the Public Domain.
"use strict";
const { _ExperimentFeature: ExperimentFeature } = ChromeUtils.importESModule(
"resource://nimbus/ExperimentAPI.sys.mjs"
);
const { NimbusTestUtils } = ChromeUtils.importESModule(
);
const { PreferencesBackupResource } = ChromeUtils.importESModule(
"resource:///modules/backup/PreferencesBackupResource.sys.mjs"
);
NimbusTestUtils.init(this);
const USER_TYPED_FEATURE = new ExperimentFeature("test-typed-prefs", {
description: "Test feature that sets each type of pref",
owner: "test@test.test",
hasExposure: false,
variables: {
string: {
type: "string",
description: "test string variable",
setPref: {
branch: "user",
pref: "nimbus-test.types.string",
},
},
int: {
type: "int",
description: "test int variable",
setPref: {
branch: "user",
pref: "nimbus-test.types.int",
},
},
boolean: {
type: "boolean",
description: "test boolean variable",
setPref: {
branch: "user",
pref: "nimbus-test.types.boolean",
},
},
json: {
type: "json",
description: "test json variable",
setPref: {
branch: "user",
pref: "nimbus-test.types.json",
},
},
},
});
function getPrefsFromMap(prefs) {
return prefs.map(([pref, value]) => {
if (!Services.prefs.prefHasUserValue(pref)) {
return [pref, undefined];
}
switch (typeof value) {
case "boolean":
return [pref, Services.prefs.getBoolPref(pref)];
case "number":
return [pref, Services.prefs.getIntPref(pref)];
case "string":
return [pref, Services.prefs.getStringPref(pref)];
default:
throw new Error("Unsupported pref type!");
}
});
}
function setPrefsFromMap(prefs) {
for (let [pref, value] of prefs) {
if (value === undefined) {
Services.prefs.clearUserPref(pref);
continue;
}
switch (typeof value) {
case "boolean":
Services.prefs.setBoolPref(pref, value);
break;
case "number":
Services.prefs.setIntPref(pref, value);
break;
case "string":
Services.prefs.setStringPref(pref, value);
break;
default:
throw new Error("Unsupported pref type!");
}
}
}
const fakeNimbusTestPrefs = [
["nimbus-test.types.boolean", false],
["nimbus-test.types.int", 100],
["nimbus-test.types.string", "bar"],
["nimbus.all_prefs_with_nimbus._prefix_should_be_removed", "not serialized"],
];
// NB: Nimbus experiment setup is adapted from
// test_setPref_getOriginalPrefValuesForAllExperiments.
function initFakeNimbusExperimentPrefs() {
const originalPrefs = getPrefsFromMap(fakeNimbusTestPrefs);
setPrefsFromMap(fakeNimbusTestPrefs);
return originalPrefs;
}
function uninitFakeNimbusExperimentPrefs(originalPrefs) {
setPrefsFromMap(originalPrefs);
}
const jsonTestValue = {
foo: "foo",
bar: 12345,
baz: true,
qux: null,
quux: ["corge"],
};
async function enrollFakeNimbusExperiment() {
const featureCleanup = NimbusTestUtils.addTestFeatures(USER_TYPED_FEATURE);
const { manager, cleanup } = await NimbusTestUtils.setupTest();
const experimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig(
{
featureId: USER_TYPED_FEATURE.featureId,
value: {
string: "hello, world",
int: 12345,
boolean: true,
json: jsonTestValue,
},
},
{ manager }
);
return { featureCleanup, experimentCleanup, cleanup };
}
async function unenrollFakeNimbusExperiment({
featureCleanup,
experimentCleanup,
cleanup,
}) {
await experimentCleanup();
featureCleanup();
await cleanup();
}
function checkNimbusHasSetPrefs() {
Assert.equal(
Services.prefs.getPrefType("nimbus-test.types.string"),
Services.prefs.PREF_STRING
);
Assert.equal(
Services.prefs.getStringPref("nimbus-test.types.string"),
"hello, world"
);
Assert.equal(
Services.prefs.getPrefType("nimbus-test.types.int"),
Services.prefs.PREF_INT
);
Assert.equal(Services.prefs.getIntPref("nimbus-test.types.int"), 12345);
Assert.equal(
Services.prefs.getPrefType("nimbus-test.types.boolean"),
Services.prefs.PREF_BOOL
);
Assert.equal(Services.prefs.getBoolPref("nimbus-test.types.boolean"), true);
Assert.equal(
Services.prefs.getPrefType("nimbus-test.types.json"),
Services.prefs.PREF_STRING
);
const parsedJson = JSON.parse(
Services.prefs.getStringPref("nimbus-test.types.json")
);
Assert.deepEqual(jsonTestValue, parsedJson);
}
async function checkBackupIgnoredNimbusPrefs(backupPrefsFilePath) {
let contents = (await IOUtils.readUTF8(backupPrefsFilePath))
.split("\n")
.map(line => line.trim());
Assert.ok(
contents.includes('user_pref("nimbus-test.types.boolean", false);')
);
Assert.ok(contents.includes('user_pref("nimbus-test.types.int", 100);'));
Assert.ok(contents.includes('user_pref("nimbus-test.types.string", "bar");'));
let checkPrefNotSerialized = pref => {
Assert.ok(
!contents.reduce((acc, line) => acc || line.includes(pref), false)
);
};
// nimbus-test.types.json had no prior value so it should not appear.
checkPrefNotSerialized("nimbus-test.types.json");
// nimbus metadata should not appear.
checkPrefNotSerialized("app.normandy.user_id");
checkPrefNotSerialized("toolkit.telemetry.cachedProfileGroupID");
// Prefs that start with "nimbus." are assumed to be internal and should
// not be included.
checkPrefNotSerialized(
"nimbus.all_prefs_with_nimbus._prefix_should_be_removed"
);
}
async function performBackup() {
let sandbox = sinon.createSandbox();
let preferencesBackupResource = new PreferencesBackupResource();
let sourcePath = await IOUtils.createUniqueDirectory(
PathUtils.tempDir,
"PreferencesBackupResource-source-test"
);
let stagingPath = await IOUtils.createUniqueDirectory(
PathUtils.tempDir,
"PreferencesBackupResource-staging-test"
);
const simpleCopyFiles = [
{ path: "xulstore.json" },
{ path: "containers.json" },
{ path: "handlers.json" },
{ path: "search.json.mozlz4" },
{ path: "user.js" },
{ path: ["chrome", "userChrome.css"] },
{ path: ["chrome", "userContent.css"] },
{ path: ["chrome", "childFolder", "someOtherStylesheet.css"] },
];
await createTestFiles(sourcePath, simpleCopyFiles);
// Create our fake database files. We don't expect these 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: "permissions.sqlite" },
{ path: "content-prefs.sqlite" },
]);
// We have no need to test that Sqlite.sys.mjs's backup method is working -
// this is something that is tested in Sqlite's own tests. We can just make
// sure that it's being called using sinon. Unfortunately, we cannot do the
// same thing with IOUtils.copy, as its methods are not stubbable.
let fakeConnection = {
backup: sandbox.stub().resolves(true),
close: sandbox.stub().resolves(true),
};
sandbox.stub(Sqlite, "openConnection").returns(fakeConnection);
await preferencesBackupResource.backup(stagingPath, sourcePath);
return { sandbox, stagingPath, sourcePath };
}
/**
* Test that the backup method correctly serializes preference values that are
* manipulated by Nimbus experiments. Nimbus-set prefs should ignore the
* Nimbus-set values.
*/
add_task(async function test_prefs_backup_does_not_backup_nimbus_prefs() {
// Pre-set some nimbus-test.* prefs so they have originalValues that
// should be backed up instead of the values Nimbus will set when we enroll
// in the experiment.
const originalPrefs = initFakeNimbusExperimentPrefs();
const cleanup = await enrollFakeNimbusExperiment();
checkNimbusHasSetPrefs();
const { sandbox, stagingPath, sourcePath } = await performBackup();
await checkBackupIgnoredNimbusPrefs(PathUtils.join(stagingPath, "prefs.js"));
await maybeRemovePath(stagingPath);
await maybeRemovePath(sourcePath);
sandbox.restore();
await unenrollFakeNimbusExperiment(cleanup);
uninitFakeNimbusExperimentPrefs(originalPrefs);
});