summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js')
-rw-r--r--toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js1088
1 files changed, 1088 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js b/toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js
new file mode 100644
index 0000000000..46f1ca9058
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryScalars.js
@@ -0,0 +1,1088 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+ChromeUtils.defineESModuleGetters(this, {
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+});
+
+const UINT_SCALAR = "telemetry.test.unsigned_int_kind";
+const STRING_SCALAR = "telemetry.test.string_kind";
+const BOOLEAN_SCALAR = "telemetry.test.boolean_kind";
+const KEYED_UINT_SCALAR = "telemetry.test.keyed_unsigned_int";
+const KEYED_EXCEED_SCALAR = "telemetry.keyed_scalars_exceed_limit";
+
+function getProcessScalars(aProcessName, aKeyed = false, aClear = false) {
+ const scalars = aKeyed
+ ? Telemetry.getSnapshotForKeyedScalars("main", aClear)[aProcessName]
+ : Telemetry.getSnapshotForScalars("main", aClear)[aProcessName];
+ return scalars || {};
+}
+
+add_task(async function test_serializationFormat() {
+ Telemetry.clearScalars();
+
+ // Set the scalars to a known value.
+ const expectedUint = 3785;
+ const expectedString = "some value";
+ Telemetry.scalarSet(UINT_SCALAR, expectedUint);
+ Telemetry.scalarSet(STRING_SCALAR, expectedString);
+ Telemetry.scalarSet(BOOLEAN_SCALAR, true);
+ Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "first_key", 1234);
+
+ // Get a snapshot of the scalars for the main process (internally called "default").
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+
+ // Check that they are serialized to the correct format.
+ Assert.equal(
+ typeof scalars[UINT_SCALAR],
+ "number",
+ UINT_SCALAR + " must be serialized to the correct format."
+ );
+ Assert.ok(
+ Number.isInteger(scalars[UINT_SCALAR]),
+ UINT_SCALAR + " must be a finite integer."
+ );
+ Assert.equal(
+ scalars[UINT_SCALAR],
+ expectedUint,
+ UINT_SCALAR + " must have the correct value."
+ );
+ Assert.equal(
+ typeof scalars[STRING_SCALAR],
+ "string",
+ STRING_SCALAR + " must be serialized to the correct format."
+ );
+ Assert.equal(
+ scalars[STRING_SCALAR],
+ expectedString,
+ STRING_SCALAR + " must have the correct value."
+ );
+ Assert.equal(
+ typeof scalars[BOOLEAN_SCALAR],
+ "boolean",
+ BOOLEAN_SCALAR + " must be serialized to the correct format."
+ );
+ Assert.equal(
+ scalars[BOOLEAN_SCALAR],
+ true,
+ BOOLEAN_SCALAR + " must have the correct value."
+ );
+ Assert.ok(
+ !(KEYED_UINT_SCALAR in scalars),
+ "Keyed scalars must be reported in a separate section."
+ );
+});
+
+add_task(async function test_keyedSerializationFormat() {
+ Telemetry.clearScalars();
+
+ const expectedKey = "first_key";
+ const expectedOtherKey = "漢語";
+ const expectedUint = 3785;
+ const expectedOtherValue = 1107;
+
+ Telemetry.scalarSet(UINT_SCALAR, expectedUint);
+ Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, expectedKey, expectedUint);
+ Telemetry.keyedScalarSet(
+ KEYED_UINT_SCALAR,
+ expectedOtherKey,
+ expectedOtherValue
+ );
+
+ // Get a snapshot of the scalars.
+ const keyedScalars = TelemetryTestUtils.getProcessScalars("parent", true);
+
+ Assert.ok(
+ !(UINT_SCALAR in keyedScalars),
+ UINT_SCALAR + " must not be serialized with the keyed scalars."
+ );
+ Assert.ok(
+ KEYED_UINT_SCALAR in keyedScalars,
+ KEYED_UINT_SCALAR + " must be serialized with the keyed scalars."
+ );
+ Assert.equal(
+ Object.keys(keyedScalars[KEYED_UINT_SCALAR]).length,
+ 2,
+ "The keyed scalar must contain exactly 2 keys."
+ );
+ Assert.ok(
+ expectedKey in keyedScalars[KEYED_UINT_SCALAR],
+ KEYED_UINT_SCALAR + " must contain the expected keys."
+ );
+ Assert.ok(
+ expectedOtherKey in keyedScalars[KEYED_UINT_SCALAR],
+ KEYED_UINT_SCALAR + " must contain the expected keys."
+ );
+ Assert.ok(
+ Number.isInteger(keyedScalars[KEYED_UINT_SCALAR][expectedKey]),
+ KEYED_UINT_SCALAR + "." + expectedKey + " must be a finite integer."
+ );
+ Assert.equal(
+ keyedScalars[KEYED_UINT_SCALAR][expectedKey],
+ expectedUint,
+ KEYED_UINT_SCALAR + "." + expectedKey + " must have the correct value."
+ );
+ Assert.equal(
+ keyedScalars[KEYED_UINT_SCALAR][expectedOtherKey],
+ expectedOtherValue,
+ KEYED_UINT_SCALAR + "." + expectedOtherKey + " must have the correct value."
+ );
+});
+
+add_task(async function test_nonexistingScalar() {
+ const NON_EXISTING_SCALAR = "telemetry.test.non_existing";
+
+ Telemetry.clearScalars();
+
+ // The JS API must not throw when used incorrectly but rather print
+ // a message to the console.
+ Telemetry.scalarAdd(NON_EXISTING_SCALAR, 11715);
+ Telemetry.scalarSet(NON_EXISTING_SCALAR, 11715);
+ Telemetry.scalarSetMaximum(NON_EXISTING_SCALAR, 11715);
+
+ // Make sure we do not throw on any operation for non-existing scalars.
+ Telemetry.keyedScalarAdd(NON_EXISTING_SCALAR, "some_key", 11715);
+ Telemetry.keyedScalarSet(NON_EXISTING_SCALAR, "some_key", 11715);
+ Telemetry.keyedScalarSetMaximum(NON_EXISTING_SCALAR, "some_key", 11715);
+
+ // Get a snapshot of the scalars.
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+
+ Assert.ok(
+ !(NON_EXISTING_SCALAR in scalars),
+ "The non existing scalar must not be persisted."
+ );
+
+ const keyedScalars = TelemetryTestUtils.getProcessScalars("parent", true);
+
+ Assert.ok(
+ !(NON_EXISTING_SCALAR in keyedScalars),
+ "The non existing keyed scalar must not be persisted."
+ );
+});
+
+add_task(async function test_expiredScalar() {
+ const EXPIRED_SCALAR = "telemetry.test.expired";
+ const EXPIRED_KEYED_SCALAR = "telemetry.test.keyed_expired";
+ const UNEXPIRED_SCALAR = "telemetry.test.unexpired";
+
+ Telemetry.clearScalars();
+
+ // Try to set the expired scalar to some value. We will not be recording the value,
+ // but we shouldn't throw.
+ Telemetry.scalarAdd(EXPIRED_SCALAR, 11715);
+ Telemetry.scalarSet(EXPIRED_SCALAR, 11715);
+ Telemetry.scalarSetMaximum(EXPIRED_SCALAR, 11715);
+ Telemetry.keyedScalarAdd(EXPIRED_KEYED_SCALAR, "some_key", 11715);
+ Telemetry.keyedScalarSet(EXPIRED_KEYED_SCALAR, "some_key", 11715);
+ Telemetry.keyedScalarSetMaximum(EXPIRED_KEYED_SCALAR, "some_key", 11715);
+
+ // The unexpired scalar has an expiration version, but far away in the future.
+ const expectedValue = 11716;
+ Telemetry.scalarSet(UNEXPIRED_SCALAR, expectedValue);
+
+ // Get a snapshot of the scalars.
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+ const keyedScalars = TelemetryTestUtils.getProcessScalars("parent");
+
+ Assert.ok(
+ !(EXPIRED_SCALAR in scalars),
+ "The expired scalar must not be persisted."
+ );
+ Assert.equal(
+ scalars[UNEXPIRED_SCALAR],
+ expectedValue,
+ "The unexpired scalar must be persisted with the correct value."
+ );
+ Assert.ok(
+ !(EXPIRED_KEYED_SCALAR in keyedScalars),
+ "The expired keyed scalar must not be persisted."
+ );
+});
+
+add_task(async function test_unsignedIntScalar() {
+ let checkScalar = expectedValue => {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+ Assert.equal(
+ scalars[UINT_SCALAR],
+ expectedValue,
+ UINT_SCALAR + " must contain the expected value."
+ );
+ };
+
+ Telemetry.clearScalars();
+
+ // Let's start with an accumulation without a prior set.
+ Telemetry.scalarAdd(UINT_SCALAR, 1);
+ Telemetry.scalarAdd(UINT_SCALAR, 2);
+ // Do we get what we expect?
+ checkScalar(3);
+
+ // Let's test setting the scalar to a value.
+ Telemetry.scalarSet(UINT_SCALAR, 3785);
+ checkScalar(3785);
+ Telemetry.scalarAdd(UINT_SCALAR, 1);
+ checkScalar(3786);
+
+ // Does setMaximum work?
+ Telemetry.scalarSet(UINT_SCALAR, 2);
+ checkScalar(2);
+ Telemetry.scalarSetMaximum(UINT_SCALAR, 5);
+ checkScalar(5);
+ // The value of the probe should still be 5, as the previous value
+ // is greater than the one we want to set.
+ Telemetry.scalarSetMaximum(UINT_SCALAR, 3);
+ checkScalar(5);
+
+ // Check that non-integer numbers get truncated and set.
+ Telemetry.scalarSet(UINT_SCALAR, 3.785);
+ checkScalar(3);
+
+ // Setting or adding a negative number must report an error through
+ // the console and drop the change (shouldn't throw).
+ Telemetry.scalarAdd(UINT_SCALAR, -5);
+ Telemetry.scalarSet(UINT_SCALAR, -5);
+ Telemetry.scalarSetMaximum(UINT_SCALAR, -1);
+ checkScalar(3);
+
+ // If we try to set a value of a different type, the JS API should not
+ // throw but rather print a console message.
+ Telemetry.scalarSet(UINT_SCALAR, 1);
+ Telemetry.scalarSet(UINT_SCALAR, "unexpected value");
+ Telemetry.scalarAdd(UINT_SCALAR, "unexpected value");
+ Telemetry.scalarSetMaximum(UINT_SCALAR, "unexpected value");
+ // The stored value must not be compromised.
+ checkScalar(1);
+});
+
+add_task(async function test_stringScalar() {
+ let checkExpectedString = expectedString => {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+ Assert.equal(
+ scalars[STRING_SCALAR],
+ expectedString,
+ STRING_SCALAR + " must contain the expected string value."
+ );
+ };
+
+ Telemetry.clearScalars();
+
+ // Let's check simple strings...
+ let expected = "test string";
+ Telemetry.scalarSet(STRING_SCALAR, expected);
+ checkExpectedString(expected);
+ expected = "漢語";
+ Telemetry.scalarSet(STRING_SCALAR, expected);
+ checkExpectedString(expected);
+
+ // We have some unsupported operations for strings.
+ Telemetry.scalarAdd(STRING_SCALAR, 1);
+ Telemetry.scalarAdd(STRING_SCALAR, "string value");
+ Telemetry.scalarSetMaximum(STRING_SCALAR, 1);
+ Telemetry.scalarSetMaximum(STRING_SCALAR, "string value");
+ Telemetry.scalarSet(STRING_SCALAR, 1);
+
+ // Try to set the scalar to a string longer than the maximum length limit.
+ const LONG_STRING =
+ "browser.qaxfiuosnzmhlg.rpvxicawolhtvmbkpnludhedobxvkjwqyeyvmv";
+ Telemetry.scalarSet(STRING_SCALAR, LONG_STRING);
+ checkExpectedString(LONG_STRING.substr(0, 50));
+});
+
+add_task(async function test_booleanScalar() {
+ let checkExpectedBool = expectedBoolean => {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+ Assert.equal(
+ scalars[BOOLEAN_SCALAR],
+ expectedBoolean,
+ BOOLEAN_SCALAR + " must contain the expected boolean value."
+ );
+ };
+
+ Telemetry.clearScalars();
+
+ // Set a test boolean value.
+ let expected = false;
+ Telemetry.scalarSet(BOOLEAN_SCALAR, expected);
+ checkExpectedBool(expected);
+ expected = true;
+ Telemetry.scalarSet(BOOLEAN_SCALAR, expected);
+ checkExpectedBool(expected);
+
+ // Check that setting a numeric value implicitly converts to boolean.
+ Telemetry.scalarSet(BOOLEAN_SCALAR, 1);
+ checkExpectedBool(true);
+ Telemetry.scalarSet(BOOLEAN_SCALAR, 0);
+ checkExpectedBool(false);
+ Telemetry.scalarSet(BOOLEAN_SCALAR, 1.0);
+ checkExpectedBool(true);
+ Telemetry.scalarSet(BOOLEAN_SCALAR, 0.0);
+ checkExpectedBool(false);
+
+ // Check that unsupported operations for booleans do not throw.
+ Telemetry.scalarAdd(BOOLEAN_SCALAR, 1);
+ Telemetry.scalarAdd(BOOLEAN_SCALAR, "string value");
+ Telemetry.scalarSetMaximum(BOOLEAN_SCALAR, 1);
+ Telemetry.scalarSetMaximum(BOOLEAN_SCALAR, "string value");
+ Telemetry.scalarSet(BOOLEAN_SCALAR, "true");
+});
+
+add_task(async function test_scalarRecording() {
+ const OPTIN_SCALAR = "telemetry.test.release_optin";
+ const OPTOUT_SCALAR = "telemetry.test.release_optout";
+
+ let checkValue = (scalarName, expectedValue) => {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+ Assert.equal(
+ scalars[scalarName],
+ expectedValue,
+ scalarName + " must contain the expected value."
+ );
+ };
+
+ let checkNotSerialized = scalarName => {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+ Assert.ok(!(scalarName in scalars), scalarName + " was not recorded.");
+ };
+
+ Telemetry.canRecordBase = false;
+ Telemetry.canRecordExtended = false;
+ Telemetry.clearScalars();
+
+ // Check that no scalar is recorded if both base and extended recording are off.
+ Telemetry.scalarSet(OPTOUT_SCALAR, 3);
+ Telemetry.scalarSet(OPTIN_SCALAR, 3);
+ checkNotSerialized(OPTOUT_SCALAR);
+ checkNotSerialized(OPTIN_SCALAR);
+
+ // Check that opt-out scalars are recorded, while opt-in are not.
+ Telemetry.canRecordBase = true;
+ Telemetry.scalarSet(OPTOUT_SCALAR, 3);
+ Telemetry.scalarSet(OPTIN_SCALAR, 3);
+ checkValue(OPTOUT_SCALAR, 3);
+ checkNotSerialized(OPTIN_SCALAR);
+
+ // Check that both opt-out and opt-in scalars are recorded.
+ Telemetry.canRecordExtended = true;
+ Telemetry.scalarSet(OPTOUT_SCALAR, 5);
+ Telemetry.scalarSet(OPTIN_SCALAR, 6);
+ checkValue(OPTOUT_SCALAR, 5);
+ checkValue(OPTIN_SCALAR, 6);
+});
+
+add_task(async function test_keyedScalarRecording() {
+ const OPTIN_SCALAR = "telemetry.test.keyed_release_optin";
+ const OPTOUT_SCALAR = "telemetry.test.keyed_release_optout";
+ const testKey = "policy_key";
+
+ let checkValue = (scalarName, expectedValue) => {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ Assert.equal(
+ scalars[scalarName][testKey],
+ expectedValue,
+ scalarName + " must contain the expected value."
+ );
+ };
+
+ let checkNotSerialized = scalarName => {
+ const scalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ Assert.ok(!(scalarName in scalars), scalarName + " was not recorded.");
+ };
+
+ Telemetry.canRecordBase = false;
+ Telemetry.canRecordExtended = false;
+ Telemetry.clearScalars();
+
+ // Check that no scalar is recorded if both base and extended recording are off.
+ Telemetry.keyedScalarSet(OPTOUT_SCALAR, testKey, 3);
+ Telemetry.keyedScalarSet(OPTIN_SCALAR, testKey, 3);
+ checkNotSerialized(OPTOUT_SCALAR);
+ checkNotSerialized(OPTIN_SCALAR);
+
+ // Check that opt-out scalars are recorded, while opt-in are not.
+ Telemetry.canRecordBase = true;
+ Telemetry.keyedScalarSet(OPTOUT_SCALAR, testKey, 3);
+ Telemetry.keyedScalarSet(OPTIN_SCALAR, testKey, 3);
+ checkValue(OPTOUT_SCALAR, 3);
+ checkNotSerialized(OPTIN_SCALAR);
+
+ // Check that both opt-out and opt-in scalars are recorded.
+ Telemetry.canRecordExtended = true;
+ Telemetry.keyedScalarSet(OPTOUT_SCALAR, testKey, 5);
+ Telemetry.keyedScalarSet(OPTIN_SCALAR, testKey, 6);
+ checkValue(OPTOUT_SCALAR, 5);
+ checkValue(OPTIN_SCALAR, 6);
+});
+
+add_task(async function test_subsession() {
+ Telemetry.clearScalars();
+
+ // Set the scalars to a known value.
+ Telemetry.scalarSet(UINT_SCALAR, 3785);
+ Telemetry.scalarSet(STRING_SCALAR, "some value");
+ Telemetry.scalarSet(BOOLEAN_SCALAR, false);
+ Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "some_random_key", 12);
+
+ // Get a snapshot and reset the subsession. The value we set must be there.
+ let scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ let keyedScalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
+
+ Assert.equal(
+ scalars[UINT_SCALAR],
+ 3785,
+ UINT_SCALAR + " must contain the expected value."
+ );
+ Assert.equal(
+ scalars[STRING_SCALAR],
+ "some value",
+ STRING_SCALAR + " must contain the expected value."
+ );
+ Assert.equal(
+ scalars[BOOLEAN_SCALAR],
+ false,
+ BOOLEAN_SCALAR + " must contain the expected value."
+ );
+ Assert.equal(
+ keyedScalars[KEYED_UINT_SCALAR].some_random_key,
+ 12,
+ KEYED_UINT_SCALAR + " must contain the expected value."
+ );
+
+ // Get a new snapshot and reset the subsession again. Since no new value
+ // was set, the scalars should not be reported.
+ scalars = TelemetryTestUtils.getProcessScalars("parent", false, true);
+ keyedScalars = TelemetryTestUtils.getProcessScalars("parent", true, true);
+
+ Assert.ok(
+ !(UINT_SCALAR in scalars),
+ UINT_SCALAR + " must be empty and not reported."
+ );
+ Assert.ok(
+ !(STRING_SCALAR in scalars),
+ STRING_SCALAR + " must be empty and not reported."
+ );
+ Assert.ok(
+ !(BOOLEAN_SCALAR in scalars),
+ BOOLEAN_SCALAR + " must be empty and not reported."
+ );
+ Assert.ok(
+ !(KEYED_UINT_SCALAR in keyedScalars),
+ KEYED_UINT_SCALAR + " must be empty and not reported."
+ );
+});
+
+add_task(async function test_keyed_uint() {
+ Telemetry.clearScalars();
+
+ const KEYS = ["a_key", "another_key", "third_key"];
+ let expectedValues = [1, 1, 1];
+
+ // Set all the keys to a baseline value.
+ for (let key of KEYS) {
+ Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, key, 1);
+ }
+
+ // Increment only one key.
+ Telemetry.keyedScalarAdd(KEYED_UINT_SCALAR, KEYS[1], 1);
+ expectedValues[1]++;
+
+ // Use SetMaximum on the third key.
+ Telemetry.keyedScalarSetMaximum(KEYED_UINT_SCALAR, KEYS[2], 37);
+ expectedValues[2] = 37;
+
+ // Get a snapshot of the scalars and make sure the keys contain
+ // the correct values.
+ const keyedScalars = TelemetryTestUtils.getProcessScalars("parent", true);
+
+ for (let k = 0; k < 3; k++) {
+ const keyName = KEYS[k];
+ Assert.equal(
+ keyedScalars[KEYED_UINT_SCALAR][keyName],
+ expectedValues[k],
+ KEYED_UINT_SCALAR + "." + keyName + " must contain the correct value."
+ );
+ }
+
+ // Do not throw when doing unsupported things on uint keyed scalars.
+ // Just test one single unsupported operation, the other are covered in the plain
+ // unsigned scalar test.
+ Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "new_key", "unexpected value");
+});
+
+add_task(async function test_keyed_boolean() {
+ Telemetry.clearScalars();
+
+ const KEYED_BOOLEAN_TYPE = "telemetry.test.keyed_boolean_kind";
+ const first_key = "first_key";
+ const second_key = "second_key";
+
+ // Set the initial values.
+ Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, first_key, true);
+ Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, second_key, false);
+
+ // Get a snapshot of the scalars and make sure the keys contain
+ // the correct values.
+ let keyedScalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ Assert.equal(
+ keyedScalars[KEYED_BOOLEAN_TYPE][first_key],
+ true,
+ "The key must contain the expected value."
+ );
+ Assert.equal(
+ keyedScalars[KEYED_BOOLEAN_TYPE][second_key],
+ false,
+ "The key must contain the expected value."
+ );
+
+ // Now flip the values and make sure we get the expected values back.
+ Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, first_key, false);
+ Telemetry.keyedScalarSet(KEYED_BOOLEAN_TYPE, second_key, true);
+
+ keyedScalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ Assert.equal(
+ keyedScalars[KEYED_BOOLEAN_TYPE][first_key],
+ false,
+ "The key must contain the expected value."
+ );
+ Assert.equal(
+ keyedScalars[KEYED_BOOLEAN_TYPE][second_key],
+ true,
+ "The key must contain the expected value."
+ );
+
+ // Do not throw when doing unsupported things on a boolean keyed scalars.
+ // Just test one single unsupported operation, the other are covered in the plain
+ // boolean scalar test.
+ Telemetry.keyedScalarAdd(KEYED_BOOLEAN_TYPE, "somehey", 1);
+});
+
+add_task(async function test_keyed_keys_length() {
+ Telemetry.clearScalars();
+
+ const LONG_KEY_STRING =
+ "browser.qaxfiuosnzmhlg.rpvxicawolhtvmbkpnludhedobxvkjwqyeyvmv.somemoresowereach70chars";
+ const NORMAL_KEY = "a_key";
+
+ // Set the value for a key within the length limits.
+ Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, NORMAL_KEY, 1);
+
+ // Now try to set and modify the value for a very long key (must not throw).
+ Telemetry.keyedScalarAdd(KEYED_UINT_SCALAR, LONG_KEY_STRING, 10);
+ Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, LONG_KEY_STRING, 1);
+ Telemetry.keyedScalarSetMaximum(KEYED_UINT_SCALAR, LONG_KEY_STRING, 10);
+
+ // Also attempt to set the value for an empty key.
+ Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, "", 1);
+
+ // Make sure the key with the right length contains the expected value.
+ let keyedScalars = TelemetryTestUtils.getProcessScalars("parent", true);
+ Assert.equal(
+ Object.keys(keyedScalars[KEYED_UINT_SCALAR]).length,
+ 1,
+ "The keyed scalar must contain exactly 1 key."
+ );
+ Assert.ok(
+ NORMAL_KEY in keyedScalars[KEYED_UINT_SCALAR],
+ "The keyed scalar must contain the expected key."
+ );
+ Assert.equal(
+ keyedScalars[KEYED_UINT_SCALAR][NORMAL_KEY],
+ 1,
+ "The key must contain the expected value."
+ );
+ Assert.ok(
+ !(LONG_KEY_STRING in keyedScalars[KEYED_UINT_SCALAR]),
+ "The data for the long key should not have been recorded."
+ );
+ Assert.ok(
+ !("" in keyedScalars[KEYED_UINT_SCALAR]),
+ "The data for the empty key should not have been recorded."
+ );
+});
+
+add_task(async function test_keyed_max_keys() {
+ Telemetry.clearScalars();
+
+ // Generate the names for the first 100 keys.
+ let keyNamesSet = new Set();
+ for (let k = 0; k < 100; k++) {
+ keyNamesSet.add("key_" + k);
+ }
+
+ // Add 100 keys to an histogram and set their initial value.
+ let valueToSet = 0;
+ keyNamesSet.forEach(keyName => {
+ Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, keyName, valueToSet++);
+ });
+
+ // Perform some operations on the 101th key. This should throw, as
+ // we're not allowed to have more than 100 keys.
+ const LAST_KEY_NAME = "overflowing_key";
+ Telemetry.keyedScalarAdd(KEYED_UINT_SCALAR, LAST_KEY_NAME, 10);
+ Telemetry.keyedScalarSet(KEYED_UINT_SCALAR, LAST_KEY_NAME, 1);
+ Telemetry.keyedScalarSetMaximum(KEYED_UINT_SCALAR, LAST_KEY_NAME, 10);
+
+ // Make sure all the keys except the last one are available and have the correct
+ // values.
+ let keyedScalars = TelemetryTestUtils.getProcessScalars("parent", true);
+
+ // Check that the keyed scalar only contain the first 100 keys.
+ const reportedKeysSet = new Set(Object.keys(keyedScalars[KEYED_UINT_SCALAR]));
+ Assert.ok(
+ [...keyNamesSet].filter(x => reportedKeysSet.has(x)) &&
+ [...reportedKeysSet].filter(x => keyNamesSet.has(x)),
+ "The keyed scalar must contain all the 100 keys, and drop the others."
+ );
+
+ // Check that all the keys recorded the expected values.
+ let expectedValue = 0;
+ keyNamesSet.forEach(keyName => {
+ Assert.equal(
+ keyedScalars[KEYED_UINT_SCALAR][keyName],
+ expectedValue++,
+ "The key must contain the expected value."
+ );
+ });
+
+ // Check that KEYED_EXCEED_SCALAR is in keyedScalars
+ Assert.ok(
+ KEYED_EXCEED_SCALAR in keyedScalars,
+ "We have exceeded maximum number of Keys."
+ );
+
+ // Generate the names for the exceeded keys
+ let keyNamesSet2 = new Set();
+ for (let k = 0; k < 100; k++) {
+ keyNamesSet2.add("key2_" + k);
+ }
+
+ // Add 100 keys to the keyed exceed scalar and set their initial value.
+ valueToSet = 0;
+ keyNamesSet2.forEach(keyName2 => {
+ Telemetry.keyedScalarSet(KEYED_EXCEED_SCALAR, keyName2, valueToSet++);
+ });
+
+ // Check that there are exactly 100 keys in KEYED_EXCEED_SCALAR
+ let snapshot = Telemetry.getSnapshotForKeyedScalars("main", false);
+ Assert.equal(
+ 100,
+ Object.keys(snapshot.parent[KEYED_UINT_SCALAR]).length,
+ "The keyed scalar must contain all the 100 keys."
+ );
+
+ // Check that KEYED_UINT_SCALAR is in keyedScalars and its value equals 3
+ Assert.ok(
+ KEYED_UINT_SCALAR in keyedScalars[KEYED_EXCEED_SCALAR],
+ "The keyed Scalar is in the keyed exceeded scalar"
+ );
+ Assert.equal(
+ keyedScalars[KEYED_EXCEED_SCALAR][KEYED_UINT_SCALAR],
+ 3,
+ "We have exactly 3 keys over the limit"
+ );
+});
+
+add_task(async function test_dynamicScalars_registration() {
+ Telemetry.clearScalars();
+
+ const TEST_CASES = [
+ {
+ category: "telemetry.test",
+ data: {
+ missing_kind: {
+ keyed: false,
+ record_on_release: true,
+ },
+ },
+ evaluation: /missing 'kind'/,
+ description: "Registration must fail if required fields are missing",
+ },
+ {
+ category: "telemetry.test",
+ data: {
+ invalid_collection: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ record_on_release: "opt-in",
+ },
+ },
+ evaluation: /Invalid 'record_on_release'/,
+ description:
+ "Registration must fail if 'record_on_release' is of the wrong type",
+ },
+ {
+ category: "telemetry.test",
+ data: {
+ invalid_kind: {
+ kind: "12",
+ },
+ },
+ evaluation: /Invalid or missing 'kind'/,
+ description: "Registration must fail if 'kind' is of the wrong type",
+ },
+ {
+ category: "telemetry.test",
+ data: {
+ invalid_expired: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ expired: "never",
+ },
+ },
+ evaluation: /Invalid 'expired'/,
+ description: "Registration must fail if 'expired' is of the wrong type",
+ },
+ {
+ category: "telemetry.test",
+ data: {
+ valid_scalar: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ keyed: false,
+ record_on_release: true,
+ },
+ invalid_scalar: {
+ expired: false,
+ },
+ },
+ evaluation: /Invalid or missing 'kind'/,
+ description:
+ "No scalar must be registered if the batch contains an invalid one",
+ },
+ {
+ category: "telemetry.test",
+ data: {
+ invalid_stores: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ keyed: false,
+ stores: true,
+ },
+ },
+ evaluation: /Invalid 'stores'/,
+ description: "Registration must fail if 'stores' is of the wrong type",
+ },
+ {
+ category: "telemetry.test",
+ data: {
+ invalid_stores: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ keyed: false,
+ stores: {},
+ },
+ },
+ evaluation: /Invalid 'stores'/,
+ description: "Registration must fail if 'stores' is of the wrong type",
+ },
+ {
+ category: "telemetry.test",
+ data: {
+ invalid_stores: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ keyed: false,
+ stores: [{}],
+ },
+ },
+ evaluation: /'stores' array isn't a string./,
+ description:
+ "Registration must fail if element in 'stores' is of the wrong type",
+ },
+ ];
+
+ for (let testCase of TEST_CASES) {
+ Assert.throws(
+ () => Telemetry.registerScalars(testCase.category, testCase.data),
+ testCase.evaluation,
+ testCase.description
+ );
+ }
+});
+
+add_task(async function test_dynamicScalars_doubleRegistration() {
+ Telemetry.clearScalars();
+
+ // Register a test scalar.
+ Telemetry.registerScalars("telemetry.test.dynamic", {
+ double_registration_1: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ record_on_release: true,
+ },
+ });
+
+ // Verify that we can record the scalar.
+ Telemetry.scalarSet("telemetry.test.dynamic.double_registration_1", 1);
+
+ // Register the same scalar again, along with a second scalar.
+ // This must not throw.
+ Telemetry.registerScalars("telemetry.test.dynamic", {
+ double_registration_1: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ record_on_release: true,
+ },
+ double_registration_2: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ record_on_release: true,
+ },
+ });
+
+ // Set the dynamic scalars to some test values.
+ Telemetry.scalarAdd("telemetry.test.dynamic.double_registration_1", 1);
+ Telemetry.scalarSet("telemetry.test.dynamic.double_registration_2", 3);
+
+ // Get a snapshot of the scalars and check that the dynamic ones were correctly set.
+ let scalars = getProcessScalars("dynamic", false, false);
+
+ Assert.equal(
+ scalars["telemetry.test.dynamic.double_registration_1"],
+ 2,
+ "The recorded scalar must contain the right value."
+ );
+ Assert.equal(
+ scalars["telemetry.test.dynamic.double_registration_2"],
+ 3,
+ "The recorded scalar must contain the right value."
+ );
+
+ // Register an existing scalar again, only change the definition
+ // to make it expire.
+ Telemetry.registerScalars("telemetry.test.dynamic", {
+ double_registration_2: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ record_on_release: true,
+ expired: true,
+ },
+ });
+
+ // Attempt to record and make sure that no recording happens.
+ Telemetry.scalarAdd("telemetry.test.dynamic.double_registration_2", 1);
+ scalars = getProcessScalars("dynamic", false, false);
+ Assert.equal(
+ scalars["telemetry.test.dynamic.double_registration_2"],
+ 3,
+ "The recorded scalar must contain the right value."
+ );
+});
+
+add_task(async function test_dynamicScalars_recording() {
+ Telemetry.clearScalars();
+
+ // Disable extended recording so that we will just record opt-out.
+ Telemetry.canRecordExtended = false;
+
+ // Register some test scalars.
+ Telemetry.registerScalars("telemetry.test.dynamic", {
+ record_optout: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ record_on_release: true,
+ },
+ record_keyed: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
+ keyed: true,
+ record_on_release: true,
+ },
+ record_optin: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_BOOLEAN,
+ record_on_release: false,
+ },
+ record_expired: {
+ kind: Ci.nsITelemetry.SCALAR_TYPE_STRING,
+ expired: true,
+ record_on_release: true,
+ },
+ });
+
+ // Set the dynamic scalars to some test values.
+ Telemetry.scalarSet("telemetry.test.dynamic.record_optout", 1);
+ Telemetry.keyedScalarSet("telemetry.test.dynamic.record_keyed", "someKey", 5);
+ Telemetry.scalarSet("telemetry.test.dynamic.record_optin", false);
+ Telemetry.scalarSet("telemetry.test.dynamic.record_expired", "test");
+
+ // Get a snapshot of the scalars and check that the dynamic ones were correctly set.
+ let scalars = getProcessScalars("dynamic", false, false);
+ let keyedScalars = getProcessScalars("dynamic", true, true);
+
+ Assert.ok(
+ !("telemetry.test.dynamic.record_optin" in scalars),
+ "Dynamic opt-in scalars must not be recorded."
+ );
+ Assert.ok(
+ "telemetry.test.dynamic.record_keyed" in keyedScalars,
+ "Dynamic opt-out keyed scalars must be recorded."
+ );
+ Assert.ok(
+ !("telemetry.test.dynamic.record_expired" in scalars),
+ "Dynamic expired scalars must not be recorded."
+ );
+ Assert.ok(
+ "telemetry.test.dynamic.record_optout" in scalars,
+ "Dynamic opt-out scalars must be recorded."
+ );
+ Assert.equal(
+ scalars["telemetry.test.dynamic.record_optout"],
+ 1,
+ "The recorded scalar must contain the right value."
+ );
+ Assert.equal(
+ keyedScalars["telemetry.test.dynamic.record_keyed"].someKey,
+ 5,
+ "The recorded keyed scalar must contain the right value."
+ );
+
+ // Enable extended recording.
+ Telemetry.canRecordExtended = true;
+
+ // Set the dynamic scalars to some test values.
+ Telemetry.scalarSet("telemetry.test.dynamic.record_optin", true);
+ Telemetry.scalarSet("telemetry.test.dynamic.record_expired", "test");
+
+ // Get a snapshot of the scalars and check that the dynamic ones were correctly set.
+ scalars = getProcessScalars("dynamic", false, true);
+
+ Assert.ok(
+ !("telemetry.test.dynamic.record_expired" in scalars),
+ "Dynamic expired scalars must not be recorded."
+ );
+ Assert.ok(
+ "telemetry.test.dynamic.record_optin" in scalars,
+ "Dynamic opt-in scalars must be recorded."
+ );
+ Assert.equal(
+ scalars["telemetry.test.dynamic.record_optin"],
+ true,
+ "The recorded scalar must contain the right value."
+ );
+});
+
+add_task(
+ {
+ skip_if: () => gIsAndroid,
+ },
+ async function test_productSpecificScalar() {
+ const DEFAULT_PRODUCT_SCALAR = "telemetry.test.default_products";
+ const DESKTOP_ONLY_SCALAR = "telemetry.test.desktop_only";
+ const MULTIPRODUCT_SCALAR = "telemetry.test.multiproduct";
+ const MOBILE_ONLY_SCALAR = "telemetry.test.mobile_only";
+ const MOBILE_ONLY_KEYED_SCALAR = "telemetry.test.keyed_mobile_only";
+
+ Telemetry.clearScalars();
+
+ // Try to set the desktop scalars
+ let expectedValue = 11714;
+ Telemetry.scalarAdd(DEFAULT_PRODUCT_SCALAR, expectedValue);
+ Telemetry.scalarAdd(DESKTOP_ONLY_SCALAR, expectedValue);
+ Telemetry.scalarAdd(MULTIPRODUCT_SCALAR, expectedValue);
+
+ // Try to set the mobile-only scalar to some value. We will not be recording the value,
+ // but we shouldn't throw.
+ let expectedKey = "some_key";
+ Telemetry.scalarSet(MOBILE_ONLY_SCALAR, 11715);
+ Telemetry.scalarSetMaximum(MOBILE_ONLY_SCALAR, 11715);
+ Telemetry.keyedScalarAdd(MOBILE_ONLY_KEYED_SCALAR, expectedKey, 11715);
+ Telemetry.keyedScalarSet(MOBILE_ONLY_KEYED_SCALAR, expectedKey, 11715);
+ Telemetry.keyedScalarSetMaximum(
+ MOBILE_ONLY_KEYED_SCALAR,
+ expectedKey,
+ 11715
+ );
+
+ // Get a snapshot of the scalars.
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+ const keyedScalars = TelemetryTestUtils.getProcessScalars("parent");
+
+ Assert.equal(
+ scalars[DEFAULT_PRODUCT_SCALAR],
+ expectedValue,
+ "The default platfomrs scalar must contain the right value"
+ );
+ Assert.equal(
+ scalars[DESKTOP_ONLY_SCALAR],
+ expectedValue,
+ "The desktop-only scalar must contain the right value"
+ );
+ Assert.equal(
+ scalars[MULTIPRODUCT_SCALAR],
+ expectedValue,
+ "The multiproduct scalar must contain the right value"
+ );
+
+ Assert.ok(
+ !(MOBILE_ONLY_SCALAR in scalars),
+ "The mobile-only scalar must not be persisted."
+ );
+ Assert.ok(
+ !(MOBILE_ONLY_KEYED_SCALAR in keyedScalars),
+ "The mobile-only keyed scalar must not be persisted."
+ );
+ }
+);
+
+add_task(
+ {
+ skip_if: () => !gIsAndroid,
+ },
+ async function test_mobileSpecificScalar() {
+ const DEFAULT_PRODUCT_SCALAR = "telemetry.test.default_products";
+ const DESKTOP_ONLY_SCALAR = "telemetry.test.desktop_only";
+ const DESKTOP_ONLY_KEYED_SCALAR = "telemetry.test.keyed_desktop_only";
+ const MULTIPRODUCT_SCALAR = "telemetry.test.multiproduct";
+ const MOBILE_ONLY_SCALAR = "telemetry.test.mobile_only";
+ const MOBILE_ONLY_KEYED_SCALAR = "telemetry.test.keyed_mobile_only";
+
+ Telemetry.clearScalars();
+
+ // Try to set the mobile and multiproduct scalars
+ let expectedValue = 11714;
+ let expectedKey = "some_key";
+ Telemetry.scalarAdd(DEFAULT_PRODUCT_SCALAR, expectedValue);
+ Telemetry.scalarAdd(MOBILE_ONLY_SCALAR, expectedValue);
+ Telemetry.keyedScalarSet(
+ MOBILE_ONLY_KEYED_SCALAR,
+ expectedKey,
+ expectedValue
+ );
+ Telemetry.scalarAdd(MULTIPRODUCT_SCALAR, expectedValue);
+
+ // Try to set the desktop-only scalar to some value. We will not be recording the value,
+ // but we shouldn't throw.
+ Telemetry.scalarSet(DESKTOP_ONLY_SCALAR, 11715);
+ Telemetry.scalarSetMaximum(DESKTOP_ONLY_SCALAR, 11715);
+ Telemetry.keyedScalarAdd(DESKTOP_ONLY_KEYED_SCALAR, expectedKey, 11715);
+ Telemetry.keyedScalarSet(DESKTOP_ONLY_KEYED_SCALAR, expectedKey, 11715);
+ Telemetry.keyedScalarSetMaximum(
+ DESKTOP_ONLY_KEYED_SCALAR,
+ expectedKey,
+ 11715
+ );
+
+ // Get a snapshot of the scalars.
+ const scalars = TelemetryTestUtils.getProcessScalars("parent");
+ const keyedScalars = TelemetryTestUtils.getProcessScalars("parent", true);
+
+ Assert.equal(
+ scalars[DEFAULT_PRODUCT_SCALAR],
+ expectedValue,
+ "The default products scalar must contain the right value"
+ );
+ Assert.equal(
+ scalars[MOBILE_ONLY_SCALAR],
+ expectedValue,
+ "The mobile-only scalar must contain the right value"
+ );
+ Assert.equal(
+ keyedScalars[MOBILE_ONLY_KEYED_SCALAR][expectedKey],
+ expectedValue,
+ "The mobile-only keyed scalar must contain the right value"
+ );
+ Assert.equal(
+ scalars[MULTIPRODUCT_SCALAR],
+ expectedValue,
+ "The multiproduct scalar must contain the right value"
+ );
+
+ Assert.ok(
+ !(DESKTOP_ONLY_SCALAR in scalars),
+ "The desktop-only scalar must not be persisted."
+ );
+ Assert.ok(
+ !(DESKTOP_ONLY_KEYED_SCALAR in keyedScalars),
+ "The desktop-only keyed scalar must not be persisted."
+ );
+ }
+);