const PREF_FEATURES = {
[USER]: new ExperimentFeature("test-set-pref-user-1", {
description: "Test feature that sets prefs on the user branch via setPref",
owner: "test@test.test",
hasExposure: false,
variables: {
foo: {
type: "string",
description: "test variable",
setPref: {
branch: USER,
pref: "nimbus.test-only.foo",
},
},
},
}),
[DEFAULT]: new ExperimentFeature("test-set-pref-default-1", {
description: "Test feature that sets prefs on the default branch via setPref",
owner: "test@test.test",
hasExposure: false,
variables: {
foo: {
type: "string",
description: "test variable",
setPref: {
branch: DEFAULT,
pref: "nimbus.test-only.foo",
},
},
},
}),
};
function setPrefs(prefs) { for (const [name, { userBranchValue, defaultBranchValue }] of Object.entries(
prefs
)) { // If the different prefs have the same value, we must set the user branch // value first. Otherwise when we try to set the user branch value after // the default value, it will see the value already set for the user // branch (because it falls back to the default branch value) and will not // set it, leaving only a default branch pref. if (typeof userBranchValue !== "undefined") {
PrefUtils.setPref(name, userBranchValue);
}
function cleanupPrefs(prefs) { for (const name of Object.keys(prefs)) {
Services.prefs.deleteBranch(name);
}
}
function checkExpectedPrefs(prefs) { for (const [name, value] of Object.entries(prefs)) { Assert.equal(
PrefUtils.getPref(name),
value,
`Pref ${name} has correct value`
);
}
}
function checkExpectedPrefBranches(prefs) { for (const [
name,
{ defaultBranchValue = null, userBranchValue = null },
] of Object.entries(prefs)) { if (userBranchValue === null) { Assert.ok(
!Services.prefs.prefHasUserValue(name),
`Pref ${name} has no value on user branch`
);
} else { Assert.equal(
PrefUtils.getPref(name, { branch: USER }),
userBranchValue,
`Pref ${name} has correct value on user branch`
);
}
if (defaultBranchValue === null) { Assert.ok(
!Services.prefs.prefHasDefaultValue(name),
`Pref ${name} has no value on default branch`
);
} else { Assert.equal(
PrefUtils.getPref(name, { branch: DEFAULT }),
defaultBranchValue,
`Pref ${name} has correct value on default branch`
);
}
}
}
/** *Generateacomparisonfunctionfor`Array.prototype.sort`. * *@param{string}property * *@returns{(a:object,b:object)=>number}Thecomparisonfunction.
*/ function compareBy(property) { return (a, b) => a[property].localeCompare(b[property]);
}
const TEST_CASES = [
{
name: "Set prefs on the user branch",
featureValue: setUserPrefs,
},
{
name: "Set prefs on the user branch with pre-existing values on the user branch",
featureValue: setUserPrefs,
setPrefsBefore: PRE_SET_PREFS[USER],
},
{
name: "Set prefs on the user branch with pre-existing values on the default branch",
featureValue: setUserPrefs,
setPrefsBefore: PRE_SET_PREFS[DEFAULT],
},
{
name: "Set prefs on the user branch with pre-existing values on both branches",
featureValue: setUserPrefs,
setPrefsBefore: PRE_SET_PREFS.BOTH_BRANCHES,
},
{
name: "Set prefs on the default branch",
featureValue: setDefaultPrefs,
},
{
name: "Set prefs on the default branch with pre-existing values on the default branch",
featureValue: setDefaultPrefs,
setPrefsBefore: PRE_SET_PREFS[DEFAULT],
},
{
name: "Set prefs on the default branch with pre-existing values on the user branch",
featureValue: setDefaultPrefs,
setPrefsBefore: PRE_SET_PREFS[USER],
expectedPrefs: {
[STRING_PREF]: PRE_SET_PREFS[USER][STRING_PREF].userBranchValue,
[INT_PREF]: PRE_SET_PREFS[USER][INT_PREF].userBranchValue,
[BOOL_PREF]: PRE_SET_PREFS[USER][BOOL_PREF].userBranchValue,
},
},
{
name: "Set prefs on the default branch with pre-existing values on both branches",
featureValue: setDefaultPrefs,
setPrefsBefore: PRE_SET_PREFS.BOTH_BRANCHES,
expectedPrefs: {
[STRING_PREF]: PRE_SET_PREFS.BOTH_BRANCHES[STRING_PREF].userBranchValue,
[INT_PREF]: PRE_SET_PREFS.BOTH_BRANCHES[INT_PREF].userBranchValue,
[BOOL_PREF]: PRE_SET_PREFS.BOTH_BRANCHES[BOOL_PREF].userBranchValue,
},
},
{
name: "Clearing prefs on the user branch (with value null) without pre-existing values",
featureValue: clearUserPrefs,
},
{
name: "Clearing prefs on the user branch (with value null) with pre-existing values on the user branch",
featureValue: clearUserPrefs,
setPrefsBefore: PRE_SET_PREFS[USER],
},
{
name: "Clearing prefs on the user branch (with value null) with pre-existing values on the default branch",
featureValue: clearUserPrefs,
setPrefsBefore: PRE_SET_PREFS[DEFAULT], // This will not affect the default branch prefs.
expectedPrefs: {
[STRING_PREF]: PRE_SET_PREFS[DEFAULT][STRING_PREF].defaultBranchValue,
[INT_PREF]: PRE_SET_PREFS[DEFAULT][INT_PREF].defaultBranchValue,
[BOOL_PREF]: PRE_SET_PREFS[DEFAULT][BOOL_PREF].defaultBranchValue,
},
},
{
name: "Clearing prefs on the user branch (with value null) with pre-existing values on both branches",
featureValue: clearUserPrefs,
setPrefsBefore: PRE_SET_PREFS.BOTH_BRANCHES,
expectedPrefs: {
[STRING_PREF]:
PRE_SET_PREFS.BOTH_BRANCHES[STRING_PREF].defaultBranchValue,
[INT_PREF]: PRE_SET_PREFS.BOTH_BRANCHES[INT_PREF].defaultBranchValue,
[BOOL_PREF]: PRE_SET_PREFS.BOTH_BRANCHES[BOOL_PREF].defaultBranchValue,
},
},
];
for (const [i, { name, ...testCase }] of TEST_CASES.entries()) {
info(`Running test case ${i}: ${name}`);
const { // The feature config to enroll.
featureValue,
// Prefs that should be set before enrollment. These will be undone after // each test case.
setPrefsBefore = {},
// Additional prefs to check after enrollment. They will be checked on the // user branch.
expectedPrefs = {},
} = testCase;
const { manager, cleanup } = await setupTest();
info("Setting initial values of prefs...");
setPrefs(setPrefsBefore);
// Collect the values of any prefs that will be set by the enrollment so we // can compare their values after unenrollment. const prefValuesBeforeEnrollment = Object.fromEntries(
Object.keys(featureValue.prefs).map(prefName => [
prefName,
PrefUtils.getPref(prefName),
])
);
info("Checking prefs were restored after unenrollment..."); // After unenrollment, the prefs should have been restored to their values // before enrollment. for (const [prefName, originalValue] of Object.entries(
prefValuesBeforeEnrollment
)) { // If the pref was set on the default branch, it won't be cleared. It will // persist until the next restart. const expectedValue =
featureValue.prefs[prefName].branch === "default" &&
originalValue === null
? featureValue.prefs[prefName].value
: originalValue; Assert.equal(PrefUtils.getPref(prefName), expectedValue);
}
info("Cleaning up..."); // Clear all the prefs we specified in `setPrefsBefore`.
cleanupPrefs(setPrefsBefore);
// Clear all prefs specified by the enrollment. for (const prefName of Object.keys(featureValue.prefs)) {
Services.prefs.deleteBranch(prefName);
}
// NB: We don't need to test setPref experiments/rollouts on both branches // for the same pref because that configuration is prohibited by // gen_feature_manifests.py
// * prefFlip experiments -> setPref experiment
{
name: "enroll in prefFlips experiments on the user branch and then a setPref experiment on the user branch",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: USER } },
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: USER } },
},
},
{
slug: SLUG_3,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
],
expectedEnrollments: [SLUG_3],
expectedUnenrollments: [SLUG_1, SLUG_2],
expectedPrefs: { [PREF_FOO]: EXPERIMENT_VALUE },
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_3 },
},
{
name: "enroll in prefFlips experiments on the default branch and then a setPref experiment on the user branch",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: DEFAULT } },
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: DEFAULT } },
},
},
{
slug: SLUG_3,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
],
expectedEnrollments: [SLUG_3],
expectedUnenrollments: [SLUG_1, SLUG_2],
expectedPrefs: { [PREF_FOO]: EXPERIMENT_VALUE },
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_3 },
},
{
name: "enroll in prefFlips experiments on the user branch and then a setPref experiment on the default branch",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: USER } },
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: USER } },
},
},
{
slug: SLUG_3,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
],
expectedEnrollments: [SLUG_3],
expectedUnenrollments: [SLUG_1, SLUG_2],
expectedPrefs: { [PREF_FOO]: EXPERIMENT_VALUE },
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_3 },
},
{
name: "enroll in prefFlips experiments on the default branch and then a setPref experiment on the default branch",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: DEFAULT } },
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: DEFAULT } },
},
},
{
slug: SLUG_3,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
],
expectedEnrollments: [SLUG_3],
expectedUnenrollments: [SLUG_1, SLUG_2],
expectedPrefs: { [PREF_FOO]: EXPERIMENT_VALUE },
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_3 },
},
{
name: "enroll in prefFlip experiment on the user branch and then a setPref experiment on the user branch and unenroll to check if original values are restored (no original value)",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_1, branch: USER },
},
},
},
{
slug: SLUG_2,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: SLUG_2,
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: null,
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in prefFlip experiment on the user branch and then a setPref experiment on the default branch and unenroll to check if original values are restored (no original value)",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_1, branch: USER },
},
},
},
{
slug: SLUG_2,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: SLUG_2,
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SLUG_2, // we can't clear the default branch
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in prefFlip experiment on the default branch and then a setPref experiment on the user branch and unenroll to check if original values are restored (no original value)",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_1, branch: USER },
},
},
},
{
slug: SLUG_2,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: SLUG_2,
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: null,
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in prefFlip experiment on the default branch and then a setPref experiment on the default branch and unenroll to check if original values are restored (no original value)",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_1, branch: USER },
},
},
},
{
slug: SLUG_2,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: SLUG_2,
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SLUG_2,
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in prefFlip experiment on the user branch and then a setPref experiment on the user branch and unenroll to check if original values are restored",
setPrefsBefore: { [PREF_FOO]: { userBranchValue: SET_BEFORE_VALUE } },
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_1, branch: USER },
},
},
},
{
slug: SLUG_2,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: SLUG_2,
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SET_BEFORE_VALUE,
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in prefFlip experiment on the user branch and then a setPref experiment on the default branch and unenroll to check if original values are restored",
setPrefsBefore: { [PREF_FOO]: { defaultBranchValue: SET_BEFORE_VALUE } },
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_1, branch: USER },
},
},
},
{
slug: SLUG_2,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: SLUG_2,
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SET_BEFORE_VALUE,
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in prefFlip experiment on the default branch and then a setPref experiment on the user branch and unenroll to check if original values are restored",
setPrefsBefore: { [PREF_FOO]: { defaultBranchValue: SET_BEFORE_VALUE } },
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_1, branch: USER },
},
},
},
{
slug: SLUG_2,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: SLUG_2,
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SET_BEFORE_VALUE,
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in prefFlip experiment on the default branch and then a setPref experiment on the default branch and unenroll to check if original values are restored",
setPrefsBefore: { [PREF_FOO]: { defaultBranchValue: SET_BEFORE_VALUE } },
enrollmentOrder: [
{
slug: SLUG_1,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_1, branch: USER },
},
},
},
{
slug: SLUG_2,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: SLUG_2,
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SET_BEFORE_VALUE,
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
}, // * setPref experiment, rollout -> prefFlips experiment
{
name: "enroll in a setPref experiment and rollout on the user branch then a prefFlips experiment on the user branch",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_2,
isRollout: true,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_3,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: USER } },
},
},
],
expectedEnrollments: [SLUG_3],
expectedUnenrollments: [SLUG_1, SLUG_2],
expectedPrefs: { [PREF_FOO]: EXPERIMENT_VALUE },
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_3 },
},
{
name: "enroll in a setPref experiment and rollout on the user branch then a prefFlips experiment on the default branch",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_2,
isRollout: true,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_3,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: DEFAULT } },
},
},
],
expectedEnrollments: [SLUG_3],
expectedUnenrollments: [SLUG_1, SLUG_2],
expectedPrefs: { [PREF_FOO]: EXPERIMENT_VALUE },
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_3 },
},
{
name: "enroll in a setPref experiment and rollout on the default branch then a prefFlips experiment on the user branch",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_2,
isRollout: true,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_3,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: USER } },
},
},
],
expectedEnrollments: [SLUG_3],
expectedUnenrollments: [SLUG_1, SLUG_2],
expectedPrefs: { [PREF_FOO]: EXPERIMENT_VALUE },
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_3 },
},
{
name: "enroll in a setPref experiment and rollout on the default branch then a prefFlips experiment on the default branch",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_2,
isRollout: true,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_3,
featureId: FEATURE_ID,
value: {
prefs: { [PREF_FOO]: { value: EXPERIMENT_VALUE, branch: DEFAULT } },
},
},
],
expectedEnrollments: [SLUG_3],
expectedUnenrollments: [SLUG_1, SLUG_2],
expectedPrefs: { [PREF_FOO]: EXPERIMENT_VALUE },
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_3 },
},
{
name: "enroll in a setPref experiment on the user branch and a prefFlips experiment on the user branch and unenroll to check if original values are restored (no original value)",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: SLUG_1,
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_2, branch: USER },
},
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: null, // we can't clear the default branch
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in a setPref experiment on the user branch and a prefFlips experiment on the default branch and unenroll to check if original values are restored (no original value)",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: SLUG_1,
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_2, branch: DEFAULT },
},
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SLUG_2, // Cannot clear the default branch
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in a setPref experiment on the default branch and a prefFlips experiment on the user branch and unenroll to check if original values are restored (no original value)",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: SLUG_1,
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_2, branch: USER },
},
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SLUG_1, // cannot clear the default branch
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in a setPref experiment on the default branch and a prefFlips experiment on the default branch and unenroll to check if original values are restored (no original value)",
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: SLUG_1,
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: SLUG_2, branch: DEFAULT },
},
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SLUG_2, // cannot clear the default branch
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in a setPref experiment on the user branch and a prefFlips experiment on the user branch and unenroll to check if original values are restored",
setPrefsBefore: {
[PREF_FOO]: { userBranchValue: SET_BEFORE_VALUE },
},
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: EXPERIMENT_VALUE, branch: USER },
},
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SET_BEFORE_VALUE,
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in a setPref experiment on the user branch and a prefFlips experiment on the default branch and unenroll to check if original values are restored",
setPrefsBefore: {
[PREF_FOO]: { userBranchValue: SET_BEFORE_VALUE },
},
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[USER].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: EXPERIMENT_VALUE, branch: DEFAULT },
},
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SET_BEFORE_VALUE,
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in a setPref experiment on the default branch and a prefFlips experiment on the user branch and unenroll to check if original values are restored",
setPrefsBefore: {
[PREF_FOO]: { defaultBranchValue: SET_BEFORE_VALUE },
},
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: EXPERIMENT_VALUE, branch: USER },
},
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SET_BEFORE_VALUE,
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
{
name: "enroll in a setPref experiment on the default branch and a prefFlips experiment on the default branch and unenroll to check if original values are restored",
setPrefsBefore: {
[PREF_FOO]: { defaultBranchValue: SET_BEFORE_VALUE },
},
enrollmentOrder: [
{
slug: SLUG_1,
featureId: PREF_FEATURES[DEFAULT].featureId,
value: {
foo: EXPERIMENT_VALUE,
},
},
{
slug: SLUG_2,
featureId: FEATURE_ID,
value: {
prefs: {
[PREF_FOO]: { value: EXPERIMENT_VALUE, branch: DEFAULT },
},
},
},
],
expectedEnrollments: [SLUG_2],
expectedUnenrollments: [SLUG_1],
unenrollmentOrder: [SLUG_2],
expectedPrefs: {
[PREF_FOO]: SET_BEFORE_VALUE,
},
unenrollReason: UnenrollReason.PREF_FLIPS_CONFLICT,
unenrollTelemetry: { conflicting_slug: SLUG_2 },
},
];
for (const [i, { name, ...testCase }] of TEST_CASES.entries()) {
info(`Running test case ${i}: ${name}`);
const { // Prefs that should be set after enrollment. These will be undone after // each test case.
setPrefsBefore = {}, // The slugs to enroll in the order they should be enrolled in, and // whether or not they should enroll as rollouts.
enrollmentOrder, // Prefs that should be set after enrollment. These will be undone // after each test case.
setPrefsAfter = {}, // The expected active enrollments after all enrollments have finished.
expectedEnrollments = [], // The expected inactive enrollments after all enrollments have finished.
expectedUnenrollments = [], // The slugs to unenroll from after enrolling and settings prefs but // before checking pref values.
unenrollmentOrder, // Prefs to check after enrollment. They will be checked on the user // branch.
expectedPrefs, // The reason for unenrollment.
unenrollReason, // Extra keys in the unenrollment telemetry.
unenrollTelemetry = {},
} = testCase;
info("Setting prefs before enrollment...");
setPrefs(setPrefsBefore);
info("Setting prefs after enrollment...");
setPrefs(setPrefsAfter);
info("Checking expected enrollments..."); for (const slug of expectedEnrollments) { const enrollment = manager.store.get(slug);
Assert.ok(
enrollment !== null && typeof enrollment !== "undefined",
`An enrollment for ${slug} should exist`
); Assert.ok(enrollment.active, `It should still be active`);
}
info("Checking expected unenrollments..."); for (const slug of expectedUnenrollments) { const enrollment = manager.store.get(slug);
Assert.ok(!!enrollment, `An enrollment for ${slug} should exist`); Assert.ok(!enrollment.active, "It should no longer be active"); Assert.equal(
enrollment.unenrollReason,
unenrollReason, "It should have unenrolled for the correct reason"
);
}
let expectedCurrentEnrollments = new Set(expectedEnrollments).difference( new Set(expectedUnenrollments)
);
await NimbusTestUtils.assert.activeEnrollments(
Array.from(expectedCurrentEnrollments)
);
if (unenrollmentOrder) {
info("Unenrolling from specific experiments before checking prefs..."); for (const slug of unenrollmentOrder ?? []) {
manager.unenroll(slug);
}
}
expectedCurrentEnrollments = expectedCurrentEnrollments.difference( new Set(unenrollmentOrder)
);
await NimbusTestUtils.assert.activeEnrollments(
Array.from(expectedCurrentEnrollments)
);
if (expectedPrefs) {
info("Checking expected prefs...");
checkExpectedPrefs(expectedPrefs);
}
info("Unenrolling from active experiments..."); for (const slug of expectedEnrollments) { if (!(unenrollmentOrder ?? []).includes(slug)) {
info(`Unenrolling from ${slug}\n`);
manager.unenroll(slug);
}
}
// Set the pref controlled by the experiment.
Services.prefs.setStringPref("test.pref.please.ignore", "test-value");
}),
secureExperiments: [recipe],
migrationState: NimbusTestUtils.migrationState.LATEST,
});
// Unenrolling is triggered by the PrefFlipsFeature in response to the // enrollment being added to the store and triggering the feature update // callback. // // That callback triggers an async unenroll() without awaiting (because it // wouldn't block the ExperimentStore anyway) so we have to wait for the // unenroll to be propagated to the database first.
await NimbusTestUtils.assert.enrollmentExists(recipe.slug, { active: false });
const enrollment = manager.store.get(recipe.slug); Assert.ok(!enrollment.active, "Experiment should not be active");
// Unenrolling is triggered by the PrefFlipsFeature in response to the // enrollment being added to the store and triggering the feature update // callback. // // That callback triggers an async unenroll() without awaiting (because it // wouldn't block the ExperimentStore anyway) so we have to wait for the // unenroll to be propagated to the database first.
await NimbusTestUtils.assert.enrollmentExists(recipe.slug, { active: false });
const enrollment = manager.store.get(recipe.slug); Assert.ok(!enrollment.active, "Experiment should not be active");
Assert.deepEqual(
setPrefSpy.getCall(0).args,
[GOOD_PREF, 123, { branch: USER }],
`should set ${GOOD_PREF}`
); Assert.deepEqual(
setPrefSpy.getCall(1).args,
[BAD_PREF, 123, { branch: USER }],
`should have attempted to set ${BAD_PREF}`
); Assert.notStrictEqual( typeof setPrefSpy.getCall(1).exception, "undefined",
`Attempting to set ${BAD_PREF} threw`
); Assert.deepEqual(
setPrefSpy.getCall(2).args,
[GOOD_PREF, null, { branch: USER }],
`should reset ${GOOD_PREF}`
); Assert.equal(
setPrefSpy.callCount, 3, "should have 3 calls to PrefUtils.setPref"
);
Assert.ok(
!Services.prefs.prefHasUserValue(GOOD_PREF),
`${GOOD_PREF} should not be set`
); Assert.equal(Services.prefs.getStringPref(BAD_PREF), "test-value");
Assert.ok(rolloutEnrollment.active, "Rollout is active"); Assert.ok(!experimentEnrollment.active, "Experiment is inactive"); Assert.equal(experimentEnrollment.unenrollReason, "prefFlips-failed");
Assert.ok(manager.store.get("rollout-1").active, "rollout-1 is active"); Assert.ok(manager.store.get("rollout-2").active, "rollout-2 is active"); Assert.ok(manager.store.get("rollout-3").active, "rollout-3 is active"); Assert.ok(manager.store.get("rollout-4").active, "rollout-4 is active");
Assert.equal(
Services.prefs.getStringPref(PREF_1),
PREF_1,
`${PREF_1} has the correct value`
); Assert.equal(
Services.prefs.getStringPref(PREF_2),
PREF_2,
`${PREF_2} has the correct value`
); Assert.equal(
Services.prefs.getStringPref(PREF_3),
PREF_3,
`${PREF_3} has the correct value`
); Assert.equal(
Services.prefs.getStringPref(PREF_4),
PREF_4,
`${PREF_4} has the correct value`
);
Assert.equal(
PrefUtils.getPref(PREF_1), null,
`${PREF_1} has the correct value after unenrollment`
); Assert.equal(
PrefUtils.getPref(PREF_2), "original-pref-2-value",
`${PREF_2} has the correct value after unenrollment`
); Assert.equal(
PrefUtils.getPref(PREF_3),
PREF_3,
`${PREF_3} has the correct value after unenrollment (can't reset default branch)`
); Assert.equal(
PrefUtils.getPref(PREF_4), "original-pref-4-value",
`${PREF_4} has the correct value`
);
await cleanup();
});
add_task(async function test_prefFlips_restore_failure_conflict() { const PREF = "pref.foo.bar";
Assert.ok(manager.store.get("rollout-1").active, "rollout-1 is active"); Assert.ok(!manager.store.get("rollout-2").active, "rollout-2 is not active"); Assert.equal(
manager.store.get("rollout-2").unenrollReason, "prefFlips-failed"
); Assert.ok(!manager.store.get("rollout-3").active, "rollout-3 is not active"); Assert.equal(
manager.store.get("rollout-3").unenrollReason, "prefFlips-failed"
);
Assert.equal(
PrefUtils.getPref(PREF), "correct-value",
`${PREF} has the correct value`
);
Assert.equal(
PrefUtils.getPref(PREF), null,
`${PREF} has the correct value after unenrollment`
);
await cleanup();
});
// Test the case where an experiment sets a default branch pref, but the user
// changed their user.js between restarts.
add_task(async function test_prefFlips_restore_failure_wrong_type() {
const PREF_1 = "foo.bar.baz";
const PREF_2 = "qux.quux.corge.grault";
Assert.ok(!enrollment.active, "Enrollment should be inactive");
Assert.equal(enrollment.unenrollReason, "prefFlips-failed");
Assert.ok(
!Services.prefs.prefHasDefaultValue(PREF_1),
`${PREF_1} has no default value`
);
Assert.ok(
!Services.prefs.prefHasDefaultValue(PREF_2),
`${PREF_2} has no default value`
);
Assert.equal(
Services.prefs.getIntPref(PREF_1), 123,
`${PREF_1} value unchanged`
);
Assert.ok(!Services.prefs.prefHasUserValue(PREF_2), `${PREF_2} has no value`);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.