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/crashreporter/test/unit/head_crashreporter.js | |
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/crashreporter/test/unit/head_crashreporter.js')
-rw-r--r-- | toolkit/crashreporter/test/unit/head_crashreporter.js | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/toolkit/crashreporter/test/unit/head_crashreporter.js b/toolkit/crashreporter/test/unit/head_crashreporter.js new file mode 100644 index 0000000000..c37c8acf8c --- /dev/null +++ b/toolkit/crashreporter/test/unit/head_crashreporter.js @@ -0,0 +1,347 @@ +const { makeFakeAppDir } = ChromeUtils.importESModule( + "resource://testing-common/AppData.sys.mjs" +); +var { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +function getEventDir() { + return PathUtils.join(do_get_tempdir().path, "crash-events"); +} + +function sendCommandAsync(command) { + return new Promise(resolve => { + sendCommand(command, resolve); + }); +} + +/* + * Run an xpcshell subprocess and crash it. + * + * @param setup + * A string of JavaScript code to execute in the subprocess + * before crashing. If this is a function and not a string, + * it will have .toSource() called on it, and turned into + * a call to itself. (for programmer convenience) + * This code will be evaluted between crasher_subprocess_head.js + * and crasher_subprocess_tail.js, so it will have access + * to everything defined in crasher_subprocess_head.js, + * which includes "crashReporter", a variable holding + * the crash reporter service. + * + * @param callback + * A JavaScript function to be called after the subprocess + * crashes. It will be passed (minidump, extra, extrafile), where + * - minidump is an nsIFile of the minidump file produced, + * - extra is an object containing the key,value pairs from + * the .extra file. + * - extrafile is an nsIFile of the extra file + * + * @param canReturnZero + * If true, the subprocess may return with a zero exit code. + * Certain types of crashes may not cause the process to + * exit with an error. + * + */ +async function do_crash(setup, callback, canReturnZero) { + // get current process filename (xpcshell) + let bin = Services.dirsvc.get("XREExeF", Ci.nsIFile); + if (!bin.exists()) { + // weird, can't find xpcshell binary? + do_throw("Can't find xpcshell binary!"); + } + // get Gre dir (GreD) + let greD = Services.dirsvc.get("GreD", Ci.nsIFile); + let headfile = do_get_file("crasher_subprocess_head.js"); + let tailfile = do_get_file("crasher_subprocess_tail.js"); + // run xpcshell -g GreD -f head -e "some setup code" -f tail + let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(bin); + let args = ["-g", greD.path, "-f", headfile.path]; + if (setup) { + if (typeof setup == "function") { + // funky, but convenient + setup = "(" + setup.toSource() + ")();"; + } + args.push("-e", setup); + } + args.push("-f", tailfile.path); + + let crashD = do_get_tempdir(); + crashD.append("crash-events"); + if (!crashD.exists()) { + crashD.create(crashD.DIRECTORY_TYPE, 0o700); + } + + Services.env.set("CRASHES_EVENTS_DIR", crashD.path); + + try { + process.run(true, args, args.length); + } catch (ex) { + // on Windows we exit with a -1 status when crashing. + } finally { + Services.env.set("CRASHES_EVENTS_DIR", ""); + } + + if (!canReturnZero) { + // should exit with an error (should have crashed) + Assert.notEqual(process.exitValue, 0); + } + + await handleMinidump(callback); +} + +function getMinidump() { + let en = do_get_tempdir().directoryEntries; + while (en.hasMoreElements()) { + let f = en.nextFile; + if (f.leafName.substr(-4) == ".dmp") { + return f; + } + } + + return null; +} + +function getMinidumpAnalyzerPath() { + const binSuffix = AppConstants.platform === "win" ? ".exe" : ""; + const exeName = "minidump-analyzer" + binSuffix; + + let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile); + exe.append(exeName); + + return exe; +} + +function runMinidumpAnalyzer(dumpFile, additionalArgs) { + let bin = getMinidumpAnalyzerPath(); + let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(bin); + let args = []; + if (additionalArgs) { + args = args.concat(additionalArgs); + } + args.push(dumpFile.path); + process.run(true /* blocking */, args, args.length); +} + +async function handleMinidump(callback) { + // find minidump + let minidump = getMinidump(); + + if (minidump == null) { + do_throw("No minidump found!"); + } + + let extrafile = minidump.clone(); + extrafile.leafName = extrafile.leafName.slice(0, -4) + ".extra"; + + let memoryfile = minidump.clone(); + memoryfile.leafName = memoryfile.leafName.slice(0, -4) + ".memory.json.gz"; + + let cleanup = async function () { + for (let file of [minidump, extrafile, memoryfile]) { + while (file.exists()) { + try { + file.remove(false); + } catch (e) { + // On Windows the file may be locked, wait briefly and try again + await new Promise(resolve => do_timeout(50, resolve)); + } + } + } + }; + + // Just in case, don't let these files linger. + registerCleanupFunction(cleanup); + + Assert.ok(extrafile.exists()); + let extra = await IOUtils.readJSON(extrafile.path); + + if (callback) { + await callback(minidump, extra, extrafile, memoryfile); + } + + await cleanup(); +} + +function spinEventLoop() { + return new Promise(resolve => { + executeSoon(resolve); + }); +} + +/** + * Helper for testing a content process crash. + * + * This variant accepts a setup function which runs in the content process + * to set data as needed _before_ the crash. The tail file triggers a generic + * crash after setup. + */ +async function do_content_crash(setup, callback) { + do_load_child_test_harness(); + + // Setting the minidump path won't work in the child, so we need to do + // that here. + Services.appinfo.minidumpPath = do_get_tempdir(); + + /* import-globals-from ../unit/crasher_subprocess_head.js */ + /* import-globals-from ../unit/crasher_subprocess_tail.js */ + + let headfile = do_get_file("../unit/crasher_subprocess_head.js"); + let tailfile = do_get_file("../unit/crasher_subprocess_tail.js"); + if (setup) { + if (typeof setup == "function") { + // funky, but convenient + setup = "(" + setup.toSource() + ")();"; + } + } + + do_get_profile(); + await makeFakeAppDir(); + await sendCommandAsync('load("' + headfile.path.replace(/\\/g, "/") + '");'); + if (setup) { + await sendCommandAsync(setup); + } + await sendCommandAsync('load("' + tailfile.path.replace(/\\/g, "/") + '");'); + await spinEventLoop(); + + let minidump = getMinidump(); + let id = minidump.leafName.slice(0, -4); + await Services.crashmanager.ensureCrashIsPresent(id); + try { + await handleMinidump(callback); + } catch (x) { + do_report_unexpected_exception(x); + } +} + +/** + * Helper for testing a content process crash. + * + * This variant accepts a trigger function which runs in the content process + * and does something to _trigger_ the crash. + */ +async function do_triggered_content_crash(trigger, callback) { + do_load_child_test_harness(); + + // Setting the minidump path won't work in the child, so we need to do + // that here. + Services.appinfo.minidumpPath = do_get_tempdir(); + + /* import-globals-from ../unit/crasher_subprocess_head.js */ + + let headfile = do_get_file("../unit/crasher_subprocess_head.js"); + if (trigger) { + if (typeof trigger == "function") { + // funky, but convenient + trigger = "(" + trigger.toSource() + ")();"; + } + } + + do_get_profile(); + await makeFakeAppDir(); + await sendCommandAsync('load("' + headfile.path.replace(/\\/g, "/") + '");'); + await sendCommandAsync(trigger); + await spinEventLoop(); + let id = getMinidump().leafName.slice(0, -4); + await Services.crashmanager.ensureCrashIsPresent(id); + try { + await handleMinidump(callback); + } catch (x) { + do_report_unexpected_exception(x); + } +} + +/* + * Run the `crash` backgroundtask subprocess, crashing it in the + * specified manner. + * + * @param crashType Integer `CrashTestUtils.CRASH_...` code. + * @param crashExtras Dictionary of key-value pairs to include in + * minidump extras. + * + * @param callback + * A JavaScript function to be called after the subprocess + * crashes. It will be passed (minidump, extra, extrafile), where + * - minidump is an nsIFile of the minidump file produced, + * - extra is an object containing the key,value pairs from + * the .extra file. + * - extrafile is an nsIFile of the extra file + * + * @param canReturnZero + * If true, the subprocess may return with a zero exit code. + * Certain types of crashes may not cause the process to + * exit with an error. + * + */ +async function do_backgroundtask_crash( + crashType, + crashExtras, + callback, + canReturnZero +) { + Assert.ok(AppConstants.MOZ_BACKGROUNDTASKS); + + // Get full path to application (not xpcshell) + let bin = Services.dirsvc.get("GreBinD", Ci.nsIFile); + if (AppConstants.platform === "win") { + bin.append(AppConstants.MOZ_APP_NAME + ".exe"); + } else { + bin.append(AppConstants.MOZ_APP_NAME); + } + + // run `application --backgroundtask crash ...`. + let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(bin); + + let args = ["--backgroundtask", "crash"]; + args.push(crashType.toString()); + + // Sorted to be deterministic. + let sorted = Object.entries(crashExtras).sort((a, b) => a[0] < b[0]); + for (let [key, value] of sorted) { + args.push(key); + args.push(value); + } + + let crashD = do_get_tempdir(); + crashD.append("crash-events"); + if (!crashD.exists()) { + crashD.create(crashD.DIRECTORY_TYPE, 0o700); + } + + Services.env.set("CRASHES_EVENTS_DIR", crashD.path); + + // Ensure `resource://testing-common` gets mapped. + let protocolHandler = Services.io + .getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + + let uri = protocolHandler.getSubstitution("testing-common"); + Assert.ok(uri, "resource://testing-common is not substituted"); + + // The equivalent of _TESTING_MODULES_DIR in xpcshell. + Services.env.set("XPCSHELL_TESTING_MODULES_URI", uri.spec); + + try { + process.run(true, args, args.length); + } catch (ex) { + // on Windows we exit with a -1 status when crashing. + } finally { + Services.env.set("CRASHES_EVENTS_DIR", ""); + Services.env.set("XPCSHELL_TESTING_MODULES_URI", ""); + } + + if (!canReturnZero) { + // should exit with an error (should have crashed) + Assert.notEqual(process.exitValue, 0); + } + + await handleMinidump(callback); +} + +// Import binary APIs via js-ctypes. +var { CrashTestUtils } = ChromeUtils.importESModule( + "resource://test/CrashTestUtils.sys.mjs" +); |