diff --git a/packages/server/lib/util/node_options.ts b/packages/server/lib/util/node_options.ts index a948dc04ca85..0acc0246443b 100644 --- a/packages/server/lib/util/node_options.ts +++ b/packages/server/lib/util/node_options.ts @@ -3,7 +3,7 @@ import debugModule from 'debug' const debug = debugModule('cypress:server:util:node_options') -const NODE_OPTIONS = `--max-http-header-size=${1024 ** 2} --http-parser=legacy` +export const NODE_OPTIONS = `--max-http-header-size=${1024 ** 2} --http-parser=legacy` /** * If Cypress was not launched via CLI, it may be missing certain startup @@ -63,8 +63,9 @@ export function forkWithCorrectOptions (): void { { stdio: 'inherit' }, ) .on('error', () => {}) - .on('exit', (code) => { - process.exit(code) + .on('exit', (code, signal) => { + debug('child exited %o', { code, signal }) + process.exit(code === null ? 1 : code) }) } diff --git a/packages/server/test/scripts/run.js b/packages/server/test/scripts/run.js index f30e816605ff..0a1ab38e1c52 100644 --- a/packages/server/test/scripts/run.js +++ b/packages/server/test/scripts/run.js @@ -164,4 +164,10 @@ console.log(cmd) const child = execa.shell(cmd, { env, stdio: 'inherit' }) -child.on('exit', process.exit) +child.on('exit', (code, signal) => { + if (signal) { + console.error(`tests exited with signal ${signal}`) + } + + process.exit(code === null ? 1 : code) +}) diff --git a/packages/server/test/unit/node_options_spec.ts b/packages/server/test/unit/node_options_spec.ts new file mode 100644 index 000000000000..130f372d035c --- /dev/null +++ b/packages/server/test/unit/node_options_spec.ts @@ -0,0 +1,63 @@ +import '../spec_helper' +import sinon from 'sinon' +import { expect } from 'chai' +import cp, { ChildProcess } from 'child_process' +import { EventEmitter } from 'events' +import * as nodeOptions from '../../lib/util/node_options' +import mockedEnv from 'mocked-env' + +describe('NODE_OPTIONS lib', function () { + context('.forkWithCorrectOptions', function () { + let fakeProc: EventEmitter + let restoreEnv + + beforeEach(() => { + restoreEnv = mockedEnv({ + NODE_OPTIONS: '', + ORIGINAL_NODE_OPTIONS: '', + }) + }) + + afterEach(() => { + restoreEnv() + }) + + it('modifies NODE_OPTIONS', function () { + process.env.NODE_OPTIONS = 'foo' + expect(process.env.NODE_OPTIONS).to.eq('foo') + sinon.stub(cp, 'spawn').callsFake(() => { + expect(process.env).to.include({ + NODE_OPTIONS: `${nodeOptions.NODE_OPTIONS} foo`, + ORIGINAL_NODE_OPTIONS: 'foo', + }) + + return null as ChildProcess // types + }) + }) + + context('when exiting', function () { + beforeEach(() => { + fakeProc = new EventEmitter() + + sinon.stub(cp, 'spawn') + .withArgs(process.execPath, sinon.match.any, { stdio: 'inherit' }) + .returns(fakeProc as ChildProcess) + + sinon.stub(process, 'exit') + }) + + it('propagates exit codes correctly', function () { + nodeOptions.forkWithCorrectOptions() + fakeProc.emit('exit', 123) + expect(process.exit).to.be.calledWith(123) + }) + + // @see https://github.com/cypress-io/cypress/issues/7722 + it('propagates signals via a non-zero exit code', function () { + nodeOptions.forkWithCorrectOptions() + fakeProc.emit('exit', null, 'SIGKILL') + expect(process.exit).to.be.calledWith(1) + }) + }) + }) +})