/* 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." ); } );