2019-10-14 21:38:54 +00:00
import { expect } from 'chai' ;
import * as childProcess from 'child_process' ;
import * as http from 'http' ;
2020-04-22 22:53:12 +00:00
import * as Busboy from 'busboy' ;
2019-10-14 21:38:54 +00:00
import * as path from 'path' ;
2021-07-28 15:56:15 +00:00
import { ifdescribe , ifit , defer , startRemoteControlApp , delay , repeatedly } from './spec-helpers' ;
2020-04-22 22:53:12 +00:00
import { app } from 'electron/main' ;
2020-04-07 00:04:09 +00:00
import { crashReporter } from 'electron/common' ;
2019-10-14 21:38:54 +00:00
import { AddressInfo } from 'net' ;
import { EventEmitter } from 'events' ;
2020-04-22 22:53:12 +00:00
import * as fs from 'fs' ;
2020-05-07 20:31:26 +00:00
import * as uuid from 'uuid' ;
2020-03-20 20:28:31 +00:00
2020-04-23 20:08:50 +00:00
const isWindowsOnArm = process . platform === 'win32' && process . arch === 'arm64' ;
2020-05-07 20:31:26 +00:00
const isLinuxOnArm = process . platform === 'linux' && process . arch . includes ( 'arm' ) ;
2020-04-23 20:08:50 +00:00
2020-04-22 22:53:12 +00:00
type CrashInfo = {
prod : string
ver : string
process_type : string // eslint-disable-line camelcase
2020-05-07 20:31:26 +00:00
ptype : string
2020-04-22 22:53:12 +00:00
platform : string
_productName : string
_version : string
upload_file_minidump : Buffer // eslint-disable-line camelcase
2020-08-07 22:30:49 +00:00
guid : string
2020-05-07 20:31:26 +00:00
mainProcessSpecific : 'mps' | undefined
rendererSpecific : 'rs' | undefined
globalParam : 'globalValue' | undefined
addedThenRemoved : 'to-be-removed' | undefined
longParam : string | undefined
2020-07-30 02:04:24 +00:00
'electron.v8-fatal.location' : string | undefined
'electron.v8-fatal.message' : string | undefined
2020-04-22 22:53:12 +00:00
}
function checkCrash ( expectedProcessType : string , fields : CrashInfo ) {
2020-05-07 20:31:26 +00:00
expect ( String ( fields . prod ) ) . to . equal ( 'Electron' , 'prod' ) ;
expect ( String ( fields . ver ) ) . to . equal ( process . versions . electron , 'ver' ) ;
expect ( String ( fields . ptype ) ) . to . equal ( expectedProcessType , 'ptype' ) ;
expect ( String ( fields . process_type ) ) . to . equal ( expectedProcessType , 'process_type' ) ;
expect ( String ( fields . platform ) ) . to . equal ( process . platform , 'platform' ) ;
expect ( String ( fields . _productName ) ) . to . equal ( 'Zombies' , '_productName' ) ;
expect ( String ( fields . _version ) ) . to . equal ( app . getVersion ( ) , '_version' ) ;
2020-04-22 22:53:12 +00:00
expect ( fields . upload_file_minidump ) . to . be . an . instanceOf ( Buffer ) ;
2020-05-07 20:31:26 +00:00
// TODO(nornagon): minidumps are sometimes (not always) turning up empty on
// 32-bit Linux. Figure out why.
if ( ! ( process . platform === 'linux' && process . arch === 'ia32' ) ) {
expect ( fields . upload_file_minidump . length ) . to . be . greaterThan ( 0 ) ;
}
2020-04-22 22:53:12 +00:00
}
2019-10-14 21:38:54 +00:00
2020-04-22 22:53:12 +00:00
const startServer = async ( ) = > {
const crashes : CrashInfo [ ] = [ ] ;
function getCrashes ( ) { return crashes ; }
const emitter = new EventEmitter ( ) ;
function waitForCrash ( ) : Promise < CrashInfo > {
return new Promise ( resolve = > {
emitter . once ( 'crash' , ( crash ) = > {
resolve ( crash ) ;
} ) ;
} ) ;
}
2019-10-14 21:38:54 +00:00
2020-04-22 22:53:12 +00:00
const server = http . createServer ( ( req , res ) = > {
const busboy = new Busboy ( { headers : req.headers } ) ;
const fields = { } as Record < string , any > ;
const files = { } as Record < string , Buffer > ;
busboy . on ( 'file' , ( fieldname , file ) = > {
const chunks = [ ] as Array < Buffer > ;
file . on ( 'data' , ( chunk ) = > {
chunks . push ( chunk ) ;
} ) ;
file . on ( 'end' , ( ) = > {
files [ fieldname ] = Buffer . concat ( chunks ) ;
} ) ;
} ) ;
busboy . on ( 'field' , ( fieldname , val ) = > {
fields [ fieldname ] = val ;
} ) ;
busboy . on ( 'finish' , ( ) = > {
2020-05-07 20:31:26 +00:00
// breakpad id must be 16 hex digits.
const reportId = Math . random ( ) . toString ( 16 ) . split ( '.' ) [ 1 ] . padStart ( 16 , '0' ) ;
2020-04-22 22:53:12 +00:00
res . end ( reportId , async ( ) = > {
req . socket . destroy ( ) ;
emitter . emit ( 'crash' , { . . . fields , . . . files } ) ;
} ) ;
} ) ;
req . pipe ( busboy ) ;
} ) ;
2019-10-14 21:38:54 +00:00
2021-01-22 19:25:47 +00:00
await new Promise < void > ( resolve = > {
2020-04-22 22:53:12 +00:00
server . listen ( 0 , '127.0.0.1' , ( ) = > { resolve ( ) ; } ) ;
2019-10-14 21:38:54 +00:00
} ) ;
2020-04-22 22:53:12 +00:00
const port = ( server . address ( ) as AddressInfo ) . port ;
2019-10-14 21:38:54 +00:00
2020-06-09 18:42:53 +00:00
defer ( ( ) = > { server . close ( ) ; } ) ;
2019-10-14 21:38:54 +00:00
2020-04-22 22:53:12 +00:00
return { getCrashes , port , waitForCrash } ;
} ;
2019-10-14 21:38:54 +00:00
2020-04-22 22:53:12 +00:00
function runApp ( appPath : string , args : Array < string > = [ ] ) {
const appProcess = childProcess . spawn ( process . execPath , [ appPath , . . . args ] ) ;
return new Promise ( resolve = > {
appProcess . once ( 'exit' , resolve ) ;
} ) ;
}
2019-10-14 21:38:54 +00:00
2020-04-22 22:53:12 +00:00
function runCrashApp ( crashType : string , port : number , extraArgs : Array < string > = [ ] ) {
const appPath = path . join ( __dirname , 'fixtures' , 'apps' , 'crash' ) ;
return runApp ( appPath , [
` --crash-type= ${ crashType } ` ,
` --crash-reporter-url=http://127.0.0.1: ${ port } ` ,
. . . extraArgs
] ) ;
}
2019-10-14 21:38:54 +00:00
2020-04-22 22:53:12 +00:00
function waitForNewFileInDir ( dir : string ) : Promise < string [ ] > {
2020-04-23 20:08:50 +00:00
function readdirIfPresent ( dir : string ) : string [ ] {
try {
return fs . readdirSync ( dir ) ;
} catch ( e ) {
return [ ] ;
}
}
const initialFiles = readdirIfPresent ( dir ) ;
2020-04-22 22:53:12 +00:00
return new Promise ( resolve = > {
const ivl = setInterval ( ( ) = > {
2020-04-23 20:08:50 +00:00
const newCrashFiles = readdirIfPresent ( dir ) . filter ( f = > ! initialFiles . includes ( f ) ) ;
2020-04-22 22:53:12 +00:00
if ( newCrashFiles . length ) {
clearInterval ( ivl ) ;
resolve ( newCrashFiles ) ;
}
} , 1000 ) ;
} ) ;
}
2019-10-14 21:38:54 +00:00
2020-05-07 20:31:26 +00:00
// TODO(nornagon): Fix tests on linux/arm.
ifdescribe ( ! isLinuxOnArm && ! process . mas && ! process . env . DISABLE_CRASH_REPORTER_TESTS ) ( 'crashReporter module' , function ( ) {
2021-11-08 09:20:43 +00:00
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' ) ;
} ) ;
2019-10-14 21:38:54 +00:00
2021-11-08 09:20:43 +00:00
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 ( ) = > {
2021-07-19 17:11:10 +00:00
const { port , waitForCrash } = await startServer ( ) ;
2021-11-08 09:20:43 +00:00
runCrashApp ( processType , port ) ;
2021-07-19 17:11:10 +00:00
const crash = await waitForCrash ( ) ;
2021-11-08 09:20:43 +00:00
expect ( crash . guid ) . to . be . a ( 'string' ) ;
2021-07-19 17:11:10 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
}
2019-10-14 21:38:54 +00:00
2021-11-08 09:20:43 +00:00
it ( 'is a consistent id' , async ( ) = > {
let crash1Guid ;
let crash2Guid ;
{
2021-07-19 17:11:10 +00:00
const { port , waitForCrash } = await startServer ( ) ;
2021-11-08 09:20:43 +00:00
runCrashApp ( 'main' , port ) ;
2021-07-19 17:11:10 +00:00
const crash = await waitForCrash ( ) ;
2021-11-08 09:20:43 +00:00
crash1Guid = crash . guid ;
}
{
2021-07-19 17:11:10 +00:00
const { port , waitForCrash } = await startServer ( ) ;
2021-11-08 09:20:43 +00:00
runCrashApp ( 'main' , port ) ;
2021-07-19 17:11:10 +00:00
const crash = await waitForCrash ( ) ;
2021-11-08 09:20:43 +00:00
crash2Guid = crash . guid ;
}
expect ( crash2Guid ) . to . equal ( crash1Guid ) ;
} ) ;
} ) ;
2021-07-19 17:11:10 +00:00
2021-11-08 09:20:43 +00:00
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 ( ) ;
} ) ;
2021-07-19 17:11:10 +00:00
2021-11-08 09:20:43 +00:00
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 ( ) ;
} ) ;
2021-07-19 17:11:10 +00:00
2021-11-08 09:20:43 +00:00
it ( 'contains v8 crash keys when a v8 crash occurs' , async ( ) = > {
const { remotely } = await startRemoteControlApp ( ) ;
const { port , waitForCrash } = await startServer ( ) ;
2021-07-19 17:11:10 +00:00
2021-11-08 09:20:43 +00:00
await remotely ( ( port : number ) = > {
require ( 'electron' ) . crashReporter . start ( {
submitURL : ` http://127.0.0.1: ${ port } ` ,
compress : false ,
ignoreSystemCrashHandler : true
2021-07-19 17:11:10 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
} , [ port ] ) ;
2021-07-19 17:11:10 +00:00
2021-11-08 09:20:43 +00:00
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()' ) ;
2021-07-19 17:11:10 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
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' ) ;
2021-07-19 17:11:10 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
} ) ;
} ) ;
2019-10-14 21:38:54 +00:00
2021-11-08 09:20:43 +00:00
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 ;
}
2021-07-19 17:11:10 +00:00
2021-11-08 09:20:43 +00:00
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 ) }
2020-08-07 22:30:49 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
setTimeout ( ( ) = > process . crash ( ) ) ;
} , port ) ;
const crash = await waitForCrash ( ) ;
expect ( stitchLongCrashParam ( crash , 'longParam' ) ) . to . have . lengthOf ( 160 * 127 + ( process . platform === 'linux' ? 159 : 0 ) , 'crash should have truncated longParam' ) ;
} ) ;
2020-08-07 22:30:49 +00:00
2021-11-08 09:20:43 +00:00
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'
}
2021-07-19 17:11:10 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
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 ) ) ;
} ) ;
} ) ;
2021-07-19 17:11:10 +00:00
2021-11-08 09:20:43 +00:00
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' ) ;
} ) ;
2021-07-19 17:11:10 +00:00
2021-11-08 09:20:43 +00:00
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' ) ;
} ) ;
2021-07-19 17:11:10 +00:00
2021-11-08 09:20:43 +00:00
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' ) ;
} ) ;
2021-07-19 17:11:10 +00:00
2021-11-08 09:20:43 +00:00
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' ) ;
} ) ;
2021-07-19 17:11:10 +00:00
2021-11-08 09:20:43 +00:00
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' ) ;
} ) ;
} ) ;
// 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 ) ;
} ) ;
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' } ) ;
2020-08-07 22:30:49 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
const reports = await remotely ( ( ) = > require ( 'electron' ) . crashReporter . getUploadedReports ( ) ) ;
expect ( reports ) . to . be . an ( 'array' ) ;
} ) ;
} ) ;
2020-08-07 22:30:49 +00:00
2021-11-08 09:20:43 +00:00
// 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()' ) ;
2020-05-07 20:31:26 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
await waitForCrash ( ) ;
// 3. get the crash from getLastCrashReport.
const firstReport = await repeatedly (
( ) = > 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 ) ;
} ) ;
} ) ;
2020-05-07 20:31:26 +00:00
2021-11-08 09:20:43 +00:00
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' }
2021-07-19 17:11:10 +00:00
} ) ;
2020-05-07 20:31:26 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
const parameters = await remotely ( ( ) = > require ( 'electron' ) . crashReporter . getParameters ( ) ) ;
expect ( parameters ) . to . have . property ( 'extra1' , 'hi' ) ;
} ) ;
2020-07-30 02:04:24 +00:00
2021-11-08 09:20:43 +00:00
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' ) ;
}
2020-07-30 02:04:24 +00:00
2021-11-08 09:20:43 +00:00
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()' ) ;
2021-07-19 17:11:10 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
expect ( rendererParameters ) . to . deep . equal ( { hello : 'world' } ) ;
} ) ;
2020-07-30 02:04:24 +00:00
2021-11-08 09:20:43 +00:00
it ( 'can be called in a node child process' , async ( ) = > {
function slurp ( stream : NodeJS.ReadableStream ) : Promise < string > {
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 ) ) ;
2020-07-30 02:04:24 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
}
// 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' } ) ;
} ) ;
} ) ;
2020-07-30 02:04:24 +00:00
2021-11-08 09:20:43 +00:00
describe ( 'crash dumps directory' , ( ) = > {
it ( 'is set by default' , ( ) = > {
expect ( app . getPath ( 'crashDumps' ) ) . to . be . a ( 'string' ) ;
} ) ;
2019-10-14 21:38:54 +00:00
2021-11-08 09:20:43 +00:00
it ( 'is inside the user data dir' , ( ) = > {
expect ( app . getPath ( 'crashDumps' ) ) . to . include ( app . getPath ( 'userData' ) ) ;
} ) ;
2020-07-30 02:04:04 +00:00
2021-11-08 09:20:43 +00:00
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()' ) ;
2020-05-07 20:31:26 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
} 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 ) ;
}
}
2020-05-07 20:31:26 +00:00
2021-11-08 09:20:43 +00:00
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' ) ;
2021-07-19 17:11:10 +00:00
} ) ;
2021-11-08 09:20:43 +00:00
let reportsDir = crashesDir ;
if ( process . platform === 'darwin' || process . platform === 'linux' ) {
reportsDir = path . join ( crashesDir , 'completed' ) ;
} else if ( process . platform === 'win32' ) {
reportsDir = path . join ( crashesDir , 'reports' ) ;
2020-05-07 20:31:26 +00:00
}
2021-11-08 09:20:43 +00:00
const newFileAppeared = waitForNewFileInDir ( reportsDir ) ;
crash ( crashingProcess , remotely ) ;
const newFiles = await newFileAppeared ;
expect ( newFiles . length ) . to . be . greaterThan ( 0 ) ;
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$/ ) ;
2020-05-07 20:31:26 +00:00
} ) ;
2020-04-22 22:53:12 +00:00
2021-11-08 09:20:43 +00:00
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' || process . platform === 'linux' ) {
reportsDir = path . join ( crashesDir , 'completed' ) ;
} else if ( process . platform === 'win32' ) {
reportsDir = path . join ( crashesDir , 'reports' ) ;
2021-07-19 17:11:10 +00:00
}
2021-11-08 09:20:43 +00:00
const newFileAppeared = waitForNewFileInDir ( reportsDir ) ;
crash ( crashingProcess , remotely ) ;
const newFiles = await newFileAppeared ;
expect ( newFiles . length ) . to . be . greaterThan ( 0 ) ;
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$/ ) ;
2021-07-19 17:11:10 +00:00
} ) ;
} ) ;
2021-11-08 09:20:43 +00:00
}
} ) ;
2019-10-14 21:38:54 +00:00
2020-04-22 22:53:12 +00:00
describe ( 'start() option validation' , ( ) = > {
it ( 'requires that the submitURL option be specified' , ( ) = > {
2019-10-14 21:38:54 +00:00
expect ( ( ) = > {
2020-05-07 20:31:26 +00:00
crashReporter . start ( { } as any ) ;
2021-03-18 21:15:19 +00:00
} ) . to . throw ( 'submitURL must be specified when uploadToServer is true' ) ;
} ) ;
it ( 'allows the submitURL option to be omitted when uploadToServer is false' , ( ) = > {
expect ( ( ) = > {
crashReporter . start ( { uploadToServer : false } as any ) ;
} ) . not . to . throw ( ) ;
2019-10-14 21:38:54 +00:00
} ) ;
2020-04-22 22:53:12 +00:00
it ( 'can be called twice' , async ( ) = > {
2020-05-07 20:31:26 +00:00
const { remotely } = await startRemoteControlApp ( ) ;
await expect ( remotely ( ( ) = > {
const { crashReporter } = require ( 'electron' ) ;
crashReporter . start ( { submitURL : 'http://127.0.0.1' } ) ;
crashReporter . start ( { submitURL : 'http://127.0.0.1' } ) ;
} ) ) . to . be . fulfilled ( ) ;
2019-10-14 21:38:54 +00:00
} ) ;
} ) ;
describe ( 'getUploadToServer()' , ( ) = > {
2020-04-22 22:53:12 +00:00
it ( 'returns true when uploadToServer is set to true (by default)' , async ( ) = > {
2020-05-07 20:31:26 +00:00
const { remotely } = await startRemoteControlApp ( ) ;
2019-10-14 21:38:54 +00:00
2020-05-07 20:31:26 +00:00
await remotely ( ( ) = > { require ( 'electron' ) . crashReporter . start ( { submitURL : 'http://127.0.0.1' } ) ; } ) ;
const uploadToServer = await remotely ( ( ) = > require ( 'electron' ) . crashReporter . getUploadToServer ( ) ) ;
2020-04-22 22:53:12 +00:00
expect ( uploadToServer ) . to . be . true ( ) ;
2019-10-14 21:38:54 +00:00
} ) ;
2020-04-22 22:53:12 +00:00
it ( 'returns false when uploadToServer is set to false in init' , async ( ) = > {
2020-05-07 20:31:26 +00:00
const { remotely } = await startRemoteControlApp ( ) ;
await remotely ( ( ) = > { require ( 'electron' ) . crashReporter . start ( { submitURL : 'http://127.0.0.1' , uploadToServer : false } ) ; } ) ;
const uploadToServer = await remotely ( ( ) = > require ( 'electron' ) . crashReporter . getUploadToServer ( ) ) ;
2020-04-22 22:53:12 +00:00
expect ( uploadToServer ) . to . be . false ( ) ;
2019-10-14 21:38:54 +00:00
} ) ;
2020-04-22 22:53:12 +00:00
it ( 'is updated by setUploadToServer' , async ( ) = > {
2020-05-07 20:31:26 +00:00
const { remotely } = await startRemoteControlApp ( ) ;
await remotely ( ( ) = > { require ( 'electron' ) . crashReporter . start ( { submitURL : 'http://127.0.0.1' } ) ; } ) ;
await remotely ( ( ) = > { require ( 'electron' ) . crashReporter . setUploadToServer ( false ) ; } ) ;
expect ( await remotely ( ( ) = > require ( 'electron' ) . crashReporter . getUploadToServer ( ) ) ) . to . be . false ( ) ;
await remotely ( ( ) = > { require ( 'electron' ) . crashReporter . setUploadToServer ( true ) ; } ) ;
expect ( await remotely ( ( ) = > require ( 'electron' ) . crashReporter . getUploadToServer ( ) ) ) . to . be . true ( ) ;
2019-10-14 21:38:54 +00:00
} ) ;
} ) ;
describe ( 'when not started' , ( ) = > {
2020-04-22 22:53:12 +00:00
it ( 'does not prevent process from crashing' , async ( ) = > {
const appPath = path . join ( __dirname , '..' , 'spec' , 'fixtures' , 'api' , 'cookie-app' ) ;
await runApp ( appPath ) ;
2019-10-14 21:38:54 +00:00
} ) ;
} ) ;
} ) ;