summaryrefslogtreecommitdiffstats
path: root/toolkit/components/glean/tests
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/components/glean/tests/browser/browser.ini19
-rw-r--r--toolkit/components/glean/tests/browser/browser_event_leak.js26
-rw-r--r--toolkit/components/glean/tests/browser/browser_fog_gmp.js84
-rw-r--r--toolkit/components/glean/tests/browser/browser_fog_gpu.js34
-rw-r--r--toolkit/components/glean/tests/browser/browser_fog_rdd.js63
-rw-r--r--toolkit/components/glean/tests/browser/browser_fog_socket.js33
-rw-r--r--toolkit/components/glean/tests/browser/browser_fog_utility.js37
-rw-r--r--toolkit/components/glean/tests/browser/browser_labeled_gifft.js50
-rw-r--r--toolkit/components/glean/tests/browser/empty_file.html9
-rw-r--r--toolkit/components/glean/tests/browser/small-shot.oggbin0 -> 6416 bytes
-rw-r--r--toolkit/components/glean/tests/gtest/Cargo.toml14
-rw-r--r--toolkit/components/glean/tests/gtest/FOGFixture.h23
-rw-r--r--toolkit/components/glean/tests/gtest/TestFog.cpp378
-rw-r--r--toolkit/components/glean/tests/gtest/moz.build16
-rw-r--r--toolkit/components/glean/tests/gtest/test.rs52
-rw-r--r--toolkit/components/glean/tests/moz.build16
-rw-r--r--toolkit/components/glean/tests/pytest/expect_helper.py34
-rw-r--r--toolkit/components/glean/tests/pytest/gifft_output_Event38
-rw-r--r--toolkit/components/glean/tests/pytest/gifft_output_EventExtra35
-rw-r--r--toolkit/components/glean/tests/pytest/gifft_output_Histogram113
-rw-r--r--toolkit/components/glean/tests/pytest/gifft_output_Scalar189
-rw-r--r--toolkit/components/glean/tests/pytest/jogfile_output287
-rw-r--r--toolkit/components/glean/tests/pytest/metrics_expires_versions_test.yaml84
-rw-r--r--toolkit/components/glean/tests/pytest/metrics_test.yaml369
-rw-r--r--toolkit/components/glean/tests/pytest/metrics_test_output693
-rw-r--r--toolkit/components/glean/tests/pytest/metrics_test_output_cpp244
-rw-r--r--toolkit/components/glean/tests/pytest/metrics_test_output_js342
-rw-r--r--toolkit/components/glean/tests/pytest/pings_test.yaml116
-rw-r--r--toolkit/components/glean/tests/pytest/pings_test_output105
-rw-r--r--toolkit/components/glean/tests/pytest/pings_test_output_cpp62
-rw-r--r--toolkit/components/glean/tests/pytest/pings_test_output_js99
-rw-r--r--toolkit/components/glean/tests/pytest/python.ini10
-rw-r--r--toolkit/components/glean/tests/pytest/test_gifft.py49
-rw-r--r--toolkit/components/glean/tests/pytest/test_glean_parser_cpp.py70
-rw-r--r--toolkit/components/glean/tests/pytest/test_glean_parser_js.py71
-rw-r--r--toolkit/components/glean/tests/pytest/test_glean_parser_rust.py89
-rw-r--r--toolkit/components/glean/tests/pytest/test_jogfile_output.py50
-rw-r--r--toolkit/components/glean/tests/pytest/test_no_expired_metrics.py46
-rw-r--r--toolkit/components/glean/tests/pytest/test_yaml_indices.py38
-rw-r--r--toolkit/components/glean/tests/test_metrics.yaml780
-rw-r--r--toolkit/components/glean/tests/test_pings.yaml42
-rw-r--r--toolkit/components/glean/tests/xpcshell/head.js6
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_FOGIPCLimit.js51
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_FOGInit.js41
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_FOGPrefs.js47
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_GIFFT.js538
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js315
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_Glean.js405
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_GleanExperiments.js28
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_GleanIPC.js157
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_JOG.js723
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_JOGIPC.js260
-rw-r--r--toolkit/components/glean/tests/xpcshell/test_MillionQ.js19
-rw-r--r--toolkit/components/glean/tests/xpcshell/xpcshell.ini30
54 files changed, 7529 insertions, 0 deletions
diff --git a/toolkit/components/glean/tests/browser/browser.ini b/toolkit/components/glean/tests/browser/browser.ini
new file mode 100644
index 0000000000..eae3f38730
--- /dev/null
+++ b/toolkit/components/glean/tests/browser/browser.ini
@@ -0,0 +1,19 @@
+# Please keep test files lexicographically sorted, with whitespace between.
+[DEFAULT]
+support-files =
+
+[browser_event_leak.js]
+
+[browser_fog_gmp.js]
+support-files = empty_file.html
+
+[browser_fog_gpu.js]
+
+[browser_fog_rdd.js]
+support-files = small-shot.ogg
+
+[browser_fog_socket.js]
+
+[browser_fog_utility.js]
+
+[browser_labeled_gifft.js]
diff --git a/toolkit/components/glean/tests/browser/browser_event_leak.js b/toolkit/components/glean/tests/browser/browser_event_leak.js
new file mode 100644
index 0000000000..1e69f17064
--- /dev/null
+++ b/toolkit/components/glean/tests/browser/browser_event_leak.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ Services.fog.testResetFOG(); // Needed for TV which reuses profiles on repeat
+ Assert.equal(
+ undefined,
+ Glean.testOnlyIpc.eventWithExtra.testGetValue(),
+ "Nothing to begin with"
+ );
+ Glean.testOnlyIpc.eventWithExtra.record({
+ extra1: "Some extra string",
+ extra2: 42,
+ extra3_longer_name: false,
+ });
+ Assert.equal(
+ 1,
+ Glean.testOnlyIpc.eventWithExtra.testGetValue().length,
+ "One event? One event."
+ );
+
+ // AND NOW, FOR THE TRUE TEST:
+ // Will this leak memory all over the place?
+});
diff --git a/toolkit/components/glean/tests/browser/browser_fog_gmp.js b/toolkit/components/glean/tests/browser/browser_fog_gmp.js
new file mode 100644
index 0000000000..4219605eea
--- /dev/null
+++ b/toolkit/components/glean/tests/browser/browser_fog_gmp.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Return a web-based URL for a given file based on the testing directory.
+ * @param {String} fileName
+ * file that caller wants its web-based url
+ */
+function GetTestWebBasedURL(fileName) {
+ return (
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.org"
+ ) + fileName
+ );
+}
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.eme.enabled", true]],
+ });
+
+ await BrowserTestUtils.withNewTab(
+ GetTestWebBasedURL("empty_file.html"),
+ async function(browser) {
+ await SpecialPowers.spawn(browser, [], async function() {
+ try {
+ let config = [
+ {
+ initDataTypes: ["webm"],
+ videoCapabilities: [{ contentType: 'video/webm; codecs="vp9"' }],
+ },
+ ];
+ let access = await content.navigator.requestMediaKeySystemAccess(
+ "org.w3.clearkey",
+ config
+ );
+
+ content.mediaKeys = await access.createMediaKeys();
+ info("got media keys, which should ensure a GMP process exists");
+ } catch (ex) {
+ ok(false, ex.toString());
+ }
+ });
+
+ ok(
+ (await ChromeUtils.requestProcInfo()).children.some(
+ p => p.type == "gmpPlugin"
+ ),
+ "Found the GMP process."
+ );
+
+ Services.fog.testResetFOG();
+
+ is(
+ undefined,
+ Glean.testOnlyIpc.aCounter.testGetValue(),
+ "Ensure we begin without value."
+ );
+
+ await TestUtils.waitForCondition(async () => {
+ try {
+ await Services.fog.testTriggerMetrics(
+ Ci.nsIXULRuntime.PROCESS_TYPE_GMPLUGIN
+ );
+ return true;
+ } catch (e) {
+ info(e);
+ return false;
+ }
+ }, "waiting until we can successfully send the TestTriggerMetrics IPC.");
+
+ await Services.fog.testFlushAllChildren();
+
+ is(
+ Glean.testOnlyIpc.aCounter.testGetValue(),
+ Ci.nsIXULRuntime.PROCESS_TYPE_GMPLUGIN,
+ "Ensure the GMP-process-set value shows up in the parent process."
+ );
+ }
+ );
+});
diff --git a/toolkit/components/glean/tests/browser/browser_fog_gpu.js b/toolkit/components/glean/tests/browser/browser_fog_gpu.js
new file mode 100644
index 0000000000..da0e699c20
--- /dev/null
+++ b/toolkit/components/glean/tests/browser/browser_fog_gpu.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ if (
+ !(await ChromeUtils.requestProcInfo()).children.some(p => p.type == "gpu")
+ ) {
+ ok(
+ true,
+ 'No GPU process? No test. Try again with --setpref "layers.gpu-process.force-enabled=true".'
+ );
+ return;
+ }
+ ok(true, "GPU Process found: Let's test.");
+
+ Services.fog.testResetFOG();
+
+ is(
+ undefined,
+ Glean.testOnlyIpc.aCounter.testGetValue(),
+ "Ensure we begin without value."
+ );
+
+ await Services.fog.testTriggerMetrics(Ci.nsIXULRuntime.PROCESS_TYPE_GPU);
+ await Services.fog.testFlushAllChildren();
+
+ is(
+ Glean.testOnlyIpc.aCounter.testGetValue(),
+ Ci.nsIXULRuntime.PROCESS_TYPE_GPU,
+ "Ensure the GPU-process-set value shows up in the parent process."
+ );
+});
diff --git a/toolkit/components/glean/tests/browser/browser_fog_rdd.js b/toolkit/components/glean/tests/browser/browser_fog_rdd.js
new file mode 100644
index 0000000000..caf9a6db1f
--- /dev/null
+++ b/toolkit/components/glean/tests/browser/browser_fog_rdd.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Return a web-based URL for a given file based on the testing directory.
+ * @param {String} fileName
+ * file that caller wants its web-based url
+ */
+function GetTestWebBasedURL(fileName) {
+ return (
+ getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.org"
+ ) + fileName
+ );
+}
+
+add_task(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["media.rdd-process.enabled", true]],
+ });
+
+ let url = GetTestWebBasedURL("small-shot.ogg");
+ info(
+ `Opening ${url} in a new tab to trigger the creation of the RDD process`
+ );
+ let tab = BrowserTestUtils.addTab(gBrowser, url);
+
+ await TestUtils.waitForCondition(
+ async () =>
+ (await ChromeUtils.requestProcInfo()).children.some(p => p.type == "rdd"),
+ "waiting to find RDD process."
+ );
+
+ Services.fog.testResetFOG();
+
+ is(
+ undefined,
+ Glean.testOnlyIpc.aCounter.testGetValue(),
+ "Ensure we begin without value."
+ );
+
+ await TestUtils.waitForCondition(async () => {
+ try {
+ await Services.fog.testTriggerMetrics(Ci.nsIXULRuntime.PROCESS_TYPE_RDD);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }, "waiting until we can successfully send the TestTriggerMetrics IPC.");
+
+ await Services.fog.testFlushAllChildren();
+
+ is(
+ Glean.testOnlyIpc.aCounter.testGetValue(),
+ Ci.nsIXULRuntime.PROCESS_TYPE_RDD,
+ "Ensure the RDD-process-set value shows up in the parent process."
+ );
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/glean/tests/browser/browser_fog_socket.js b/toolkit/components/glean/tests/browser/browser_fog_socket.js
new file mode 100644
index 0000000000..4ca4f085a1
--- /dev/null
+++ b/toolkit/components/glean/tests/browser/browser_fog_socket.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ if (
+ !(await ChromeUtils.requestProcInfo()).children.some(
+ p => p.type == "socket"
+ )
+ ) {
+ ok(true, "No Socket process? No test.");
+ return;
+ }
+ ok(true, "Socket process found: Let's test.");
+
+ Services.fog.testResetFOG();
+
+ is(
+ undefined,
+ Glean.testOnlyIpc.aCounter.testGetValue(),
+ "Ensure we begin without value."
+ );
+
+ await Services.fog.testTriggerMetrics(Ci.nsIXULRuntime.PROCESS_TYPE_SOCKET);
+ await Services.fog.testFlushAllChildren();
+
+ is(
+ Glean.testOnlyIpc.aCounter.testGetValue(),
+ Ci.nsIXULRuntime.PROCESS_TYPE_SOCKET,
+ "Ensure the Socket-process-set value shows up in the parent process."
+ );
+});
diff --git a/toolkit/components/glean/tests/browser/browser_fog_utility.js b/toolkit/components/glean/tests/browser/browser_fog_utility.js
new file mode 100644
index 0000000000..aaea9cfb42
--- /dev/null
+++ b/toolkit/components/glean/tests/browser/browser_fog_utility.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async () => {
+ const utilityProcessTest = Cc[
+ "@mozilla.org/utility-process-test;1"
+ ].createInstance(Ci.nsIUtilityProcessTest);
+ await utilityProcessTest
+ .startProcess()
+ .then(async () => {
+ Services.fog.testResetFOG();
+
+ is(
+ undefined,
+ Glean.testOnlyIpc.aCounter.testGetValue(),
+ "Ensure we begin without value."
+ );
+
+ await Services.fog.testTriggerMetrics(
+ Ci.nsIXULRuntime.PROCESS_TYPE_UTILITY
+ );
+ await Services.fog.testFlushAllChildren();
+
+ is(
+ Glean.testOnlyIpc.aCounter.testGetValue(),
+ Ci.nsIXULRuntime.PROCESS_TYPE_UTILITY,
+ "Ensure the utility-process-set value shows up in the parent process."
+ );
+ })
+ .catch(async () => {
+ ok(false, "Cannot start Utility process?");
+ });
+
+ await utilityProcessTest.stopProcess();
+});
diff --git a/toolkit/components/glean/tests/browser/browser_labeled_gifft.js b/toolkit/components/glean/tests/browser/browser_labeled_gifft.js
new file mode 100644
index 0000000000..2203424e82
--- /dev/null
+++ b/toolkit/components/glean/tests/browser/browser_labeled_gifft.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function keyedScalarValue(aScalarName) {
+ let snapshot = Services.telemetry.getSnapshotForKeyedScalars();
+ return "parent" in snapshot ? snapshot.parent[aScalarName] : undefined;
+}
+
+add_task(async () => {
+ Assert.equal(
+ undefined,
+ Glean.testOnlyIpc.aLabeledCounter.a_label.testGetValue(),
+ "New labels with no values should return undefined"
+ );
+ Glean.testOnlyIpc.aLabeledCounter.a_label.add(1);
+ Glean.testOnlyIpc.aLabeledCounter.another_label.add(2);
+ Assert.equal(1, Glean.testOnlyIpc.aLabeledCounter.a_label.testGetValue());
+ Assert.equal(
+ 2,
+ Glean.testOnlyIpc.aLabeledCounter.another_label.testGetValue()
+ );
+ // What about invalid/__other__?
+ Assert.equal(
+ undefined,
+ Glean.testOnlyIpc.aLabeledCounter.__other__.testGetValue()
+ );
+ Glean.testOnlyIpc.aLabeledCounter.InvalidLabel.add(3);
+ Assert.throws(
+ () => Glean.testOnlyIpc.aLabeledCounter.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Can't get the value when you're error'd"
+ );
+
+ let value = keyedScalarValue(
+ "telemetry.test.another_mirror_for_labeled_counter"
+ );
+ Assert.deepEqual(
+ {
+ a_label: 1,
+ another_label: 2,
+ InvalidLabel: 3,
+ },
+ value
+ );
+
+ // AND NOW, FOR THE TRUE TEST:
+ // Will this leak memory all over the place?
+});
diff --git a/toolkit/components/glean/tests/browser/empty_file.html b/toolkit/components/glean/tests/browser/empty_file.html
new file mode 100644
index 0000000000..af8440ac16
--- /dev/null
+++ b/toolkit/components/glean/tests/browser/empty_file.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ </head>
+ <body>
+ This page is intentionally left blank.
+ </body>
+</html>
diff --git a/toolkit/components/glean/tests/browser/small-shot.ogg b/toolkit/components/glean/tests/browser/small-shot.ogg
new file mode 100644
index 0000000000..1a41623f81
--- /dev/null
+++ b/toolkit/components/glean/tests/browser/small-shot.ogg
Binary files differ
diff --git a/toolkit/components/glean/tests/gtest/Cargo.toml b/toolkit/components/glean/tests/gtest/Cargo.toml
new file mode 100644
index 0000000000..914efad469
--- /dev/null
+++ b/toolkit/components/glean/tests/gtest/Cargo.toml
@@ -0,0 +1,14 @@
+[package]
+name = "fog-gtest"
+version = "0.1.0"
+authors = ["glean-team@mozilla.com"]
+license = "MPL-2.0"
+description = "Tests for the FOG crate"
+
+[dependencies]
+fog = { path = "../../api" }
+jog = { path = "../../bindings/jog" }
+nsstring = { path = "../../../../../xpcom/rust/nsstring" }
+
+[lib]
+path = "test.rs"
diff --git a/toolkit/components/glean/tests/gtest/FOGFixture.h b/toolkit/components/glean/tests/gtest/FOGFixture.h
new file mode 100644
index 0000000000..da3c6fc123
--- /dev/null
+++ b/toolkit/components/glean/tests/gtest/FOGFixture.h
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#ifndef FOGFixture_h_
+#define FOGFixture_h_
+
+#include "gtest/gtest.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "nsString.h"
+
+using namespace mozilla::glean::impl;
+
+class FOGFixture : public ::testing::Test {
+ protected:
+ FOGFixture() = default;
+ virtual void SetUp() {
+ nsCString empty;
+ ASSERT_EQ(NS_OK, fog_test_reset(&empty, &empty));
+ }
+};
+
+#endif // FOGFixture_h_
diff --git a/toolkit/components/glean/tests/gtest/TestFog.cpp b/toolkit/components/glean/tests/gtest/TestFog.cpp
new file mode 100644
index 0000000000..c3dd1a7914
--- /dev/null
+++ b/toolkit/components/glean/tests/gtest/TestFog.cpp
@@ -0,0 +1,378 @@
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "FOGFixture.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/glean/GleanPings.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/Tuple.h"
+#include "nsTArray.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/Unused.h"
+#include "nsString.h"
+#include "prtime.h"
+
+using mozilla::Preferences;
+using namespace mozilla::glean;
+using namespace mozilla::glean::impl;
+
+#define DATA_PREF "datareporting.healthreport.uploadEnabled"
+
+extern "C" {
+// This function is called by the rust code in test.rs if a non-fatal test
+// failure occurs.
+void GTest_FOG_ExpectFailure(const char* aMessage) {
+ EXPECT_STREQ(aMessage, "");
+}
+}
+
+TEST_F(FOGFixture, BuiltinPingsRegistered) {
+ Preferences::SetInt("telemetry.fog.test.localhost_port", -1);
+ nsAutoCString metricsPingName("metrics");
+ nsAutoCString baselinePingName("baseline");
+ nsAutoCString eventsPingName("events");
+ ASSERT_EQ(NS_OK, fog_submit_ping(&metricsPingName));
+ ASSERT_EQ(NS_OK, fog_submit_ping(&baselinePingName));
+ ASSERT_EQ(NS_OK, fog_submit_ping(&eventsPingName));
+}
+
+TEST_F(FOGFixture, TestCppCounterWorks) {
+ mozilla::glean::test_only::bad_code.Add(42);
+
+ ASSERT_EQ(42, mozilla::glean::test_only::bad_code.TestGetValue("test-ping"_ns)
+ .unwrap()
+ .value());
+ // And test that the ping name's optional, while you're at it:
+ ASSERT_EQ(42, test_only::bad_code.TestGetValue().unwrap().value());
+}
+
+TEST_F(FOGFixture, TestCppStringWorks) {
+ auto kValue = "cheez!"_ns;
+ mozilla::glean::test_only::cheesy_string.Set(kValue);
+
+ ASSERT_STREQ(kValue.get(), mozilla::glean::test_only::cheesy_string
+ .TestGetValue("test-ping"_ns)
+ .unwrap()
+ .value()
+ .get());
+}
+
+TEST_F(FOGFixture, TestCppTimespanWorks) {
+ mozilla::glean::test_only::can_we_time_it.Start();
+ PR_Sleep(PR_MillisecondsToInterval(10));
+ mozilla::glean::test_only::can_we_time_it.Stop();
+
+ ASSERT_TRUE(
+ mozilla::glean::test_only::can_we_time_it.TestGetValue("test-ping"_ns)
+ .unwrap()
+ .value() > 0);
+}
+
+TEST_F(FOGFixture, TestCppUuidWorks) {
+ nsCString kTestUuid("decafdec-afde-cafd-ecaf-decafdecafde");
+ test_only::what_id_it.Set(kTestUuid);
+ ASSERT_STREQ(kTestUuid.get(),
+ test_only::what_id_it.TestGetValue("test-ping"_ns)
+ .unwrap()
+ .value()
+ .get());
+
+ test_only::what_id_it.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_STRNE(kTestUuid.get(),
+ test_only::what_id_it.TestGetValue("test-ping"_ns)
+ .unwrap()
+ .value()
+ .get());
+}
+
+TEST_F(FOGFixture, TestCppBooleanWorks) {
+ mozilla::glean::test_only::can_we_flag_it.Set(false);
+
+ ASSERT_EQ(false, mozilla::glean::test_only::can_we_flag_it
+ .TestGetValue("test-ping"_ns)
+ .unwrap()
+ .value());
+}
+
+MATCHER_P(BitEq, x, "bit equal") {
+ static_assert(sizeof(x) == sizeof(arg));
+ return std::memcmp(&arg, &x, sizeof(x)) == 0;
+}
+
+TEST_F(FOGFixture, TestCppDatetimeWorks) {
+ PRExplodedTime date{0, 35, 10, 12, 6, 10, 2020, 0, 0, {5 * 60 * 60, 0}};
+ test_only::what_a_date.Set(&date);
+
+ auto received = test_only::what_a_date.TestGetValue("test-ping"_ns).unwrap();
+ ASSERT_THAT(received.value(), BitEq(date));
+}
+
+using mozilla::MakeTuple;
+using mozilla::Some;
+using mozilla::Tuple;
+using mozilla::glean::test_only_ipc::AnEventExtra;
+using mozilla::glean::test_only_ipc::EventWithExtraExtra;
+
+TEST_F(FOGFixture, TestCppEventWorks) {
+ test_only_ipc::no_extra_event.Record();
+ ASSERT_TRUE(test_only_ipc::no_extra_event.TestGetValue("store1"_ns)
+ .unwrap()
+ .isSome());
+
+ AnEventExtra extra = {.extra1 = Some("can set extras"_ns)};
+ test_only_ipc::an_event.Record(Some(extra));
+ auto optEvents = test_only_ipc::an_event.TestGetValue("store1"_ns).unwrap();
+ ASSERT_TRUE(optEvents.isSome());
+
+ auto events = optEvents.extract();
+ ASSERT_EQ(1UL, events.Length());
+ ASSERT_STREQ("test_only.ipc", events[0].mCategory.get());
+ ASSERT_STREQ("an_event", events[0].mName.get());
+ ASSERT_EQ(1UL, events[0].mExtra.Length());
+ ASSERT_STREQ("extra1", mozilla::Get<0>(events[0].mExtra[0]).get());
+ ASSERT_STREQ("can set extras", mozilla::Get<1>(events[0].mExtra[0]).get());
+}
+
+TEST_F(FOGFixture, TestCppEventsWithDifferentExtraTypes) {
+ EventWithExtraExtra extra = {.extra1 = Some("can set extras"_ns),
+ .extra2 = Some(37),
+ .extra3LongerName = Some(false)};
+ test_only_ipc::event_with_extra.Record(Some(extra));
+ auto optEvents =
+ test_only_ipc::event_with_extra.TestGetValue("store1"_ns).unwrap();
+ ASSERT_TRUE(optEvents.isSome());
+
+ auto events = optEvents.extract();
+ ASSERT_EQ(1UL, events.Length());
+
+ // The list of extra key/value pairs can be in any order.
+ ASSERT_EQ(3UL, events[0].mExtra.Length());
+ for (auto extra : events[0].mExtra) {
+ auto key = mozilla::Get<0>(extra);
+ auto value = mozilla::Get<1>(extra);
+
+ if (key == "extra1"_ns) {
+ ASSERT_STREQ("can set extras", value.get());
+ } else if (key == "extra2"_ns) {
+ ASSERT_STREQ("37", value.get());
+ } else if (key == "extra3_longer_name"_ns) {
+ ASSERT_STREQ("false", value.get());
+ } else {
+ ASSERT_TRUE(false)
+ << "Invalid extra item recorded.";
+ }
+ }
+}
+
+TEST_F(FOGFixture, TestCppMemoryDistWorks) {
+ test_only::do_you_remember.Accumulate(7);
+ test_only::do_you_remember.Accumulate(17);
+
+ DistributionData data =
+ test_only::do_you_remember.TestGetValue("test-ping"_ns).unwrap().ref();
+ // Sum is in bytes, test_only::do_you_remember is in megabytes. So
+ // multiplication ahoy!
+ ASSERT_EQ(data.sum, 24UL * 1024 * 1024);
+ for (const auto& entry : data.values) {
+ const uint64_t bucket = entry.GetKey();
+ const uint64_t count = entry.GetData();
+ ASSERT_TRUE(count == 0 ||
+ (count == 1 && (bucket == 17520006 || bucket == 7053950)))
+ << "Only two occupied buckets";
+ }
+}
+
+TEST_F(FOGFixture, TestCppCustomDistWorks) {
+ test_only_ipc::a_custom_dist.AccumulateSamples({7, 268435458});
+
+ DistributionData data =
+ test_only_ipc::a_custom_dist.TestGetValue("store1"_ns).unwrap().ref();
+ ASSERT_EQ(data.sum, 7UL + 268435458);
+ for (const auto& entry : data.values) {
+ const uint64_t bucket = entry.GetKey();
+ const uint64_t count = entry.GetData();
+ ASSERT_TRUE(count == 0 ||
+ (count == 1 && (bucket == 1 || bucket == 268435456)))
+ << "Only two occupied buckets";
+ }
+}
+
+TEST_F(FOGFixture, TestCppPings) {
+ test_only::one_ping_one_bool.Set(false);
+ const auto& ping = mozilla::glean_pings::OnePingOnly;
+ bool submitted = false;
+ ping.TestBeforeNextSubmit([&submitted](const nsACString& aReason) {
+ submitted = true;
+ ASSERT_EQ(false,
+ test_only::one_ping_one_bool.TestGetValue().unwrap().ref());
+ });
+ ping.Submit();
+ ASSERT_TRUE(submitted)
+ << "Must have actually called the lambda.";
+}
+
+TEST_F(FOGFixture, TestCppStringLists) {
+ auto kValue = "cheez!"_ns;
+ auto kValue2 = "cheezier!"_ns;
+ auto kValue3 = "cheeziest."_ns;
+
+ nsTArray<nsCString> cheezList;
+ cheezList.EmplaceBack(kValue);
+ cheezList.EmplaceBack(kValue2);
+
+ test_only::cheesy_string_list.Set(cheezList);
+
+ auto val = test_only::cheesy_string_list.TestGetValue().unwrap().value();
+ // Note: This is fragile if the order is ever not preserved.
+ ASSERT_STREQ(kValue.get(), val[0].get());
+ ASSERT_STREQ(kValue2.get(), val[1].get());
+
+ test_only::cheesy_string_list.Add(kValue3);
+
+ val = test_only::cheesy_string_list.TestGetValue().unwrap().value();
+ ASSERT_STREQ(kValue3.get(), val[2].get());
+}
+
+TEST_F(FOGFixture, TestCppTimingDistWorks) {
+ auto id1 = test_only::what_time_is_it.Start();
+ auto id2 = test_only::what_time_is_it.Start();
+ PR_Sleep(PR_MillisecondsToInterval(5));
+ auto id3 = test_only::what_time_is_it.Start();
+ test_only::what_time_is_it.Cancel(std::move(id1));
+ PR_Sleep(PR_MillisecondsToInterval(5));
+ test_only::what_time_is_it.StopAndAccumulate(std::move(id2));
+ test_only::what_time_is_it.StopAndAccumulate(std::move(id3));
+
+ DistributionData data =
+ test_only::what_time_is_it.TestGetValue().unwrap().ref();
+ const uint64_t NANOS_IN_MILLIS = 1e6;
+
+ // bug 1701847 - Sleeps don't necessarily round up as you'd expect.
+ // Give ourselves a 40000ns (0.04ms) window to be off on fast machines.
+ const uint64_t EPSILON = 40000;
+
+ // We don't know exactly how long those sleeps took, only that it was at
+ // least 15ms total.
+ ASSERT_GT(data.sum, (uint64_t)(15 * NANOS_IN_MILLIS) - EPSILON);
+
+ // We also can't guarantee the buckets, but we can guarantee two samples.
+ uint64_t sampleCount = 0;
+ for (const auto& value : data.values.Values()) {
+ sampleCount += value;
+ }
+ ASSERT_EQ(sampleCount, (uint64_t)2);
+}
+
+TEST_F(FOGFixture, TestLabeledBooleanWorks) {
+ ASSERT_EQ(mozilla::Nothing(),
+ test_only::mabels_like_balloons.Get("hot_air"_ns)
+ .TestGetValue()
+ .unwrap());
+ test_only::mabels_like_balloons.Get("hot_air"_ns).Set(true);
+ test_only::mabels_like_balloons.Get("helium"_ns).Set(false);
+ ASSERT_EQ(true, test_only::mabels_like_balloons.Get("hot_air"_ns)
+ .TestGetValue()
+ .unwrap()
+ .ref());
+ ASSERT_EQ(false, test_only::mabels_like_balloons.Get("helium"_ns)
+ .TestGetValue()
+ .unwrap()
+ .ref());
+}
+
+TEST_F(FOGFixture, TestLabeledCounterWorks) {
+ ASSERT_EQ(mozilla::Nothing(),
+ test_only::mabels_kitchen_counters.Get("marble"_ns)
+ .TestGetValue()
+ .unwrap());
+ test_only::mabels_kitchen_counters.Get("marble"_ns).Add(1);
+ test_only::mabels_kitchen_counters.Get("laminate"_ns).Add(2);
+ ASSERT_EQ(1, test_only::mabels_kitchen_counters.Get("marble"_ns)
+ .TestGetValue()
+ .unwrap()
+ .ref());
+ ASSERT_EQ(2, test_only::mabels_kitchen_counters.Get("laminate"_ns)
+ .TestGetValue()
+ .unwrap()
+ .ref());
+}
+
+TEST_F(FOGFixture, TestLabeledStringWorks) {
+ ASSERT_EQ(mozilla::Nothing(),
+ test_only::mabels_balloon_strings.Get("twine"_ns)
+ .TestGetValue()
+ .unwrap());
+ test_only::mabels_balloon_strings.Get("twine"_ns).Set("seems acceptable"_ns);
+ test_only::mabels_balloon_strings.Get("parachute_cord"_ns)
+ .Set("preferred"_ns);
+ ASSERT_STREQ("seems acceptable",
+ test_only::mabels_balloon_strings.Get("twine"_ns)
+ .TestGetValue()
+ .unwrap()
+ .ref()
+ .get());
+ ASSERT_STREQ("preferred",
+ test_only::mabels_balloon_strings.Get("parachute_cord"_ns)
+ .TestGetValue()
+ .unwrap()
+ .ref()
+ .get());
+}
+
+TEST_F(FOGFixture, TestCppQuantityWorks) {
+ // This joke only works in base 13.
+ const uint32_t kValue = 6 * 9;
+ mozilla::glean::test_only::meaning_of_life.Set(kValue);
+
+ ASSERT_EQ(kValue, mozilla::glean::test_only::meaning_of_life.TestGetValue()
+ .unwrap()
+ .value());
+}
+
+TEST_F(FOGFixture, TestCppRateWorks) {
+ // 1) Standard rate with internal denominator
+ const int32_t kNum = 22;
+ const int32_t kDen = 7; // because I like pi, even just approximately.
+
+ test_only_ipc::irate.AddToNumerator(kNum);
+ test_only_ipc::irate.AddToDenominator(kDen);
+ auto value = test_only_ipc::irate.TestGetValue().unwrap();
+ ASSERT_EQ(kNum, value.ref().first);
+ ASSERT_EQ(kDen, value.ref().second);
+
+ // 2) Rate with external denominator
+ test_only_ipc::rate_with_external_denominator.AddToNumerator(kNum);
+ test_only_ipc::an_external_denominator.Add(kDen);
+ value = test_only_ipc::rate_with_external_denominator.TestGetValue().unwrap();
+ ASSERT_EQ(kNum, value.ref().first);
+ ASSERT_EQ(kDen, value.ref().second);
+ ASSERT_EQ(
+ kDen,
+ test_only_ipc::an_external_denominator.TestGetValue().unwrap().extract());
+}
+
+TEST_F(FOGFixture, TestCppUrlWorks) {
+ auto kValue = "https://example.com/fog/gtest"_ns;
+ mozilla::glean::test_only_ipc::a_url.Set(kValue);
+
+ ASSERT_STREQ(kValue.get(),
+ mozilla::glean::test_only_ipc::a_url.TestGetValue("store1"_ns)
+ .unwrap()
+ .value()
+ .get());
+}
+
+extern "C" void Rust_TestRustInGTest();
+TEST_F(FOGFixture, TestRustInGTest) { Rust_TestRustInGTest(); }
+
+extern "C" void Rust_TestJogfile();
+TEST_F(FOGFixture, TestJogfile) { Rust_TestJogfile(); }
diff --git a/toolkit/components/glean/tests/gtest/moz.build b/toolkit/components/glean/tests/gtest/moz.build
new file mode 100644
index 0000000000..329604e929
--- /dev/null
+++ b/toolkit/components/glean/tests/gtest/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if not CONFIG["MOZ_GLEAN_ANDROID"]:
+ UNIFIED_SOURCES += [
+ "TestFog.cpp",
+ ]
+
+ TEST_HARNESS_FILES.gtest += ["../pytest/jogfile_output"]
+
+FINAL_LIBRARY = "xul-gtest"
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/toolkit/components/glean/tests/gtest/test.rs b/toolkit/components/glean/tests/gtest/test.rs
new file mode 100644
index 0000000000..f84454b266
--- /dev/null
+++ b/toolkit/components/glean/tests/gtest/test.rs
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+extern crate nsstring;
+use nsstring::nsString;
+
+fn nonfatal_fail(msg: String) {
+ extern "C" {
+ fn GTest_FOG_ExpectFailure(message: *const ::std::os::raw::c_char);
+ }
+ unsafe {
+ let msg = ::std::ffi::CString::new(msg).unwrap();
+ GTest_FOG_ExpectFailure(msg.as_ptr());
+ }
+}
+
+/// This macro checks if the expression evaluates to true,
+/// and causes a non-fatal GTest test failure if it doesn't.
+macro_rules! expect {
+ ($x:expr) => {
+ match (&$x) {
+ true => {}
+ false => nonfatal_fail(format!(
+ "check failed: (`{}`) at {}:{}",
+ stringify!($x),
+ file!(),
+ line!()
+ )),
+ }
+ };
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_TestRustInGTest() {
+ // Just a smoke test, we show here how tests might work that both
+ // a) Are in Rust, and
+ // b) Require Gecko
+ // This demonstration doesn't actually require Gecko. But we pretend it
+ // does so we remember how to do this rust-in-gtest pattern.
+ fog::metrics::test_only::bad_code.add(12);
+ expect!(fog::metrics::test_only::bad_code.test_get_value(None) == Some(12));
+}
+
+#[no_mangle]
+pub extern "C" fn Rust_TestJogfile() {
+ // Ensure that the generated jogfile in t/c/g/tests/pytest
+ // (which is installed nearby using TEST_HARNESS_FILES)
+ // can be consumed by JOG's inner workings
+ //
+ // If it can't, that's perhaps a sign that the inner workings need to be updated.
+ expect!(jog::jog_load_jogfile(&nsString::from("jogfile_output")));
+}
diff --git a/toolkit/components/glean/tests/moz.build b/toolkit/components/glean/tests/moz.build
new file mode 100644
index 0000000000..e7de2c3b66
--- /dev/null
+++ b/toolkit/components/glean/tests/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+PYTHON_UNITTEST_MANIFESTS += ["pytest/python.ini"]
+
+TEST_DIRS += ["gtest"]
+
+XPCSHELL_TESTS_MANIFESTS += ["xpcshell/xpcshell.ini"]
+
+BROWSER_CHROME_MANIFESTS += ["browser/browser.ini"]
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Telemetry")
diff --git a/toolkit/components/glean/tests/pytest/expect_helper.py b/toolkit/components/glean/tests/pytest/expect_helper.py
new file mode 100644
index 0000000000..543ef04026
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/expect_helper.py
@@ -0,0 +1,34 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import inspect
+import os
+
+
+def expect(path, actual):
+ """
+ Assert that the content of the file at `path` contains `actual`.
+
+ If the environment variable `UPDATE_EXPECT` is set, the path content is updated to `actual`.
+ This allows to update the file contents easily.
+ """
+
+ callerframerecord = inspect.stack()[1]
+ frame = callerframerecord[0]
+ info = inspect.getframeinfo(frame)
+ msg = f"""
+Unexpected content in {path} (at {info.filename}:{info.lineno})
+
+If the code generation was changed,
+run the test suite again with `UPDATE_EXPECT=1` set to update the test files.
+""".strip()
+
+ if "UPDATE_EXPECT" in os.environ:
+ with open(path, "w") as file:
+ file.write(actual)
+
+ expected = None
+ with open(path, "r") as file:
+ expected = file.read()
+ assert actual == expected, msg
diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Event b/toolkit/components/glean/tests/pytest/gifft_output_Event
new file mode 100644
index 0000000000..565fcca59d
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/gifft_output_Event
@@ -0,0 +1,38 @@
+// -*- mode: C++ -*-
+
+/* This file is auto-generated by run_glean_parser.py.
+ It is only for internal use by types in
+ toolkit/components/glean/bindings/private */
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/DataMutex.h"
+#include "nsThreadUtils.h"
+
+#ifndef mozilla_glean_EventGifftMap_h
+#define mozilla_glean_EventGifftMap_h
+
+namespace mozilla::glean {
+
+using Telemetry::EventID;
+
+
+static inline Maybe<EventID> EventIdForMetric(uint32_t aId) {
+ switch(aId) {
+ case 17: { // test.nested.event_metric
+ return Some(EventID::EventMetric_EnumNames_AreStrange);
+ }
+ case 18: { // test.nested.event_metric_with_extra
+ return Some(EventID::EventMetric_EnumName_WithExtra);
+ }
+ default: {
+ return Nothing();
+ }
+ }
+}
+
+} // namespace mozilla::glean
+#endif // mozilla_glean_EventGifftMaps_h
diff --git a/toolkit/components/glean/tests/pytest/gifft_output_EventExtra b/toolkit/components/glean/tests/pytest/gifft_output_EventExtra
new file mode 100644
index 0000000000..f5f2671d99
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/gifft_output_EventExtra
@@ -0,0 +1,35 @@
+// -*- mode: C++ -*-
+
+/* This file is auto-generated by run_glean_parser.py.
+ It is only for internal use by types in
+ toolkit/components/glean/bindings/private */
+
+#include "mozilla/glean/bindings/Event.h"
+#include "mozilla/glean/GleanMetrics.h"
+
+namespace mozilla::glean {
+
+template <>
+/*static*/ const nsCString impl::EventMetric<NoExtraKeys>::ExtraStringForKey(uint32_t aKey) {
+ MOZ_ASSERT_UNREACHABLE("What are you doing here? No extra keys!");
+ return ""_ns;
+}
+
+template <>
+/*static*/ const nsCString impl::EventMetric<test_nested::EventMetricWithExtraExtra>::ExtraStringForKey(uint32_t aKey) {
+ using test_nested::EventMetricWithExtraExtra;
+ switch (aKey) {
+ case 0: {
+ return "an_extra_key"_ns;
+ }
+ case 1: {
+ return "another_extra_key"_ns;
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("Impossible event key reached.");
+ return ""_ns;
+ }
+ }
+}
+
+}; // namespace mozilla::glean
diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Histogram b/toolkit/components/glean/tests/pytest/gifft_output_Histogram
new file mode 100644
index 0000000000..ef01708dfe
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/gifft_output_Histogram
@@ -0,0 +1,113 @@
+// -*- mode: C++ -*-
+
+/* This file is auto-generated by run_glean_parser.py.
+ It is only for internal use by types in
+ toolkit/components/glean/bindings/private */
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/DataMutex.h"
+#include "nsThreadUtils.h"
+
+#ifndef mozilla_glean_HistogramGifftMap_h
+#define mozilla_glean_HistogramGifftMap_h
+
+namespace mozilla::glean {
+
+using Telemetry::HistogramID;
+
+
+using MetricId = uint32_t; // Same type as in api/src/private/mod.rs
+using TimerId = uint64_t; // Same as in TimingDistribution.h.
+using MetricTimerTuple = Tuple<MetricId, TimerId>;
+class MetricTimerTupleHashKey : public PLDHashEntryHdr {
+ public:
+ using KeyType = const MetricTimerTuple&;
+ using KeyTypePointer = const MetricTimerTuple*;
+
+ explicit MetricTimerTupleHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
+ MetricTimerTupleHashKey(MetricTimerTupleHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)),
+ mValue(std::move(aOther.mValue)) {}
+ ~MetricTimerTupleHashKey() = default;
+
+ KeyType GetKey() const { return mValue; }
+ bool KeyEquals(KeyTypePointer aKey) const {
+ return Get<0>(*aKey) == Get<0>(mValue) && Get<1>(*aKey) == Get<1>(mValue);
+ }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ // Chosen because this is how nsIntegralHashKey does it.
+ return HashGeneric(Get<0>(*aKey), Get<1>(*aKey));
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ const MetricTimerTuple mValue;
+};
+
+typedef StaticDataMutex<UniquePtr<nsTHashMap<MetricTimerTupleHashKey, TimeStamp>>> TimerToStampMutex;
+static inline Maybe<TimerToStampMutex::AutoLock> GetTimerIdToStartsLock() {
+ static TimerToStampMutex sTimerIdToStarts("sTimerIdToStarts");
+ auto lock = sTimerIdToStarts.Lock();
+ // GIFFT will work up to the end of AppShutdownTelemetry.
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
+ return Nothing();
+ }
+ if (!*lock) {
+ *lock = MakeUnique<nsTHashMap<MetricTimerTupleHashKey, TimeStamp>>();
+ RefPtr<nsIRunnable> cleanupFn = NS_NewRunnableFunction(__func__, [&] {
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
+ auto lock = sTimerIdToStarts.Lock();
+ *lock = nullptr; // deletes, see UniquePtr.h
+ return;
+ }
+ RunOnShutdown([&] {
+ auto lock = sTimerIdToStarts.Lock();
+ *lock = nullptr; // deletes, see UniquePtr.h
+ }, ShutdownPhase::XPCOMWillShutdown);
+ });
+ // Both getting the main thread and dispatching to it can fail.
+ // In that event we leak. Grab a pointer so we have something to NS_RELEASE
+ // in that case.
+ nsIRunnable* temp = cleanupFn.get();
+ nsCOMPtr<nsIThread> mainThread;
+ if (NS_FAILED(NS_GetMainThread(getter_AddRefs(mainThread)))
+ || NS_FAILED(mainThread->Dispatch(cleanupFn.forget(), nsIThread::DISPATCH_NORMAL))
+ ) {
+ // Failed to dispatch cleanup routine.
+ // First, un-leak the runnable (but only if we actually attempted dispatch)
+ if (!cleanupFn) {
+ NS_RELEASE(temp);
+ }
+ // Next, cleanup immediately, and allow metrics to try again later.
+ *lock = nullptr;
+ return Nothing();
+ }
+ }
+ return Some(std::move(lock));
+}
+
+static Maybe<HistogramID> HistogramIdForMetric(uint32_t aId) {
+ switch(aId) {
+ case 12: { // test.timing_distribution_metric
+ return Some(HistogramID::SOME_TIME_HISTOGRAM_MS);
+ }
+ case 13: { // test.memory_distribution_metric
+ return Some(HistogramID::SOME_MEM_HISTOGRAM_KB);
+ }
+ case 14: { // test.custom_distribution_metric
+ return Some(HistogramID::SOME_LINEAR_HISTOGRAM);
+ }
+ default: {
+ return Nothing();
+ }
+ }
+}
+
+} // namespace mozilla::glean
+#endif // mozilla_glean_HistogramGifftMaps_h
diff --git a/toolkit/components/glean/tests/pytest/gifft_output_Scalar b/toolkit/components/glean/tests/pytest/gifft_output_Scalar
new file mode 100644
index 0000000000..0e79e4d00d
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/gifft_output_Scalar
@@ -0,0 +1,189 @@
+// -*- mode: C++ -*-
+
+/* This file is auto-generated by run_glean_parser.py.
+ It is only for internal use by types in
+ toolkit/components/glean/bindings/private */
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/Tuple.h"
+#include "nsClassHashtable.h"
+#include "nsIThread.h"
+#include "nsTHashMap.h"
+#include "nsThreadUtils.h"
+
+#ifndef mozilla_glean_ScalarGifftMap_h
+#define mozilla_glean_ScalarGifftMap_h
+
+namespace mozilla::glean {
+
+using Telemetry::ScalarID;
+
+typedef nsUint32HashKey SubmetricIdHashKey;
+typedef nsTHashMap<SubmetricIdHashKey, Tuple<ScalarID, nsString>>
+ SubmetricToLabeledMirrorMapType;
+typedef StaticDataMutex<UniquePtr<SubmetricToLabeledMirrorMapType>>
+ SubmetricToMirrorMutex;
+static inline Maybe<SubmetricToMirrorMutex::AutoLock> GetLabeledMirrorLock() {
+ static SubmetricToMirrorMutex sLabeledMirrors("sLabeledMirrors");
+ auto lock = sLabeledMirrors.Lock();
+ // GIFFT will work up to the end of AppShutdownTelemetry.
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
+ return Nothing();
+ }
+ if (!*lock) {
+ *lock = MakeUnique<SubmetricToLabeledMirrorMapType>();
+ RefPtr<nsIRunnable> cleanupFn = NS_NewRunnableFunction(__func__, [&] {
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
+ auto lock = sLabeledMirrors.Lock();
+ *lock = nullptr; // deletes, see UniquePtr.h
+ return;
+ }
+ RunOnShutdown([&] {
+ auto lock = sLabeledMirrors.Lock();
+ *lock = nullptr; // deletes, see UniquePtr.h
+ }, ShutdownPhase::XPCOMWillShutdown);
+ });
+ // Both getting the main thread and dispatching to it can fail.
+ // In that event we leak. Grab a pointer so we have something to NS_RELEASE
+ // in that case.
+ nsIRunnable* temp = cleanupFn.get();
+ nsCOMPtr<nsIThread> mainThread;
+ if (NS_FAILED(NS_GetMainThread(getter_AddRefs(mainThread)))
+ || NS_FAILED(mainThread->Dispatch(cleanupFn.forget(), nsIThread::DISPATCH_NORMAL))
+ ) {
+ // Failed to dispatch cleanup routine.
+ // First, un-leak the runnable (but only if we actually attempted dispatch)
+ if (!cleanupFn) {
+ NS_RELEASE(temp);
+ }
+ // Next, cleanup immediately, and allow metrics to try again later.
+ *lock = nullptr;
+ return Nothing();
+ }
+ }
+ return Some(std::move(lock));
+}
+
+namespace {
+class ScalarIDHashKey : public PLDHashEntryHdr {
+ public:
+ typedef const ScalarID& KeyType;
+ typedef const ScalarID* KeyTypePointer;
+
+ explicit ScalarIDHashKey(KeyTypePointer aKey) : mValue(*aKey) {}
+ ScalarIDHashKey(ScalarIDHashKey&& aOther)
+ : PLDHashEntryHdr(std::move(aOther)), mValue(std::move(aOther.mValue)) {}
+ ~ScalarIDHashKey() = default;
+
+ KeyType GetKey() const { return mValue; }
+ bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; }
+
+ static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
+ static PLDHashNumber HashKey(KeyTypePointer aKey) {
+ return static_cast<std::underlying_type<ScalarID>::type>(*aKey);
+ }
+ enum { ALLOW_MEMMOVE = true };
+
+ private:
+ const ScalarID mValue;
+};
+} // namespace
+typedef StaticDataMutex<UniquePtr<nsTHashMap<ScalarIDHashKey, TimeStamp>>> TimesToStartsMutex;
+static inline Maybe<TimesToStartsMutex::AutoLock> GetTimesToStartsLock() {
+ static TimesToStartsMutex sTimespanStarts("sTimespanStarts");
+ auto lock = sTimespanStarts.Lock();
+ // GIFFT will work up to the end of AppShutdownTelemetry.
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
+ return Nothing();
+ }
+ if (!*lock) {
+ *lock = MakeUnique<nsTHashMap<ScalarIDHashKey, TimeStamp>>();
+ RefPtr<nsIRunnable> cleanupFn = NS_NewRunnableFunction(__func__, [&] {
+ if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
+ auto lock = sTimespanStarts.Lock();
+ *lock = nullptr; // deletes, see UniquePtr.h
+ return;
+ }
+ RunOnShutdown([&] {
+ auto lock = sTimespanStarts.Lock();
+ *lock = nullptr; // deletes, see UniquePtr.h
+ }, ShutdownPhase::XPCOMWillShutdown);
+ });
+ // Both getting the main thread and dispatching to it can fail.
+ // In that event we leak. Grab a pointer so we have something to NS_RELEASE
+ // in that case.
+ nsIRunnable* temp = cleanupFn.get();
+ nsCOMPtr<nsIThread> mainThread;
+ if (NS_FAILED(NS_GetMainThread(getter_AddRefs(mainThread)))
+ || NS_FAILED(mainThread->Dispatch(cleanupFn.forget(), nsIThread::DISPATCH_NORMAL))
+ ) {
+ // Failed to dispatch cleanup routine.
+ // First, un-leak the runnable (but only if we actually attempted dispatch)
+ if (!cleanupFn) {
+ NS_RELEASE(temp);
+ }
+ // Next, cleanup immediately, and allow metrics to try again later.
+ *lock = nullptr;
+ return Nothing();
+ }
+ }
+ return Some(std::move(lock));
+}
+
+static inline bool IsSubmetricId(uint32_t aId) {
+ // Submetrics have the 2^25 bit set.
+ // (ID_BITS - ID_SIGNAL_BITS, keep it in sync with js.py).
+ return (aId & (1 << 25)) > 0;
+}
+
+static Maybe<ScalarID> ScalarIdForMetric(uint32_t aId) {
+ switch(aId) {
+ case 1: { // test.boolean_metric
+ return Some(ScalarID::SOME_BOOL_SCALAR);
+ }
+ case 2: { // test.labeled_boolean_metric
+ return Some(ScalarID::SOME_KEYED_BOOL_SCALAR);
+ }
+ case 3: { // test.labeled_boolean_metric_labels
+ return Some(ScalarID::SOME_OTHER_KEYED_BOOL_SCALAR);
+ }
+ case 4: { // test.counter_metric
+ return Some(ScalarID::SOME_UINT_SCALAR);
+ }
+ case 5: { // test.labeled_counter_metric
+ return Some(ScalarID::SOME_KEYED_UINT_SCALAR);
+ }
+ case 6: { // test.labeled_counter_metric_labels
+ return Some(ScalarID::SOME_OTHER_KEYED_UINT_SCALAR);
+ }
+ case 7: { // test.string_metric
+ return Some(ScalarID::SOME_STRING_SCALAR);
+ }
+ case 10: { // test.string_list_metric
+ return Some(ScalarID::YET_ANOTHER_KEYED_BOOL_SCALAR);
+ }
+ case 11: { // test.timespan_metric
+ return Some(ScalarID::SOME_OTHER_UINT_SCALAR);
+ }
+ case 15: { // test.nested.uuid_metric
+ return Some(ScalarID::SOME_OTHER_STRING_SCALAR);
+ }
+ case 16: { // test.nested.datetime_metric
+ return Some(ScalarID::SOME_STILL_OTHER_STRING_SCALAR);
+ }
+ case 19: { // test.nested.quantity_metric
+ return Some(ScalarID::TELEMETRY_TEST_MIRROR_FOR_QUANTITY);
+ }
+ default: {
+ return Nothing();
+ }
+ }
+}
+
+} // namespace mozilla::glean
+#endif // mozilla_glean_ScalarGifftMaps_h
diff --git a/toolkit/components/glean/tests/pytest/jogfile_output b/toolkit/components/glean/tests/pytest/jogfile_output
new file mode 100644
index 0000000000..7a38cc2fc0
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/jogfile_output
@@ -0,0 +1,287 @@
+{
+ "metrics": {
+ "test": [
+ [
+ "boolean",
+ "boolean_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false
+ ],
+ [
+ "labeled_boolean",
+ "labeled_boolean_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false
+ ],
+ [
+ "labeled_boolean",
+ "labeled_boolean_metric_labels",
+ [
+ "metrics"
+ ],
+ "application",
+ false
+ ],
+ [
+ "counter",
+ "counter_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false
+ ],
+ [
+ "labeled_counter",
+ "labeled_counter_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false
+ ],
+ [
+ "labeled_counter",
+ "labeled_counter_metric_labels",
+ [
+ "metrics"
+ ],
+ "application",
+ false
+ ],
+ [
+ "string",
+ "string_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false
+ ],
+ [
+ "labeled_string",
+ "labeled_string_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false
+ ],
+ [
+ "labeled_string",
+ "labeled_string_metric_labels",
+ [
+ "metrics"
+ ],
+ "application",
+ false
+ ],
+ [
+ "string_list",
+ "string_list_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false
+ ],
+ [
+ "timespan",
+ "timespan_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false,
+ {
+ "time_unit": "millisecond"
+ }
+ ],
+ [
+ "timing_distribution",
+ "timing_distribution_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false,
+ {
+ "time_unit": "nanosecond"
+ }
+ ],
+ [
+ "memory_distribution",
+ "memory_distribution_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false,
+ {
+ "memory_unit": "kilobyte"
+ }
+ ],
+ [
+ "custom_distribution",
+ "custom_distribution_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false,
+ {
+ "bucket_count": 100,
+ "histogram_type": "linear",
+ "range_max": 100,
+ "range_min": 0
+ }
+ ]
+ ],
+ "test.nested": [
+ [
+ "uuid",
+ "uuid_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false
+ ],
+ [
+ "datetime",
+ "datetime_metric",
+ [
+ "metrics"
+ ],
+ "application",
+ false,
+ {
+ "time_unit": "millisecond"
+ }
+ ],
+ [
+ "event",
+ "event_metric",
+ [
+ "events"
+ ],
+ "ping",
+ false,
+ {
+ "allowed_extra_keys": []
+ }
+ ],
+ [
+ "event",
+ "event_metric_with_extra",
+ [
+ "events"
+ ],
+ "ping",
+ false,
+ {
+ "allowed_extra_keys": [
+ "an_extra_key",
+ "another_extra_key"
+ ]
+ }
+ ],
+ [
+ "quantity",
+ "quantity_metric",
+ [
+ "metrics"
+ ],
+ "ping",
+ false
+ ],
+ [
+ "rate",
+ "rate_metric",
+ [
+ "metrics"
+ ],
+ "ping",
+ false
+ ],
+ [
+ "rate",
+ "rate_with_external_denominator",
+ [
+ "metrics"
+ ],
+ "ping",
+ false
+ ],
+ [
+ "denominator",
+ "external_denominator",
+ [
+ "metrics"
+ ],
+ "ping",
+ false,
+ {
+ "numerators": [
+ [
+ "rate_with_external_denominator",
+ "test.nested",
+ [
+ "metrics"
+ ],
+ "ping",
+ false,
+ null
+ ]
+ ]
+ }
+ ]
+ ]
+ },
+ "pings": [
+ [
+ "not-baseline",
+ true,
+ false,
+ [
+ "background",
+ "dirty_startup",
+ "foreground"
+ ]
+ ],
+ [
+ "not-metrics",
+ true,
+ false,
+ [
+ "overdue",
+ "reschedule",
+ "today",
+ "tomorrow",
+ "upgrade"
+ ]
+ ],
+ [
+ "not-events",
+ true,
+ false,
+ [
+ "background",
+ "max_capacity",
+ "startup"
+ ]
+ ],
+ [
+ "not-deletion-request",
+ true,
+ true,
+ []
+ ]
+ ]
+} \ No newline at end of file
diff --git a/toolkit/components/glean/tests/pytest/metrics_expires_versions_test.yaml b/toolkit/components/glean/tests/pytest/metrics_expires_versions_test.yaml
new file mode 100644
index 0000000000..ea73a6465e
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/metrics_expires_versions_test.yaml
@@ -0,0 +1,84 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This file is FOR TESTING PURPOSES ONLY.
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+
+test:
+ expired1:
+ type: boolean
+ expires: 41
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1664306
+ data_reviews:
+ - https://example.com
+ no_lint:
+ - EXPIRED
+
+ expired2:
+ type: labeled_boolean
+ expires: 42
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1664306
+ data_reviews:
+ - https://example.com
+ no_lint:
+ - EXPIRED
+
+ unexpired:
+ type: labeled_boolean
+ expires: 100
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1664306
+ data_reviews:
+ - https://example.com
+ labels:
+ - one_label
+ - two_labels
+
+ never:
+ type: string
+ expires: never
+ description: A never-expiring metric
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1664306
+ data_reviews:
+ - https://example.com
+
+ always:
+ type: string
+ expires: expired
+ description: An already-expired metric
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1664306
+ data_reviews:
+ - https://example.com
+ no_lint:
+ - EXPIRED
diff --git a/toolkit/components/glean/tests/pytest/metrics_test.yaml b/toolkit/components/glean/tests/pytest/metrics_test.yaml
new file mode 100644
index 0000000000..8b4a7404f1
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/metrics_test.yaml
@@ -0,0 +1,369 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This file defines the metrics that are recorded by the Glean SDK. They are
+# automatically converted to platform-specific code at build time using the
+# `glean_parser` PyPI package.
+
+# This file is presently for Internal FOG Use Only.
+# You should not add metrics here until probably about January of 2021.
+# If you're looking for the metrics.yaml for Geckoveiw Streaming Telemetry,
+# you can find that one in toolkit/components/telemetry/geckoview/streaming.
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+
+test:
+ boolean_metric:
+ type: boolean
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: SOME_BOOL_SCALAR
+
+ labeled_boolean_metric:
+ type: labeled_boolean
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: SOME_KEYED_BOOL_SCALAR
+
+ labeled_boolean_metric_labels:
+ type: labeled_boolean
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ labels:
+ - one_label
+ - two_labels
+ - three_labels
+ - four_labels
+ - five_labels
+ - six_labels
+ - seven_labels
+ - eight_labels
+ - nine_labels
+ - ten_labels
+ telemetry_mirror: SOME_OTHER_KEYED_BOOL_SCALAR
+
+ counter_metric:
+ type: counter
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: SOME_UINT_SCALAR
+
+ labeled_counter_metric:
+ type: labeled_counter
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: SOME_KEYED_UINT_SCALAR
+
+ labeled_counter_metric_labels:
+ type: labeled_counter
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ labels:
+ - one_label
+ - two_labels
+ telemetry_mirror: SOME_OTHER_KEYED_UINT_SCALAR
+
+ string_metric:
+ type: string
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: SOME_STRING_SCALAR
+
+ labeled_string_metric:
+ type: labeled_string
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+
+ labeled_string_metric_labels:
+ type: labeled_string
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ labels:
+ - one_label
+ - two_labels
+
+ string_list_metric:
+ type: string_list
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: YET_ANOTHER_KEYED_BOOL_SCALAR
+
+ timespan_metric:
+ type: timespan
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: SOME_OTHER_UINT_SCALAR
+
+ timing_distribution_metric:
+ type: timing_distribution
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: SOME_TIME_HISTOGRAM_MS
+
+ memory_distribution_metric:
+ type: memory_distribution
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ memory_unit: kilobyte
+ telemetry_mirror: SOME_MEM_HISTOGRAM_KB
+
+ custom_distribution_metric:
+ type: custom_distribution
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ range_min: 0
+ range_max: 100
+ bucket_count: 100
+ histogram_type: linear
+ telemetry_mirror: SOME_LINEAR_HISTOGRAM
+
+test.nested:
+ uuid_metric:
+ type: uuid
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: SOME_OTHER_STRING_SCALAR
+
+ datetime_metric:
+ type: datetime
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: application
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: SOME_STILL_OTHER_STRING_SCALAR
+
+ event_metric:
+ type: event
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: ping
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: EventMetric_EnumNames_AreStrange
+
+ event_metric_with_extra:
+ type: event
+ expires: never
+ description: |
+ A multi-line
+ description
+ lifetime: ping
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1635260/
+ data_reviews:
+ - https://example.com
+ extra_keys:
+ an_extra_key:
+ type: string
+ description: An extra key description
+ another_extra_key:
+ type: string
+ description: Another extra key description
+ telemetry_mirror: EventMetric_EnumName_WithExtra
+
+ quantity_metric:
+ type: quantity
+ unit: someunit
+ expires: never
+ description: |
+ A multi-line
+ description
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1704846/
+ data_reviews:
+ - https://example.com
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_QUANTITY
+
+ rate_metric:
+ type: rate
+ expires: never
+ description: |
+ A multi-line
+ description
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1704846/
+ data_reviews:
+ - https://example.com
+
+ rate_with_external_denominator:
+ type: rate
+ denominator_metric: test.nested.external_denominator
+ expires: never
+ description: |
+ A multi-line
+ description
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1704846/
+ data_reviews:
+ - https://example.com
+
+ external_denominator:
+ type: counter
+ expires: never
+ description: |
+ A multi-line
+ description
+ notification_emails:
+ - glean-team@mozilla.com
+ bugs:
+ - https://bugzilla.mozilla.org/1704846/
+ data_reviews:
+ - https://example.com
diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output b/toolkit/components/glean/tests/pytest/metrics_test_output
new file mode 100644
index 0000000000..9bb1438306
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/metrics_test_output
@@ -0,0 +1,693 @@
+// -*- mode: Rust -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+pub mod test {
+ use crate::private::*;
+ use glean::CommonMetricData;
+ #[allow(unused_imports)] // HistogramType might be unusued, let's avoid warnings
+ use glean::HistogramType;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.boolean_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static boolean_metric: Lazy<BooleanMetric> = Lazy::new(|| {
+ BooleanMetric::new(1.into(), CommonMetricData {
+ name: "boolean_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_boolean_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_boolean_metric: Lazy<LabeledMetric<LabeledBooleanMetric>> = Lazy::new(|| {
+ LabeledMetric::new(2.into(), CommonMetricData {
+ name: "labeled_boolean_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, None)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_boolean_metric_labels
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_boolean_metric_labels: Lazy<LabeledMetric<LabeledBooleanMetric>> = Lazy::new(|| {
+ LabeledMetric::new(3.into(), CommonMetricData {
+ name: "labeled_boolean_metric_labels".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, Some({const S: &'static str = "eight_labelsfive_labelsfour_labelsnine_labelsone_labelseven_labelssix_labelsten_labelsthree_labelstwo_labels";const LENGTHS: [u8; 10] = [12, 11, 11, 11, 9, 12, 10, 10, 12, 10];let mut offset = 0;LENGTHS.iter().map(|len| { let start = offset; offset += *len as usize; S[start..offset].into()}).collect()}))
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.counter_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static counter_metric: Lazy<CounterMetric> = Lazy::new(|| {
+ CounterMetric::new(4.into(), CommonMetricData {
+ name: "counter_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_counter_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_counter_metric: Lazy<LabeledMetric<LabeledCounterMetric>> = Lazy::new(|| {
+ LabeledMetric::new(5.into(), CommonMetricData {
+ name: "labeled_counter_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, None)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_counter_metric_labels
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_counter_metric_labels: Lazy<LabeledMetric<LabeledCounterMetric>> = Lazy::new(|| {
+ LabeledMetric::new(6.into(), CommonMetricData {
+ name: "labeled_counter_metric_labels".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, Some(vec!["one_label".into(), "two_labels".into()]))
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.string_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static string_metric: Lazy<StringMetric> = Lazy::new(|| {
+ StringMetric::new(7.into(), CommonMetricData {
+ name: "string_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_string_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_string_metric: Lazy<LabeledMetric<LabeledStringMetric>> = Lazy::new(|| {
+ LabeledMetric::new(8.into(), CommonMetricData {
+ name: "labeled_string_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, None)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.labeled_string_metric_labels
+ ///
+ /// A multi-line
+ /// description
+ pub static labeled_string_metric_labels: Lazy<LabeledMetric<LabeledStringMetric>> = Lazy::new(|| {
+ LabeledMetric::new(9.into(), CommonMetricData {
+ name: "labeled_string_metric_labels".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, Some(vec!["one_label".into(), "two_labels".into()]))
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.string_list_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static string_list_metric: Lazy<StringListMetric> = Lazy::new(|| {
+ StringListMetric::new(10.into(), CommonMetricData {
+ name: "string_list_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.timespan_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static timespan_metric: Lazy<TimespanMetric> = Lazy::new(|| {
+ TimespanMetric::new(11.into(), CommonMetricData {
+ name: "timespan_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, TimeUnit::Millisecond)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.timing_distribution_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static timing_distribution_metric: Lazy<TimingDistributionMetric> = Lazy::new(|| {
+ TimingDistributionMetric::new(12.into(), CommonMetricData {
+ name: "timing_distribution_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, TimeUnit::Nanosecond)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.memory_distribution_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static memory_distribution_metric: Lazy<MemoryDistributionMetric> = Lazy::new(|| {
+ MemoryDistributionMetric::new(13.into(), CommonMetricData {
+ name: "memory_distribution_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, MemoryUnit::Kilobyte)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.custom_distribution_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static custom_distribution_metric: Lazy<CustomDistributionMetric> = Lazy::new(|| {
+ CustomDistributionMetric::new(14.into(), CommonMetricData {
+ name: "custom_distribution_metric".into(),
+ category: "test".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, 0, 100, 100, HistogramType::Linear)
+ });
+
+}
+pub mod test_nested {
+ use crate::private::*;
+ use glean::CommonMetricData;
+ #[allow(unused_imports)] // HistogramType might be unusued, let's avoid warnings
+ use glean::HistogramType;
+ use once_cell::sync::Lazy;
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.uuid_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static uuid_metric: Lazy<UuidMetric> = Lazy::new(|| {
+ UuidMetric::new(15.into(), CommonMetricData {
+ name: "uuid_metric".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.datetime_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static datetime_metric: Lazy<DatetimeMetric> = Lazy::new(|| {
+ DatetimeMetric::new(16.into(), CommonMetricData {
+ name: "datetime_metric".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Application,
+ disabled: false,
+ ..Default::default()
+ }, TimeUnit::Millisecond)
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.event_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static event_metric: Lazy<EventMetric<NoExtraKeys>> = Lazy::new(|| {
+ EventMetric::new(17.into(), CommonMetricData {
+ name: "event_metric".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["events".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[derive(Default, Debug, Clone, Hash, Eq, PartialEq)]
+ pub struct EventMetricWithExtraExtra {
+ pub an_extra_key: Option<String>,
+ pub another_extra_key: Option<String>,
+ }
+
+ impl ExtraKeys for EventMetricWithExtraExtra {
+ const ALLOWED_KEYS: &'static [&'static str] = &["an_extra_key", "another_extra_key"];
+
+ fn into_ffi_extra(self) -> ::std::collections::HashMap<String, String> {
+ let mut map = ::std::collections::HashMap::new();
+ self.an_extra_key.and_then(|val| map.insert("an_extra_key".into(), val.to_string()));
+ self.another_extra_key.and_then(|val| map.insert("another_extra_key".into(), val.to_string()));
+ map
+ }
+ }
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.event_metric_with_extra
+ ///
+ /// A multi-line
+ /// description
+ pub static event_metric_with_extra: Lazy<EventMetric<EventMetricWithExtraExtra>> = Lazy::new(|| {
+ EventMetric::new(18.into(), CommonMetricData {
+ name: "event_metric_with_extra".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["events".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.quantity_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static quantity_metric: Lazy<QuantityMetric> = Lazy::new(|| {
+ QuantityMetric::new(19.into(), CommonMetricData {
+ name: "quantity_metric".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.rate_metric
+ ///
+ /// A multi-line
+ /// description
+ pub static rate_metric: Lazy<RateMetric> = Lazy::new(|| {
+ RateMetric::new(20.into(), CommonMetricData {
+ name: "rate_metric".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.rate_with_external_denominator
+ ///
+ /// A multi-line
+ /// description
+ pub static rate_with_external_denominator: Lazy<NumeratorMetric> = Lazy::new(|| {
+ NumeratorMetric::new(21.into(), CommonMetricData {
+ name: "rate_with_external_denominator".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ })
+ });
+
+ #[allow(non_upper_case_globals)]
+ /// generated from test.nested.external_denominator
+ ///
+ /// A multi-line
+ /// description
+ pub static external_denominator: Lazy<DenominatorMetric> = Lazy::new(|| {
+ DenominatorMetric::new(22.into(), CommonMetricData {
+ name: "external_denominator".into(),
+ category: "test.nested".into(),
+ send_in_pings: vec!["metrics".into()],
+ lifetime: Lifetime::Ping,
+ disabled: false,
+ ..Default::default()
+ }, vec![CommonMetricData {name: "rate_with_external_denominator".into(), category: "test.nested".into(), send_in_pings: vec!["metrics".into()], lifetime: Lifetime::Ping, disabled: false, ..Default::default()}])
+ });
+
+}
+
+#[allow(dead_code)]
+pub(crate) mod __glean_metric_maps {
+ use std::collections::HashMap;
+
+ use crate::metrics::extra_keys_len;
+ use crate::private::*;
+ use once_cell::sync::Lazy;
+
+ pub static BOOLEAN_MAP: Lazy<HashMap<MetricId, &Lazy<BooleanMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(1.into(), &super::test::boolean_metric);
+ map
+ });
+
+ pub static LABELED_BOOLEAN_MAP: Lazy<HashMap<MetricId, &Lazy<LabeledMetric<LabeledBooleanMetric>>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(2);
+ map.insert(2.into(), &super::test::labeled_boolean_metric);
+ map.insert(3.into(), &super::test::labeled_boolean_metric_labels);
+ map
+ });
+
+ pub static COUNTER_MAP: Lazy<HashMap<MetricId, &Lazy<CounterMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(4.into(), &super::test::counter_metric);
+ map
+ });
+
+ pub static LABELED_COUNTER_MAP: Lazy<HashMap<MetricId, &Lazy<LabeledMetric<LabeledCounterMetric>>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(2);
+ map.insert(5.into(), &super::test::labeled_counter_metric);
+ map.insert(6.into(), &super::test::labeled_counter_metric_labels);
+ map
+ });
+
+ pub static STRING_MAP: Lazy<HashMap<MetricId, &Lazy<StringMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(7.into(), &super::test::string_metric);
+ map
+ });
+
+ pub static LABELED_STRING_MAP: Lazy<HashMap<MetricId, &Lazy<LabeledMetric<LabeledStringMetric>>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(2);
+ map.insert(8.into(), &super::test::labeled_string_metric);
+ map.insert(9.into(), &super::test::labeled_string_metric_labels);
+ map
+ });
+
+ pub static STRING_LIST_MAP: Lazy<HashMap<MetricId, &Lazy<StringListMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(10.into(), &super::test::string_list_metric);
+ map
+ });
+
+ pub static TIMESPAN_MAP: Lazy<HashMap<MetricId, &Lazy<TimespanMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(11.into(), &super::test::timespan_metric);
+ map
+ });
+
+ pub static TIMING_DISTRIBUTION_MAP: Lazy<HashMap<MetricId, &Lazy<TimingDistributionMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(12.into(), &super::test::timing_distribution_metric);
+ map
+ });
+
+ pub static MEMORY_DISTRIBUTION_MAP: Lazy<HashMap<MetricId, &Lazy<MemoryDistributionMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(13.into(), &super::test::memory_distribution_metric);
+ map
+ });
+
+ pub static CUSTOM_DISTRIBUTION_MAP: Lazy<HashMap<MetricId, &Lazy<CustomDistributionMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(14.into(), &super::test::custom_distribution_metric);
+ map
+ });
+
+ pub static UUID_MAP: Lazy<HashMap<MetricId, &Lazy<UuidMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(15.into(), &super::test_nested::uuid_metric);
+ map
+ });
+
+ pub static DATETIME_MAP: Lazy<HashMap<MetricId, &Lazy<DatetimeMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(16.into(), &super::test_nested::datetime_metric);
+ map
+ });
+
+ pub static QUANTITY_MAP: Lazy<HashMap<MetricId, &Lazy<QuantityMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(19.into(), &super::test_nested::quantity_metric);
+ map
+ });
+
+ pub static RATE_MAP: Lazy<HashMap<MetricId, &Lazy<RateMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(20.into(), &super::test_nested::rate_metric);
+ map
+ });
+
+ pub static NUMERATOR_MAP: Lazy<HashMap<MetricId, &Lazy<NumeratorMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(21.into(), &super::test_nested::rate_with_external_denominator);
+ map
+ });
+
+ pub static DENOMINATOR_MAP: Lazy<HashMap<MetricId, &Lazy<DenominatorMetric>>> = Lazy::new(|| {
+ let mut map = HashMap::with_capacity(1);
+ map.insert(22.into(), &super::test_nested::external_denominator);
+ map
+ });
+
+
+ /// Wrapper to record an event based on its metric ID.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `extra` - An map of (extra key id, string) pairs.
+ /// The map will be decoded into the appropriate `ExtraKeys` type.
+ /// # Returns
+ ///
+ /// Returns `Ok(())` if the event was found and `record` was called with the given `extra`,
+ /// or an `EventRecordingError::InvalidId` if no event by that ID exists
+ /// or an `EventRecordingError::InvalidExtraKey` if the `extra` map could not be deserialized.
+ pub(crate) fn record_event_by_id(metric_id: u32, extra: HashMap<String, String>) -> Result<(), EventRecordingError> {
+ match metric_id {
+ 17 => {
+ assert!(
+ extra_keys_len(&super::test_nested::event_metric) != 0 || extra.is_empty(),
+ "No extra keys allowed, but some were passed"
+ );
+
+ super::test_nested::event_metric.record_raw(extra);
+ Ok(())
+ }
+ 18 => {
+ assert!(
+ extra_keys_len(&super::test_nested::event_metric_with_extra) != 0 || extra.is_empty(),
+ "No extra keys allowed, but some were passed"
+ );
+
+ super::test_nested::event_metric_with_extra.record_raw(extra);
+ Ok(())
+ }
+ _ => Err(EventRecordingError::InvalidId),
+ }
+ }
+
+ /// Wrapper to record an event based on its metric ID, with a provided timestamp.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `timestamp` - The time at which this event was recorded.
+ /// * `extra` - An map of (extra key id, string) pairs.
+ /// The map will be decoded into the appropriate `ExtraKeys` type.
+ /// # Returns
+ ///
+ /// Returns `Ok(())` if the event was found and `record` was called with the given `extra`,
+ /// or an `EventRecordingError::InvalidId` if no event by that ID exists
+ /// or an `EventRecordingError::InvalidExtraKey` if the event doesn't take extra pairs,
+ /// but some are passed in.
+ pub(crate) fn record_event_by_id_with_time(metric_id: MetricId, timestamp: u64, extra: HashMap<String, String>) -> Result<(), EventRecordingError> {
+ match metric_id {
+ MetricId(17) => {
+ if extra_keys_len(&super::test_nested::event_metric) == 0 && !extra.is_empty() {
+ return Err(EventRecordingError::InvalidExtraKey);
+ }
+
+ super::test_nested::event_metric.record_with_time(timestamp, extra);
+ Ok(())
+ }
+ MetricId(18) => {
+ if extra_keys_len(&super::test_nested::event_metric_with_extra) == 0 && !extra.is_empty() {
+ return Err(EventRecordingError::InvalidExtraKey);
+ }
+
+ super::test_nested::event_metric_with_extra.record_with_time(timestamp, extra);
+ Ok(())
+ }
+ _ => Err(EventRecordingError::InvalidId),
+ }
+ }
+
+ /// Wrapper to record an event based on its metric ID.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `extra` - An map of (string, string) pairs.
+ /// The map will be decoded into the appropriate `ExtraKeys` types.
+ /// # Returns
+ ///
+ /// Returns `Ok(())` if the event was found and `record` was called with the given `extra`,
+ /// or an `EventRecordingError::InvalidId` if no event by that ID exists
+ /// or an `EventRecordingError::InvalidExtraKey` if the `extra` map could not be deserialized.
+ pub(crate) fn record_event_by_id_with_strings(metric_id: u32, extra: HashMap<String, String>) -> Result<(), EventRecordingError> {
+ match metric_id {
+ 17 => {
+ assert!(
+ extra_keys_len(&super::test_nested::event_metric) != 0 || extra.is_empty(),
+ "No extra keys allowed, but some were passed"
+ );
+
+ super::test_nested::event_metric.record_raw(extra);
+ Ok(())
+ }
+ 18 => {
+ assert!(
+ extra_keys_len(&super::test_nested::event_metric_with_extra) != 0 || extra.is_empty(),
+ "No extra keys allowed, but some were passed"
+ );
+
+ super::test_nested::event_metric_with_extra.record_raw(extra);
+ Ok(())
+ }
+ _ => Err(EventRecordingError::InvalidId),
+ }
+ }
+
+ /// Wrapper to get the currently stored events for event metric.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `ping_name` - (Optional) The ping name to look into.
+ /// Defaults to the first value in `send_in_pings`.
+ ///
+ /// # Returns
+ ///
+ /// Returns the recorded events or `None` if nothing stored.
+ ///
+ /// # Panics
+ ///
+ /// Panics if no event by the given metric ID could be found.
+ pub(crate) fn event_test_get_value_wrapper(metric_id: u32, ping_name: Option<String>) -> Option<Vec<RecordedEvent>> {
+ match metric_id {
+ 17 => super::test_nested::event_metric.test_get_value(ping_name.as_deref()),
+ 18 => super::test_nested::event_metric_with_extra.test_get_value(ping_name.as_deref()),
+ _ => panic!("No event for metric id {}", metric_id),
+ }
+ }
+
+ /// Check the provided event for errors.
+ ///
+ /// # Arguments
+ ///
+ /// * `metric_id` - The metric's ID to look up
+ /// * `ping_name` - (Optional) The ping name to look into.
+ /// Defaults to the first value in `send_in_pings`.
+ ///
+ /// # Returns
+ ///
+ /// Returns a string for the recorded error or `None`.
+ ///
+ /// # Panics
+ ///
+ /// Panics if no event by the given metric ID could be found.
+ #[allow(unused_variables)]
+ pub(crate) fn event_test_get_error(metric_id: u32) -> Option<String> {
+ #[cfg(feature = "with_gecko")]
+ match metric_id {
+ 17 => test_get_errors!(super::test_nested::event_metric),
+ 18 => test_get_errors!(super::test_nested::event_metric_with_extra),
+ _ => panic!("No event for metric id {}", metric_id),
+ }
+
+ #[cfg(not(feature = "with_gecko"))]
+ {
+ return None;
+ }
+ }
+
+ pub(crate) mod submetric_maps {
+ use std::sync::{
+ atomic::AtomicU32,
+ RwLock,
+ };
+ use super::*;
+
+ pub(crate) const SUBMETRIC_BIT: u32 = 25;
+ pub(crate) static NEXT_LABELED_SUBMETRIC_ID: AtomicU32 = AtomicU32::new((1 << SUBMETRIC_BIT) + 1);
+ pub(crate) static LABELED_METRICS_TO_IDS: Lazy<RwLock<HashMap<(u32, String), u32>>> = Lazy::new(||
+ RwLock::new(HashMap::new())
+ );
+
+ pub static BOOLEAN_MAP: Lazy<RwLock<HashMap<MetricId, LabeledBooleanMetric>>> = Lazy::new(||
+ RwLock::new(HashMap::new())
+ );
+ pub static COUNTER_MAP: Lazy<RwLock<HashMap<MetricId, LabeledCounterMetric>>> = Lazy::new(||
+ RwLock::new(HashMap::new())
+ );
+ pub static STRING_MAP: Lazy<RwLock<HashMap<MetricId, LabeledStringMetric>>> = Lazy::new(||
+ RwLock::new(HashMap::new())
+ );
+ }
+}
+
diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output_cpp b/toolkit/components/glean/tests/pytest/metrics_test_output_cpp
new file mode 100644
index 0000000000..c9a9cbc619
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/metrics_test_output_cpp
@@ -0,0 +1,244 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Metrics_h
+#define mozilla_Metrics_h
+
+#include "mozilla/glean/bindings/MetricTypes.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/Maybe.h"
+#include "nsTArray.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla::glean {
+struct NoExtraKeys;
+
+namespace test {
+ /**
+ * generated from test.boolean_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::BooleanMetric boolean_metric(1);
+
+ /**
+ * generated from test.labeled_boolean_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::Labeled<impl::BooleanMetric> labeled_boolean_metric(2);
+
+ /**
+ * generated from test.labeled_boolean_metric_labels
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::Labeled<impl::BooleanMetric> labeled_boolean_metric_labels(3);
+
+ /**
+ * generated from test.counter_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::CounterMetric counter_metric(4);
+
+ /**
+ * generated from test.labeled_counter_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::Labeled<impl::CounterMetric> labeled_counter_metric(5);
+
+ /**
+ * generated from test.labeled_counter_metric_labels
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::Labeled<impl::CounterMetric> labeled_counter_metric_labels(6);
+
+ /**
+ * generated from test.string_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::StringMetric string_metric(7);
+
+ /**
+ * generated from test.labeled_string_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::Labeled<impl::StringMetric> labeled_string_metric(8);
+
+ /**
+ * generated from test.labeled_string_metric_labels
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::Labeled<impl::StringMetric> labeled_string_metric_labels(9);
+
+ /**
+ * generated from test.string_list_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::StringListMetric string_list_metric(10);
+
+ /**
+ * generated from test.timespan_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::TimespanMetric timespan_metric(11);
+
+ /**
+ * generated from test.timing_distribution_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::TimingDistributionMetric timing_distribution_metric(12);
+
+ /**
+ * generated from test.memory_distribution_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::MemoryDistributionMetric memory_distribution_metric(13);
+
+ /**
+ * generated from test.custom_distribution_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::CustomDistributionMetric custom_distribution_metric(14);
+
+}
+namespace test_nested {
+ /**
+ * generated from test.nested.uuid_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::UuidMetric uuid_metric(15);
+
+ /**
+ * generated from test.nested.datetime_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::DatetimeMetric datetime_metric(16);
+
+ /**
+ * generated from test.nested.event_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::EventMetric<NoExtraKeys> event_metric(17);
+
+ /**
+ * generated from test.nested.event_metric_with_extra
+ */
+ struct EventMetricWithExtraExtra {
+ mozilla::Maybe<nsCString> anExtraKey;
+ mozilla::Maybe<nsCString> anotherExtraKey;
+
+ Tuple<nsTArray<nsCString>, nsTArray<nsCString>> ToFfiExtra() const {
+ nsTArray<nsCString> extraKeys;
+ nsTArray<nsCString> extraValues;
+ if (anExtraKey) {
+ extraKeys.AppendElement()->AssignASCII("an_extra_key");
+ extraValues.EmplaceBack(anExtraKey.value());
+ }
+ if (anotherExtraKey) {
+ extraKeys.AppendElement()->AssignASCII("another_extra_key");
+ extraValues.EmplaceBack(anotherExtraKey.value());
+ }
+ return MakeTuple(std::move(extraKeys), std::move(extraValues));
+ }
+ };
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::EventMetric<EventMetricWithExtraExtra> event_metric_with_extra(18);
+
+ /**
+ * generated from test.nested.quantity_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::QuantityMetric quantity_metric(19);
+
+ /**
+ * generated from test.nested.rate_metric
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::RateMetric rate_metric(20);
+
+ /**
+ * generated from test.nested.rate_with_external_denominator
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::NumeratorMetric rate_with_external_denominator(21);
+
+ /**
+ * generated from test.nested.external_denominator
+ */
+ /**
+ * A multi-line
+ * description
+ */
+ constexpr impl::DenominatorMetric external_denominator(22);
+
+}
+
+} // namespace mozilla::glean
+
+#endif // mozilla_Metrics_h
diff --git a/toolkit/components/glean/tests/pytest/metrics_test_output_js b/toolkit/components/glean/tests/pytest/metrics_test_output_js
new file mode 100644
index 0000000000..3c682d5ee7
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/metrics_test_output_js
@@ -0,0 +1,342 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_GleanJSMetricsLookup_h
+#define mozilla_GleanJSMetricsLookup_h
+
+#include "mozilla/PerfectHash.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/glean/bindings/MetricTypes.h"
+#include "mozilla/glean/fog_ffi_generated.h"
+
+#define GLEAN_INDEX_BITS (32)
+#define GLEAN_TYPE_BITS (5)
+#define GLEAN_ID_BITS (27)
+#define GLEAN_TYPE_ID(id) ((id) >> GLEAN_ID_BITS)
+#define GLEAN_METRIC_ID(id) ((id) & ((1ULL << GLEAN_ID_BITS) - 1))
+#define GLEAN_OFFSET(entry) (entry & ((1ULL << GLEAN_INDEX_BITS) - 1))
+
+namespace mozilla::glean {
+
+// The category lookup table's entry type
+using category_entry_t = uint32_t;
+// The metric lookup table's entry type
+// This is a bitpacked type with 32 bits available to index into
+// the string table, 5 bits available to signify the metric type,
+// and the remaining 27 bits devoted to 2 "signal"
+// bits to signify important characteristics (metric's a labeled metric's
+// submetric, metric's been registered at runtime) and 25 bits
+// for built-in metric ids.
+// Gives room for 33554432 of each combination of
+// characteristics (which hopefully will prove to be enough).
+using metric_entry_t = uint64_t;
+
+static_assert(GLEAN_INDEX_BITS + GLEAN_TYPE_BITS + GLEAN_ID_BITS == sizeof(metric_entry_t) * 8, "Index, Type, and ID bits need to fit into a metric_entry_t");
+static_assert(GLEAN_TYPE_BITS + GLEAN_ID_BITS <= sizeof(uint32_t) * 8, "Metric Types and IDs need to fit into at most 32 bits");
+static_assert(2 < UINT32_MAX, "Too many metric categories generated.");
+static_assert(22 < 33554432, "Too many metrics generated. Need room for 2 signal bits.");
+static_assert(18 < 32, "Too many different metric types.");
+
+static already_AddRefed<nsISupports> NewMetricFromId(uint32_t id) {
+ uint32_t typeId = GLEAN_TYPE_ID(id);
+ uint32_t metricId = GLEAN_METRIC_ID(id);
+
+ switch (typeId) {
+ case 1: /* boolean */
+ {
+ return MakeAndAddRef<GleanBoolean>(metricId);
+ }
+ case 2: /* labeled_boolean */
+ {
+ return MakeAndAddRef<GleanLabeled>(metricId, 2);
+ }
+ case 3: /* counter */
+ {
+ return MakeAndAddRef<GleanCounter>(metricId);
+ }
+ case 4: /* labeled_counter */
+ {
+ return MakeAndAddRef<GleanLabeled>(metricId, 4);
+ }
+ case 5: /* string */
+ {
+ return MakeAndAddRef<GleanString>(metricId);
+ }
+ case 6: /* labeled_string */
+ {
+ return MakeAndAddRef<GleanLabeled>(metricId, 6);
+ }
+ case 7: /* string_list */
+ {
+ return MakeAndAddRef<GleanStringList>(metricId);
+ }
+ case 8: /* timespan */
+ {
+ return MakeAndAddRef<GleanTimespan>(metricId);
+ }
+ case 9: /* timing_distribution */
+ {
+ return MakeAndAddRef<GleanTimingDistribution>(metricId);
+ }
+ case 10: /* memory_distribution */
+ {
+ return MakeAndAddRef<GleanMemoryDistribution>(metricId);
+ }
+ case 11: /* custom_distribution */
+ {
+ return MakeAndAddRef<GleanCustomDistribution>(metricId);
+ }
+ case 12: /* uuid */
+ {
+ return MakeAndAddRef<GleanUuid>(metricId);
+ }
+ case 13: /* datetime */
+ {
+ return MakeAndAddRef<GleanDatetime>(metricId);
+ }
+ case 14: /* event */
+ {
+ return MakeAndAddRef<GleanEvent>(metricId);
+ }
+ case 15: /* quantity */
+ {
+ return MakeAndAddRef<GleanQuantity>(metricId);
+ }
+ case 16: /* rate */
+ {
+ return MakeAndAddRef<GleanRate>(metricId);
+ }
+ case 17: /* numerator */
+ {
+ return MakeAndAddRef<GleanNumerator>(metricId);
+ }
+ case 18: /* denominator */
+ {
+ return MakeAndAddRef<GleanDenominator>(metricId);
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid type ID reached when trying to instantiate a new metric");
+ return nullptr;
+ }
+}
+
+/**
+ * Create a submetric instance for a labeled metric of the provided type and id for the given label.
+ * Assigns or retrieves an id for the submetric from the SDK.
+ *
+ * @param aParentTypeId - The type of the parent labeled metric identified as a number generated during codegen.
+ * Only used to identify which X of LabeledX you are so that X can be created here.
+ * @param aParentMetricId - The metric id for the parent labeled metric.
+ * @param aLabel - The label for the submetric. Might not adhere to the SDK label format.
+ * @param aSubmetricId - an outparam which is assigned the submetric's SDK-generated submetric id.
+ * Used only by GIFFT.
+ */
+static already_AddRefed<nsISupports> NewSubMetricFromIds(uint32_t aParentTypeId, uint32_t aParentMetricId, const nsACString& aLabel, uint32_t* aSubmetricId) {
+ switch (aParentTypeId) {
+ case 2: { /* labeled_boolean */
+ auto id = impl::fog_labeled_boolean_get(aParentMetricId, &aLabel);
+ *aSubmetricId = id;
+ return MakeAndAddRef<GleanBoolean>(id);
+ }
+ case 4: { /* labeled_counter */
+ auto id = impl::fog_labeled_counter_get(aParentMetricId, &aLabel);
+ *aSubmetricId = id;
+ return MakeAndAddRef<GleanCounter>(id);
+ }
+ case 6: { /* labeled_string */
+ auto id = impl::fog_labeled_string_get(aParentMetricId, &aLabel);
+ *aSubmetricId = id;
+ return MakeAndAddRef<GleanString>(id);
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("Invalid type ID for submetric.");
+ return nullptr;
+ }
+ }
+}
+
+static Maybe<uint32_t> category_result_check(const nsACString& aKey, category_entry_t entry);
+static Maybe<uint32_t> metric_result_check(const nsACString& aKey, metric_entry_t entry);
+
+#if defined(_MSC_VER) && !defined(__clang__)
+const char gCategoryStringTable[] = {
+#else
+constexpr char gCategoryStringTable[] = {
+#endif
+ /* 0 - "test" */ 't', 'e', 's', 't', '\0',
+ /* 5 - "testNested" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '\0',
+};
+
+
+static_assert(sizeof(gCategoryStringTable) < UINT32_MAX, "Category string table is too large.");
+
+const category_entry_t sCategoryByNameLookupEntries[] = {
+ 5ul,
+ 0ul
+};
+
+
+
+static Maybe<uint32_t>
+CategoryByNameLookup(const nsACString& aKey)
+{
+ static const uint8_t BASES[] = {
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+
+
+ const char* bytes = aKey.BeginReading();
+ size_t length = aKey.Length();
+ auto& entry = mozilla::perfecthash::Lookup(bytes, length, BASES,
+ sCategoryByNameLookupEntries);
+ return category_result_check(aKey, entry);
+}
+
+
+#if defined(_MSC_VER) && !defined(__clang__)
+const char gMetricStringTable[] = {
+#else
+constexpr char gMetricStringTable[] = {
+#endif
+ /* 0 - "test.booleanMetric" */ 't', 'e', 's', 't', '.', 'b', 'o', 'o', 'l', 'e', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 19 - "test.labeledBooleanMetric" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 45 - "test.labeledBooleanMetricLabels" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'B', 'o', 'o', 'l', 'e', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', 'L', 'a', 'b', 'e', 'l', 's', '\0',
+ /* 77 - "test.counterMetric" */ 't', 'e', 's', 't', '.', 'c', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 96 - "test.labeledCounterMetric" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'C', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 122 - "test.labeledCounterMetricLabels" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'C', 'o', 'u', 'n', 't', 'e', 'r', 'M', 'e', 't', 'r', 'i', 'c', 'L', 'a', 'b', 'e', 'l', 's', '\0',
+ /* 154 - "test.stringMetric" */ 't', 'e', 's', 't', '.', 's', 't', 'r', 'i', 'n', 'g', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 172 - "test.labeledStringMetric" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'S', 't', 'r', 'i', 'n', 'g', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 197 - "test.labeledStringMetricLabels" */ 't', 'e', 's', 't', '.', 'l', 'a', 'b', 'e', 'l', 'e', 'd', 'S', 't', 'r', 'i', 'n', 'g', 'M', 'e', 't', 'r', 'i', 'c', 'L', 'a', 'b', 'e', 'l', 's', '\0',
+ /* 228 - "test.stringListMetric" */ 't', 'e', 's', 't', '.', 's', 't', 'r', 'i', 'n', 'g', 'L', 'i', 's', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 250 - "test.timespanMetric" */ 't', 'e', 's', 't', '.', 't', 'i', 'm', 'e', 's', 'p', 'a', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 270 - "test.timingDistributionMetric" */ 't', 'e', 's', 't', '.', 't', 'i', 'm', 'i', 'n', 'g', 'D', 'i', 's', 't', 'r', 'i', 'b', 'u', 't', 'i', 'o', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 300 - "test.memoryDistributionMetric" */ 't', 'e', 's', 't', '.', 'm', 'e', 'm', 'o', 'r', 'y', 'D', 'i', 's', 't', 'r', 'i', 'b', 'u', 't', 'i', 'o', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 330 - "test.customDistributionMetric" */ 't', 'e', 's', 't', '.', 'c', 'u', 's', 't', 'o', 'm', 'D', 'i', 's', 't', 'r', 'i', 'b', 'u', 't', 'i', 'o', 'n', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 360 - "testNested.uuidMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'u', 'u', 'i', 'd', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 382 - "testNested.datetimeMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'd', 'a', 't', 'e', 't', 'i', 'm', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 408 - "testNested.eventMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 431 - "testNested.eventMetricWithExtra" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'v', 'e', 'n', 't', 'M', 'e', 't', 'r', 'i', 'c', 'W', 'i', 't', 'h', 'E', 'x', 't', 'r', 'a', '\0',
+ /* 463 - "testNested.quantityMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'q', 'u', 'a', 'n', 't', 'i', 't', 'y', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 489 - "testNested.rateMetric" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'M', 'e', 't', 'r', 'i', 'c', '\0',
+ /* 511 - "testNested.rateWithExternalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'r', 'a', 't', 'e', 'W', 'i', 't', 'h', 'E', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0',
+ /* 550 - "testNested.externalDenominator" */ 't', 'e', 's', 't', 'N', 'e', 's', 't', 'e', 'd', '.', 'e', 'x', 't', 'e', 'r', 'n', 'a', 'l', 'D', 'e', 'n', 'o', 'm', 'i', 'n', 'a', 't', 'o', 'r', '\0',
+};
+
+
+static_assert(sizeof(gMetricStringTable) < 4294967296, "Metric string table is too large.");
+
+const metric_entry_t sMetricByNameLookupEntries[] = {
+ 6341068335467200842ull,
+ 1729382274090139725ull,
+ 1152921513196781587ull,
+ 2305843034983497850ull,
+ 5764607578868810028ull,
+ 8070450605262373272ull,
+ 3458764548180279468ull,
+ 10376293635950903846ull,
+ 8646911366155731407ull,
+ 9799832879352513023ull,
+ 2882303791581888666ull,
+ 1152921517491748909ull,
+ 4035225309073637604ull,
+ 6917529092065591656ull,
+ 4611686065672028410ull,
+ 7493989848663982462ull,
+ 8070450609557340591ull,
+ 3458764552475246789ull,
+ 5188146822270419214ull,
+ 576460756598390784ull,
+ 2305843030688530528ull,
+ 9223372122754122217ull
+};
+
+
+
+static Maybe<uint32_t>
+MetricByNameLookup(const nsACString& aKey)
+{
+ static const uint8_t BASES[] = {
+ 0, 0, 0, 1, 0, 2, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0,
+ 1, 1, 0, 0, 1, 0, 0, 1, 0, 4, 0, 0, 1, 0, 1, 0,
+ 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 3, 0, 6, 0, 0,
+ 0, 0, 10, 0, 0, 1, 0, 0, 0, 0, 4, 0, 0, 0, 0, 17,
+ };
+
+
+ const char* bytes = aKey.BeginReading();
+ size_t length = aKey.Length();
+ auto& entry = mozilla::perfecthash::Lookup(bytes, length, BASES,
+ sMetricByNameLookupEntries);
+ return metric_result_check(aKey, entry);
+}
+
+
+/**
+ * Get a category's name from the string table.
+ */
+static const char* GetCategoryName(category_entry_t entry) {
+ MOZ_ASSERT(entry < sizeof(gCategoryStringTable), "Entry identifier offset larger than string table");
+ return &gCategoryStringTable[entry];
+}
+
+/**
+ * Get a metric's identifier from the string table.
+ */
+static const char* GetMetricIdentifier(metric_entry_t entry) {
+ uint32_t offset = GLEAN_OFFSET(entry);
+ MOZ_ASSERT(offset < sizeof(gMetricStringTable), "Entry identifier offset larger than string table");
+ return &gMetricStringTable[offset];
+}
+
+/**
+ * Check that the found entry is pointing to the right key
+ * and return it.
+ * Or return `Nothing()` if the entry was not found.
+ */
+static Maybe<uint32_t> category_result_check(const nsACString& aKey, category_entry_t entry) {
+ if (MOZ_UNLIKELY(entry > sizeof(gCategoryStringTable))) {
+ return Nothing();
+ }
+ if (aKey.EqualsASCII(gCategoryStringTable + entry)) {
+ return Some(entry);
+ }
+ return Nothing();
+}
+
+/**
+ * Check if the found entry index is pointing to the right key
+ * and return the corresponding metric ID.
+ * Or return `Nothing()` if the entry was not found.
+ */
+static Maybe<uint32_t> metric_result_check(const nsACString& aKey, uint64_t entry) {
+ uint32_t metricId = entry >> GLEAN_INDEX_BITS;
+ uint32_t offset = GLEAN_OFFSET(entry);
+
+ if (offset > sizeof(gMetricStringTable)) {
+ return Nothing();
+ }
+
+ if (aKey.EqualsASCII(gMetricStringTable + offset)) {
+ return Some(metricId);
+ }
+
+ return Nothing();
+}
+
+
+#undef GLEAN_INDEX_BITS
+#undef GLEAN_ID_BITS
+#undef GLEAN_TYPE_ID
+#undef GLEAN_METRIC_ID
+#undef GLEAN_OFFSET
+
+} // namespace mozilla::glean
+#endif // mozilla_GleanJSMetricsLookup_h
diff --git a/toolkit/components/glean/tests/pytest/pings_test.yaml b/toolkit/components/glean/tests/pytest/pings_test.yaml
new file mode 100644
index 0000000000..efa6ba9e0a
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/pings_test.yaml
@@ -0,0 +1,116 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This file defines the built-in pings that are recorded by the Glean SDK. They
+# are automatically converted to Kotlin code at build time using the
+# `glean_parser` PyPI package.
+
+---
+$schema: moz://mozilla.org/schemas/glean/pings/1-0-0
+
+not-baseline:
+ description: >
+ This ping is intended to provide metrics that are managed by the library
+ itself, and not explicitly set by the application or included in the
+ application's `metrics.yaml` file.
+ The `baseline` ping is automatically sent when the application is moved to
+ the background.
+ include_client_id: true
+ bugs:
+ - https://bugzilla.mozilla.org/1512938
+ - https://bugzilla.mozilla.org/1599877
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1512938#c3
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1599877#c25
+ notification_emails:
+ - glean-team@mozilla.com
+ reasons:
+ dirty_startup: |
+ The ping was submitted at startup, because the application process was
+ killed before the Glean SDK had the chance to generate this ping, when
+ going to background, in the last session.
+
+ *Note*: this ping will not contain the `glean.baseline.duration` metric.
+ background: |
+ The ping was submitted before going to background.
+ foreground: |
+ The ping was submitted when the application went to foreground, which
+ includes when the application starts.
+
+ *Note*: this ping will not contain the `glean.baseline.duration` metric.
+
+not-metrics:
+ description: >
+ The `metrics` ping is intended for all of the metrics that are explicitly
+ set by the application or are included in the application's `metrics.yaml`
+ file (except events).
+ The reported data is tied to the ping's *measurement window*, which is the
+ time between the collection of two `metrics` ping. Ideally, this window is
+ expected to be about 24 hours, given that the collection is scheduled daily
+ at 4AM. Data in the `ping_info` section of the ping can be used to infer the
+ length of this window.
+ include_client_id: true
+ bugs:
+ - https://bugzilla.mozilla.org/1512938
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1512938#c3
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1557048#c13
+ notification_emails:
+ - glean-team@mozilla.com
+ reasons:
+ overdue: |
+ The last ping wasn't submitted on the current calendar day, but it's after
+ 4am, so this ping submitted immediately
+ today: |
+ The last ping wasn't submitted on the current calendar day, but it is
+ still before 4am, so schedule to send this ping on the current calendar
+ day at 4am.
+ tomorrow: |
+ The last ping was already submitted on the current calendar day, so
+ schedule this ping for the next calendar day at 4am.
+ upgrade: |
+ This ping was submitted at startup because the application was just
+ upgraded.
+ reschedule: |
+ A ping was just submitted. This ping was rescheduled for the next calendar
+ day at 4am.
+
+not-events:
+ description: >
+ The events ping's purpose is to transport all of the event metric
+ information. The `events` ping is automatically sent when the application is
+ moved to the background.
+ include_client_id: true
+ bugs:
+ - https://bugzilla.mozilla.org/1512938
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1512938#c3
+ notification_emails:
+ - glean-team@mozilla.com
+ reasons:
+ startup: |
+ The ping was submitted at startup. The events ping is always sent if there
+ are any pending events at startup, because event timestamps can not be
+ mixed across runs of the application.
+ background: |
+ The ping was submitted before going to background.
+ max_capacity: |
+ The maximum number of events was reached (default 500 events).
+
+not-deletion-request:
+ description: >
+ This ping is submitted when a user opts out of
+ sending technical and interaction data to Mozilla.
+ This ping is intended to communicate to the Data Pipeline
+ that the user wishes to have their reported Telemetry data deleted.
+ As such it attempts to send itself at the moment the user
+ opts out of data collection.
+ include_client_id: true
+ send_if_empty: true
+ bugs:
+ - https://bugzilla.mozilla.org/1587095
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1587095#c6
+ notification_emails:
+ - glean-team@mozilla.com
diff --git a/toolkit/components/glean/tests/pytest/pings_test_output b/toolkit/components/glean/tests/pytest/pings_test_output
new file mode 100644
index 0000000000..a6e32ddadf
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/pings_test_output
@@ -0,0 +1,105 @@
+// -*- mode: Rust -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+use crate::private::Ping;
+use once_cell::sync::Lazy;
+
+#[allow(non_upper_case_globals)]
+/// This ping is intended to provide metrics that are managed by the library
+/// itself, and not explicitly set by the application or included in the
+/// application's `metrics.yaml` file. The `baseline` ping is automatically sent
+/// when the application is moved to the background.
+pub static not_baseline: Lazy<Ping> = Lazy::new(|| {
+ Ping::new(
+ "not-baseline",
+ true,
+ false,
+ vec!["background".into(), "dirty_startup".into(), "foreground".into()],
+ )
+});
+
+#[allow(non_upper_case_globals)]
+/// The `metrics` ping is intended for all of the metrics that are explicitly set
+/// by the application or are included in the application's `metrics.yaml` file
+/// (except events). The reported data is tied to the ping's *measurement window*,
+/// which is the time between the collection of two `metrics` ping. Ideally, this
+/// window is expected to be about 24 hours, given that the collection is scheduled
+/// daily at 4AM. Data in the `ping_info` section of the ping can be used to infer
+/// the length of this window.
+pub static not_metrics: Lazy<Ping> = Lazy::new(|| {
+ Ping::new(
+ "not-metrics",
+ true,
+ false,
+ vec!["overdue".into(), "reschedule".into(), "today".into(), "tomorrow".into(), "upgrade".into()],
+ )
+});
+
+#[allow(non_upper_case_globals)]
+/// The events ping's purpose is to transport all of the event metric information.
+/// The `events` ping is automatically sent when the application is moved to the
+/// background.
+pub static not_events: Lazy<Ping> = Lazy::new(|| {
+ Ping::new(
+ "not-events",
+ true,
+ false,
+ vec!["background".into(), "max_capacity".into(), "startup".into()],
+ )
+});
+
+#[allow(non_upper_case_globals)]
+/// This ping is submitted when a user opts out of sending technical and
+/// interaction data to Mozilla. This ping is intended to communicate to the Data
+/// Pipeline that the user wishes to have their reported Telemetry data deleted. As
+/// such it attempts to send itself at the moment the user opts out of data
+/// collection.
+pub static not_deletion_request: Lazy<Ping> = Lazy::new(|| {
+ Ping::new(
+ "not-deletion-request",
+ true,
+ true,
+ vec![],
+ )
+});
+
+
+/// Instantiate each custom ping once to trigger registration.
+#[doc(hidden)]
+pub fn register_pings() {
+ let _ = &*not_baseline;
+ let _ = &*not_metrics;
+ let _ = &*not_events;
+ let _ = &*not_deletion_request;
+}
+
+#[cfg(feature = "with_gecko")]
+pub(crate) fn submit_ping_by_id(id: u32, reason: Option<&str>) {
+ if id & (1 << crate::factory::DYNAMIC_PING_BIT) > 0 {
+ let map = crate::factory::__jog_metric_maps::PING_MAP
+ .read()
+ .expect("Read lock for dynamic ping map was poisoned!");
+ if let Some(ping) = map.get(&id) {
+ ping.submit(reason);
+ } else {
+ // TODO: instrument this error.
+ log::error!("Cannot submit unknown dynamic ping {} by id.", id);
+ }
+ return;
+ }
+ match id {
+ 1 => not_baseline.submit(reason),
+ 2 => not_metrics.submit(reason),
+ 3 => not_events.submit(reason),
+ 4 => not_deletion_request.submit(reason),
+ _ => {
+ // TODO: instrument this error.
+ log::error!("Cannot submit unknown ping {} by id.", id);
+ }
+ }
+}
diff --git a/toolkit/components/glean/tests/pytest/pings_test_output_cpp b/toolkit/components/glean/tests/pytest/pings_test_output_cpp
new file mode 100644
index 0000000000..7f9cc03947
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/pings_test_output_cpp
@@ -0,0 +1,62 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glean_Pings_h
+#define mozilla_glean_Pings_h
+
+#include "mozilla/glean/bindings/Ping.h"
+
+namespace mozilla::glean_pings {
+
+/*
+ * Generated from not-baseline.
+ *
+ * This ping is intended to provide metrics that are managed by the library
+ * itself, and not explicitly set by the application or included in the
+ * application's `metrics.yaml` file. The `baseline` ping is automatically sent
+ * when the application is moved to the background.
+ */
+constexpr glean::impl::Ping NotBaseline(1);
+
+/*
+ * Generated from not-metrics.
+ *
+ * The `metrics` ping is intended for all of the metrics that are explicitly set
+ * by the application or are included in the application's `metrics.yaml` file
+ * (except events). The reported data is tied to the ping's *measurement window*,
+ * which is the time between the collection of two `metrics` ping. Ideally, this
+ * window is expected to be about 24 hours, given that the collection is scheduled
+ * daily at 4AM. Data in the `ping_info` section of the ping can be used to infer
+ * the length of this window.
+ */
+constexpr glean::impl::Ping NotMetrics(2);
+
+/*
+ * Generated from not-events.
+ *
+ * The events ping's purpose is to transport all of the event metric information.
+ * The `events` ping is automatically sent when the application is moved to the
+ * background.
+ */
+constexpr glean::impl::Ping NotEvents(3);
+
+/*
+ * Generated from not-deletion-request.
+ *
+ * This ping is submitted when a user opts out of sending technical and
+ * interaction data to Mozilla. This ping is intended to communicate to the Data
+ * Pipeline that the user wishes to have their reported Telemetry data deleted. As
+ * such it attempts to send itself at the moment the user opts out of data
+ * collection.
+ */
+constexpr glean::impl::Ping NotDeletionRequest(4);
+
+
+} // namespace mozilla::glean_pings
+
+#endif // mozilla_glean_Pings_h
diff --git a/toolkit/components/glean/tests/pytest/pings_test_output_js b/toolkit/components/glean/tests/pytest/pings_test_output_js
new file mode 100644
index 0000000000..ecbc6ccf7c
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/pings_test_output_js
@@ -0,0 +1,99 @@
+// -*- mode: C++ -*-
+
+// AUTOGENERATED BY glean_parser. DO NOT EDIT.
+
+/* 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_GleanJSPingsLookup_h
+#define mozilla_GleanJSPingsLookup_h
+
+#define GLEAN_PING_INDEX_BITS (16)
+#define GLEAN_PING_ID(entry) ((entry) >> GLEAN_PING_INDEX_BITS)
+#define GLEAN_PING_INDEX(entry) ((entry) & ((1UL << GLEAN_PING_INDEX_BITS) - 1))
+
+namespace mozilla::glean {
+
+// Contains the ping id and the index into the ping string table.
+using ping_entry_t = uint32_t;
+
+static Maybe<uint32_t> ping_result_check(const nsACString& aKey, ping_entry_t aEntry);
+
+#if defined(_MSC_VER) && !defined(__clang__)
+const char gPingStringTable[] = {
+#else
+constexpr char gPingStringTable[] = {
+#endif
+ /* 0 - "notBaseline" */ 'n', 'o', 't', 'B', 'a', 's', 'e', 'l', 'i', 'n', 'e', '\0',
+ /* 12 - "notMetrics" */ 'n', 'o', 't', 'M', 'e', 't', 'r', 'i', 'c', 's', '\0',
+ /* 23 - "notEvents" */ 'n', 'o', 't', 'E', 'v', 'e', 'n', 't', 's', '\0',
+ /* 33 - "notDeletionRequest" */ 'n', 'o', 't', 'D', 'e', 'l', 'e', 't', 'i', 'o', 'n', 'R', 'e', 'q', 'u', 'e', 's', 't', '\0',
+};
+
+
+
+const ping_entry_t sPingByNameLookupEntries[] = {
+ 65536,
+ 196631,
+ 262177,
+ 131084
+};
+
+
+
+static Maybe<uint32_t>
+PingByNameLookup(const nsACString& aKey)
+{
+ static const uint8_t BASES[] = {
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ };
+
+
+ const char* bytes = aKey.BeginReading();
+ size_t length = aKey.Length();
+ auto& entry = mozilla::perfecthash::Lookup(bytes, length, BASES,
+ sPingByNameLookupEntries);
+ return ping_result_check(aKey, entry);
+}
+
+
+/**
+ * Get a ping's name given its entry from the PHF.
+ */
+static const char* GetPingName(ping_entry_t aEntry) {
+ uint32_t idx = GLEAN_PING_INDEX(aEntry);
+ MOZ_ASSERT(idx < sizeof(gPingStringTable), "Ping index larger than string table");
+ return &gPingStringTable[idx];
+}
+
+/**
+ * Check if the found entry is pointing at the correct ping.
+ * PHF can false-positive a result when the key isn't present, so we check
+ * for a string match. If it fails, return Nothing(). If we found it,
+ * return the ping's id.
+ */
+static Maybe<uint32_t> ping_result_check(const nsACString& aKey, ping_entry_t aEntry) {
+ uint32_t idx = GLEAN_PING_INDEX(aEntry);
+ uint32_t id = GLEAN_PING_ID(aEntry);
+
+ if (MOZ_UNLIKELY(idx > sizeof(gPingStringTable))) {
+ return Nothing();
+ }
+
+ if (aKey.EqualsASCII(&gPingStringTable[idx])) {
+ return Some(id);
+ }
+
+ return Nothing();
+}
+
+#undef GLEAN_PING_INDEX_BITS
+#undef GLEAN_PING_ID
+#undef GLEAN_PING_INDEX
+
+} // namespace mozilla::glean
+#endif // mozilla_GleanJSPingsLookup_h
diff --git a/toolkit/components/glean/tests/pytest/python.ini b/toolkit/components/glean/tests/pytest/python.ini
new file mode 100644
index 0000000000..87573cf519
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/python.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+subsuite = fog
+
+[test_gifft.py]
+[test_glean_parser_rust.py]
+[test_glean_parser_cpp.py]
+[test_glean_parser_js.py]
+[test_jogfile_output.py]
+[test_no_expired_metrics.py]
+[test_yaml_indices.py]
diff --git a/toolkit/components/glean/tests/pytest/test_gifft.py b/toolkit/components/glean/tests/pytest/test_gifft.py
new file mode 100644
index 0000000000..5de0640bca
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/test_gifft.py
@@ -0,0 +1,49 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import io
+import sys
+from os import path
+from pathlib import Path
+
+import mozunit
+from expect_helper import expect
+
+# Shenanigans to import the FOG glean_parser runner
+FOG_ROOT_PATH = path.abspath(
+ path.join(path.dirname(__file__), path.pardir, path.pardir)
+)
+sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext"))
+import run_glean_parser
+
+
+def test_gifft_codegen():
+ """
+ A regression test. Very fragile.
+ It generates C++ for GIFFT for metrics_test.yaml and compares it
+ byte-for-byte with expected output C++ files.
+ To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite:
+
+ UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest
+ """
+
+ options = {"allow_reserved": False}
+ here_path = Path(path.dirname(__file__))
+ input_files = [here_path / "metrics_test.yaml"]
+
+ all_objs, options = run_glean_parser.parse_with_options(input_files, options)
+
+ for probe_type in ("Event", "Histogram", "Scalar"):
+ output_fd = io.StringIO()
+ cpp_fd = io.StringIO()
+ run_glean_parser.output_gifft_map(output_fd, probe_type, all_objs, cpp_fd)
+
+ expect(here_path / f"gifft_output_{probe_type}", output_fd.getvalue())
+
+ if probe_type == "Event":
+ expect(here_path / "gifft_output_EventExtra", cpp_fd.getvalue())
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/toolkit/components/glean/tests/pytest/test_glean_parser_cpp.py b/toolkit/components/glean/tests/pytest/test_glean_parser_cpp.py
new file mode 100644
index 0000000000..05fd691782
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/test_glean_parser_cpp.py
@@ -0,0 +1,70 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import io
+import sys
+from os import path
+from pathlib import Path
+
+import mozunit
+from expect_helper import expect
+
+# Shenanigans to import the cpp outputter extension
+FOG_ROOT_PATH = path.abspath(
+ path.join(path.dirname(__file__), path.pardir, path.pardir)
+)
+sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext"))
+import cpp
+import run_glean_parser
+
+
+def test_all_metric_types():
+ """Honestly, this is a pretty bad test.
+ It generates C++ for a given test metrics.yaml and compares it byte-for-byte
+ with an expected output C++ file.
+ Expect it to be fragile.
+ To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite:
+
+ UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "metrics_test.yaml"))]
+
+ all_objs, options = run_glean_parser.parse_with_options(input_files, options)
+
+ output_fd = io.StringIO()
+ cpp.output_cpp(all_objs, output_fd, options)
+
+ expect(
+ path.join(path.dirname(__file__), "metrics_test_output_cpp"),
+ output_fd.getvalue(),
+ )
+
+
+def test_fake_pings():
+ """Another similarly-fragile test.
+ It generates C++ for pings_test.yaml, comparing it byte-for-byte
+ with an expected output C++ file `pings_test_output_cpp`.
+ Expect it to be fragile.
+ To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite:
+
+ UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "pings_test.yaml"))]
+
+ all_objs, options = run_glean_parser.parse_with_options(input_files, options)
+
+ output_fd = io.StringIO()
+ cpp.output_cpp(all_objs, output_fd, options)
+
+ expect(
+ path.join(path.dirname(__file__), "pings_test_output_cpp"), output_fd.getvalue()
+ )
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/toolkit/components/glean/tests/pytest/test_glean_parser_js.py b/toolkit/components/glean/tests/pytest/test_glean_parser_js.py
new file mode 100644
index 0000000000..81026231a6
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/test_glean_parser_js.py
@@ -0,0 +1,71 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import io
+import sys
+from os import path
+from pathlib import Path
+
+import mozunit
+from expect_helper import expect
+
+# Shenanigans to import the js outputter extension
+FOG_ROOT_PATH = path.abspath(
+ path.join(path.dirname(__file__), path.pardir, path.pardir)
+)
+sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext"))
+import run_glean_parser
+
+import js
+
+
+def test_all_metric_types():
+ """Honestly, this is a pretty bad test.
+ It generates C++ for a given test metrics.yaml and compares it byte-for-byte
+ with an expected output C++ file.
+ Expect it to be fragile.
+ To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite:
+
+ UPDATE_EXPECT=1 mach test toolkit/components/glean/tests/pytest
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "metrics_test.yaml"))]
+
+ all_objs, options = run_glean_parser.parse_with_options(input_files, options)
+
+ output_fd = io.StringIO()
+ js.output_js(all_objs, output_fd, options)
+
+ expect(
+ path.join(path.dirname(__file__), "metrics_test_output_js"),
+ output_fd.getvalue(),
+ )
+
+
+def test_fake_pings():
+ """Another similarly-fragile test.
+ It generates C++ for pings_test.yaml, comparing it byte-for-byte
+ with an expected output C++ file `pings_test_output_js`.
+ Expect it to be fragile.
+ To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite:
+
+ UPDATE_EXPECT=1 mach test toolkit/components/glean/tests/pytest
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "pings_test.yaml"))]
+
+ all_objs, options = run_glean_parser.parse_with_options(input_files, options)
+
+ output_fd = io.StringIO()
+ js.output_js(all_objs, output_fd, options)
+
+ expect(
+ path.join(path.dirname(__file__), "pings_test_output_js"), output_fd.getvalue()
+ )
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/toolkit/components/glean/tests/pytest/test_glean_parser_rust.py b/toolkit/components/glean/tests/pytest/test_glean_parser_rust.py
new file mode 100644
index 0000000000..a586b0419b
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/test_glean_parser_rust.py
@@ -0,0 +1,89 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import io
+import sys
+from os import path
+from pathlib import Path
+
+import mozunit
+from expect_helper import expect
+
+# Shenanigans to import the rust outputter extension
+FOG_ROOT_PATH = path.abspath(
+ path.join(path.dirname(__file__), path.pardir, path.pardir)
+)
+sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext"))
+import run_glean_parser
+import rust
+
+# Shenanigans to import the in-tree glean_parser
+GECKO_PATH = path.join(FOG_ROOT_PATH, path.pardir, path.pardir, path.pardir)
+sys.path.append(path.join(GECKO_PATH, "third_party", "python", "glean_parser"))
+
+
+def test_all_metric_types():
+ """Honestly, this is a pretty bad test.
+ It generates Rust for a given test metrics.yaml and compares it byte-for-byte
+ with an expected output Rust file.
+ Expect it to be fragile.
+ To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite:
+
+ UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "metrics_test.yaml"))]
+
+ all_objs, options = run_glean_parser.parse_with_options(input_files, options)
+
+ output_fd = io.StringIO()
+ rust.output_rust(all_objs, output_fd, options)
+
+ expect(
+ path.join(path.dirname(__file__), "metrics_test_output"), output_fd.getvalue()
+ )
+
+
+def test_fake_pings():
+ """Another similarly-bad test.
+ It generates Rust for pings_test.yaml, comparing it byte-for-byte
+ with an expected output Rust file.
+ Expect it to be fragile.
+ To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite:
+
+ UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest
+ """
+
+ options = {"allow_reserved": False}
+ input_files = [Path(path.join(path.dirname(__file__), "pings_test.yaml"))]
+
+ all_objs, options = run_glean_parser.parse_with_options(input_files, options)
+
+ output_fd = io.StringIO()
+ rust.output_rust(all_objs, output_fd, options)
+
+ expect(path.join(path.dirname(__file__), "pings_test_output"), output_fd.getvalue())
+
+
+def test_expires_version():
+ """This test relies on the intermediary object format output by glean_parser.
+ Expect it to be fragile on glean_parser updates that change that format.
+ """
+
+ # The test file has 41, 42, 100. Use 42.0a1 here to ensure "expires == version" means expired.
+ options = run_glean_parser.get_parser_options("42.0a1")
+ input_files = [
+ Path(path.join(path.dirname(__file__), "metrics_expires_versions_test.yaml"))
+ ]
+
+ all_objs, options = run_glean_parser.parse_with_options(input_files, options)
+
+ assert all_objs["test"]["expired1"].disabled is True
+ assert all_objs["test"]["expired2"].disabled is True
+ assert all_objs["test"]["unexpired"].disabled is False
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/toolkit/components/glean/tests/pytest/test_jogfile_output.py b/toolkit/components/glean/tests/pytest/test_jogfile_output.py
new file mode 100644
index 0000000000..801c0967ff
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/test_jogfile_output.py
@@ -0,0 +1,50 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import io
+import sys
+from os import path
+from pathlib import Path
+
+import mozunit
+from expect_helper import expect
+
+# Shenanigans to import the FOG glean_parser runner
+FOG_ROOT_PATH = path.abspath(
+ path.join(path.dirname(__file__), path.pardir, path.pardir)
+)
+sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext"))
+import jog
+import run_glean_parser
+
+
+def test_jogfile_output():
+ """
+ A regression test. Very fragile.
+ It generates a jogfile for metrics_test.yaml and compares it
+ byte-for-byte with an expected output file.
+
+ Also, part one of a two-part test.
+ The generated jogfile is consumed in Rust_TestJogfile in t/c/g/tests/gtest/test.rs
+ This is to ensure that the jogfile we generate in Python can be consumed in Rust.
+
+ To generate new expected output files, set `UPDATE_EXPECT=1` when running the test suite:
+
+ UPDATE_EXPECT=1 mach test toolkit/components/glean/pytest
+ """
+
+ options = {"allow_reserved": False}
+ here_path = Path(path.dirname(__file__))
+ input_files = [here_path / "metrics_test.yaml", here_path / "pings_test.yaml"]
+
+ all_objs, options = run_glean_parser.parse_with_options(input_files, options)
+
+ output_fd = io.StringIO()
+ jog.output_file(all_objs, output_fd, options)
+
+ expect(here_path / "jogfile_output", output_fd.getvalue())
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/toolkit/components/glean/tests/pytest/test_no_expired_metrics.py b/toolkit/components/glean/tests/pytest/test_no_expired_metrics.py
new file mode 100644
index 0000000000..9783acaf0a
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/test_no_expired_metrics.py
@@ -0,0 +1,46 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import sys
+from os import path
+from pathlib import Path
+
+import mozunit
+
+# Shenanigans to import the metrics index's list of metrics.yamls
+FOG_ROOT_PATH = path.abspath(
+ path.join(path.dirname(__file__), path.pardir, path.pardir)
+)
+sys.path.append(FOG_ROOT_PATH)
+from metrics_index import metrics_yamls, tags_yamls
+
+# Shenanigans to import run_glean_parser
+sys.path.append(path.join(FOG_ROOT_PATH, "build_scripts", "glean_parser_ext"))
+import run_glean_parser
+
+# Shenanigans to import the in-tree glean_parser
+GECKO_PATH = path.join(FOG_ROOT_PATH, path.pardir, path.pardir, path.pardir)
+sys.path.append(path.join(GECKO_PATH, "third_party", "python", "glean_parser"))
+from glean_parser import lint, parser, util
+
+
+def test_no_metrics_expired():
+ """
+ Of all the metrics included in this build, are any expired?
+ If so, they must be removed or renewed.
+
+ (This also checks other lints, as a treat.)
+ """
+ with open("browser/config/version.txt", "r") as version_file:
+ app_version = version_file.read().strip()
+
+ options = run_glean_parser.get_parser_options(app_version)
+ paths = [Path(x) for x in metrics_yamls] + [Path(x) for x in tags_yamls]
+ all_objs = parser.parse_objects(paths, options)
+ assert not util.report_validation_errors(all_objs)
+ assert not lint.lint_metrics(all_objs.value, options)
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/toolkit/components/glean/tests/pytest/test_yaml_indices.py b/toolkit/components/glean/tests/pytest/test_yaml_indices.py
new file mode 100644
index 0000000000..a8d185e59f
--- /dev/null
+++ b/toolkit/components/glean/tests/pytest/test_yaml_indices.py
@@ -0,0 +1,38 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import sys
+from os import path
+
+import mozunit
+
+# Shenanigans to import the metrics index's lists of yamls
+FOG_ROOT_PATH = path.abspath(
+ path.join(path.dirname(__file__), path.pardir, path.pardir)
+)
+sys.path.append(FOG_ROOT_PATH)
+import metrics_index
+
+
+def test_yamls_sorted():
+ """
+ Ensure the yamls indices are sorted lexicographically.
+ """
+ # Ignore lists that are the concatenation of others.
+ to_ignore = ["metrics_yamls", "pings_yamls"]
+
+ # Fetch names of all variables defined in the `metrics_index` module.
+ yaml_lists = [item for item in dir(metrics_index) if not item.startswith("__")]
+ for name in yaml_lists:
+ if name in to_ignore:
+ continue
+
+ yamls_to_test = metrics_index.__dict__[name]
+ assert (
+ sorted(yamls_to_test) == yamls_to_test
+ ), f"{name} must be be lexicographically sorted."
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/toolkit/components/glean/tests/test_metrics.yaml b/toolkit/components/glean/tests/test_metrics.yaml
new file mode 100644
index 0000000000..9ff471fd6b
--- /dev/null
+++ b/toolkit/components/glean/tests/test_metrics.yaml
@@ -0,0 +1,780 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This file is for Internal FOG Test Use Only.
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+
+test_only:
+ bad_code:
+ type: counter
+ description: |
+ Number of times we encountered bad code.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ can_we_time_it:
+ type: timespan
+ time_unit: nanosecond
+ description: |
+ Test metric for a timespan.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ cheesy_string:
+ type: string
+ description: |
+ Only the cheesiest of strings.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673662
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673662#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ cheesy_string_list:
+ type: string_list
+ description: |
+ Only the cheesiest of strings. In list form!
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1682960
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1682960#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ what_a_date:
+ type: datetime
+ time_unit: second
+ description: >
+ ...To be writing FOG code.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ what_id_it: # Using a different metrics yaml style for fun.
+ type: uuid
+ description: |
+ Just a UUID.
+ This is a test-only metric.
+ bugs: ["https://bugzilla.mozilla.org/show_bug.cgi?id=1673664"]
+ data_reviews: ["https://bugzilla.mozilla.org/show_bug.cgi?id=1673664#c1"]
+ data_sensitivity: ["technical"]
+ notification_emails: ["glean-team@mozilla.com"]
+ expires: never
+ send_in_pings: ["test-ping"]
+
+ can_we_flag_it:
+ type: boolean
+ description: |
+ Test metric for a boolean.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+
+ do_you_remember:
+ type: memory_distribution
+ memory_unit: megabyte
+ description: |
+ They say it's the second thing to go.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673648
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673648#c1
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - test-ping
+
+ what_time_is_it:
+ type: timing_distribution
+ time_unit: microsecond
+ description: |
+ Adheres to at least two of the top ten fallacies programmers believe
+ about time.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673663
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673663#c1
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - test-ping
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_TIMING
+
+ mabels_kitchen_counters:
+ type: labeled_counter
+ description: |
+ Counts Mabels labeled by their kitchen counters.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277#c1
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - test-ping
+
+ mabels_bathroom_counters:
+ type: labeled_counter
+ description: |
+ Counts Mabels labeled by their bathroom counters.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1683171
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1683171#c1
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - test-ping
+
+ mabels_like_balloons:
+ type: labeled_boolean
+ description: |
+ Does the labeled Mabel like balloons?
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277#c1
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - test-ping
+
+ mabels_balloon_strings:
+ type: labeled_string
+ description: |
+ What do the labeled Mabel's liked balloons' strings say?
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277#c1
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - test-ping
+
+ mabels_label_maker:
+ type: labeled_string
+ description: |
+ Mabel just got a label maker and wants to party like it's
+ 1999.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1696388
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1696388
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - test-ping
+
+ mirror_time:
+ type: timespan
+ time_unit: nanosecond
+ description: |
+ Mirrored metric for a timespan.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_TIMESPAN
+
+ mirror_time_nanos:
+ type: timespan
+ time_unit: nanosecond
+ description: |
+ Mirrored metric for a timespan.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1704106
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1704106#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - test-ping
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_TIMESPAN_NANOS
+
+ mirrors_for_labeled_bools:
+ type: labeled_boolean
+ description: |
+ Mirrored metric.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1675277#c1
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - test-ping
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_LABELED_BOOL
+
+ one_ping_one_bool:
+ type: boolean
+ description: |
+ One bool for one ping only.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685402
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685402#c1
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - one-ping-only
+
+ meaning_of_life:
+ type: quantity
+ unit: unfathomable
+ description: |
+ Measures the one true answer to the Ultimate Question of Life,
+ the Universe, and Everything.
+ Approximately.
+ This is a test-only metric.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1704846
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1704846#c1
+ data_sensitivity:
+ - technical
+ expires: never
+ notification_emails:
+ - glean-team@mozilla.com
+ send_in_pings:
+ - test-ping
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_QUANTITY
+
+
+test_only.ipc:
+ a_counter:
+ type: counter
+ description: |
+ This is a test-only metric.
+ Just counting things.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_COUNTER
+ a_bool:
+ type: boolean
+ description: |
+ This is a test-only metric.
+ Just flagging things.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TELEMETRY_TEST_BOOLEAN_KIND
+ a_date:
+ type: datetime
+ time_unit: second
+ description: |
+ This is a test-only metric.
+ Just putting things on the calendar.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_DATE
+ a_string:
+ type: string
+ description: |
+ This is a test-only metric.
+ Just setting some strings.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TELEMETRY_TEST_MULTIPLE_STORES_STRING
+ a_memory_dist:
+ type: memory_distribution
+ memory_unit: kilobyte
+ description: |
+ This is a test-only metric.
+ Just measuring memory.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TELEMETRY_TEST_LINEAR
+ a_timing_dist:
+ type: timing_distribution
+ description: |
+ This is a test-only metric.
+ Just measuring time.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TELEMETRY_TEST_EXPONENTIAL
+ a_custom_dist:
+ type: custom_distribution
+ description: |
+ This is a test-only metric.
+ Just measuring samples.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1713398
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1713398#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ range_min: 1
+ range_max: 2147483646
+ bucket_count: 10
+ histogram_type: linear
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_CUSTOM
+ a_string_list:
+ type: string_list
+ description: |
+ This is a test-only metric.
+ Just appending some strings.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TELEMETRY_TEST_KEYED_BOOLEAN_KIND
+ an_event:
+ type: event
+ extra_keys:
+ extra1:
+ type: string
+ description: "Some extra data"
+ extra2:
+ type: string
+ description: "Some extra data again"
+ description: |
+ This is a test-only metric.
+ Just recording some events.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TelemetryTest_MirrorWithExtra_Object1
+ event_with_extra:
+ type: event
+ extra_keys:
+ extra1:
+ type: string
+ description: "Some extra data"
+ extra2:
+ type: quantity
+ description: "Some extra data again"
+ extra3_longer_name:
+ type: boolean
+ description: "Some extra data again. Also tests extras with underscores"
+ description: |
+ This is a test-only metric.
+ Just recording some events.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ no_extra_event:
+ type: event
+ description: |
+ This is a test-only metric.
+ Just recording some events without the extra fuss.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ telemetry_mirror: TelemetryTest_NotExpiredOptout_Object1
+ a_uuid:
+ type: uuid
+ description: |
+ This is a test-only metric.
+ Just recording some unique identifiers.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TELEMETRY_TEST_STRING_KIND
+ a_labeled_counter:
+ type: labeled_counter
+ description: |
+ This is a test-only metric.
+ Just counting labeled things.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1688281
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1688281#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TELEMETRY_TEST_ANOTHER_MIRROR_FOR_LABELED_COUNTER
+ another_labeled_counter:
+ type: labeled_counter
+ description: |
+ This is a test-only metric.
+ Just another metric counting labeled things.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1688281
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1685406
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1758795
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1688281#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_LABELED_COUNTER
+ a_quantity:
+ type: quantity
+ unit: squad
+ description: |
+ This is a test-only metric.
+ Just quantifying things.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1704846
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1704846#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ irate:
+ type: rate
+ description: |
+ This is a test-only metric.
+ A rate that isn't happy about it.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_RATE
+ rate_with_external_denominator:
+ type: rate
+ denominator_metric: test_only.ipc.an_external_denominator
+ description: |
+ This is a test-only metric.
+ A rate with a denominator that is Out There.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ an_external_denominator:
+ type: counter
+ description: |
+ This is a test-only metric.
+ A denominator not from around here.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1694496#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ a_url:
+ type: url
+ description: |
+ This is a test-only metric.
+ Just setting some Urls.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1766980
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1646165#c1
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+ no_lint:
+ - COMMON_PREFIX
+ telemetry_mirror: TELEMETRY_TEST_MIRROR_FOR_URL
+
+test_only.jog:
+ a_counter:
+ type: counter
+ description: |
+ This is a test-only metric.
+ Just counting things.
+ Tied closely to test_jog_name_collision.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1767039
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1767039
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
+
+ an_event:
+ type: event
+ extra_keys:
+ extra1:
+ type: string
+ description: "Some extra data"
+ extra2:
+ type: string
+ description: "Some extra data again"
+ description: |
+ This is a test-only metric.
+ Just recording some events.
+ Tied closely to test_jog_name_collision.
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1767039
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1767039
+ data_sensitivity:
+ - technical
+ notification_emails:
+ - glean-team@mozilla.com
+ expires: never
+ send_in_pings:
+ - store1
diff --git a/toolkit/components/glean/tests/test_pings.yaml b/toolkit/components/glean/tests/test_pings.yaml
new file mode 100644
index 0000000000..d62f682109
--- /dev/null
+++ b/toolkit/components/glean/tests/test_pings.yaml
@@ -0,0 +1,42 @@
+# 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
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This file defines the pings that are recorded by the Glean SDK. They are
+# automatically converted to platform-specific code at build time using the
+# `glean_parser` PyPI package.
+
+# This file is presently for Internal FOG Test Use Only.
+
+---
+$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
+
+one-ping-only:
+ description: |
+ This ping is for tests only.
+ include_client_id: false
+ send_if_empty: true
+ bugs:
+ - https://bugzilla.mozilla.org/1673660
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1673660#c1
+ notification_emails:
+ - chutten@mozilla.com
+ - glean-team@mozilla.com
+ no_lint:
+ - REDUNDANT_PING
+
+test-ping:
+ description: |
+ This ping is for tests only.
+ include_client_id: false
+ send_if_empty: true
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1737157
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1737157#c1
+ notification_emails:
+ - chutten@mozilla.com
+ - glean-team@mozilla.com
+ no_lint:
+ - REDUNDANT_PING
diff --git a/toolkit/components/glean/tests/xpcshell/head.js b/toolkit/components/glean/tests/xpcshell/head.js
new file mode 100644
index 0000000000..f42bd02822
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/head.js
@@ -0,0 +1,6 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
diff --git a/toolkit/components/glean/tests/xpcshell/test_FOGIPCLimit.js b/toolkit/components/glean/tests/xpcshell/test_FOGIPCLimit.js
new file mode 100644
index 0000000000..10ab9f5bc3
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_FOGIPCLimit.js
@@ -0,0 +1,51 @@
+/* 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 { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+);
+
+add_setup(
+ /* on Android FOG is set up through head.js */
+ { skip_if: () => !runningInParent || AppConstants.platform == "android" },
+ function test_setup() {
+ // Give FOG a temp profile to init within.
+ do_get_profile();
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ Services.fog.initializeFOG();
+ }
+);
+
+// Keep in sync with ipc.rs.
+// "Why no -1?" Because the limit's 100k. The -1 is because of atomic ops.
+const FOG_IPC_PAYLOAD_ACCESS_LIMIT = 100000;
+
+add_task({ skip_if: () => runningInParent }, async function run_child_stuff() {
+ for (let i = 0; i < FOG_IPC_PAYLOAD_ACCESS_LIMIT + 1; i++) {
+ Glean.testOnly.badCode.add(1);
+ }
+});
+
+add_task(
+ { skip_if: () => !runningInParent },
+ async function test_fog_ipc_limit() {
+ await run_test_in_child("test_FOGIPCLimit.js");
+
+ await ContentTaskUtils.waitForCondition(() => {
+ return !!Glean.testOnly.badCode.testGetValue();
+ }, "Waiting for IPC.");
+
+ // The child exceeded the number of accesses to trigger an IPC flush.
+ Assert.greater(
+ Glean.testOnly.badCode.testGetValue(),
+ FOG_IPC_PAYLOAD_ACCESS_LIMIT
+ );
+ }
+);
diff --git a/toolkit/components/glean/tests/xpcshell/test_FOGInit.js b/toolkit/components/glean/tests/xpcshell/test_FOGInit.js
new file mode 100644
index 0000000000..f8c4a410ad
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_FOGInit.js
@@ -0,0 +1,41 @@
+/* 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"
+);
+
+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();
+
+ // Glean init (via `chrono`) gets the timezone via unprotected write.
+ // This is being worked around:
+ // https://github.com/chronotope/chrono/pull/677
+ // Until that reaches a release and we update to it (bug 1780401), ensure
+ // local time has been loaded by JS before we kick of Glean init.
+ new Date().getHours(); // used for its side effect.
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ Services.fog.initializeFOG();
+ }
+);
+
+add_task(function test_fog_init_works() {
+ if (new Date().getHours() >= 3 && new Date().getHours() <= 4) {
+ // We skip this test if it's too close to 4AM, when we might send a
+ // "metrics" ping between init and this test being run.
+ Assert.ok(true, "Too close to 'metrics' ping send window. Skipping test.");
+ return;
+ }
+ Assert.greater(
+ Glean.fog.initialization.testGetValue(),
+ 0,
+ "FOG init happened, and its time was measured."
+ );
+});
diff --git a/toolkit/components/glean/tests/xpcshell/test_FOGPrefs.js b/toolkit/components/glean/tests/xpcshell/test_FOGPrefs.js
new file mode 100644
index 0000000000..943ffb3186
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_FOGPrefs.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TELEMETRY_SERVER_PREF = "toolkit.telemetry.server";
+const UPLOAD_PREF = "datareporting.healthreport.uploadEnabled";
+const LOCALHOST_PREF = "telemetry.fog.test.localhost_port";
+
+// FOG needs a profile directory to put its data in.
+do_get_profile();
+
+// We want Glean to use a localhost server so we can be SURE not to send data to the outside world.
+// Yes, the port spells GLEAN on a T9 keyboard, why do you ask?
+Services.prefs.setIntPref(LOCALHOST_PREF, 45326);
+// We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+Services.fog.initializeFOG();
+
+add_task(function test_fog_upload_only() {
+ // Don't forget to point the telemetry server to localhost, or Telemetry
+ // might make a non-local connection during the test run.
+ Services.prefs.setStringPref(
+ TELEMETRY_SERVER_PREF,
+ "http://localhost/telemetry-fake/"
+ );
+ // Be sure to set port=-1 for faking success _before_ enabling upload.
+ // Or else there's a short window where we might send something.
+ Services.prefs.setIntPref(LOCALHOST_PREF, -1);
+ Services.prefs.setBoolPref(UPLOAD_PREF, true);
+
+ const value = 42;
+ Glean.testOnly.meaningOfLife.set(value);
+ // We specifically look at the custom ping's value because we know it
+ // won't be reset by being sent.
+ Assert.equal(value, Glean.testOnly.meaningOfLife.testGetValue("test-ping"));
+
+ // Despite upload being disabled, we keep the old values around.
+ Services.prefs.setBoolPref(UPLOAD_PREF, false);
+ Assert.equal(value, Glean.testOnly.meaningOfLife.testGetValue("test-ping"));
+
+ // Now, when we turn the fake upload off, we clear the stores
+ Services.prefs.setIntPref(LOCALHOST_PREF, 0);
+ Assert.equal(
+ undefined,
+ Glean.testOnly.meaningOfLife.testGetValue("test-ping")
+ );
+});
diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFT.js b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js
new file mode 100644
index 0000000000..20923d057e
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_GIFFT.js
@@ -0,0 +1,538 @@
+/* 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"
+);
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+const Telemetry = Services.telemetry;
+
+function sleep(ms) {
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+function scalarValue(aScalarName) {
+ let snapshot = Telemetry.getSnapshotForScalars();
+ return "parent" in snapshot ? snapshot.parent[aScalarName] : undefined;
+}
+
+function keyedScalarValue(aScalarName) {
+ let snapshot = Telemetry.getSnapshotForKeyedScalars();
+ return "parent" in snapshot ? snapshot.parent[aScalarName] : undefined;
+}
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+ );
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ // On Android FOG is set up through head.js.
+ if (AppConstants.platform != "android") {
+ Services.fog.initializeFOG();
+ }
+});
+
+add_task(function test_gifft_counter() {
+ Glean.testOnlyIpc.aCounter.add(20);
+ Assert.equal(20, Glean.testOnlyIpc.aCounter.testGetValue());
+ Assert.equal(20, scalarValue("telemetry.test.mirror_for_counter"));
+});
+
+add_task(function test_gifft_boolean() {
+ Glean.testOnlyIpc.aBool.set(false);
+ Assert.equal(false, Glean.testOnlyIpc.aBool.testGetValue());
+ Assert.equal(false, scalarValue("telemetry.test.boolean_kind"));
+});
+
+add_task(function test_gifft_datetime() {
+ const dateStr = "2021-03-22T16:06:00";
+ const value = new Date(dateStr);
+ Glean.testOnlyIpc.aDate.set(value.getTime() * 1000);
+
+ let received = Glean.testOnlyIpc.aDate.testGetValue();
+ Assert.equal(value.getTime(), received.getTime());
+ Assert.ok(scalarValue("telemetry.test.mirror_for_date").startsWith(dateStr));
+});
+
+add_task(function test_gifft_string() {
+ const value = "a string!";
+ Glean.testOnlyIpc.aString.set(value);
+
+ Assert.equal(value, Glean.testOnlyIpc.aString.testGetValue());
+ Assert.equal(value, scalarValue("telemetry.test.multiple_stores_string"));
+});
+
+add_task(function test_gifft_memory_dist() {
+ Glean.testOnlyIpc.aMemoryDist.accumulate(7);
+ Glean.testOnlyIpc.aMemoryDist.accumulate(17);
+
+ let data = Glean.testOnlyIpc.aMemoryDist.testGetValue();
+ // `data.sum` is in bytes, but the metric is in KB.
+ Assert.equal(24 * 1024, data.sum, "Sum's correct");
+ for (let [bucket, count] of Object.entries(data.values)) {
+ Assert.ok(
+ count == 0 || (count == 1 && (bucket == 6888 || bucket == 17109)),
+ `Only two buckets have a sample ${bucket} ${count}`
+ );
+ }
+
+ data = Telemetry.getHistogramById("TELEMETRY_TEST_LINEAR").snapshot();
+ Telemetry.getHistogramById("TELEMETRY_TEST_LINEAR").clear();
+ Assert.equal(24, data.sum, "Histogram's in `memory_unit` units");
+ Assert.equal(2, data.values["1"], "Both samples in a low bucket");
+
+ // MemoryDistribution's Accumulate method to takes
+ // a platform specific type (size_t).
+ // Glean's, however, is i64, and, glean_memory_dist is uint64_t
+ // What happens when we give accumulate dubious values?
+ // This may occur on some uncommon platforms.
+ // Note: there are issues in JS with numbers above 2**53
+ Glean.testOnlyIpc.aMemoryDist.accumulate(36893488147419103232);
+ let dubiousValue = Object.entries(
+ Glean.testOnlyIpc.aMemoryDist.testGetValue().values
+ )[0][1];
+ Assert.equal(
+ dubiousValue,
+ 1,
+ "Greater than 64-Byte number did not accumulate correctly"
+ );
+
+ // Values lower than the out-of-range value are not clamped
+ // resulting in an exception being thrown from the glean side
+ // when the value exceeds the glean maximum allowed value
+ Glean.testOnlyIpc.aMemoryDist.accumulate(Math.pow(2, 31));
+ Assert.throws(
+ () => Glean.testOnlyIpc.aMemoryDist.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Did not accumulate correctly"
+ );
+});
+
+add_task(function test_gifft_custom_dist() {
+ Glean.testOnlyIpc.aCustomDist.accumulateSamples([7, 268435458]);
+
+ let data = Glean.testOnlyIpc.aCustomDist.testGetValue();
+ 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}`
+ );
+ }
+
+ data = Telemetry.getHistogramById(
+ "TELEMETRY_TEST_MIRROR_FOR_CUSTOM"
+ ).snapshot();
+ Telemetry.getHistogramById("TELEMETRY_TEST_MIRROR_FOR_CUSTOM").clear();
+ Assert.equal(7 + 268435458, data.sum, "Sum in histogram is correct");
+ Assert.equal(1, data.values["1"], "One sample in the low bucket");
+ // Yes, the bucket is off-by-one compared to Glean.
+ Assert.equal(1, data.values["268435457"], "One sample in the next bucket");
+});
+
+add_task(async function test_gifft_timing_dist() {
+ let t1 = Glean.testOnlyIpc.aTimingDist.start();
+ // Interleave some other metric's samples. bug 1768636.
+ let ot1 = Glean.testOnly.whatTimeIsIt.start();
+ let t2 = Glean.testOnlyIpc.aTimingDist.start();
+ let ot2 = Glean.testOnly.whatTimeIsIt.start();
+ Glean.testOnly.whatTimeIsIt.cancel(ot1);
+ Glean.testOnly.whatTimeIsIt.cancel(ot2);
+
+ await sleep(5);
+
+ let t3 = Glean.testOnlyIpc.aTimingDist.start();
+ Glean.testOnlyIpc.aTimingDist.cancel(t1);
+
+ await sleep(5);
+
+ Glean.testOnlyIpc.aTimingDist.stopAndAccumulate(t2); // 10ms
+ Glean.testOnlyIpc.aTimingDist.stopAndAccumulate(t3); // 5ms
+
+ let data = Glean.testOnlyIpc.aTimingDist.testGetValue();
+ 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.
+ // 10 and 5 input value can be trunacted to 4. + 9. >= 13. from cast
+ Assert.greater(data.sum, 13 * 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, [bucket, count]) => acc + count,
+ 0
+ ),
+ "Only two buckets with samples"
+ );
+
+ data = Telemetry.getHistogramById("TELEMETRY_TEST_EXPONENTIAL").snapshot();
+ // Suffers from same cast truncation issue of 9.... and 4.... values
+ Assert.greaterOrEqual(data.sum, 13, "Histogram's in milliseconds");
+ Assert.equal(
+ 2,
+ Object.entries(data.values).reduce(
+ (acc, [bucket, count]) => acc + count,
+ 0
+ ),
+ "Only two samples"
+ );
+});
+
+add_task(function test_gifft_string_list_works() {
+ const value = "a string!";
+ const value2 = "another string!";
+ const value3 = "yet another string.";
+
+ // `set` doesn't work in the mirror, so use `add`
+ Glean.testOnlyIpc.aStringList.add(value);
+ Glean.testOnlyIpc.aStringList.add(value2);
+ Glean.testOnlyIpc.aStringList.add(value3);
+
+ let val = Glean.testOnlyIpc.aStringList.testGetValue();
+ // Note: This is incredibly fragile and will break if we ever rearrange items
+ // in the string list.
+ Assert.deepEqual([value, value2, value3], val);
+
+ val = keyedScalarValue("telemetry.test.keyed_boolean_kind");
+ // This too may be fragile.
+ Assert.deepEqual(
+ {
+ [value]: true,
+ [value2]: true,
+ [value3]: true,
+ },
+ val
+ );
+});
+
+add_task(function test_gifft_events() {
+ Telemetry.setEventRecordingEnabled("telemetry.test", true);
+
+ 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);
+
+ TelemetryTestUtils.assertEvents(
+ [
+ ["telemetry.test", "not_expired_optout", "object1", undefined, undefined],
+ ["telemetry.test", "mirror_with_extra", "object1", null, extra],
+ ],
+ { category: "telemetry.test" }
+ );
+});
+
+add_task(function test_gifft_uuid() {
+ const kTestUuid = "decafdec-afde-cafd-ecaf-decafdecafde";
+ Glean.testOnlyIpc.aUuid.set(kTestUuid);
+ Assert.equal(kTestUuid, Glean.testOnlyIpc.aUuid.testGetValue());
+ Assert.equal(kTestUuid, scalarValue("telemetry.test.string_kind"));
+});
+
+add_task(function test_gifft_labeled_counter() {
+ Assert.equal(
+ undefined,
+ Glean.testOnlyIpc.aLabeledCounter.a_label.testGetValue(),
+ "New labels with no values should return undefined"
+ );
+ Glean.testOnlyIpc.aLabeledCounter.a_label.add(1);
+ Glean.testOnlyIpc.aLabeledCounter.another_label.add(2);
+ Glean.testOnlyIpc.aLabeledCounter.a_label.add(3);
+ Assert.equal(4, Glean.testOnlyIpc.aLabeledCounter.a_label.testGetValue());
+ Assert.equal(
+ 2,
+ Glean.testOnlyIpc.aLabeledCounter.another_label.testGetValue()
+ );
+ // What about invalid/__other__?
+ Assert.equal(
+ undefined,
+ Glean.testOnlyIpc.aLabeledCounter.__other__.testGetValue()
+ );
+ Glean.testOnlyIpc.aLabeledCounter.InvalidLabel.add(3);
+ Assert.throws(
+ () => Glean.testOnlyIpc.aLabeledCounter.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Can't get the value when you're error'd"
+ );
+
+ let value = keyedScalarValue(
+ "telemetry.test.another_mirror_for_labeled_counter"
+ );
+ Assert.deepEqual(
+ {
+ a_label: 4,
+ another_label: 2,
+ InvalidLabel: 3,
+ },
+ value
+ );
+});
+
+add_task(async function test_gifft_timespan() {
+ // We start, briefly sleep and then stop.
+ // That guarantees some time to measure.
+ Glean.testOnly.mirrorTime.start();
+ await sleep(10);
+ Glean.testOnly.mirrorTime.stop();
+
+ const NANOS_IN_MILLIS = 1e6;
+ // bug 1701949 - Sleep gets close, but sometimes doesn't wait long enough.
+ const EPSILON = 40000;
+ Assert.greater(
+ Glean.testOnly.mirrorTime.testGetValue(),
+ 10 * NANOS_IN_MILLIS - EPSILON
+ );
+ // Mirrored to milliseconds.
+ Assert.greaterOrEqual(scalarValue("telemetry.test.mirror_for_timespan"), 9);
+});
+
+add_task(async function test_gifft_timespan_raw() {
+ Glean.testOnly.mirrorTimeNanos.setRaw(15 /*ns*/);
+
+ Assert.equal(15, Glean.testOnly.mirrorTimeNanos.testGetValue());
+ // setRaw, unlike start/stop, mirrors the raw value directly.
+ Assert.equal(scalarValue("telemetry.test.mirror_for_timespan_nanos"), 15);
+});
+
+add_task(async function test_gifft_labeled_boolean() {
+ Assert.equal(
+ undefined,
+ Glean.testOnly.mirrorsForLabeledBools.a_label.testGetValue(),
+ "New labels with no values should return undefined"
+ );
+ Glean.testOnly.mirrorsForLabeledBools.a_label.set(true);
+ Glean.testOnly.mirrorsForLabeledBools.another_label.set(false);
+ Assert.equal(
+ true,
+ Glean.testOnly.mirrorsForLabeledBools.a_label.testGetValue()
+ );
+ Assert.equal(
+ false,
+ Glean.testOnly.mirrorsForLabeledBools.another_label.testGetValue()
+ );
+ // What about invalid/__other__?
+ Assert.equal(
+ undefined,
+ Glean.testOnly.mirrorsForLabeledBools.__other__.testGetValue()
+ );
+ Glean.testOnly.mirrorsForLabeledBools.InvalidLabel.set(true);
+ Assert.throws(
+ () => Glean.testOnly.mirrorsForLabeledBools.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Should throw because of a recording error."
+ );
+
+ // In Telemetry there is no invalid label
+ let value = keyedScalarValue("telemetry.test.mirror_for_labeled_bool");
+ Assert.deepEqual(
+ {
+ a_label: true,
+ another_label: false,
+ InvalidLabel: true,
+ },
+ value
+ );
+});
+
+add_task(function test_gifft_boolean() {
+ Glean.testOnly.meaningOfLife.set(42);
+ Assert.equal(42, Glean.testOnly.meaningOfLife.testGetValue());
+ Assert.equal(42, scalarValue("telemetry.test.mirror_for_quantity"));
+});
+
+add_task(function test_gifft_rate() {
+ Glean.testOnlyIpc.irate.addToNumerator(22);
+ Glean.testOnlyIpc.irate.addToDenominator(7);
+ Assert.deepEqual(
+ { numerator: 22, denominator: 7 },
+ Glean.testOnlyIpc.irate.testGetValue()
+ );
+ Assert.deepEqual(
+ { numerator: 22, denominator: 7 },
+ keyedScalarValue("telemetry.test.mirror_for_rate")
+ );
+});
+
+add_task(
+ {
+ // bug 1670259 to see if we can implement `testResetFOG` on Android.
+ skip_if: () => AppConstants.platform == "android",
+ },
+ function test_gifft_numeric_limits() {
+ // Glean and Telemetry don't share the same storage sizes or signedness.
+ // Check the edges.
+
+ // 0) Reset everything
+ Services.fog.testResetFOG();
+ Services.telemetry.getSnapshotForHistograms("main", true /* aClearStore */);
+ Services.telemetry.getSnapshotForScalars("main", true /* aClearStore */);
+ Services.telemetry.getSnapshotForKeyedScalars(
+ "main",
+ true /* aClearStore */
+ );
+
+ // 1) Counter: i32 (saturates), mirrored to uint Scalar: u32 (overflows)
+ // 1.1) Negative parameters refused.
+ Glean.testOnlyIpc.aCounter.add(-20);
+ // Unfortunately we can't check what the error was, due to API design.
+ // (chutten blames chutten for his shortsightedness)
+ Assert.throws(
+ () => Glean.testOnlyIpc.aCounter.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Can't get the value when you're error'd"
+ );
+ Assert.equal(undefined, scalarValue("telemetry.test.mirror_for_counter"));
+ // Clear the error state
+ Services.fog.testResetFOG();
+
+ // 1.2) Values that sum larger than u32::max saturate (counter) and overflow (Scalar)
+ // Sums to 2^32 + 1
+ Glean.testOnlyIpc.aCounter.add(Math.pow(2, 31) - 1);
+ Glean.testOnlyIpc.aCounter.add(1);
+ Glean.testOnlyIpc.aCounter.add(Math.pow(2, 31) - 1);
+ Glean.testOnlyIpc.aCounter.add(2);
+ // Glean doesn't actually throw on saturation (bug 1751469),
+ // so we can just check the saturation value.
+ Assert.equal(
+ Math.pow(2, 31) - 1,
+ Glean.testOnlyIpc.aCounter.testGetValue()
+ );
+ // Telemetry will have wrapped around to 1
+ Assert.equal(1, scalarValue("telemetry.test.mirror_for_counter"));
+
+ // 2) Quantity: i64 (saturates), mirrored to uint Scalar: u32 (overflows)
+ // 2.1) Negative parameters refused.
+ Glean.testOnly.meaningOfLife.set(-42);
+ // Glean will error on this.
+ Assert.throws(
+ () => Glean.testOnly.meaningOfLife.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Can't get the value when you're error'd"
+ );
+ // GIFFT doesn't tell Telemetry about the weird value at all.
+ Assert.equal(undefined, scalarValue("telemetry.test.mirror_for_quantity"));
+ // Clear the error state
+ Services.fog.testResetFOG();
+
+ // 2.2) A parameter larger than u32::max is passed to Glean unchanged,
+ // but is clamped to u32::max before being passed to Telemetry.
+ Glean.testOnly.meaningOfLife.set(Math.pow(2, 32));
+ Assert.equal(Math.pow(2, 32), Glean.testOnly.meaningOfLife.testGetValue());
+ Assert.equal(
+ Math.pow(2, 32) - 1,
+ scalarValue("telemetry.test.mirror_for_quantity")
+ );
+
+ // 3) Rate: two i32 (saturates), mirrored to keyed uint Scalar: u32s (overflow)
+ // 3.1) Negative parameters refused.
+ Glean.testOnlyIpc.irate.addToNumerator(-22);
+ Glean.testOnlyIpc.irate.addToDenominator(7);
+ Assert.throws(
+ () => Glean.testOnlyIpc.irate.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Can't get the value when you're error'd"
+ );
+ Assert.deepEqual(
+ { denominator: 7 },
+ keyedScalarValue("telemetry.test.mirror_for_rate")
+ );
+ // Clear the error state
+ Services.fog.testResetFOG();
+ // Clear the partial Telemetry value
+ Services.telemetry.getSnapshotForKeyedScalars(
+ "main",
+ true /* aClearStore */
+ );
+
+ // Now the denominator:
+ Glean.testOnlyIpc.irate.addToNumerator(22);
+ Glean.testOnlyIpc.irate.addToDenominator(-7);
+ Assert.throws(
+ () => Glean.testOnlyIpc.irate.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Can't get the value when you're error'd"
+ );
+ Assert.deepEqual(
+ { numerator: 22 },
+ keyedScalarValue("telemetry.test.mirror_for_rate")
+ );
+
+ // 4) Timespan
+ // ( Can't overflow time without finding a way to get TimeStamp to think
+ // we're 2^32 milliseconds later without waiting a month )
+
+ // 5) TimingDistribution
+ // ( Can't overflow time with start() and stopAndAccumulate() without
+ // waiting for ages. But we _do_ have a test-only raw API...)
+ // The max sample for timing_distribution is 600000000000.
+ // The type for timing_distribution samples is i64.
+ // This means when we explore the edges of GIFFT's limits, we're well past
+ // Glean's limits. All we can get out of Glean is errors.
+ // (Which is good for data, difficult for tests.)
+ // But GIFFT should properly saturate in Telemetry at i32::max,
+ // so we shall test that.
+ Glean.testOnlyIpc.aTimingDist.testAccumulateRawMillis(Math.pow(2, 31) + 1);
+ Glean.testOnlyIpc.aTimingDist.testAccumulateRawMillis(Math.pow(2, 32) + 1);
+ Assert.throws(
+ () => Glean.testOnlyIpc.aTimingDist.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Can't get the value when you're error'd"
+ );
+ let snapshot = Telemetry.getHistogramById(
+ "TELEMETRY_TEST_EXPONENTIAL"
+ ).snapshot();
+ Assert.equal(
+ snapshot.values["2147483646"],
+ 2,
+ "samples > i32::max should end up in the top bucket"
+ );
+ }
+);
+
+add_task(function test_gifft_url() {
+ const value = "https://www.example.com";
+ Glean.testOnlyIpc.aUrl.set(value);
+
+ Assert.equal(value, Glean.testOnlyIpc.aUrl.testGetValue());
+ Assert.equal(value, scalarValue("telemetry.test.mirror_for_url"));
+});
+
+add_task(function test_gifft_url_cropped() {
+ const value = `https://example.com${"/test".repeat(47)}`;
+ Glean.testOnlyIpc.aUrl.set(value);
+
+ Assert.equal(value, Glean.testOnlyIpc.aUrl.testGetValue());
+ // We expect the mirrored URL to be truncated at the maximum
+ // length supported by string scalars.
+ Assert.equal(
+ value.substring(0, 50),
+ scalarValue("telemetry.test.mirror_for_url")
+ );
+});
diff --git a/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js
new file mode 100644
index 0000000000..73a9da9be2
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_GIFFTIPC.js
@@ -0,0 +1,315 @@
+/* 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 { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+);
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+const Telemetry = Services.telemetry;
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+function sleep(ms) {
+ /* eslint-disable mozilla/no-arbitrary-setTimeout */
+ return new Promise(resolve => setTimeout(resolve, ms));
+}
+
+function scalarValue(aScalarName, aProcessName) {
+ let snapshot = Telemetry.getSnapshotForScalars();
+ return aProcessName in snapshot
+ ? snapshot[aProcessName][aScalarName]
+ : undefined;
+}
+
+function keyedScalarValue(aScalarName, aProcessName) {
+ let snapshot = Telemetry.getSnapshotForKeyedScalars();
+ return aProcessName in snapshot
+ ? snapshot[aProcessName][aScalarName]
+ : undefined;
+}
+
+add_setup({ skip_if: () => !runningInParent }, function test_setup() {
+ // Give FOG a temp profile to init within.
+ do_get_profile();
+
+ // Allows these tests to properly run on e.g. Thunderbird
+ Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+ );
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ // on Android FOG is set up through head.js
+ if (AppConstants.platform != "android") {
+ Services.fog.initializeFOG();
+ }
+});
+
+const COUNT = 42;
+const CHEESY_STRING = "a very cheesy string!";
+const CHEESIER_STRING = "a much cheesier string!";
+const CUSTOM_SAMPLES = [3, 4];
+const EVENT_EXTRA = { extra1: "so very extra" };
+const MEMORIES = [13, 31];
+const MEMORY_BUCKETS = ["13193", "31378"]; // buckets are strings : |
+const A_LABEL_COUNT = 3;
+const ANOTHER_LABEL_COUNT = 5;
+const INVALID_COUNTERS = 7;
+const IRATE_NUMERATOR = 44;
+const IRATE_DENOMINATOR = 14;
+
+add_task({ skip_if: () => runningInParent }, async function run_child_stuff() {
+ let oldCanRecordBase = Telemetry.canRecordBase;
+ Telemetry.canRecordBase = true; // Ensure we're able to record things.
+
+ Glean.testOnlyIpc.aCounter.add(COUNT);
+ Glean.testOnlyIpc.aStringList.add(CHEESY_STRING);
+ Glean.testOnlyIpc.aStringList.add(CHEESIER_STRING);
+
+ Glean.testOnlyIpc.noExtraEvent.record();
+ Glean.testOnlyIpc.anEvent.record(EVENT_EXTRA);
+
+ for (let memory of MEMORIES) {
+ Glean.testOnlyIpc.aMemoryDist.accumulate(memory);
+ }
+
+ let t1 = Glean.testOnlyIpc.aTimingDist.start();
+ let t2 = Glean.testOnlyIpc.aTimingDist.start();
+
+ await sleep(5);
+
+ let t3 = Glean.testOnlyIpc.aTimingDist.start();
+ Glean.testOnlyIpc.aTimingDist.cancel(t1);
+
+ await sleep(5);
+
+ Glean.testOnlyIpc.aTimingDist.stopAndAccumulate(t2); // 10ms
+ Glean.testOnlyIpc.aTimingDist.stopAndAccumulate(t3); // 5ms
+
+ Glean.testOnlyIpc.aCustomDist.accumulateSamples(CUSTOM_SAMPLES);
+
+ Glean.testOnlyIpc.aLabeledCounter.a_label.add(A_LABEL_COUNT);
+ Glean.testOnlyIpc.aLabeledCounter.another_label.add(ANOTHER_LABEL_COUNT);
+
+ // Has to be different from aLabeledCounter so the error we record doesn't
+ // get in the way.
+ Glean.testOnlyIpc.anotherLabeledCounter.InvalidLabel.add(INVALID_COUNTERS);
+
+ Glean.testOnlyIpc.irate.addToNumerator(IRATE_NUMERATOR);
+ Glean.testOnlyIpc.irate.addToDenominator(IRATE_DENOMINATOR);
+ Telemetry.canRecordBase = oldCanRecordBase;
+});
+
+add_task(
+ { skip_if: () => !runningInParent },
+ async function test_child_metrics() {
+ Telemetry.setEventRecordingEnabled("telemetry.test", true);
+
+ // Clear any stray Telemetry data
+ Telemetry.clearScalars();
+ Telemetry.getSnapshotForHistograms("main", true);
+ Telemetry.clearEvents();
+
+ await run_test_in_child("test_GIFFTIPC.js");
+
+ // Wait for both IPC mechanisms to flush.
+ await Services.fog.testFlushAllChildren();
+ await ContentTaskUtils.waitForCondition(() => {
+ let snapshot = Telemetry.getSnapshotForKeyedScalars();
+ return (
+ "content" in snapshot &&
+ "telemetry.test.mirror_for_rate" in snapshot.content
+ );
+ }, "failed to find content telemetry in parent");
+
+ // boolean
+ // Doesn't work over IPC
+
+ // counter
+ Assert.equal(Glean.testOnlyIpc.aCounter.testGetValue(), COUNT);
+ Assert.equal(
+ scalarValue("telemetry.test.mirror_for_counter", "content"),
+ COUNT,
+ "content-process Scalar has expected count"
+ );
+
+ // custom_distribution
+ const customSampleSum = CUSTOM_SAMPLES.reduce((acc, a) => acc + a, 0);
+ const customData = Glean.testOnlyIpc.aCustomDist.testGetValue("store1");
+ Assert.equal(customSampleSum, customData.sum, "Sum's correct");
+ for (let [bucket, count] of Object.entries(customData.values)) {
+ Assert.ok(
+ count == 0 || (count == CUSTOM_SAMPLES.length && bucket == 1), // both values in the low bucket
+ `Only two buckets have a sample ${bucket} ${count}`
+ );
+ }
+ const histSnapshot = Telemetry.getSnapshotForHistograms(
+ "main",
+ false,
+ false
+ );
+ const histData = histSnapshot.content.TELEMETRY_TEST_MIRROR_FOR_CUSTOM;
+ Assert.equal(customSampleSum, histData.sum, "Sum in histogram's correct");
+ Assert.equal(2, histData.values["1"], "Two samples in the first bucket");
+
+ // datetime
+ // Doesn't work over IPC
+
+ // event
+ 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);
+
+ 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(EVENT_EXTRA, events[0].extra);
+
+ TelemetryTestUtils.assertEvents(
+ [
+ [
+ "telemetry.test",
+ "not_expired_optout",
+ "object1",
+ undefined,
+ undefined,
+ ],
+ ["telemetry.test", "mirror_with_extra", "object1", null, EVENT_EXTRA],
+ ],
+ { category: "telemetry.test" },
+ { process: "content" }
+ );
+
+ // labeled_boolean
+ // Doesn't work over IPC
+
+ // labeled_counter
+ const counters = Glean.testOnlyIpc.aLabeledCounter;
+ Assert.equal(counters.a_label.testGetValue(), A_LABEL_COUNT);
+ Assert.equal(counters.another_label.testGetValue(), ANOTHER_LABEL_COUNT);
+
+ Assert.throws(
+ () => Glean.testOnlyIpc.anotherLabeledCounter.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Invalid labels record errors, which throw"
+ );
+
+ let value = keyedScalarValue(
+ "telemetry.test.another_mirror_for_labeled_counter",
+ "content"
+ );
+ Assert.deepEqual(
+ {
+ a_label: A_LABEL_COUNT,
+ another_label: ANOTHER_LABEL_COUNT,
+ },
+ value
+ );
+ value = keyedScalarValue(
+ "telemetry.test.mirror_for_labeled_counter",
+ "content"
+ );
+ Assert.deepEqual(
+ {
+ InvalidLabel: INVALID_COUNTERS,
+ },
+ value
+ );
+
+ // labeled_string
+ // Doesn't work over IPC
+
+ // memory_distribution
+ const memoryData = Glean.testOnlyIpc.aMemoryDist.testGetValue();
+ const memorySum = MEMORIES.reduce((acc, a) => acc + a, 0);
+ // The sum's in bytes, but the metric's in KB
+ Assert.equal(memorySum * 1024, memoryData.sum);
+ for (let [bucket, count] of Object.entries(memoryData.values)) {
+ // We could assert instead, but let's skip to save the logspam.
+ if (count == 0) {
+ continue;
+ }
+ Assert.ok(count == 1 && MEMORY_BUCKETS.includes(bucket));
+ }
+
+ const memoryHist = histSnapshot.content.TELEMETRY_TEST_LINEAR;
+ Assert.equal(
+ memorySum,
+ memoryHist.sum,
+ "Histogram's in `memory_unit` units"
+ );
+ Assert.equal(2, memoryHist.values["1"], "Samples are in the right bucket");
+
+ // quantity
+ // Doesn't work over IPC
+
+ // rate
+ Assert.deepEqual(
+ { numerator: IRATE_NUMERATOR, denominator: IRATE_DENOMINATOR },
+ Glean.testOnlyIpc.irate.testGetValue()
+ );
+ Assert.deepEqual(
+ { numerator: IRATE_NUMERATOR, denominator: IRATE_DENOMINATOR },
+ keyedScalarValue("telemetry.test.mirror_for_rate", "content")
+ );
+
+ // string
+ // Doesn't work over IPC
+
+ // string_list
+ // Note: this will break if string list ever rearranges its items.
+ const cheesyStrings = Glean.testOnlyIpc.aStringList.testGetValue();
+ Assert.deepEqual(cheesyStrings, [CHEESY_STRING, CHEESIER_STRING]);
+ // Note: this will break if keyed scalars rearrange their items.
+ Assert.deepEqual(
+ {
+ [CHEESY_STRING]: true,
+ [CHEESIER_STRING]: true,
+ },
+ keyedScalarValue("telemetry.test.keyed_boolean_kind", "content")
+ );
+
+ // timespan
+ // Doesn't work over IPC
+
+ // timing_distribution
+ const NANOS_IN_MILLIS = 1e6;
+ const EPSILON = 40000; // bug 1701949
+ const times = Glean.testOnlyIpc.aTimingDist.testGetValue();
+ Assert.greater(times.sum, 15 * NANOS_IN_MILLIS - EPSILON);
+ // We can't guarantee any specific time values (thank you clocks),
+ // but we can assert there are only two samples.
+ Assert.equal(
+ 2,
+ Object.entries(times.values).reduce(
+ (acc, [bucket, count]) => acc + count,
+ 0
+ )
+ );
+ const timingHist = histSnapshot.content.TELEMETRY_TEST_EXPONENTIAL;
+ Assert.greaterOrEqual(timingHist.sum, 13, "Histogram's in milliseconds.");
+ // Both values, 10 and 5, are truncated by a cast in AccumulateTimeDelta
+ // Minimally downcast 9. + 4. could realistically result in 13.
+ Assert.equal(
+ 2,
+ Object.entries(timingHist.values).reduce(
+ (acc, [bucket, count]) => acc + count,
+ 0
+ ),
+ "Only two samples"
+ );
+
+ // uuid
+ // Doesn't work over IPC
+ }
+);
diff --git a/toolkit/components/glean/tests/xpcshell/test_Glean.js b/toolkit/components/glean/tests/xpcshell/test_Glean.js
new file mode 100644
index 0000000000..cfab9d77ff
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_Glean.js
@@ -0,0 +1,405 @@
+/* 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() {
+ 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(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "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);
+
+ 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);
+
+ // Quantities need to be non-negative.
+ // This does not record a Glean error.
+ let extra4 = {
+ extra2: -1,
+ };
+ Glean.testOnlyIpc.eventWithExtra.record(extra4);
+ events = Glean.testOnlyIpc.eventWithExtra.testGetValue();
+ // Unchanged number of events
+ Assert.equal(1, events.length, "Recorded one event too many.");
+
+ // 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(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "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");
+ // `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("store1");
+ 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(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/
+ );
+});
+
+add_task(
+ /* TODO(bug 1737520): Enable custom ping support on Android */
+ { skip_if: () => AppConstants.platform == "android" },
+ function test_fog_custom_pings() {
+ Assert.ok("onePingOnly" in GleanPings);
+ let submitted = false;
+ Glean.testOnly.onePingOneBool.set(false);
+ GleanPings.onePingOnly.testBeforeNextSubmit(reason => {
+ submitted = true;
+ Assert.equal(false, Glean.testOnly.onePingOneBool.testGetValue());
+ });
+ GleanPings.onePingOnly.submit();
+ Assert.ok(submitted, "Ping was submitted, callback was 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
+
+ let data = Glean.testOnly.whatTimeIsIt.testGetValue();
+ 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, [bucket, count]) => acc + count,
+ 0
+ ),
+ "Only two 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.throws(
+ () => Glean.testOnly.mabelsLabelMaker.camelCase.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Should throw because of an invalid label."
+ );
+ Assert.throws(
+ () => Glean.testOnly.mabelsLabelMaker.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Should throw because of an invalid label."
+ );
+ // This test _should_ throw because we are calling data after an invalid label
+ // has been set.
+ Assert.throws(
+ () => Glean.testOnly.mabelsLabelMaker["dot.separated"].testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "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.InvalidLabel.set(true);
+ Assert.throws(
+ () => Glean.testOnly.mabelsLikeBalloons.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "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.InvalidLabel.add(1);
+ Assert.throws(
+ () => Glean.testOnly.mabelsKitchenCounters.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "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.InvalidLabel.set("valid");
+ Assert.throws(
+ () => Glean.testOnly.mabelsBalloonStrings.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/
+ );
+});
+
+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("store1"));
+});
diff --git a/toolkit/components/glean/tests/xpcshell/test_GleanExperiments.js b/toolkit/components/glean/tests/xpcshell/test_GleanExperiments.js
new file mode 100644
index 0000000000..cf8871e9d1
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_GleanExperiments.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// 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_experiment_annotations() {
+ const id = "my-experiment-id";
+ const branch = "my-branch";
+ const extra = { extra_key: "extra_value" };
+ Services.fog.setExperimentActive(id, branch, extra);
+
+ let data = Services.fog.testGetExperimentData(id);
+ Assert.equal(data.branch, branch);
+ Assert.deepEqual(data.extra, extra);
+
+ // Unknown id gets nothing.
+ Assert.equal(undefined, Services.fog.testGetExperimentData(id + id));
+
+ // Inactive id gets nothing.
+ Services.fog.setExperimentInactive(id);
+ Assert.equal(undefined, Services.fog.testGetExperimentData(id));
+});
diff --git a/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js b/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js
new file mode 100644
index 0000000000..182a584841
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_GleanIPC.js
@@ -0,0 +1,157 @@
+/* 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: () => !runningInParent || AppConstants.platform == "android" },
+ function test_setup() {
+ // Give FOG a temp profile to init within.
+ do_get_profile();
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ Services.fog.initializeFOG();
+ }
+);
+
+const BAD_CODE_COUNT = 42;
+const CHEESY_STRING = "a very cheesy string!";
+const CHEESIER_STRING = "a much cheesier string!";
+const EVENT_EXTRA = { extra1: "so very extra" };
+const MEMORIES = [13, 31];
+const MEMORY_BUCKETS = ["13509772", "32131834"]; // buckets are strings : |
+const COUNTERS_NEAR_THE_SINK = 3;
+const COUNTERS_WITH_JUNK_ON_THEM = 5;
+const INVALID_COUNTERS = 7;
+
+add_task({ skip_if: () => runningInParent }, async function run_child_stuff() {
+ Glean.testOnly.badCode.add(BAD_CODE_COUNT);
+ Glean.testOnly.cheesyStringList.add(CHEESY_STRING);
+ Glean.testOnly.cheesyStringList.add(CHEESIER_STRING);
+
+ Glean.testOnlyIpc.noExtraEvent.record();
+ Glean.testOnlyIpc.anEvent.record(EVENT_EXTRA);
+
+ for (let memory of MEMORIES) {
+ Glean.testOnly.doYouRemember.accumulate(memory);
+ }
+
+ 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
+
+ Glean.testOnlyIpc.aCustomDist.accumulateSamples([3, 4]);
+
+ Glean.testOnly.mabelsKitchenCounters.near_the_sink.add(
+ COUNTERS_NEAR_THE_SINK
+ );
+ Glean.testOnly.mabelsKitchenCounters.with_junk_on_them.add(
+ COUNTERS_WITH_JUNK_ON_THEM
+ );
+
+ Glean.testOnly.mabelsBathroomCounters.InvalidLabel.add(INVALID_COUNTERS);
+
+ Glean.testOnlyIpc.irate.addToNumerator(44);
+ Glean.testOnlyIpc.irate.addToDenominator(14);
+});
+
+add_task(
+ { skip_if: () => !runningInParent },
+ async function test_child_metrics() {
+ await run_test_in_child("test_GleanIPC.js");
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(Glean.testOnly.badCode.testGetValue(), BAD_CODE_COUNT);
+
+ // Note: this will break if string list ever rearranges its items.
+ const cheesyStrings = Glean.testOnly.cheesyStringList.testGetValue();
+ Assert.deepEqual(cheesyStrings, [CHEESY_STRING, CHEESIER_STRING]);
+
+ const data = Glean.testOnly.doYouRemember.testGetValue();
+ Assert.equal(MEMORIES.reduce((a, b) => a + b, 0) * 1024 * 1024, data.sum);
+ for (let [bucket, count] of Object.entries(data.values)) {
+ // We could assert instead, but let's skip to save the logspam.
+ if (count == 0) {
+ continue;
+ }
+ Assert.ok(count == 1 && MEMORY_BUCKETS.includes(bucket));
+ }
+
+ const customData = Glean.testOnlyIpc.aCustomDist.testGetValue("store1");
+ Assert.equal(3 + 4, customData.sum, "Sum's correct");
+ for (let [bucket, count] of Object.entries(customData.values)) {
+ Assert.ok(
+ count == 0 || (count == 2 && bucket == 1), // both values in the low bucket
+ `Only two buckets have a sample ${bucket} ${count}`
+ );
+ }
+
+ 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);
+
+ 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(EVENT_EXTRA, events[0].extra);
+
+ const NANOS_IN_MILLIS = 1e6;
+ const EPSILON = 40000; // bug 1701949
+ const times = Glean.testOnly.whatTimeIsIt.testGetValue();
+ Assert.greater(times.sum, 15 * NANOS_IN_MILLIS - EPSILON);
+ // We can't guarantee any specific time values (thank you clocks),
+ // but we can assert there are only two samples.
+ Assert.equal(
+ 2,
+ Object.entries(times.values).reduce(
+ (acc, [bucket, count]) => acc + count,
+ 0
+ )
+ );
+
+ const mabelsCounters = Glean.testOnly.mabelsKitchenCounters;
+ Assert.equal(
+ mabelsCounters.near_the_sink.testGetValue(),
+ COUNTERS_NEAR_THE_SINK
+ );
+ Assert.equal(
+ mabelsCounters.with_junk_on_them.testGetValue(),
+ COUNTERS_WITH_JUNK_ON_THEM
+ );
+
+ Assert.throws(
+ () => Glean.testOnly.mabelsBathroomCounters.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Invalid labels record errors, which throw"
+ );
+
+ Assert.deepEqual(
+ { numerator: 44, denominator: 14 },
+ Glean.testOnlyIpc.irate.testGetValue()
+ );
+ }
+);
diff --git a/toolkit/components/glean/tests/xpcshell/test_JOG.js b/toolkit/components/glean/tests/xpcshell/test_JOG.js
new file mode 100644
index 0000000000..c18ce8cb74
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_JOG.js
@@ -0,0 +1,723 @@
+/* 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_task(
+ /* 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_jog_counter_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "counter",
+ "jog_cat",
+ "jog_counter",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ Glean.jogCat.jogCounter.add(53);
+ Assert.equal(53, Glean.jogCat.jogCounter.testGetValue());
+});
+
+add_task(async function test_jog_string_works() {
+ const value = "an active string!";
+ Services.fog.testRegisterRuntimeMetric(
+ "string",
+ "jog_cat",
+ "jog_string",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ Glean.jogCat.jogString.set(value);
+
+ Assert.equal(value, Glean.jogCat.jogString.testGetValue());
+});
+
+add_task(async function test_jog_string_list_works() {
+ const value = "an active string!";
+ const value2 = "a more active string!";
+ const value3 = "the most active of strings.";
+ Services.fog.testRegisterRuntimeMetric(
+ "string_list",
+ "jog_cat",
+ "jog_string_list",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+
+ const jogList = [value, value2];
+ Glean.jogCat.jogStringList.set(jogList);
+
+ let val = Glean.jogCat.jogStringList.testGetValue();
+ // Note: This is incredibly fragile and will break if we ever rearrange items
+ // in the string list.
+ Assert.deepEqual(jogList, val);
+
+ Glean.jogCat.jogStringList.add(value3);
+ Assert.ok(Glean.jogCat.jogStringList.testGetValue().includes(value3));
+});
+
+add_task(async function test_jog_timespan_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "timespan",
+ "jog_cat",
+ "jog_timespan",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ time_unit: "millisecond" })
+ );
+ Glean.jogCat.jogTimespan.start();
+ Glean.jogCat.jogTimespan.cancel();
+ Assert.equal(undefined, Glean.jogCat.jogTimespan.testGetValue());
+
+ // We start, briefly sleep and then stop.
+ // That guarantees some time to measure.
+ Glean.jogCat.jogTimespan.start();
+ await sleep(10);
+ Glean.jogCat.jogTimespan.stop();
+
+ Assert.ok(Glean.jogCat.jogTimespan.testGetValue() > 0);
+});
+
+add_task(async function test_jog_uuid_works() {
+ const kTestUuid = "decafdec-afde-cafd-ecaf-decafdecafde";
+ Services.fog.testRegisterRuntimeMetric(
+ "uuid",
+ "jog_cat",
+ "jog_uuid",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ Glean.jogCat.jogUuid.set(kTestUuid);
+ Assert.equal(kTestUuid, Glean.jogCat.jogUuid.testGetValue());
+
+ Glean.jogCat.jogUuid.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.jogCat.jogUuid.testGetValue());
+});
+
+add_task(function test_jog_datetime_works() {
+ const value = new Date("2020-06-11T12:00:00");
+ Services.fog.testRegisterRuntimeMetric(
+ "datetime",
+ "jog_cat",
+ "jog_datetime",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ time_unit: "nanosecond" })
+ );
+
+ Glean.jogCat.jogDatetime.set(value.getTime() * 1000);
+
+ const received = Glean.jogCat.jogDatetime.testGetValue();
+ Assert.equal(received.getTime(), value.getTime());
+});
+
+add_task(function test_jog_boolean_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "boolean",
+ "jog_cat",
+ "jog_bool",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ Glean.jogCat.jogBool.set(false);
+ Assert.equal(false, Glean.jogCat.jogBool.testGetValue());
+});
+
+add_task(async function test_jog_event_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "event",
+ "jog_cat",
+ "jog_event_no_extra",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ Glean.jogCat.jogEventNoExtra.record();
+ var events = Glean.jogCat.jogEventNoExtra.testGetValue();
+ Assert.equal(1, events.length);
+ Assert.equal("jog_cat", events[0].category);
+ Assert.equal("jog_event_no_extra", events[0].name);
+
+ Services.fog.testRegisterRuntimeMetric(
+ "event",
+ "jog_cat",
+ "jog_event",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ allowed_extra_keys: ["extra1", "extra2"] })
+ );
+ let extra = { extra1: "can set extras", extra2: "passing more data" };
+ Glean.jogCat.jogEvent.record(extra);
+ events = Glean.jogCat.jogEvent.testGetValue();
+ Assert.equal(1, events.length);
+ Assert.equal("jog_cat", events[0].category);
+ Assert.equal("jog_event", events[0].name);
+ Assert.deepEqual(extra, events[0].extra);
+
+ Services.fog.testRegisterRuntimeMetric(
+ "event",
+ "jog_cat",
+ "jog_event_with_extra",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({
+ allowed_extra_keys: ["extra1", "extra2", "extra3_longer_name"],
+ })
+ );
+ let extra2 = {
+ extra1: "can set extras",
+ extra2: 37,
+ extra3_longer_name: false,
+ };
+ Glean.jogCat.jogEventWithExtra.record(extra2);
+ events = Glean.jogCat.jogEventWithExtra.testGetValue();
+ Assert.equal(1, events.length);
+ Assert.equal("jog_cat", events[0].category);
+ Assert.equal("jog_event_with_extra", events[0].name);
+ let expectedExtra = {
+ extra1: "can set extras",
+ extra2: "37",
+ extra3_longer_name: "false",
+ };
+ Assert.deepEqual(expectedExtra, events[0].extra);
+
+ // Quantities need to be non-negative.
+ let extra4 = {
+ extra2: -1,
+ };
+ Glean.jogCat.jogEventWithExtra.record(extra4);
+ events = Glean.jogCat.jogEventWithExtra.testGetValue();
+ Assert.equal(1, events.length, "Recorded one event too many.");
+
+ // Invalid extra keys don't crash, the event is not recorded.
+ let extra3 = {
+ extra1_nonexistent_extra: "this does not crash",
+ };
+ Glean.jogCat.jogEventWithExtra.record(extra3);
+ // And test methods throw appropriately
+ Assert.throws(
+ () => Glean.jogCat.jogEventWithExtra.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/
+ );
+});
+
+add_task(async function test_jog_memory_distribution_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "memory_distribution",
+ "jog_cat",
+ "jog_memory_dist",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ memory_unit: "megabyte" })
+ );
+ Glean.jogCat.jogMemoryDist.accumulate(7);
+ Glean.jogCat.jogMemoryDist.accumulate(17);
+
+ let data = Glean.jogCat.jogMemoryDist.testGetValue();
+ // `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_jog_custom_distribution_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "custom_distribution",
+ "jog_cat",
+ "jog_custom_dist",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({
+ range_min: 1,
+ range_max: 2147483646,
+ bucket_count: 10,
+ histogram_type: "linear",
+ })
+ );
+ Glean.jogCat.jogCustomDist.accumulateSamples([7, 268435458]);
+
+ let data = Glean.jogCat.jogCustomDist.testGetValue();
+ 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.jogCat.jogCustomDist.accumulateSamples([-7]);
+ Assert.throws(
+ () => Glean.jogCat.jogCustomDist.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/
+ );
+});
+
+add_task(
+ /* TODO: Enable custom ping support on Android */
+ { skip_if: () => AppConstants.platform == "android" },
+ async function test_jog_custom_pings() {
+ Services.fog.testRegisterRuntimeMetric(
+ "boolean",
+ "jog_cat",
+ "jog_ping_bool",
+ ["jog-ping"],
+ `"ping"`,
+ false
+ );
+ Services.fog.testRegisterRuntimePing("jog-ping", true, true, []);
+ Assert.ok("jogPing" in GleanPings);
+ let submitted = false;
+ Glean.jogCat.jogPingBool.set(false);
+ GleanPings.jogPing.testBeforeNextSubmit(reason => {
+ submitted = true;
+ Assert.equal(false, Glean.jogCat.jogPingBool.testGetValue());
+ });
+ GleanPings.jogPing.submit();
+ Assert.ok(submitted, "Ping was submitted, callback was called.");
+ // ping-lifetime value was cleared.
+ Assert.equal(undefined, Glean.jogCat.jogPingBool.testGetValue());
+ }
+);
+
+add_task(async function test_jog_timing_distribution_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "timing_distribution",
+ "jog_cat",
+ "jog_timing_dist",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ time_unit: "microsecond" })
+ );
+ let t1 = Glean.jogCat.jogTimingDist.start();
+ let t2 = Glean.jogCat.jogTimingDist.start();
+
+ await sleep(5);
+
+ let t3 = Glean.jogCat.jogTimingDist.start();
+ Glean.jogCat.jogTimingDist.cancel(t1);
+
+ await sleep(5);
+
+ Glean.jogCat.jogTimingDist.stopAndAccumulate(t2); // 10ms
+ Glean.jogCat.jogTimingDist.stopAndAccumulate(t3); // 5ms
+
+ let data = Glean.jogCat.jogTimingDist.testGetValue();
+ 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, [bucket, count]) => acc + count,
+ 0
+ ),
+ "Only two buckets with samples"
+ );
+});
+
+add_task(async function test_jog_labeled_boolean_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "labeled_boolean",
+ "jog_cat",
+ "jog_labeled_bool",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ Assert.equal(
+ undefined,
+ Glean.jogCat.jogLabeledBool.label_1.testGetValue(),
+ "New labels with no values should return undefined"
+ );
+ Glean.jogCat.jogLabeledBool.label_1.set(true);
+ Glean.jogCat.jogLabeledBool.label_2.set(false);
+ Assert.equal(true, Glean.jogCat.jogLabeledBool.label_1.testGetValue());
+ Assert.equal(false, Glean.jogCat.jogLabeledBool.label_2.testGetValue());
+ // What about invalid/__other__?
+ Assert.equal(undefined, Glean.jogCat.jogLabeledBool.__other__.testGetValue());
+ Glean.jogCat.jogLabeledBool.InvalidLabel.set(true);
+ Assert.throws(
+ () => Glean.jogCat.jogLabeledBool.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Should throw because of a recording error."
+ );
+});
+
+add_task(async function test_jog_labeled_boolean_with_static_labels_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "labeled_boolean",
+ "jog_cat",
+ "jog_labeled_bool_with_labels",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ labels: ["label_1", "label_2"] })
+ );
+ Assert.equal(
+ undefined,
+ Glean.jogCat.jogLabeledBoolWithLabels.label_1.testGetValue(),
+ "New labels with no values should return undefined"
+ );
+ Glean.jogCat.jogLabeledBoolWithLabels.label_1.set(true);
+ Glean.jogCat.jogLabeledBoolWithLabels.label_2.set(false);
+ Assert.equal(
+ true,
+ Glean.jogCat.jogLabeledBoolWithLabels.label_1.testGetValue()
+ );
+ Assert.equal(
+ false,
+ Glean.jogCat.jogLabeledBoolWithLabels.label_2.testGetValue()
+ );
+ // What about invalid/__other__?
+ Assert.equal(
+ undefined,
+ Glean.jogCat.jogLabeledBoolWithLabels.__other__.testGetValue()
+ );
+ Glean.jogCat.jogLabeledBoolWithLabels.label_3.set(true);
+ Assert.equal(
+ true,
+ Glean.jogCat.jogLabeledBoolWithLabels.__other__.testGetValue()
+ );
+ // TODO: Test that we have the right number and type of errors (bug 1683171)
+});
+
+add_task(async function test_jog_labeled_counter_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "labeled_counter",
+ "jog_cat",
+ "jog_labeled_counter",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ Assert.equal(
+ undefined,
+ Glean.jogCat.jogLabeledCounter.label_1.testGetValue(),
+ "New labels with no values should return undefined"
+ );
+ Glean.jogCat.jogLabeledCounter.label_1.add(1);
+ Glean.jogCat.jogLabeledCounter.label_2.add(2);
+ Assert.equal(1, Glean.jogCat.jogLabeledCounter.label_1.testGetValue());
+ Assert.equal(2, Glean.jogCat.jogLabeledCounter.label_2.testGetValue());
+ // What about invalid/__other__?
+ Assert.equal(
+ undefined,
+ Glean.jogCat.jogLabeledCounter.__other__.testGetValue()
+ );
+ Glean.jogCat.jogLabeledCounter.InvalidLabel.add(1);
+ Assert.throws(
+ () => Glean.jogCat.jogLabeledCounter.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Should throw because of a recording error."
+ );
+});
+
+add_task(async function test_jog_labeled_counter_with_static_labels_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "labeled_counter",
+ "jog_cat",
+ "jog_labeled_counter_with_labels",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ labels: ["label_1", "label_2"] })
+ );
+ Assert.equal(
+ undefined,
+ Glean.jogCat.jogLabeledCounterWithLabels.label_1.testGetValue(),
+ "New labels with no values should return undefined"
+ );
+ Glean.jogCat.jogLabeledCounterWithLabels.label_1.add(1);
+ Glean.jogCat.jogLabeledCounterWithLabels.label_2.add(2);
+ Assert.equal(
+ 1,
+ Glean.jogCat.jogLabeledCounterWithLabels.label_1.testGetValue()
+ );
+ Assert.equal(
+ 2,
+ Glean.jogCat.jogLabeledCounterWithLabels.label_2.testGetValue()
+ );
+ // What about invalid/__other__?
+ Assert.equal(
+ undefined,
+ Glean.jogCat.jogLabeledCounterWithLabels.__other__.testGetValue()
+ );
+ Glean.jogCat.jogLabeledCounterWithLabels.InvalidLabel.add(1);
+ // TODO:(bug 1766515) - This should throw.
+ /*Assert.throws(
+ () => Glean.jogCat.jogLabeledCounterWithLabels.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Should throw because of a recording error."
+ );*/
+ Assert.equal(
+ 1,
+ Glean.jogCat.jogLabeledCounterWithLabels.__other__.testGetValue()
+ );
+});
+
+add_task(async function test_jog_labeled_string_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "labeled_string",
+ "jog_cat",
+ "jog_labeled_string",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ Assert.equal(
+ undefined,
+ Glean.jogCat.jogLabeledString.label_1.testGetValue(),
+ "New labels with no values should return undefined"
+ );
+ Glean.jogCat.jogLabeledString.label_1.set("crimson");
+ Glean.jogCat.jogLabeledString.label_2.set("various");
+ Assert.equal("crimson", Glean.jogCat.jogLabeledString.label_1.testGetValue());
+ Assert.equal("various", Glean.jogCat.jogLabeledString.label_2.testGetValue());
+ // What about invalid/__other__?
+ Assert.equal(
+ undefined,
+ Glean.jogCat.jogLabeledString.__other__.testGetValue()
+ );
+ Glean.jogCat.jogLabeledString.InvalidLabel.set("valid");
+ Assert.throws(
+ () => Glean.jogCat.jogLabeledString.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/
+ );
+});
+
+add_task(async function test_jog_labeled_string_with_labels_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "labeled_string",
+ "jog_cat",
+ "jog_labeled_string_with_labels",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ labels: ["label_1", "label_2"] })
+ );
+ Assert.equal(
+ undefined,
+ Glean.jogCat.jogLabeledStringWithLabels.label_1.testGetValue(),
+ "New labels with no values should return undefined"
+ );
+ Glean.jogCat.jogLabeledStringWithLabels.label_1.set("crimson");
+ Glean.jogCat.jogLabeledStringWithLabels.label_2.set("various");
+ Assert.equal(
+ "crimson",
+ Glean.jogCat.jogLabeledStringWithLabels.label_1.testGetValue()
+ );
+ Assert.equal(
+ "various",
+ Glean.jogCat.jogLabeledStringWithLabels.label_2.testGetValue()
+ );
+ // What about invalid/__other__?
+ Assert.equal(
+ undefined,
+ Glean.jogCat.jogLabeledStringWithLabels.__other__.testGetValue()
+ );
+ Glean.jogCat.jogLabeledStringWithLabels.InvalidLabel.set("valid");
+ // TODO:(bug 1766515) - This should throw.
+ /*Assert.throws(
+ () => Glean.jogCat.jogLabeledStringWithLabels.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/
+ );*/
+ Assert.equal(
+ "valid",
+ Glean.jogCat.jogLabeledStringWithLabels.__other__.testGetValue()
+ );
+});
+
+add_task(function test_jog_quantity_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "quantity",
+ "jog_cat",
+ "jog_quantity",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ Glean.jogCat.jogQuantity.set(42);
+ Assert.equal(42, Glean.jogCat.jogQuantity.testGetValue());
+});
+
+add_task(function test_jog_rate_works() {
+ Services.fog.testRegisterRuntimeMetric(
+ "rate",
+ "jog_cat",
+ "jog_rate",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ // 1) Standard rate with internal denominator
+ Glean.jogCat.jogRate.addToNumerator(22);
+ Glean.jogCat.jogRate.addToDenominator(7);
+ Assert.deepEqual(
+ { numerator: 22, denominator: 7 },
+ Glean.jogCat.jogRate.testGetValue()
+ );
+
+ Services.fog.testRegisterRuntimeMetric(
+ "denominator",
+ "jog_cat",
+ "jog_denominator",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({
+ numerators: [
+ {
+ name: "jog_rate_ext",
+ category: "jog_cat",
+ send_in_pings: ["test-only"],
+ lifetime: "ping",
+ disabled: false,
+ },
+ ],
+ })
+ );
+ Services.fog.testRegisterRuntimeMetric(
+ "rate",
+ "jog_cat",
+ "jog_rate_ext",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ // 2) Rate with external denominator
+ Glean.jogCat.jogDenominator.add(11);
+ Glean.jogCat.jogRateExt.addToNumerator(121);
+ Assert.equal(11, Glean.jogCat.jogDenominator.testGetValue());
+ Assert.deepEqual(
+ { numerator: 121, denominator: 11 },
+ Glean.jogCat.jogRateExt.testGetValue()
+ );
+});
+
+add_task(function test_jog_dotted_categories_work() {
+ Services.fog.testRegisterRuntimeMetric(
+ "counter",
+ "jog_cat.dotted",
+ "jog_counter",
+ ["test-only"],
+ `"ping"`,
+ false
+ );
+ Glean.jogCatDotted.jogCounter.add(314);
+ Assert.equal(314, Glean.jogCatDotted.jogCounter.testGetValue());
+});
+
+add_task(
+ /* TODO: Enable custom ping support on Android */
+ { skip_if: () => AppConstants.platform == "android" },
+ async function test_jog_ping_works() {
+ const kReason = "reason-1";
+ Services.fog.testRegisterRuntimePing("my-ping", true, true, [kReason]);
+ let submitted = false;
+ GleanPings.myPing.testBeforeNextSubmit(reason => {
+ submitted = true;
+ Assert.equal(kReason, reason);
+ });
+ GleanPings.myPing.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());
+ const kValue = 42;
+ Glean.testOnlyJog.aCounter.add(kValue);
+ Assert.equal(kValue, Glean.testOnlyJog.aCounter.testGetValue());
+
+ // Let's overwrite the test_only.jog.a_counter counter.
+ Services.fog.testRegisterRuntimeMetric(
+ "counter",
+ "test_only.jog",
+ "a_counter",
+ ["store1"],
+ `"ping"`,
+ true // changing the metric to disabled.
+ );
+
+ Assert.ok("aCounter" in Glean.testOnlyJog);
+ Assert.equal(kValue, Glean.testOnlyJog.aCounter.testGetValue());
+ Glean.testOnlyJog.aCounter.add(kValue);
+ Assert.equal(
+ kValue,
+ Glean.testOnlyJog.aCounter.testGetValue(),
+ "value of now-disabled metric remains unchanged."
+ );
+
+ // Now let's mess with events:
+ Assert.ok("anEvent" in Glean.testOnlyJog);
+ Assert.equal(undefined, Glean.testOnlyJog.anEvent.testGetValue());
+ const extra12 = {
+ extra1: "a value",
+ extra2: "another value",
+ };
+ Glean.testOnlyJog.anEvent.record(extra12);
+ Assert.deepEqual(extra12, Glean.testOnlyJog.anEvent.testGetValue()[0].extra);
+ Services.fog.testRegisterRuntimeMetric(
+ "event",
+ "test_only.jog",
+ "an_event",
+ ["store1"],
+ `"ping"`,
+ false,
+ JSON.stringify({ allowed_extra_keys: ["extra1", "extra2", "extra3"] }) // New extra key just dropped
+ );
+ const extra123 = {
+ extra1: "different value",
+ extra2: "another different value",
+ extra3: 42,
+ };
+ Glean.testOnlyJog.anEvent.record(extra123);
+ Assert.deepEqual(extra123, Glean.testOnlyJog.anEvent.testGetValue()[1].extra);
+});
diff --git a/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js b/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js
new file mode 100644
index 0000000000..2df5a934ba
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_JOGIPC.js
@@ -0,0 +1,260 @@
+/* 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_task(
+ /* on Android FOG is set up through head.js */
+ { skip_if: () => !runningInParent || AppConstants.platform == "android" },
+ function test_setup() {
+ // Give FOG a temp profile to init within.
+ do_get_profile();
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ Services.fog.initializeFOG();
+ }
+);
+
+const COUNT = 42;
+const STRING = "a string!";
+const ANOTHER_STRING = "another string!";
+const EVENT_EXTRA = { extra1: "so very extra" };
+const MEMORIES = [13, 31];
+const MEMORY_BUCKETS = ["13509772", "32131834"]; // buckets are strings : |
+const COUNTERS_1 = 3;
+const COUNTERS_2 = 5;
+const INVALID_COUNTERS = 7;
+
+// It is CRUCIAL that we register metrics in the same order in the parent and
+// in the child or their metric ids will not line up and ALL WILL EXPLODE.
+const METRICS = [
+ ["counter", "jog_ipc", "jog_counter", ["test-only"], `"ping"`, false],
+ ["string_list", "jog_ipc", "jog_string_list", ["test-only"], `"ping"`, false],
+ ["event", "jog_ipc", "jog_event_no_extra", ["test-only"], `"ping"`, false],
+ [
+ "event",
+ "jog_ipc",
+ "jog_event",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ allowed_extra_keys: ["extra1"] }),
+ ],
+ [
+ "memory_distribution",
+ "jog_ipc",
+ "jog_memory_dist",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ memory_unit: "megabyte" }),
+ ],
+ [
+ "timing_distribution",
+ "jog_ipc",
+ "jog_timing_dist",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ time_unit: "nanosecond" }),
+ ],
+ [
+ "custom_distribution",
+ "jog_ipc",
+ "jog_custom_dist",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({
+ range_min: 1,
+ range_max: 2147483646,
+ bucket_count: 10,
+ histogram_type: "linear",
+ }),
+ ],
+ [
+ "labeled_counter",
+ "jog_ipc",
+ "jog_labeled_counter",
+ ["test-only"],
+ `"ping"`,
+ false,
+ ],
+ [
+ "labeled_counter",
+ "jog_ipc",
+ "jog_labeled_counter_err",
+ ["test-only"],
+ `"ping"`,
+ false,
+ ],
+ [
+ "labeled_counter",
+ "jog_ipc",
+ "jog_labeled_counter_with_labels",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ labels: ["label_1", "label_2"] }),
+ ],
+ [
+ "labeled_counter",
+ "jog_ipc",
+ "jog_labeled_counter_with_labels_err",
+ ["test-only"],
+ `"ping"`,
+ false,
+ JSON.stringify({ labels: ["label_1", "label_2"] }),
+ ],
+ ["rate", "jog_ipc", "jog_rate", ["test-only"], `"ping"`, false],
+];
+
+add_task({ skip_if: () => runningInParent }, async function run_child_stuff() {
+ for (let metric of METRICS) {
+ Services.fog.testRegisterRuntimeMetric(...metric);
+ }
+ Glean.jogIpc.jogCounter.add(COUNT);
+ Glean.jogIpc.jogStringList.add(STRING);
+ Glean.jogIpc.jogStringList.add(ANOTHER_STRING);
+
+ Glean.jogIpc.jogEventNoExtra.record();
+ Glean.jogIpc.jogEvent.record(EVENT_EXTRA);
+
+ for (let memory of MEMORIES) {
+ Glean.jogIpc.jogMemoryDist.accumulate(memory);
+ }
+
+ let t1 = Glean.jogIpc.jogTimingDist.start();
+ let t2 = Glean.jogIpc.jogTimingDist.start();
+
+ await sleep(5);
+
+ let t3 = Glean.jogIpc.jogTimingDist.start();
+ Glean.jogIpc.jogTimingDist.cancel(t1);
+
+ await sleep(5);
+
+ Glean.jogIpc.jogTimingDist.stopAndAccumulate(t2); // 10ms
+ Glean.jogIpc.jogTimingDist.stopAndAccumulate(t3); // 5ms
+
+ Glean.jogIpc.jogCustomDist.accumulateSamples([3, 4]);
+
+ Glean.jogIpc.jogLabeledCounter.label_1.add(COUNTERS_1);
+ Glean.jogIpc.jogLabeledCounter.label_2.add(COUNTERS_2);
+
+ Glean.jogIpc.jogLabeledCounterErr.InvalidLabel.add(INVALID_COUNTERS);
+
+ Glean.jogIpc.jogLabeledCounterWithLabels.label_1.add(COUNTERS_1);
+ Glean.jogIpc.jogLabeledCounterWithLabels.label_2.add(COUNTERS_2);
+
+ Glean.jogIpc.jogLabeledCounterWithLabelsErr.InvalidLabel.add(
+ INVALID_COUNTERS
+ );
+
+ Glean.jogIpc.jogRate.addToNumerator(44);
+ Glean.jogIpc.jogRate.addToDenominator(14);
+});
+
+add_task(
+ { skip_if: () => !runningInParent },
+ async function test_child_metrics() {
+ for (let metric of METRICS) {
+ Services.fog.testRegisterRuntimeMetric(...metric);
+ }
+ await run_test_in_child("test_JOGIPC.js");
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(Glean.jogIpc.jogCounter.testGetValue(), COUNT);
+
+ // Note: this will break if string list ever rearranges its items.
+ const strings = Glean.jogIpc.jogStringList.testGetValue();
+ Assert.deepEqual(strings, [STRING, ANOTHER_STRING]);
+
+ const data = Glean.jogIpc.jogMemoryDist.testGetValue();
+ Assert.equal(MEMORIES.reduce((a, b) => a + b, 0) * 1024 * 1024, data.sum);
+ for (let [bucket, count] of Object.entries(data.values)) {
+ // We could assert instead, but let's skip to save the logspam.
+ if (count == 0) {
+ continue;
+ }
+ Assert.ok(count == 1 && MEMORY_BUCKETS.includes(bucket));
+ }
+
+ const customData = Glean.jogIpc.jogCustomDist.testGetValue();
+ Assert.equal(3 + 4, customData.sum, "Sum's correct");
+ for (let [bucket, count] of Object.entries(customData.values)) {
+ Assert.ok(
+ count == 0 || (count == 2 && bucket == 1), // both values in the low bucket
+ `Only two buckets have a sample ${bucket} ${count}`
+ );
+ }
+
+ let events = Glean.jogIpc.jogEventNoExtra.testGetValue();
+ Assert.equal(1, events.length);
+ Assert.equal("jog_ipc", events[0].category);
+ Assert.equal("jog_event_no_extra", events[0].name);
+
+ events = Glean.jogIpc.jogEvent.testGetValue();
+ Assert.equal(1, events.length);
+ Assert.equal("jog_ipc", events[0].category);
+ Assert.equal("jog_event", events[0].name);
+ Assert.deepEqual(EVENT_EXTRA, events[0].extra);
+
+ const NANOS_IN_MILLIS = 1e6;
+ const EPSILON = 40000; // bug 1701949
+ const times = Glean.jogIpc.jogTimingDist.testGetValue();
+ Assert.greater(times.sum, 15 * NANOS_IN_MILLIS - EPSILON);
+ // We can't guarantee any specific time values (thank you clocks),
+ // but we can assert there are only two samples.
+ Assert.equal(
+ 2,
+ Object.entries(times.values).reduce(
+ (acc, [bucket, count]) => acc + count,
+ 0
+ )
+ );
+
+ const labeledCounter = Glean.jogIpc.jogLabeledCounter;
+ Assert.equal(labeledCounter.label_1.testGetValue(), COUNTERS_1);
+ Assert.equal(labeledCounter.label_2.testGetValue(), COUNTERS_2);
+
+ Assert.throws(
+ () => Glean.jogIpc.jogLabeledCounterErr.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Invalid labels record errors, which throw"
+ );
+
+ const labeledCounterWLabels = Glean.jogIpc.jogLabeledCounterWithLabels;
+ Assert.equal(labeledCounterWLabels.label_1.testGetValue(), COUNTERS_1);
+ Assert.equal(labeledCounterWLabels.label_2.testGetValue(), COUNTERS_2);
+
+ // TODO:(bug 1766515) - This should throw.
+ /*Assert.throws(
+ () =>
+ Glean.jogIpc.jogLabeledCounterWithLabelsErr.__other__.testGetValue(),
+ /NS_ERROR_LOSS_OF_SIGNIFICANT_DATA/,
+ "Invalid labels record errors, which throw"
+ );*/
+ Assert.equal(
+ Glean.jogIpc.jogLabeledCounterWithLabelsErr.__other__.testGetValue(),
+ INVALID_COUNTERS
+ );
+
+ Assert.deepEqual(
+ { numerator: 44, denominator: 14 },
+ Glean.jogIpc.jogRate.testGetValue()
+ );
+ }
+);
diff --git a/toolkit/components/glean/tests/xpcshell/test_MillionQ.js b/toolkit/components/glean/tests/xpcshell/test_MillionQ.js
new file mode 100644
index 0000000000..d98e73b451
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/test_MillionQ.js
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function test_queue_longer_than_1k() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // Before init, try and fill the preinit queue with > 1000 tasks.
+ const kIterations = 2000;
+ for (let _i = 0; _i < kIterations; _i++) {
+ Glean.testOnly.badCode.add(1);
+ }
+
+ Services.fog.initializeFOG();
+
+ Assert.equal(kIterations, Glean.testOnly.badCode.testGetValue());
+});
diff --git a/toolkit/components/glean/tests/xpcshell/xpcshell.ini b/toolkit/components/glean/tests/xpcshell/xpcshell.ini
new file mode 100644
index 0000000000..01c527d865
--- /dev/null
+++ b/toolkit/components/glean/tests/xpcshell/xpcshell.ini
@@ -0,0 +1,30 @@
+# Please keep test files lexicographically sorted, with whitespace between.
+[DEFAULT]
+firefox-appdir = browser
+head = head.js
+
+[test_FOGInit.js]
+
+[test_FOGIPCLimit.js]
+
+[test_FOGPrefs.js]
+skip-if = os == "android" # FOG isn't responsible for monitoring prefs and controlling upload on Android
+
+[test_GIFFT.js]
+run-sequentially = very high failure rate in parallel
+
+[test_GIFFTIPC.js]
+
+[test_Glean.js]
+
+[test_GleanIPC.js]
+
+[test_GleanExperiments.js]
+skip-if = os == "android" # FOG isn't responsible for experiment annotations on Android
+
+[test_JOG.js]
+
+[test_JOGIPC.js]
+
+[test_MillionQ.js]
+skip-if = os == "android" # Android inits its own FOG, so the test won't work.