Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android' OR appname == 'thunderbird'
- Manifest: toolkit/components/nimbus/test/unit/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
*/
const { ExperimentAPI } = ChromeUtils.importESModule(
"resource://nimbus/ExperimentAPI.sys.mjs"
);
const { recordTargetingContext } = ChromeUtils.importESModule(
"resource://nimbus/lib/TargetingContextRecorder.sys.mjs"
);
const { JsonSchema } = ChromeUtils.importESModule(
"resource://gre/modules/JsonSchema.sys.mjs"
);
add_setup(function setup() {
do_get_profile();
Services.fog.initializeFOG();
});
const SCHEMAS = {
get nimbusTelemetry() {
return fetch(
"resource://nimbus/schemas/NimbusTelemetryFeature.schema.json",
{ credentials: "omit" }
).then(rsp => rsp.json());
},
};
function nimbusTargetingContextTelemetryDisabled() {
return !Services.prefs.getBoolPref(
"nimbus.telemetry.targetingContextEnabled"
);
}
add_task(
{ skip_if: nimbusTargetingContextTelemetryDisabled },
async function test_enrollAndUnenroll_gleanMetricConfiguration() {
const experiment = ExperimentFakes.recipe("experiment", {
bucketConfig: {
...ExperimentFakes.recipe.bucketConfig,
count: 1000,
},
branches: [
{
slug: "control",
ratio: 1,
features: [
{
featureId: "nimbusTelemetry",
value: {
gleanMetricConfiguration: {
metrics_enabled: {
"nimbus_targeting_environment.targeting_context_value": true,
},
},
},
},
],
},
],
});
const rollout = ExperimentFakes.recipe("rollout", {
bucketConfig: experiment.bucketConfig,
isRollout: true,
branches: [
{
slug: "control",
ratio: 1,
features: [
{
featureId: "nimbusTelemetry",
value: {
gleanMetricConfiguration: {
metrics_enabled: {
"nimbus_events.enrollment_status": true,
},
},
},
},
],
},
],
});
const manager = ExperimentFakes.manager();
sinon.stub(ExperimentAPI, "_manager").get(() => manager);
sinon.stub(ExperimentAPI, "_store").get(() => manager.store);
await manager.onStartup();
await manager.store.ready();
Glean.nimbusTargetingEnvironment.targetingContextValue.set(
"nothing-active-0"
);
Glean.nimbusEvents.enrollmentStatus.record({ reason: "nothing-active-0" });
Assert.equal(
Glean.nimbusTargetingEnvironment.targetingContextValue.testGetValue(),
null,
"targetingContextValue not recorded by default"
);
Assert.equal(
Glean.nimbusEvents.enrollmentStatus.testGetValue("events"),
null,
"enrollmentStatus not recorded by default"
);
await manager.enroll(rollout, "rs-loader");
Glean.nimbusTargetingEnvironment.targetingContextValue.set(
"rollout-active-1"
);
Glean.nimbusEvents.enrollmentStatus.record({ reason: "rollout-active-1" });
Assert.equal(
Glean.nimbusTargetingEnvironment.targetingContextValue.testGetValue(),
null,
"targetingContextValue not recorded by default"
);
{
const events = Glean.nimbusEvents.enrollmentStatus.testGetValue("events");
Assert.equal(events?.length ?? 0, 1, "enrollmentStatus recorded once");
Assert.equal(
events[0].extra.reason,
"rollout-active-1",
"enrollmentStatus recorded once"
);
}
await manager.enroll(experiment, "rs-loader");
Glean.nimbusTargetingEnvironment.targetingContextValue.set(
"experiment-active-2"
);
Glean.nimbusEvents.enrollmentStatus.record({
reason: "experiment-active-2",
});
Assert.equal(
Glean.nimbusTargetingEnvironment.targetingContextValue.testGetValue(),
"experiment-active-2"
);
{
const events = Glean.nimbusEvents.enrollmentStatus.testGetValue("events");
Assert.equal(
events?.length ?? 0,
1,
"enrollmentStatus not recorded again"
);
}
await manager.unenroll("experiment");
Glean.nimbusTargetingEnvironment.targetingContextValue.set(
"rollout-active-3"
);
Glean.nimbusEvents.enrollmentStatus.record({ reason: "rollout-active-3" });
Assert.equal(
Glean.nimbusTargetingEnvironment.targetingContextValue.testGetValue(),
"experiment-active-2",
"targetingContextValue was not recorded again"
);
{
const events = Glean.nimbusEvents.enrollmentStatus.testGetValue("events");
Assert.equal(events?.length ?? 0, 2, "enrollmentStatus recorded again");
Assert.equal(
events[1].extra.reason,
"rollout-active-3",
"enrollmentStatus recorded with correct value"
);
}
await manager.unenroll("rollout");
Glean.nimbusTargetingEnvironment.targetingContextValue.set(
"nothing-active-0"
);
Glean.nimbusEvents.enrollmentStatus.record({ reason: "nothing-active-0" });
Assert.equal(
Glean.nimbusTargetingEnvironment.targetingContextValue.testGetValue(),
"experiment-active-2",
"targetingContextValue was not recorded again"
);
{
const events = Glean.nimbusEvents.enrollmentStatus.testGetValue("events");
Assert.equal(
events?.length ?? 0,
2,
"enrollmentStatus not recorded again"
);
}
Services.fog.testResetFOG();
manager.store._deleteForTests("experiment");
manager.store._deleteForTests("rollout");
await assertEmptyStore(manager.store);
}
);
add_task(async function test_featureConfigSchema_gleanMetricConfiguration() {
function metricConfig(metric, value) {
return {
gleanMetricConfiguration: {
metrics_enabled: {
[metric]: value,
},
},
};
}
const validator = new JsonSchema.Validator(await SCHEMAS.nimbusTelemetry);
const ALLOWED = [
"nimbus_targeting_environment.targeting_context_value",
"nimbus_targeting_environment.pref_type_errors",
"nimbus_targeting_environment.attr_eval_errors",
"nimbus_targeting_environment.user_set_prefs",
"nimbus_targeting_environment.pref_values",
"nimbus_targeting_context.active_experiments",
"nimbus_targeting_context.active_rollouts",
"nimbus_targeting_context.addresses_saved",
"nimbus_events.enrollment",
"nimbus_events.is_ready",
"nimbus_events.enrollment_status",
];
for (const metric of ALLOWED) {
for (const value of [true, false]) {
Assert.ok(
validator.validate(metricConfig(metric, value)).valid,
`Can control ${metric} with nimbusTelemetry`
);
}
}
Assert.ok(
!validator.validate(metricConfig("nimbus_events.enrollment", "true")).valid,
"Schema enforces boolean values"
);
Assert.ok(
!validator.validate(metricConfig("bogus", true)).valid,
"Schema restricts metrics to nimbus metrics"
);
});
add_task(async function test_featureConfigSchema_nimbusTargetingEnvironment() {
const validator = new JsonSchema.Validator(await SCHEMAS.nimbusTelemetry);
Assert.ok(
validator.validate({
nimbusTargetingEnvironment: {
recordAttrs: ["activeExperiments"],
},
}).valid,
"Schema validates"
);
Assert.ok(
!validator.validate({
nimbusTargetingEnvironment: {
recordAttrs: "activeExperiments",
},
}).valid,
"Schema enforces types for recordAttrs"
);
});
add_task(
{ skip_if: nimbusTargetingContextTelemetryDisabled },
async function test_nimbusTargetingEnvironment_recordAttrs() {
const sandbox = sinon.createSandbox();
const manager = ExperimentFakes.manager();
sandbox.stub(ExperimentAPI, "_manager").get(() => manager);
sandbox.stub(ExperimentAPI, "_store").get(() => manager.store);
await manager.onStartup();
await manager.store.ready();
const cleanup = await ExperimentFakes.enrollWithFeatureConfig(
{
featureId: "nimbusTelemetry",
value: {
gleanMetricConfiguration: {
metrics_enabled: {
"nimbus_targeting_environment.targeting_context_value": true,
},
},
nimbusTargetingEnvironment: {
recordAttrs: [
"activeExperiments",
"activeRollouts",
"enrollmentsMap",
],
},
},
},
{
manager,
source: "test",
slug: "config",
}
);
Assert.equal(
Glean.nimbusTargetingEnvironment.targetingContextValue.testGetValue(),
null,
"The targetingContextValue metric has not been recorded yet."
);
GleanPings.nimbusTargetingContext.testBeforeNextSubmit(() => {
Assert.deepEqual(
JSON.parse(
Glean.nimbusTargetingEnvironment.targetingContextValue.testGetValue()
),
{
activeExperiments: ["config"],
activeRollouts: [],
enrollmentsMap: [
{
experimentSlug: "config",
branchSlug: "control",
},
],
}
);
});
await recordTargetingContext();
await cleanup();
await assertEmptyStore(manager.store);
await sandbox.restore();
Services.fog.testResetFOG();
}
);