gecko-dev/toolkit/components/normandy/test/browser/browser_AddonStudies.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

156 lines
5 KiB
JavaScript

"use strict";
ChromeUtils.import("resource://gre/modules/IndexedDB.jsm", this);
ChromeUtils.import("resource://gre/modules/AddonManager.jsm", this);
ChromeUtils.import("resource://testing-common/TestUtils.jsm", this);
ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm", this);
ChromeUtils.import("resource://normandy/lib/AddonStudies.jsm", this);
ChromeUtils.import("resource://normandy/lib/TelemetryEvents.jsm", this);
// Initialize test utils
AddonTestUtils.initMochitest(this);
let _startArgsFactoryId = 1;
function startArgsFactory(args) {
return Object.assign({
recipeId: _startArgsFactoryId++,
name: "Test",
description: "Test",
addonUrl: "http://test/addon.xpi",
}, args);
}
decorate_task(
AddonStudies.withStudies(),
async function testGetMissing() {
is(
await AddonStudies.get("does-not-exist"),
null,
"get returns null when the requested study does not exist"
);
}
);
decorate_task(
AddonStudies.withStudies([
addonStudyFactory({name: "test-study"}),
]),
async function testGet([study]) {
const storedStudy = await AddonStudies.get(study.recipeId);
Assert.deepEqual(study, storedStudy, "get retrieved a study from storage.");
}
);
decorate_task(
AddonStudies.withStudies([
addonStudyFactory(),
addonStudyFactory(),
]),
async function testGetAll(studies) {
const storedStudies = await AddonStudies.getAll();
Assert.deepEqual(
new Set(storedStudies),
new Set(studies),
"getAll returns every stored study.",
);
}
);
decorate_task(
AddonStudies.withStudies([
addonStudyFactory({name: "test-study"}),
]),
async function testHas([study]) {
let hasStudy = await AddonStudies.has(study.recipeId);
ok(hasStudy, "has returns true for a study that exists in storage.");
hasStudy = await AddonStudies.has("does-not-exist");
ok(!hasStudy, "has returns false for a study that doesn't exist in storage.");
}
);
decorate_task(
AddonStudies.withStudies([
addonStudyFactory({name: "test-study1"}),
addonStudyFactory({name: "test-study2"}),
]),
async function testClear([study1, study2]) {
const hasAll = (
(await AddonStudies.has(study1.recipeId)) &&
(await AddonStudies.has(study2.recipeId))
);
ok(hasAll, "Before calling clear, both studies are in storage.");
await AddonStudies.clear();
const hasAny = (
(await AddonStudies.has(study1.recipeId)) ||
(await AddonStudies.has(study2.recipeId))
);
ok(!hasAny, "After calling clear, all studies are removed from storage.");
}
);
decorate_task(
AddonStudies.withStudies([
addonStudyFactory({active: true, addonId: "does.not.exist@example.com", studyEndDate: null}),
addonStudyFactory({active: true, addonId: "installed@example.com"}),
addonStudyFactory({active: false, addonId: "already.gone@example.com", studyEndDate: new Date(2012, 1)}),
]),
withSendEventStub,
withInstalledWebExtension({id: "installed@example.com"}),
async function testInit([activeUninstalledStudy, activeInstalledStudy, inactiveStudy], sendEventStub) {
await AddonStudies.init();
const newActiveStudy = await AddonStudies.get(activeUninstalledStudy.recipeId);
ok(!newActiveStudy.active, "init marks studies as inactive if their add-on is not installed.");
ok(
newActiveStudy.studyEndDate,
"init sets the study end date if a study's add-on is not installed."
);
Assert.deepEqual(
sendEventStub.getCall(0).args,
["unenroll", "addon_study", activeUninstalledStudy.name, {
addonId: activeUninstalledStudy.addonId,
addonVersion: activeUninstalledStudy.addonVersion,
reason: "uninstalled-sideload",
}],
"AddonStudies.init() should send the correct telemetry event"
);
const newInactiveStudy = await AddonStudies.get(inactiveStudy.recipeId);
is(
newInactiveStudy.studyEndDate.getFullYear(),
2012,
"init does not modify inactive studies."
);
const newActiveInstalledStudy = await AddonStudies.get(activeInstalledStudy.recipeId);
Assert.deepEqual(
activeInstalledStudy,
newActiveInstalledStudy,
"init does not modify studies whose add-on is still installed."
);
// Only activeUninstalledStudy should have generated any events
ok(sendEventStub.calledOnce, "no extra events should be generated");
}
);
decorate_task(
AddonStudies.withStudies([
addonStudyFactory({active: true, addonId: "installed@example.com", studyEndDate: null}),
]),
withInstalledWebExtension({id: "installed@example.com"}, /* expectUninstall: */ true),
async function testInit([study], [id, addonFile]) {
const addon = await AddonManager.getAddonByID(id);
await addon.uninstall();
await TestUtils.topicObserved("shield-study-ended");
const newStudy = await AddonStudies.get(study.recipeId);
ok(!newStudy.active, "Studies are marked as inactive when their add-on is uninstalled.");
ok(
newStudy.studyEndDate,
"The study end date is set when the add-on for the study is uninstalled."
);
}
);