gecko-dev/toolkit/components/normandy/test/browser/browser_PreferenceRollouts.js
Michael Cooper 5538808771 Bug 1502182 - In Normandy, never close IndexedDB databases, and be explicit about objectStore modes r=Gijs,asuth
I suspect that the root cause of bug 1502182 is that we try open a database
connection before the old one is fully closed. Instead of dealing with
complicatedasync bookkeeping to make sure this doesn't happen, this patch
simply never closes the database connection.  I don't think any of the
benefits of closing IndexedDB databases apply to Normandy, and it isn't
a significant cost to simply keep them open.

Additionally, the patch distinguishes between readonly and readwrite
transactions with the database. This was originally done to try and fix
the bug. When it didn't help, I decided to leave the change in because
it is a beneficial change anyways.

Differential Revision: https://phabricator.services.mozilla.com/D10629

--HG--
extra : moz-landing-system : lando
2018-11-07 23:21:52 +00:00

189 lines
6.5 KiB
JavaScript

"use strict";
ChromeUtils.import("resource://gre/modules/IndexedDB.jsm", this);
ChromeUtils.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
ChromeUtils.import("resource://normandy/lib/PreferenceRollouts.jsm", this);
ChromeUtils.import("resource://normandy/lib/TelemetryEvents.jsm", this);
decorate_task(
PreferenceRollouts.withTestMock,
async function testGetMissing() {
is(
await PreferenceRollouts.get("does-not-exist"),
null,
"get should return null when the requested rollout does not exist"
);
}
);
decorate_task(
PreferenceRollouts.withTestMock,
async function testAddUpdateAndGet() {
const rollout = {slug: "test-rollout", state: PreferenceRollouts.STATE_ACTIVE, preferences: []};
await PreferenceRollouts.add(rollout);
let storedRollout = await PreferenceRollouts.get(rollout.slug);
Assert.deepEqual(rollout, storedRollout, "get should retrieve a rollout from storage.");
rollout.state = PreferenceRollouts.STATE_GRADUATED;
await PreferenceRollouts.update(rollout);
storedRollout = await PreferenceRollouts.get(rollout.slug);
Assert.deepEqual(rollout, storedRollout, "get should retrieve a rollout from storage.");
},
);
decorate_task(
PreferenceRollouts.withTestMock,
async function testCantUpdateNonexistent() {
const rollout = {slug: "test-rollout", state: PreferenceRollouts.STATE_ACTIVE, preferences: []};
await Assert.rejects(
PreferenceRollouts.update(rollout),
/doesn't already exist/,
"Update should fail if the rollout doesn't exist",
);
ok(!await PreferenceRollouts.has("test-rollout"), "rollout should not have been added");
},
);
decorate_task(
PreferenceRollouts.withTestMock,
async function testGetAll() {
const rollout1 = {slug: "test-rollout-1", preference: []};
const rollout2 = {slug: "test-rollout-2", preference: []};
await PreferenceRollouts.add(rollout1);
await PreferenceRollouts.add(rollout2);
const storedRollouts = await PreferenceRollouts.getAll();
Assert.deepEqual(
storedRollouts.sort((a, b) => a.id - b.id),
[rollout1, rollout2],
"getAll should return every stored rollout.",
);
}
);
decorate_task(
PreferenceRollouts.withTestMock,
async function testGetAllActive() {
const rollout1 = {slug: "test-rollout-1", state: PreferenceRollouts.STATE_ACTIVE};
const rollout2 = {slug: "test-rollout-2", state: PreferenceRollouts.STATE_GRADUATED};
const rollout3 = {slug: "test-rollout-3", state: PreferenceRollouts.STATE_ROLLED_BACK};
await PreferenceRollouts.add(rollout1);
await PreferenceRollouts.add(rollout2);
await PreferenceRollouts.add(rollout3);
const activeRollouts = await PreferenceRollouts.getAllActive();
Assert.deepEqual(activeRollouts, [rollout1], "getAllActive should return only active rollouts");
}
);
decorate_task(
PreferenceRollouts.withTestMock,
async function testHas() {
const rollout = {slug: "test-rollout", preferences: []};
await PreferenceRollouts.add(rollout);
ok(await PreferenceRollouts.has(rollout.slug), "has should return true for an existing rollout");
ok(!await PreferenceRollouts.has("does not exist"), "has should return false for a missing rollout");
}
);
// recordOriginalValue should update storage to note the original values
decorate_task(
PreferenceRollouts.withTestMock,
async function testRecordOriginalValuesUpdatesPreviousValues() {
await PreferenceRollouts.add({
slug: "test-rollout",
state: PreferenceRollouts.STATE_ACTIVE,
preferences: [{preferenceName: "test.pref", value: 2, previousValue: null}],
});
await PreferenceRollouts.recordOriginalValues({"test.pref": 1});
Assert.deepEqual(
await PreferenceRollouts.getAll(),
[{
slug: "test-rollout",
state: PreferenceRollouts.STATE_ACTIVE,
preferences: [{preferenceName: "test.pref", value: 2, previousValue: 1}],
}],
"rollout in database should be updated",
);
},
);
// recordOriginalValue should graduate a study when it is no longer relevant.
decorate_task(
PreferenceRollouts.withTestMock,
withSendEventStub,
async function testRecordOriginalValuesUpdatesPreviousValues(sendEventStub) {
await PreferenceRollouts.add({
slug: "test-rollout",
state: PreferenceRollouts.STATE_ACTIVE,
preferences: [
{preferenceName: "test.pref1", value: 2, previousValue: null},
{preferenceName: "test.pref2", value: 2, previousValue: null},
],
});
// one pref being the same isn't enough to graduate
await PreferenceRollouts.recordOriginalValues({"test.pref1": 1, "test.pref2": 2});
let rollout = await PreferenceRollouts.get("test-rollout");
is(
rollout.state,
PreferenceRollouts.STATE_ACTIVE,
"rollouts should remain active when only one pref matches the built-in default",
);
Assert.deepEqual(sendEventStub.args, [], "no events should be sent yet");
// both prefs is enough
await PreferenceRollouts.recordOriginalValues({"test.pref1": 2, "test.pref2": 2});
rollout = await PreferenceRollouts.get("test-rollout");
is(
rollout.state,
PreferenceRollouts.STATE_GRADUATED,
"rollouts should graduate when all prefs matches the built-in defaults",
);
Assert.deepEqual(
sendEventStub.args,
[["graduate", "preference_rollout", "test-rollout", {}]],
"a graduation event should be sent",
);
},
);
// init should mark active rollouts in telemetry
decorate_task(
PreferenceRollouts.withTestMock,
withStub(TelemetryEnvironment, "setExperimentActive"),
async function testInitTelemetry(setExperimentActiveStub) {
await PreferenceRollouts.add({
slug: "test-rollout-active-1",
state: PreferenceRollouts.STATE_ACTIVE,
});
await PreferenceRollouts.add({
slug: "test-rollout-active-2",
state: PreferenceRollouts.STATE_ACTIVE,
});
await PreferenceRollouts.add({
slug: "test-rollout-rolled-back",
state: PreferenceRollouts.STATE_ROLLED_BACK,
});
await PreferenceRollouts.add({
slug: "test-rollout-graduated",
state: PreferenceRollouts.STATE_GRADUATED,
});
await PreferenceRollouts.init();
Assert.deepEqual(
setExperimentActiveStub.args,
[
["test-rollout-active-1", "active", {type: "normandy-prefrollout"}],
["test-rollout-active-2", "active", {type: "normandy-prefrollout"}],
],
"init should set activate a telemetry experiment for active preferences"
);
},
);