diff --git a/.circleci/config.yml b/.circleci/config.yml index 68cf567953..af8624b9c8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,7 +45,7 @@ executors: type: enum enum: ["medium", "xlarge", "2xlarge+"] docker: - - image: electron.azurecr.io/build:4fc81b50f9c0980699d329bc32062fac20a26701 + - image: electron.azurecr.io/build:fe71f448c9b00708c7a8a67a0210bcef5055ac64 resource_class: << parameters.size >> macos: diff --git a/build/zip.py b/build/zip.py index 285544aed0..19fe514cd9 100644 --- a/build/zip.py +++ b/build/zip.py @@ -31,12 +31,6 @@ PATHS_TO_SKIP = [ # //chrome/browser/resources/ssl/ssl_error_assistant, but we don't need to # ship it. 'pyproto', - # On Windows, this binary doesn't exist (the crashpad handler is built-in). - # On MacOS, the binary is called 'chrome_crashpad_handler' and is inside the - # app bundle. - # On Linux, we don't use crashpad, but this binary is still built for some - # reason. Exclude it from the zip. - './crashpad_handler', # Skip because these are outputs that we don't need. 'resources/inspector', 'gen/third_party/devtools-frontend/src', diff --git a/patches/chromium/crash_allow_setting_more_options.patch b/patches/chromium/crash_allow_setting_more_options.patch index 261818089d..0016add76e 100644 --- a/patches/chromium/crash_allow_setting_more_options.patch +++ b/patches/chromium/crash_allow_setting_more_options.patch @@ -74,6 +74,32 @@ index 39557cce474439238255ecd28030215085db0c81..5b3f980837911c710686ab91a2a81c31 #if defined(OS_ANDROID) // Used by WebView to sample crashes without generating the unwanted dumps. If // the returned value is less than 100, crash dumping will be sampled to that +diff --git a/components/crash/core/app/crashpad_linux.cc b/components/crash/core/app/crashpad_linux.cc +index 5f97c1ef00d9c63a7b16265cc97d9f145adae550..8c3028f228373b5e1145fe3235dc06663f8b087f 100644 +--- a/components/crash/core/app/crashpad_linux.cc ++++ b/components/crash/core/app/crashpad_linux.cc +@@ -165,6 +165,7 @@ base::FilePath PlatformCrashpadInitialization( + // where crash_reporter provides it's own values for lsb-release. + annotations["lsb-release"] = base::GetLinuxDistro(); + #endif ++ crash_reporter_client->GetProcessSimpleAnnotations(&annotations); + + std::vector arguments; + if (crash_reporter_client->ShouldMonitorCrashHandlerExpensively()) { +@@ -186,6 +187,13 @@ base::FilePath PlatformCrashpadInitialization( + } + #endif + ++ if (!crash_reporter_client->GetShouldRateLimit()) { ++ arguments.push_back("--no-rate-limit"); ++ } ++ if (!crash_reporter_client->GetShouldCompressUploads()) { ++ arguments.push_back("--no-upload-gzip"); ++ } ++ + bool result = + client.StartHandler(handler_path, database_path, metrics_path, url, + annotations, arguments, false, false); diff --git a/components/crash/core/app/crashpad_mac.mm b/components/crash/core/app/crashpad_mac.mm index e3fc1fb2bcab31d6a7cb325a892acb26dc00d4e4..fd654d6e514de416457c283caeb1895dba6286e1 100644 --- a/components/crash/core/app/crashpad_mac.mm diff --git a/script/zip_manifests/dist_zip.linux.arm.manifest b/script/zip_manifests/dist_zip.linux.arm.manifest index 95f27df842..2bca76d434 100644 --- a/script/zip_manifests/dist_zip.linux.arm.manifest +++ b/script/zip_manifests/dist_zip.linux.arm.manifest @@ -3,6 +3,7 @@ LICENSES.chromium.html chrome-sandbox chrome_100_percent.pak chrome_200_percent.pak +crashpad_handler electron icudtl.dat libEGL.so diff --git a/script/zip_manifests/dist_zip.linux.arm64.manifest b/script/zip_manifests/dist_zip.linux.arm64.manifest index 95f27df842..2bca76d434 100644 --- a/script/zip_manifests/dist_zip.linux.arm64.manifest +++ b/script/zip_manifests/dist_zip.linux.arm64.manifest @@ -3,6 +3,7 @@ LICENSES.chromium.html chrome-sandbox chrome_100_percent.pak chrome_200_percent.pak +crashpad_handler electron icudtl.dat libEGL.so diff --git a/script/zip_manifests/dist_zip.linux.x64.manifest b/script/zip_manifests/dist_zip.linux.x64.manifest index 95f27df842..2bca76d434 100644 --- a/script/zip_manifests/dist_zip.linux.x64.manifest +++ b/script/zip_manifests/dist_zip.linux.x64.manifest @@ -3,6 +3,7 @@ LICENSES.chromium.html chrome-sandbox chrome_100_percent.pak chrome_200_percent.pak +crashpad_handler electron icudtl.dat libEGL.so diff --git a/script/zip_manifests/dist_zip.linux.x86.manifest b/script/zip_manifests/dist_zip.linux.x86.manifest index 95f27df842..2bca76d434 100644 --- a/script/zip_manifests/dist_zip.linux.x86.manifest +++ b/script/zip_manifests/dist_zip.linux.x86.manifest @@ -3,6 +3,7 @@ LICENSES.chromium.html chrome-sandbox chrome_100_percent.pak chrome_200_percent.pak +crashpad_handler electron icudtl.dat libEGL.so diff --git a/shell/app/electron_crash_reporter_client.cc b/shell/app/electron_crash_reporter_client.cc index 39c330d4fb..87dfdc344f 100644 --- a/shell/app/electron_crash_reporter_client.cc +++ b/shell/app/electron_crash_reporter_client.cc @@ -153,6 +153,9 @@ bool ElectronCrashReporterClient::GetCrashDumpLocation( base::FilePath* crash_dir) { bool result = base::PathService::Get(electron::DIR_CRASH_DUMPS, crash_dir); { + // If the DIR_CRASH_DUMPS path is overridden with + // app.setPath('crashDumps', ...) then the directory might not have been + // created. base::ThreadRestrictions::ScopedAllowIO allow_io; if (result && !base::PathExists(*crash_dir)) { return base::CreateDirectory(*crash_dir); @@ -162,13 +165,6 @@ bool ElectronCrashReporterClient::GetCrashDumpLocation( } #endif -#if defined(OS_MAC) || defined(OS_LINUX) -bool ElectronCrashReporterClient::GetCrashMetricsLocation( - base::FilePath* metrics_dir) { - return base::PathService::Get(chrome::DIR_USER_DATA, metrics_dir); -} -#endif // OS_MAC || OS_LINUX - bool ElectronCrashReporterClient::IsRunningUnattended() { return !collect_stats_consent_; } diff --git a/shell/app/electron_crash_reporter_client.h b/shell/app/electron_crash_reporter_client.h index f5f50671cf..345607e605 100644 --- a/shell/app/electron_crash_reporter_client.h +++ b/shell/app/electron_crash_reporter_client.h @@ -52,10 +52,6 @@ class ElectronCrashReporterClient : public crash_reporter::CrashReporterClient { bool GetCrashDumpLocation(base::FilePath* crash_dir) override; #endif -#if defined(OS_MAC) || defined(OS_LINUX) - bool GetCrashMetricsLocation(base::FilePath* metrics_dir) override; -#endif - bool IsRunningUnattended() override; bool GetCollectStatsConsent() override; diff --git a/shell/app/electron_main_delegate.cc b/shell/app/electron_main_delegate.cc index 5647b7c811..074338c84e 100644 --- a/shell/app/electron_main_delegate.cc +++ b/shell/app/electron_main_delegate.cc @@ -58,7 +58,8 @@ #endif #if !defined(MAS_BUILD) -#include "components/crash/core/app/crashpad.h" // nogncheck +#include "components/crash/core/app/crash_switches.h" // nogncheck +#include "components/crash/core/app/crashpad.h" // nogncheck #include "components/crash/core/common/crash_key.h" #include "components/crash/core/common/crash_keys.h" #include "shell/app/electron_crash_reporter_client.h" @@ -369,9 +370,19 @@ void ElectronMainDelegate::PreSandboxStartup() { #endif #if defined(OS_LINUX) + // Zygote needs to call InitCrashReporter() in RunZygote(). if (process_type != ::switches::kZygoteProcess && !process_type.empty()) { ElectronCrashReporterClient::Create(); - breakpad::InitCrashReporter(process_type); + if (crash_reporter::IsCrashpadEnabled()) { + if (command_line->HasSwitch( + crash_reporter::switches::kCrashpadHandlerPid)) { + crash_reporter::InitializeCrashpad(false, process_type); + crash_reporter::SetFirstChanceExceptionHandler( + v8::TryHandleWebAssemblyTrapPosix); + } + } else { + breakpad::InitCrashReporter(process_type); + } } #endif @@ -466,7 +477,16 @@ void ElectronMainDelegate::ZygoteForked() { base::CommandLine::ForCurrentProcess(); std::string process_type = command_line->GetSwitchValueASCII(::switches::kProcessType); - breakpad::InitCrashReporter(process_type); + if (crash_reporter::IsCrashpadEnabled()) { + if (command_line->HasSwitch( + crash_reporter::switches::kCrashpadHandlerPid)) { + crash_reporter::InitializeCrashpad(false, process_type); + crash_reporter::SetFirstChanceExceptionHandler( + v8::TryHandleWebAssemblyTrapPosix); + } + } else { + breakpad::InitCrashReporter(process_type); + } // Reset the command line for the newly spawned process. crash_keys::SetCrashKeysFromCommandLine(*command_line); diff --git a/shell/browser/api/electron_api_crash_reporter.cc b/shell/browser/api/electron_api_crash_reporter.cc index 212057bce9..75bc4bbb52 100644 --- a/shell/browser/api/electron_api_crash_reporter.cc +++ b/shell/browser/api/electron_api_crash_reporter.cc @@ -44,6 +44,7 @@ #include "base/guid.h" #include "components/crash/core/app/breakpad_linux.h" #include "components/crash/core/common/crash_keys.h" +#include "components/upload_list/combining_upload_list.h" #include "v8/include/v8-wasm-trap-handler-posix.h" #include "v8/include/v8.h" #endif @@ -150,16 +151,29 @@ void Start(const std::string& submit_url, ? "node" : command_line->GetSwitchValueASCII(::switches::kProcessType); #if defined(OS_LINUX) - ::crash_keys::SetMetricsClientIdFromGUID(GetClientId()); - auto& global_crash_keys = GetGlobalCrashKeysMutable(); - for (const auto& pair : global_extra) { - global_crash_keys[pair.first] = pair.second; + if (::crash_reporter::IsCrashpadEnabled()) { + for (const auto& pair : extra) + electron::crash_keys::SetCrashKey(pair.first, pair.second); + { + base::ThreadRestrictions::ScopedAllowIO allow_io; + ::crash_reporter::InitializeCrashpad(process_type.empty(), process_type); + } + if (ignore_system_crash_handler) { + crashpad::CrashpadInfo::GetCrashpadInfo() + ->set_system_crash_reporter_forwarding(crashpad::TriState::kDisabled); + } + } else { + ::crash_keys::SetMetricsClientIdFromGUID(GetClientId()); + auto& global_crash_keys = GetGlobalCrashKeysMutable(); + for (const auto& pair : global_extra) { + global_crash_keys[pair.first] = pair.second; + } + for (const auto& pair : extra) + electron::crash_keys::SetCrashKey(pair.first, pair.second); + for (const auto& pair : global_extra) + electron::crash_keys::SetCrashKey(pair.first, pair.second); + breakpad::InitCrashReporter(process_type); } - for (const auto& pair : extra) - electron::crash_keys::SetCrashKey(pair.first, pair.second); - for (const auto& pair : global_extra) - electron::crash_keys::SetCrashKey(pair.first, pair.second); - breakpad::InitCrashReporter(process_type); #elif defined(OS_MAC) for (const auto& pair : extra) electron::crash_keys::SetCrashKey(pair.first, pair.second); @@ -203,7 +217,20 @@ scoped_refptr CreateCrashUploadList() { base::PathService::Get(electron::DIR_CRASH_DUMPS, &crash_dir_path); base::FilePath upload_log_path = crash_dir_path.AppendASCII(CrashUploadList::kReporterLogFilename); - return base::MakeRefCounted(upload_log_path); + scoped_refptr result = + base::MakeRefCounted(upload_log_path); + if (crash_reporter::IsCrashpadEnabled()) { + // Crashpad keeps the records of C++ crashes (segfaults, etc) in its + // internal database. The JavaScript error reporter writes JS error upload + // records to the older text format. Combine the two to present a complete + // list to the user. + // TODO(nornagon): what is "The JavaScript error reporter", and do we care + // about it? + std::vector> uploaders = { + base::MakeRefCounted(), std::move(result)}; + result = base::MakeRefCounted(std::move(uploaders)); + } + return result; #endif // defined(OS_MAC) || defined(OS_WIN) } diff --git a/shell/browser/electron_browser_client.cc b/shell/browser/electron_browser_client.cc index ebcba97354..9e0ba62208 100644 --- a/shell/browser/electron_browser_client.cc +++ b/shell/browser/electron_browser_client.cc @@ -298,6 +298,12 @@ breakpad::CrashHandlerHostLinux* CreateCrashHandlerHost( } int GetCrashSignalFD(const base::CommandLine& command_line) { + if (crash_reporter::IsCrashpadEnabled()) { + int fd; + pid_t pid; + return crash_reporter::GetHandlerSocket(&fd, &pid) ? fd : -1; + } + // Extensions have the same process type as renderers. if (command_line.HasSwitch(extensions::switches::kExtensionProcess)) { static breakpad::CrashHandlerHostLinux* crash_handler = nullptr; @@ -526,20 +532,37 @@ void ElectronBrowserClient::AppendExtraCommandLineSwitches( #if defined(OS_LINUX) bool enable_crash_reporter = false; - enable_crash_reporter = breakpad::IsCrashReporterEnabled(); + if (crash_reporter::IsCrashpadEnabled()) { + command_line->AppendSwitch(::switches::kEnableCrashpad); + enable_crash_reporter = true; + + int fd; + pid_t pid; + + if (crash_reporter::GetHandlerSocket(&fd, &pid)) { + command_line->AppendSwitchASCII( + crash_reporter::switches::kCrashpadHandlerPid, + base::NumberToString(pid)); + } + } else { + enable_crash_reporter = breakpad::IsCrashReporterEnabled(); + } + if (enable_crash_reporter) { std::string switch_value = api::crash_reporter::GetClientId() + ",no_channel"; command_line->AppendSwitchASCII(::switches::kEnableCrashReporter, switch_value); - for (const auto& pair : api::crash_reporter::GetGlobalCrashKeys()) { - if (!switch_value.empty()) - switch_value += ","; - switch_value += pair.first; - switch_value += "="; - switch_value += pair.second; + if (!crash_reporter::IsCrashpadEnabled()) { + for (const auto& pair : api::crash_reporter::GetGlobalCrashKeys()) { + if (!switch_value.empty()) + switch_value += ","; + switch_value += pair.first; + switch_value += "="; + switch_value += pair.second; + } + command_line->AppendSwitchASCII(switches::kGlobalCrashKeys, switch_value); } - command_line->AppendSwitchASCII(switches::kGlobalCrashKeys, switch_value); } #endif diff --git a/spec-main/api-crash-reporter-spec.ts b/spec-main/api-crash-reporter-spec.ts index 739d5c09a8..3bdc528e7d 100644 --- a/spec-main/api-crash-reporter-spec.ts +++ b/spec-main/api-crash-reporter-spec.ts @@ -138,224 +138,455 @@ function waitForNewFileInDir (dir: string): Promise { // TODO(nornagon): Fix tests on linux/arm. ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_TESTS)('crashReporter module', function () { - describe('should send minidump', () => { - it('when renderer crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('renderer', port); - const crash = await waitForCrash(); - checkCrash('renderer', crash); - expect(crash.mainProcessSpecific).to.be.undefined(); - }); - - it('when sandboxed renderer crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('sandboxed-renderer', port); - const crash = await waitForCrash(); - checkCrash('renderer', crash); - expect(crash.mainProcessSpecific).to.be.undefined(); - }); - - // TODO(nornagon): Minidump generation in main/node process on Linux/Arm is - // broken (//components/crash prints "Failed to generate minidump"). Figure - // out why. - ifit(!isLinuxOnArm)('when main process crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('main', port); - const crash = await waitForCrash(); - checkCrash('browser', crash); - expect(crash.mainProcessSpecific).to.equal('mps'); - }); - - ifit(!isLinuxOnArm)('when a node process crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('node', port); - const crash = await waitForCrash(); - checkCrash('node', crash); - expect(crash.mainProcessSpecific).to.be.undefined(); - expect(crash.rendererSpecific).to.be.undefined(); - }); - - describe('with guid', () => { - for (const processType of ['main', 'renderer', 'sandboxed-renderer']) { - it(`when ${processType} crashes`, async () => { + for (const withLinuxCrashpad of (process.platform === 'linux' ? [false, true] : [false])) { + const crashpadExtraArgs = withLinuxCrashpad ? ['--enable-crashpad'] : []; + describe(withLinuxCrashpad ? '(with crashpad)' : '', () => { + describe('should send minidump', () => { + it('when renderer crashes', async () => { const { port, waitForCrash } = await startServer(); - runCrashApp(processType, port); + runCrashApp('renderer', port, crashpadExtraArgs); const crash = await waitForCrash(); - expect(crash.guid).to.be.a('string'); + checkCrash('renderer', crash); + expect(crash.mainProcessSpecific).to.be.undefined(); }); - } - it('is a consistent id', async () => { - let crash1Guid; - let crash2Guid; - { + it('when sandboxed renderer crashes', async () => { const { port, waitForCrash } = await startServer(); - runCrashApp('main', port); + runCrashApp('sandboxed-renderer', port, crashpadExtraArgs); const crash = await waitForCrash(); - crash1Guid = crash.guid; - } - { + checkCrash('renderer', crash); + expect(crash.mainProcessSpecific).to.be.undefined(); + }); + + // TODO(nornagon): Minidump generation in main/node process on Linux/Arm is + // broken (//components/crash prints "Failed to generate minidump"). Figure + // out why. + ifit(!isLinuxOnArm)('when main process crashes', async () => { const { port, waitForCrash } = await startServer(); - runCrashApp('main', port); + runCrashApp('main', port, crashpadExtraArgs); const crash = await waitForCrash(); - crash2Guid = crash.guid; - } - expect(crash2Guid).to.equal(crash1Guid); - }); - }); + checkCrash('browser', crash); + expect(crash.mainProcessSpecific).to.equal('mps'); + }); - describe('with extra parameters', () => { - it('when renderer crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('renderer', port, ['--set-extra-parameters-in-renderer']); - const crash = await waitForCrash(); - checkCrash('renderer', crash); - expect(crash.mainProcessSpecific).to.be.undefined(); - expect(crash.rendererSpecific).to.equal('rs'); - expect(crash.addedThenRemoved).to.be.undefined(); - }); + ifit(!isLinuxOnArm)('when a node process crashes', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('node', port, crashpadExtraArgs); + const crash = await waitForCrash(); + checkCrash('node', crash); + expect(crash.mainProcessSpecific).to.be.undefined(); + expect(crash.rendererSpecific).to.be.undefined(); + }); - it('when sandboxed renderer crashes', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('sandboxed-renderer', port, ['--set-extra-parameters-in-renderer']); - const crash = await waitForCrash(); - checkCrash('renderer', crash); - expect(crash.mainProcessSpecific).to.be.undefined(); - expect(crash.rendererSpecific).to.equal('rs'); - expect(crash.addedThenRemoved).to.be.undefined(); - }); + describe('with guid', () => { + for (const processType of ['main', 'renderer', 'sandboxed-renderer']) { + it(`when ${processType} crashes`, async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp(processType, port, crashpadExtraArgs); + const crash = await waitForCrash(); + expect(crash.guid).to.be.a('string'); + }); + } - it('contains v8 crash keys when a v8 crash occurs', async () => { - const { remotely } = await startRemoteControlApp(); - const { port, waitForCrash } = await startServer(); - - await remotely((port: number) => { - require('electron').crashReporter.start({ - submitURL: `http://127.0.0.1:${port}`, - compress: false, - ignoreSystemCrashHandler: true + it('is a consistent id', async () => { + let crash1Guid; + let crash2Guid; + { + const { port, waitForCrash } = await startServer(); + runCrashApp('main', port, crashpadExtraArgs); + const crash = await waitForCrash(); + crash1Guid = crash.guid; + } + { + const { port, waitForCrash } = await startServer(); + runCrashApp('main', port, crashpadExtraArgs); + const crash = await waitForCrash(); + crash2Guid = crash.guid; + } + expect(crash2Guid).to.equal(crash1Guid); }); - }, [port]); - - remotely(() => { - const { BrowserWindow } = require('electron'); - const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }); - bw.loadURL('about:blank'); - bw.webContents.executeJavaScript('process._linkedBinding(\'electron_common_v8_util\').triggerFatalErrorForTesting()'); }); - const crash = await waitForCrash(); - expect(crash.prod).to.equal('Electron'); - expect(crash._productName).to.equal('electron-test-remote-control'); - expect(crash.process_type).to.equal('renderer'); - expect(crash['electron.v8-fatal.location']).to.equal('v8::Context::New()'); - expect(crash['electron.v8-fatal.message']).to.equal('Circular extension dependency'); + describe('with extra parameters', () => { + it('when renderer crashes', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('renderer', port, ['--set-extra-parameters-in-renderer', ...crashpadExtraArgs]); + const crash = await waitForCrash(); + checkCrash('renderer', crash); + expect(crash.mainProcessSpecific).to.be.undefined(); + expect(crash.rendererSpecific).to.equal('rs'); + expect(crash.addedThenRemoved).to.be.undefined(); + }); + + it('when sandboxed renderer crashes', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('sandboxed-renderer', port, ['--set-extra-parameters-in-renderer', ...crashpadExtraArgs]); + const crash = await waitForCrash(); + checkCrash('renderer', crash); + expect(crash.mainProcessSpecific).to.be.undefined(); + expect(crash.rendererSpecific).to.equal('rs'); + expect(crash.addedThenRemoved).to.be.undefined(); + }); + + it('contains v8 crash keys when a v8 crash occurs', async () => { + const { remotely } = await startRemoteControlApp(crashpadExtraArgs); + const { port, waitForCrash } = await startServer(); + + await remotely((port: number) => { + require('electron').crashReporter.start({ + submitURL: `http://127.0.0.1:${port}`, + compress: false, + ignoreSystemCrashHandler: true + }); + }, [port]); + + remotely(() => { + const { BrowserWindow } = require('electron'); + const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }); + bw.loadURL('about:blank'); + bw.webContents.executeJavaScript('process._linkedBinding(\'electron_common_v8_util\').triggerFatalErrorForTesting()'); + }); + + const crash = await waitForCrash(); + expect(crash.prod).to.equal('Electron'); + expect(crash._productName).to.equal('electron-test-remote-control'); + expect(crash.process_type).to.equal('renderer'); + expect(crash['electron.v8-fatal.location']).to.equal('v8::Context::New()'); + expect(crash['electron.v8-fatal.message']).to.equal('Circular extension dependency'); + }); + }); }); - }); - }); - ifdescribe(!isLinuxOnArm)('extra parameter limits', () => { - function stitchLongCrashParam (crash: any, paramKey: string) { - if (crash[paramKey]) return crash[paramKey]; - let chunk = 1; - let stitched = ''; - while (crash[`${paramKey}__${chunk}`]) { - stitched += crash[`${paramKey}__${chunk}`]; - chunk++; - } - return stitched; - } + ifdescribe(!isLinuxOnArm)('extra parameter limits', () => { + function stitchLongCrashParam (crash: any, paramKey: string) { + if (crash[paramKey]) return crash[paramKey]; + let chunk = 1; + let stitched = ''; + while (crash[`${paramKey}__${chunk}`]) { + stitched += crash[`${paramKey}__${chunk}`]; + chunk++; + } + return stitched; + } - it('should truncate extra values longer than 5 * 4096 characters', async () => { - const { port, waitForCrash } = await startServer(); - const { remotely } = await startRemoteControlApp(); - remotely((port: number) => { - require('electron').crashReporter.start({ - submitURL: `http://127.0.0.1:${port}`, - compress: false, - ignoreSystemCrashHandler: true, - extra: { longParam: 'a'.repeat(100000) } + it('should truncate extra values longer than 5 * 4096 characters', async () => { + const { port, waitForCrash } = await startServer(); + const { remotely } = await startRemoteControlApp(crashpadExtraArgs); + remotely((port: number) => { + require('electron').crashReporter.start({ + submitURL: `http://127.0.0.1:${port}`, + compress: false, + ignoreSystemCrashHandler: true, + extra: { longParam: 'a'.repeat(100000) } + }); + setTimeout(() => process.crash()); + }, port); + const crash = await waitForCrash(); + expect(stitchLongCrashParam(crash, 'longParam')).to.have.lengthOf(160 * 127 + (withLinuxCrashpad ? 159 : 0), 'crash should have truncated longParam'); }); - setTimeout(() => process.crash()); - }, port); - const crash = await waitForCrash(); - expect(stitchLongCrashParam(crash, 'longParam')).to.have.lengthOf(160 * 127, 'crash should have truncated longParam'); - }); - it('should omit extra keys with names longer than the maximum', async () => { - const kKeyLengthMax = 39; - const { port, waitForCrash } = await startServer(); - const { remotely } = await startRemoteControlApp(); - remotely((port: number, kKeyLengthMax: number) => { - require('electron').crashReporter.start({ - submitURL: `http://127.0.0.1:${port}`, - compress: false, - ignoreSystemCrashHandler: true, - extra: { - ['a'.repeat(kKeyLengthMax + 10)]: 'value', - ['b'.repeat(kKeyLengthMax)]: 'value', - 'not-long': 'not-long-value' + it('should omit extra keys with names longer than the maximum', async () => { + const kKeyLengthMax = 39; + const { port, waitForCrash } = await startServer(); + const { remotely } = await startRemoteControlApp(crashpadExtraArgs); + remotely((port: number, kKeyLengthMax: number) => { + require('electron').crashReporter.start({ + submitURL: `http://127.0.0.1:${port}`, + compress: false, + ignoreSystemCrashHandler: true, + extra: { + ['a'.repeat(kKeyLengthMax + 10)]: 'value', + ['b'.repeat(kKeyLengthMax)]: 'value', + 'not-long': 'not-long-value' + } + }); + require('electron').crashReporter.addExtraParameter('c'.repeat(kKeyLengthMax + 10), 'value'); + setTimeout(() => process.crash()); + }, port, kKeyLengthMax); + const crash = await waitForCrash(); + expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax + 10)); + expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax)); + expect(crash).to.have.property('b'.repeat(kKeyLengthMax), 'value'); + expect(crash).to.have.property('not-long', 'not-long-value'); + expect(crash).not.to.have.property('c'.repeat(kKeyLengthMax + 10)); + expect(crash).not.to.have.property('c'.repeat(kKeyLengthMax)); + }); + }); + + describe('globalExtra', () => { + ifit(!isLinuxOnArm)('should be sent with main process dumps', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('main', port, ['--add-global-param=globalParam:globalValue', ...crashpadExtraArgs]); + const crash = await waitForCrash(); + expect(crash.globalParam).to.equal('globalValue'); + }); + + it('should be sent with renderer process dumps', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('renderer', port, ['--add-global-param=globalParam:globalValue', ...crashpadExtraArgs]); + const crash = await waitForCrash(); + expect(crash.globalParam).to.equal('globalValue'); + }); + + it('should be sent with sandboxed renderer process dumps', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('sandboxed-renderer', port, ['--add-global-param=globalParam:globalValue', ...crashpadExtraArgs]); + const crash = await waitForCrash(); + expect(crash.globalParam).to.equal('globalValue'); + }); + + ifit(!isLinuxOnArm)('should not be overridden by extra in main process', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('main', port, ['--add-global-param=mainProcessSpecific:global', ...crashpadExtraArgs]); + const crash = await waitForCrash(); + expect(crash.mainProcessSpecific).to.equal('global'); + }); + + ifit(!isLinuxOnArm)('should not be overridden by extra in renderer process', async () => { + const { port, waitForCrash } = await startServer(); + runCrashApp('main', port, ['--add-global-param=rendererSpecific:global', ...crashpadExtraArgs]); + const crash = await waitForCrash(); + expect(crash.rendererSpecific).to.equal('global'); + }); + }); + + // TODO(nornagon): also test crashing main / sandboxed renderers. + ifit(!isWindowsOnArm)('should not send a minidump when uploadToServer is false', async () => { + const { port, waitForCrash, getCrashes } = await startServer(); + waitForCrash().then(() => expect.fail('expected not to receive a dump')); + await runCrashApp('renderer', port, ['--no-upload', ...crashpadExtraArgs]); + // wait a sec in case the crash reporter is about to upload a crash + await delay(1000); + expect(getCrashes()).to.have.length(0); + }); + + describe('getUploadedReports', () => { + it('returns an array of reports', async () => { + const { remotely } = await startRemoteControlApp(crashpadExtraArgs); + await remotely(() => { + require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' }); + }); + const reports = await remotely(() => require('electron').crashReporter.getUploadedReports()); + expect(reports).to.be.an('array'); + }); + }); + + // TODO(nornagon): re-enable on woa + ifdescribe(!isWindowsOnArm)('getLastCrashReport', () => { + it('returns the last uploaded report', async () => { + const { remotely } = await startRemoteControlApp(crashpadExtraArgs); + const { port, waitForCrash } = await startServer(); + + // 0. clear the crash reports directory. + const dir = await remotely(() => require('electron').app.getPath('crashDumps')); + try { + fs.rmdirSync(dir, { recursive: true }); + fs.mkdirSync(dir); + } catch (e) { /* ignore */ } + + // 1. start the crash reporter. + await remotely((port: number) => { + require('electron').crashReporter.start({ + submitURL: `http://127.0.0.1:${port}`, + compress: false, + ignoreSystemCrashHandler: true + }); + }, [port]); + // 2. generate a crash in the renderer. + remotely(() => { + const { BrowserWindow } = require('electron'); + const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }); + bw.loadURL('about:blank'); + bw.webContents.executeJavaScript('process.crash()'); + }); + await waitForCrash(); + // 3. get the crash from getLastCrashReport. + const firstReport = await remotely(() => require('electron').crashReporter.getLastCrashReport()); + expect(firstReport).to.not.be.null(); + expect(firstReport.date).to.be.an.instanceOf(Date); + expect((+new Date()) - (+firstReport.date)).to.be.lessThan(30000); + }); + }); + + describe('getParameters', () => { + it('returns all of the current parameters', async () => { + const { remotely } = await startRemoteControlApp(crashpadExtraArgs); + await remotely(() => { + require('electron').crashReporter.start({ + submitURL: 'http://127.0.0.1', + extra: { extra1: 'hi' } + }); + }); + const parameters = await remotely(() => require('electron').crashReporter.getParameters()); + expect(parameters).to.have.property('extra1', 'hi'); + }); + + it('reflects added and removed parameters', async () => { + const { remotely } = await startRemoteControlApp(crashpadExtraArgs); + await remotely(() => { + require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' }); + require('electron').crashReporter.addExtraParameter('hello', 'world'); + }); + { + const parameters = await remotely(() => require('electron').crashReporter.getParameters()); + expect(parameters).to.have.property('hello', 'world'); + } + + await remotely(() => { require('electron').crashReporter.removeExtraParameter('hello'); }); + + { + const parameters = await remotely(() => require('electron').crashReporter.getParameters()); + expect(parameters).not.to.have.property('hello'); } }); - require('electron').crashReporter.addExtraParameter('c'.repeat(kKeyLengthMax + 10), 'value'); - setTimeout(() => process.crash()); - }, port, kKeyLengthMax); - const crash = await waitForCrash(); - expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax + 10)); - expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax)); - expect(crash).to.have.property('b'.repeat(kKeyLengthMax), 'value'); - expect(crash).to.have.property('not-long', 'not-long-value'); - expect(crash).not.to.have.property('c'.repeat(kKeyLengthMax + 10)); - expect(crash).not.to.have.property('c'.repeat(kKeyLengthMax)); - }); - }); - describe('globalExtra', () => { - ifit(!isLinuxOnArm)('should be sent with main process dumps', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('main', port, ['--add-global-param=globalParam:globalValue']); - const crash = await waitForCrash(); - expect(crash.globalParam).to.equal('globalValue'); - }); + it('can be called in the renderer', async () => { + const { remotely } = await startRemoteControlApp(crashpadExtraArgs); + const rendererParameters = await remotely(async () => { + const { crashReporter, BrowserWindow } = require('electron'); + crashReporter.start({ submitURL: 'http://' }); + const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }); + bw.loadURL('about:blank'); + await bw.webContents.executeJavaScript('require(\'electron\').crashReporter.addExtraParameter(\'hello\', \'world\')'); + return bw.webContents.executeJavaScript('require(\'electron\').crashReporter.getParameters()'); + }); + if (process.platform === 'linux') { + // On Linux, 'getParameters' will also include the global parameters, + // because breakpad doesn't support global parameters. + expect(rendererParameters).to.have.property('hello', 'world'); + } else { + expect(rendererParameters).to.deep.equal({ hello: 'world' }); + } + }); - it('should be sent with renderer process dumps', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('renderer', port, ['--add-global-param=globalParam:globalValue']); - const crash = await waitForCrash(); - expect(crash.globalParam).to.equal('globalValue'); - }); + it('can be called in a node child process', async () => { + function slurp (stream: NodeJS.ReadableStream): Promise { + return new Promise((resolve, reject) => { + const chunks: Buffer[] = []; + stream.on('data', chunk => { chunks.push(chunk); }); + stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); + stream.on('error', e => reject(e)); + }); + } + // TODO(nornagon): how to enable crashpad in a node child process...? + const child = childProcess.fork(path.join(__dirname, 'fixtures', 'module', 'print-crash-parameters.js'), [], { silent: true }); + const output = await slurp(child.stdout!); + expect(JSON.parse(output)).to.deep.equal({ hello: 'world' }); + }); + }); - it('should be sent with sandboxed renderer process dumps', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('sandboxed-renderer', port, ['--add-global-param=globalParam:globalValue']); - const crash = await waitForCrash(); - expect(crash.globalParam).to.equal('globalValue'); - }); + describe('crash dumps directory', () => { + it('is set by default', () => { + expect(app.getPath('crashDumps')).to.be.a('string'); + }); - ifit(!isLinuxOnArm)('should not be overridden by extra in main process', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('main', port, ['--add-global-param=mainProcessSpecific:global']); - const crash = await waitForCrash(); - expect(crash.mainProcessSpecific).to.equal('global'); - }); + it('is inside the user data dir', () => { + expect(app.getPath('crashDumps')).to.include(app.getPath('userData')); + }); - ifit(!isLinuxOnArm)('should not be overridden by extra in renderer process', async () => { - const { port, waitForCrash } = await startServer(); - runCrashApp('main', port, ['--add-global-param=rendererSpecific:global']); - const crash = await waitForCrash(); - expect(crash.rendererSpecific).to.equal('global'); - }); - }); + function crash (processType: string, remotely: Function) { + if (processType === 'main') { + return remotely(() => { + setTimeout(() => { process.crash(); }); + }); + } else if (processType === 'renderer') { + return remotely(() => { + const { BrowserWindow } = require('electron'); + const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }); + bw.loadURL('about:blank'); + bw.webContents.executeJavaScript('process.crash()'); + }); + } else if (processType === 'sandboxed-renderer') { + const preloadPath = path.join(__dirname, 'fixtures', 'apps', 'crash', 'sandbox-preload.js'); + return remotely((preload: string) => { + const { BrowserWindow } = require('electron'); + const bw = new BrowserWindow({ show: false, webPreferences: { sandbox: true, preload, contextIsolation: false } }); + bw.loadURL('about:blank'); + }, preloadPath); + } else if (processType === 'node') { + const crashScriptPath = path.join(__dirname, 'fixtures', 'apps', 'crash', 'node-crash.js'); + return remotely((crashScriptPath: string) => { + const { app } = require('electron'); + const childProcess = require('child_process'); + const version = app.getVersion(); + const url = 'http://127.0.0.1'; + childProcess.fork(crashScriptPath, [url, version], { silent: true }); + }, crashScriptPath); + } + } - // TODO(nornagon): also test crashing main / sandboxed renderers. - ifit(!isWindowsOnArm)('should not send a minidump when uploadToServer is false', async () => { - const { port, waitForCrash, getCrashes } = await startServer(); - waitForCrash().then(() => expect.fail('expected not to receive a dump')); - await runCrashApp('renderer', port, ['--no-upload']); - // wait a sec in case the crash reporter is about to upload a crash - await delay(1000); - expect(getCrashes()).to.have.length(0); - }); + const processList = process.platform === 'linux' ? ['main', 'renderer', 'sandboxed-renderer'] + : ['main', 'renderer', 'sandboxed-renderer', 'node']; + for (const crashingProcess of processList) { + describe(`when ${crashingProcess} crashes`, () => { + it('stores crashes in the crash dump directory when uploadToServer: false', async () => { + const { remotely } = await startRemoteControlApp(crashpadExtraArgs); + const crashesDir = await remotely(() => { + const { crashReporter, app } = require('electron'); + crashReporter.start({ submitURL: 'http://127.0.0.1', uploadToServer: false, ignoreSystemCrashHandler: true }); + return app.getPath('crashDumps'); + }); + let reportsDir = crashesDir; + if (process.platform === 'darwin' || (process.platform === 'linux' && withLinuxCrashpad)) { + reportsDir = path.join(crashesDir, 'completed'); + } else if (process.platform === 'win32') { + reportsDir = path.join(crashesDir, 'reports'); + } + const newFileAppeared = waitForNewFileInDir(reportsDir); + crash(crashingProcess, remotely); + const newFiles = await newFileAppeared; + expect(newFiles.length).to.be.greaterThan(0); + if (process.platform === 'linux' && !withLinuxCrashpad) { + if (crashingProcess === 'main') { + expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8}-[0-9a-f]{8}\.dmp$/); + } else { + const process = crashingProcess === 'sandboxed-renderer' ? 'renderer' : crashingProcess; + const regex = RegExp(`chromium-${process}-minidump-[0-9a-f]{16}.dmp`); + expect(newFiles[0]).to.match(regex); + } + } else { + expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.dmp$/); + } + }); + + it('respects an overridden crash dump directory', async () => { + const { remotely } = await startRemoteControlApp(crashpadExtraArgs); + const crashesDir = path.join(app.getPath('temp'), uuid.v4()); + const remoteCrashesDir = await remotely((crashesDir: string) => { + const { crashReporter, app } = require('electron'); + app.setPath('crashDumps', crashesDir); + crashReporter.start({ submitURL: 'http://127.0.0.1', uploadToServer: false, ignoreSystemCrashHandler: true }); + return app.getPath('crashDumps'); + }, crashesDir); + expect(remoteCrashesDir).to.equal(crashesDir); + + let reportsDir = crashesDir; + if (process.platform === 'darwin' || (process.platform === 'linux' && withLinuxCrashpad)) { + reportsDir = path.join(crashesDir, 'completed'); + } else if (process.platform === 'win32') { + reportsDir = path.join(crashesDir, 'reports'); + } + const newFileAppeared = waitForNewFileInDir(reportsDir); + crash(crashingProcess, remotely); + const newFiles = await newFileAppeared; + expect(newFiles.length).to.be.greaterThan(0); + if (process.platform === 'linux' && !withLinuxCrashpad) { + if (crashingProcess === 'main') { + expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8}-[0-9a-f]{8}\.dmp$/); + } else { + const process = crashingProcess !== 'sandboxed-renderer' ? crashingProcess : 'renderer'; + const regex = RegExp(`chromium-${process}-minidump-[0-9a-f]{16}.dmp`); + expect(newFiles[0]).to.match(regex); + } + } else { + expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.dmp$/); + } + }); + }); + } + }); + }); + } describe('start() option validation', () => { it('requires that the submitURL option be specified', () => { @@ -380,54 +611,6 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_ }); }); - describe('getUploadedReports', () => { - it('returns an array of reports', async () => { - const { remotely } = await startRemoteControlApp(); - await remotely(() => { - require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' }); - }); - const reports = await remotely(() => require('electron').crashReporter.getUploadedReports()); - expect(reports).to.be.an('array'); - }); - }); - - // TODO(nornagon): re-enable on woa - ifdescribe(!isWindowsOnArm)('getLastCrashReport', () => { - it('returns the last uploaded report', async () => { - const { remotely } = await startRemoteControlApp(); - const { port, waitForCrash } = await startServer(); - - // 0. clear the crash reports directory. - const dir = await remotely(() => require('electron').app.getPath('crashDumps')); - try { - fs.rmdirSync(dir, { recursive: true }); - fs.mkdirSync(dir); - } catch (e) { /* ignore */ } - - // 1. start the crash reporter. - await remotely((port: number) => { - require('electron').crashReporter.start({ - submitURL: `http://127.0.0.1:${port}`, - compress: false, - ignoreSystemCrashHandler: true - }); - }, [port]); - // 2. generate a crash in the renderer. - remotely(() => { - const { BrowserWindow } = require('electron'); - const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }); - bw.loadURL('about:blank'); - bw.webContents.executeJavaScript('process.crash()'); - }); - await waitForCrash(); - // 3. get the crash from getLastCrashReport. - const firstReport = await remotely(() => require('electron').crashReporter.getLastCrashReport()); - expect(firstReport).to.not.be.null(); - expect(firstReport.date).to.be.an.instanceOf(Date); - expect((+new Date()) - (+firstReport.date)).to.be.lessThan(30000); - }); - }); - describe('getUploadToServer()', () => { it('returns true when uploadToServer is set to true (by default)', async () => { const { remotely } = await startRemoteControlApp(); @@ -454,183 +637,6 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_ }); }); - describe('getParameters', () => { - it('returns all of the current parameters', async () => { - const { remotely } = await startRemoteControlApp(); - await remotely(() => { - require('electron').crashReporter.start({ - submitURL: 'http://127.0.0.1', - extra: { extra1: 'hi' } - }); - }); - const parameters = await remotely(() => require('electron').crashReporter.getParameters()); - expect(parameters).to.have.property('extra1', 'hi'); - }); - - it('reflects added and removed parameters', async () => { - const { remotely } = await startRemoteControlApp(); - await remotely(() => { - require('electron').crashReporter.start({ submitURL: 'http://127.0.0.1' }); - require('electron').crashReporter.addExtraParameter('hello', 'world'); - }); - { - const parameters = await remotely(() => require('electron').crashReporter.getParameters()); - expect(parameters).to.have.property('hello', 'world'); - } - - await remotely(() => { require('electron').crashReporter.removeExtraParameter('hello'); }); - - { - const parameters = await remotely(() => require('electron').crashReporter.getParameters()); - expect(parameters).not.to.have.property('hello'); - } - }); - - it('can be called in the renderer', async () => { - const { remotely } = await startRemoteControlApp(); - const rendererParameters = await remotely(async () => { - const { crashReporter, BrowserWindow } = require('electron'); - crashReporter.start({ submitURL: 'http://' }); - const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }); - bw.loadURL('about:blank'); - await bw.webContents.executeJavaScript('require(\'electron\').crashReporter.addExtraParameter(\'hello\', \'world\')'); - return bw.webContents.executeJavaScript('require(\'electron\').crashReporter.getParameters()'); - }); - if (process.platform === 'linux') { - // On Linux, 'getParameters' will also include the global parameters, - // because breakpad doesn't support global parameters. - expect(rendererParameters).to.have.property('hello', 'world'); - } else { - expect(rendererParameters).to.deep.equal({ hello: 'world' }); - } - }); - - it('can be called in a node child process', async () => { - function slurp (stream: NodeJS.ReadableStream): Promise { - return new Promise((resolve, reject) => { - const chunks: Buffer[] = []; - stream.on('data', chunk => { chunks.push(chunk); }); - stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); - stream.on('error', e => reject(e)); - }); - } - const child = childProcess.fork(path.join(__dirname, 'fixtures', 'module', 'print-crash-parameters.js'), [], { silent: true }); - const output = await slurp(child.stdout!); - expect(JSON.parse(output)).to.deep.equal({ hello: 'world' }); - }); - }); - - describe('crash dumps directory', () => { - it('is set by default', () => { - expect(app.getPath('crashDumps')).to.be.a('string'); - }); - - it('is inside the user data dir', () => { - expect(app.getPath('crashDumps')).to.include(app.getPath('userData')); - }); - - function crash (processType: string, remotely: Function) { - if (processType === 'main') { - return remotely(() => { - setTimeout(() => { process.crash(); }); - }); - } else if (processType === 'renderer') { - return remotely(() => { - const { BrowserWindow } = require('electron'); - const bw = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } }); - bw.loadURL('about:blank'); - bw.webContents.executeJavaScript('process.crash()'); - }); - } else if (processType === 'sandboxed-renderer') { - const preloadPath = path.join(__dirname, 'fixtures', 'apps', 'crash', 'sandbox-preload.js'); - return remotely((preload: string) => { - const { BrowserWindow } = require('electron'); - const bw = new BrowserWindow({ show: false, webPreferences: { sandbox: true, preload, contextIsolation: false } }); - bw.loadURL('about:blank'); - }, preloadPath); - } else if (processType === 'node') { - const crashScriptPath = path.join(__dirname, 'fixtures', 'apps', 'crash', 'node-crash.js'); - return remotely((crashScriptPath: string) => { - const { app } = require('electron'); - const childProcess = require('child_process'); - const version = app.getVersion(); - const url = 'http://127.0.0.1'; - childProcess.fork(crashScriptPath, [url, version], { silent: true }); - }, crashScriptPath); - } - } - - const processList = process.platform === 'linux' ? ['main', 'renderer', 'sandboxed-renderer'] - : ['main', 'renderer', 'sandboxed-renderer', 'node']; - for (const crashingProcess of processList) { - describe(`when ${crashingProcess} crashes`, () => { - it('stores crashes in the crash dump directory when uploadToServer: false', async () => { - const { remotely } = await startRemoteControlApp(); - const crashesDir = await remotely(() => { - const { crashReporter, app } = require('electron'); - crashReporter.start({ submitURL: 'http://127.0.0.1', uploadToServer: false, ignoreSystemCrashHandler: true }); - return app.getPath('crashDumps'); - }); - let reportsDir = crashesDir; - if (process.platform === 'darwin') { - reportsDir = path.join(crashesDir, 'completed'); - } else if (process.platform === 'win32') { - reportsDir = path.join(crashesDir, 'reports'); - } - const newFileAppeared = waitForNewFileInDir(reportsDir); - crash(crashingProcess, remotely); - const newFiles = await newFileAppeared; - expect(newFiles.length).to.be.greaterThan(0); - if (process.platform === 'linux') { - if (crashingProcess === 'main') { - expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8}-[0-9a-f]{8}\.dmp$/); - } else { - const process = crashingProcess === 'sandboxed-renderer' ? 'renderer' : crashingProcess; - const regex = RegExp(`chromium-${process}-minidump-[0-9a-f]{16}.dmp`); - expect(newFiles[0]).to.match(regex); - } - } else { - expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.dmp$/); - } - }); - - it('respects an overridden crash dump directory', async () => { - const { remotely } = await startRemoteControlApp(); - const crashesDir = path.join(app.getPath('temp'), uuid.v4()); - const remoteCrashesDir = await remotely((crashesDir: string) => { - const { crashReporter, app } = require('electron'); - app.setPath('crashDumps', crashesDir); - crashReporter.start({ submitURL: 'http://127.0.0.1', uploadToServer: false, ignoreSystemCrashHandler: true }); - return app.getPath('crashDumps'); - }, crashesDir); - expect(remoteCrashesDir).to.equal(crashesDir); - - let reportsDir = crashesDir; - if (process.platform === 'darwin') { - reportsDir = path.join(crashesDir, 'completed'); - } else if (process.platform === 'win32') { - reportsDir = path.join(crashesDir, 'reports'); - } - const newFileAppeared = waitForNewFileInDir(reportsDir); - crash(crashingProcess, remotely); - const newFiles = await newFileAppeared; - expect(newFiles.length).to.be.greaterThan(0); - if (process.platform === 'linux') { - if (crashingProcess === 'main') { - expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{8}-[0-9a-f]{8}\.dmp$/); - } else { - const process = crashingProcess !== 'sandboxed-renderer' ? crashingProcess : 'renderer'; - const regex = RegExp(`chromium-${process}-minidump-[0-9a-f]{16}.dmp`); - expect(newFiles[0]).to.match(regex); - } - } else { - expect(newFiles[0]).to.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.dmp$/); - } - }); - }); - } - }); - describe('when not started', () => { it('does not prevent process from crashing', async () => { const appPath = path.join(__dirname, '..', 'spec', 'fixtures', 'api', 'cookie-app');