test: move crashReporter specs to the main process (#20417)

This commit is contained in:
Jeremy Apthorp 2019-10-14 14:38:54 -07:00 committed by GitHub
parent eb100cdf9e
commit 8dad0c7aaa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 476 additions and 447 deletions

View file

@ -119,6 +119,10 @@ Remove a extra parameter from the current set of parameters so that it will not
See all of the current parameters being passed to the crash reporter. See all of the current parameters being passed to the crash reporter.
### `crashReporter.getCrashesDirectory()`
Returns `String` - The directory where crashes are temporarily stored before being uploaded.
## Crash Report Payload ## Crash Report Payload
The crash reporter will send the following data to the `submitURL` as The crash reporter will send the following data to the `submitURL` as

View file

@ -61,10 +61,6 @@ class CrashReporter {
return this.crashesDirectory return this.crashesDirectory
} }
getProductName () {
return this.productName
}
getUploadToServer () { getUploadToServer () {
if (process.type === 'browser') { if (process.type === 'browser') {
return binding.getUploadToServer() return binding.getUploadToServer()

View file

@ -130,5 +130,9 @@
"python script/check-trailing-whitespace.py --fix", "python script/check-trailing-whitespace.py --fix",
"git add filenames.auto.gni" "git add filenames.auto.gni"
] ]
},
"dependencies": {
"@types/multiparty": "^0.0.32",
"@types/temp": "^0.8.34"
} }
} }

View file

@ -0,0 +1,440 @@
import { expect } from 'chai'
import * as childProcess from 'child_process'
import * as fs from 'fs'
import * as http from 'http'
import * as multiparty from 'multiparty'
import * as path from 'path'
import { ifdescribe, ifit } from './spec-helpers'
import * as temp from 'temp'
import * as url from 'url'
import { ipcMain, app, BrowserWindow, crashReporter, BrowserWindowConstructorOptions } from 'electron'
import { AddressInfo } from 'net'
import { closeWindow, closeAllWindows } from './window-helpers'
import { EventEmitter } from 'events'
temp.track()
const afterTest: ((() => void) | (() => Promise<void>))[] = []
async function cleanup() {
for (const cleanup of afterTest) {
const r = cleanup()
if (r instanceof Promise)
await r
}
afterTest.length = 0
}
// TODO(alexeykuzmin): [Ch66] This test fails on Linux. Fix it and enable back.
ifdescribe(!process.mas && !process.env.DISABLE_CRASH_REPORTER_TESTS && process.platform !== 'linux')('crashReporter module', function () {
let originalTempDirectory: string
let tempDirectory = null
const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures')
before(() => {
tempDirectory = temp.mkdirSync('electronCrashReporterSpec-')
originalTempDirectory = app.getPath('temp')
app.setPath('temp', tempDirectory)
})
after(() => {
app.setPath('temp', originalTempDirectory)
try {
temp.cleanupSync()
} catch (e) {
// ignore.
console.warn(e.stack)
}
})
afterEach(cleanup)
it('should send minidump when node processes crash', async () => {
const { port, waitForCrash } = await startServer()
const crashesDir = path.join(app.getPath('temp'), `${app.name} Crashes`)
const version = app.getVersion()
const crashPath = path.join(fixtures, 'module', 'crash.js')
childProcess.fork(crashPath, [port.toString(), version, crashesDir], { silent: true })
const crash = await waitForCrash()
checkCrash('node', crash)
})
const generateSpecs = (description: string, browserWindowOpts: BrowserWindowConstructorOptions) => {
describe(description, () => {
let w: BrowserWindow
beforeEach(() => {
w = new BrowserWindow(Object.assign({ show: false }, browserWindowOpts))
})
afterEach(async () => {
await closeWindow(w)
w = null as unknown as BrowserWindow
})
it('should send minidump when renderer crashes', async () => {
const { port, waitForCrash } = await startServer()
w.loadFile(path.join(fixtures, 'api', 'crash.html'), { query: { port: port.toString() } })
const crash = await waitForCrash()
checkCrash('renderer', crash)
})
ifit(!browserWindowOpts.webPreferences!.sandbox)('should send minidump when node processes crash', async function () {
const { port, waitForCrash } = await startServer()
const crashesDir = path.join(app.getPath('temp'), `${app.name} Crashes`)
const version = app.getVersion()
const crashPath = path.join(fixtures, 'module', 'crash.js')
w.loadFile(path.join(fixtures, 'api', 'crash_child.html'), { query: { port: port.toString(), crashesDir, crashPath, version } })
const crash = await waitForCrash()
expect(String((crash as any).newExtra)).to.equal('newExtra')
expect((crash as any).removeExtra).to.be.undefined()
checkCrash('node', crash)
})
describe('when uploadToServer is false', () => {
after(() => { crashReporter.setUploadToServer(true) })
it('should not send minidump', async () => {
const { port, getCrashes } = await startServer()
crashReporter.setUploadToServer(false)
let crashesDir = crashReporter.getCrashesDirectory()
const existingDumpFiles = new Set()
// crashpad puts the dump files in the "completed" subdirectory
if (process.platform === 'darwin') {
crashesDir = path.join(crashesDir, 'completed')
} else {
crashesDir = path.join(crashesDir, 'reports')
}
const crashUrl = url.format({
protocol: 'file',
pathname: path.join(fixtures, 'api', 'crash.html'),
search: `?port=${port}&skipUpload=1`
})
w.loadURL(crashUrl)
await new Promise(resolve => {
ipcMain.once('list-existing-dumps', (event) => {
fs.readdir(crashesDir, (err, files) => {
if (!err) {
for (const file of files) {
if (/\.dmp$/.test(file)) {
existingDumpFiles.add(file)
}
}
}
event.returnValue = null // allow the renderer to crash
resolve()
})
})
})
const dumpFileCreated = async () => {
async function getDumps() {
const files = await fs.promises.readdir(crashesDir)
return files.filter((file) => /\.dmp$/.test(file) && !existingDumpFiles.has(file))
}
for (let i = 0; i < 30; i++) {
const dumps = await getDumps()
if (dumps.length) {
return path.join(crashesDir, dumps[0])
}
await new Promise(resolve => setTimeout(resolve, 1000))
}
}
const dumpFile = await dumpFileCreated()
expect(dumpFile).to.be.a('string')
// dump file should not be deleted when not uploading, so we wait
// 1s and assert it still exists
await new Promise(resolve => setTimeout(resolve, 1000))
expect(fs.existsSync(dumpFile!)).to.be.true()
// the server should not have received any crashes.
expect(getCrashes()).to.be.empty()
})
})
it('should send minidump with updated extra parameters', async function () {
const { port, waitForCrash } = await startServer()
const crashUrl = url.format({
protocol: 'file',
pathname: path.join(fixtures, 'api', 'crash-restart.html'),
search: `?port=${port}`
})
w.loadURL(crashUrl)
const crash = await waitForCrash()
checkCrash('renderer', crash)
})
})
}
generateSpecs('without sandbox', {
webPreferences: {
nodeIntegration: true
}
})
generateSpecs('with sandbox', {
webPreferences: {
sandbox: true,
preload: path.join(fixtures, 'module', 'preload-sandbox.js')
}
})
describe('start(options)', () => {
it('requires that the companyName and submitURL options be specified', () => {
expect(() => {
crashReporter.start({ companyName: 'Missing submitURL' } as any)
}).to.throw('submitURL is a required option to crashReporter.start')
expect(() => {
crashReporter.start({ submitURL: 'Missing companyName' } as any)
}).to.throw('companyName is a required option to crashReporter.start')
})
it('can be called multiple times', () => {
expect(() => {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
crashReporter.start({
companyName: 'Umbrella Corporation 2',
submitURL: 'http://127.0.0.1/more-crashes'
})
}).to.not.throw()
})
})
describe('getCrashesDirectory', () => {
it('correctly returns the directory', () => {
const crashesDir = crashReporter.getCrashesDirectory()
const dir = path.join(app.getPath('temp'), 'Electron Test Main Crashes')
expect(crashesDir).to.equal(dir)
})
})
describe('getUploadedReports', () => {
it('returns an array of reports', () => {
const reports = crashReporter.getUploadedReports()
expect(reports).to.be.an('array')
})
})
// TODO(alexeykuzmin): This suite should explicitly
// generate several crash reports instead of hoping
// that there will be enough of them already.
describe('getLastCrashReport', () => {
it('correctly returns the most recent report', () => {
const reports = crashReporter.getUploadedReports()
expect(reports).to.be.an('array')
expect(reports).to.have.lengthOf.at.least(2,
'There are not enough reports for this test')
const lastReport = crashReporter.getLastCrashReport()
expect(lastReport).to.be.an('object')
expect(lastReport.date).to.be.an.instanceOf(Date)
// Let's find the newest report.
const { report: newestReport } = reports.reduce((acc, cur) => {
const timestamp = new Date(cur.date).getTime()
return (timestamp > acc.timestamp)
? { report: cur, timestamp: timestamp }
: acc
}, { timestamp: -Infinity } as { timestamp: number, report?: any })
expect(newestReport).to.be.an('object')
expect(lastReport.date.getTime()).to.be.equal(
newestReport.date.getTime(),
'Last report is not the newest.')
})
})
describe('getUploadToServer()', () => {
it('returns true when uploadToServer is set to true', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes',
uploadToServer: true
})
expect(crashReporter.getUploadToServer()).to.be.true()
})
it('returns false when uploadToServer is set to false', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes',
uploadToServer: true
})
crashReporter.setUploadToServer(false)
expect(crashReporter.getUploadToServer()).to.be.false()
})
})
describe('setUploadToServer(uploadToServer)', () => {
afterEach(closeAllWindows)
it('throws an error when called from the renderer process', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
w.loadURL('about:blank')
await expect(
w.webContents.executeJavaScript(`require('electron').crashReporter.setUploadToServer(true)`)
).to.eventually.be.rejected()
await expect(
w.webContents.executeJavaScript(`require('electron').crashReporter.getUploadToServer()`)
).to.eventually.be.rejected()
})
it('sets uploadToServer false when called with false', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes',
uploadToServer: true
})
crashReporter.setUploadToServer(false)
expect(crashReporter.getUploadToServer()).to.be.false()
})
it('sets uploadToServer true when called with true', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes',
uploadToServer: false
})
crashReporter.setUploadToServer(true)
expect(crashReporter.getUploadToServer()).to.be.true()
})
})
describe('Parameters', () => {
it('returns all of the current parameters', () => {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
const parameters = crashReporter.getParameters()
expect(parameters).to.be.an('object')
})
it('adds a parameter to current parameters', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
crashReporter.addExtraParameter('hello', 'world')
expect(crashReporter.getParameters()).to.have.property('hello')
})
it('removes a parameter from current parameters', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
crashReporter.addExtraParameter('hello', 'world')
expect(crashReporter.getParameters()).to.have.property('hello')
crashReporter.removeExtraParameter('hello')
expect(crashReporter.getParameters()).to.not.have.property('hello')
})
})
describe('when not started', () => {
it('does not prevent process from crashing', (done) => {
const appPath = path.join(fixtures, 'api', 'cookie-app')
const appProcess = childProcess.spawn(process.execPath, [appPath])
appProcess.once('close', () => {
done()
})
})
})
})
type CrashInfo = {
prod: string
ver: string
process_type: string
platform: string
extra1: string
extra2: string
extra3: undefined
_productName: string
_companyName: string
_version: string
}
async function waitForCrashReport() {
for (let times = 0; times < 10; times++) {
if (crashReporter.getLastCrashReport() != null) {
return
}
await new Promise(resolve => setTimeout(resolve, 100))
}
throw new Error('No crash report available')
}
async function checkReport(reportId: string) {
await waitForCrashReport()
expect(crashReporter.getLastCrashReport().id).to.equal(reportId)
expect(crashReporter.getUploadedReports()).to.be.an('array').that.is.not.empty()
expect(crashReporter.getUploadedReports()[0].id).to.equal(reportId)
}
function checkCrash(expectedProcessType: string, fields: CrashInfo) {
expect(String(fields.prod)).to.equal('Electron')
expect(String(fields.ver)).to.equal(process.versions.electron)
expect(String(fields.process_type)).to.equal(expectedProcessType)
expect(String(fields.platform)).to.equal(process.platform)
expect(String(fields.extra1)).to.equal('extra1')
expect(String(fields.extra2)).to.equal('extra2')
expect(fields.extra3).to.be.undefined()
expect(String(fields._productName)).to.equal('Zombies')
expect(String(fields._companyName)).to.equal('Umbrella Corporation')
expect(String(fields._version)).to.equal(app.getVersion())
}
let crashReporterPort = 0
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)
})
})
}
const server = http.createServer((req, res) => {
const form = new multiparty.Form()
form.parse(req, (error, fields) => {
crashes.push(fields)
if (error) throw error
const reportId = 'abc-123-def-456-abc-789-abc-123-abcd'
res.end(reportId, async () => {
await checkReport(reportId)
req.socket.destroy()
emitter.emit('crash', fields)
})
})
})
await new Promise(resolve => {
server.listen(crashReporterPort, '127.0.0.1', () => { resolve() })
})
const port = (server.address() as AddressInfo).port
if (crashReporterPort === 0) {
// We can only start the crash reporter once, and after that these
// parameters are fixed.
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1:' + port
})
crashReporterPort = port
}
afterTest.push(() => { server.close() })
return { getCrashes, port, waitForCrash }
}

View file

@ -91,7 +91,7 @@ app.whenReady().then(() => {
walker.on('end', () => { walker.on('end', () => {
testFiles.sort() testFiles.sort()
testFiles.forEach((file) => mocha.addFile(file)) sortToEnd(testFiles, f => f.includes('crash-reporter')).forEach((file) => mocha.addFile(file))
const cb = () => { const cb = () => {
// Ensure the callback is called after runner is defined // Ensure the callback is called after runner is defined
process.nextTick(() => { process.nextTick(() => {
@ -101,3 +101,15 @@ app.whenReady().then(() => {
const runner = mocha.run(cb) const runner = mocha.run(cb)
}) })
}) })
function partition (xs, f) {
const trues = []
const falses = []
xs.forEach(x => (f(x) ? trues : falses).push(x))
return [trues, falses]
}
function sortToEnd (xs, f) {
const [end, beginning] = partition(xs, f)
return beginning.concat(end)
}

View file

@ -1,435 +0,0 @@
const chai = require('chai')
const dirtyChai = require('dirty-chai')
const childProcess = require('child_process')
const fs = require('fs')
const http = require('http')
const multiparty = require('multiparty')
const path = require('path')
const temp = require('temp').track()
const url = require('url')
const { closeWindow } = require('./window-helpers')
const { ifit, ifdescribe } = require('./spec-helpers')
const { remote } = require('electron')
const { app, BrowserWindow, crashReporter } = remote
const { expect } = chai
chai.use(dirtyChai)
// TODO(alexeykuzmin): [Ch66] Fails on linux. Fix it and enable back.
ifdescribe(!process.mas && process.platform !== 'linux')('crashReporter module', () => {
let originalTempDirectory = null
let tempDirectory = null
const specTimeout = 10000
before(() => {
tempDirectory = temp.mkdirSync('electronCrashReporterSpec-')
originalTempDirectory = app.getPath('temp')
app.setPath('temp', tempDirectory)
})
after(() => {
app.setPath('temp', originalTempDirectory)
})
const fixtures = path.resolve(__dirname, 'fixtures')
const generateSpecs = (description, browserWindowOpts) => {
describe(description, () => {
let w = null
let stopServer = null
beforeEach(() => {
stopServer = null
w = new BrowserWindow(Object.assign({ show: false }, browserWindowOpts))
})
afterEach(() => closeWindow(w).then(() => { w = null }))
afterEach((done) => {
if (stopServer != null) {
stopServer(done)
} else {
done()
}
})
it('should send minidump when renderer crashes', function (done) {
this.timeout(specTimeout)
stopServer = startServer({
callback (port) {
w.loadFile(path.join(fixtures, 'api', 'crash.html'), { query: { port } })
},
processType: 'renderer',
done: done
})
})
ifit(!browserWindowOpts.webPreferences.sandbox)('should send minidump when node processes crash', function (done) {
this.timeout(specTimeout)
stopServer = startServer({
callback (port) {
const crashesDir = path.join(app.getPath('temp'), `${app.name} Crashes`)
const version = app.getVersion()
const crashPath = path.join(fixtures, 'module', 'crash.js')
w.loadFile(path.join(fixtures, 'api', 'crash_child.html'), { query: { port, crashesDir, crashPath, version } })
},
processType: 'node',
done: done,
preAssert: fields => {
expect(String(fields.newExtra)).to.equal('newExtra')
expect(fields.removeExtra).to.be.undefined()
}
})
})
it('should not send minidump if uploadToServer is false', function (done) {
this.timeout(specTimeout)
let dumpFile
let crashesDir = crashReporter.getCrashesDirectory()
const existingDumpFiles = new Set()
if (process.platform !== 'linux') {
// crashpad puts the dump files in the "completed" subdirectory
if (process.platform === 'darwin') {
crashesDir = path.join(crashesDir, 'completed')
} else {
crashesDir = path.join(crashesDir, 'reports')
}
crashReporter.setUploadToServer(false)
}
const testDone = (uploaded) => {
if (uploaded) return done(new Error('Uploaded crash report'))
if (process.platform !== 'linux') crashReporter.setUploadToServer(true)
expect(fs.existsSync(dumpFile)).to.be.true()
done()
}
let pollInterval
const pollDumpFile = () => {
fs.readdir(crashesDir, (err, files) => {
if (err) return
const dumps = files.filter((file) => /\.dmp$/.test(file) && !existingDumpFiles.has(file))
if (!dumps.length) return
expect(dumps).to.have.lengthOf(1)
dumpFile = path.join(crashesDir, dumps[0])
clearInterval(pollInterval)
// dump file should not be deleted when not uploading, so we wait
// 1s and assert it still exists in `testDone`
setTimeout(testDone, 1000)
})
}
remote.ipcMain.once('list-existing-dumps', (event) => {
fs.readdir(crashesDir, (err, files) => {
if (!err) {
for (const file of files) {
if (/\.dmp$/.test(file)) {
existingDumpFiles.add(file)
}
}
}
event.returnValue = null // allow the renderer to crash
pollInterval = setInterval(pollDumpFile, 100)
})
})
stopServer = startServer({
callback (port) {
const crashUrl = url.format({
protocol: 'file',
pathname: path.join(fixtures, 'api', 'crash.html'),
search: `?port=${port}&skipUpload=1`
})
w.loadURL(crashUrl)
},
processType: 'renderer',
done: testDone.bind(null, true)
})
})
it('should send minidump with updated extra parameters', function (done) {
this.timeout(specTimeout)
stopServer = startServer({
callback (port) {
const crashUrl = url.format({
protocol: 'file',
pathname: path.join(fixtures, 'api', 'crash-restart.html'),
search: `?port=${port}`
})
w.loadURL(crashUrl)
},
processType: 'renderer',
done: done
})
})
})
}
generateSpecs('without sandbox', {
webPreferences: {
nodeIntegration: true
}
})
generateSpecs('with sandbox', {
webPreferences: {
sandbox: true,
preload: path.join(fixtures, 'module', 'preload-sandbox.js')
}
})
generateSpecs('with remote module disabled', {
webPreferences: {
nodeIntegration: true,
enableRemoteModule: false
}
})
describe('getProductName', () => {
it('returns the product name if one is specified', () => {
const name = crashReporter.getProductName()
const expectedName = 'Electron Test'
expect(name).to.equal(expectedName)
})
})
describe('start(options)', () => {
it('requires that the companyName and submitURL options be specified', () => {
expect(() => {
crashReporter.start({ companyName: 'Missing submitURL' })
}).to.throw('submitURL is a required option to crashReporter.start')
expect(() => {
crashReporter.start({ submitURL: 'Missing companyName' })
}).to.throw('companyName is a required option to crashReporter.start')
})
it('can be called multiple times', () => {
expect(() => {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
crashReporter.start({
companyName: 'Umbrella Corporation 2',
submitURL: 'http://127.0.0.1/more-crashes'
})
}).to.not.throw()
})
})
describe('getCrashesDirectory', () => {
it('correctly returns the directory', () => {
const crashesDir = crashReporter.getCrashesDirectory()
const dir = path.join(app.getPath('temp'), 'Electron Test Crashes')
expect(crashesDir).to.equal(dir)
})
})
describe('getUploadedReports', () => {
it('returns an array of reports', () => {
const reports = crashReporter.getUploadedReports()
expect(reports).to.be.an('array')
})
})
// TODO(alexeykuzmin): This suite should explicitly
// generate several crash reports instead of hoping
// that there will be enough of them already.
describe('getLastCrashReport', () => {
it('correctly returns the most recent report', () => {
const reports = crashReporter.getUploadedReports()
expect(reports).to.be.an('array')
expect(reports).to.have.lengthOf.at.least(2,
'There are not enough reports for this test')
const lastReport = crashReporter.getLastCrashReport()
expect(lastReport).to.be.an('object').that.includes.a.key('date')
// Let's find the newest report.
const { report: newestReport } = reports.reduce((acc, cur) => {
const timestamp = new Date(cur.date).getTime()
return (timestamp > acc.timestamp)
? { report: cur, timestamp: timestamp }
: acc
}, { timestamp: -Infinity })
expect(newestReport).to.be.an('object')
expect(lastReport.date.getTime()).to.be.equal(
newestReport.date.getTime(),
'Last report is not the newest.')
})
})
describe('getUploadToServer()', () => {
it('throws an error when called from the renderer process', () => {
expect(() => require('electron').crashReporter.getUploadToServer()).to.throw()
})
it('returns true when uploadToServer is set to true', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes',
uploadToServer: true
})
expect(crashReporter.getUploadToServer()).to.be.true()
})
it('returns false when uploadToServer is set to false', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes',
uploadToServer: true
})
crashReporter.setUploadToServer(false)
expect(crashReporter.getUploadToServer()).to.be.false()
})
})
describe('setUploadToServer(uploadToServer)', () => {
it('throws an error when called from the renderer process', () => {
expect(() => require('electron').crashReporter.setUploadToServer('arg')).to.throw()
})
it('sets uploadToServer false when called with false', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes',
uploadToServer: true
})
crashReporter.setUploadToServer(false)
expect(crashReporter.getUploadToServer()).to.be.false()
})
it('sets uploadToServer true when called with true', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes',
uploadToServer: false
})
crashReporter.setUploadToServer(true)
expect(crashReporter.getUploadToServer()).to.be.true()
})
})
describe('Parameters', () => {
it('returns all of the current parameters', () => {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
const parameters = crashReporter.getParameters()
expect(parameters).to.be.an('object')
})
it('adds a parameter to current parameters', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
crashReporter.addExtraParameter('hello', 'world')
expect(crashReporter.getParameters()).to.have.a.property('hello')
})
it('removes a parameter from current parameters', function () {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1/crashes'
})
crashReporter.addExtraParameter('hello', 'world')
expect(crashReporter.getParameters()).to.have.a.property('hello')
crashReporter.removeExtraParameter('hello')
expect(crashReporter.getParameters()).to.not.have.a.property('hello')
})
})
describe('when not started', () => {
it('does not prevent process from crashing', (done) => {
const appPath = path.join(fixtures, 'api', 'cookie-app')
const appProcess = childProcess.spawn(process.execPath, [appPath])
appProcess.once('close', () => {
done()
})
})
})
})
const waitForCrashReport = () => {
return new Promise((resolve, reject) => {
let times = 0
const checkForReport = () => {
if (crashReporter.getLastCrashReport() != null) {
resolve()
} else if (times >= 10) {
reject(new Error('No crash report available'))
} else {
times++
setTimeout(checkForReport, 100)
}
}
checkForReport()
})
}
const startServer = ({ callback, processType, done, preAssert, postAssert }) => {
let called = false
const server = http.createServer((req, res) => {
const form = new multiparty.Form()
form.parse(req, (error, fields) => {
if (error) throw error
if (called) return
called = true
expect(String(fields.prod)).to.equal('Electron')
expect(String(fields.ver)).to.equal(process.versions.electron)
expect(String(fields.process_type)).to.equal(processType)
expect(String(fields.platform)).to.equal(process.platform)
expect(String(fields.extra1)).to.equal('extra1')
expect(String(fields.extra2)).to.equal('extra2')
expect(fields.extra3).to.be.undefined()
expect(String(fields._productName)).to.equal('Zombies')
expect(String(fields._companyName)).to.equal('Umbrella Corporation')
expect(String(fields._version)).to.equal(app.getVersion())
if (preAssert) preAssert(fields)
const reportId = 'abc-123-def-456-abc-789-abc-123-abcd'
res.end(reportId, () => {
waitForCrashReport().then(() => {
if (postAssert) postAssert(reportId)
expect(crashReporter.getLastCrashReport().id).to.equal(reportId)
expect(crashReporter.getUploadedReports()).to.be.an('array').that.is.not.empty()
expect(crashReporter.getUploadedReports()[0].id).to.equal(reportId)
req.socket.destroy()
done()
}, done)
})
})
})
const activeConnections = new Set()
server.on('connection', (connection) => {
activeConnections.add(connection)
connection.once('close', () => {
activeConnections.delete(connection)
})
})
let { port } = remote.process
server.listen(port, '127.0.0.1', () => {
port = server.address().port
remote.process.port = port
if (process.platform !== 'linux') {
crashReporter.start({
companyName: 'Umbrella Corporation',
submitURL: 'http://127.0.0.1:' + port
})
}
callback(port)
})
return function stopServer (done) {
for (const connection of activeConnections) {
connection.destroy()
}
server.close(() => {
done()
})
}
}

View file

@ -76,8 +76,6 @@
no_recurse: true no_recurse: true
}) })
const crashSpec = 'api-crash-reporter-spec.js'
// This allows you to run specific modules only: // This allows you to run specific modules only:
// npm run test -match=menu // npm run test -match=menu
const moduleMatch = process.env.npm_config_match const moduleMatch = process.env.npm_config_match
@ -86,8 +84,7 @@
const testFiles = [] const testFiles = []
walker.on('file', (file) => { walker.on('file', (file) => {
if (/-spec\.js$/.test(file) && !file.includes(crashSpec) && if (/-spec\.js$/.test(file) && (!moduleMatch || moduleMatch.test(file))) {
(!moduleMatch || moduleMatch.test(file))) {
testFiles.push(file) testFiles.push(file)
} }
}) })
@ -95,9 +92,6 @@
walker.on('end', () => { walker.on('end', () => {
testFiles.sort() testFiles.sort()
testFiles.forEach((file) => mocha.addFile(file)) testFiles.forEach((file) => mocha.addFile(file))
if (!process.env.npm_config_match || new RegExp(process.env.npm_config_match, 'g').test(crashSpec)) {
mocha.addFile(path.resolve(__dirname, '..', crashSpec))
}
const runner = mocha.run(() => { const runner = mocha.run(() => {
// Ensure the callback is called after runner is defined // Ensure the callback is called after runner is defined

View file

@ -241,6 +241,13 @@
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.7.tgz#315d570ccb56c53452ff8638738df60726d5b6ea"
integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ== integrity sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==
"@types/multiparty@^0.0.32":
version "0.0.32"
resolved "https://registry.yarnpkg.com/@types/multiparty/-/multiparty-0.0.32.tgz#99226df6050dae605fa6f9489d6c0b5ab61fdc00"
integrity sha512-zuQEcL9pHiW3u1fgkOWE6T/RwskrFZ3W63aKQPDs7EokIjtbsGL7aVlI4tI86qjL4B3hcFxDRtIGxwRwMTS2Dw==
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@^12.0.10": "@types/node@*", "@types/node@^12.0.10":
version "12.6.1" version "12.6.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.1.tgz#d5544f6de0aae03eefbb63d5120f6c8be0691946" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.1.tgz#d5544f6de0aae03eefbb63d5120f6c8be0691946"
@ -295,6 +302,13 @@
resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.4.tgz#b4ffc7dc97b498c969b360a41eee247f82616370"
integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ== integrity sha512-78AdXtlhpCHT0K3EytMpn4JNxaf5tbqbLcbIRoQIHzpTIyjpxLQKRoxU55ujBXAtg3Nl2h/XWvfDa9dsMOd0pQ==
"@types/temp@^0.8.34":
version "0.8.34"
resolved "https://registry.yarnpkg.com/@types/temp/-/temp-0.8.34.tgz#03e4b3cb67cbb48c425bbf54b12230fef85540ac"
integrity sha512-oLa9c5LHXgS6UimpEVp08De7QvZ+Dfu5bMQuWyMhf92Z26Q10ubEMOWy9OEfUdzW7Y/sDWVHmUaLFtmnX/2j0w==
dependencies:
"@types/node" "*"
"@types/through@*": "@types/through@*":
version "0.0.29" version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.29.tgz#72943aac922e179339c651fa34a4428a4d722f93" resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.29.tgz#72943aac922e179339c651fa34a4428a4d722f93"