diff --git a/packages/exec/__tests__/exec.test.ts b/packages/exec/__tests__/exec.test.ts index b048aa36d6..5fcacb56a3 100644 --- a/packages/exec/__tests__/exec.test.ts +++ b/packages/exec/__tests__/exec.test.ts @@ -16,6 +16,11 @@ const SPAWN_WAIT_SCRIPT = path.join( 'scripts', 'spawn-wait-for-file.cjs' ) +const SELF_TERMINATE_SCRIPT = path.join( + __dirname, + 'scripts', + 'self-terminate.cjs' +) let outstream: stream.Writable let errstream: stream.Writable @@ -192,6 +197,24 @@ describe('@actions/exec', () => { } }) + it('Fails when process terminates via signal', async () => { + if (IS_WINDOWS) { + return + } + + const nodePath: string = await io.which('node', true) + const _testExecOptions = getExecOptions() + + await exec + .exec(`"${nodePath}"`, [SELF_TERMINATE_SCRIPT], _testExecOptions) + .then(() => { + throw new Error('Should not have succeeded') + }) + .catch(err => { + expect(err.message).toContain('terminated by signal SIGTERM') + }) + }) + it('Succeeds on stderr by default', async () => { const scriptPath: string = path.join( __dirname, diff --git a/packages/exec/__tests__/scripts/self-terminate.cjs b/packages/exec/__tests__/scripts/self-terminate.cjs new file mode 100644 index 0000000000..ed11e232ca --- /dev/null +++ b/packages/exec/__tests__/scripts/self-terminate.cjs @@ -0,0 +1,3 @@ +setTimeout(() => { + process.kill(process.pid, 'SIGTERM') +}, 50) diff --git a/packages/exec/src/toolrunner.ts b/packages/exec/src/toolrunner.ts index 02483b2841..fc53ee337f 100644 --- a/packages/exec/src/toolrunner.ts +++ b/packages/exec/src/toolrunner.ts @@ -501,15 +501,21 @@ export class ToolRunner extends events.EventEmitter { state.CheckComplete() }) - cp.on('exit', (code: number) => { + cp.on('exit', (code: number | null, signal: NodeJS.Signals | null) => { state.processExitCode = code + state.processSignal = signal state.processExited = true - this._debug(`Exit code ${code} received from tool '${this.toolPath}'`) + if (signal) { + this._debug(`Signal ${signal} received from tool '${this.toolPath}'`) + } else { + this._debug(`Exit code ${code} received from tool '${this.toolPath}'`) + } state.CheckComplete() }) - cp.on('close', (code: number) => { + cp.on('close', (code: number | null, signal: NodeJS.Signals | null) => { state.processExitCode = code + state.processSignal = signal state.processExited = true state.processClosed = true this._debug(`STDIO streams have closed for tool '${this.toolPath}'`) @@ -625,7 +631,8 @@ class ExecState extends events.EventEmitter { processClosed = false // tracks whether the process has exited and stdio is closed processError = '' - processExitCode = 0 + processExitCode: number | null = 0 + processSignal: NodeJS.Signals | null = null processExited = false // tracks whether the process has exited processStderr = false // tracks whether stderr was written to private delay = 10000 // 10 seconds @@ -658,6 +665,10 @@ class ExecState extends events.EventEmitter { error = new Error( `There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}` ) + } else if (this.processSignal) { + error = new Error( + `The process '${this.toolPath}' failed due to signal ${this.processSignal}` + ) } else if (this.processExitCode !== 0 && !this.options.ignoreReturnCode) { error = new Error( `The process '${this.toolPath}' failed with exit code ${this.processExitCode}`