diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/telemetry/tests/browser | |
parent | Initial commit. (diff) | |
download | firefox-esr-upstream.tar.xz firefox-esr-upstream.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/telemetry/tests/browser')
7 files changed, 567 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/browser/browser.ini b/toolkit/components/telemetry/tests/browser/browser.ini new file mode 100644 index 0000000000..4efecc0352 --- /dev/null +++ b/toolkit/components/telemetry/tests/browser/browser.ini @@ -0,0 +1,13 @@ +# 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/. +[DEFAULT] +support-files = + file_iframe.html + file_media.html + gizmo.mp4 + +[browser_UpdatePingSuccess.js] +[browser_DynamicScalars.js] +skip-if = verify +[browser_media_element_in_page_scalar.js] diff --git a/toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js b/toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js new file mode 100644 index 0000000000..74a7e48695 --- /dev/null +++ b/toolkit/components/telemetry/tests/browser/browser_DynamicScalars.js @@ -0,0 +1,243 @@ +"use strict"; + +const { TelemetryController } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryController.sys.mjs" +); +const { TelemetryUtils } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryUtils.sys.mjs" +); + +const CONTENT_CREATED = "ipc:content-created"; + +async function waitForProcessesScalars( + aProcesses, + aKeyed, + aAdditionalCondition = data => true +) { + await TestUtils.waitForCondition(() => { + const scalars = aKeyed + ? Services.telemetry.getSnapshotForKeyedScalars("main", false) + : Services.telemetry.getSnapshotForScalars("main", false); + return ( + aProcesses.every(p => Object.keys(scalars).includes(p)) && + aAdditionalCondition(scalars) + ); + }); +} + +add_task(async function test_setup() { + // Make sure the newly spawned content processes will have extended Telemetry enabled. + // Since Telemetry reads the prefs only at process startup, flush all cached + // and preallocated processes so they pick up the setting. + await SpecialPowers.pushPrefEnv({ + set: [ + [TelemetryUtils.Preferences.OverridePreRelease, true], + ["dom.ipc.processPrelaunch.enabled", false], + ], + }); + Services.ppmm.releaseCachedProcesses(); + await SpecialPowers.pushPrefEnv({ + set: [["dom.ipc.processPrelaunch.enabled", true]], + }); + + // And take care of the already initialized one as well. + let canRecordExtended = Services.telemetry.canRecordExtended; + Services.telemetry.canRecordExtended = true; + registerCleanupFunction( + () => (Services.telemetry.canRecordExtended = canRecordExtended) + ); +}); + +add_task(async function test_recording() { + let currentPid = gBrowser.selectedBrowser.frameLoader.remoteTab.osPid; + + // Register test scalars before spawning the content process: the scalar + // definitions will propagate to it. + Services.telemetry.registerScalars("telemetry.test.dynamic", { + pre_content_spawn: { + kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, + keyed: false, + record_on_release: true, + }, + pre_content_spawn_expiration: { + kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, + keyed: false, + record_on_release: true, + }, + }); + + Services.telemetry.scalarSet( + "telemetry.test.dynamic.pre_content_spawn_expiration", + 3 + ); + + let processCreated = TestUtils.topicObserved(CONTENT_CREATED); + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank", forceNewProcess: true }, + async function (browser) { + // Make sure our new browser is in its own process. The processCreated + // promise should have already resolved by this point. + await processCreated; + let newPid = browser.frameLoader.remoteTab.osPid; + ok(currentPid != newPid, "The new tab must spawn its own process"); + + // Register test scalars after spawning the content process: the scalar + // definitions will propagate to it. + // Also attempt to register again "pre_content_spawn_expiration" and set + // it to expired. + Services.telemetry.registerScalars("telemetry.test.dynamic", { + post_content_spawn: { + kind: Ci.nsITelemetry.SCALAR_TYPE_BOOLEAN, + keyed: false, + record_on_release: false, + }, + post_content_spawn_keyed: { + kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, + keyed: true, + record_on_release: true, + }, + pre_content_spawn_expiration: { + kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, + keyed: false, + record_on_release: true, + expired: true, + }, + }); + + // Accumulate from the content process into both dynamic scalars. + await SpecialPowers.spawn(browser, [], async function () { + Services.telemetry.scalarAdd( + "telemetry.test.dynamic.pre_content_spawn_expiration", + 1 + ); + Services.telemetry.scalarSet( + "telemetry.test.dynamic.pre_content_spawn", + 3 + ); + Services.telemetry.scalarSet( + "telemetry.test.dynamic.post_content_spawn", + true + ); + Services.telemetry.keyedScalarSet( + "telemetry.test.dynamic.post_content_spawn_keyed", + "testKey", + 3 + ); + }); + } + ); + + // Wait for the dynamic scalars to appear non-keyed snapshots. + await waitForProcessesScalars(["dynamic"], true, scalars => { + // Wait for the scalars set in the content process to be available. + return "telemetry.test.dynamic.post_content_spawn_keyed" in scalars.dynamic; + }); + + // Verify the content of the snapshots. + const scalars = Services.telemetry.getSnapshotForScalars("main", false); + ok( + "dynamic" in scalars, + "The scalars must contain the 'dynamic' process section" + ); + ok( + "telemetry.test.dynamic.pre_content_spawn" in scalars.dynamic, + "Dynamic scalars registered before a process spawns must be present." + ); + is( + scalars.dynamic["telemetry.test.dynamic.pre_content_spawn"], + 3, + "The dynamic scalar must contain the expected value." + ); + is( + scalars.dynamic["telemetry.test.dynamic.pre_content_spawn_expiration"], + 3, + "The dynamic scalar must not be updated after being expired." + ); + ok( + "telemetry.test.dynamic.post_content_spawn" in scalars.dynamic, + "Dynamic scalars registered after a process spawns must be present." + ); + is( + scalars.dynamic["telemetry.test.dynamic.post_content_spawn"], + true, + "The dynamic scalar must contain the expected value." + ); + + // Wait for the dynamic scalars to appear in the keyed snapshots. + await waitForProcessesScalars(["dynamic"], true); + + const keyedScalars = Services.telemetry.getSnapshotForKeyedScalars( + "main", + false + ); + ok( + "dynamic" in keyedScalars, + "The keyed scalars must contain the 'dynamic' process section" + ); + ok( + "telemetry.test.dynamic.post_content_spawn_keyed" in keyedScalars.dynamic, + "Dynamic keyed scalars registered after a process spawns must be present." + ); + is( + keyedScalars.dynamic["telemetry.test.dynamic.post_content_spawn_keyed"] + .testKey, + 3, + "The dynamic keyed scalar must contain the expected value." + ); +}); + +add_task(async function test_aggregation() { + Services.telemetry.clearScalars(); + + // Register test scalars before spawning the content process: the scalar + // definitions will propagate to it. Also cheat TelemetrySession to put + // the test scalar in the payload by using "cheattest" instead of "test" in + // the scalar category name. + Services.telemetry.registerScalars("telemetry.cheattest.dynamic", { + test_aggregation: { + kind: Ci.nsITelemetry.SCALAR_TYPE_COUNT, + keyed: false, + record_on_release: true, + }, + }); + + const SCALAR_FULL_NAME = "telemetry.cheattest.dynamic.test_aggregation"; + Services.telemetry.scalarAdd(SCALAR_FULL_NAME, 1); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank", forceNewProcess: true }, + async function (browser) { + // Accumulate from the content process into both dynamic scalars. + await SpecialPowers.spawn( + browser, + [SCALAR_FULL_NAME], + async function (aName) { + Services.telemetry.scalarAdd(aName, 3); + } + ); + } + ); + + // Wait for the dynamic scalars to appear. Since we're testing that children + // and parent data get aggregated, we might need to wait a bit more: + // TelemetryIPCAccumulator.cpp sends batches to the parent process every 2 seconds. + await waitForProcessesScalars(["dynamic"], false, scalarData => { + return ( + "dynamic" in scalarData && + SCALAR_FULL_NAME in scalarData.dynamic && + scalarData.dynamic[SCALAR_FULL_NAME] == 4 + ); + }); + + // Check that the definitions made it to the ping payload. + const pingData = TelemetryController.getCurrentPingData(true); + ok( + "dynamic" in pingData.payload.processes, + "The ping payload must contain the 'dynamic' process section" + ); + is( + pingData.payload.processes.dynamic.scalars[SCALAR_FULL_NAME], + 4, + "The dynamic scalar must contain the aggregated parent and children data." + ); +}); diff --git a/toolkit/components/telemetry/tests/browser/browser_UpdatePingSuccess.js b/toolkit/components/telemetry/tests/browser/browser_UpdatePingSuccess.js new file mode 100644 index 0000000000..09e5e3e62e --- /dev/null +++ b/toolkit/components/telemetry/tests/browser/browser_UpdatePingSuccess.js @@ -0,0 +1,165 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +*/ + +"use strict"; + +const { TelemetryUtils } = ChromeUtils.importESModule( + "resource://gre/modules/TelemetryUtils.sys.mjs" +); +const { TelemetryArchiveTesting } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryArchiveTesting.sys.mjs" +); + +add_task(async function test_updatePing() { + const TEST_VERSION = "37.85"; + const TEST_BUILDID = "20150711123724"; + const XML_UPDATE = `<?xml version="1.0"?> + <updates xmlns="http://www.mozilla.org/2005/app-update"> + <update appVersion="${Services.appinfo.version}" buildID="20080811053724" + channel="nightly" displayVersion="Version 1.0" + installDate="1238441400314" isCompleteUpdate="true" type="minor" + name="Update Test 1.0" detailsURL="http://example.com/" + previousAppVersion="${TEST_VERSION}" + serviceURL="https://example.com/" foregroundDownload="true" + statusText="The Update was successfully installed"> + <patch type="complete" URL="http://example.com/" size="775" + selected="true" state="succeeded"/> + </update> + </updates>`; + + // Set the preferences needed for the test: they will be cleared up + // after it runs. + await SpecialPowers.pushPrefEnv({ + set: [ + [TelemetryUtils.Preferences.UpdatePing, true], + ["browser.startup.homepage_override.mstone", TEST_VERSION], + ["browser.startup.homepage_override.buildID", TEST_BUILDID], + ["toolkit.telemetry.log.level", "Trace"], + ], + }); + + registerCleanupFunction(async () => { + let activeUpdateFile = getActiveUpdateFile(); + activeUpdateFile.remove(false); + reloadUpdateManagerData(true); + }); + writeUpdatesToXMLFile(XML_UPDATE); + reloadUpdateManagerData(false); + + // Start monitoring the ping archive. + let archiveChecker = new TelemetryArchiveTesting.Checker(); + await archiveChecker.promiseInit(); + + // Manually call the BrowserContentHandler: this automatically gets called when + // the browser is started and an update was applied successfully in order to + // display the "update" info page. + Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler).defaultArgs; + + // We cannot control when the ping will be generated/archived after we trigger + // an update, so let's make sure to have one before moving on with validation. + let updatePing; + await BrowserTestUtils.waitForCondition( + async function () { + // Check that the ping made it into the Telemetry archive. + // The test data is defined in ../data/sharedUpdateXML.js + updatePing = await archiveChecker.promiseFindPing("update", [ + [["payload", "reason"], "success"], + [["payload", "previousBuildId"], TEST_BUILDID], + [["payload", "previousVersion"], TEST_VERSION], + ]); + return !!updatePing; + }, + "Make sure the ping is generated before trying to validate it.", + 500, + 100 + ); + + ok(updatePing, "The 'update' ping must be correctly sent."); + + // We have no easy way to simulate a previously applied update from toolkit/telemetry. + // Instead of moving this test to mozapps/update as well, just test that the + // "previousChannel" field is present and either a string or null. + ok( + "previousChannel" in updatePing.payload, + "The payload must contain the 'previousChannel' field" + ); + const channelField = updatePing.payload.previousChannel; + if (channelField != null) { + ok( + typeof channelField == "string", + "'previousChannel' must be a string, if available." + ); + } + + // Also make sure that the ping contains both a client id and an + // environment section. + ok("clientId" in updatePing, "The update ping must report a client id."); + ok( + "environment" in updatePing, + "The update ping must report the environment." + ); +}); + +/** + * Removes the updates.xml file and returns the nsIFile for the + * active-update.xml file. + * + * @return The nsIFile for the active-update.xml file. + */ +function getActiveUpdateFile() { + let updateRootDir = Services.dirsvc.get("UpdRootD", Ci.nsIFile); + let updatesFile = updateRootDir.clone(); + updatesFile.append("updates.xml"); + if (updatesFile.exists()) { + // The following is non-fatal. + try { + updatesFile.remove(false); + } catch (e) {} + } + let activeUpdateFile = updateRootDir.clone(); + activeUpdateFile.append("active-update.xml"); + return activeUpdateFile; +} + +/** + * Reloads the update xml files. + * + * @param skipFiles (optional) + * If true, the update xml files will not be read and the metadata will + * be reset. If false (the default), the update xml files will be read + * to populate the update metadata. + */ +function reloadUpdateManagerData(skipFiles = false) { + Cc["@mozilla.org/updates/update-manager;1"] + .getService(Ci.nsIUpdateManager) + .QueryInterface(Ci.nsIObserver) + .observe(null, "um-reload-update-data", skipFiles ? "skip-files" : ""); +} + +/** + * Writes the updates specified to the active-update.xml file. + * + * @param aText + * The updates represented as a string to write to the active-update.xml + * file. + */ +function writeUpdatesToXMLFile(aText) { + const PERMS_FILE = 0o644; + + const MODE_WRONLY = 0x02; + const MODE_CREATE = 0x08; + const MODE_TRUNCATE = 0x20; + + let activeUpdateFile = getActiveUpdateFile(); + if (!activeUpdateFile.exists()) { + activeUpdateFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE); + } + let fos = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + let flags = MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE; + fos.init(activeUpdateFile, flags, PERMS_FILE, 0); + fos.write(aText, aText.length); + fos.close(); +} diff --git a/toolkit/components/telemetry/tests/browser/browser_media_element_in_page_scalar.js b/toolkit/components/telemetry/tests/browser/browser_media_element_in_page_scalar.js new file mode 100644 index 0000000000..3c9dd8f8ee --- /dev/null +++ b/toolkit/components/telemetry/tests/browser/browser_media_element_in_page_scalar.js @@ -0,0 +1,128 @@ +"use strict"; + +const { TelemetryTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/TelemetryTestUtils.sys.mjs" +); +const LOCATION = + "https://example.com/browser/toolkit/components/telemetry/tests/browser/"; +const CORS_LOCATION = + "https://example.org/browser/toolkit/components/telemetry/tests/browser/"; +const MEDIA_SCALAR_NAME = "media.element_in_page_count"; + +/** + * 'media.element_in_page_count' is a permanant scalar, this test is used to + * check if that scalar can be accumulated correctly under different situations. + */ +add_task(async function start_tests() { + // Clean all scalars first to prevent being interfered by former test. + TelemetryTestUtils.getProcessScalars("parent", false, true /* clear */); + + await testMediaInPageScalar({ + description: "load a page with one media element", + url: "file_media.html", + expectedScalarCount: 1, + }); + await testMediaInPageScalar({ + description: "load a page with multiple media elements", + url: "file_media.html", + options: { + createSecondMedia: true, + }, + expectedScalarCount: 1, + }); + await testMediaInPageScalar({ + description: "load a page with media element created from iframe", + url: "file_iframe.html", + options: { + iframeUrl: "file_media.html", + }, + expectedScalarCount: 1, + }); + await testMediaInPageScalar({ + description: "load a page with media element created from CORS iframe", + url: "file_iframe.html", + options: { + iframeUrl: "file_media.html", + CORSIframe: true, + }, + expectedScalarCount: 1, + }); + await testMediaInPageScalar({ + description: "run multiple tabs, all loading media page", + url: "file_media.html", + options: { + tabNums: 2, + }, + expectedScalarCount: 2, + }); +}); + +async function testMediaInPageScalar({ + description, + url, + options, + expectedScalarCount, +} = {}) { + info(`media scalar should be undefined in the start`); + let scalars = TelemetryTestUtils.getProcessScalars("parent"); + is(scalars[MEDIA_SCALAR_NAME], undefined, "has not created media scalar yet"); + + info(`run test '${description}'`); + url = LOCATION + url; + await runMediaPage(url, options); + + info(`media scalar should be increased to ${expectedScalarCount}`); + scalars = TelemetryTestUtils.getProcessScalars( + "parent", + false, + true /* clear */ + ); + is( + scalars[MEDIA_SCALAR_NAME], + expectedScalarCount, + "media scalar count is correct" + ); + info("============= Next Testcase ============="); +} + +/** + * The following are helper functions. + */ +async function runMediaPage(url, options = {}) { + const tabNums = options.tabNums ? options.tabNums : 1; + for (let idx = 0; idx < tabNums; idx++) { + info(`open a tab loading media page`); + const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + if (options.iframeUrl) { + let iframeURL = options.CORSIframe ? CORS_LOCATION : LOCATION; + iframeURL += options.iframeUrl; + await loadPageForIframe(tab, iframeURL); + } + + if (options.createSecondMedia) { + info(`create second media in the page`); + await createMedia(tab); + } + + info(`remove tab`); + await BrowserTestUtils.removeTab(tab); + await BrowserUtils.promiseObserved("window-global-destroyed"); + } +} + +function createMedia(tab) { + return SpecialPowers.spawn(tab.linkedBrowser, [], _ => { + const video = content.document.createElement("VIDEO"); + video.src = "gizmo.mp4"; + video.loop = true; + content.document.body.appendChild(video); + }); +} + +function loadPageForIframe(tab, url) { + return SpecialPowers.spawn(tab.linkedBrowser, [url], async url => { + const iframe = content.document.getElementById("iframe"); + iframe.src = url; + await new Promise(r => (iframe.onload = r)); + }); +} diff --git a/toolkit/components/telemetry/tests/browser/file_iframe.html b/toolkit/components/telemetry/tests/browser/file_iframe.html new file mode 100644 index 0000000000..271c179eb2 --- /dev/null +++ b/toolkit/components/telemetry/tests/browser/file_iframe.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<title>Media loaded in iframe</title> +</head> +<body> +<iframe id="iframe"></iframe> +</body> +</html> diff --git a/toolkit/components/telemetry/tests/browser/file_media.html b/toolkit/components/telemetry/tests/browser/file_media.html new file mode 100644 index 0000000000..e2109d18f5 --- /dev/null +++ b/toolkit/components/telemetry/tests/browser/file_media.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> +<title>media page</title> +</head> +<body> +<video id="video" src="gizmo.mp4" loop></video> +</body> +</html> diff --git a/toolkit/components/telemetry/tests/browser/gizmo.mp4 b/toolkit/components/telemetry/tests/browser/gizmo.mp4 Binary files differnew file mode 100644 index 0000000000..87efad5ade --- /dev/null +++ b/toolkit/components/telemetry/tests/browser/gizmo.mp4 |