Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Errors
- This test gets skipped with pattern: os == 'android'
- This test failed 9 times in the preceding 30 days. quicksearch this test
- Manifest: toolkit/components/telemetry/dap/tests/xpcshell/xpcshell.toml
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
);
const { PlacesTestUtils } = ChromeUtils.importESModule(
);
const { NetUtil } = ChromeUtils.importESModule(
"resource://gre/modules/NetUtil.sys.mjs"
);
const { PlacesUtils } = ChromeUtils.importESModule(
"resource://gre/modules/PlacesUtils.sys.mjs"
);
const { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
const { NimbusTestUtils } = ChromeUtils.importESModule(
);
const { DAPIncrementality } = ChromeUtils.importESModule(
"resource://gre/modules/DAPIncrementality.sys.mjs"
);
NimbusTestUtils.init(this);
const BinaryInputStream = Components.Constructor(
"@mozilla.org/binaryinputstream;1",
"nsIBinaryInputStream",
"setInputStream"
);
const PREF_LEADER = "toolkit.telemetry.dap.leader.url";
const PREF_HELPER = "toolkit.telemetry.dap.helper.url";
const TRANSITION_TYPED = PlacesUtils.history.TRANSITION_TYPED;
const TASK_ID = "o-91EcR2kfxfAmkKPPHifXKqiH7Upm0Ilw5joB3L_pE";
let server;
let server_addr;
// The dummy test server will record report sizes in this list.
let server_requests = [];
function uploadHandler(request, response) {
Assert.equal(
request.getHeader("Content-Type"),
"application/dap-report",
"Wrong Content-Type header."
);
let body = new BinaryInputStream(request.bodyInputStream);
server_requests.push(body.available());
response.setStatusLine(request.httpVersion, 200);
}
function resetServerRequests() {
server_requests.length = 0;
}
add_setup(async function () {
do_get_profile();
// Set up a mock server to represent the DAP endpoints.
server = new HttpServer();
server.registerPrefixHandler("/leader_endpoint/tasks/", uploadHandler);
server.start(-1);
const i = server.identity;
server_addr = i.primaryScheme + "://" + i.primaryHost + ":" + i.primaryPort;
Services.prefs.setStringPref(PREF_LEADER, server_addr + "/leader_endpoint");
Services.prefs.setStringPref(PREF_HELPER, server_addr + "/helper_endpoint");
registerCleanupFunction(() => {
Services.prefs.clearUserPref(PREF_LEADER);
Services.prefs.clearUserPref(PREF_HELPER);
return new Promise(resolve => {
server.stop(resolve);
});
});
});
function openSubCapDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("SubmissionCap", 1);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
function openIncrDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("DAPIncrementality", 1);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
function countRecords(store) {
return new Promise((resolve, reject) => {
const request = store.count();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function getFreqCapCount(db) {
const tx = db.transaction("freq_caps", "readonly");
const store = tx.objectStore("freq_caps");
const count = await countRecords(store);
await tx.done;
return count;
}
async function getReportCount(db) {
const tx = db.transaction("reports", "readonly");
const store = tx.objectStore("reports");
const count = await countRecords(store);
await tx.done;
return count;
}
async function getReport(db, taskId) {
const tx = db.transaction("reports", "readonly");
const store = await tx.objectStore("reports");
return new Promise((resolve, reject) => {
const request = store.get(taskId);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function getReferrerState(db, taskId) {
const tx = db.transaction("referrer", "readonly");
const store = await tx.objectStore("referrer");
return new Promise((resolve, reject) => {
const request = store.get(taskId);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
add_task(
{
// Requires Normandy.
skip_if: () => !AppConstants.MOZ_NORMANDY,
},
async function testVisitMeasurementNimbus() {
resetServerRequests();
const { cleanup } = await NimbusTestUtils.setupTest();
await DAPIncrementality.startup();
Assert.strictEqual(
DAPIncrementality.dapReportContoller,
null,
"dapReportContoller should not exist before enrollment"
);
// Enroll in experiment to count 1 url
const doExperimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig({
featureId: "dapIncrementality",
value: {
measurementType: "visitMeasurement",
taskId: TASK_ID,
length: 2,
timePrecision: 3600,
visitCountUrls: [
{
url: "*://*.example.com/",
bucket: 0,
},
{
url: "*://*.mozilla.org/",
bucket: 1,
},
],
},
});
Assert.notStrictEqual(
DAPIncrementality.dapReportContoller,
null,
"dapReportContoller should be active"
);
// Verify there are no pending reports to submit
const subCapDb = await openSubCapDatabase();
let numRecords = await getReportCount(subCapDb);
Assert.equal(numRecords, 0, "Should be no pending reports");
// Visit a url that does not match a pattern.
let timestamp = Date.now() * 1000;
await PlacesTestUtils.addVisits({
transition: TRANSITION_TYPED,
visitDate: timestamp,
});
// Verify there are 0 pending reports to send
numRecords = await getReportCount(subCapDb);
Assert.equal(numRecords, 0, "Should be no pending reports");
// Visit a url by typing in the url bar
timestamp = Date.now() * 1000;
await PlacesTestUtils.addVisits({
transition: TRANSITION_TYPED,
visitDate: timestamp,
});
// Verify there is 1 pending report to send and measurement matches bucket value.
numRecords = await getReportCount(subCapDb);
Assert.equal(numRecords, 1, "Should be 1 pending report");
// Trigger submission of the report
await DAPIncrementality.dapReportContoller.submit(1000, "unit-test");
// Verify there are 0 pending reports
numRecords = await getReportCount(subCapDb);
Assert.equal(numRecords, 0, "Should be no pending reports");
// Verify submission capping is active
numRecords = await getFreqCapCount(subCapDb);
Assert.equal(numRecords, 1, "Should be 1 cap entry");
// Unenroll experiment
await doExperimentCleanup();
Services.tm.spinEventLoopUntil(
"Wait for DAPIncrementality to flush",
() => DAPIncrementality.config === null
);
// Verify freq cap cleanup after unenrollment
numRecords = await getFreqCapCount(subCapDb);
Assert.equal(numRecords, 0, "Should be 0 cap entries");
// Verify pending report cleanup after unenrollment
numRecords = await getReportCount(subCapDb);
Assert.equal(numRecords, 0, "Should be no pending reports");
// Verify server requests
Assert.deepEqual(
server_requests,
[390, 390, 390],
"Should have one report on enrollment, second for triggered submission, third on unenrollment"
);
Assert.strictEqual(
DAPIncrementality.dapReportContoller,
null,
"dapReportContoller should not exist after unenrollment"
);
await cleanup();
}
);
add_task(
{
// Requires Normandy.
skip_if: () => !AppConstants.MOZ_NORMANDY,
},
async function testParsingMultiUrlVisitMeasurementNimbus() {
resetServerRequests();
const { cleanup } = await NimbusTestUtils.setupTest();
await DAPIncrementality.startup();
Assert.strictEqual(
DAPIncrementality.dapReportContoller,
null,
"dapReportContoller should not exist before enrollment"
);
const expectedVisitCountUrls = [
{
url: "*://*.mozilla.org/",
bucket: 0,
},
{
url: "*://*.example.com/",
bucket: 1,
},
];
// Enroll in experiment to count 2 urls
const doExperimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig({
featureId: "dapIncrementality",
value: {
measurementType: "visitMeasurement",
taskId: TASK_ID,
length: 2,
timePrecision: 3600,
visitCountUrls: expectedVisitCountUrls,
},
});
const toKey = ({ url, bucket }) => `${url}#${bucket}`;
const expectedKeySorted = expectedVisitCountUrls.map(toKey).sort();
const parsedKeySorted = DAPIncrementality.config.visitUrlPatterns
.map(({ pattern, bucket }) => `${pattern.pattern}#${bucket}`)
.sort();
Assert.deepEqual(
parsedKeySorted,
expectedKeySorted,
"patterns and buckets match"
);
// Trigger submission of the report
await DAPIncrementality.dapReportContoller.submit(1000, "unit-test");
// Unenroll experiment
await doExperimentCleanup();
Services.tm.spinEventLoopUntil(
"Wait for DAPIncrementality to flush",
() => DAPIncrementality.config === null
);
// Verify server requests
Assert.deepEqual(
server_requests,
[390, 390, 390],
"Should have one report on enrollment, second for triggered submission, third on unenrollment"
);
await cleanup();
}
);
add_task(
{
// Requires Normandy.
skip_if: () => !AppConstants.MOZ_NORMANDY,
},
async function testReferrerMeasurementNimbus() {
resetServerRequests();
const { cleanup } = await NimbusTestUtils.setupTest();
await DAPIncrementality.startup();
Assert.strictEqual(
DAPIncrementality.dapReportContoller,
null,
"dapReportContoller should not exist before enrollment"
);
// Enroll experiment with 1 referrer url and 1 target url
const doExperimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig({
featureId: "dapIncrementality",
value: {
measurementType: "referrerMeasurement",
taskId: TASK_ID,
length: 3,
timePrecision: 3600,
referrerUrls: [
{
url: "*://*.mozilla.org/ref",
bucket: 1,
},
],
targetUrls: "*://*.mozilla.org/target",
unknownReferrerBucket: 2,
},
});
// Visit neither referrer nor target Urls
let timestamp = Date.now() * 1000;
await PlacesTestUtils.addVisits({
transition: TRANSITION_TYPED,
visitDate: timestamp,
});
// Verify no referrer state stored
const incrDb = await openIncrDatabase();
let state = await getReferrerState(incrDb, TASK_ID);
Assert.strictEqual(
state,
undefined,
"No referrer state should be recorded"
);
// Visit the referrer url
timestamp = Date.now() * 1000;
await PlacesTestUtils.addVisits({
transition: TRANSITION_TYPED,
visitDate: timestamp,
});
// Verify referrer state matches the bucket for the referred url
state = await getReferrerState(incrDb, TASK_ID);
Assert.strictEqual(state.bucket, 1, "Referrer state should be 1");
// Visit target url
timestamp = Date.now() * 1000;
await PlacesTestUtils.addVisits({
transition: TRANSITION_TYPED,
visitDate: timestamp,
});
// Verify the pending report matches the bucket for the referred url
const subCapDb = await openSubCapDatabase();
let pendingReport = await getReport(subCapDb, TASK_ID);
Assert.equal(
pendingReport.measurement,
1,
"Pending measurement value should be 1"
);
// Verify that the referrer state has been cleared
state = await getReferrerState(incrDb, TASK_ID);
Assert.strictEqual(state, undefined, "Referrer state should be cleared");
// Trigger submission of the report
await DAPIncrementality.dapReportContoller.submit(1000, "unit-test");
// Unenroll experiment
await doExperimentCleanup();
Services.tm.spinEventLoopUntil(
"Wait for DAPIncrementality to flush",
() => DAPIncrementality.config === null
);
// Verify server requests
Assert.deepEqual(
server_requests,
[438, 438, 438],
"Should have one report on enrollment, second for triggered submission, third on unenrollment"
);
await cleanup();
}
);
add_task(
{
// Requires Normandy.
skip_if: () => !AppConstants.MOZ_NORMANDY,
},
async function testReferrerMeasurementParsingMultiTargetNimbus() {
resetServerRequests();
const { cleanup } = await NimbusTestUtils.setupTest();
await DAPIncrementality.startup();
Assert.strictEqual(
DAPIncrementality.dapReportContoller,
null,
"dapReportContoller should not exist before enrollment"
);
// Enroll experiment with 1 referrer url and 2 target urls
const targetUrls = "*://*.mozilla.org/target1,*://*.mozilla.org/target2";
const doExperimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig({
featureId: "dapIncrementality",
value: {
measurementType: "referrerMeasurement",
taskId: TASK_ID,
length: 3,
timePrecision: 3600,
referrerUrls: [
{
url: "*://*.mozilla.org/ref",
bucket: 1,
},
],
targetUrls,
unknownReferrerBucket: 2,
},
});
Assert.strictEqual(
DAPIncrementality.config.targetUrlPatterns.length,
2,
"List should contain 2 urls patterns"
);
const missingUrls = DAPIncrementality.config.targetUrlPatterns.filter(
s => !targetUrls.includes(s.pattern)
);
Assert.deepEqual(
missingUrls,
[],
"All urls in the list should be found in the targetUrlPatterns"
);
// Unenroll experiment
await doExperimentCleanup();
Services.tm.spinEventLoopUntil(
"Wait for DAPIncrementality to flush",
() => DAPIncrementality.config === null
);
// Verify server requests
Assert.deepEqual(
server_requests,
[438, 438],
"Should have one report on enrollment, second on unenrollment"
);
await cleanup();
}
);
add_task(
{
// Requires Normandy.
skip_if: () => !AppConstants.MOZ_NORMANDY,
},
async function testReferrerMeasurementUnknownBucketNotSetNimbus() {
resetServerRequests();
const { cleanup } = await NimbusTestUtils.setupTest();
await DAPIncrementality.startup();
Assert.strictEqual(
DAPIncrementality.dapReportContoller,
null,
"dapReportContoller should not exist before enrollment"
);
// Enroll experiment with 1 referrer url and 1 target url without unknown bucket defined.
const doExperimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig({
featureId: "dapIncrementality",
value: {
measurementType: "referrerMeasurement",
taskId: TASK_ID,
length: 3,
timePrecision: 3600,
referrerUrls: [
{
url: "*://*.mozilla.org/ref",
bucket: 1,
},
],
targetUrls: "*://*.mozilla.org/target",
},
});
// Visit target url without prior referrer visit
let timestamp = Date.now() * 1000;
await PlacesTestUtils.addVisits({
transition: TRANSITION_TYPED,
visitDate: timestamp,
});
// Verify no referrer state is recorded
const incrDb = await openIncrDatabase();
let state = await getReferrerState(incrDb, TASK_ID);
Assert.strictEqual(
state,
undefined,
"No referrer state should be recorded"
);
// Verify there is no pending report since there is no unknown bucket defined.
const subCapDb = await openSubCapDatabase();
let pendingReport = await getReport(subCapDb, TASK_ID);
Assert.equal(
pendingReport,
undefined,
"No pending report should be recorded"
);
// Unenroll experiment
await doExperimentCleanup();
Services.tm.spinEventLoopUntil(
"Wait for DAPIncrementality to flush",
() => DAPIncrementality.config === null
);
// Verify server requests
Assert.deepEqual(
server_requests,
[438, 438],
"Should have one report on enrollment, second on unenrollment"
);
await cleanup();
}
);
add_task(
{
// Requires Normandy.
skip_if: () => !AppConstants.MOZ_NORMANDY,
},
async function testReferrerMeasurementUnknownBucketSet() {
resetServerRequests();
const { cleanup } = await NimbusTestUtils.setupTest();
await DAPIncrementality.startup();
Assert.strictEqual(
DAPIncrementality.dapReportContoller,
null,
"dapReportContoller should not exist before enrollment"
);
// Enroll experiment with 1 referrer url and 1 target url
const doExperimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig({
featureId: "dapIncrementality",
value: {
measurementType: "referrerMeasurement",
taskId: TASK_ID,
length: 3,
timePrecision: 3600,
referrerUrls: [
{
url: "*://*.mozilla.org/ref",
bucket: 1,
},
],
targetUrls: "*://*.mozilla.org/target",
unknownReferrerBucket: 2,
},
});
// Visit neither referrer nor target Urls
let timestamp = Date.now() * 1000;
await PlacesTestUtils.addVisits({
transition: TRANSITION_TYPED,
visitDate: timestamp,
});
// Verify no referrer state stored
const incrDb = await openIncrDatabase();
let state = await getReferrerState(incrDb, TASK_ID);
Assert.strictEqual(
state,
undefined,
"No referrer state should be recorded"
);
// Visit target url without prior referrer visit
timestamp = Date.now() * 1000;
await PlacesTestUtils.addVisits({
transition: TRANSITION_TYPED,
visitDate: timestamp,
});
// Verify no referrer state stored
state = await getReferrerState(incrDb, TASK_ID);
Assert.strictEqual(
state,
undefined,
"No referrer state should be recorded"
);
// Verify there is a pending report to submit with the value for unknownReferrerBucket
const subCapDb = await openSubCapDatabase();
let pendingReport = await getReport(subCapDb, TASK_ID);
Assert.equal(
pendingReport.measurement,
2,
"Pending measurement value should be 2"
);
// Trigger submission of the report
await DAPIncrementality.dapReportContoller.submit(1000, "unit-test");
// Unenroll experiment
await doExperimentCleanup();
Services.tm.spinEventLoopUntil(
"Wait for DAPIncrementality to flush",
() => DAPIncrementality.config === null
);
// Verify server requests
Assert.deepEqual(
server_requests,
[438, 438, 438],
"Should have one report on enrollment, second for triggered submission, third on unenrollment"
);
await cleanup();
}
);
add_task(
{
// Requires Normandy.
skip_if: () => !AppConstants.MOZ_NORMANDY,
},
async function testMultiReferrerMeasurementUnknownBucketNotSetNimbus() {
resetServerRequests();
const { cleanup } = await NimbusTestUtils.setupTest();
await DAPIncrementality.startup();
Assert.strictEqual(
DAPIncrementality.dapReportContoller,
null,
"dapReportContoller should not exist before enrollment"
);
// Enroll experiment with 2 referrer urls and 1 target url without unknown bucket defined.
const doExperimentCleanup = await NimbusTestUtils.enrollWithFeatureConfig({
featureId: "dapIncrementality",
value: {
measurementType: "referrerMeasurement",
taskId: TASK_ID,
length: 3,
timePrecision: 3600,
referrerUrls: [
{
url: "*://*.mozilla.org/ref",
bucket: 1,
},
{
url: "*://*.mozilla.org/ref2",
bucket: 2,
},
],
targetUrls: "*://*.mozilla.org/target",
},
});
// Visit first referrer url
let timestamp = Date.now() * 1000;
await PlacesTestUtils.addVisits({
transition: TRANSITION_TYPED,
visitDate: timestamp,
});
// Verify that referrer state matches the specifed bucket for the first url
const incrDb = await openIncrDatabase();
let state = await getReferrerState(incrDb, TASK_ID);
Assert.strictEqual(state.bucket, 1, "Referrer state should be 1");
// Visit second referrer url
timestamp = Date.now() * 1000;
await PlacesTestUtils.addVisits({
transition: TRANSITION_TYPED,
visitDate: timestamp,
});
// Verify that referrer state matches the specifed bucket for the second url
state = await getReferrerState(incrDb, TASK_ID);
Assert.strictEqual(state.bucket, 2, "Referrer state should be 2");
// Visit target url
timestamp = Date.now() * 1000;
await PlacesTestUtils.addVisits({
transition: TRANSITION_TYPED,
visitDate: timestamp,
});
// Verify the pending report measurement is 2
const subCapDb = await openSubCapDatabase();
let pendingReport = await getReport(subCapDb, TASK_ID);
Assert.equal(
pendingReport.measurement,
2,
"Pending measurement value should be 2"
);
// Verify referrer state is cleard
state = await getReferrerState(incrDb, TASK_ID);
Assert.strictEqual(state, undefined, "Referrer state should be cleared");
// Trigger submission of the report
await DAPIncrementality.dapReportContoller.submit(1000, "unit-test");
// Unenroll experiment
await doExperimentCleanup();
Services.tm.spinEventLoopUntil(
"Wait for DAPIncrementality to flush",
() => DAPIncrementality.config === null
);
// Verify server requests
Assert.deepEqual(
server_requests,
[438, 438, 438],
"Should have one report on enrollment, second for triggered submission, third on unenrollment"
);
await cleanup();
}
);