diff --git a/packages/helpers/__tests__/command-runner.test.ts b/packages/helpers/__tests__/command-runner.test.ts index e7f70973..255a5916 100644 --- a/packages/helpers/__tests__/command-runner.test.ts +++ b/packages/helpers/__tests__/command-runner.test.ts @@ -1,4 +1,5 @@ import * as exec from '@actions/exec' +import * as core from '@actions/core' import {CommandRunner, createCommandRunner} from '../src/helpers' describe('command-runner', () => { @@ -12,6 +13,7 @@ describe('command-runner', () => { describe('CommandRunner', () => { const execSpy = jest.spyOn(exec, 'exec') + const failSpy = jest.spyOn(core, 'setFailed') afterEach(() => { jest.resetAllMocks() @@ -199,6 +201,228 @@ describe('command-runner', () => { expect(middleware).toHaveBeenCalledTimes(1) }) + + it('fails if event matches', async () => { + execSpy.mockImplementation( + createExecMock({stdout: '', stderr: '', exitCode: 1}) + ) + + failSpy.mockImplementation(() => {}) + + await createCommandRunner('echo', ['hello', 'world'], { + silent: true + }) + .on('!stdout', 'fail') + .run() + + expect(failSpy).toHaveBeenCalledWith( + `The command "echo" finished with exit code 1 and produced an empty output.` + ) + }) + + it('throws error if event matches', async () => { + execSpy.mockImplementation( + createExecMock({stdout: '', stderr: '', exitCode: 1}) + ) + + const cmdPromise = createCommandRunner('echo', ['hello', 'world']) + .onExitCode('> 0', 'throw') + .run() + + await expect(cmdPromise).rejects.toThrow( + 'The command "echo" finished with exit code 1 and produced an empty output.' + ) + }) + + it('logs if event matches', async () => { + execSpy.mockImplementation( + createExecMock({stdout: '', stderr: 'test', exitCode: 1}) + ) + + const logSpy = jest.spyOn(core, 'error') + + await createCommandRunner('echo', ['hello', 'world'], { + silent: true + }) + .on('!ok', 'log') + .run() + + expect(logSpy).toHaveBeenCalledWith( + 'The command "echo" finished with exit code 1 and produced an error: test' + ) + }) + }) + + describe('default handlers', () => { + test('onEmptyOutput', async () => { + execSpy.mockImplementation( + createExecMock({stdout: '', stderr: '', exitCode: 0}) + ) + + const notCalledMiddleware = jest.fn() + const middleware = jest.fn() + + await createCommandRunner('echo', ['hello', 'world']) + .onError(notCalledMiddleware) + .onEmptyOutput(middleware) + .onError(notCalledMiddleware) + .run() + + expect(middleware).toHaveBeenCalledTimes(1) + expect(notCalledMiddleware).not.toHaveBeenCalled() + }) + + test('onExecutionError', async () => { + execSpy.mockImplementation(() => { + throw new Error('test') + }) + + const notCalledMiddleware = jest.fn() + const middleware = jest.fn() + + await createCommandRunner('echo', ['hello', 'world'], { + silent: true + }) + .onSuccess(notCalledMiddleware) + .onExecutionError(middleware) + .onSuccess(notCalledMiddleware) + .run() + + expect(middleware).toHaveBeenCalledTimes(1) + expect(notCalledMiddleware).not.toHaveBeenCalled() + }) + + test('onStdError', async () => { + execSpy.mockImplementation( + createExecMock({stdout: '', stderr: 'test', exitCode: 0}) + ) + + const notCalledMiddleware = jest.fn() + const middleware = jest.fn() + + await createCommandRunner('echo', ['hello', 'world'], { + silent: true + }) + .onSuccess(notCalledMiddleware) + .onStdError(middleware) + .onSuccess(notCalledMiddleware) + .run() + + expect(middleware).toHaveBeenCalledTimes(1) + expect(notCalledMiddleware).not.toHaveBeenCalled() + }) + + test('onError', async () => { + execSpy.mockImplementation( + createExecMock({stdout: '', stderr: 'test', exitCode: 0}) + ) + + const notCalledMiddleware = jest.fn() + const middleware = jest.fn() + + await createCommandRunner('echo', ['hello', 'world'], { + silent: true + }) + .onSuccess(notCalledMiddleware) + .onError(middleware) + .onSuccess(notCalledMiddleware) + .run() + + execSpy.mockImplementation(() => { + throw new Error('test') + }) + + await createCommandRunner('echo', ['hello', 'world'], { + silent: true + }) + .onSuccess(notCalledMiddleware) + .onError(middleware) + .onSuccess(notCalledMiddleware) + .run() + + expect(middleware).toHaveBeenCalledTimes(2) + expect(notCalledMiddleware).not.toHaveBeenCalled() + }) + + test('onSpecificError', async () => { + execSpy.mockImplementation( + createExecMock({stdout: '', stderr: 'test', exitCode: 0}) + ) + + const notCalledMiddleware = jest.fn() + const middleware = jest.fn() + + await createCommandRunner('echo', ['hello', 'world'], { + silent: true + }) + .onSuccess(notCalledMiddleware) + .onSpecificError(/test/, middleware) + .onSuccess(notCalledMiddleware) + .run() + + expect(middleware).toHaveBeenCalledTimes(1) + expect(notCalledMiddleware).not.toHaveBeenCalled() + }) + + test('onSuccess', async () => { + execSpy.mockImplementation( + createExecMock({stdout: '', stderr: '', exitCode: 0}) + ) + + const notCalledMiddleware = jest.fn() + const middleware = jest.fn() + + await createCommandRunner('echo', ['hello', 'world'], { + silent: true + }) + .onError(notCalledMiddleware) + .onSuccess(middleware) + .onError(notCalledMiddleware) + .run() + + expect(middleware).toHaveBeenCalledTimes(1) + expect(notCalledMiddleware).not.toHaveBeenCalled() + }) + + test('onExitcode', async () => { + execSpy.mockImplementation( + createExecMock({stdout: '', stderr: '', exitCode: 2}) + ) + + const notCalledMiddleware = jest.fn() + const middleware = jest.fn() + + await createCommandRunner('echo', ['hello', 'world'], { + silent: true + }) + .onSuccess(notCalledMiddleware) + .onExitCode('> 0', middleware) + .onSuccess(notCalledMiddleware) + .run() + + expect(middleware).toHaveBeenCalledTimes(1) + expect(notCalledMiddleware).not.toHaveBeenCalled() + }) + + test('onOutput', async () => { + execSpy.mockImplementation( + createExecMock({stdout: 'test', stderr: '', exitCode: 0}) + ) + + const notCalledMiddleware = jest.fn() + const middleware = jest.fn() + + await createCommandRunner('echo', ['hello', 'world'], { + silent: true + }) + .onError(notCalledMiddleware) + .onOutput(/test/, middleware) + .onError(notCalledMiddleware) + .run() + + expect(middleware).toHaveBeenCalledTimes(1) + expect(notCalledMiddleware).not.toHaveBeenCalled() + }) }) }) }) diff --git a/packages/helpers/src/command-runner/middleware.ts b/packages/helpers/src/command-runner/middleware.ts index aa8632ba..8814c2e6 100644 --- a/packages/helpers/src/command-runner/middleware.ts +++ b/packages/helpers/src/command-runner/middleware.ts @@ -92,36 +92,38 @@ export const failAction: CommandRunnerAction = message => async ctx => { /** * Throws an error with the given message or with a default one depending on execution conditions. */ -export const throwError: CommandRunnerAction = message => async ctx => { - const events = getEventTypesFromContext(ctx) +export const throwError: CommandRunnerAction = message => { + return async ctx => { + const events = getEventTypesFromContext(ctx) + + if (message !== undefined) { + throw new Error( + typeof message === 'string' ? message : message(ctx, events) + ) + } + + if (events.includes('execerr')) { + throw new Error( + `The command "${ctx.commandLine}" failed to run: ${ctx.execerr?.message}` + ) + } + + if (events.includes('stderr')) { + throw new Error( + `The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an error: ${ctx.stderr}` + ) + } + + if (!events.includes('stdout')) { + throw new Error( + `The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an empty output.` + ) + } - if (message !== undefined) { throw new Error( - typeof message === 'string' ? message : message(ctx, events) + `The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced the following output: ${ctx.stdout}` ) } - - if (events.includes('execerr')) { - throw new Error( - `The command "${ctx.commandLine}" failed to run: ${ctx.execerr?.message}` - ) - } - - if (events.includes('stderr')) { - throw new Error( - `The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an error: ${ctx.stderr}` - ) - } - - if (!events.includes('stdout')) { - throw new Error( - `The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an empty output.` - ) - } - - throw new Error( - `The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced the following output: ${ctx.stdout}` - ) } /** @@ -345,7 +347,7 @@ export const matchExitCode = ( return async (ctx, next) => { // if exit code is undefined, NaN will not match anything if (matcherFn(ctx.exitCode ?? NaN)) { - composedMiddleware(ctx, next) + await composedMiddleware(ctx, next) return }