summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/tests/xpcshell
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/glean/tests/xpcshell')
-rw-r--r--toolkit/components/glean/tests/xpcshell/head.js152
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_GIFFT.js10
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js7
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_Glean.js118
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_GleanIPC.js5
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_JOG.js27
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_JOGIPC.js5
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_OHTTP.js32
-rw-r--r--toolkit/components/glean/tests/xpcshell/xpcshell.toml3
9 files changed, 325 insertions, 34 deletions
diff --git a/toolkit/components/glean/tests/xpcshell/head.js b/toolkit/components/glean/tests/xpcshell/head.js
index f42bd02822..eaf8fa9c61 100644
--- a/toolkit/components/glean/tests/xpcshell/head.js
+++ b/toolkit/components/glean/tests/xpcshell/head.js
@@ -4,3 +4,155 @@
const { XPCOMUtils } = ChromeUtils.importESModule(
"resource://gre/modules/XPCOMUtils.sys.mjs"
);
+
+ChromeUtils.defineESModuleGetters(this, {
+ HttpServer: "resource://testing-common/httpd.sys.mjs",
+ NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
+});
+
+const PingServer = {
+ _httpServer: null,
+ _started: false,
+ _defers: [Promise.withResolvers()],
+ _currentDeferred: 0,
+
+ get port() {
+ return this._httpServer.identity.primaryPort;
+ },
+
+ get host() {
+ return this._httpServer.identity.primaryHost;
+ },
+
+ get started() {
+ return this._started;
+ },
+
+ registerPingHandler(handler) {
+ this._httpServer.registerPrefixHandler("/submit/", handler);
+ },
+
+ resetPingHandler() {
+ this.registerPingHandler(request => {
+ let r = request;
+ console.trace(
+ `defaultPingHandler() - ${r.method} ${r.scheme}://${r.host}:${r.port}${r.path}`
+ );
+ let deferred = this._defers[this._defers.length - 1];
+ this._defers.push(Promise.withResolvers());
+ deferred.resolve(request);
+ });
+ },
+
+ start() {
+ this._httpServer = new HttpServer();
+ this._httpServer.start(-1);
+ this._started = true;
+ this.clearRequests();
+ this.resetPingHandler();
+ },
+
+ stop() {
+ return new Promise(resolve => {
+ this._httpServer.stop(resolve);
+ this._started = false;
+ });
+ },
+
+ clearRequests() {
+ this._defers = [Promise.withResolvers()];
+ this._currentDeferred = 0;
+ },
+
+ promiseNextRequest() {
+ const deferred = this._defers[this._currentDeferred++];
+ // Send the ping to the consumer on the next tick, so that the completion gets
+ // signaled to Telemetry.
+ return new Promise(r =>
+ Services.tm.dispatchToMainThread(() => r(deferred.promise))
+ );
+ },
+
+ promiseNextPing() {
+ return this.promiseNextRequest().then(request =>
+ decodeRequestPayload(request)
+ );
+ },
+
+ async promiseNextRequests(count) {
+ let results = [];
+ for (let i = 0; i < count; ++i) {
+ results.push(await this.promiseNextRequest());
+ }
+
+ return results;
+ },
+
+ promiseNextPings(count) {
+ return this.promiseNextRequests(count).then(requests => {
+ return Array.from(requests, decodeRequestPayload);
+ });
+ },
+};
+
+/**
+ * Decode the payload of an HTTP request into a ping.
+ *
+ * @param {object} request The data representing an HTTP request (nsIHttpRequest).
+ * @returns {object} The decoded ping payload.
+ */
+function decodeRequestPayload(request) {
+ let s = request.bodyInputStream;
+ let payload = null;
+
+ if (
+ request.hasHeader("content-encoding") &&
+ request.getHeader("content-encoding") == "gzip"
+ ) {
+ let observer = {
+ buffer: "",
+ onStreamComplete(loader, context, status, length, result) {
+ // String.fromCharCode can only deal with 500,000 characters
+ // at a time, so chunk the result into parts of that size.
+ const chunkSize = 500000;
+ for (let offset = 0; offset < result.length; offset += chunkSize) {
+ this.buffer += String.fromCharCode.apply(
+ String,
+ result.slice(offset, offset + chunkSize)
+ );
+ }
+ },
+ };
+
+ let scs = Cc["@mozilla.org/streamConverters;1"].getService(
+ Ci.nsIStreamConverterService
+ );
+ let listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance(
+ Ci.nsIStreamLoader
+ );
+ listener.init(observer);
+ let converter = scs.asyncConvertData(
+ "gzip",
+ "uncompressed",
+ listener,
+ null
+ );
+ converter.onStartRequest(null, null);
+ converter.onDataAvailable(null, s, 0, s.available());
+ converter.onStopRequest(null, null, null);
+ // TODO: nsIScriptableUnicodeConverter is deprecated
+ // But I can't figure out how else to ungzip bodyInputStream.
+ let unicodeConverter = Cc[
+ "@mozilla.org/intl/scriptableunicodeconverter"
+ ].createInstance(Ci.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = "UTF-8";
+ let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer);
+ utf8string += unicodeConverter.Finish();
+ payload = JSON.parse(utf8string);
+ } else {
+ let bytes = NetUtil.readInputStream(s, s.available());
+ payload = JSON.parse(new TextDecoder().decode(bytes));
+ }
+
+ return payload;
+}
diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFT.js b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js
index 015b4d4e38..8fcf755d3a 100644
--- a/toolkit/components/glean/tests/xpcshell/test_GIFFT.js
+++ b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js
@@ -176,10 +176,7 @@ add_task(async function test_gifft_timing_dist() {
// But we can guarantee it's only two samples.
Assert.equal(
2,
- Object.entries(data.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- ),
+ Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0),
"Only two buckets with samples"
);
@@ -188,10 +185,7 @@ add_task(async function test_gifft_timing_dist() {
Assert.greaterOrEqual(data.sum, 13, "Histogram's in milliseconds");
Assert.equal(
2,
- Object.entries(data.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- ),
+ Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0),
"Only two samples"
);
});
diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js
index a318d57b9c..ae4c48b6e8 100644
--- a/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js
+++ b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js
@@ -291,10 +291,7 @@ add_task(
// but we can assert there are only two samples.
Assert.equal(
2,
- Object.entries(times.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- )
+ Object.entries(times.values).reduce((acc, [, count]) => acc + count, 0)
);
const timingHist = histSnapshot.content.TELEMETRY_TEST_EXPONENTIAL;
Assert.greaterOrEqual(timingHist.sum, 13, "Histogram's in milliseconds.");
@@ -303,7 +300,7 @@ add_task(
Assert.equal(
2,
Object.entries(timingHist.values).reduce(
- (acc, [bucket, count]) => acc + count,
+ (acc, [, count]) => acc + count,
0
),
"Only two samples"
diff --git a/toolkit/components/glean/tests/xpcshell/test_Glean.js b/toolkit/components/glean/tests/xpcshell/test_Glean.js
index 8375c22a3e..18b450a69b 100644
--- a/toolkit/components/glean/tests/xpcshell/test_Glean.js
+++ b/toolkit/components/glean/tests/xpcshell/test_Glean.js
@@ -182,6 +182,7 @@ add_task(async function test_fog_memory_distribution_works() {
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)) {
@@ -196,6 +197,7 @@ add_task(async function test_fog_custom_distribution_works() {
Glean.testOnlyIpc.aCustomDist.accumulateSamples([7, 268435458]);
let data = Glean.testOnlyIpc.aCustomDist.testGetValue("store1");
+ 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(
@@ -216,7 +218,7 @@ add_task(function test_fog_custom_pings() {
Assert.ok("onePingOnly" in GleanPings);
let submitted = false;
Glean.testOnly.onePingOneBool.set(false);
- GleanPings.onePingOnly.testBeforeNextSubmit(reason => {
+ GleanPings.onePingOnly.testBeforeNextSubmit(() => {
submitted = true;
Assert.equal(false, Glean.testOnly.onePingOneBool.testGetValue());
});
@@ -227,7 +229,7 @@ add_task(function test_fog_custom_pings() {
add_task(function test_recursive_testBeforeNextSubmit() {
Assert.ok("onePingOnly" in GleanPings);
let submitted = 0;
- let rec = reason => {
+ let rec = () => {
submitted++;
GleanPings.onePingOnly.testBeforeNextSubmit(rec);
};
@@ -255,6 +257,10 @@ add_task(async function test_fog_timing_distribution_works() {
Glean.testOnly.whatTimeIsIt.stopAndAccumulate(t3); // 5ms
let data = Glean.testOnly.whatTimeIsIt.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;
@@ -266,10 +272,7 @@ add_task(async function test_fog_timing_distribution_works() {
// But we can guarantee it's only two samples.
Assert.equal(
2,
- Object.entries(data.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- ),
+ Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0),
"Only two buckets with samples"
);
});
@@ -456,3 +459,106 @@ add_task(async function test_fog_text_works_unusual_character() {
Assert.greater(rslt.length, 100);
});
+
+add_task(async function test_fog_object_works() {
+ if (!Glean.testOnly.balloons) {
+ // FIXME(bug 1883857): object metric type not available, e.g. in artifact builds.
+ // Skipping this test.
+ return;
+ }
+
+ 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", diameter: null },
+ ];
+ Assert.deepEqual(expected, result);
+
+ // These values are coerced to null or removed.
+ balloons = [
+ { colour: "inf", diameter: Infinity },
+ { colour: "negative-inf", diameter: -1 / 0 },
+ { colour: "nan", diameter: NaN },
+ { colour: "undef", diameter: undefined },
+ ];
+ Glean.testOnly.balloons.set(balloons);
+ result = Glean.testOnly.balloons.testGetValue();
+ expected = [
+ { colour: "inf", diameter: null },
+ { colour: "negative-inf", diameter: null },
+ { colour: "nan", diameter: null },
+ { colour: "undef", diameter: null },
+ ];
+ 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."
+ );
+});
diff --git a/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js b/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js
index 3d665c23b9..2db1c53218 100644
--- a/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js
+++ b/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js
@@ -127,10 +127,7 @@ add_task(
// but we can assert there are only two samples.
Assert.equal(
2,
- Object.entries(times.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- )
+ Object.entries(times.values).reduce((acc, [, count]) => acc + count, 0)
);
const mabelsCounters = Glean.testOnly.mabelsKitchenCounters;
diff --git a/toolkit/components/glean/tests/xpcshell/test_JOG.js b/toolkit/components/glean/tests/xpcshell/test_JOG.js
index 53b1d25962..00f3ded135 100644
--- a/toolkit/components/glean/tests/xpcshell/test_JOG.js
+++ b/toolkit/components/glean/tests/xpcshell/test_JOG.js
@@ -289,11 +289,11 @@ add_task(async function test_jog_custom_pings() {
`"ping"`,
false
);
- Services.fog.testRegisterRuntimePing("jog-ping", true, true, true, []);
+ Services.fog.testRegisterRuntimePing("jog-ping", true, true, true, true, []);
Assert.ok("jogPing" in GleanPings);
let submitted = false;
Glean.jogCat.jogPingBool.set(false);
- GleanPings.jogPing.testBeforeNextSubmit(reason => {
+ GleanPings.jogPing.testBeforeNextSubmit(() => {
submitted = true;
Assert.equal(false, Glean.jogCat.jogPingBool.testGetValue());
});
@@ -338,10 +338,7 @@ add_task(async function test_jog_timing_distribution_works() {
// But we can guarantee it's only two samples.
Assert.equal(
2,
- Object.entries(data.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- ),
+ Object.entries(data.values).reduce((acc, [, count]) => acc + count, 0),
"Only two buckets with samples"
);
});
@@ -642,7 +639,9 @@ add_task(function test_jog_dotted_categories_work() {
add_task(async function test_jog_ping_works() {
const kReason = "reason-1";
- Services.fog.testRegisterRuntimePing("my-ping", true, true, true, [kReason]);
+ Services.fog.testRegisterRuntimePing("my-ping", true, true, true, true, [
+ kReason,
+ ]);
let submitted = false;
GleanPings.myPing.testBeforeNextSubmit(reason => {
submitted = true;
@@ -652,6 +651,20 @@ add_task(async function test_jog_ping_works() {
Assert.ok(submitted, "Ping must have been submitted");
});
+add_task(async function test_jog_noinfo_ping_works() {
+ const kReason = "reason-1";
+ Services.fog.testRegisterRuntimePing("noinfo-ping", true, true, true, false, [
+ kReason,
+ ]);
+ let submitted = false;
+ GleanPings.noinfoPing.testBeforeNextSubmit(reason => {
+ submitted = true;
+ Assert.equal(kReason, reason);
+ });
+ GleanPings.noinfoPing.submit("reason-1");
+ Assert.ok(submitted, "Ping must have been submitted");
+});
+
add_task(function test_jog_name_collision() {
Assert.ok("aCounter" in Glean.testOnlyJog);
Assert.equal(undefined, Glean.testOnlyJog.aCounter.testGetValue());
diff --git a/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js b/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js
index 505dba6825..b43a448c53 100644
--- a/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js
+++ b/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js
@@ -226,10 +226,7 @@ add_task(
// but we can assert there are only two samples.
Assert.equal(
2,
- Object.entries(times.values).reduce(
- (acc, [bucket, count]) => acc + count,
- 0
- )
+ Object.entries(times.values).reduce((acc, [, count]) => acc + count, 0)
);
const labeledCounter = Glean.jogIpc.jogLabeledCounter;
diff --git a/toolkit/components/glean/tests/xpcshell/test_OHTTP.js b/toolkit/components/glean/tests/xpcshell/test_OHTTP.js
new file mode 100644
index 0000000000..76d1d2a67b
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_OHTTP.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async () => {
+ // FOG needs a profile dir to put its data in.
+ do_get_profile();
+
+ PingServer.start();
+
+ registerCleanupFunction(async () => {
+ await PingServer.stop();
+ });
+
+ Services.prefs.setIntPref(
+ "telemetry.fog.test.localhost_port",
+ PingServer.port
+ );
+ // Port pref needs to be set before init, so let's reset to reinit.
+ Services.fog.testResetFOG();
+});
+
+add_task(async () => {
+ PingServer.clearRequests();
+ GleanPings.testOhttpPing.submit();
+
+ let ping = await PingServer.promiseNextPing();
+
+ ok(!("client_info" in ping), "No client_info allowed.");
+ ok(!("ping_info" in ping), "No ping_info allowed.");
+});
diff --git a/toolkit/components/glean/tests/xpcshell/xpcshell.toml b/toolkit/components/glean/tests/xpcshell/xpcshell.toml
index 40b1a22bf4..22806a32e6 100644
--- a/toolkit/components/glean/tests/xpcshell/xpcshell.toml
+++ b/toolkit/components/glean/tests/xpcshell/xpcshell.toml
@@ -30,3 +30,6 @@ skip-if = ["os == 'android'"] # Server Knobs on mobile will be handled by the sp
["test_MillionQ.js"]
skip-if = ["os == 'android'"] # Android inits its own FOG, so the test won't work.
+
+["test_OHTTP.js"]
+skip-if = ["os == 'android'"] # FOG isn't responsible for monitoring prefs and controlling upload on Android