/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", this); ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this); // We need both in order to capture stacks. const ENABLE_TESTS = AppConstants.MOZ_GECKO_PROFILER; /** * Ensures that the sctucture of the javascript object used for capturing stacks * is as intended. The structure is expected to be as in this example: * * { * "memoryMap": [ * [String, String], * ... * ], * "stacks": [ * [ * [Integer, Integer], // Frame * ... * ], * ... * ], * "captures": [ * [String, Integer, Integer], * ... * ] * } * * @param {Object} obj abject to be inpected vor validity. * * @return {Boolean} True if the structure is valid. False - otherwise. */ function checkObjectStructure(obj) { // Ensuring an object is given. if (!obj || typeof obj !== "object") { return false; } // Ensuring all properties exist inside the object and are arrays. for (let property of ["memoryMap", "stacks", "captures"]) { if (!(property in obj) || !Array.isArray(obj[property])) { return false; } } return true; } /** * A helper for triggering a stack capture and returning the new state of stacks. * * @param {String} key The key for capturing stack. * @param {Boolean} clear True to reset captured stacks, False - otherwise. * * @return {Object} captured stacks. */ function captureStacks(key, clear = true) { Telemetry.captureStack(key); let stacks = Telemetry.snapshotCapturedStacks(clear); Assert.ok(checkObjectStructure(stacks)); return stacks; } const TEST_STACK_KEYS = ["TEST-KEY1", "TEST-KEY2"]; /** * Ensures that captured stacks appear in pings, if any were captured. */ add_task( { skip_if: () => !ENABLE_TESTS, }, async function test_capturedStacksAppearInPings() { await TelemetryController.testSetup(); captureStacks("DOES-NOT-MATTER", false); let ping = TelemetryController.getCurrentPingData(); Assert.ok("capturedStacks" in ping.payload.processes.parent); let capturedStacks = ping.payload.processes.parent.capturedStacks; Assert.ok(checkObjectStructure(capturedStacks)); } ); /** * Ensures that capturing a stack for a new key increases the number * of captured stacks and adds a new entry to captures. */ add_task( { skip_if: () => !ENABLE_TESTS, }, function test_CaptureStacksIncreasesNumberOfCapturedStacks() { // Construct a unique key for this test. let key = TEST_STACK_KEYS[0] + "-UNIQUE-KEY-1"; // Ensure that no captures for the key exist. let original = Telemetry.snapshotCapturedStacks(); Assert.equal( undefined, original.captures.find(capture => capture[0] === key) ); // Capture stack and find updated capture stats for TEST_STACK_KEYS[0]. let updated = captureStacks(key); // Ensure that a new element has been appended to both stacks and captures. Assert.equal(original.stacks.length + 1, updated.stacks.length); Assert.equal(original.captures.length + 1, updated.captures.length); // Ensure that the capture info for the key exists and structured well. Assert.deepEqual( [key, original.stacks.length, 1], updated.captures.find(capture => capture[0] === key) ); } ); /** * Ensures that stacks are grouped by the key. If a stack is captured * more than once for the key, the length of stacks does not increase. */ add_task( { skip_if: () => !ENABLE_TESTS, }, function test_CaptureStacksGroupsDuplicateStacks() { // Make sure that there are initial captures for TEST_STACK_KEYS[0]. let stacks = captureStacks(TEST_STACK_KEYS[0], false); let original = { captures: stacks.captures.find( capture => capture[0] === TEST_STACK_KEYS[0] ), stacks: stacks.stacks, }; // Capture stack and find updated capture stats for TEST_STACK_KEYS[0]. stacks = captureStacks(TEST_STACK_KEYS[0]); let updated = { captures: stacks.captures.find( capture => capture[0] === TEST_STACK_KEYS[0] ), stacks: stacks.stacks, }; // The length of captured stacks should remain same. Assert.equal(original.stacks.length, updated.stacks.length); // We expect the info on captures to look as original. Only // stack counter should be increased by one. let expectedCaptures = original.captures; expectedCaptures[2]++; Assert.deepEqual(expectedCaptures, updated.captures); } ); /** * Ensure that capturing the stack for a key does not affect info * for other keys. */ add_task( { skip_if: () => !ENABLE_TESTS, }, function test_CaptureStacksSeparatesInformationByKeys() { // Make sure that there are initial captures for TEST_STACK_KEYS[0]. let stacks = captureStacks(TEST_STACK_KEYS[0], false); let original = { captures: stacks.captures.find( capture => capture[0] === TEST_STACK_KEYS[0] ), stacks: stacks.stacks, }; // Capture stack for a new key. let uniqueKey = TEST_STACK_KEYS[1] + "-UNIQUE-KEY-2"; let updated = captureStacks(uniqueKey); // The length of captured stacks should increase to reflect the new capture. Assert.equal(original.stacks.length + 1, updated.stacks.length); // The information for TEST_STACK_KEYS[0] should remain same. Assert.deepEqual( original.captures, updated.captures.find(capture => capture[0] === TEST_STACK_KEYS[0]) ); } ); /** * Ensure that Telemetry does not allow weird keys. */ add_task( { skip_if: () => !ENABLE_TESTS, }, function test_CaptureStacksDoesNotAllowBadKey() { for (let badKey of [null, 'KEY-!@"#$%^&*()_']) { let stacks = captureStacks(badKey); let captureData = stacks.captures.find(capture => capture[0] === badKey); Assert.ok(!captureData, `"${badKey}" should not be allowed as a key`); } } ); function run_test() { do_get_profile(true); run_next_test(); }