951 lines
29 KiB
JavaScript
951 lines
29 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
const { AppConstants } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/AppConstants.sys.mjs"
|
|
);
|
|
const { setTimeout } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/Timer.sys.mjs"
|
|
);
|
|
|
|
function sleep(ms) {
|
|
/* eslint-disable mozilla/no-arbitrary-setTimeout */
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
add_setup(
|
|
/* on Android FOG is set up through head.js */
|
|
{ skip_if: () => AppConstants.platform == "android" },
|
|
function test_setup() {
|
|
// FOG needs a profile directory to put its data in.
|
|
do_get_profile();
|
|
|
|
// We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
|
|
Services.fog.initializeFOG();
|
|
}
|
|
);
|
|
|
|
add_task(function test_fog_counter_works() {
|
|
Glean.testOnly.badCode.add(31);
|
|
Assert.equal(31, Glean.testOnly.badCode.testGetValue("test-ping"));
|
|
});
|
|
|
|
add_task(async function test_fog_string_works() {
|
|
Assert.equal(null, Glean.testOnly.cheesyString.testGetValue());
|
|
|
|
// Setting `undefined` will be ignored.
|
|
Glean.testOnly.cheesyString.set(undefined);
|
|
Assert.equal(null, Glean.testOnly.cheesyString.testGetValue());
|
|
|
|
const value = "a cheesy string!";
|
|
Glean.testOnly.cheesyString.set(value);
|
|
|
|
Assert.equal(value, Glean.testOnly.cheesyString.testGetValue("test-ping"));
|
|
});
|
|
|
|
add_task(async function test_fog_string_list_works() {
|
|
const value = "a cheesy string!";
|
|
const value2 = "a cheesier string!";
|
|
const value3 = "the cheeziest of strings.";
|
|
|
|
const cheeseList = [value, value2];
|
|
Glean.testOnly.cheesyStringList.set(cheeseList);
|
|
|
|
let val = Glean.testOnly.cheesyStringList.testGetValue();
|
|
// Note: This is incredibly fragile and will break if we ever rearrange items
|
|
// in the string list.
|
|
Assert.deepEqual(cheeseList, val);
|
|
|
|
Glean.testOnly.cheesyStringList.add(value3);
|
|
Assert.ok(Glean.testOnly.cheesyStringList.testGetValue().includes(value3));
|
|
});
|
|
|
|
add_task(async function test_fog_timespan_works() {
|
|
Glean.testOnly.canWeTimeIt.start();
|
|
Glean.testOnly.canWeTimeIt.cancel();
|
|
Assert.equal(undefined, Glean.testOnly.canWeTimeIt.testGetValue());
|
|
|
|
// We start, briefly sleep and then stop.
|
|
// That guarantees some time to measure.
|
|
Glean.testOnly.canWeTimeIt.start();
|
|
await sleep(10);
|
|
Glean.testOnly.canWeTimeIt.stop();
|
|
|
|
Assert.ok(Glean.testOnly.canWeTimeIt.testGetValue("test-ping") > 0);
|
|
});
|
|
|
|
add_task(async function test_fog_timespan_throws_on_stop_wout_start() {
|
|
Glean.testOnly.canWeTimeIt.stop();
|
|
Assert.throws(
|
|
() => Glean.testOnly.canWeTimeIt.testGetValue(),
|
|
/DataError/,
|
|
"Should throw because stop was called without start."
|
|
);
|
|
});
|
|
|
|
add_task(async function test_fog_uuid_works() {
|
|
const kTestUuid = "decafdec-afde-cafd-ecaf-decafdecafde";
|
|
Glean.testOnly.whatIdIt.set(kTestUuid);
|
|
Assert.equal(kTestUuid, Glean.testOnly.whatIdIt.testGetValue("test-ping"));
|
|
|
|
Glean.testOnly.whatIdIt.generateAndSet();
|
|
// Since we generate v4 UUIDs, and the first character of the third group
|
|
// isn't 4, this won't ever collide with kTestUuid.
|
|
Assert.notEqual(kTestUuid, Glean.testOnly.whatIdIt.testGetValue("test-ping"));
|
|
});
|
|
|
|
add_task(function test_fog_datetime_works() {
|
|
const value = new Date("2020-06-11T12:00:00");
|
|
|
|
Glean.testOnly.whatADate.set(value.getTime() * 1000);
|
|
|
|
const received = Glean.testOnly.whatADate.testGetValue("test-ping");
|
|
Assert.equal(received.getTime(), value.getTime());
|
|
});
|
|
|
|
add_task(function test_fog_boolean_works() {
|
|
Glean.testOnly.canWeFlagIt.set(false);
|
|
Assert.equal(false, Glean.testOnly.canWeFlagIt.testGetValue("test-ping"));
|
|
// While you're here, might as well test that the ping name's optional.
|
|
Assert.equal(false, Glean.testOnly.canWeFlagIt.testGetValue());
|
|
});
|
|
|
|
add_task(async function test_fog_event_works() {
|
|
Glean.testOnlyIpc.noExtraEvent.record();
|
|
var events = Glean.testOnlyIpc.noExtraEvent.testGetValue();
|
|
Assert.equal(1, events.length);
|
|
Assert.equal("test_only.ipc", events[0].category);
|
|
Assert.equal("no_extra_event", events[0].name);
|
|
|
|
let extra = { extra1: "can set extras", extra2: "passing more data" };
|
|
Glean.testOnlyIpc.anEvent.record(extra);
|
|
events = Glean.testOnlyIpc.anEvent.testGetValue();
|
|
Assert.equal(1, events.length);
|
|
Assert.equal("test_only.ipc", events[0].category);
|
|
Assert.equal("an_event", events[0].name);
|
|
Assert.deepEqual(extra, events[0].extra);
|
|
|
|
// Corner case: Event with extra with `undefined` value.
|
|
// Should pretend that extra key isn't there.
|
|
extra = { extra1: undefined, extra2: "defined" };
|
|
Glean.testOnlyIpc.anEvent.record(extra);
|
|
events = Glean.testOnlyIpc.anEvent.testGetValue();
|
|
Assert.equal(2, events.length);
|
|
Assert.deepEqual({ extra2: "defined" }, events[1].extra);
|
|
|
|
let extra2 = {
|
|
extra1: "can set extras",
|
|
extra2: 37,
|
|
extra3_longer_name: false,
|
|
};
|
|
Glean.testOnlyIpc.eventWithExtra.record(extra2);
|
|
events = Glean.testOnlyIpc.eventWithExtra.testGetValue();
|
|
Assert.equal(1, events.length);
|
|
Assert.equal("test_only.ipc", events[0].category);
|
|
Assert.equal("event_with_extra", events[0].name);
|
|
let expectedExtra = {
|
|
extra1: "can set extras",
|
|
extra2: "37",
|
|
extra3_longer_name: "false",
|
|
};
|
|
Assert.deepEqual(expectedExtra, events[0].extra);
|
|
|
|
// camelCase extras work.
|
|
let extra5 = {
|
|
extra4CamelCase: false,
|
|
};
|
|
Glean.testOnlyIpc.eventWithExtra.record(extra5);
|
|
events = Glean.testOnlyIpc.eventWithExtra.testGetValue();
|
|
Assert.equal(2, events.length, "Recorded one event too many.");
|
|
expectedExtra = {
|
|
extra4CamelCase: "false",
|
|
};
|
|
Assert.deepEqual(expectedExtra, events[1].extra);
|
|
|
|
// Passing `null` works.
|
|
Glean.testOnlyIpc.eventWithExtra.record(null);
|
|
events = Glean.testOnlyIpc.eventWithExtra.testGetValue();
|
|
Assert.equal(3, events.length, "Recorded another event.");
|
|
Assert.equal(events[2].extra, null);
|
|
|
|
// Invalid extra keys don't crash, the event is not recorded,
|
|
// but an error is recorded.
|
|
let extra3 = {
|
|
extra1_nonexistent_extra: "this does not crash",
|
|
};
|
|
Glean.testOnlyIpc.eventWithExtra.record(extra3);
|
|
Assert.throws(
|
|
() => Glean.testOnlyIpc.eventWithExtra.testGetValue(),
|
|
/DataError/,
|
|
"Should throw because of a recording error."
|
|
);
|
|
|
|
// Supplying extras when there aren't any defined results in the event not
|
|
// being recorded, but an error is.
|
|
Glean.testOnlyIpc.noExtraEvent.record(extra3);
|
|
Assert.throws(
|
|
() => Glean.testOnlyIpc.eventWithExtra.testGetValue(),
|
|
/DataError/,
|
|
"Should throw because of a recording error."
|
|
);
|
|
});
|
|
|
|
add_task(async function test_fog_memory_distribution_works() {
|
|
Glean.testOnly.doYouRemember.accumulate(7);
|
|
Glean.testOnly.doYouRemember.accumulate(17);
|
|
|
|
let data = Glean.testOnly.doYouRemember.testGetValue("test-ping");
|
|
Assert.equal(2, data.count, "Count of entries is correct");
|
|
// `data.sum` is in bytes, but the metric is in MB.
|
|
Assert.equal(24 * 1024 * 1024, data.sum, "Sum's correct");
|
|
for (let [bucket, count] of Object.entries(data.values)) {
|
|
Assert.ok(
|
|
count == 0 || (count == 1 && (bucket == 17520006 || bucket == 7053950)),
|
|
"Only two buckets have a sample"
|
|
);
|
|
}
|
|
});
|
|
|
|
add_task(async function test_fog_custom_distribution_works() {
|
|
Glean.testOnlyIpc.aCustomDist.accumulateSamples([7, 268435458]);
|
|
|
|
let data = Glean.testOnlyIpc.aCustomDist.testGetValue("test-ping");
|
|
Assert.equal(2, data.count, "Count of entries is correct");
|
|
Assert.equal(7 + 268435458, data.sum, "Sum's correct");
|
|
for (let [bucket, count] of Object.entries(data.values)) {
|
|
Assert.ok(
|
|
count == 0 || (count == 1 && (bucket == 1 || bucket == 268435456)),
|
|
`Only two buckets have a sample ${bucket} ${count}`
|
|
);
|
|
}
|
|
|
|
// Negative values will not be recorded, instead an error is recorded.
|
|
Glean.testOnlyIpc.aCustomDist.accumulateSamples([-7]);
|
|
Assert.throws(
|
|
() => Glean.testOnlyIpc.aCustomDist.testGetValue(),
|
|
/DataError/
|
|
);
|
|
});
|
|
|
|
add_task(function test_fog_custom_pings() {
|
|
Assert.ok("onePingOnly" in GleanPings);
|
|
let submitted = false;
|
|
Glean.testOnly.onePingOneBool.set(false);
|
|
GleanPings.onePingOnly.testBeforeNextSubmit(() => {
|
|
submitted = true;
|
|
Assert.equal(false, Glean.testOnly.onePingOneBool.testGetValue());
|
|
});
|
|
GleanPings.onePingOnly.submit();
|
|
Assert.ok(submitted, "Ping was submitted, callback was called.");
|
|
});
|
|
|
|
add_task(function test_recursive_testBeforeNextSubmit() {
|
|
Assert.ok("onePingOnly" in GleanPings);
|
|
let submitted = 0;
|
|
let rec = () => {
|
|
submitted++;
|
|
GleanPings.onePingOnly.testBeforeNextSubmit(rec);
|
|
};
|
|
GleanPings.onePingOnly.testBeforeNextSubmit(rec);
|
|
GleanPings.onePingOnly.submit();
|
|
GleanPings.onePingOnly.submit();
|
|
GleanPings.onePingOnly.submit();
|
|
Assert.equal(3, submitted, "Ping was submitted 3 times");
|
|
// Be kind and remove the callback.
|
|
GleanPings.onePingOnly.testBeforeNextSubmit(() => {});
|
|
});
|
|
|
|
add_task(function test_testBeforeNextSubmit_error() {
|
|
Assert.ok("onePingOnly" in GleanPings);
|
|
|
|
let submitted = false;
|
|
GleanPings.onePingOnly.testBeforeNextSubmit(() => {
|
|
submitted = true;
|
|
throw new Error("oh no");
|
|
});
|
|
|
|
Assert.throws(
|
|
() => GleanPings.onePingOnly.submit(),
|
|
/oh no/,
|
|
"testBeforeNextSubmit error thrown from submit"
|
|
);
|
|
|
|
Assert.ok(submitted, "Did submit ping");
|
|
});
|
|
|
|
add_task(async function test_testSubmission() {
|
|
Assert.ok("onePingOnly" in GleanPings);
|
|
|
|
let submitReason = null;
|
|
await GleanPings.onePingOnly.testSubmission(
|
|
reason => (submitReason = reason),
|
|
() => GleanPings.onePingOnly.submit("raison d'être")
|
|
);
|
|
|
|
Assert.equal(
|
|
submitReason,
|
|
"raison d'être",
|
|
"ping callback called with correct reason"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_testSubmission_async() {
|
|
Assert.ok("onePingOnly" in GleanPings);
|
|
|
|
const orderOfOperations = [];
|
|
|
|
// We are going to intentionally block submission so that we can delay it
|
|
// until after testSubmission returns a promise.
|
|
const blocker = Promise.withResolvers();
|
|
|
|
const testPromise = GleanPings.onePingOnly.testSubmission(
|
|
() => orderOfOperations.push("test-callback"),
|
|
async () => {
|
|
orderOfOperations.push("await-blocker");
|
|
await blocker.promise;
|
|
orderOfOperations.push("submit");
|
|
GleanPings.onePingOnly.submit();
|
|
}
|
|
);
|
|
orderOfOperations.push("test-submission-queued");
|
|
|
|
blocker.resolve();
|
|
await testPromise;
|
|
|
|
Assert.deepEqual(orderOfOperations, [
|
|
"await-blocker",
|
|
"test-submission-queued",
|
|
"submit",
|
|
"test-callback",
|
|
]);
|
|
});
|
|
|
|
add_task(async function test_testSubmission_error() {
|
|
Assert.ok("onePingOnly" in GleanPings);
|
|
|
|
await Assert.rejects(
|
|
GleanPings.onePingOnly.testSubmission(
|
|
() => {
|
|
throw new Error("uh oh");
|
|
},
|
|
() => GleanPings.onePingOnly.submit()
|
|
),
|
|
/NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS/,
|
|
"testSubmission callback threw"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_testSubmission_unsubmitted() {
|
|
Assert.ok("onePingOnly" in GleanPings);
|
|
|
|
let submitted = false;
|
|
await Assert.rejects(
|
|
GleanPings.onePingOnly.testSubmission(
|
|
() => (submitted = true),
|
|
() => {}
|
|
),
|
|
/Ping did not submit immediately/,
|
|
"Threw immediately because the ping did not submit"
|
|
);
|
|
|
|
Assert.ok(!submitted, "callback not called");
|
|
});
|
|
|
|
add_task(async function test_testSubmission_timeout() {
|
|
Assert.ok("onePingOnly" in GleanPings);
|
|
|
|
let submitted = false;
|
|
await Assert.rejects(
|
|
GleanPings.onePingOnly.testSubmission(
|
|
() => (submitted = true),
|
|
() => {},
|
|
1
|
|
),
|
|
/Ping was not submitted after timeout/,
|
|
"Threw after a timeout"
|
|
);
|
|
|
|
Assert.ok(!submitted, "callback not called");
|
|
});
|
|
|
|
add_task(async function test_fog_timing_distribution_works() {
|
|
let t1 = Glean.testOnly.whatTimeIsIt.start();
|
|
let t2 = Glean.testOnly.whatTimeIsIt.start();
|
|
|
|
await sleep(5);
|
|
|
|
let t3 = Glean.testOnly.whatTimeIsIt.start();
|
|
Glean.testOnly.whatTimeIsIt.cancel(t1);
|
|
|
|
await sleep(5);
|
|
|
|
Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t2); // 10ms
|
|
Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t3); // 5ms
|
|
// samples are measured in microseconds, since that's the unit listed in metrics.yaml
|
|
Glean.testOnly.whatTimeIsIt.accumulateSingleSample(5000); // 5ms
|
|
Glean.testOnly.whatTimeIsIt.accumulateSamples([2000, 8000]); // 10ms
|
|
|
|
let data = Glean.testOnly.whatTimeIsIt.testGetValue();
|
|
|
|
// Cancelled timers should not be counted.
|
|
Assert.equal(5, data.count, "Count of entries is correct");
|
|
|
|
const NANOS_IN_MILLIS = 1e6;
|
|
// bug 1701949 - Sleep gets close, but sometimes doesn't wait long enough.
|
|
const EPSILON = 40000;
|
|
|
|
// Variance in timing makes getting the sum impossible to know.
|
|
Assert.greater(data.sum, 30 * NANOS_IN_MILLIS - EPSILON);
|
|
|
|
// No guarantees from timers means no guarantees on buckets.
|
|
// But we can guarantee it's only five samples.
|
|
Assert.equal(
|
|
5,
|
|
Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0),
|
|
"Only five buckets with samples"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_fog_labels_conform() {
|
|
Glean.testOnly.mabelsLabelMaker.singleword.set("portmanteau");
|
|
Assert.equal(
|
|
"portmanteau",
|
|
Glean.testOnly.mabelsLabelMaker.singleword.testGetValue()
|
|
);
|
|
Glean.testOnly.mabelsLabelMaker.snake_case.set("snek");
|
|
Assert.equal(
|
|
"snek",
|
|
Glean.testOnly.mabelsLabelMaker.snake_case.testGetValue()
|
|
);
|
|
Glean.testOnly.mabelsLabelMaker["dash-character"].set("Dash Rendar");
|
|
Assert.equal(
|
|
"Dash Rendar",
|
|
Glean.testOnly.mabelsLabelMaker["dash-character"].testGetValue()
|
|
);
|
|
Glean.testOnly.mabelsLabelMaker["dot.separated"].set("dot product");
|
|
Assert.equal(
|
|
"dot product",
|
|
Glean.testOnly.mabelsLabelMaker["dot.separated"].testGetValue()
|
|
);
|
|
Glean.testOnly.mabelsLabelMaker.camelCase.set("wednesday");
|
|
Assert.equal(
|
|
"wednesday",
|
|
Glean.testOnly.mabelsLabelMaker.camelCase.testGetValue()
|
|
);
|
|
const veryLong = "1".repeat(112);
|
|
Glean.testOnly.mabelsLabelMaker[veryLong].set("seventy-two");
|
|
Assert.throws(
|
|
() => Glean.testOnly.mabelsLabelMaker[veryLong].testGetValue(),
|
|
/DataError/,
|
|
"Should throw because of an invalid label."
|
|
);
|
|
// This test should _now_ throw because we are calling data after an invalid
|
|
// label has been set.
|
|
Assert.throws(
|
|
() => Glean.testOnly.mabelsLabelMaker["dot.separated"].testGetValue(),
|
|
/DataError/,
|
|
"Should throw because of an invalid label."
|
|
);
|
|
});
|
|
|
|
add_task(async function test_fog_labeled_boolean_works() {
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.mabelsLikeBalloons.at_parties.testGetValue(),
|
|
"New labels with no values should return undefined"
|
|
);
|
|
Glean.testOnly.mabelsLikeBalloons.at_parties.set(true);
|
|
Glean.testOnly.mabelsLikeBalloons.at_funerals.set(false);
|
|
Assert.equal(
|
|
true,
|
|
Glean.testOnly.mabelsLikeBalloons.at_parties.testGetValue()
|
|
);
|
|
Assert.equal(
|
|
false,
|
|
Glean.testOnly.mabelsLikeBalloons.at_funerals.testGetValue()
|
|
);
|
|
// What about invalid/__other__?
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.mabelsLikeBalloons.__other__.testGetValue()
|
|
);
|
|
Glean.testOnly.mabelsLikeBalloons["1".repeat(112)].set(true);
|
|
Assert.throws(
|
|
() => Glean.testOnly.mabelsLikeBalloons.__other__.testGetValue(),
|
|
/DataError/,
|
|
"Should throw because of a recording error."
|
|
);
|
|
});
|
|
|
|
add_task(async function test_fog_labeled_counter_works() {
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.mabelsKitchenCounters.near_the_sink.testGetValue(),
|
|
"New labels with no values should return undefined"
|
|
);
|
|
Glean.testOnly.mabelsKitchenCounters.near_the_sink.add(1);
|
|
Glean.testOnly.mabelsKitchenCounters.with_junk_on_them.add(2);
|
|
Assert.equal(
|
|
1,
|
|
Glean.testOnly.mabelsKitchenCounters.near_the_sink.testGetValue()
|
|
);
|
|
Assert.equal(
|
|
2,
|
|
Glean.testOnly.mabelsKitchenCounters.with_junk_on_them.testGetValue()
|
|
);
|
|
// What about invalid/__other__?
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.mabelsKitchenCounters.__other__.testGetValue()
|
|
);
|
|
Glean.testOnly.mabelsKitchenCounters["1".repeat(112)].add(1);
|
|
Assert.throws(
|
|
() => Glean.testOnly.mabelsKitchenCounters.__other__.testGetValue(),
|
|
/DataError/,
|
|
"Should throw because of a recording error."
|
|
);
|
|
});
|
|
|
|
add_task(async function test_fog_labeled_string_works() {
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.mabelsBalloonStrings.colour_of_99.testGetValue(),
|
|
"New labels with no values should return undefined"
|
|
);
|
|
Glean.testOnly.mabelsBalloonStrings.colour_of_99.set("crimson");
|
|
Glean.testOnly.mabelsBalloonStrings.string_lengths.set("various");
|
|
Assert.equal(
|
|
"crimson",
|
|
Glean.testOnly.mabelsBalloonStrings.colour_of_99.testGetValue()
|
|
);
|
|
Assert.equal(
|
|
"various",
|
|
Glean.testOnly.mabelsBalloonStrings.string_lengths.testGetValue()
|
|
);
|
|
// What about invalid/__other__?
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.mabelsBalloonStrings.__other__.testGetValue()
|
|
);
|
|
Glean.testOnly.mabelsBalloonStrings["1".repeat(112)].set("valid");
|
|
Assert.throws(
|
|
() => Glean.testOnly.mabelsBalloonStrings.__other__.testGetValue(),
|
|
/DataError/
|
|
);
|
|
});
|
|
|
|
add_task(function test_fog_quantity_works() {
|
|
Glean.testOnly.meaningOfLife.set(42);
|
|
Assert.equal(42, Glean.testOnly.meaningOfLife.testGetValue());
|
|
});
|
|
|
|
add_task(function test_fog_rate_works() {
|
|
// 1) Standard rate with internal denominator
|
|
Glean.testOnlyIpc.irate.addToNumerator(22);
|
|
Glean.testOnlyIpc.irate.addToDenominator(7);
|
|
Assert.deepEqual(
|
|
{ numerator: 22, denominator: 7 },
|
|
Glean.testOnlyIpc.irate.testGetValue()
|
|
);
|
|
|
|
// 2) Rate with external denominator
|
|
Glean.testOnlyIpc.anExternalDenominator.add(11);
|
|
Glean.testOnlyIpc.rateWithExternalDenominator.addToNumerator(121);
|
|
Assert.equal(11, Glean.testOnlyIpc.anExternalDenominator.testGetValue());
|
|
Assert.deepEqual(
|
|
{ numerator: 121, denominator: 11 },
|
|
Glean.testOnlyIpc.rateWithExternalDenominator.testGetValue()
|
|
);
|
|
});
|
|
|
|
add_task(async function test_fog_url_works() {
|
|
const value = "https://www.example.com/fog";
|
|
Glean.testOnlyIpc.aUrl.set(value);
|
|
|
|
Assert.equal(value, Glean.testOnlyIpc.aUrl.testGetValue("test-ping"));
|
|
});
|
|
|
|
add_task(async function test_fog_text_works() {
|
|
const value =
|
|
"Before the risin' sun, we fly, So many roads to choose, We'll start out walkin' and learn to run, (We've only just begun)";
|
|
Glean.testOnlyIpc.aText.set(value);
|
|
|
|
let rslt = Glean.testOnlyIpc.aText.testGetValue();
|
|
|
|
Assert.equal(value, rslt);
|
|
|
|
Assert.equal(121, rslt.length);
|
|
});
|
|
|
|
add_task(async function test_fog_text_works_unusual_character() {
|
|
const value =
|
|
"The secret to Dominique Ansel's viennoiserie is the use of Isigny Sainte-Mère butter and Les Grands Moulins de Paris flour";
|
|
Glean.testOnlyIpc.aText.set(value);
|
|
|
|
let rslt = Glean.testOnlyIpc.aText.testGetValue();
|
|
|
|
Assert.equal(value, rslt);
|
|
|
|
Assert.greater(rslt.length, 100);
|
|
});
|
|
|
|
add_task(async function test_fog_object_works() {
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.balloons.testGetValue(),
|
|
"No object stored"
|
|
);
|
|
|
|
// Can't store not-objects.
|
|
let invalidValues = [1, "str", false, undefined, null, NaN, Infinity];
|
|
for (let value of invalidValues) {
|
|
Assert.throws(
|
|
() => Glean.testOnly.balloons.set(value),
|
|
/is not an object/,
|
|
"Should throw a type error"
|
|
);
|
|
}
|
|
|
|
// No invalid value will be stored.
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.balloons.testGetValue(),
|
|
"No object stored"
|
|
);
|
|
|
|
// `JS_Stringify` internally throws
|
|
// an `TypeError: cyclic object value` exception.
|
|
// That's cleared and `set` should not throw on it.
|
|
// This eventually should log a proper error in Glean.
|
|
let selfref = {};
|
|
selfref.a = selfref;
|
|
Glean.testOnly.balloons.set(selfref);
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.balloons.testGetValue(),
|
|
"No object stored"
|
|
);
|
|
|
|
let balloons = [
|
|
{ colour: "red", diameter: 5 },
|
|
{ colour: "blue", diameter: 7 },
|
|
{ colour: "orange" },
|
|
];
|
|
Glean.testOnly.balloons.set(balloons);
|
|
|
|
let result = Glean.testOnly.balloons.testGetValue();
|
|
let expected = [
|
|
{ colour: "red", diameter: 5 },
|
|
{ colour: "blue", diameter: 7 },
|
|
{ colour: "orange" },
|
|
];
|
|
Assert.deepEqual(expected, result);
|
|
});
|
|
|
|
add_task(
|
|
// FIXME(bug 1947194): JOG object metrics don't do schema validation yet
|
|
{
|
|
skip_if: () =>
|
|
Services.prefs.getBoolPref("telemetry.fog.artifact_build", false),
|
|
},
|
|
async function test_fog_object_verifies_structure() {
|
|
// These values are coerced to null or removed.
|
|
let balloons = [
|
|
{ colour: "inf", diameter: Infinity },
|
|
{ colour: "negative-inf", diameter: -1 / 0 },
|
|
{ colour: "nan", diameter: NaN },
|
|
{ colour: "undef", diameter: undefined },
|
|
];
|
|
Glean.testOnly.balloons.set(balloons);
|
|
let result = Glean.testOnly.balloons.testGetValue();
|
|
let expected = [
|
|
{ colour: "inf" },
|
|
{ colour: "negative-inf" },
|
|
{ colour: "nan" },
|
|
{ colour: "undef" },
|
|
];
|
|
Assert.deepEqual(expected, result);
|
|
|
|
// colour != color.
|
|
let invalid = [{ color: "orange" }, { color: "red", diameter: "small" }];
|
|
Glean.testOnly.balloons.set(invalid);
|
|
Assert.throws(
|
|
() => Glean.testOnly.balloons.testGetValue(),
|
|
/invalid_value/,
|
|
"Should throw because last object was invalid."
|
|
);
|
|
|
|
Services.fog.testResetFOG();
|
|
// set again to ensure it's stored
|
|
balloons = [
|
|
{ colour: "red", diameter: 5 },
|
|
{ colour: "blue", diameter: 7 },
|
|
];
|
|
Glean.testOnly.balloons.set(balloons);
|
|
result = Glean.testOnly.balloons.testGetValue();
|
|
Assert.deepEqual(balloons, result);
|
|
|
|
invalid = [{ colour: "red", diameter: 5, extra: "field" }];
|
|
Glean.testOnly.balloons.set(invalid);
|
|
Assert.throws(
|
|
() => Glean.testOnly.balloons.testGetValue(),
|
|
/invalid_value/,
|
|
"Should throw because last object was invalid."
|
|
);
|
|
}
|
|
);
|
|
|
|
add_task(async function test_fog_complex_object_works() {
|
|
if (!Glean.testOnly.crashStack) {
|
|
// FIXME(bug 1883857): object metric type not available, e.g. in artifact builds.
|
|
// Skipping this test.
|
|
return;
|
|
}
|
|
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.crashStack.testGetValue(),
|
|
"No object stored"
|
|
);
|
|
|
|
Glean.testOnly.crashStack.set({});
|
|
let result = Glean.testOnly.crashStack.testGetValue();
|
|
Assert.deepEqual({}, result);
|
|
|
|
let stack = {
|
|
status: "OK",
|
|
crash_info: {
|
|
typ: "main",
|
|
address: "0xf001ba11",
|
|
crashing_thread: 1,
|
|
},
|
|
main_module: 0,
|
|
modules: [
|
|
{
|
|
base_addr: "0x00000000",
|
|
end_addr: "0x00004000",
|
|
},
|
|
],
|
|
};
|
|
|
|
Glean.testOnly.crashStack.set(stack);
|
|
result = Glean.testOnly.crashStack.testGetValue();
|
|
Assert.deepEqual(stack, result);
|
|
|
|
stack = {
|
|
status: "OK",
|
|
modules: [
|
|
{
|
|
base_addr: "0x00000000",
|
|
end_addr: "0x00004000",
|
|
},
|
|
],
|
|
};
|
|
Glean.testOnly.crashStack.set(stack);
|
|
result = Glean.testOnly.crashStack.testGetValue();
|
|
Assert.deepEqual(stack, result);
|
|
|
|
// FIXME(bug 1947194): JOG object metrics don't do schema validation yet
|
|
if (!Services.prefs.getBoolPref("telemetry.fog.artifact_build", false)) {
|
|
stack = {
|
|
status: "OK",
|
|
modules: [],
|
|
};
|
|
Glean.testOnly.crashStack.set(stack);
|
|
result = Glean.testOnly.crashStack.testGetValue();
|
|
Assert.deepEqual({ status: "OK" }, result);
|
|
|
|
stack = {
|
|
status: "OK",
|
|
};
|
|
Glean.testOnly.crashStack.set(stack);
|
|
result = Glean.testOnly.crashStack.testGetValue();
|
|
Assert.deepEqual(stack, result);
|
|
}
|
|
});
|
|
|
|
add_task(
|
|
// FIXME(1898464): ride-along pings are not handled correctly in artifact builds.
|
|
{
|
|
skip_if: () =>
|
|
Services.prefs.getBoolPref("telemetry.fog.artifact_build", false),
|
|
},
|
|
function test_fog_ride_along_pings() {
|
|
Assert.equal(null, Glean.testOnly.badCode.testGetValue("test-ping"));
|
|
Assert.equal(null, Glean.testOnly.badCode.testGetValue("ride-along-ping"));
|
|
|
|
Glean.testOnly.badCode.add(37);
|
|
Assert.equal(37, Glean.testOnly.badCode.testGetValue("test-ping"));
|
|
Assert.equal(37, Glean.testOnly.badCode.testGetValue("ride-along-ping"));
|
|
|
|
let testPingSubmitted = false;
|
|
|
|
GleanPings.testPing.testBeforeNextSubmit(() => {
|
|
testPingSubmitted = true;
|
|
});
|
|
// FIXME(bug 1896356):
|
|
// We can't use `testBeforeNextSubmit` for `ride-along-ping`
|
|
// because it's triggered internally, but the callback would only be available
|
|
// in the C++ bits, not in the internal Rust parts.
|
|
|
|
// Submit only a single ping, the other will ride along.
|
|
GleanPings.testPing.submit();
|
|
|
|
Assert.ok(
|
|
testPingSubmitted,
|
|
"Test ping was submitted, callback was called."
|
|
);
|
|
|
|
// Both pings have been submitted, so the values should be cleared.
|
|
Assert.equal(null, Glean.testOnly.badCode.testGetValue("test-ping"));
|
|
Assert.equal(null, Glean.testOnly.badCode.testGetValue("ride-along-ping"));
|
|
}
|
|
);
|
|
|
|
add_task(async function test_fog_labeled_custom_distribution_works() {
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.mabelsCustomLabelLengths.monospace.testGetValue(),
|
|
"New labels with no values should return undefined"
|
|
);
|
|
Glean.testOnly.mabelsCustomLabelLengths.monospace.accumulateSamples([1, 42]);
|
|
Glean.testOnly.mabelsCustomLabelLengths.sanserif.accumulateSingleSample(13);
|
|
let monospace =
|
|
Glean.testOnly.mabelsCustomLabelLengths.monospace.testGetValue();
|
|
Assert.equal(2, monospace.count);
|
|
Assert.equal(43, monospace.sum);
|
|
Assert.deepEqual({ 0: 0, 1: 2, 268435456: 0 }, monospace.values);
|
|
let sanserif =
|
|
Glean.testOnly.mabelsCustomLabelLengths.sanserif.testGetValue();
|
|
Assert.equal(1, sanserif.count);
|
|
Assert.equal(13, sanserif.sum);
|
|
Assert.deepEqual({ 0: 0, 1: 1, 268435456: 0 }, sanserif.values);
|
|
// What about invalid/__other__?
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.mabelsCustomLabelLengths.__other__.testGetValue()
|
|
);
|
|
Glean.testOnly.mabelsCustomLabelLengths[
|
|
"1".repeat(112)
|
|
].accumulateSingleSample(3);
|
|
Assert.throws(
|
|
() => Glean.testOnly.mabelsCustomLabelLengths.__other__.testGetValue(),
|
|
/DataError/
|
|
);
|
|
});
|
|
|
|
add_task(async function test_fog_labeled_memory_distribution_works() {
|
|
Glean.testOnly.whatDoYouRemember.twenty_years_ago.accumulate(7);
|
|
Glean.testOnly.whatDoYouRemember.twenty_years_ago.accumulate(17);
|
|
|
|
let data = Glean.testOnly.whatDoYouRemember.twenty_years_ago.testGetValue();
|
|
Assert.equal(2, data.count, "Count of entries is correct");
|
|
// `data.sum` is in bytes, but the metric is in MB.
|
|
Assert.equal(24 * 1024 * 1024, data.sum, "Sum's correct");
|
|
for (let [bucket, count] of Object.entries(data.values)) {
|
|
Assert.ok(
|
|
count == 0 || (count == 1 && (bucket == 17520006 || bucket == 7053950)),
|
|
"Only two buckets have a sample"
|
|
);
|
|
}
|
|
});
|
|
|
|
add_task(async function test_labeled_timing_distribution_works() {
|
|
let t1 = Glean.testOnly.whereHasTheTimeGone.west.start();
|
|
let t2 = Glean.testOnly.whereHasTheTimeGone.west.start();
|
|
|
|
await sleep(5);
|
|
|
|
let t3 = Glean.testOnly.whereHasTheTimeGone.west.start();
|
|
Glean.testOnly.whereHasTheTimeGone.west.cancel(t1);
|
|
|
|
await sleep(5);
|
|
|
|
Glean.testOnly.whereHasTheTimeGone.west.stopAndAccumulate(t2); // 10ms
|
|
Glean.testOnly.whereHasTheTimeGone.west.stopAndAccumulate(t3); // 5ms
|
|
|
|
let data = Glean.testOnly.whereHasTheTimeGone.west.testGetValue();
|
|
|
|
// Cancelled timers should not be counted.
|
|
Assert.equal(2, data.count, "Count of entries is correct");
|
|
|
|
const NANOS_IN_MILLIS = 1e6;
|
|
// bug 1701949 - Sleep gets close, but sometimes doesn't wait long enough.
|
|
const EPSILON = 40000;
|
|
|
|
// Variance in timing makes getting the sum impossible to know.
|
|
Assert.greater(data.sum, 15 * NANOS_IN_MILLIS - EPSILON);
|
|
|
|
// No guarantees from timers means no guarantees on buckets.
|
|
// But we can guarantee it's only two samples.
|
|
Assert.equal(
|
|
2,
|
|
Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0),
|
|
"Only two buckets with samples"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_fog_labeled_quantity_works() {
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.buttonJars.up.testGetValue(),
|
|
"New labels with no values should return undefined"
|
|
);
|
|
Glean.testOnly.buttonJars.up.set(2);
|
|
Glean.testOnly.buttonJars.curling.set(0);
|
|
Assert.equal(2, Glean.testOnly.buttonJars.up.testGetValue());
|
|
Assert.equal(0, Glean.testOnly.buttonJars.curling.testGetValue());
|
|
// What about invalid/__other__?
|
|
Assert.equal(undefined, Glean.testOnly.buttonJars.__other__.testGetValue());
|
|
Glean.testOnly.buttonJars["1".repeat(112)].set(0);
|
|
Assert.throws(
|
|
() => Glean.testOnly.buttonJars.__other__.testGetValue(),
|
|
/DataError/,
|
|
"Should throw because of a recording error."
|
|
);
|
|
});
|
|
|
|
add_task(async function test_submit_throws() {
|
|
GleanPings.onePingOnly.testBeforeNextSubmit(() => {
|
|
throw new Error("inside callback");
|
|
});
|
|
|
|
Assert.throws(
|
|
() => GleanPings.onePingOnly.submit(),
|
|
/inside callback/,
|
|
"Should throw inside callback"
|
|
);
|
|
});
|
|
|
|
add_task(function test_collection_disabled_pings_work() {
|
|
// This test should work equally for full builds and artifact builds.
|
|
|
|
Assert.ok("collectionDisabledPing" in GleanPings);
|
|
|
|
// collection-enabled=false pings are disabled by default.
|
|
// No data is collected for metrics going into that ping.
|
|
Glean.testOnly.collectionDisabledCounter.add(1);
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.collectionDisabledCounter.testGetValue()
|
|
);
|
|
|
|
// After enabling a ping we can record data into it
|
|
GleanPings.collectionDisabledPing.setEnabled(true);
|
|
Glean.testOnly.collectionDisabledCounter.add(2);
|
|
Assert.equal(2, Glean.testOnly.collectionDisabledCounter.testGetValue());
|
|
|
|
let submitted = false;
|
|
GleanPings.collectionDisabledPing.testBeforeNextSubmit(() => {
|
|
submitted = true;
|
|
Assert.equal(2, Glean.testOnly.collectionDisabledCounter.testGetValue());
|
|
});
|
|
GleanPings.collectionDisabledPing.submit();
|
|
Assert.ok(submitted, "Ping was submitted, callback was called.");
|
|
Assert.equal(
|
|
undefined,
|
|
Glean.testOnly.collectionDisabledCounter.testGetValue()
|
|
);
|
|
});
|