mirror of https://github.com/actions/toolkit
508 lines
14 KiB
TypeScript
508 lines
14 KiB
TypeScript
import * as exec from '../src/exec'
|
|
import * as core from '@actions/core'
|
|
import * as io from '@actions/io'
|
|
import {CommandRunner, createCommandRunner} from '../src/command-runner'
|
|
|
|
describe('command-runner', () => {
|
|
describe('createCommandRunner', () => {
|
|
it('creates a command object', async () => {
|
|
const command = createCommandRunner('echo')
|
|
expect(command).toBeDefined()
|
|
expect(command).toBeInstanceOf(CommandRunner)
|
|
})
|
|
})
|
|
|
|
describe('CommandRunner', () => {
|
|
const execSpy = jest.spyOn(exec, 'exec')
|
|
const failSpy = jest.spyOn(core, 'setFailed')
|
|
|
|
afterEach(() => {
|
|
jest.resetAllMocks()
|
|
})
|
|
|
|
it('runs basic commands', async () => {
|
|
execSpy.mockImplementation(async () => 0)
|
|
|
|
const command = createCommandRunner('echo', ['hello', 'world'], {
|
|
silent: true
|
|
})
|
|
command.run()
|
|
|
|
expect(execSpy).toHaveBeenCalledTimes(1)
|
|
expect(execSpy).toHaveBeenCalledWith(
|
|
'echo',
|
|
['hello', 'world'],
|
|
expect.objectContaining({
|
|
silent: true,
|
|
ignoreReturnCode: true
|
|
})
|
|
)
|
|
})
|
|
|
|
it('throws error if command is not specified', async () => {
|
|
const command = createCommandRunner()
|
|
await expect(command.run()).rejects.toThrow('Command was not specified')
|
|
})
|
|
|
|
it('will have exec error if it occured', async () => {
|
|
execSpy.mockImplementation(async () => {
|
|
throw new Error('test')
|
|
})
|
|
|
|
const command = createCommandRunner('echo', ['hello', 'world'], {
|
|
silent: true
|
|
})
|
|
const context = await command.run()
|
|
|
|
expect(context.execerr).toBeDefined()
|
|
expect(context.execerr?.message).toBe('test')
|
|
})
|
|
|
|
it('allows to set command, args and options', async () => {
|
|
execSpy.mockImplementation(async () => 0)
|
|
|
|
createCommandRunner()
|
|
.setCommand('echo')
|
|
.setArgs(['hello', 'world'])
|
|
.setOptions({silent: true})
|
|
.run()
|
|
|
|
expect(execSpy).toHaveBeenCalledTimes(1)
|
|
expect(execSpy).toHaveBeenCalledWith(
|
|
'echo',
|
|
['hello', 'world'],
|
|
expect.objectContaining({
|
|
silent: true,
|
|
ignoreReturnCode: true
|
|
})
|
|
)
|
|
})
|
|
|
|
it('allows to modify command, args and options', async () => {
|
|
execSpy.mockImplementation(async () => 0)
|
|
|
|
createCommandRunner('echo', ['hello', 'world'], {silent: true})
|
|
.setCommand(commandLine => `${commandLine} hello world`)
|
|
.setArgs(() => [])
|
|
.setOptions(options => ({...options, env: {test: 'test'}}))
|
|
.run()
|
|
|
|
expect(execSpy).toHaveBeenCalledTimes(1)
|
|
expect(execSpy).toHaveBeenCalledWith(
|
|
'echo hello world',
|
|
[],
|
|
expect.objectContaining({
|
|
silent: true,
|
|
ignoreReturnCode: true,
|
|
env: {test: 'test'}
|
|
})
|
|
)
|
|
})
|
|
|
|
const createExecMock = (output: {
|
|
stdout: string
|
|
stderr: string
|
|
exitCode: number
|
|
}): typeof exec.exec => {
|
|
const stdoutBuffer = Buffer.from(output.stdout, 'utf8')
|
|
const stderrBuffer = Buffer.from(output.stderr, 'utf8')
|
|
|
|
return async (
|
|
commandLine?: string,
|
|
args?: string[],
|
|
options?: exec.ExecOptions
|
|
) => {
|
|
options?.listeners?.stdout?.(stdoutBuffer)
|
|
options?.listeners?.stderr?.(stderrBuffer)
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 5))
|
|
return output.exitCode
|
|
}
|
|
}
|
|
|
|
it('allows to use middlewares', async () => {
|
|
execSpy.mockImplementation(
|
|
createExecMock({stdout: 'hello', stderr: '', exitCode: 0})
|
|
)
|
|
|
|
const command = createCommandRunner('echo', ['hello', 'world'], {
|
|
silent: true
|
|
})
|
|
|
|
const middleware = jest.fn()
|
|
|
|
await command.use(middleware).run()
|
|
|
|
expect(middleware).toHaveBeenCalledTimes(1)
|
|
|
|
expect(middleware).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
args: ['hello', 'world'],
|
|
commandLine: 'echo',
|
|
execerr: null,
|
|
exitCode: 0,
|
|
options: {failOnStdErr: false, ignoreReturnCode: true, silent: true},
|
|
stderr: '',
|
|
stdout: 'hello'
|
|
}),
|
|
expect.any(Function)
|
|
)
|
|
})
|
|
|
|
describe('CommandRunner.prototype.on', () => {
|
|
it('passes control to next middleware if nothing has matched', async () => {
|
|
execSpy.mockImplementation(
|
|
createExecMock({
|
|
stdout: 'hello',
|
|
stderr: '',
|
|
exitCode: 0
|
|
})
|
|
)
|
|
|
|
const willBeCalled = jest.fn()
|
|
const willNotBeCalled = jest.fn()
|
|
await createCommandRunner('echo', ['hello', 'world'], {
|
|
silent: true
|
|
})
|
|
.on('!stdout', willNotBeCalled)
|
|
.use(willBeCalled)
|
|
.run()
|
|
|
|
expect(willNotBeCalled).not.toHaveBeenCalled()
|
|
expect(willBeCalled).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('runs a middleware if event matches', async () => {
|
|
execSpy.mockImplementation(
|
|
createExecMock({stdout: '', stderr: '', exitCode: 0})
|
|
)
|
|
|
|
const middleware = jest.fn()
|
|
|
|
await createCommandRunner('echo', ['hello', 'world'], {
|
|
silent: true
|
|
})
|
|
.on('ok', middleware)
|
|
.run()
|
|
|
|
expect(middleware).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('runs a middleware if event matches with negation', async () => {
|
|
execSpy.mockImplementation(
|
|
createExecMock({stdout: '', stderr: '', exitCode: 1})
|
|
)
|
|
|
|
const middleware = jest.fn()
|
|
await createCommandRunner('echo', ['hello', 'world'], {
|
|
silent: true
|
|
})
|
|
.on('!stdout', middleware)
|
|
.run()
|
|
|
|
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()
|
|
})
|
|
})
|
|
})
|
|
|
|
const IS_WINDOWS = process.platform === 'win32'
|
|
|
|
describe('no-mock testing', () => {
|
|
beforeEach(() => {
|
|
jest.restoreAllMocks()
|
|
})
|
|
|
|
it('creates a command object', async () => {
|
|
let toolpath: string
|
|
let args: string[]
|
|
if (IS_WINDOWS) {
|
|
toolpath = await io.which('cmd', true)
|
|
args = ['/c', 'echo', 'hello']
|
|
} else {
|
|
toolpath = await io.which('echo', true)
|
|
args = ['hello']
|
|
}
|
|
const command = createCommandRunner(`"${toolpath}"`, args)
|
|
expect(command).toBeDefined()
|
|
expect(command).toBeInstanceOf(CommandRunner)
|
|
})
|
|
|
|
it('runs a command with non-zero exit code', async () => {
|
|
const runner = createCommandRunner()
|
|
|
|
runner.setOptions({
|
|
silent: true
|
|
})
|
|
|
|
if (IS_WINDOWS) {
|
|
runner.setCommand(await io.which('cmd', true))
|
|
runner.setArgs(['/c', 'dir'])
|
|
} else {
|
|
runner.setCommand(await io.which('ls', true))
|
|
runner.setArgs(['-l'])
|
|
}
|
|
|
|
runner.setArgs((args: string[]) => [...args, 'non-existent-dir'])
|
|
|
|
const cmdPromise = runner.onError('throw').run()
|
|
|
|
await expect(cmdPromise).rejects.toThrow()
|
|
})
|
|
|
|
it('runs a command with zero exit code', async () => {
|
|
const runner = createCommandRunner()
|
|
|
|
if (IS_WINDOWS) {
|
|
runner.setCommand(await io.which('cmd', true))
|
|
runner.setArgs(['/c', 'echo'])
|
|
} else {
|
|
runner.setCommand(await io.which('echo', true))
|
|
}
|
|
|
|
runner.setArgs((args: string[]) => [...args, 'hello'])
|
|
|
|
const result = await runner.run()
|
|
|
|
expect(result.stdout).toContain('hello')
|
|
expect(result.exitCode).toEqual(0)
|
|
})
|
|
|
|
it('runs a command with empty output', async () => {
|
|
const runner = createCommandRunner()
|
|
|
|
if (IS_WINDOWS) {
|
|
runner.setCommand(await io.which('cmd', true))
|
|
runner.setArgs(['/c', 'echo.'])
|
|
} else {
|
|
runner.setCommand(await io.which('echo', true))
|
|
}
|
|
|
|
const cmdPromise = runner.onEmptyOutput('throw').run()
|
|
|
|
await expect(cmdPromise).rejects.toThrow()
|
|
})
|
|
})
|
|
})
|