Bug 1956080 - Write enrollment updates to the NimbusEnrollments table r=jhirsch,nimbus-reviewers,relud,nalexander
Differential Revision: https://phabricator.services.mozilla.com/D249472
This commit is contained in:
committed by
brennie@mozilla.com
parent
0bbb1c3d7a
commit
128370986e
@@ -482,6 +482,10 @@ export class ExperimentManager {
|
|||||||
* as `recipe` and re-enrollment is prevented.
|
* as `recipe` and re-enrollment is prevented.
|
||||||
*/
|
*/
|
||||||
async enroll(recipe, source, { reenroll = false, branchSlug } = {}) {
|
async enroll(recipe, source, { reenroll = false, branchSlug } = {}) {
|
||||||
|
if (typeof source !== "string") {
|
||||||
|
throw new Error("source is required");
|
||||||
|
}
|
||||||
|
|
||||||
let { slug, branches, bucketConfig, isFirefoxLabsOptIn } = recipe;
|
let { slug, branches, bucketConfig, isFirefoxLabsOptIn } = recipe;
|
||||||
|
|
||||||
const enrollment = this.store.get(slug);
|
const enrollment = this.store.get(slug);
|
||||||
@@ -573,11 +577,11 @@ export class ExperimentManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._enroll(recipe, branch, source);
|
return this._enroll(recipe, branch.slug, source);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _enroll(
|
async _enroll(recipe, branchSlug, source) {
|
||||||
{
|
const {
|
||||||
slug,
|
slug,
|
||||||
userFacingName,
|
userFacingName,
|
||||||
userFacingDescription,
|
userFacingDescription,
|
||||||
@@ -590,10 +594,9 @@ export class ExperimentManager {
|
|||||||
firefoxLabsDescriptionLinks = null,
|
firefoxLabsDescriptionLinks = null,
|
||||||
firefoxLabsGroup,
|
firefoxLabsGroup,
|
||||||
requiresRestart = false,
|
requiresRestart = false,
|
||||||
},
|
} = recipe;
|
||||||
branch,
|
|
||||||
source
|
const branch = recipe.branches.find(b => b.slug === branchSlug);
|
||||||
) {
|
|
||||||
const { prefs, prefsToSet } = this._getPrefsForBranch(branch, isRollout);
|
const { prefs, prefsToSet } = this._getPrefsForBranch(branch, isRollout);
|
||||||
|
|
||||||
// Unenroll in any conflicting prefFlips enrollments.
|
// Unenroll in any conflicting prefFlips enrollments.
|
||||||
@@ -604,7 +607,6 @@ export class ExperimentManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {Enrollment} */
|
|
||||||
const enrollment = {
|
const enrollment = {
|
||||||
slug,
|
slug,
|
||||||
branch,
|
branch,
|
||||||
@@ -634,13 +636,15 @@ export class ExperimentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this._prefFlips._annotateEnrollment(enrollment);
|
await this._prefFlips._annotateEnrollment(enrollment);
|
||||||
this.store.addEnrollment(enrollment);
|
|
||||||
|
|
||||||
lazy.NimbusTelemetry.recordEnrollment(enrollment);
|
await this.store._addEnrollmentToDatabase(enrollment, recipe);
|
||||||
|
this.store.addEnrollment(enrollment);
|
||||||
|
|
||||||
this._setEnrollmentPrefs(prefsToSet);
|
this._setEnrollmentPrefs(prefsToSet);
|
||||||
this._updatePrefObservers(enrollment);
|
this._updatePrefObservers(enrollment);
|
||||||
|
|
||||||
|
lazy.NimbusTelemetry.recordEnrollment(enrollment);
|
||||||
|
|
||||||
lazy.log.debug(
|
lazy.log.debug(
|
||||||
`New ${isRollout ? "rollout" : "experiment"} started: ${slug}, ${
|
`New ${isRollout ? "rollout" : "experiment"} started: ${slug}, ${
|
||||||
branch.slug
|
branch.slug
|
||||||
@@ -686,7 +690,7 @@ export class ExperimentManager {
|
|||||||
...recipe,
|
...recipe,
|
||||||
slug,
|
slug,
|
||||||
},
|
},
|
||||||
branch,
|
branch.slug,
|
||||||
lazy.NimbusTelemetry.EnrollmentSource.FORCE_ENROLLMENT
|
lazy.NimbusTelemetry.EnrollmentSource.FORCE_ENROLLMENT
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -872,6 +876,17 @@ export class ExperimentManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(bug 1956082): This is an async method that we are not awaiting.
|
||||||
|
//
|
||||||
|
// Changing the entire unenrollment flow to be asynchronous requires changes
|
||||||
|
// to a lot of tests and it only really matters once we're actually checking
|
||||||
|
// the database contents.
|
||||||
|
//
|
||||||
|
// For now, we're going to return the promise which will make unenroll()
|
||||||
|
// awaitable in the few contexts that need to synchronize reads and writes
|
||||||
|
// right now (i.e., tests).
|
||||||
|
await this.store._deactivateEnrollmentInDatabase(slug, cause.reason);
|
||||||
|
|
||||||
this.store.updateExperiment(slug, {
|
this.store.updateExperiment(slug, {
|
||||||
active: false,
|
active: false,
|
||||||
unenrollReason: cause.reason,
|
unenrollReason: cause.reason,
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ import { SharedDataMap } from "resource://nimbus/lib/SharedDataMap.sys.mjs";
|
|||||||
const lazy = {};
|
const lazy = {};
|
||||||
|
|
||||||
ChromeUtils.defineESModuleGetters(lazy, {
|
ChromeUtils.defineESModuleGetters(lazy, {
|
||||||
|
ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
|
||||||
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
|
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
|
||||||
PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs",
|
PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs",
|
||||||
|
ProfilesDatastoreService:
|
||||||
|
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs",
|
||||||
});
|
});
|
||||||
|
|
||||||
// This branch is used to store experiment data
|
// This branch is used to store experiment data
|
||||||
@@ -470,4 +473,103 @@ export class ExperimentStore extends SharedDataMap {
|
|||||||
lazy.syncDataStore.deleteDefault(slugOrFeatureId);
|
lazy.syncDataStore.deleteDefault(slugOrFeatureId);
|
||||||
lazy.syncDataStore.delete(slugOrFeatureId);
|
lazy.syncDataStore.delete(slugOrFeatureId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _addEnrollmentToDatabase(enrollment, recipe) {
|
||||||
|
if (
|
||||||
|
!Services.prefs.getBoolPref(
|
||||||
|
"nimbus.profilesdatastoreservice.enabled",
|
||||||
|
false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// We are in an xpcshell test that has not initialized the
|
||||||
|
// ProfilesDatastoreService.
|
||||||
|
//
|
||||||
|
// TODO(bug 1967779): require the ProfilesDatastoreService to be initialized
|
||||||
|
// and remove this check.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileId = lazy.ExperimentAPI.profileId;
|
||||||
|
|
||||||
|
const conn = await lazy.ProfilesDatastoreService.getConnection();
|
||||||
|
await conn.execute(
|
||||||
|
`
|
||||||
|
INSERT INTO NimbusEnrollments VALUES(
|
||||||
|
null,
|
||||||
|
:profileId,
|
||||||
|
:slug,
|
||||||
|
:branchSlug,
|
||||||
|
jsonb(:recipe),
|
||||||
|
:active,
|
||||||
|
:unenrollReason,
|
||||||
|
:lastSeen,
|
||||||
|
jsonb(:setPrefs),
|
||||||
|
jsonb(:prefFlips),
|
||||||
|
:source
|
||||||
|
)
|
||||||
|
ON CONFLICT(profileId, slug)
|
||||||
|
DO UPDATE SET
|
||||||
|
branchSlug = excluded.branchSlug,
|
||||||
|
recipe = excluded.recipe,
|
||||||
|
active = excluded.active,
|
||||||
|
unenrollReason = excluded.unenrollReason,
|
||||||
|
lastSeen = excluded.lastSeen,
|
||||||
|
setPrefs = excluded.setPrefs,
|
||||||
|
prefFlips = excluded.setPrefs,
|
||||||
|
source = excluded.source;
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
profileId,
|
||||||
|
slug: enrollment.slug,
|
||||||
|
branchSlug: enrollment.branch.slug,
|
||||||
|
recipe: recipe ? JSON.stringify(recipe) : null,
|
||||||
|
active: enrollment.active,
|
||||||
|
unenrollReason: null,
|
||||||
|
lastSeen: enrollment.lastSeen,
|
||||||
|
setPrefs: enrollment.prefs ? JSON.stringify(enrollment.prefs) : null,
|
||||||
|
prefFlips: enrollment.prefFlips
|
||||||
|
? JSON.stringify(enrollment.prefFlips)
|
||||||
|
: null,
|
||||||
|
source: enrollment.source,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _deactivateEnrollmentInDatabase(slug, unenrollReason = "unknown") {
|
||||||
|
if (
|
||||||
|
!Services.prefs.getBoolPref(
|
||||||
|
"nimbus.profilesdatastoreservice.enabled",
|
||||||
|
false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// We are in an xpcshell test that has not initialized the
|
||||||
|
// ProfilesDatastoreService.
|
||||||
|
//
|
||||||
|
// TODO(bug 1967779): require the ProfilesDatastoreService to be initialized
|
||||||
|
// and remove this check.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const profileId = lazy.ExperimentAPI.profileId;
|
||||||
|
|
||||||
|
const conn = await lazy.ProfilesDatastoreService.getConnection();
|
||||||
|
await conn.execute(
|
||||||
|
`
|
||||||
|
UPDATE NimbusEnrollments SET
|
||||||
|
active = false,
|
||||||
|
unenrollReason = :unenrollReason,
|
||||||
|
recipe = null,
|
||||||
|
prefFlips = null,
|
||||||
|
setPrefs = null
|
||||||
|
WHERE
|
||||||
|
profileId = :profileId AND
|
||||||
|
slug = :slug;
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
slug,
|
||||||
|
profileId,
|
||||||
|
unenrollReason,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -518,7 +518,7 @@ export class PrefFlipsFeature {
|
|||||||
const entry = this.#prefs.get(pref);
|
const entry = this.#prefs.get(pref);
|
||||||
|
|
||||||
if (entry.branch !== branch || entry.value !== value) {
|
if (entry.branch !== branch || entry.value !== value) {
|
||||||
throw new PrefFlipsFailedError(pref);
|
throw new PrefFlipsFailedError(pref, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
entry.slugs.add(slug);
|
entry.slugs.add(slug);
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
|||||||
JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
|
JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
|
||||||
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
|
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
|
||||||
ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",
|
ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",
|
||||||
|
ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
|
||||||
ProfilesDatastoreService:
|
ProfilesDatastoreService:
|
||||||
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs",
|
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs",
|
||||||
RemoteSettingsExperimentLoader:
|
RemoteSettingsExperimentLoader:
|
||||||
"resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs",
|
"resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs",
|
||||||
|
TestUtils: "resource://testing-common/TestUtils.sys.mjs",
|
||||||
sinon: "resource://testing-common/Sinon.sys.mjs",
|
sinon: "resource://testing-common/Sinon.sys.mjs",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -133,7 +135,7 @@ export const NimbusTestUtils = {
|
|||||||
* @param {object} store
|
* @param {object} store
|
||||||
* The `ExperimentStore`.
|
* The `ExperimentStore`.
|
||||||
*/
|
*/
|
||||||
storeIsEmpty(store) {
|
async storeIsEmpty(store) {
|
||||||
NimbusTestUtils.Assert.deepEqual(
|
NimbusTestUtils.Assert.deepEqual(
|
||||||
store
|
store
|
||||||
.getAll()
|
.getAll()
|
||||||
@@ -159,11 +161,7 @@ export const NimbusTestUtils = {
|
|||||||
|
|
||||||
NimbusTestUtils.cleanupStorePrefCache();
|
NimbusTestUtils.cleanupStorePrefCache();
|
||||||
|
|
||||||
// TODO(bug 1956082): This is an async method that we are not awaiting.
|
await NimbusTestUtils.cleanupEnrollmentDatabase();
|
||||||
//
|
|
||||||
// Only browser tests and tests that otherwise have manually enabled the
|
|
||||||
// ProfilesDatastoreService need to await the result.
|
|
||||||
return NimbusTestUtils.cleanupEnrollmentDatabase();
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -428,13 +426,32 @@ export const NimbusTestUtils = {
|
|||||||
|
|
||||||
const conn = await lazy.ProfilesDatastoreService.getConnection();
|
const conn = await lazy.ProfilesDatastoreService.getConnection();
|
||||||
|
|
||||||
// TODO(bug 1956080): This should only delete inactive enrollments, but
|
const activeSlugs = await conn
|
||||||
// unenrolling does not yet trigger database writes.
|
.execute(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
slug
|
||||||
|
FROM NimbusEnrollments
|
||||||
|
WHERE
|
||||||
|
profileId = :profileId AND
|
||||||
|
active = true;
|
||||||
|
`,
|
||||||
|
{ profileId }
|
||||||
|
)
|
||||||
|
.then(rows => rows.map(row => row.getResultByName("slug")));
|
||||||
|
|
||||||
|
NimbusTestUtils.Assert.deepEqual(
|
||||||
|
activeSlugs,
|
||||||
|
[],
|
||||||
|
`No active slugs in NimbusEnrollments for ${profileId}`
|
||||||
|
);
|
||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
`
|
`
|
||||||
DELETE FROM NimbusEnrollments
|
DELETE FROM NimbusEnrollments
|
||||||
WHERE
|
WHERE
|
||||||
profileId = :profileId;
|
profileId = :profileId AND
|
||||||
|
active = false;
|
||||||
`,
|
`,
|
||||||
{ profileId }
|
{ profileId }
|
||||||
);
|
);
|
||||||
@@ -501,15 +518,9 @@ export const NimbusTestUtils = {
|
|||||||
|
|
||||||
experimentManager.store._syncToChildren({ flush: true });
|
experimentManager.store._syncToChildren({ flush: true });
|
||||||
|
|
||||||
return function doEnrollmentCleanup() {
|
return async function doEnrollmentCleanup() {
|
||||||
// TODO(bug 1956082): This is an async method that we are not awaiting.
|
await experimentManager.unenroll(enrollment.slug);
|
||||||
//
|
|
||||||
// Only browser tests and tests that otherwise have manually enabled the
|
|
||||||
// ProfilesDatastoreService need to await the result.
|
|
||||||
const promise = experimentManager.unenroll(enrollment.slug);
|
|
||||||
experimentManager.store._deleteForTests(enrollment.slug);
|
experimentManager.store._deleteForTests(enrollment.slug);
|
||||||
|
|
||||||
return promise;
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -725,6 +736,9 @@ export const NimbusTestUtils = {
|
|||||||
Services.fog.testResetFOG();
|
Services.fog.testResetFOG();
|
||||||
Services.telemetry.clearEvents();
|
Services.telemetry.clearEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove all migration state.
|
||||||
|
Services.prefs.deleteBranch("nimbus.migrations.");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -795,6 +809,80 @@ export const NimbusTestUtils = {
|
|||||||
`Experiment ${experiment.slug} not valid`
|
`Experiment ${experiment.slug} not valid`
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for the given slugs to be the only active enrollments in the
|
||||||
|
* NimbusEnrollments table.
|
||||||
|
*
|
||||||
|
* @param {string[]} expectedSlugs The slugs of the only active enrollmetns we
|
||||||
|
* expect.
|
||||||
|
*/
|
||||||
|
async waitForActiveEnrollments(expectedSlugs) {
|
||||||
|
const profileId = ExperimentAPI.profileId;
|
||||||
|
|
||||||
|
await lazy.TestUtils.waitForCondition(async () => {
|
||||||
|
const conn = await lazy.ProfilesDatastoreService.getConnection();
|
||||||
|
const slugs = await conn
|
||||||
|
.execute(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
slug
|
||||||
|
FROM NimbusEnrollments
|
||||||
|
WHERE
|
||||||
|
active = true AND
|
||||||
|
profileId = :profileId;
|
||||||
|
`,
|
||||||
|
{ profileId }
|
||||||
|
)
|
||||||
|
.then(rows => rows.map(row => row.getResultByName("slug")));
|
||||||
|
|
||||||
|
return lazy.ObjectUtils.deepEqual(slugs.sort(), expectedSlugs.sort());
|
||||||
|
}, `Waiting for enrollments of ${expectedSlugs} to sync to database`);
|
||||||
|
},
|
||||||
|
|
||||||
|
async waitForInactiveEnrollment(slug) {
|
||||||
|
const profileId = ExperimentAPI.profileId;
|
||||||
|
|
||||||
|
await lazy.TestUtils.waitForCondition(async () => {
|
||||||
|
const conn = await lazy.ProfilesDatastoreService.getConnection();
|
||||||
|
const result = await conn.execute(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
active
|
||||||
|
FROM NimbusEnrollments
|
||||||
|
WHERE
|
||||||
|
slug = :slug AND
|
||||||
|
profileId = :profileId;
|
||||||
|
`,
|
||||||
|
{ profileId, slug }
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.length === 1 && !result[0].getResultByName("active");
|
||||||
|
}, `Waiting for ${slug} enrollment to exist and be inactive`);
|
||||||
|
},
|
||||||
|
|
||||||
|
async waitForAllUnenrollments() {
|
||||||
|
const profileId = ExperimentAPI.profileId;
|
||||||
|
|
||||||
|
await lazy.TestUtils.waitForCondition(async () => {
|
||||||
|
const conn = await lazy.ProfilesDatastoreService.getConnection();
|
||||||
|
const slugs = await conn
|
||||||
|
.execute(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
slug
|
||||||
|
FROM NimbusEnrollments
|
||||||
|
WHERE
|
||||||
|
active = true AND
|
||||||
|
profileId = :profileId;
|
||||||
|
`,
|
||||||
|
{ profileId }
|
||||||
|
)
|
||||||
|
.then(rows => rows.map(row => row.getResultByName("slug")));
|
||||||
|
|
||||||
|
return slugs.length === 0;
|
||||||
|
}, "Waiting for unenrollments to sync to database");
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.defineProperties(NimbusTestUtils.factories.experiment, {
|
Object.defineProperties(NimbusTestUtils.factories.experiment, {
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ add_task(async function testGetFromChildNewEnrollment() {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
testInt: 123,
|
testInt: 123,
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
|
"test"
|
||||||
);
|
);
|
||||||
|
|
||||||
// Immediately serialize sharedData and broadcast changes to the child processes.
|
// Immediately serialize sharedData and broadcast changes to the child processes.
|
||||||
@@ -143,7 +144,8 @@ add_task(async function testGetFromChildExistingEnrollment() {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
testInt: 456,
|
testInt: 456,
|
||||||
},
|
},
|
||||||
})
|
}),
|
||||||
|
"test"
|
||||||
);
|
);
|
||||||
|
|
||||||
// We don't have to wait for this to update in the client, but we *do* have to
|
// We don't have to wait for this to update in the client, but we *do* have to
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ add_task(async function test_experiment_enroll_unenroll_Telemetry() {
|
|||||||
EVENT_FILTER
|
EVENT_FILTER
|
||||||
);
|
);
|
||||||
|
|
||||||
cleanup();
|
await cleanup();
|
||||||
|
|
||||||
TelemetryTestUtils.assertEvents(
|
TelemetryTestUtils.assertEvents(
|
||||||
[
|
[
|
||||||
@@ -98,7 +98,7 @@ add_task(async function test_experiment_expose_Telemetry() {
|
|||||||
EVENT_FILTER
|
EVENT_FILTER
|
||||||
);
|
);
|
||||||
|
|
||||||
cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
add_task(async function test_rollout_expose_Telemetry() {
|
add_task(async function test_rollout_expose_Telemetry() {
|
||||||
@@ -132,5 +132,5 @@ add_task(async function test_rollout_expose_Telemetry() {
|
|||||||
EVENT_FILTER
|
EVENT_FILTER
|
||||||
);
|
);
|
||||||
|
|
||||||
cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
/* import-globals-from ../../../../../toolkit/profile/test/xpcshell/head.js */
|
||||||
|
/* import-globals-from ../../../../../browser/components/profiles/tests/unit/head.js */
|
||||||
|
|
||||||
const { sinon } = ChromeUtils.importESModule(
|
const { sinon } = ChromeUtils.importESModule(
|
||||||
"resource://testing-common/Sinon.sys.mjs"
|
"resource://testing-common/Sinon.sys.mjs"
|
||||||
);
|
);
|
||||||
@@ -23,8 +26,19 @@ ChromeUtils.defineESModuleGetters(this, {
|
|||||||
|
|
||||||
NimbusTestUtils.init(this);
|
NimbusTestUtils.init(this);
|
||||||
|
|
||||||
add_setup(function () {
|
add_setup(async function () {
|
||||||
do_get_profile();
|
do_get_profile();
|
||||||
|
|
||||||
|
await initSelectableProfileService();
|
||||||
|
|
||||||
|
// TODO(bug 1967779): require the ProfilesDatastoreService to be initialized
|
||||||
|
Services.prefs.setBoolPref("nimbus.profilesdatastoreservice.enabled", true);
|
||||||
|
registerCleanupFunction(() => {
|
||||||
|
Services.prefs.setBoolPref(
|
||||||
|
"nimbus.profilesdatastoreservice.enabled",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ add_task(async function test_ExperimentFeature_test_helper_ready() {
|
|||||||
"set by remote config"
|
"set by remote config"
|
||||||
);
|
);
|
||||||
|
|
||||||
cleanupExperiment();
|
await cleanupExperiment();
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -212,7 +212,7 @@ add_task(async function test_allow_multiple_exposure_events() {
|
|||||||
// We expect 3 events
|
// We expect 3 events
|
||||||
Assert.equal(3, exposureEvents.length);
|
Assert.equal(3, exposureEvents.length);
|
||||||
|
|
||||||
doExperimentCleanup();
|
await doExperimentCleanup();
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const { AppConstants } = ChromeUtils.importESModule(
|
|
||||||
"resource://gre/modules/AppConstants.sys.mjs"
|
|
||||||
);
|
|
||||||
|
|
||||||
const FEATURE_ID = "testfeature1";
|
const FEATURE_ID = "testfeature1";
|
||||||
// Note: this gets deleted at the end of tests
|
// Note: this gets deleted at the end of tests
|
||||||
const TEST_PREF_BRANCH = "testfeature1.";
|
const TEST_PREF_BRANCH = "testfeature1.";
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ add_task(async function test_enroll_optin_recipe_branch_selection() {
|
|||||||
Assert.ok(
|
Assert.ok(
|
||||||
manager._enroll.calledOnceWith(
|
manager._enroll.calledOnceWith(
|
||||||
optInRecipe,
|
optInRecipe,
|
||||||
optInRecipe.branches[0],
|
optInRecipe.branches[0].slug,
|
||||||
"test"
|
"test"
|
||||||
),
|
),
|
||||||
"should call ._enroll() with the correct arguments"
|
"should call ._enroll() with the correct arguments"
|
||||||
@@ -871,7 +871,7 @@ add_task(async function test_featureIds_is_stored() {
|
|||||||
"Has expected value"
|
"Has expected value"
|
||||||
);
|
);
|
||||||
|
|
||||||
doExperimentCleanup();
|
await doExperimentCleanup();
|
||||||
|
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
@@ -907,7 +907,7 @@ add_task(async function experiment_and_rollout_enroll_and_cleanup() {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
doExperimentCleanup();
|
await doExperimentCleanup();
|
||||||
|
|
||||||
Assert.ok(
|
Assert.ok(
|
||||||
!Services.prefs.getBoolPref(
|
!Services.prefs.getBoolPref(
|
||||||
@@ -921,7 +921,7 @@ add_task(async function experiment_and_rollout_enroll_and_cleanup() {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
doRolloutCleanup();
|
await doRolloutCleanup();
|
||||||
|
|
||||||
Assert.ok(
|
Assert.ok(
|
||||||
!Services.prefs.getBoolPref(
|
!Services.prefs.getBoolPref(
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ const { UnenrollmentCause } = ChromeUtils.importESModule(
|
|||||||
"resource://nimbus/lib/ExperimentManager.sys.mjs"
|
"resource://nimbus/lib/ExperimentManager.sys.mjs"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { ProfilesDatastoreService } = ChromeUtils.importESModule(
|
||||||
|
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs"
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* onStartup()
|
* onStartup()
|
||||||
* - should set call setExperimentActive for each active experiment
|
* - should set call setExperimentActive for each active experiment
|
||||||
@@ -364,3 +368,208 @@ add_task(async function test_experimentStore_updateEvent() {
|
|||||||
|
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
add_task(async function testDb() {
|
||||||
|
const conn = await ProfilesDatastoreService.getConnection();
|
||||||
|
|
||||||
|
function processRow(row) {
|
||||||
|
const fields = [
|
||||||
|
"profileId",
|
||||||
|
"slug",
|
||||||
|
"branchSlug",
|
||||||
|
"recipe",
|
||||||
|
"active",
|
||||||
|
"unenrollReason",
|
||||||
|
"lastSeen",
|
||||||
|
"setPrefs",
|
||||||
|
"prefFlips",
|
||||||
|
"source",
|
||||||
|
];
|
||||||
|
|
||||||
|
const processed = {};
|
||||||
|
|
||||||
|
for (const field of fields) {
|
||||||
|
processed[field] = row.getResultByName(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
processed.recipe = JSON.parse(processed.recipe);
|
||||||
|
processed.setPrefs = JSON.parse(processed.setPrefs);
|
||||||
|
processed.prefFlips = JSON.parse(processed.prefFlips);
|
||||||
|
|
||||||
|
return processed;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEnrollment(slug) {
|
||||||
|
const results = await conn.execute(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
profileId,
|
||||||
|
slug,
|
||||||
|
branchSlug,
|
||||||
|
json(recipe) AS recipe,
|
||||||
|
active,
|
||||||
|
unenrollReason,
|
||||||
|
lastSeen,
|
||||||
|
json(setPrefs) AS setPrefs,
|
||||||
|
json(prefFlips) AS prefFlips,
|
||||||
|
source
|
||||||
|
FROM NimbusEnrollments
|
||||||
|
WHERE
|
||||||
|
slug = :slug AND
|
||||||
|
profileId = :profileId;
|
||||||
|
`,
|
||||||
|
{ slug, profileId: ExperimentAPI.profileId }
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.equal(
|
||||||
|
results.length,
|
||||||
|
1,
|
||||||
|
`Exactly one enrollment should be returned for ${slug}`
|
||||||
|
);
|
||||||
|
return processRow(results[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getEnrollmentSlugs() {
|
||||||
|
const result = await conn.execute(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
slug
|
||||||
|
FROM NimbusEnrollments
|
||||||
|
WHERE
|
||||||
|
profileId = :profileId;
|
||||||
|
`,
|
||||||
|
{ profileId: ExperimentAPI.profileId }
|
||||||
|
);
|
||||||
|
|
||||||
|
return result.map(row => row.getResultByName("slug")).sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { manager, cleanup } = await NimbusTestUtils.setupTest();
|
||||||
|
|
||||||
|
const experimentRecipe = NimbusTestUtils.factories.recipe("experiment", {
|
||||||
|
branches: [
|
||||||
|
{
|
||||||
|
ratio: 1,
|
||||||
|
slug: "control",
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
featureId: "no-feature-firefox-desktop",
|
||||||
|
value: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ratio: 0, // Force enrollment in control
|
||||||
|
slug: "treatment",
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
featureId: "no-feature-firefox-desktop",
|
||||||
|
value: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const rolloutRecipe = NimbusTestUtils.factories.recipe.withFeatureConfig(
|
||||||
|
"rollout",
|
||||||
|
{ branchSlug: "rollout", featureId: "no-feature-firefox-desktop" }
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.deepEqual(
|
||||||
|
await getEnrollmentSlugs(),
|
||||||
|
[],
|
||||||
|
"There are no database entries"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Enroll in an experiment
|
||||||
|
await manager.enroll(experimentRecipe, "test");
|
||||||
|
Assert.deepEqual(
|
||||||
|
await getEnrollmentSlugs(),
|
||||||
|
[experimentRecipe.slug],
|
||||||
|
"There is one enrollment"
|
||||||
|
);
|
||||||
|
|
||||||
|
let experimentEnrollment = await getEnrollment(experimentRecipe.slug);
|
||||||
|
Assert.ok(experimentEnrollment.active, "experiment enrollment is active");
|
||||||
|
Assert.deepEqual(
|
||||||
|
experimentEnrollment.recipe,
|
||||||
|
experimentRecipe,
|
||||||
|
"experiment enrollment has the correct recipe"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
experimentEnrollment.branchSlug,
|
||||||
|
manager.store.get(experimentRecipe.slug).branch.slug,
|
||||||
|
"experiment branch slug matches"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Enroll in a rollout.
|
||||||
|
await manager.enroll(rolloutRecipe, "test");
|
||||||
|
Assert.deepEqual(
|
||||||
|
await getEnrollmentSlugs(),
|
||||||
|
[experimentRecipe.slug, rolloutRecipe.slug].sort(),
|
||||||
|
"There are two enrollments"
|
||||||
|
);
|
||||||
|
|
||||||
|
let rolloutEnrollment = await getEnrollment(rolloutRecipe.slug);
|
||||||
|
Assert.ok(rolloutEnrollment.active, "rollout enrollment is active");
|
||||||
|
Assert.deepEqual(
|
||||||
|
rolloutEnrollment.recipe,
|
||||||
|
rolloutRecipe,
|
||||||
|
"rollout enrollment has the correct recipe"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
rolloutEnrollment.branchSlug,
|
||||||
|
manager.store.get(rolloutRecipe.slug).branch.slug,
|
||||||
|
"rollout branch slug matches"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Unenroll from the rollout.
|
||||||
|
await manager.unenroll(rolloutRecipe.slug, { reason: "recipe-not-seen" });
|
||||||
|
Assert.deepEqual(
|
||||||
|
await getEnrollmentSlugs(),
|
||||||
|
[experimentRecipe.slug, rolloutRecipe.slug].sort(),
|
||||||
|
"There are two enrollments"
|
||||||
|
);
|
||||||
|
|
||||||
|
rolloutEnrollment = await getEnrollment(rolloutRecipe.slug);
|
||||||
|
Assert.ok(!rolloutEnrollment.active, "rollout enrollment is inactive");
|
||||||
|
Assert.equal(
|
||||||
|
rolloutEnrollment.recipe,
|
||||||
|
null,
|
||||||
|
"rollout enrollment recipe is null"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
rolloutEnrollment.unenrollReason,
|
||||||
|
"recipe-not-seen",
|
||||||
|
"rollout unenrollReason"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
rolloutEnrollment.branchSlug,
|
||||||
|
manager.store.get(rolloutRecipe.slug).branch.slug,
|
||||||
|
"rollout branch slug matches"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Unenroll from the experiment.
|
||||||
|
await manager.unenroll(experimentEnrollment.slug, { reason: "targeting" });
|
||||||
|
|
||||||
|
experimentEnrollment = await getEnrollment(experimentRecipe.slug);
|
||||||
|
Assert.ok(!experimentEnrollment.active, "experiment enrollment is inactive");
|
||||||
|
Assert.equal(
|
||||||
|
experimentEnrollment.recipe,
|
||||||
|
null,
|
||||||
|
"experiment enrollment recipe is null"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
experimentEnrollment.unenrollReason,
|
||||||
|
"targeting",
|
||||||
|
"experiment unenrollReason"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
experimentEnrollment.branchSlug,
|
||||||
|
manager.store.get(experimentRecipe.slug).branch.slug,
|
||||||
|
"experiment branch slug matches"
|
||||||
|
);
|
||||||
|
|
||||||
|
await cleanup();
|
||||||
|
});
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ const { PrefUtils } = ChromeUtils.importESModule(
|
|||||||
"resource://normandy/lib/PrefUtils.sys.mjs"
|
"resource://normandy/lib/PrefUtils.sys.mjs"
|
||||||
);
|
);
|
||||||
|
|
||||||
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
const { ProfilesDatastoreService } = ChromeUtils.importESModule(
|
||||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs"
|
||||||
);
|
);
|
||||||
|
|
||||||
function assertIncludes(array, obj, msg) {
|
function assertIncludes(array, obj, msg) {
|
||||||
@@ -319,7 +319,7 @@ add_task(async function test_enroll_setPref_rolloutsAndExperiments() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const enrollmentKind of unenrollOrder) {
|
for (const enrollmentKind of unenrollOrder) {
|
||||||
cleanupFns[enrollmentKind]();
|
await cleanupFns[enrollmentKind]();
|
||||||
|
|
||||||
assertExpectedPrefValues(
|
assertExpectedPrefValues(
|
||||||
pref,
|
pref,
|
||||||
@@ -1712,9 +1712,6 @@ add_task(async function test_prefChange() {
|
|||||||
const cleanupFunctions = {};
|
const cleanupFunctions = {};
|
||||||
const slugs = {};
|
const slugs = {};
|
||||||
|
|
||||||
await manager.store.init();
|
|
||||||
await manager.onStartup();
|
|
||||||
|
|
||||||
setPrefs(pref, { defaultBranchValue, userBranchValue });
|
setPrefs(pref, { defaultBranchValue, userBranchValue });
|
||||||
|
|
||||||
info(`Enrolling in ${Array.from(Object.keys(configs)).join(", ")} ...`);
|
info(`Enrolling in ${Array.from(Object.keys(configs)).join(", ")} ...`);
|
||||||
@@ -1744,6 +1741,10 @@ add_task(async function test_prefChange() {
|
|||||||
|
|
||||||
PrefUtils.setPref(pref, OVERWRITE_VALUE, { branch: setBranch });
|
PrefUtils.setPref(pref, OVERWRITE_VALUE, { branch: setBranch });
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForActiveEnrollments(
|
||||||
|
expectedEnrollments.map(kind => slugs[kind])
|
||||||
|
);
|
||||||
|
|
||||||
if (expectedDefault === null) {
|
if (expectedDefault === null) {
|
||||||
Assert.ok(
|
Assert.ok(
|
||||||
!Services.prefs.prefHasDefaultValue(pref),
|
!Services.prefs.prefHasDefaultValue(pref),
|
||||||
@@ -1783,6 +1784,9 @@ add_task(async function test_prefChange() {
|
|||||||
for (const enrollmentKind of Object.keys(configs)) {
|
for (const enrollmentKind of Object.keys(configs)) {
|
||||||
if (!expectedEnrollments.includes(enrollmentKind)) {
|
if (!expectedEnrollments.includes(enrollmentKind)) {
|
||||||
const slug = slugs[enrollmentKind];
|
const slug = slugs[enrollmentKind];
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(slug);
|
||||||
|
|
||||||
const enrollment = manager.store.get(slug);
|
const enrollment = manager.store.get(slug);
|
||||||
|
|
||||||
Assert.ok(
|
Assert.ok(
|
||||||
@@ -1873,7 +1877,7 @@ add_task(async function test_prefChange() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const enrollmentKind of expectedEnrollments) {
|
for (const enrollmentKind of expectedEnrollments) {
|
||||||
cleanupFunctions[enrollmentKind]();
|
await cleanupFunctions[enrollmentKind]();
|
||||||
}
|
}
|
||||||
|
|
||||||
Services.prefs.deleteBranch(pref);
|
Services.prefs.deleteBranch(pref);
|
||||||
@@ -2322,7 +2326,7 @@ add_task(async function test_deleteBranch() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const cleanupFn of cleanupFunctions) {
|
for (const cleanupFn of cleanupFunctions) {
|
||||||
cleanupFn();
|
await cleanupFn();
|
||||||
}
|
}
|
||||||
|
|
||||||
Services.prefs.deleteBranch(PREFS[USER]);
|
Services.prefs.deleteBranch(PREFS[USER]);
|
||||||
@@ -2405,6 +2409,11 @@ add_task(async function test_clearUserPref() {
|
|||||||
|
|
||||||
for (const enrollmentKind of Object.keys(configs)) {
|
for (const enrollmentKind of Object.keys(configs)) {
|
||||||
const slug = slugs[enrollmentKind];
|
const slug = slugs[enrollmentKind];
|
||||||
|
|
||||||
|
if (!expectedEnrolled) {
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(slug);
|
||||||
|
}
|
||||||
|
|
||||||
const enrollment = manager.store.get(slug);
|
const enrollment = manager.store.get(slug);
|
||||||
Assert.ok(
|
Assert.ok(
|
||||||
enrollment !== null,
|
enrollment !== null,
|
||||||
@@ -2438,7 +2447,7 @@ add_task(async function test_clearUserPref() {
|
|||||||
|
|
||||||
if (expectedEnrolled) {
|
if (expectedEnrolled) {
|
||||||
for (const cleanupFn of Object.values(cleanupFns)) {
|
for (const cleanupFn of Object.values(cleanupFns)) {
|
||||||
cleanupFn();
|
await cleanupFn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2676,7 +2685,7 @@ add_task(async function test_prefChanged_noPrefSet() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
doEnrollmentCleanup();
|
await doEnrollmentCleanup();
|
||||||
await cleanup();
|
await cleanup();
|
||||||
|
|
||||||
Services.prefs.deleteBranch(pref);
|
Services.prefs.deleteBranch(pref);
|
||||||
@@ -3388,7 +3397,7 @@ add_task(async function test_setPref_types() {
|
|||||||
|
|
||||||
Assert.deepEqual(json, jsonPrefValue);
|
Assert.deepEqual(json, jsonPrefValue);
|
||||||
|
|
||||||
experimentCleanup();
|
await experimentCleanup();
|
||||||
featureCleanup();
|
featureCleanup();
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
@@ -3481,3 +3490,56 @@ add_task(async function test_setPref_types_restore() {
|
|||||||
await cleanup();
|
await cleanup();
|
||||||
featureCleanup();
|
featureCleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
add_task(async function testDb() {
|
||||||
|
const { manager, cleanup } = await setupTest();
|
||||||
|
|
||||||
|
PrefUtils.setPref("nimbus.qa.pref-1", "foo", { branch: DEFAULT });
|
||||||
|
|
||||||
|
await manager.enroll(
|
||||||
|
NimbusTestUtils.factories.recipe.withFeatureConfig("slug", {
|
||||||
|
featureId: "nimbus-qa-1",
|
||||||
|
value: { value: "hello" },
|
||||||
|
}),
|
||||||
|
"test"
|
||||||
|
);
|
||||||
|
|
||||||
|
const conn = await ProfilesDatastoreService.getConnection();
|
||||||
|
const [result] = await conn.execute(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
json(setPrefs) as setPrefs
|
||||||
|
FROM NimbusEnrollments
|
||||||
|
WHERE
|
||||||
|
profileId = :profileId AND
|
||||||
|
slug = :slug;
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
slug: "slug",
|
||||||
|
profileId: ExperimentAPI.profileId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const setPrefs = JSON.parse(result.getResultByName("setPrefs"));
|
||||||
|
const enrollment = manager.store.get("slug");
|
||||||
|
|
||||||
|
Assert.deepEqual(
|
||||||
|
setPrefs,
|
||||||
|
enrollment.prefs,
|
||||||
|
"setPrefs stored in the database"
|
||||||
|
);
|
||||||
|
Assert.deepEqual(setPrefs, [
|
||||||
|
{
|
||||||
|
name: "nimbus.qa.pref-1",
|
||||||
|
branch: "default",
|
||||||
|
featureId: "nimbus-qa-1",
|
||||||
|
variable: "value",
|
||||||
|
originalValue: "foo",
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await manager.unenroll("slug");
|
||||||
|
await cleanup();
|
||||||
|
|
||||||
|
Services.prefs.deleteBranch("nimbus.qa.pref-1");
|
||||||
|
});
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ add_task(async function test_unenroll_opt_out() {
|
|||||||
|
|
||||||
Services.prefs.setBoolPref(STUDIES_OPT_OUT_PREF, false);
|
Services.prefs.setBoolPref(STUDIES_OPT_OUT_PREF, false);
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(experiment.slug);
|
||||||
|
|
||||||
Assert.equal(
|
Assert.equal(
|
||||||
manager.store.get(experiment.slug).active,
|
manager.store.get(experiment.slug).active,
|
||||||
false,
|
false,
|
||||||
@@ -119,6 +121,8 @@ add_task(async function test_unenroll_rollout_opt_out() {
|
|||||||
|
|
||||||
Services.prefs.setBoolPref(STUDIES_OPT_OUT_PREF, false);
|
Services.prefs.setBoolPref(STUDIES_OPT_OUT_PREF, false);
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(rollout.slug);
|
||||||
|
|
||||||
Assert.equal(
|
Assert.equal(
|
||||||
manager.store.get(rollout.slug).active,
|
manager.store.get(rollout.slug).active,
|
||||||
false,
|
false,
|
||||||
@@ -171,6 +175,8 @@ add_task(async function test_unenroll_uploadPref() {
|
|||||||
|
|
||||||
Services.prefs.setBoolPref(UPLOAD_ENABLED_PREF, false);
|
Services.prefs.setBoolPref(UPLOAD_ENABLED_PREF, false);
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(recipe.slug);
|
||||||
|
|
||||||
Assert.equal(
|
Assert.equal(
|
||||||
manager.store.get(recipe.slug).active,
|
manager.store.get(recipe.slug).active,
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ add_task(async function test_initOnUpdateEventsFire() {
|
|||||||
storePath = await NimbusTestUtils.saveStore(store);
|
storePath = await NimbusTestUtils.saveStore(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { sandbox, store, initExperimentAPI, cleanup } = await setupTest({
|
const { sandbox, initExperimentAPI, cleanup } = await setupTest({
|
||||||
storePath,
|
storePath,
|
||||||
init: false,
|
init: false,
|
||||||
});
|
});
|
||||||
@@ -136,13 +136,14 @@ add_task(async function test_initOnUpdateEventsFire() {
|
|||||||
|
|
||||||
NimbusFeatures.testFeature.offUpdate(onFeatureUpdate);
|
NimbusFeatures.testFeature.offUpdate(onFeatureUpdate);
|
||||||
|
|
||||||
store.updateExperiment("testFeature-1", { active: false });
|
await NimbusTestUtils.cleanupManager([
|
||||||
store.updateExperiment("testFeature-2", { active: false });
|
"testFeature-1",
|
||||||
store.updateExperiment("coenroll-1", { active: false });
|
"testFeature-2",
|
||||||
store.updateExperiment("coenroll-2", { active: false });
|
"coenroll-1",
|
||||||
store.updateExperiment("coenroll-3", { active: false });
|
"coenroll-2",
|
||||||
store.updateExperiment("coenroll-4", { active: false });
|
"coenroll-3",
|
||||||
|
"coenroll-4",
|
||||||
|
]);
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -857,8 +858,6 @@ add_task(async function test_restore() {
|
|||||||
Assert.ok(store.get("experiment"));
|
Assert.ok(store.get("experiment"));
|
||||||
Assert.ok(store.get("rollout"));
|
Assert.ok(store.get("rollout"));
|
||||||
|
|
||||||
store.updateExperiment("experiment", { active: false });
|
await NimbusTestUtils.cleanupManager(["experiment", "rollout"]);
|
||||||
store.updateExperiment("rollout", { active: false });
|
|
||||||
|
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
/* Any copyright is dedicated to the Public Domain.
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
/* import-globals-from ../../../../../toolkit/profile/test/xpcshell/head.js */
|
|
||||||
/* import-globals-from ../../../../../browser/components/profiles/tests/unit/head.js */
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
LABS_MIGRATION_FEATURE_MAP,
|
LABS_MIGRATION_FEATURE_MAP,
|
||||||
LEGACY_NIMBUS_MIGRATION_PREF,
|
LEGACY_NIMBUS_MIGRATION_PREF,
|
||||||
@@ -47,16 +44,6 @@ function getEnabledPrefForFeature(featureId) {
|
|||||||
|
|
||||||
add_setup(async function setup() {
|
add_setup(async function setup() {
|
||||||
Services.fog.initializeFOG();
|
Services.fog.initializeFOG();
|
||||||
|
|
||||||
Services.prefs.setBoolPref("nimbus.profilesdatastoreservice.enabled", true);
|
|
||||||
registerCleanupFunction(() => {
|
|
||||||
Services.prefs.setBoolPref(
|
|
||||||
"nimbus.profilesdatastoreservice.enabled",
|
|
||||||
false
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await initSelectableProfileService();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,11 +84,7 @@ async function setupTest({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { initExperimentAPI, ...ctx } = await NimbusTestUtils.setupTest({
|
||||||
initExperimentAPI,
|
|
||||||
cleanup: baseCleanup,
|
|
||||||
...ctx
|
|
||||||
} = await NimbusTestUtils.setupTest({
|
|
||||||
init: false,
|
init: false,
|
||||||
clearTelemetry: true,
|
clearTelemetry: true,
|
||||||
...args,
|
...args,
|
||||||
@@ -141,13 +124,7 @@ async function setupTest({
|
|||||||
ctx.initExperimentAPI = initExperimentAPI;
|
ctx.initExperimentAPI = initExperimentAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return ctx;
|
||||||
...ctx,
|
|
||||||
async cleanup() {
|
|
||||||
await baseCleanup();
|
|
||||||
Services.prefs.deleteBranch("nimbus.migrations");
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeMigrations(phase, count) {
|
function makeMigrations(phase, count) {
|
||||||
|
|||||||
@@ -204,13 +204,14 @@ add_task(async function test_checkExperimentSelfReference() {
|
|||||||
add_task(async function test_optIn_debug_disabled() {
|
add_task(async function test_optIn_debug_disabled() {
|
||||||
info("Testing users cannot opt-in when nimbus.debug is false");
|
info("Testing users cannot opt-in when nimbus.debug is false");
|
||||||
|
|
||||||
const recipe = NimbusTestUtils.factories.recipe("foo");
|
const recipe = NimbusTestUtils.factories.recipe("foo", {
|
||||||
const { sandbox, loader, initExperimentAPI, cleanup } =
|
targeting: "false",
|
||||||
|
});
|
||||||
|
const { loader, initExperimentAPI, cleanup } =
|
||||||
await NimbusTestUtils.setupTest({
|
await NimbusTestUtils.setupTest({
|
||||||
init: false,
|
init: false,
|
||||||
experiments: [recipe],
|
experiments: [recipe],
|
||||||
});
|
});
|
||||||
sandbox.stub(loader, "updateRecipes").resolves();
|
|
||||||
|
|
||||||
await initExperimentAPI();
|
await initExperimentAPI();
|
||||||
|
|
||||||
@@ -238,12 +239,12 @@ add_task(async function test_optIn_studies_disabled() {
|
|||||||
"Testing users cannot opt-in when telemetry is disabled or studies are disabled."
|
"Testing users cannot opt-in when telemetry is disabled or studies are disabled."
|
||||||
);
|
);
|
||||||
|
|
||||||
const recipe = NimbusTestUtils.factories.recipe("foo");
|
const recipe = NimbusTestUtils.factories.recipe("foo", {
|
||||||
const { sandbox, loader, initExperimentAPI, cleanup } =
|
targeting: "false",
|
||||||
|
});
|
||||||
|
const { loader, initExperimentAPI, cleanup } =
|
||||||
await NimbusTestUtils.setupTest({ init: false, experiments: [recipe] });
|
await NimbusTestUtils.setupTest({ init: false, experiments: [recipe] });
|
||||||
|
|
||||||
sandbox.stub(loader, "updateRecipes").resolves();
|
|
||||||
|
|
||||||
await initExperimentAPI();
|
await initExperimentAPI();
|
||||||
|
|
||||||
Services.prefs.setBoolPref(DEBUG_PREF, true);
|
Services.prefs.setBoolPref(DEBUG_PREF, true);
|
||||||
|
|||||||
@@ -12,9 +12,6 @@ const { PanelTestProvider } = ChromeUtils.importESModule(
|
|||||||
const { TelemetryEnvironment } = ChromeUtils.importESModule(
|
const { TelemetryEnvironment } = ChromeUtils.importESModule(
|
||||||
"resource://gre/modules/TelemetryEnvironment.sys.mjs"
|
"resource://gre/modules/TelemetryEnvironment.sys.mjs"
|
||||||
);
|
);
|
||||||
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
|
||||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
|
||||||
);
|
|
||||||
const { UnenrollmentCause } = ChromeUtils.importESModule(
|
const { UnenrollmentCause } = ChromeUtils.importESModule(
|
||||||
"resource://nimbus/lib/ExperimentManager.sys.mjs"
|
"resource://nimbus/lib/ExperimentManager.sys.mjs"
|
||||||
);
|
);
|
||||||
@@ -957,7 +954,7 @@ add_task(async function test_updateRecipes_rollout_bucketing() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
manager.unenroll(experiment.slug);
|
await manager.unenroll(experiment.slug);
|
||||||
|
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
@@ -1008,7 +1005,7 @@ add_task(async function test_reenroll_rollout_resized() {
|
|||||||
"New enrollment should not have unenroll reason"
|
"New enrollment should not have unenroll reason"
|
||||||
);
|
);
|
||||||
|
|
||||||
manager.unenroll(rollout.slug);
|
await manager.unenroll(rollout.slug);
|
||||||
|
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
@@ -1025,7 +1022,7 @@ add_task(async function test_experiment_reenroll() {
|
|||||||
"Should enroll in experiment"
|
"Should enroll in experiment"
|
||||||
);
|
);
|
||||||
|
|
||||||
manager.unenroll(experiment.slug);
|
await manager.unenroll(experiment.slug);
|
||||||
Assert.ok(
|
Assert.ok(
|
||||||
!manager.store.getExperimentForFeature("testFeature"),
|
!manager.store.getExperimentForFeature("testFeature"),
|
||||||
"Should unenroll from experiment"
|
"Should unenroll from experiment"
|
||||||
@@ -1162,8 +1159,8 @@ add_task(async function test_active_and_past_experiment_targeting() {
|
|||||||
["experiment-a", "experiment-b", "rollout-a", "rollout-b"]
|
["experiment-a", "experiment-b", "rollout-a", "rollout-b"]
|
||||||
);
|
);
|
||||||
|
|
||||||
manager.unenroll("experiment-c");
|
await manager.unenroll("experiment-c");
|
||||||
manager.unenroll("rollout-c");
|
await manager.unenroll("rollout-c");
|
||||||
|
|
||||||
cleanupFeatures();
|
cleanupFeatures();
|
||||||
await cleanup();
|
await cleanup();
|
||||||
@@ -1329,14 +1326,12 @@ add_task(async function test_enrollment_targeting() {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const slug of [
|
await NimbusTestUtils.cleanupManager([
|
||||||
"experiment-b",
|
"experiment-b",
|
||||||
"experiment-c",
|
"experiment-c",
|
||||||
"rollout-b",
|
"rollout-b",
|
||||||
"rollout-c",
|
"rollout-c",
|
||||||
]) {
|
]);
|
||||||
manager.unenroll(slug);
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupFeatures();
|
cleanupFeatures();
|
||||||
await cleanup();
|
await cleanup();
|
||||||
@@ -1425,7 +1420,7 @@ add_task(
|
|||||||
const isReadyEvents = Glean.nimbusEvents.isReady.testGetValue("events");
|
const isReadyEvents = Glean.nimbusEvents.isReady.testGetValue("events");
|
||||||
|
|
||||||
Assert.equal(isReadyEvents.length, 3);
|
Assert.equal(isReadyEvents.length, 3);
|
||||||
manager.unenroll(recipe.slug);
|
await manager.unenroll(recipe.slug);
|
||||||
|
|
||||||
await cleanup();
|
await cleanup();
|
||||||
}
|
}
|
||||||
@@ -1594,7 +1589,7 @@ add_task(async function test_updateRecipes_optInsStayEnrolled() {
|
|||||||
await loader.updateRecipes();
|
await loader.updateRecipes();
|
||||||
Assert.ok(manager.store.get("opt-in")?.active, "Opt-in stayed enrolled");
|
Assert.ok(manager.store.get("opt-in")?.active, "Opt-in stayed enrolled");
|
||||||
|
|
||||||
manager.unenroll("opt-in");
|
await manager.unenroll("opt-in");
|
||||||
|
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
@@ -1914,8 +1909,8 @@ add_task(async function test_updateRecipes_enrollmentStatus_telemetry() {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
manager.unenroll("stays-enrolled");
|
await manager.unenroll("stays-enrolled");
|
||||||
manager.unenroll("enrolls");
|
await manager.unenroll("enrolls");
|
||||||
|
|
||||||
cleanupFeatures();
|
cleanupFeatures();
|
||||||
await cleanup();
|
await cleanup();
|
||||||
@@ -2027,8 +2022,8 @@ add_task(async function test_updateRecipes_enrollmentStatus_notEnrolled() {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
manager.unenroll("enrolled-experiment");
|
await manager.unenroll("enrolled-experiment");
|
||||||
manager.unenroll("enrolled-rollout");
|
await manager.unenroll("enrolled-rollout");
|
||||||
|
|
||||||
cleanupFeatures();
|
cleanupFeatures();
|
||||||
await cleanup();
|
await cleanup();
|
||||||
@@ -2218,8 +2213,8 @@ add_task(async function testUnenrollsFirst() {
|
|||||||
await loader.updateRecipes();
|
await loader.updateRecipes();
|
||||||
assertEnrollments(manager.store, ["e3", "r3"], ["e1", "e2", "r1", "r2"]);
|
assertEnrollments(manager.store, ["e3", "r3"], ["e1", "e2", "r1", "r2"]);
|
||||||
|
|
||||||
manager.unenroll("e3");
|
await manager.unenroll("e3");
|
||||||
manager.unenroll("r3");
|
await manager.unenroll("r3");
|
||||||
|
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,9 +4,6 @@
|
|||||||
const { MatchStatus } = ChromeUtils.importESModule(
|
const { MatchStatus } = ChromeUtils.importESModule(
|
||||||
"resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs"
|
"resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs"
|
||||||
);
|
);
|
||||||
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
|
||||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
|
||||||
);
|
|
||||||
|
|
||||||
const LOCALIZATIONS = {
|
const LOCALIZATIONS = {
|
||||||
"en-US": {
|
"en-US": {
|
||||||
@@ -213,7 +210,7 @@ add_task(async function test_getLocalizedValue() {
|
|||||||
"_getLocalizedValue() with a nested localization"
|
"_getLocalizedValue() with a nested localization"
|
||||||
);
|
);
|
||||||
|
|
||||||
doExperimentCleanup();
|
await doExperimentCleanup();
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -247,6 +244,8 @@ add_task(async function test_getLocalizedValue_unenroll_missingEntry() {
|
|||||||
"_getLocalizedValue() with a bogus localization"
|
"_getLocalizedValue() with a bogus localization"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(enrollment.slug);
|
||||||
|
|
||||||
Assert.equal(
|
Assert.equal(
|
||||||
manager.store.getExperimentForFeature(FEATURE_ID),
|
manager.store.getExperimentForFeature(FEATURE_ID),
|
||||||
null,
|
null,
|
||||||
@@ -316,6 +315,8 @@ add_task(async function test_getLocalizedValue_unenroll_missingEntry() {
|
|||||||
"_getLocalizedValue() with a bogus localization"
|
"_getLocalizedValue() with a bogus localization"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(enrollment.slug);
|
||||||
|
|
||||||
Assert.equal(
|
Assert.equal(
|
||||||
manager.store.getExperimentForFeature(FEATURE_ID),
|
manager.store.getExperimentForFeature(FEATURE_ID),
|
||||||
null,
|
null,
|
||||||
@@ -393,7 +394,7 @@ add_task(async function test_getVariables() {
|
|||||||
"getVariable() returns substitutions inside arrays"
|
"getVariable() returns substitutions inside arrays"
|
||||||
);
|
);
|
||||||
|
|
||||||
doExperimentCleanup();
|
await doExperimentCleanup();
|
||||||
await cleanup();
|
await cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -645,6 +646,9 @@ add_task(async function test_getVariables_fallback_unenroll() {
|
|||||||
waldo: ["fallback-waldo-pref-value"],
|
waldo: ["fallback-waldo-pref-value"],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment("experiment");
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment("rollout");
|
||||||
|
|
||||||
Assert.equal(
|
Assert.equal(
|
||||||
manager.store.getExperimentForFeature(FEATURE_ID),
|
manager.store.getExperimentForFeature(FEATURE_ID),
|
||||||
null,
|
null,
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ const { PrefUtils } = ChromeUtils.importESModule(
|
|||||||
const { JsonSchema } = ChromeUtils.importESModule(
|
const { JsonSchema } = ChromeUtils.importESModule(
|
||||||
"resource://gre/modules/JsonSchema.sys.mjs"
|
"resource://gre/modules/JsonSchema.sys.mjs"
|
||||||
);
|
);
|
||||||
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
|
||||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
const { ProfilesDatastoreService } = ChromeUtils.importESModule(
|
||||||
|
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs"
|
||||||
);
|
);
|
||||||
|
|
||||||
const USER = "user";
|
const USER = "user";
|
||||||
@@ -145,6 +146,7 @@ async function setupTest({ ...args } = {}) {
|
|||||||
...ctx,
|
...ctx,
|
||||||
async cleanup() {
|
async cleanup() {
|
||||||
assertNoObservers(ctx.manager);
|
assertNoObservers(ctx.manager);
|
||||||
|
await NimbusTestUtils.waitForAllUnenrollments();
|
||||||
await baseCleanup();
|
await baseCleanup();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -1606,6 +1608,8 @@ add_task(async function test_prefFlips_unenrollment() {
|
|||||||
Assert.ok(enrollment.active, `It should still be active`);
|
Assert.ok(enrollment.active, `It should still be active`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForActiveEnrollments(expectedEnrollments);
|
||||||
|
|
||||||
info("Checking expected unenrollments...");
|
info("Checking expected unenrollments...");
|
||||||
for (const slug of expectedUnenrollments) {
|
for (const slug of expectedUnenrollments) {
|
||||||
const enrollment = manager.store.get(slug);
|
const enrollment = manager.store.get(slug);
|
||||||
@@ -1614,6 +1618,13 @@ add_task(async function test_prefFlips_unenrollment() {
|
|||||||
Assert.ok(!enrollment.active, "It should no longer be active");
|
Assert.ok(!enrollment.active, "It should no longer be active");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let expectedCurrentEnrollments = new Set(expectedEnrollments).difference(
|
||||||
|
new Set(expectedUnenrollments)
|
||||||
|
);
|
||||||
|
await NimbusTestUtils.waitForActiveEnrollments(
|
||||||
|
Array.from(expectedCurrentEnrollments)
|
||||||
|
);
|
||||||
|
|
||||||
if (unenrollmentOrder) {
|
if (unenrollmentOrder) {
|
||||||
info("Unenrolling from specific experiments before checking prefs...");
|
info("Unenrolling from specific experiments before checking prefs...");
|
||||||
for (const slug of unenrollmentOrder ?? []) {
|
for (const slug of unenrollmentOrder ?? []) {
|
||||||
@@ -1621,6 +1632,13 @@ add_task(async function test_prefFlips_unenrollment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expectedCurrentEnrollments = expectedCurrentEnrollments.difference(
|
||||||
|
new Set(unenrollmentOrder)
|
||||||
|
);
|
||||||
|
await NimbusTestUtils.waitForActiveEnrollments(
|
||||||
|
Array.from(expectedCurrentEnrollments)
|
||||||
|
);
|
||||||
|
|
||||||
if (expectedPrefs) {
|
if (expectedPrefs) {
|
||||||
info("Checking expected prefs...");
|
info("Checking expected prefs...");
|
||||||
checkExpectedPrefs(expectedPrefs);
|
checkExpectedPrefs(expectedPrefs);
|
||||||
@@ -1634,6 +1652,8 @@ add_task(async function test_prefFlips_unenrollment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForActiveEnrollments([]);
|
||||||
|
|
||||||
info("Cleaning up prefs...");
|
info("Cleaning up prefs...");
|
||||||
Services.prefs.deleteBranch(PREF_FOO);
|
Services.prefs.deleteBranch(PREF_FOO);
|
||||||
Services.prefs.deleteBranch(PREF_BAR);
|
Services.prefs.deleteBranch(PREF_BAR);
|
||||||
@@ -2273,6 +2293,15 @@ add_task(async function test_prefFlips_failed() {
|
|||||||
const { manager, cleanup } = await setupTest();
|
const { manager, cleanup } = await setupTest();
|
||||||
await manager.enroll(recipe, "test");
|
await manager.enroll(recipe, "test");
|
||||||
|
|
||||||
|
// 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.waitForInactiveEnrollment(recipe.slug);
|
||||||
|
|
||||||
const enrollment = manager.store.get(recipe.slug);
|
const enrollment = manager.store.get(recipe.slug);
|
||||||
Assert.ok(!enrollment.active, "Experiment should not be active");
|
Assert.ok(!enrollment.active, "Experiment should not be active");
|
||||||
|
|
||||||
@@ -2342,6 +2371,15 @@ add_task(async function test_prefFlips_failed_multiple_prefs() {
|
|||||||
|
|
||||||
await manager.enroll(recipe, "test");
|
await manager.enroll(recipe, "test");
|
||||||
|
|
||||||
|
// 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.waitForInactiveEnrollment(recipe.slug);
|
||||||
|
|
||||||
const enrollment = manager.store.get(recipe.slug);
|
const enrollment = manager.store.get(recipe.slug);
|
||||||
Assert.ok(!enrollment.active, "Experiment should not be active");
|
Assert.ok(!enrollment.active, "Experiment should not be active");
|
||||||
|
|
||||||
@@ -2470,8 +2508,11 @@ add_task(async function test_prefFlips_failed_experiment_and_rollout_1() {
|
|||||||
Assert.ok(enrollment.active, `The enrollment for ${slug} is active`);
|
Assert.ok(enrollment.active, `The enrollment for ${slug} is active`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForActiveEnrollments(expectedEnrollments);
|
||||||
|
|
||||||
info("Checking expected unenrollments...");
|
info("Checking expected unenrollments...");
|
||||||
for (const slug of expectedUnenrollments) {
|
for (const slug of expectedUnenrollments) {
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(slug);
|
||||||
const enrollment = manager.store.get(slug);
|
const enrollment = manager.store.get(slug);
|
||||||
Assert.ok(!enrollment.active, "The enrollment is no longer active.");
|
Assert.ok(!enrollment.active, "The enrollment is no longer active.");
|
||||||
}
|
}
|
||||||
@@ -2577,6 +2618,8 @@ add_task(async function test_prefFlips_failed_experiment_and_rollout_2() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForActiveEnrollments(expectedEnrollments);
|
||||||
|
|
||||||
info("Checking expected enrollments...");
|
info("Checking expected enrollments...");
|
||||||
for (const slug of expectedEnrollments) {
|
for (const slug of expectedEnrollments) {
|
||||||
const enrollment = manager.store.get(slug);
|
const enrollment = manager.store.get(slug);
|
||||||
@@ -2585,6 +2628,7 @@ add_task(async function test_prefFlips_failed_experiment_and_rollout_2() {
|
|||||||
|
|
||||||
info("Checking expected unenrollments...");
|
info("Checking expected unenrollments...");
|
||||||
for (const slug of expectedUnenrollments) {
|
for (const slug of expectedUnenrollments) {
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(slug);
|
||||||
const enrollment = manager.store.get(slug);
|
const enrollment = manager.store.get(slug);
|
||||||
Assert.ok(!enrollment.active, "The enrollment is no longer active.");
|
Assert.ok(!enrollment.active, "The enrollment is no longer active.");
|
||||||
}
|
}
|
||||||
@@ -2643,6 +2687,8 @@ add_task(async function test_prefFlips_update_failure() {
|
|||||||
{ manager, slug: "experiment" }
|
{ manager, slug: "experiment" }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForActiveEnrollments(["rollout"]);
|
||||||
|
|
||||||
const rolloutEnrollment = manager.store.get("rollout");
|
const rolloutEnrollment = manager.store.get("rollout");
|
||||||
const experimentEnrollment = manager.store.get("experiment");
|
const experimentEnrollment = manager.store.get("experiment");
|
||||||
|
|
||||||
@@ -2653,7 +2699,7 @@ add_task(async function test_prefFlips_update_failure() {
|
|||||||
Assert.equal(Services.prefs.getStringPref("pref.one"), "one");
|
Assert.equal(Services.prefs.getStringPref("pref.one"), "one");
|
||||||
Assert.equal(Services.prefs.getStringPref("pref.two"), "two");
|
Assert.equal(Services.prefs.getStringPref("pref.two"), "two");
|
||||||
|
|
||||||
cleanupExperiment();
|
await cleanupExperiment();
|
||||||
|
|
||||||
Services.prefs.deleteBranch("pref.one");
|
Services.prefs.deleteBranch("pref.one");
|
||||||
Services.prefs.deleteBranch("pref.two");
|
Services.prefs.deleteBranch("pref.two");
|
||||||
@@ -2893,6 +2939,9 @@ add_task(async function test_prefFlips_restore_failure_conflict() {
|
|||||||
|
|
||||||
const { manager, cleanup } = await setupTest({ storePath });
|
const { manager, cleanup } = await setupTest({ storePath });
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForActiveEnrollments(["rollout-1"]);
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment("rollout-2");
|
||||||
|
|
||||||
Assert.ok(manager.store.get("rollout-1").active, "rollout-1 is active");
|
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.ok(!manager.store.get("rollout-2").active, "rollout-2 is not active");
|
||||||
Assert.equal(
|
Assert.equal(
|
||||||
@@ -2977,7 +3026,12 @@ add_task(async function test_prefFlips_restore_failure_wrong_type() {
|
|||||||
|
|
||||||
Services.prefs.setIntPref(PREF_1, 123);
|
Services.prefs.setIntPref(PREF_1, 123);
|
||||||
|
|
||||||
const { manager, cleanup } = await setupTest({ storePath });
|
const { manager, cleanup } = await setupTest({
|
||||||
|
storePath,
|
||||||
|
secureExperiments: [recipe],
|
||||||
|
});
|
||||||
|
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(recipe.slug);
|
||||||
|
|
||||||
const enrollment = manager.store.get(recipe.slug);
|
const enrollment = manager.store.get(recipe.slug);
|
||||||
|
|
||||||
@@ -3026,6 +3080,7 @@ add_task(
|
|||||||
PrefUtils.setPref(PREF, "default-value", { branch: DEFAULT });
|
PrefUtils.setPref(PREF, "default-value", { branch: DEFAULT });
|
||||||
|
|
||||||
await manager.enroll(recipe, "rs-loader");
|
await manager.enroll(recipe, "rs-loader");
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(recipe.slug);
|
||||||
|
|
||||||
let enrollment = manager.store.get(recipe.slug);
|
let enrollment = manager.store.get(recipe.slug);
|
||||||
|
|
||||||
@@ -3033,6 +3088,8 @@ add_task(
|
|||||||
Assert.equal(enrollment.unenrollReason, "prefFlips-failed");
|
Assert.equal(enrollment.unenrollReason, "prefFlips-failed");
|
||||||
|
|
||||||
await manager.enroll(recipe, "rs-loader", { reenroll: true });
|
await manager.enroll(recipe, "rs-loader", { reenroll: true });
|
||||||
|
await NimbusTestUtils.waitForInactiveEnrollment(recipe.slug);
|
||||||
|
|
||||||
enrollment = manager.store.get(recipe.slug);
|
enrollment = manager.store.get(recipe.slug);
|
||||||
|
|
||||||
Assert.ok(!enrollment.active, "enrollment should not be active");
|
Assert.ok(!enrollment.active, "enrollment should not be active");
|
||||||
@@ -3043,3 +3100,59 @@ add_task(
|
|||||||
await cleanup();
|
await cleanup();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
add_task(async function testDb() {
|
||||||
|
const { manager, cleanup } = await setupTest();
|
||||||
|
|
||||||
|
PrefUtils.setPref("foo.bar.baz", "foo");
|
||||||
|
|
||||||
|
await manager.enroll(
|
||||||
|
NimbusTestUtils.factories.recipe.withFeatureConfig("slug", {
|
||||||
|
featureId: "prefFlips",
|
||||||
|
value: {
|
||||||
|
prefs: {
|
||||||
|
"foo.bar.baz": {
|
||||||
|
value: "bar",
|
||||||
|
branch: "user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
"test"
|
||||||
|
);
|
||||||
|
|
||||||
|
const conn = await ProfilesDatastoreService.getConnection();
|
||||||
|
const [result] = await conn.execute(
|
||||||
|
`
|
||||||
|
SELECT
|
||||||
|
json(prefFlips) as prefFlips
|
||||||
|
FROM NimbusEnrollments
|
||||||
|
WHERE
|
||||||
|
profileId = :profileId AND
|
||||||
|
slug = :slug;
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
slug: "slug",
|
||||||
|
profileId: ExperimentAPI.profileId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const prefFlips = JSON.parse(result.getResultByName("prefFlips"));
|
||||||
|
const enrollment = manager.store.get("slug");
|
||||||
|
|
||||||
|
Assert.deepEqual(
|
||||||
|
prefFlips,
|
||||||
|
enrollment.prefFlips,
|
||||||
|
"prefFlips stored in the database"
|
||||||
|
);
|
||||||
|
Assert.deepEqual(prefFlips, {
|
||||||
|
originalValues: {
|
||||||
|
"foo.bar.baz": "foo",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await manager.unenroll("slug");
|
||||||
|
await cleanup();
|
||||||
|
|
||||||
|
Services.prefs.deleteBranch("foo.bar.baz");
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
head = "head.js"
|
head = "../../../../../toolkit/profile/test/xpcshell/head.js ../../../../../browser/components/profiles/tests/unit/head.js head.js"
|
||||||
tags = "nimbus"
|
tags = "nimbus"
|
||||||
firefox-appdir = "browser"
|
firefox-appdir = "browser"
|
||||||
support-files = ["reference_aboutwelcome_experiment_content.json"]
|
support-files = ["reference_aboutwelcome_experiment_content.json"]
|
||||||
@@ -39,7 +39,6 @@ run-sequentially = "very high failure rate in parallel"
|
|||||||
["test_FirefoxLabs.js"]
|
["test_FirefoxLabs.js"]
|
||||||
|
|
||||||
["test_Migrations.js"]
|
["test_Migrations.js"]
|
||||||
head = "../../../../../toolkit/profile/test/xpcshell/head.js ../../../../../browser/components/profiles/tests/unit/head.js head.js"
|
|
||||||
|
|
||||||
["test_NimbusTestUtils.js"]
|
["test_NimbusTestUtils.js"]
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user