Revert "Bug 1956080 - Add telemetry about database writes r=nimbus-reviewers,relud,chumphreys" for causing multiple bc failures.
This reverts commit53b0e55e9e. Revert "Bug 1956080 - Write enrollment updates to the NimbusEnrollments table r=jhirsch,nimbus-reviewers,relud,nalexander" This reverts commit128370986e. Revert "Bug 1956080 - Add a migration to copy existing enrollments to the NimbusEnrollments table r=jhirsch,nimbus-reviewers,relud,Gijs" This reverts commit0bbb1c3d7a.
This commit is contained in:
committed by
imoraru@mozilla.com
parent
52662d4efb
commit
7e9a937ea1
@@ -2037,11 +2037,6 @@ pref("messaging-system.rsexperimentloader.collection_id", "nimbus-desktop-experi
|
||||
pref("nimbus.debug", false);
|
||||
pref("nimbus.validation.enabled", true);
|
||||
|
||||
// Should Nimbus write to the shared ProfilesDatastoreService? Only used by tests.
|
||||
// TODO(bug 1967779): Require the ProfileDatastoreService by default and remove
|
||||
// this pref.
|
||||
pref("nimbus.profilesdatastoreservice.enabled", true);
|
||||
|
||||
// Enable the targeting context telemetry by default, but allow it to be
|
||||
// disabled, e.g., for artifact builds.
|
||||
// See-also: https://bugzilla.mozilla.org/show_bug.cgi?id=1936317
|
||||
|
||||
@@ -50,8 +50,3 @@ user_pref("preferences.force-disable.check.once.policy", true);
|
||||
user_pref("app.update.disabledForTesting", true);
|
||||
// Better stacks for errors.
|
||||
user_pref("javascript.options.asyncstack_capture_debuggee_only", false);
|
||||
|
||||
// Disable writing to the ProfileDatastoreService by Nimbus in xpcshell tests.
|
||||
// TODO(bug 1967779): Require the ProfileDatastoreService by default and remove
|
||||
// this.
|
||||
user_pref("nimbus.profilesdatastoreservice.enabled", false);
|
||||
|
||||
@@ -482,10 +482,6 @@ export class ExperimentManager {
|
||||
* as `recipe` and re-enrollment is prevented.
|
||||
*/
|
||||
async enroll(recipe, source, { reenroll = false, branchSlug } = {}) {
|
||||
if (typeof source !== "string") {
|
||||
throw new Error("source is required");
|
||||
}
|
||||
|
||||
let { slug, branches, bucketConfig, isFirefoxLabsOptIn } = recipe;
|
||||
|
||||
const enrollment = this.store.get(slug);
|
||||
@@ -577,11 +573,11 @@ export class ExperimentManager {
|
||||
}
|
||||
}
|
||||
|
||||
return this._enroll(recipe, branch.slug, source);
|
||||
return this._enroll(recipe, branch, source);
|
||||
}
|
||||
|
||||
async _enroll(recipe, branchSlug, source) {
|
||||
const {
|
||||
async _enroll(
|
||||
{
|
||||
slug,
|
||||
userFacingName,
|
||||
userFacingDescription,
|
||||
@@ -594,9 +590,10 @@ export class ExperimentManager {
|
||||
firefoxLabsDescriptionLinks = null,
|
||||
firefoxLabsGroup,
|
||||
requiresRestart = false,
|
||||
} = recipe;
|
||||
|
||||
const branch = recipe.branches.find(b => b.slug === branchSlug);
|
||||
},
|
||||
branch,
|
||||
source
|
||||
) {
|
||||
const { prefs, prefsToSet } = this._getPrefsForBranch(branch, isRollout);
|
||||
|
||||
// Unenroll in any conflicting prefFlips enrollments.
|
||||
@@ -607,6 +604,7 @@ export class ExperimentManager {
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {Enrollment} */
|
||||
const enrollment = {
|
||||
slug,
|
||||
branch,
|
||||
@@ -636,15 +634,13 @@ export class ExperimentManager {
|
||||
}
|
||||
|
||||
await this._prefFlips._annotateEnrollment(enrollment);
|
||||
|
||||
await this.store._addEnrollmentToDatabase(enrollment, recipe);
|
||||
this.store.addEnrollment(enrollment);
|
||||
|
||||
lazy.NimbusTelemetry.recordEnrollment(enrollment);
|
||||
|
||||
this._setEnrollmentPrefs(prefsToSet);
|
||||
this._updatePrefObservers(enrollment);
|
||||
|
||||
lazy.NimbusTelemetry.recordEnrollment(enrollment);
|
||||
|
||||
lazy.log.debug(
|
||||
`New ${isRollout ? "rollout" : "experiment"} started: ${slug}, ${
|
||||
branch.slug
|
||||
@@ -690,7 +686,7 @@ export class ExperimentManager {
|
||||
...recipe,
|
||||
slug,
|
||||
},
|
||||
branch.slug,
|
||||
branch,
|
||||
lazy.NimbusTelemetry.EnrollmentSource.FORCE_ENROLLMENT
|
||||
);
|
||||
|
||||
@@ -876,17 +872,6 @@ 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, {
|
||||
active: false,
|
||||
unenrollReason: cause.reason,
|
||||
|
||||
@@ -7,11 +7,8 @@ import { SharedDataMap } from "resource://nimbus/lib/SharedDataMap.sys.mjs";
|
||||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
|
||||
NimbusFeatures: "resource://nimbus/ExperimentAPI.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
|
||||
@@ -234,10 +231,6 @@ export class ExperimentStore extends SharedDataMap {
|
||||
this._emitFeatureUpdate(featureId, "feature-enrollments-loaded");
|
||||
}
|
||||
|
||||
await this._reportStartupDatabaseConsistency();
|
||||
|
||||
// Clean up the old recipes *after* we report database consistency so that
|
||||
// we're not racing.
|
||||
Services.tm.idleDispatchToMainThread(() => this._cleanupOldRecipes());
|
||||
}
|
||||
|
||||
@@ -477,173 +470,4 @@ export class ExperimentStore extends SharedDataMap {
|
||||
lazy.syncDataStore.deleteDefault(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;
|
||||
|
||||
let success = true;
|
||||
try {
|
||||
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,
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`ExperimentStore: Failed writing enrollment for ${enrollment.slug} to NimbusEnrollments`,
|
||||
e
|
||||
);
|
||||
success = false;
|
||||
}
|
||||
|
||||
Glean.nimbusEvents.databaseWrite.record({ success });
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
let success = true;
|
||||
try {
|
||||
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,
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`ExperimentStore: Failed writing unenrollment for ${slug} to NimbusEnrollments`,
|
||||
e
|
||||
);
|
||||
success = false;
|
||||
}
|
||||
|
||||
Glean.nimbusEvents.databaseWrite.record({ success });
|
||||
}
|
||||
|
||||
async _reportStartupDatabaseConsistency() {
|
||||
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 conn = await lazy.ProfilesDatastoreService.getConnection();
|
||||
const rows = await conn.execute(
|
||||
`
|
||||
SELECT
|
||||
slug,
|
||||
active
|
||||
FROM NimbusEnrollments
|
||||
WHERE
|
||||
profileId = :profileId;
|
||||
`,
|
||||
{
|
||||
profileId: lazy.ExperimentAPI.profileId,
|
||||
}
|
||||
);
|
||||
|
||||
const dbEnrollments = rows.map(row => row.getResultByName("active"));
|
||||
const storeEnrollments = this.getAll().map(e => e.active);
|
||||
|
||||
function countActive(sum, active) {
|
||||
return sum + Number(active);
|
||||
}
|
||||
|
||||
const dbActiveCount = dbEnrollments.reduce(countActive, 0);
|
||||
const storeActiveCount = storeEnrollments.reduce(countActive, 0);
|
||||
|
||||
Glean.nimbusEvents.startupDatabaseConsistency.record({
|
||||
total_db_count: dbEnrollments.length,
|
||||
total_store_count: storeEnrollments.length,
|
||||
db_active_count: dbActiveCount,
|
||||
store_active_count: storeActiveCount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
FirefoxLabs: "resource://nimbus/FirefoxLabs.sys.mjs",
|
||||
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
|
||||
NimbusTelemetry: "resource://nimbus/lib/Telemetry.sys.mjs",
|
||||
ProfilesDatastoreService:
|
||||
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs",
|
||||
});
|
||||
|
||||
ChromeUtils.defineLazyGetter(lazy, "log", () => {
|
||||
@@ -88,122 +86,6 @@ function migrateMultiphase() {
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateEnrollmentsToSql() {
|
||||
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;
|
||||
|
||||
// This migration runs before the ExperimentManager is fully initialized. We
|
||||
// need to initialize *just* the ExperimentStore so that we can copy its
|
||||
// enrollments to the SQL database. This must occur *before* the
|
||||
// ExperimentManager is initialized because that may cause unenrollments and
|
||||
// those enrollments need to exist in both the ExperimentStore and SQL
|
||||
// database.
|
||||
|
||||
const enrollments = await lazy.ExperimentAPI.manager.store.getAll();
|
||||
|
||||
// Likewise, the set of all recipes is
|
||||
const { recipes } =
|
||||
await lazy.ExperimentAPI._rsLoader.getRecipesFromAllCollections();
|
||||
|
||||
const recipesBySlug = new Map(recipes.map(r => [r.slug, r]));
|
||||
|
||||
const rows = enrollments.map(enrollment => {
|
||||
const { active, slug, source } = enrollment;
|
||||
|
||||
let recipe;
|
||||
if (source === "rs-loader") {
|
||||
recipe = recipesBySlug.get(slug);
|
||||
}
|
||||
if (!recipe) {
|
||||
// If this enrollment is not from the RemoteSettingsExperimentLoader or
|
||||
// the experiment has since ended we re-create as much of the recipe as we
|
||||
// can from the enrollment.
|
||||
//
|
||||
// We are early in Nimbus startup and we have not yet called
|
||||
// ExperimentManager.onStartup. When the ExperimentManager is initialized
|
||||
// later in ExperimentAPI.init() it may cause unenrollments due to state
|
||||
// being changed (e.g., if studies have been disabled). To process those
|
||||
// unenrollments, there needs to be an enrollment record
|
||||
// in the database for *every* enrollment in the JSON store *and* each
|
||||
// needs to have a valid `recipe` field because in bug 1956082 we will
|
||||
// stop using ExperimentStoreData.json as the source-of-truth and rely
|
||||
// entirely on the NimbusEnrollments table.
|
||||
recipe = {
|
||||
slug,
|
||||
userFacingName: enrollment.userFacingName,
|
||||
userFacingDescription: enrollment.userFacingDescription,
|
||||
featureIds: enrollment.featureIds,
|
||||
isRollout: enrollment.isRollout ?? false,
|
||||
localizations: enrollment.localizations ?? null,
|
||||
isFirefoxLabsOptIn: enrollment.isFirefoxLabsOptIn ?? false,
|
||||
firefoxLabsTitle: enrollment.firefoxLabsTitle ?? false,
|
||||
firefoxLabsDescription: enrollment.firefoxLabsDescription ?? null,
|
||||
firefoxLabsDescriptionLinks:
|
||||
enrollment.firefoxLabsDescriptionLinks ?? null,
|
||||
firefoxLabsGroup: enrollment.firefoxLabsGroup ?? null,
|
||||
requiresRestart: enrollment.requiresRestart ?? false,
|
||||
branches: [
|
||||
{
|
||||
...enrollment.branch,
|
||||
ratio: enrollment.branch.ratio ?? 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
profileId,
|
||||
slug,
|
||||
branchSlug: enrollment.branch.slug,
|
||||
recipe: recipe ? JSON.stringify(recipe) : null,
|
||||
active,
|
||||
unenrollReason: active ? null : enrollment.unenrollReason,
|
||||
lastSeen: enrollment.lastSeen ?? new Date().toJSON(),
|
||||
setPrefs: enrollment.prefs ? JSON.stringify(enrollment.prefs) : null,
|
||||
prefFlips: enrollment.prefFlips
|
||||
? JSON.stringify(enrollment.prefFlips)
|
||||
: null,
|
||||
source,
|
||||
};
|
||||
});
|
||||
|
||||
const conn = await lazy.ProfilesDatastoreService.getConnection();
|
||||
await conn.executeTransaction(async () => {
|
||||
for (const row of rows) {
|
||||
await conn.execute(
|
||||
`
|
||||
INSERT INTO NimbusEnrollments VALUES(
|
||||
null,
|
||||
:profileId,
|
||||
:slug,
|
||||
:branchSlug,
|
||||
jsonb(:recipe),
|
||||
:active,
|
||||
:unenrollReason,
|
||||
:lastSeen,
|
||||
jsonb(:setPrefs),
|
||||
jsonb(:prefFlips),
|
||||
:source
|
||||
);`,
|
||||
row
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate the pre-Nimbus Firefox Labs experiences into Nimbus enrollments.
|
||||
*
|
||||
@@ -289,6 +171,7 @@ async function migrateFirefoxLabsEnrollments() {
|
||||
{ mode: "shared" }
|
||||
);
|
||||
}
|
||||
|
||||
export class MigrationError extends Error {
|
||||
static Reason = Object.freeze({
|
||||
UNKNOWN: "unknown",
|
||||
@@ -368,9 +251,7 @@ export const NimbusMigrations = {
|
||||
migration("multi-phase-migrations", migrateMultiphase),
|
||||
],
|
||||
|
||||
[Phase.AFTER_STORE_INITIALIZED]: [
|
||||
migration("import-enrollments-to-sql", migrateEnrollmentsToSql),
|
||||
],
|
||||
[Phase.AFTER_STORE_INITIALIZED]: [],
|
||||
|
||||
[Phase.AFTER_REMOTE_SETTINGS_UPDATE]: [
|
||||
migration("firefox-labs-enrollments", migrateFirefoxLabsEnrollments),
|
||||
|
||||
@@ -518,7 +518,7 @@ export class PrefFlipsFeature {
|
||||
const entry = this.#prefs.get(pref);
|
||||
|
||||
if (entry.branch !== branch || entry.value !== value) {
|
||||
throw new PrefFlipsFailedError(pref, value);
|
||||
throw new PrefFlipsFailedError(pref);
|
||||
}
|
||||
|
||||
entry.slugs.add(slug);
|
||||
|
||||
@@ -924,10 +924,7 @@ export class EnrollmentsContext {
|
||||
const invalidFeatureIds = new Set();
|
||||
const missingL10nIds = new Set();
|
||||
|
||||
if (
|
||||
validateSchema ||
|
||||
(typeof localizations === "object" && localizations !== null)
|
||||
) {
|
||||
if (validateSchema || typeof localizations !== "undefined") {
|
||||
for (const [branchIdx, branch] of branches.entries()) {
|
||||
const features = branch.features ?? [branch.feature];
|
||||
for (const feature of features) {
|
||||
|
||||
@@ -1031,68 +1031,6 @@ nimbus_events:
|
||||
- project-nimbus@mozilla.com
|
||||
expires: never
|
||||
|
||||
startup_database_consistency:
|
||||
type: event
|
||||
description: >
|
||||
Emitted during ExperimentManager startup with details about the
|
||||
ExperimentDataStore compared to the NimbusEnrollments database table.
|
||||
extra_keys:
|
||||
total_db_count:
|
||||
description: >
|
||||
The total number of enrollments in the NimbusEnrollments table.
|
||||
type: quantity
|
||||
unit: enrollments
|
||||
|
||||
total_store_count:
|
||||
description: >
|
||||
The total number of enrollments in the ExperimentStore JSON file.
|
||||
type: quantity
|
||||
unit: enrollments
|
||||
|
||||
db_active_count:
|
||||
description: >
|
||||
The number of active enrollments in the NimbusEnrollments table.
|
||||
type: quantity
|
||||
unit: enrollments
|
||||
|
||||
store_active_count:
|
||||
description: >
|
||||
The number of active enrollments in the ExperimentStore JSON file.
|
||||
type: quantity
|
||||
unit: enrollments
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1956079
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1956080
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1956079
|
||||
notification_emails:
|
||||
- beth@mozilla.com
|
||||
- project-nimbus@mozilla.com
|
||||
data_sensitivity:
|
||||
- technical
|
||||
expires: never
|
||||
|
||||
database_write:
|
||||
type: event
|
||||
description: >
|
||||
Emitted when writing to the NimbusEnrollments database table.
|
||||
extra_keys:
|
||||
success:
|
||||
type: boolean
|
||||
description: Whether or not the write suceeded.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1956079
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1956080
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1956079
|
||||
notification_emails:
|
||||
- beth@mozilla.com
|
||||
- project-nimbus@mozilla.com
|
||||
data_sensitivity:
|
||||
- technical
|
||||
expires: never
|
||||
|
||||
|
||||
normandy:
|
||||
expose_nimbus_experiment:
|
||||
type: event
|
||||
|
||||
@@ -16,12 +16,8 @@ ChromeUtils.defineESModuleGetters(lazy, {
|
||||
JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
|
||||
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
|
||||
ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",
|
||||
ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
|
||||
ProfilesDatastoreService:
|
||||
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs",
|
||||
RemoteSettingsExperimentLoader:
|
||||
"resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs",
|
||||
TestUtils: "resource://testing-common/TestUtils.sys.mjs",
|
||||
sinon: "resource://testing-common/Sinon.sys.mjs",
|
||||
});
|
||||
|
||||
@@ -135,7 +131,7 @@ export const NimbusTestUtils = {
|
||||
* @param {object} store
|
||||
* The `ExperimentStore`.
|
||||
*/
|
||||
async storeIsEmpty(store) {
|
||||
storeIsEmpty(store) {
|
||||
NimbusTestUtils.Assert.deepEqual(
|
||||
store
|
||||
.getAll()
|
||||
@@ -160,8 +156,6 @@ export const NimbusTestUtils = {
|
||||
);
|
||||
|
||||
NimbusTestUtils.cleanupStorePrefCache();
|
||||
|
||||
await NimbusTestUtils.cleanupEnrollmentDatabase();
|
||||
},
|
||||
},
|
||||
|
||||
@@ -194,7 +188,6 @@ export const NimbusTestUtils = {
|
||||
value: { testInt: 123, enabled: true },
|
||||
},
|
||||
],
|
||||
firefoxLabsTitle: null,
|
||||
},
|
||||
source: "NimbusTestUtils",
|
||||
isEnrollmentPaused: true,
|
||||
@@ -205,14 +198,6 @@ export const NimbusTestUtils = {
|
||||
featureIds: props?.branch?.features?.map(f => f.featureId) ?? [
|
||||
"testFeature",
|
||||
],
|
||||
isRollout: false,
|
||||
isFirefoxLabsOptIn: false,
|
||||
firefoxLabsTitle: null,
|
||||
firefoxLabsDescription: null,
|
||||
firefoxLabsDescriptionLinks: null,
|
||||
firefoxLabsGroup: null,
|
||||
requiresRestart: false,
|
||||
localizations: null,
|
||||
...props,
|
||||
};
|
||||
},
|
||||
@@ -268,13 +253,6 @@ export const NimbusTestUtils = {
|
||||
],
|
||||
targeting: "true",
|
||||
isRollout: false,
|
||||
isFirefoxLabsOptIn: false,
|
||||
firefoxLabsTitle: null,
|
||||
firefoxLabsDescription: null,
|
||||
firefoxLabsDescriptionLinks: null,
|
||||
firefoxLabsGroup: null,
|
||||
requiresRestart: false,
|
||||
localizations: null,
|
||||
...props,
|
||||
};
|
||||
},
|
||||
@@ -404,57 +382,7 @@ export const NimbusTestUtils = {
|
||||
await experimentManager.unenroll(slug);
|
||||
}
|
||||
|
||||
await NimbusTestUtils.assert.storeIsEmpty(experimentManager.store);
|
||||
},
|
||||
|
||||
async cleanupEnrollmentDatabase() {
|
||||
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 = ExperimentAPI.profileId;
|
||||
|
||||
const conn = await lazy.ProfilesDatastoreService.getConnection();
|
||||
|
||||
const activeSlugs = await conn
|
||||
.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(
|
||||
`
|
||||
DELETE FROM NimbusEnrollments
|
||||
WHERE
|
||||
profileId = :profileId AND
|
||||
active = false;
|
||||
`,
|
||||
{ profileId }
|
||||
);
|
||||
NimbusTestUtils.assert.storeIsEmpty(experimentManager.store);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -518,9 +446,15 @@ export const NimbusTestUtils = {
|
||||
|
||||
experimentManager.store._syncToChildren({ flush: true });
|
||||
|
||||
return async function doEnrollmentCleanup() {
|
||||
await experimentManager.unenroll(enrollment.slug);
|
||||
return function doEnrollmentCleanup() {
|
||||
// TODO(bug 1956082): This is an async method that we are not awaiting.
|
||||
//
|
||||
// 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);
|
||||
|
||||
return promise;
|
||||
};
|
||||
},
|
||||
|
||||
@@ -736,9 +670,6 @@ export const NimbusTestUtils = {
|
||||
Services.fog.testResetFOG();
|
||||
Services.telemetry.clearEvents();
|
||||
}
|
||||
|
||||
// Remove all migration state.
|
||||
Services.prefs.deleteBranch("nimbus.migrations.");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -809,80 +740,6 @@ export const NimbusTestUtils = {
|
||||
`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, {
|
||||
@@ -966,7 +823,6 @@ Object.defineProperties(NimbusTestUtils.factories.recipe, {
|
||||
value: { testInt: 123, enabled: true },
|
||||
},
|
||||
],
|
||||
firefoxLabsTitle: null,
|
||||
},
|
||||
{
|
||||
slug: "treatment",
|
||||
@@ -977,7 +833,6 @@ Object.defineProperties(NimbusTestUtils.factories.recipe, {
|
||||
value: { testInt: 123, enabled: true },
|
||||
},
|
||||
],
|
||||
firefoxLabsTitle: null,
|
||||
},
|
||||
];
|
||||
},
|
||||
@@ -1004,7 +859,6 @@ Object.defineProperties(NimbusTestUtils.factories.recipe, {
|
||||
value,
|
||||
},
|
||||
],
|
||||
firefoxLabsTitle: null,
|
||||
},
|
||||
],
|
||||
...props,
|
||||
|
||||
@@ -61,8 +61,7 @@ add_task(async function testGetFromChildNewEnrollment() {
|
||||
enabled: true,
|
||||
testInt: 123,
|
||||
},
|
||||
}),
|
||||
"test"
|
||||
})
|
||||
);
|
||||
|
||||
// Immediately serialize sharedData and broadcast changes to the child processes.
|
||||
@@ -144,8 +143,7 @@ add_task(async function testGetFromChildExistingEnrollment() {
|
||||
enabled: false,
|
||||
testInt: 456,
|
||||
},
|
||||
}),
|
||||
"test"
|
||||
})
|
||||
);
|
||||
|
||||
// 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
|
||||
);
|
||||
|
||||
await cleanup();
|
||||
cleanup();
|
||||
|
||||
TelemetryTestUtils.assertEvents(
|
||||
[
|
||||
@@ -98,7 +98,7 @@ add_task(async function test_experiment_expose_Telemetry() {
|
||||
EVENT_FILTER
|
||||
);
|
||||
|
||||
await cleanup();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
add_task(async function test_rollout_expose_Telemetry() {
|
||||
@@ -132,5 +132,5 @@ add_task(async function test_rollout_expose_Telemetry() {
|
||||
EVENT_FILTER
|
||||
);
|
||||
|
||||
await cleanup();
|
||||
cleanup();
|
||||
});
|
||||
|
||||
@@ -356,7 +356,7 @@ add_task(async function test_finalizeRemoteConfigs_cleanup() {
|
||||
"Pref was cleared"
|
||||
);
|
||||
|
||||
await fooCleanup();
|
||||
fooCleanup();
|
||||
// This will also remove the inactive recipe from the store
|
||||
// the previous update (from recipe not seen code path)
|
||||
// only sets the recipe as inactive
|
||||
@@ -445,8 +445,6 @@ add_task(async function remote_defaults_active_remote_defaults() {
|
||||
await featureUpdate;
|
||||
|
||||
Assert.ok(fooFeature.getVariable("enabled"), "Targeting should match");
|
||||
|
||||
await NimbusTestUtils.cleanupManager(["foo", "bar"]);
|
||||
ExperimentAPI.manager.store._deleteForTests("foo");
|
||||
ExperimentAPI.manager.store._deleteForTests("bar");
|
||||
|
||||
@@ -515,7 +513,7 @@ add_task(async function remote_defaults_variables_storage() {
|
||||
"Test types are returned correctly"
|
||||
);
|
||||
|
||||
await doCleanup();
|
||||
doCleanup();
|
||||
|
||||
Assert.equal(
|
||||
Services.prefs.getIntPref(`${SYNC_DEFAULTS_PREF_BRANCH}bar.storage`, -1),
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
"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(
|
||||
"resource://testing-common/Sinon.sys.mjs"
|
||||
);
|
||||
@@ -26,19 +23,8 @@ ChromeUtils.defineESModuleGetters(this, {
|
||||
|
||||
NimbusTestUtils.init(this);
|
||||
|
||||
add_setup(async function () {
|
||||
add_setup(function () {
|
||||
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"
|
||||
);
|
||||
|
||||
await cleanupExperiment();
|
||||
cleanupExperiment();
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
@@ -212,7 +212,7 @@ add_task(async function test_allow_multiple_exposure_events() {
|
||||
// We expect 3 events
|
||||
Assert.equal(3, exposureEvents.length);
|
||||
|
||||
await doExperimentCleanup();
|
||||
doExperimentCleanup();
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
const { AppConstants } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/AppConstants.sys.mjs"
|
||||
);
|
||||
|
||||
const FEATURE_ID = "testfeature1";
|
||||
// Note: this gets deleted at the end of tests
|
||||
const TEST_PREF_BRANCH = "testfeature1.";
|
||||
|
||||
@@ -135,7 +135,7 @@ add_task(async function test_enroll_optin_recipe_branch_selection() {
|
||||
Assert.ok(
|
||||
manager._enroll.calledOnceWith(
|
||||
optInRecipe,
|
||||
optInRecipe.branches[0].slug,
|
||||
optInRecipe.branches[0],
|
||||
"test"
|
||||
),
|
||||
"should call ._enroll() with the correct arguments"
|
||||
@@ -871,7 +871,7 @@ add_task(async function test_featureIds_is_stored() {
|
||||
"Has expected value"
|
||||
);
|
||||
|
||||
await doExperimentCleanup();
|
||||
doExperimentCleanup();
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
@@ -907,7 +907,7 @@ add_task(async function experiment_and_rollout_enroll_and_cleanup() {
|
||||
)
|
||||
);
|
||||
|
||||
await doExperimentCleanup();
|
||||
doExperimentCleanup();
|
||||
|
||||
Assert.ok(
|
||||
!Services.prefs.getBoolPref(
|
||||
@@ -921,7 +921,7 @@ add_task(async function experiment_and_rollout_enroll_and_cleanup() {
|
||||
)
|
||||
);
|
||||
|
||||
await doRolloutCleanup();
|
||||
doRolloutCleanup();
|
||||
|
||||
Assert.ok(
|
||||
!Services.prefs.getBoolPref(
|
||||
|
||||
@@ -15,10 +15,6 @@ const { UnenrollmentCause } = ChromeUtils.importESModule(
|
||||
"resource://nimbus/lib/ExperimentManager.sys.mjs"
|
||||
);
|
||||
|
||||
const { ProfilesDatastoreService } = ChromeUtils.importESModule(
|
||||
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs"
|
||||
);
|
||||
|
||||
/**
|
||||
* onStartup()
|
||||
* - should set call setExperimentActive for each active experiment
|
||||
@@ -368,208 +364,3 @@ add_task(async function test_experimentStore_updateEvent() {
|
||||
|
||||
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"
|
||||
);
|
||||
|
||||
const { ProfilesDatastoreService } = ChromeUtils.importESModule(
|
||||
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs"
|
||||
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
function assertIncludes(array, obj, msg) {
|
||||
@@ -319,7 +319,7 @@ add_task(async function test_enroll_setPref_rolloutsAndExperiments() {
|
||||
}
|
||||
|
||||
for (const enrollmentKind of unenrollOrder) {
|
||||
await cleanupFns[enrollmentKind]();
|
||||
cleanupFns[enrollmentKind]();
|
||||
|
||||
assertExpectedPrefValues(
|
||||
pref,
|
||||
@@ -1712,6 +1712,9 @@ add_task(async function test_prefChange() {
|
||||
const cleanupFunctions = {};
|
||||
const slugs = {};
|
||||
|
||||
await manager.store.init();
|
||||
await manager.onStartup();
|
||||
|
||||
setPrefs(pref, { defaultBranchValue, userBranchValue });
|
||||
|
||||
info(`Enrolling in ${Array.from(Object.keys(configs)).join(", ")} ...`);
|
||||
@@ -1741,10 +1744,6 @@ add_task(async function test_prefChange() {
|
||||
|
||||
PrefUtils.setPref(pref, OVERWRITE_VALUE, { branch: setBranch });
|
||||
|
||||
await NimbusTestUtils.waitForActiveEnrollments(
|
||||
expectedEnrollments.map(kind => slugs[kind])
|
||||
);
|
||||
|
||||
if (expectedDefault === null) {
|
||||
Assert.ok(
|
||||
!Services.prefs.prefHasDefaultValue(pref),
|
||||
@@ -1784,9 +1783,6 @@ add_task(async function test_prefChange() {
|
||||
for (const enrollmentKind of Object.keys(configs)) {
|
||||
if (!expectedEnrollments.includes(enrollmentKind)) {
|
||||
const slug = slugs[enrollmentKind];
|
||||
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(slug);
|
||||
|
||||
const enrollment = manager.store.get(slug);
|
||||
|
||||
Assert.ok(
|
||||
@@ -1877,7 +1873,7 @@ add_task(async function test_prefChange() {
|
||||
);
|
||||
|
||||
for (const enrollmentKind of expectedEnrollments) {
|
||||
await cleanupFunctions[enrollmentKind]();
|
||||
cleanupFunctions[enrollmentKind]();
|
||||
}
|
||||
|
||||
Services.prefs.deleteBranch(pref);
|
||||
@@ -2326,7 +2322,7 @@ add_task(async function test_deleteBranch() {
|
||||
);
|
||||
|
||||
for (const cleanupFn of cleanupFunctions) {
|
||||
await cleanupFn();
|
||||
cleanupFn();
|
||||
}
|
||||
|
||||
Services.prefs.deleteBranch(PREFS[USER]);
|
||||
@@ -2409,11 +2405,6 @@ add_task(async function test_clearUserPref() {
|
||||
|
||||
for (const enrollmentKind of Object.keys(configs)) {
|
||||
const slug = slugs[enrollmentKind];
|
||||
|
||||
if (!expectedEnrolled) {
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(slug);
|
||||
}
|
||||
|
||||
const enrollment = manager.store.get(slug);
|
||||
Assert.ok(
|
||||
enrollment !== null,
|
||||
@@ -2447,7 +2438,7 @@ add_task(async function test_clearUserPref() {
|
||||
|
||||
if (expectedEnrolled) {
|
||||
for (const cleanupFn of Object.values(cleanupFns)) {
|
||||
await cleanupFn();
|
||||
cleanupFn();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2685,7 +2676,7 @@ add_task(async function test_prefChanged_noPrefSet() {
|
||||
);
|
||||
}
|
||||
|
||||
await doEnrollmentCleanup();
|
||||
doEnrollmentCleanup();
|
||||
await cleanup();
|
||||
|
||||
Services.prefs.deleteBranch(pref);
|
||||
@@ -3397,7 +3388,7 @@ add_task(async function test_setPref_types() {
|
||||
|
||||
Assert.deepEqual(json, jsonPrefValue);
|
||||
|
||||
await experimentCleanup();
|
||||
experimentCleanup();
|
||||
featureCleanup();
|
||||
await cleanup();
|
||||
});
|
||||
@@ -3490,56 +3481,3 @@ add_task(async function test_setPref_types_restore() {
|
||||
await cleanup();
|
||||
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,8 +60,6 @@ add_task(async function test_unenroll_opt_out() {
|
||||
|
||||
Services.prefs.setBoolPref(STUDIES_OPT_OUT_PREF, false);
|
||||
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(experiment.slug);
|
||||
|
||||
Assert.equal(
|
||||
manager.store.get(experiment.slug).active,
|
||||
false,
|
||||
@@ -121,8 +119,6 @@ add_task(async function test_unenroll_rollout_opt_out() {
|
||||
|
||||
Services.prefs.setBoolPref(STUDIES_OPT_OUT_PREF, false);
|
||||
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(rollout.slug);
|
||||
|
||||
Assert.equal(
|
||||
manager.store.get(rollout.slug).active,
|
||||
false,
|
||||
@@ -175,8 +171,6 @@ add_task(async function test_unenroll_uploadPref() {
|
||||
|
||||
Services.prefs.setBoolPref(UPLOAD_ENABLED_PREF, false);
|
||||
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(recipe.slug);
|
||||
|
||||
Assert.equal(
|
||||
manager.store.get(recipe.slug).active,
|
||||
false,
|
||||
|
||||
@@ -6,10 +6,6 @@ const { ExperimentStore } = ChromeUtils.importESModule(
|
||||
|
||||
const { SYNC_DATA_PREF_BRANCH, SYNC_DEFAULTS_PREF_BRANCH } = ExperimentStore;
|
||||
|
||||
add_setup(function () {
|
||||
Services.fog.initializeFOG();
|
||||
});
|
||||
|
||||
async function setupTest({ ...args } = {}) {
|
||||
const ctx = await NimbusTestUtils.setupTest({ ...args });
|
||||
|
||||
@@ -106,7 +102,7 @@ add_task(async function test_initOnUpdateEventsFire() {
|
||||
storePath = await NimbusTestUtils.saveStore(store);
|
||||
}
|
||||
|
||||
const { sandbox, initExperimentAPI, cleanup } = await setupTest({
|
||||
const { sandbox, store, initExperimentAPI, cleanup } = await setupTest({
|
||||
storePath,
|
||||
init: false,
|
||||
});
|
||||
@@ -140,14 +136,13 @@ add_task(async function test_initOnUpdateEventsFire() {
|
||||
|
||||
NimbusFeatures.testFeature.offUpdate(onFeatureUpdate);
|
||||
|
||||
await NimbusTestUtils.cleanupManager([
|
||||
"testFeature-1",
|
||||
"testFeature-2",
|
||||
"coenroll-1",
|
||||
"coenroll-2",
|
||||
"coenroll-3",
|
||||
"coenroll-4",
|
||||
]);
|
||||
store.updateExperiment("testFeature-1", { active: false });
|
||||
store.updateExperiment("testFeature-2", { active: false });
|
||||
store.updateExperiment("coenroll-1", { active: false });
|
||||
store.updateExperiment("coenroll-2", { active: false });
|
||||
store.updateExperiment("coenroll-3", { active: false });
|
||||
store.updateExperiment("coenroll-4", { active: false });
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
@@ -862,94 +857,8 @@ add_task(async function test_restore() {
|
||||
Assert.ok(store.get("experiment"));
|
||||
Assert.ok(store.get("rollout"));
|
||||
|
||||
await NimbusTestUtils.cleanupManager(["experiment", "rollout"]);
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
add_task(async function test_restoreDatabaseConsistency() {
|
||||
Services.fog.testResetFOG();
|
||||
|
||||
let storePath;
|
||||
|
||||
{
|
||||
const store = await NimbusTestUtils.stubs.store();
|
||||
await store.init();
|
||||
|
||||
const experimentRecipe = NimbusTestUtils.factories.recipe.withFeatureConfig(
|
||||
"experiment",
|
||||
{ featureId: "no-feature-firefox-desktop" }
|
||||
);
|
||||
const experimentEnrollment =
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig("experiment", {
|
||||
featureId: "no-feature-firefox-desktop",
|
||||
});
|
||||
|
||||
const rolloutRecipe = NimbusTestUtils.factories.recipe.withFeatureConfig(
|
||||
"rollout",
|
||||
{ featureId: "no-feature-firefox-desktop" },
|
||||
{ isRollout: true }
|
||||
);
|
||||
const rolloutEnrollment =
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig(
|
||||
"rollout",
|
||||
{ featureId: "no-feature-firefox-desktop" },
|
||||
{ isRollout: true }
|
||||
);
|
||||
|
||||
const inactiveExperimentEnrollment =
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig(
|
||||
"inactive",
|
||||
{ featureId: "no-feature-firefox-desktop" },
|
||||
{ active: false }
|
||||
);
|
||||
|
||||
store.addEnrollment(experimentEnrollment);
|
||||
await store._addEnrollmentToDatabase(
|
||||
experimentEnrollment,
|
||||
experimentRecipe
|
||||
);
|
||||
|
||||
store.addEnrollment(rolloutEnrollment);
|
||||
await store._addEnrollmentToDatabase(rolloutEnrollment, rolloutRecipe);
|
||||
|
||||
store.addEnrollment(inactiveExperimentEnrollment);
|
||||
await store._addEnrollmentToDatabase(inactiveExperimentEnrollment, null);
|
||||
|
||||
// We should expect to see three successful databaseWrite events.
|
||||
const events = Glean.nimbusEvents.databaseWrite
|
||||
.testGetValue("events")
|
||||
.map(ev => ev.extra);
|
||||
|
||||
Assert.deepEqual(events, [
|
||||
{ success: "true" },
|
||||
{ success: "true" },
|
||||
{ success: "true" },
|
||||
]);
|
||||
|
||||
storePath = await NimbusTestUtils.saveStore(store);
|
||||
}
|
||||
|
||||
// Initializing the store above will submit the event we care about. Disregard
|
||||
// any metrics previously recorded.
|
||||
Services.fog.testResetFOG();
|
||||
|
||||
const { cleanup } = await NimbusTestUtils.setupTest({
|
||||
storePath,
|
||||
clearTelemetry: true,
|
||||
});
|
||||
|
||||
const events = Glean.nimbusEvents.startupDatabaseConsistency
|
||||
.testGetValue("events")
|
||||
.map(ev => ev.extra);
|
||||
Assert.deepEqual(events, [
|
||||
{
|
||||
total_db_count: "3",
|
||||
total_store_count: "3",
|
||||
db_active_count: "2",
|
||||
store_active_count: "2",
|
||||
},
|
||||
]);
|
||||
|
||||
await NimbusTestUtils.cleanupManager(["rollout", "experiment"]);
|
||||
store.updateExperiment("experiment", { active: false });
|
||||
store.updateExperiment("rollout", { active: false });
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
@@ -9,14 +9,6 @@ const {
|
||||
NimbusMigrations,
|
||||
} = ChromeUtils.importESModule("resource://nimbus/lib/Migrations.sys.mjs");
|
||||
|
||||
const { NimbusTelemetry } = ChromeUtils.importESModule(
|
||||
"resource://nimbus/lib/Telemetry.sys.mjs"
|
||||
);
|
||||
|
||||
const { ProfilesDatastoreService } = ChromeUtils.importESModule(
|
||||
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs"
|
||||
);
|
||||
|
||||
/** @typedef {import("../../lib/Migrations.sys.mjs").Migration} Migration */
|
||||
/** @typedef {import("../../lib/Migrations.sys.mjs").Phase} Phase */
|
||||
|
||||
@@ -42,7 +34,7 @@ function getEnabledPrefForFeature(featureId) {
|
||||
return NimbusFeatures[featureId].manifest.variables.enabled.setPref.pref;
|
||||
}
|
||||
|
||||
add_setup(async function setup() {
|
||||
add_setup(function setup() {
|
||||
Services.fog.initializeFOG();
|
||||
});
|
||||
|
||||
@@ -84,7 +76,11 @@ async function setupTest({
|
||||
);
|
||||
}
|
||||
|
||||
const { initExperimentAPI, ...ctx } = await NimbusTestUtils.setupTest({
|
||||
const {
|
||||
initExperimentAPI,
|
||||
cleanup: baseCleanup,
|
||||
...ctx
|
||||
} = await NimbusTestUtils.setupTest({
|
||||
init: false,
|
||||
clearTelemetry: true,
|
||||
...args,
|
||||
@@ -106,16 +102,7 @@ async function setupTest({
|
||||
}
|
||||
|
||||
if (migrations) {
|
||||
// If the test only specifies some of the phases ensure that there are
|
||||
// placeholders so that NimbusMigrations doesn't get mad.
|
||||
const migrationsStub = Object.assign(
|
||||
Object.fromEntries(
|
||||
Object.values(NimbusMigrations.Phase).map(phase => [phase, []])
|
||||
),
|
||||
migrations
|
||||
);
|
||||
|
||||
sandbox.stub(NimbusMigrations, "MIGRATIONS").get(() => migrationsStub);
|
||||
sandbox.stub(NimbusMigrations, "MIGRATIONS").get(() => migrations);
|
||||
}
|
||||
|
||||
if (init) {
|
||||
@@ -124,7 +111,13 @@ async function setupTest({
|
||||
ctx.initExperimentAPI = initExperimentAPI;
|
||||
}
|
||||
|
||||
return ctx;
|
||||
return {
|
||||
...ctx,
|
||||
async cleanup() {
|
||||
await baseCleanup();
|
||||
Services.prefs.deleteBranch("nimbus.migrations");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function makeMigrations(phase, count) {
|
||||
@@ -928,604 +921,3 @@ add_task(async function test_migration_firefoxLabsEnrollments_idempotent() {
|
||||
Services.prefs.clearUserPref(pref);
|
||||
}
|
||||
});
|
||||
|
||||
const IMPORT_TO_SQL_MIGRATION =
|
||||
NimbusMigrations.MIGRATIONS[
|
||||
NimbusMigrations.Phase.AFTER_STORE_INITIALIZED
|
||||
][0];
|
||||
|
||||
add_task(async function testMigrateEnrollmentsToSql() {
|
||||
const PREFFLIPS_EXPERIMENT_VALUE = {
|
||||
prefs: {
|
||||
"foo.bar.baz": {
|
||||
branch: "default",
|
||||
value: "prefFlips-experiment-value",
|
||||
},
|
||||
},
|
||||
};
|
||||
let storePath;
|
||||
|
||||
const experiments = [
|
||||
NimbusTestUtils.factories.recipe.withFeatureConfig(
|
||||
"experiment-1",
|
||||
{
|
||||
branchSlug: "experiment-1",
|
||||
featureId: "no-feature-firefox-desktop",
|
||||
},
|
||||
{
|
||||
bogus: "foobar",
|
||||
}
|
||||
),
|
||||
NimbusTestUtils.factories.recipe.withFeatureConfig("experiment-2", {
|
||||
branchSlug: "experiment-2",
|
||||
featureId: "no-feature-firefox-desktop",
|
||||
}),
|
||||
NimbusTestUtils.factories.recipe.withFeatureConfig(
|
||||
"rollout-1",
|
||||
{
|
||||
featureId: "no-feature-firefox-desktop",
|
||||
},
|
||||
{
|
||||
isRollout: true,
|
||||
isFirefoxLabsOptIn: true,
|
||||
firefoxLabsTitle: "title",
|
||||
firefoxLabsDescription: "description",
|
||||
firefoxLabsDescriptionLinks: {
|
||||
foo: "https://example.com",
|
||||
},
|
||||
firefoxLabsGroup: "group",
|
||||
requiresRestart: true,
|
||||
}
|
||||
),
|
||||
NimbusTestUtils.factories.recipe.withFeatureConfig(
|
||||
"rollout-2",
|
||||
{
|
||||
featureId: "no-feature-firefox-desktop",
|
||||
},
|
||||
{ isRollout: true }
|
||||
),
|
||||
NimbusTestUtils.factories.recipe.withFeatureConfig("setPref-experiment", {
|
||||
featureId: "nimbus-qa-1",
|
||||
value: {
|
||||
value: "qa-1",
|
||||
},
|
||||
}),
|
||||
NimbusTestUtils.factories.recipe.withFeatureConfig(
|
||||
"setPref-rollout",
|
||||
{
|
||||
featureId: "nimbus-qa-2",
|
||||
value: {
|
||||
value: "qa-2",
|
||||
},
|
||||
},
|
||||
{ isRollout: true }
|
||||
),
|
||||
];
|
||||
const secureExperiments = [
|
||||
NimbusTestUtils.factories.recipe.withFeatureConfig("prefFlips-experiment", {
|
||||
featureId: "prefFlips",
|
||||
value: PREFFLIPS_EXPERIMENT_VALUE,
|
||||
}),
|
||||
NimbusTestUtils.factories.recipe.withFeatureConfig(
|
||||
"prefFlips-rollout",
|
||||
{ featureId: "prefFlips", value: { prefs: {} } },
|
||||
{ isRollout: true }
|
||||
),
|
||||
];
|
||||
|
||||
{
|
||||
const store = NimbusTestUtils.stubs.store();
|
||||
|
||||
await store.init();
|
||||
|
||||
store.addEnrollment(
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig(
|
||||
"inactive-1",
|
||||
{ featureId: "no-feature-firefox-desktop" },
|
||||
{
|
||||
active: false,
|
||||
unenrollReason: "reason-1",
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
}
|
||||
)
|
||||
);
|
||||
store.addEnrollment(
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig(
|
||||
"inactive-2",
|
||||
{ branchSlug: "treatment-a", featureId: "no-feature-firefox-desktop" },
|
||||
{
|
||||
active: false,
|
||||
unenrollReason: "reason-2",
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
}
|
||||
)
|
||||
);
|
||||
store.addEnrollment(
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig(
|
||||
"expired-but-active",
|
||||
{ featureId: "no-feature-firefox-desktop" },
|
||||
{ source: NimbusTelemetry.EnrollmentSource.RS_LOADER }
|
||||
)
|
||||
);
|
||||
|
||||
store.addEnrollment(
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig(
|
||||
"experiment-1",
|
||||
{
|
||||
branchSlug: "experiment-1",
|
||||
featureId: "no-feature-firefox-desktop",
|
||||
},
|
||||
{ source: NimbusTelemetry.EnrollmentSource.RS_LOADER }
|
||||
)
|
||||
);
|
||||
store.addEnrollment(
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig(
|
||||
"rollout-1",
|
||||
{ featureId: "no-feature-firefox-desktop" },
|
||||
{
|
||||
isRollout: true,
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
isFirefoxLabsOptIn: true,
|
||||
firefoxLabsTitle: "title",
|
||||
firefoxLabsDescription: "description",
|
||||
firefoxLabsDescriptionLinks: {
|
||||
foo: "https://example.com",
|
||||
},
|
||||
firefoxLabsGroup: "group",
|
||||
requiresRestart: true,
|
||||
}
|
||||
)
|
||||
);
|
||||
store.addEnrollment(
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig(
|
||||
"prefFlips-experiment",
|
||||
{
|
||||
featureId: "prefFlips",
|
||||
value: PREFFLIPS_EXPERIMENT_VALUE,
|
||||
},
|
||||
{
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
prefFlips: {
|
||||
originalValues: {
|
||||
"foo.bar.baz": "original-value",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
store.addEnrollment(
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig(
|
||||
"setPref-experiment",
|
||||
{
|
||||
featureId: "nimbus-qa-1",
|
||||
value: { value: "qa-1" },
|
||||
},
|
||||
{
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
prefs: [
|
||||
{
|
||||
name: "nimbus.qa.pref-1",
|
||||
branch: "default",
|
||||
featureId: "nimbus-qa-1",
|
||||
variable: "value",
|
||||
originalValue: "original-value",
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
);
|
||||
store.addEnrollment(
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig(
|
||||
"devtools",
|
||||
{
|
||||
branchSlug: "devtools",
|
||||
featureId: "no-feature-firefox-desktop",
|
||||
},
|
||||
{ source: "nimbus-devtools" }
|
||||
)
|
||||
);
|
||||
store.addEnrollment(
|
||||
NimbusTestUtils.factories.experiment.withFeatureConfig(
|
||||
"optin",
|
||||
{
|
||||
branchSlug: "force-enroll",
|
||||
featureId: "no-feature-firefox-desktop",
|
||||
},
|
||||
{
|
||||
source: NimbusTelemetry.EnrollmentSource.FORCE_ENROLLMENT,
|
||||
localizations: {
|
||||
"en-US": {
|
||||
foo: "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
storePath = await NimbusTestUtils.saveStore(store);
|
||||
}
|
||||
|
||||
let importMigrationError = null;
|
||||
|
||||
// We need to run our test *directly after* the migration completes, before
|
||||
// the rest of Nimbus has had a chance to initialize, so we know that any
|
||||
// changes to the database were from the migration and not, e.g.,
|
||||
// updateRecipes().
|
||||
async function testImportMigration() {
|
||||
try {
|
||||
const conn = await ProfilesDatastoreService.getConnection();
|
||||
|
||||
const result = 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
|
||||
`);
|
||||
|
||||
const dbEnrollments = Object.fromEntries(
|
||||
result.map(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.slug, processed];
|
||||
})
|
||||
);
|
||||
|
||||
Assert.deepEqual(
|
||||
Object.keys(dbEnrollments).sort(),
|
||||
[
|
||||
"inactive-1",
|
||||
"inactive-2",
|
||||
"expired-but-active",
|
||||
"experiment-1",
|
||||
"rollout-1",
|
||||
"setPref-experiment",
|
||||
"prefFlips-experiment",
|
||||
"devtools",
|
||||
"optin",
|
||||
].sort(),
|
||||
"Should have rows for the expected enrollments"
|
||||
);
|
||||
|
||||
// The profileId is the same for every enrollment.
|
||||
const profileId = ExperimentAPI.profileId;
|
||||
|
||||
function assertEnrollment(expected) {
|
||||
const enrollment = dbEnrollments[expected.slug];
|
||||
const { slug } = expected;
|
||||
|
||||
function msg(s) {
|
||||
return `${slug}: ${s}`;
|
||||
}
|
||||
|
||||
Assert.equal(enrollment.slug, slug, "slug matches");
|
||||
Assert.equal(enrollment.profileId, profileId, msg("profileId"));
|
||||
Assert.equal(enrollment.active, expected.active, msg("active"));
|
||||
Assert.equal(
|
||||
enrollment.unenrollReason,
|
||||
expected.unenrollReason,
|
||||
msg("unenrollReason")
|
||||
);
|
||||
Assert.deepEqual(
|
||||
enrollment.setPrefs,
|
||||
expected.setPrefs,
|
||||
msg("setPrefs")
|
||||
);
|
||||
Assert.deepEqual(
|
||||
enrollment.prefFlips,
|
||||
expected.prefFlips,
|
||||
msg("prefFlips")
|
||||
);
|
||||
Assert.equal(enrollment.source, expected.source, msg("source"));
|
||||
|
||||
Assert.ok(
|
||||
typeof enrollment.lastSeen === "string",
|
||||
msg("lastSeen serialized as string")
|
||||
);
|
||||
|
||||
Assert.ok(
|
||||
typeof enrollment.recipe === "object" && enrollment.recipe !== null,
|
||||
msg("recipe is object")
|
||||
);
|
||||
|
||||
const requiredRecipeFields = [
|
||||
"slug",
|
||||
"userFacingName",
|
||||
"userFacingDescription",
|
||||
"featureIds",
|
||||
"isRollout",
|
||||
"localizations",
|
||||
"isFirefoxLabsOptIn",
|
||||
"firefoxLabsDescription",
|
||||
"firefoxLabsDescriptionLinks",
|
||||
"firefoxLabsGroup",
|
||||
"requiresRestart",
|
||||
"branches",
|
||||
];
|
||||
|
||||
for (const recipeField of requiredRecipeFields) {
|
||||
Assert.ok(
|
||||
Object.hasOwn(enrollment.recipe, recipeField),
|
||||
msg(`recipe has ${recipeField} field`)
|
||||
);
|
||||
}
|
||||
|
||||
const storeEnrollment = ExperimentAPI.manager.store.get(slug);
|
||||
|
||||
Assert.equal(enrollment.recipe.slug, slug, msg("recipe.slug"));
|
||||
Assert.equal(
|
||||
enrollment.recipe.isRollout,
|
||||
storeEnrollment.isRollout,
|
||||
msg("recipe.isRollout")
|
||||
);
|
||||
Assert.ok(
|
||||
enrollment.recipe.branches.find(
|
||||
b => b.slug === enrollment.branchSlug
|
||||
),
|
||||
msg("recipe has branch matching branchSlug")
|
||||
);
|
||||
|
||||
for (const [i, branch] of enrollment.recipe.branches.entries()) {
|
||||
Assert.ok(
|
||||
typeof branch.ratio === "number",
|
||||
msg(`recipe.branches[${i}].ratio is a number`)
|
||||
);
|
||||
|
||||
Assert.ok(
|
||||
typeof branch.features === "object" && branch.features !== null,
|
||||
msg(`recipe.branches[${i}].features is an object`)
|
||||
);
|
||||
|
||||
for (const featureId of enrollment.recipe.featureIds) {
|
||||
const idx = branch.features.findIndex(
|
||||
fc => fc.featureId === featureId
|
||||
);
|
||||
Assert.ok(
|
||||
idx !== -1,
|
||||
msg(
|
||||
`recipe.branches[${i}].features[${idx}].featureId = ${featureId}`
|
||||
)
|
||||
);
|
||||
|
||||
const featureConfig = branch.features[idx];
|
||||
Assert.ok(
|
||||
typeof featureConfig.value === "object" &&
|
||||
featureConfig.value !== null,
|
||||
msg(`recipe.branches[${i}].features[${idx}].value is an object`)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assertEnrollment({
|
||||
slug: "inactive-1",
|
||||
branchSlug: "control",
|
||||
active: false,
|
||||
unenrollReason: "reason-1",
|
||||
setPrefs: null,
|
||||
prefFlips: null,
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
});
|
||||
|
||||
assertEnrollment({
|
||||
slug: "inactive-2",
|
||||
branchSlug: "treatment-a",
|
||||
active: false,
|
||||
unenrollReason: "reason-2",
|
||||
setPrefs: null,
|
||||
prefFlips: null,
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
});
|
||||
|
||||
assertEnrollment({
|
||||
slug: "expired-but-active",
|
||||
branchSlug: "control",
|
||||
active: true,
|
||||
unenrollReason: null,
|
||||
setPrefs: null,
|
||||
prefFlips: null,
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
});
|
||||
|
||||
assertEnrollment({
|
||||
slug: "experiment-1",
|
||||
branchSlug: "experiment-1",
|
||||
active: true,
|
||||
unenrollReason: null,
|
||||
setPrefs: null,
|
||||
prefFlips: null,
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
});
|
||||
|
||||
Assert.equal(
|
||||
dbEnrollments["experiment-1"].recipe.bogus,
|
||||
"foobar",
|
||||
`experiment-1: entire recipe from Remote Settings is captured`
|
||||
);
|
||||
|
||||
assertEnrollment({
|
||||
slug: "rollout-1",
|
||||
branchSlug: "control",
|
||||
active: true,
|
||||
unenrollReason: null,
|
||||
setPrefs: null,
|
||||
prefFlips: null,
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
});
|
||||
|
||||
Assert.ok(
|
||||
dbEnrollments["rollout-1"].recipe.isFirefoxLabsOptIn,
|
||||
`rollout-1: recipe.isFirefoxLabsOptIn`
|
||||
);
|
||||
Assert.equal(
|
||||
dbEnrollments["rollout-1"].recipe.firefoxLabsTitle,
|
||||
"title",
|
||||
`rollout-1: recipe.firefoxLabsTitle`
|
||||
);
|
||||
Assert.equal(
|
||||
dbEnrollments["rollout-1"].recipe.firefoxLabsDescription,
|
||||
"description",
|
||||
`rollout-1: recipe.firefoxLabsDescription`
|
||||
);
|
||||
Assert.deepEqual(
|
||||
dbEnrollments["rollout-1"].recipe.firefoxLabsDescriptionLinks,
|
||||
{
|
||||
foo: "https://example.com",
|
||||
},
|
||||
`rollout-1: recipe.firefoxLabsDescriptionLinks`
|
||||
);
|
||||
Assert.equal(
|
||||
dbEnrollments["rollout-1"].recipe.firefoxLabsGroup,
|
||||
"group",
|
||||
`rollout-1: recipe.firefoxLabsGroup`
|
||||
);
|
||||
Assert.ok(
|
||||
dbEnrollments["rollout-1"].recipe.requiresRestart,
|
||||
`rollout-1: recipe.requiresRestart`
|
||||
);
|
||||
|
||||
assertEnrollment({
|
||||
slug: "prefFlips-experiment",
|
||||
branchSlug: "control",
|
||||
active: true,
|
||||
unenrollReason: null,
|
||||
setPrefs: null,
|
||||
prefFlips: {
|
||||
originalValues: {
|
||||
"foo.bar.baz": "original-value",
|
||||
},
|
||||
},
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
});
|
||||
|
||||
assertEnrollment({
|
||||
slug: "setPref-experiment",
|
||||
branchSlug: "control",
|
||||
active: true,
|
||||
unenrollReason: null,
|
||||
setPrefs: [
|
||||
{
|
||||
name: "nimbus.qa.pref-1",
|
||||
branch: "default",
|
||||
featureId: "nimbus-qa-1",
|
||||
variable: "value",
|
||||
originalValue: "original-value",
|
||||
},
|
||||
],
|
||||
prefFlips: null,
|
||||
source: NimbusTelemetry.EnrollmentSource.RS_LOADER,
|
||||
});
|
||||
|
||||
assertEnrollment({
|
||||
slug: "devtools",
|
||||
branchSlug: "devtools",
|
||||
active: true,
|
||||
unenrollReason: null,
|
||||
setPrefs: null,
|
||||
prefFlips: null,
|
||||
source: "nimbus-devtools",
|
||||
});
|
||||
|
||||
assertEnrollment({
|
||||
slug: "optin",
|
||||
branchSlug: "force-enroll",
|
||||
active: true,
|
||||
unenrollReason: null,
|
||||
setPrefs: null,
|
||||
prefFlips: null,
|
||||
source: NimbusTelemetry.EnrollmentSource.FORCE_ENROLLMENT,
|
||||
});
|
||||
|
||||
Assert.deepEqual(
|
||||
dbEnrollments.optin.recipe.localizations,
|
||||
{
|
||||
"en-US": {
|
||||
foo: "foo",
|
||||
},
|
||||
},
|
||||
"optin: localizations is captured in recipe"
|
||||
);
|
||||
} catch (e) {
|
||||
importMigrationError = e;
|
||||
}
|
||||
}
|
||||
|
||||
const { cleanup } = await setupTest({
|
||||
storePath,
|
||||
experiments,
|
||||
secureExperiments,
|
||||
migrations: {
|
||||
[NimbusMigrations.Phase.AFTER_STORE_INITIALIZED]: [
|
||||
IMPORT_TO_SQL_MIGRATION,
|
||||
{ name: "test-import-migration", fn: testImportMigration },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// NimbusMigrations swallows errors
|
||||
if (importMigrationError) {
|
||||
throw importMigrationError;
|
||||
}
|
||||
|
||||
await NimbusTestUtils.cleanupManager([
|
||||
"experiment-1",
|
||||
"experiment-2",
|
||||
"rollout-1",
|
||||
"rollout-2",
|
||||
"prefFlips-experiment",
|
||||
"prefFlips-rollout",
|
||||
"setPref-experiment",
|
||||
"setPref-rollout",
|
||||
"devtools",
|
||||
"optin",
|
||||
]);
|
||||
|
||||
await cleanup();
|
||||
|
||||
// On unenroll, we should 'reset' foo.bar.baz, nimbus.qa.pref-1, and nimbus.qa.pref-2.
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref("foo.bar.baz"),
|
||||
"original-value",
|
||||
"foo.bar.baz restored"
|
||||
);
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref("nimbus.qa.pref-1"),
|
||||
"original-value",
|
||||
"nimbus.qs.pref-1 restored"
|
||||
);
|
||||
Assert.ok(
|
||||
!Services.prefs.prefHasUserValue("nimbus.qa.pref-2"),
|
||||
"nimbus.qa.pref-2 restored"
|
||||
);
|
||||
Services.prefs.deleteBranch("foo.bar.baz");
|
||||
Services.prefs.deleteBranch("nimbus.qa.pref-1");
|
||||
Services.prefs.deleteBranch("nimbus.qa.pref-2");
|
||||
});
|
||||
|
||||
@@ -204,14 +204,13 @@ add_task(async function test_checkExperimentSelfReference() {
|
||||
add_task(async function test_optIn_debug_disabled() {
|
||||
info("Testing users cannot opt-in when nimbus.debug is false");
|
||||
|
||||
const recipe = NimbusTestUtils.factories.recipe("foo", {
|
||||
targeting: "false",
|
||||
});
|
||||
const { loader, initExperimentAPI, cleanup } =
|
||||
const recipe = NimbusTestUtils.factories.recipe("foo");
|
||||
const { sandbox, loader, initExperimentAPI, cleanup } =
|
||||
await NimbusTestUtils.setupTest({
|
||||
init: false,
|
||||
experiments: [recipe],
|
||||
});
|
||||
sandbox.stub(loader, "updateRecipes").resolves();
|
||||
|
||||
await initExperimentAPI();
|
||||
|
||||
@@ -239,12 +238,12 @@ add_task(async function test_optIn_studies_disabled() {
|
||||
"Testing users cannot opt-in when telemetry is disabled or studies are disabled."
|
||||
);
|
||||
|
||||
const recipe = NimbusTestUtils.factories.recipe("foo", {
|
||||
targeting: "false",
|
||||
});
|
||||
const { loader, initExperimentAPI, cleanup } =
|
||||
const recipe = NimbusTestUtils.factories.recipe("foo");
|
||||
const { sandbox, loader, initExperimentAPI, cleanup } =
|
||||
await NimbusTestUtils.setupTest({ init: false, experiments: [recipe] });
|
||||
|
||||
sandbox.stub(loader, "updateRecipes").resolves();
|
||||
|
||||
await initExperimentAPI();
|
||||
|
||||
Services.prefs.setBoolPref(DEBUG_PREF, true);
|
||||
|
||||
@@ -12,6 +12,9 @@ const { PanelTestProvider } = ChromeUtils.importESModule(
|
||||
const { TelemetryEnvironment } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/TelemetryEnvironment.sys.mjs"
|
||||
);
|
||||
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
||||
);
|
||||
const { UnenrollmentCause } = ChromeUtils.importESModule(
|
||||
"resource://nimbus/lib/ExperimentManager.sys.mjs"
|
||||
);
|
||||
@@ -954,7 +957,7 @@ add_task(async function test_updateRecipes_rollout_bucketing() {
|
||||
}
|
||||
);
|
||||
|
||||
await manager.unenroll(experiment.slug);
|
||||
manager.unenroll(experiment.slug);
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
@@ -1005,7 +1008,7 @@ add_task(async function test_reenroll_rollout_resized() {
|
||||
"New enrollment should not have unenroll reason"
|
||||
);
|
||||
|
||||
await manager.unenroll(rollout.slug);
|
||||
manager.unenroll(rollout.slug);
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
@@ -1022,7 +1025,7 @@ add_task(async function test_experiment_reenroll() {
|
||||
"Should enroll in experiment"
|
||||
);
|
||||
|
||||
await manager.unenroll(experiment.slug);
|
||||
manager.unenroll(experiment.slug);
|
||||
Assert.ok(
|
||||
!manager.store.getExperimentForFeature("testFeature"),
|
||||
"Should unenroll from experiment"
|
||||
@@ -1159,8 +1162,8 @@ add_task(async function test_active_and_past_experiment_targeting() {
|
||||
["experiment-a", "experiment-b", "rollout-a", "rollout-b"]
|
||||
);
|
||||
|
||||
await manager.unenroll("experiment-c");
|
||||
await manager.unenroll("rollout-c");
|
||||
manager.unenroll("experiment-c");
|
||||
manager.unenroll("rollout-c");
|
||||
|
||||
cleanupFeatures();
|
||||
await cleanup();
|
||||
@@ -1326,12 +1329,14 @@ add_task(async function test_enrollment_targeting() {
|
||||
[]
|
||||
);
|
||||
|
||||
await NimbusTestUtils.cleanupManager([
|
||||
for (const slug of [
|
||||
"experiment-b",
|
||||
"experiment-c",
|
||||
"rollout-b",
|
||||
"rollout-c",
|
||||
]);
|
||||
]) {
|
||||
manager.unenroll(slug);
|
||||
}
|
||||
|
||||
cleanupFeatures();
|
||||
await cleanup();
|
||||
@@ -1420,7 +1425,7 @@ add_task(
|
||||
const isReadyEvents = Glean.nimbusEvents.isReady.testGetValue("events");
|
||||
|
||||
Assert.equal(isReadyEvents.length, 3);
|
||||
await manager.unenroll(recipe.slug);
|
||||
manager.unenroll(recipe.slug);
|
||||
|
||||
await cleanup();
|
||||
}
|
||||
@@ -1589,7 +1594,7 @@ add_task(async function test_updateRecipes_optInsStayEnrolled() {
|
||||
await loader.updateRecipes();
|
||||
Assert.ok(manager.store.get("opt-in")?.active, "Opt-in stayed enrolled");
|
||||
|
||||
await manager.unenroll("opt-in");
|
||||
manager.unenroll("opt-in");
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
@@ -1909,8 +1914,8 @@ add_task(async function test_updateRecipes_enrollmentStatus_telemetry() {
|
||||
},
|
||||
]);
|
||||
|
||||
await manager.unenroll("stays-enrolled");
|
||||
await manager.unenroll("enrolls");
|
||||
manager.unenroll("stays-enrolled");
|
||||
manager.unenroll("enrolls");
|
||||
|
||||
cleanupFeatures();
|
||||
await cleanup();
|
||||
@@ -2022,8 +2027,8 @@ add_task(async function test_updateRecipes_enrollmentStatus_notEnrolled() {
|
||||
]
|
||||
);
|
||||
|
||||
await manager.unenroll("enrolled-experiment");
|
||||
await manager.unenroll("enrolled-rollout");
|
||||
manager.unenroll("enrolled-experiment");
|
||||
manager.unenroll("enrolled-rollout");
|
||||
|
||||
cleanupFeatures();
|
||||
await cleanup();
|
||||
@@ -2213,8 +2218,8 @@ add_task(async function testUnenrollsFirst() {
|
||||
await loader.updateRecipes();
|
||||
assertEnrollments(manager.store, ["e3", "r3"], ["e1", "e2", "r1", "r2"]);
|
||||
|
||||
await manager.unenroll("e3");
|
||||
await manager.unenroll("r3");
|
||||
manager.unenroll("e3");
|
||||
manager.unenroll("r3");
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
const { MatchStatus } = ChromeUtils.importESModule(
|
||||
"resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs"
|
||||
);
|
||||
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
const LOCALIZATIONS = {
|
||||
"en-US": {
|
||||
@@ -210,7 +213,7 @@ add_task(async function test_getLocalizedValue() {
|
||||
"_getLocalizedValue() with a nested localization"
|
||||
);
|
||||
|
||||
await doExperimentCleanup();
|
||||
doExperimentCleanup();
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
@@ -244,8 +247,6 @@ add_task(async function test_getLocalizedValue_unenroll_missingEntry() {
|
||||
"_getLocalizedValue() with a bogus localization"
|
||||
);
|
||||
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(enrollment.slug);
|
||||
|
||||
Assert.equal(
|
||||
manager.store.getExperimentForFeature(FEATURE_ID),
|
||||
null,
|
||||
@@ -315,8 +316,6 @@ add_task(async function test_getLocalizedValue_unenroll_missingEntry() {
|
||||
"_getLocalizedValue() with a bogus localization"
|
||||
);
|
||||
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(enrollment.slug);
|
||||
|
||||
Assert.equal(
|
||||
manager.store.getExperimentForFeature(FEATURE_ID),
|
||||
null,
|
||||
@@ -394,7 +393,7 @@ add_task(async function test_getVariables() {
|
||||
"getVariable() returns substitutions inside arrays"
|
||||
);
|
||||
|
||||
await doExperimentCleanup();
|
||||
doExperimentCleanup();
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
@@ -646,9 +645,6 @@ add_task(async function test_getVariables_fallback_unenroll() {
|
||||
waldo: ["fallback-waldo-pref-value"],
|
||||
});
|
||||
|
||||
await NimbusTestUtils.waitForInactiveEnrollment("experiment");
|
||||
await NimbusTestUtils.waitForInactiveEnrollment("rollout");
|
||||
|
||||
Assert.equal(
|
||||
manager.store.getExperimentForFeature(FEATURE_ID),
|
||||
null,
|
||||
|
||||
@@ -7,9 +7,8 @@ const { PrefUtils } = ChromeUtils.importESModule(
|
||||
const { JsonSchema } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/JsonSchema.sys.mjs"
|
||||
);
|
||||
|
||||
const { ProfilesDatastoreService } = ChromeUtils.importESModule(
|
||||
"moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs"
|
||||
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
const USER = "user";
|
||||
@@ -146,7 +145,6 @@ async function setupTest({ ...args } = {}) {
|
||||
...ctx,
|
||||
async cleanup() {
|
||||
assertNoObservers(ctx.manager);
|
||||
await NimbusTestUtils.waitForAllUnenrollments();
|
||||
await baseCleanup();
|
||||
},
|
||||
};
|
||||
@@ -1608,8 +1606,6 @@ add_task(async function test_prefFlips_unenrollment() {
|
||||
Assert.ok(enrollment.active, `It should still be active`);
|
||||
}
|
||||
|
||||
await NimbusTestUtils.waitForActiveEnrollments(expectedEnrollments);
|
||||
|
||||
info("Checking expected unenrollments...");
|
||||
for (const slug of expectedUnenrollments) {
|
||||
const enrollment = manager.store.get(slug);
|
||||
@@ -1618,13 +1614,6 @@ add_task(async function test_prefFlips_unenrollment() {
|
||||
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) {
|
||||
info("Unenrolling from specific experiments before checking prefs...");
|
||||
for (const slug of unenrollmentOrder ?? []) {
|
||||
@@ -1632,13 +1621,6 @@ add_task(async function test_prefFlips_unenrollment() {
|
||||
}
|
||||
}
|
||||
|
||||
expectedCurrentEnrollments = expectedCurrentEnrollments.difference(
|
||||
new Set(unenrollmentOrder)
|
||||
);
|
||||
await NimbusTestUtils.waitForActiveEnrollments(
|
||||
Array.from(expectedCurrentEnrollments)
|
||||
);
|
||||
|
||||
if (expectedPrefs) {
|
||||
info("Checking expected prefs...");
|
||||
checkExpectedPrefs(expectedPrefs);
|
||||
@@ -1652,8 +1634,6 @@ add_task(async function test_prefFlips_unenrollment() {
|
||||
}
|
||||
}
|
||||
|
||||
await NimbusTestUtils.waitForActiveEnrollments([]);
|
||||
|
||||
info("Cleaning up prefs...");
|
||||
Services.prefs.deleteBranch(PREF_FOO);
|
||||
Services.prefs.deleteBranch(PREF_BAR);
|
||||
@@ -2293,15 +2273,6 @@ add_task(async function test_prefFlips_failed() {
|
||||
const { manager, cleanup } = await setupTest();
|
||||
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);
|
||||
Assert.ok(!enrollment.active, "Experiment should not be active");
|
||||
|
||||
@@ -2371,15 +2342,6 @@ add_task(async function test_prefFlips_failed_multiple_prefs() {
|
||||
|
||||
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);
|
||||
Assert.ok(!enrollment.active, "Experiment should not be active");
|
||||
|
||||
@@ -2508,11 +2470,8 @@ add_task(async function test_prefFlips_failed_experiment_and_rollout_1() {
|
||||
Assert.ok(enrollment.active, `The enrollment for ${slug} is active`);
|
||||
}
|
||||
|
||||
await NimbusTestUtils.waitForActiveEnrollments(expectedEnrollments);
|
||||
|
||||
info("Checking expected unenrollments...");
|
||||
for (const slug of expectedUnenrollments) {
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(slug);
|
||||
const enrollment = manager.store.get(slug);
|
||||
Assert.ok(!enrollment.active, "The enrollment is no longer active.");
|
||||
}
|
||||
@@ -2618,8 +2577,6 @@ add_task(async function test_prefFlips_failed_experiment_and_rollout_2() {
|
||||
);
|
||||
}
|
||||
|
||||
await NimbusTestUtils.waitForActiveEnrollments(expectedEnrollments);
|
||||
|
||||
info("Checking expected enrollments...");
|
||||
for (const slug of expectedEnrollments) {
|
||||
const enrollment = manager.store.get(slug);
|
||||
@@ -2628,7 +2585,6 @@ add_task(async function test_prefFlips_failed_experiment_and_rollout_2() {
|
||||
|
||||
info("Checking expected unenrollments...");
|
||||
for (const slug of expectedUnenrollments) {
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(slug);
|
||||
const enrollment = manager.store.get(slug);
|
||||
Assert.ok(!enrollment.active, "The enrollment is no longer active.");
|
||||
}
|
||||
@@ -2687,8 +2643,6 @@ add_task(async function test_prefFlips_update_failure() {
|
||||
{ manager, slug: "experiment" }
|
||||
);
|
||||
|
||||
await NimbusTestUtils.waitForActiveEnrollments(["rollout"]);
|
||||
|
||||
const rolloutEnrollment = manager.store.get("rollout");
|
||||
const experimentEnrollment = manager.store.get("experiment");
|
||||
|
||||
@@ -2699,7 +2653,7 @@ add_task(async function test_prefFlips_update_failure() {
|
||||
Assert.equal(Services.prefs.getStringPref("pref.one"), "one");
|
||||
Assert.equal(Services.prefs.getStringPref("pref.two"), "two");
|
||||
|
||||
await cleanupExperiment();
|
||||
cleanupExperiment();
|
||||
|
||||
Services.prefs.deleteBranch("pref.one");
|
||||
Services.prefs.deleteBranch("pref.two");
|
||||
@@ -2939,9 +2893,6 @@ add_task(async function test_prefFlips_restore_failure_conflict() {
|
||||
|
||||
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-2").active, "rollout-2 is not active");
|
||||
Assert.equal(
|
||||
@@ -3026,12 +2977,7 @@ add_task(async function test_prefFlips_restore_failure_wrong_type() {
|
||||
|
||||
Services.prefs.setIntPref(PREF_1, 123);
|
||||
|
||||
const { manager, cleanup } = await setupTest({
|
||||
storePath,
|
||||
secureExperiments: [recipe],
|
||||
});
|
||||
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(recipe.slug);
|
||||
const { manager, cleanup } = await setupTest({ storePath });
|
||||
|
||||
const enrollment = manager.store.get(recipe.slug);
|
||||
|
||||
@@ -3080,7 +3026,6 @@ add_task(
|
||||
PrefUtils.setPref(PREF, "default-value", { branch: DEFAULT });
|
||||
|
||||
await manager.enroll(recipe, "rs-loader");
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(recipe.slug);
|
||||
|
||||
let enrollment = manager.store.get(recipe.slug);
|
||||
|
||||
@@ -3088,8 +3033,6 @@ add_task(
|
||||
Assert.equal(enrollment.unenrollReason, "prefFlips-failed");
|
||||
|
||||
await manager.enroll(recipe, "rs-loader", { reenroll: true });
|
||||
await NimbusTestUtils.waitForInactiveEnrollment(recipe.slug);
|
||||
|
||||
enrollment = manager.store.get(recipe.slug);
|
||||
|
||||
Assert.ok(!enrollment.active, "enrollment should not be active");
|
||||
@@ -3100,59 +3043,3 @@ add_task(
|
||||
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]
|
||||
head = "../../../../../toolkit/profile/test/xpcshell/head.js ../../../../../browser/components/profiles/tests/unit/head.js head.js"
|
||||
head = "head.js"
|
||||
tags = "nimbus"
|
||||
firefox-appdir = "browser"
|
||||
support-files = ["reference_aboutwelcome_experiment_content.json"]
|
||||
|
||||
Reference in New Issue
Block a user