From da4c7e7ed675c3bf405668739c3012d140856109 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 15 May 2024 05:34:42 +0200 Subject: Adding upstream version 126.0. Signed-off-by: Daniel Baumann --- .../tests/xpcshell/test_feature_posix_signals.js | 194 +++++++++++++++++++++ tools/profiler/tests/xpcshell/xpcshell.toml | 12 +- 2 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 tools/profiler/tests/xpcshell/test_feature_posix_signals.js (limited to 'tools/profiler/tests/xpcshell') diff --git a/tools/profiler/tests/xpcshell/test_feature_posix_signals.js b/tools/profiler/tests/xpcshell/test_feature_posix_signals.js new file mode 100644 index 0000000000..28fbf890e8 --- /dev/null +++ b/tools/profiler/tests/xpcshell/test_feature_posix_signals.js @@ -0,0 +1,194 @@ +/* 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/. */ + +ChromeUtils.defineESModuleGetters(this, { + Downloads: "resource://gre/modules/Downloads.sys.mjs", + FileUtils: "resource://gre/modules/FileUtils.sys.mjs", + BrowserTestUtils: "resource://testing-common/BrowserTestUtils.sys.mjs", +}); + +const { ctypes } = ChromeUtils.importESModule( + "resource://gre/modules/ctypes.sys.mjs" +); + +// Derived from functionality in js/src/devtools/rootAnalysis/utility.js +function openLibrary(names) { + for (const name of names) { + try { + return ctypes.open(name); + } catch (e) {} + } + return undefined; +} + +// Derived heavily from equivalent sandbox testing code. +// For more details see: +// https://searchfox.org/mozilla-central/rev/1aaacaeb4fa3aca6837ecc157e43e947229ba8ce/security/sandbox/test/browser_content_sandbox_utils.js#89 +function raiseSignal(pid, sig) { + try { + const libc = openLibrary([ + "libc.so.6", + "libc.so", + "libc.dylib", + "libSystem.B.dylib", + ]); + if (!libc) { + info("Failed to open any libc shared object"); + return { ok: false }; + } + + // c.f. https://man7.org/linux/man-pages/man2/kill.2.html + // This choice of typing for `pid` is complex, and brittle, as it's + // platform dependent. Getting it wrong can result in incoreect + // generation/calling of the `kill` function. Unfortunately, as it's + // defined as `pid_t` in a header, we can't easily get access to it. + // For now, we just use an integer, and hope that the system int size + // aligns with the `pid_t` size. + const kill = libc.declare( + "kill", + ctypes.default_abi, + ctypes.int, // return value + ctypes.int32_t, // pid + ctypes.int // sig + ); + + let kres = kill(pid, sig); + if (kres != 0) { + info(`Kill returned a non-zero result ${kres}.`); + return { ok: false }; + } + + libc.close(); + } catch (e) { + info(`Exception ${e} thrown while trying to call kill`); + return { ok: false }; + } + + return { ok: true }; +} + +// We would like to use the following to wait for a stop signal to actually be +// handled: +// await Services.profiler.waitOnePeriodicSampling(); +// However, as we are trying to shut down the profiler using the sampler +// thread, this can cause complications between the callback waiting for the +// sampling to be over, and the sampler thread actually finishing. +// Instead, we use the BrowserTestUtils.waitForCondition to wait until the +// profiler is no longer active. +async function waitUntilProfilerStopped(interval = 1000, maxTries = 100) { + await BrowserTestUtils.waitForCondition( + () => !Services.profiler.IsActive(), + "the profiler should be inactive", + interval, + maxTries + ); +} + +async function cleanupAfterTest() { + // We need to cleanup written profiles after a test + // Get the system downloads directory, and use it to build a profile file + let profile = FileUtils.File(await Downloads.getSystemDownloadsDirectory()); + + // Get the process ID + let pid = Services.appinfo.processID; + + // write it to the profile file name + profile.append(`profile_0_${pid}.json`); + + // remove the file! + await IOUtils.remove(profile.path, { ignoreAbsent: true }); + + // Make sure the profiler is fully stopped, even if the test failed + await Services.profiler.StopProfiler(); +} + +// Hardcode the constants SIGUSR1 and SIGUSR2. +// This is an absolutely terrible idea, as they are implementation defined! +// However, it turns out that for 99% of the platforms we care about, and for +// 99.999% of the platforms we test, these constants are, well, constant. +// Additionally, these constants are only for _testing_ the signal handling +// feature - the actual feature relies on platform specific definitions. This +// may cause a mismatch if we test on on, say, a gnu hurd kernel, or on a +// linux kernel running on sparc, but the feature will not break - only +// the testing. +// const SIGUSR1 = Services.appinfo.OS === "Darwin" ? 30 : 10; +const SIGUSR2 = Services.appinfo.OS === "Darwin" ? 31 : 12; + +add_task(async () => { + info("Test that stopping the profiler with a posix signal works."); + registerCleanupFunction(cleanupAfterTest); + + Assert.ok( + !Services.profiler.IsActive(), + "The profiler should not begin the test active." + ); + + const entries = 100; + const interval = 1; + const threads = []; + const features = []; + + // Start the profiler, and ensure that it's active + await Services.profiler.StartProfiler(entries, interval, threads, features); + Assert.ok(Services.profiler.IsActive(), "The profiler should now be active."); + + // Get the process ID + let pid = Services.appinfo.processID; + + // Try and stop the profiler using a signal. + let result = raiseSignal(pid, SIGUSR2); + Assert.ok(result, "Raising a SIGUSR2 signal should succeed."); + + await waitUntilProfilerStopped(); + + do_test_finished(); +}); + +add_task(async () => { + info( + "Test that stopping the profiler with a posix signal writes a profile file to the system download directory." + ); + registerCleanupFunction(cleanupAfterTest); + + Assert.ok( + !Services.profiler.IsActive(), + "The profiler should not begin the test active." + ); + + const entries = 100; + const interval = 1; + const threads = []; + const features = []; + + // Get the system downloads directory, and use it to build a profile file + let profile = FileUtils.File(await Downloads.getSystemDownloadsDirectory()); + + // Get the process ID + let pid = Services.appinfo.processID; + + // use the pid to construct the name of the profile, and resulting file + profile.append(`profile_0_${pid}.json`); + + // Start the profiler, and ensure that it's active + await Services.profiler.StartProfiler(entries, interval, threads, features); + Assert.ok(Services.profiler.IsActive(), "The profiler should now be active."); + + // Try and stop the profiler using a signal. + let result = raiseSignal(pid, SIGUSR2); + Assert.ok(result, "Raising a SIGUSR2 signal should succeed."); + + // Wait for the file to exist + await BrowserTestUtils.waitForCondition( + async () => await IOUtils.exists(profile.path), + "Waiting for a profile file to be written to disk." + ); + + await waitUntilProfilerStopped(); + Assert.ok( + !Services.profiler.IsActive(), + "The profiler should now be inactive." + ); + + do_test_finished(); +}); diff --git a/tools/profiler/tests/xpcshell/xpcshell.toml b/tools/profiler/tests/xpcshell/xpcshell.toml index 5c094899a4..2cde39d09f 100644 --- a/tools/profiler/tests/xpcshell/xpcshell.toml +++ b/tools/profiler/tests/xpcshell/xpcshell.toml @@ -24,9 +24,6 @@ skip-if = ["!debug"] ["test_feature_fileioall.js"] skip-if = ["release_or_beta"] -# The sanitizer checks appears to overwrite our own memory hooks in xpcshell tests, -# and no allocation markers are gathered. Skip this test in that configuration. - ["test_feature_java.js"] skip-if = ["os != 'android'"] @@ -50,6 +47,12 @@ skip-if = [ "socketprocess_networking", ] +["test_feature_posix_signals.js"] +skip-if = [ + "ccov", + "os == 'win'", +] + # Native stackwalking is somewhat unreliable depending on the platform. # # We don't have frame pointers on macOS release and beta, so stack walking does not @@ -61,6 +64,9 @@ skip-if = [ # For sanitizer builds, there were many intermittents, and we're not getting much # additional coverage there, so it's better to be a bit more reliable. +# The sanitizer checks appears to overwrite our own memory hooks in xpcshell tests, +# and no allocation markers are gathered. Skip this test in that configuration. + ["test_feature_stackwalking.js"] skip-if = [ "os == 'mac' && release_or_beta", -- cgit v1.2.3