Backed out 2 changesets (bug 1594035) for Browser-chrome failiures on normandy/test/browser/browser_Normandy.js. CLOSED TREE

Backed out changeset 066674b8313d (bug 1594035)
Backed out changeset 6f93019be0d6 (bug 1594035)

--HG--
extra : rebase_source : 7815196d6ce40c374d2ea1d0438230f0bc7201bc
This commit is contained in:
Dorel Luca 2019-11-22 05:14:24 +02:00
Родитель 98edc028b6
Коммит f22af0f64c
8 изменённых файлов: 220 добавлений и 455 удалений

Просмотреть файл

@ -20,7 +20,6 @@ XPCOMUtils.defineLazyModuleGetters(this, {
RecipeRunner: "resource://normandy/lib/RecipeRunner.jsm",
ShieldPreferences: "resource://normandy/lib/ShieldPreferences.jsm",
TelemetryEvents: "resource://normandy/lib/TelemetryEvents.jsm",
PromiseUtils: "resource://gre/modules/PromiseUtils.jsm",
});
var EXPORTED_SYMBOLS = ["Normandy"];
@ -44,13 +43,6 @@ var Normandy = {
rolloutPrefsChanged: {},
async init({ runAsync = true } = {}) {
this.uiAvailableDeferred = PromiseUtils.defer();
if (runAsync) {
Services.obs.addObserver(this, UI_AVAILABLE_NOTIFICATION);
} else {
this.uiAvailableDeferred.resolve();
}
// Initialization that needs to happen before the first paint on startup.
await NormandyMigrations.applyAll();
this.rolloutPrefsChanged = this.applyStartupPrefs(
@ -60,24 +52,26 @@ var Normandy = {
STARTUP_EXPERIMENT_PREFS_BRANCH
);
// Once the UI has loaded (and so we are no longer at risk of delaying it),
// call finishInit.
this.uiAvailableDeferred.promise.then(() => this.finishInit());
if (runAsync) {
Services.obs.addObserver(this, UI_AVAILABLE_NOTIFICATION);
} else {
// Remove any observers, if present.
try {
Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
} catch (e) {}
await this.finishInit();
}
},
observe(subject, topic, data) {
if (topic === UI_AVAILABLE_NOTIFICATION) {
Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
this.uiAvailableDeferred.resolve();
this.finishInit();
}
},
async finishInit() {
// Remove any observers, if present.
try {
Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
} catch (e) {}
try {
TelemetryEvents.init();
} catch (err) {

Просмотреть файл

@ -21,16 +21,6 @@ ChromeUtils.defineModuleGetter(
"RecipeRunner",
"resource://normandy/lib/RecipeRunner.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"AddonRollouts",
"resource://normandy/lib/AddonRollouts.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"PreferenceRollouts",
"resource://normandy/lib/PreferenceRollouts.jsm"
);
var EXPORTED_SYMBOLS = ["NormandyMigrations"];
@ -62,29 +52,20 @@ const NormandyMigrations = {
},
async applyOne(id) {
const migration = this.migrations[id]();
log.debug(`Running Normandy migration #${id} - ${migration.name}`);
const migration = this.migrations[id];
log.debug(`Running Normandy migration ${migration.name}`);
await migration();
},
migrations: [
// Each of these are functions that return the migration function. This way
// the lazy importers won't be triggered until we actually need to run the
// migration. This saves startup time in the common case that all migrations
// are already run.
() => migrateShieldPrefs,
() => migrateStudiesEnabledWithoutHealthReporting,
() =>
AddonStudies.migrations.migration01StudyFieldsToSlugAndUserFacingFields,
() => PreferenceExperiments.migrations.migration01MoveExperiments,
() => PreferenceExperiments.migrations.migration02MultiPreference,
() => PreferenceExperiments.migrations.migration03AddActionName,
() => PreferenceExperiments.migrations.migration04RenameNameToSlug,
() => RecipeRunner.migrations.migration01RemoveOldRecipesCollection,
() => PreferenceExperiments.migrations.migration05AddFillerEnrollmentId,
() => AddonStudies.migrations.migration02AddFillerEnrollmentId,
() => AddonRollouts.migrations.migration01AddFillerEnrollmentId,
() => PreferenceRollouts.migrations.migration01AddFillerEnrollmentId,
migrateShieldPrefs,
migrateStudiesEnabledWithoutHealthReporting,
AddonStudies.migrateAddonStudyFieldsToSlugAndUserFacingFields,
PreferenceExperiments.migrations.migration01MoveExperiments,
PreferenceExperiments.migrations.migration02MultiPreference,
PreferenceExperiments.migrations.migration03AddActionName,
PreferenceExperiments.migrations.migration04RenameNameToSlug,
RecipeRunner.migrations.migration01RemoveOldRecipesCollection,
],
};

Просмотреть файл

@ -14,11 +14,6 @@ ChromeUtils.defineModuleGetter(
"TelemetryEnvironment",
"resource://gre/modules/TelemetryEnvironment.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"TelemetryEvents",
"resource://normandy/lib/TelemetryEvents.jsm"
);
/**
* AddonRollouts store info about an active or expired addon rollouts.
@ -128,40 +123,6 @@ const AddonRollouts = {
return getStore(db, "readwrite").put(rollout);
},
/**
* Update many existing rollouts. More efficient than calling `update` many
* times in a row.
* @param {Array<PreferenceRollout>} rollouts
* @throws If any of the passed rollouts have a slug that doesn't exist in the database already.
*/
async updateMany(rollouts) {
// Don't touch the database if there is nothing to do
if (!rollouts.length) {
return;
}
// Both of the below operations use .map() instead of a normal loop becaues
// once we get the object store, we can't let it expire by spinning the
// event loop. This approach queues up all the interactions with the store
// immediately, preventing it from expiring too soon.
const db = await getDatabase();
let store = await getStore(db, "readonly");
await Promise.all(
rollouts.map(async ({ slug }) => {
let existingRollout = await store.get(slug);
if (!existingRollout) {
throw new Error(`Tried to update ${slug}, but it doesn't exist.`);
}
})
);
// awaiting spun the event loop, so the store is now invalid. Get a new
// store. This is also a chance to get it in readwrite mode.
store = await getStore(db, "readwrite");
await Promise.all(rollouts.map(rollout => store.put(rollout)));
},
/**
* Test whether there is a rollout in storage with the given slug.
* @param {string} slug
@ -213,18 +174,4 @@ const AddonRollouts = {
}
};
},
migrations: {
async migration01AddFillerEnrollmentId() {
const rollouts = await AddonRollouts.getAll();
const rolloutsToUpdate = [];
for (const rollout of rollouts) {
if (typeof rollout.enrollmentId != "string") {
rollout.enrollmentId = TelemetryEvents.NO_ENROLLMENT_ID_MARKER;
rolloutsToUpdate.push(rollout);
}
}
await AddonRollouts.updateMany(rolloutsToUpdate);
},
},
};

Просмотреть файл

@ -179,6 +179,54 @@ var AddonStudies = {
});
},
/**
* Change from "name" and "description" to "slug", "userFacingName",
* and "userFacingDescription".
*
* This is called as needed by NormandyMigrations.jsm, which handles tracking
* if this migration has already been run.
*/
async migrateAddonStudyFieldsToSlugAndUserFacingFields() {
const db = await getDatabase();
const studies = await db.objectStore(STORE_NAME, "readonly").getAll();
// If there are no studies, stop here to avoid opening the DB again.
if (studies.length === 0) {
return;
}
// Object stores expire after `await`, so this method accumulates a bunch of
// promises, and then awaits them at the end.
const writePromises = [];
const objectStore = db.objectStore(STORE_NAME, "readwrite");
for (const study of studies) {
// use existing name as slug
if (!study.slug) {
study.slug = study.name;
}
// Rename `name` and `description` as `userFacingName` and `userFacingDescription`
if (study.name && !study.userFacingName) {
study.userFacingName = study.name;
}
delete study.name;
if (study.description && !study.userFacingDescription) {
study.userFacingDescription = study.description;
}
delete study.description;
// Specify that existing recipes don't have branches
if (!study.branch) {
study.branch = AddonStudies.NO_BRANCHES_MARKER;
}
writePromises.push(objectStore.put(study));
}
await Promise.all(writePromises);
},
/**
* If a study add-on is uninstalled, mark the study as having ended.
* @param {Addon} addon
@ -272,42 +320,6 @@ var AddonStudies = {
return getStore(db, "readwrite").put(study);
},
/**
* Update many existing studies. More efficient than calling `update` many
* times in a row.
* @param {Array<AddonStudy>} studies
* @throws If any of the passed studies have a slug that doesn't exist in the database already.
*/
async updateMany(studies) {
// Don't touch the database if there is nothing to do
if (!studies.length) {
return;
}
// Both of the below operations use .map() instead of a normal loop becaues
// once we get the object store, we can't let it expire by spinning the
// event loop. This approach queues up all the interactions with the store
// immediately, preventing it from expiring too soon.
const db = await getDatabase();
let store = await getStore(db, "readonly");
await Promise.all(
studies.map(async ({ recipeId }) => {
let existingStudy = await store.get(recipeId);
if (!existingStudy) {
throw new Error(
`Tried to update addon study ${recipeId}, but it doesn't exist.`
);
}
})
);
// awaiting spun the event loop, so the store is now invalid. Get a new
// store. This is also a chance to get it in readwrite mode.
store = await getStore(db, "readwrite");
await Promise.all(studies.map(study => store.put(study)));
},
/**
* Remove a study from storage
* @param recipeId The recipeId of the study to delete
@ -407,56 +419,6 @@ var AddonStudies = {
// the listeners fail.
await Promise.all(promises);
},
migrations: {
/**
* Change from "name" and "description" to "slug", "userFacingName",
* and "userFacingDescription".
*
* This is called as needed by NormandyMigrations.jsm, which handles tracking
* if this migration has already been run.
*/
async migration01StudyFieldsToSlugAndUserFacingFields() {
// If there are no studies, stop here to avoid opening the DB again.
const studies = await AddonStudies.getAll();
for (const study of studies) {
// use existing name as slug
if (!study.slug) {
study.slug = study.name;
}
// Rename `name` and `description` as `userFacingName` and `userFacingDescription`
if (study.name && !study.userFacingName) {
study.userFacingName = study.name;
}
delete study.name;
if (study.description && !study.userFacingDescription) {
study.userFacingDescription = study.description;
}
delete study.description;
// Specify that existing recipes don't have branches
if (!study.branch) {
study.branch = AddonStudies.NO_BRANCHES_MARKER;
}
}
await AddonStudies.updateMany(studies);
},
async migration02AddFillerEnrollmentId() {
const studies = await AddonStudies.getAll();
const studiesToUpdate = [];
for (const study of studies) {
if (typeof study.enrollmentId != "string") {
study.enrollmentId = TelemetryEvents.NO_ENROLLMENT_ID_MARKER;
studiesToUpdate.push(study);
}
}
await AddonStudies.updateMany(studiesToUpdate);
},
},
};
AddonStudies.NO_BRANCHES_MARKER = "__NO_BRANCHES__";

Просмотреть файл

@ -892,13 +892,6 @@ var PreferenceExperiments = {
},
experimentType: oldExperiment.experimentType,
};
// Propogate any existing enrollmentId. This isn't likely to ever occur
// in the real world, but the tests have mock data that includes an
// enrollment ID here, which makes the tests that actually rely on
// enrollmentId easier.
if (oldExperiment.enrollmentId) {
v2Experiments[expName].enrollmentId = oldExperiment.enrollmentId;
}
}
storage.data.experiments = v2Experiments;
storage.saveSoon();
@ -933,18 +926,5 @@ var PreferenceExperiments = {
}
storage.saveSoon();
},
async migration05AddFillerEnrollmentId(storage = null) {
if (!storage) {
storage = await ensureStorage();
}
// Make sure that all experiments have a string enrollmentId
for (const experiment of Object.values(storage.data.experiments)) {
if (typeof experiment.enrollmentId != "string") {
experiment.enrollmentId = TelemetryEvents.NO_ENROLLMENT_ID_MARKER;
}
}
storage.saveSoon();
},
},
};

Просмотреть файл

@ -228,40 +228,6 @@ var PreferenceRollouts = {
return getStore(db, "readwrite").put(rollout);
},
/**
* Update many existing rollouts. More efficient than calling `update` many
* times in a row.
* @param {Array<PreferenceRollout>} rollouts
* @throws If any of the passed rollouts have a slug that doesn't exist in the database already.
*/
async updateMany(rollouts) {
// Don't touch the database if there is nothing to do
if (!rollouts.length) {
return;
}
// Both of the below operations use .map() instead of a normal loop becaues
// once we get the object store, we can't let it expire by spinning the
// event loop. This approach queues up all the interactions with the store
// immediately, preventing it from expiring too soon.
const db = await getDatabase();
let store = await getStore(db, "readonly");
await Promise.all(
rollouts.map(async ({ slug }) => {
let existingRollout = await store.get(slug);
if (!existingRollout) {
throw new Error(`Tried to update ${slug}, but it doesn't exist.`);
}
})
);
// awaiting spun the event loop, so the store is now invalid. Get a new
// store. This is also a chance to get it in readwrite mode.
store = await getStore(db, "readwrite");
await Promise.all(rollouts.map(rollout => store.put(rollout)));
},
/**
* Test whether there is a rollout in storage with the given slug.
* @param {string} slug
@ -314,18 +280,4 @@ var PreferenceRollouts = {
}
}
},
migrations: {
async migration01AddFillerEnrollmentId() {
let rollouts = await PreferenceRollouts.getAll();
let rolloutsToUpdate = [];
for (const rollout of rollouts) {
if (typeof rollout.enrollmentId != "string") {
rollout.enrollmentId = TelemetryEvents.NO_ENROLLMENT_ID_MARKER;
rolloutsToUpdate.push(rollout);
}
}
await PreferenceRollouts.updateMany(rolloutsToUpdate);
},
},
};

Просмотреть файл

@ -10,8 +10,6 @@ var EXPORTED_SYMBOLS = ["TelemetryEvents"];
const TELEMETRY_CATEGORY = "normandy";
const TelemetryEvents = {
NO_ENROLLMENT_ID_MARKER: "__NO_ENROLLMENT_ID__",
init() {
Services.telemetry.setEventRecordingEnabled(TELEMETRY_CATEGORY, true);
},

Просмотреть файл

@ -48,14 +48,35 @@ function experimentFactory(attrs) {
const NOW = new Date();
// Note that the below data is not entirely representative of what we see in the
// real world. Specifically, even the older format includes one experiment with
// an enrollmentId. There isn't any way this could have happened in the real
// world. However, including it here lets us use a consistent set of data for
// all the tests. The simplification of the tests seems worth it.
const mockDataVersions = [
// before any migrations
{
const mockV1Data = {
hypothetical_experiment: {
name: "hypothetical_experiment",
branch: "hypo_1",
expired: false,
lastSeen: NOW.toJSON(),
preferenceName: "some.pref",
preferenceValue: 2,
preferenceType: "integer",
previousPreferenceValue: 1,
preferenceBranchType: "user",
experimentType: "exp",
},
another_experiment: {
name: "another_experiment",
branch: "another_4",
expired: true,
lastSeen: NOW.toJSON(),
preferenceName: "another.pref",
preferenceValue: true,
preferenceType: "boolean",
previousPreferenceValue: false,
preferenceBranchType: "default",
experimentType: "exp",
},
};
const mockV2Data = {
experiments: {
hypothetical_experiment: {
name: "hypothetical_experiment",
branch: "hypo_1",
@ -67,7 +88,6 @@ const mockDataVersions = [
previousPreferenceValue: 1,
preferenceBranchType: "user",
experimentType: "exp",
enrollmentId: "existing-enrollment-id",
},
another_experiment: {
name: "another_experiment",
@ -82,193 +102,116 @@ const mockDataVersions = [
experimentType: "exp",
},
},
};
// after migration 01
{
experiments: {
hypothetical_experiment: {
name: "hypothetical_experiment",
branch: "hypo_1",
expired: false,
lastSeen: NOW.toJSON(),
preferenceName: "some.pref",
preferenceValue: 2,
preferenceType: "integer",
previousPreferenceValue: 1,
preferenceBranchType: "user",
experimentType: "exp",
enrollmentId: "existing-enrollment-id",
const mockV3Data = {
experiments: {
hypothetical_experiment: {
name: "hypothetical_experiment",
branch: "hypo_1",
expired: false,
lastSeen: NOW.toJSON(),
preferences: {
"some.pref": {
preferenceValue: 2,
preferenceType: "integer",
previousPreferenceValue: 1,
preferenceBranchType: "user",
},
},
another_experiment: {
name: "another_experiment",
branch: "another_4",
expired: true,
lastSeen: NOW.toJSON(),
preferenceName: "another.pref",
preferenceValue: true,
preferenceType: "boolean",
previousPreferenceValue: false,
preferenceBranchType: "default",
experimentType: "exp",
experimentType: "exp",
},
another_experiment: {
name: "another_experiment",
branch: "another_4",
expired: true,
lastSeen: NOW.toJSON(),
preferences: {
"another.pref": {
preferenceValue: true,
preferenceType: "boolean",
previousPreferenceValue: false,
preferenceBranchType: "default",
},
},
experimentType: "exp",
},
},
};
// after migration 02
{
experiments: {
hypothetical_experiment: {
name: "hypothetical_experiment",
branch: "hypo_1",
expired: false,
lastSeen: NOW.toJSON(),
preferences: {
"some.pref": {
preferenceValue: 2,
preferenceType: "integer",
previousPreferenceValue: 1,
preferenceBranchType: "user",
},
const mockV4Data = {
experiments: {
hypothetical_experiment: {
name: "hypothetical_experiment",
branch: "hypo_1",
actionName: "SinglePreferenceExperimentAction",
expired: false,
lastSeen: NOW.toJSON(),
preferences: {
"some.pref": {
preferenceValue: 2,
preferenceType: "integer",
previousPreferenceValue: 1,
preferenceBranchType: "user",
},
experimentType: "exp",
enrollmentId: "existing-enrollment-id",
},
another_experiment: {
name: "another_experiment",
branch: "another_4",
expired: true,
lastSeen: NOW.toJSON(),
preferences: {
"another.pref": {
preferenceValue: true,
preferenceType: "boolean",
previousPreferenceValue: false,
preferenceBranchType: "default",
},
experimentType: "exp",
},
another_experiment: {
name: "another_experiment",
branch: "another_4",
actionName: "SinglePreferenceExperimentAction",
expired: true,
lastSeen: NOW.toJSON(),
preferences: {
"another.pref": {
preferenceValue: true,
preferenceType: "boolean",
previousPreferenceValue: false,
preferenceBranchType: "default",
},
experimentType: "exp",
},
experimentType: "exp",
},
},
};
// after migration 03
{
experiments: {
hypothetical_experiment: {
name: "hypothetical_experiment",
branch: "hypo_1",
actionName: "SinglePreferenceExperimentAction",
expired: false,
lastSeen: NOW.toJSON(),
preferences: {
"some.pref": {
preferenceValue: 2,
preferenceType: "integer",
previousPreferenceValue: 1,
preferenceBranchType: "user",
},
const mockV5Data = {
experiments: {
hypothetical_experiment: {
slug: "hypothetical_experiment",
branch: "hypo_1",
actionName: "SinglePreferenceExperimentAction",
expired: false,
lastSeen: NOW.toJSON(),
preferences: {
"some.pref": {
preferenceValue: 2,
preferenceType: "integer",
previousPreferenceValue: 1,
preferenceBranchType: "user",
},
experimentType: "exp",
enrollmentId: "existing-enrollment-id",
},
another_experiment: {
name: "another_experiment",
branch: "another_4",
actionName: "SinglePreferenceExperimentAction",
expired: true,
lastSeen: NOW.toJSON(),
preferences: {
"another.pref": {
preferenceValue: true,
preferenceType: "boolean",
previousPreferenceValue: false,
preferenceBranchType: "default",
},
experimentType: "exp",
},
another_experiment: {
slug: "another_experiment",
branch: "another_4",
actionName: "SinglePreferenceExperimentAction",
expired: true,
lastSeen: NOW.toJSON(),
preferences: {
"another.pref": {
preferenceValue: true,
preferenceType: "boolean",
previousPreferenceValue: false,
preferenceBranchType: "default",
},
experimentType: "exp",
},
experimentType: "exp",
},
},
// after migration 04
{
experiments: {
hypothetical_experiment: {
slug: "hypothetical_experiment",
branch: "hypo_1",
actionName: "SinglePreferenceExperimentAction",
expired: false,
lastSeen: NOW.toJSON(),
preferences: {
"some.pref": {
preferenceValue: 2,
preferenceType: "integer",
previousPreferenceValue: 1,
preferenceBranchType: "user",
},
},
experimentType: "exp",
enrollmentId: "existing-enrollment-id",
},
another_experiment: {
slug: "another_experiment",
branch: "another_4",
actionName: "SinglePreferenceExperimentAction",
expired: true,
lastSeen: NOW.toJSON(),
preferences: {
"another.pref": {
preferenceValue: true,
preferenceType: "boolean",
previousPreferenceValue: false,
preferenceBranchType: "default",
},
},
experimentType: "exp",
},
},
},
// after migration 05
{
experiments: {
hypothetical_experiment: {
slug: "hypothetical_experiment",
branch: "hypo_1",
actionName: "SinglePreferenceExperimentAction",
expired: false,
lastSeen: NOW.toJSON(),
preferences: {
"some.pref": {
preferenceValue: 2,
preferenceType: "integer",
previousPreferenceValue: 1,
preferenceBranchType: "user",
},
},
experimentType: "exp",
enrollmentId: "existing-enrollment-id",
},
another_experiment: {
slug: "another_experiment",
branch: "another_4",
actionName: "SinglePreferenceExperimentAction",
expired: true,
lastSeen: NOW.toJSON(),
preferences: {
"another.pref": {
preferenceValue: true,
preferenceType: "boolean",
previousPreferenceValue: false,
preferenceBranchType: "default",
},
},
experimentType: "exp",
enrollmentId: "__NO_ENROLLMENT_ID__",
},
},
},
];
};
/**
* Make a mock `JsonFile` object with a no-op `saveSoon` method and a deep copy
@ -283,33 +226,37 @@ function makeMockJsonFile(data = {}) {
};
}
let migrationsList = [
PreferenceExperiments.migrations.migration01MoveExperiments,
PreferenceExperiments.migrations.migration02MultiPreference,
PreferenceExperiments.migrations.migration03AddActionName,
PreferenceExperiments.migrations.migration04RenameNameToSlug,
PreferenceExperiments.migrations.migration05AddFillerEnrollmentId,
];
/** Test that each migration results in the expected data */
add_task(async function test_migrations() {
let mockJsonFile = makeMockJsonFile(mockDataVersions[0]);
for (let i = 0; i < migrationsList.length; i++) {
await migrationsList[i](mockJsonFile);
Assert.deepEqual(
mockJsonFile.data,
mockDataVersions[i + 1],
`Migration ${i + 1} should produce the expected results`
);
}
let mockJsonFile = makeMockJsonFile(mockV1Data);
await PreferenceExperiments.migrations.migration01MoveExperiments(
mockJsonFile
);
Assert.deepEqual(mockJsonFile.data, mockV2Data);
mockJsonFile = makeMockJsonFile(mockV2Data);
await PreferenceExperiments.migrations.migration02MultiPreference(
mockJsonFile
);
Assert.deepEqual(mockJsonFile.data, mockV3Data);
mockJsonFile = makeMockJsonFile(mockV3Data);
await PreferenceExperiments.migrations.migration03AddActionName(mockJsonFile);
Assert.deepEqual(mockJsonFile.data, mockV4Data);
mockJsonFile = makeMockJsonFile(mockV4Data);
await PreferenceExperiments.migrations.migration04RenameNameToSlug(
mockJsonFile
);
Assert.deepEqual(mockJsonFile.data, mockV5Data);
});
add_task(async function migration03KeepsActionName() {
let mockData = JSON.parse(JSON.stringify(mockDataVersions[2]));
let mockData = JSON.parse(JSON.stringify(mockV3Data));
mockData.experiments.another_experiment.actionName = "SomeOldAction";
const mockJsonFile = makeMockJsonFile(mockData);
// Output should be the same as mockDataVersions[3], but preserving the action.
const migratedData = JSON.parse(JSON.stringify(mockDataVersions[3]));
// Output should be the same as mockV4Data, but preserving the action.
const migratedData = JSON.parse(JSON.stringify(mockV4Data));
migratedData.experiments.another_experiment.actionName = "SomeOldAction";
await PreferenceExperiments.migrations.migration03AddActionName(mockJsonFile);
@ -317,9 +264,13 @@ add_task(async function migration03KeepsActionName() {
});
add_task(async function migrations_are_idempotent() {
for (let i = 0; i < migrationsList.length; i++) {
const migration = migrationsList[i];
const mockOldData = mockDataVersions[i];
let dataVersions = [
[PreferenceExperiments.migrations.migration01MoveExperiments, mockV1Data],
[PreferenceExperiments.migrations.migration02MultiPreference, mockV2Data],
[PreferenceExperiments.migrations.migration03AddActionName, mockV3Data],
[PreferenceExperiments.migrations.migration04RenameNameToSlug, mockV4Data],
];
for (const [migration, mockOldData] of dataVersions) {
const mockJsonFileOnce = makeMockJsonFile(mockOldData);
const mockJsonFileTwice = makeMockJsonFile(mockOldData);
await migration(mockJsonFileOnce);