1
0
Fork 0

Implement new logic for the command wrapper and add tests

pull/1562/head
Dusan Trickovic 2023-09-19 12:00:52 +02:00
parent 2a75b5e2b3
commit 72a2612e0c
2 changed files with 124 additions and 138 deletions

View File

@ -1,53 +1,83 @@
import {Command} from '../src/exec-command-wrapper' import CommandHelper from '../src/exec-command-wrapper'
import * as io from '@actions/io' import * as io from '@actions/io'
const IS_LINUX = process.platform === 'linux' const IS_WINDOWS = process.platform === 'win32'
describe('Command', () => { describe('Command', () => {
it('creates a command object', async () => { it('creates a command object', async () => {
if (IS_LINUX) { let toolpath: string
const toolpath = await io.which('echo', true) let args: string[]
const command = new Command(`"${toolpath}"`, ['hello']) if (IS_WINDOWS) {
expect(command).toBeDefined() toolpath = await io.which('cmd', true)
expect(command).toBeInstanceOf(Command) args = ['/c', 'echo', 'hello']
} else {
toolpath = await io.which('echo', true)
args = ['hello']
} }
const command = new CommandHelper(`"${toolpath}"`, args)
expect(command).toBeDefined()
expect(command).toBeInstanceOf(CommandHelper)
}) })
it('runs a command with non-zero exit code', async () => { it('runs a command with non-zero exit code', async () => {
if (IS_LINUX) { let toolpath: string
const nonExistentDir = 'non-existent-dir' let args: string[]
const toolpath = await io.which('ls', true) if (IS_WINDOWS) {
const args = ['-l', nonExistentDir] toolpath = await io.which('cmd', true)
const command = new Command(`"${toolpath}"`, args) args = ['/c', 'dir', 'non-existent-dir']
} else {
let failed = false toolpath = await io.which('ls', true)
args = ['-l', 'non-existent-dir']
await command.execute().catch(err => { }
failed = true const command = new CommandHelper(`"${toolpath}"`, args, undefined, {
expect(err.message).toContain( throwOnEmptyOutput: true
`The process '${toolpath}' failed with exit code ` })
) try {
}) const result = await command.execute()
expect(result.exitCode).not.toEqual(0)
expect(failed).toBe(true) } catch (err) {
expect(err.message).toContain(
`The process '${toolpath}' failed with exit code `
)
} }
}) })
it('runs a command with zero exit code', async () => { it('runs a command with zero exit code', async () => {
if (IS_LINUX) { let toolpath: string
const toolpath = await io.which('echo', true) let args: string[]
const command = new Command(`"${toolpath}"`, ['hello']) if (IS_WINDOWS) {
const result = await command.execute() toolpath = await io.which('cmd', true)
expect(result).toEqual('hello') args = ['/c', 'echo', 'hello']
} else {
toolpath = await io.which('echo', true)
args = ['hello']
} }
const command = new CommandHelper(`"${toolpath}"`, args)
const result = await command.execute()
expect(result.stdout).toContain('hello')
expect(result.exitCode).toEqual(0)
}) })
it('runs a command with empty output', async () => { it('runs a command with empty output', async () => {
if (IS_LINUX) { let toolpath: string
const toolpath = await io.which('echo', true) let args: string[]
const command = new Command(`"${toolpath}"`, ['']) if (IS_WINDOWS) {
toolpath = await io.which('cmd', true)
args = ['/c', 'echo.']
} else {
toolpath = await io.which('echo', true)
args = ['']
}
const command = new CommandHelper(`"${toolpath}"`, args, undefined, {
throwOnEmptyOutput: true
})
try {
const result = await command.execute() const result = await command.execute()
expect(result).toEqual('') expect(result.stdout).toBe('')
} catch (err) {
expect(err.message).toContain('Command produced empty output.')
} }
}) })
}) })

View File

@ -1,123 +1,79 @@
import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import * as core from '@actions/core'
export class Command { export default class CommandHelper {
private readonly commandText: string private commandText: string
private readonly args: string[] private args: string[]
private readonly options: exec.ExecOptions | undefined private options: exec.ExecOptions | undefined
private failOnError = false private throwOnError: boolean
private throwOnError = false private throwOnEmptyOutput: boolean
private failOnError: boolean
private failOnEmptyOutput = false private failOnEmptyOutput: boolean
private throwOnEmptyOutput = false
constructor( constructor(
commandText: string, commandText: string,
args: string[], args: string[] = [],
options: exec.ExecOptions | undefined = undefined options: exec.ExecOptions | undefined = {},
config: {
throwOnError?: boolean
throwOnEmptyOutput?: boolean
failOnError?: boolean
failOnEmptyOutput?: boolean
} = {}
) { ) {
this.commandText = commandText this.commandText = commandText
this.args = args this.args = args
this.options = options this.options = options
this.throwOnError = config.throwOnError ?? false
this.throwOnEmptyOutput = config.throwOnEmptyOutput ?? false
this.failOnError = config.failOnError ?? false
this.failOnEmptyOutput = config.failOnEmptyOutput ?? false
} }
get failOn(): {error: () => Command; empty: () => Command} { async execute(): Promise<exec.ExecOutput> {
return { try {
error: this.setFailOnError, const output = await exec.getExecOutput(
empty: this.setFailOnEmptyOutput this.commandText,
} this.args,
} this.options
get throwOn(): {error: () => Command; empty: () => Command} {
return {
error: this.setThrowOnError,
empty: this.setThrowOnEmptyOutput
}
}
private setFailOnError = (): Command => {
this.failOnError = true
return this
}
private setThrowOnError = (): Command => {
this.throwOnError = true
return this
}
private setFailOnEmptyOutput = (): Command => {
this.failOnEmptyOutput = true
return this
}
private setThrowOnEmptyOutput = (): Command => {
this.throwOnEmptyOutput = true
return this
}
private setFailedOnNonZeroExitCode(
command: string,
exitCode: number,
error: string
): void {
if (exitCode !== 0) {
error = !error.trim()
? `The '${command}' command failed with exit code: ${exitCode}`
: error
core.setFailed(error)
}
return
}
private throwErrorOnNonZeroExitCode(
command: string,
exitCode: number,
error: string
): void {
if (exitCode !== 0) {
error = !error.trim()
? `The '${command}' command failed with exit code: ${exitCode}`
: error
throw new Error(error)
}
return
}
async execute(): Promise<string> {
const {stdout, stderr, exitCode} = await exec.getExecOutput(
this.commandText,
this.args,
this.options
)
if (this.failOnError) {
this.setFailedOnNonZeroExitCode(this.commandText, exitCode, stderr)
return stdout.trim()
}
if (this.throwOnError) {
this.throwErrorOnNonZeroExitCode(this.commandText, exitCode, stderr)
return stdout.trim()
}
if (this.failOnEmptyOutput && !stdout.trim()) {
core.setFailed(
`The '${this.commandText}' command failed with empty output`
) )
return stdout.trim()
}
if (this.throwOnEmptyOutput && !stdout.trim()) { if (this.throwOnError && output.stderr) {
throw new Error( this.onError(output.stderr).throw()
`The '${this.commandText}' command failed with empty output` }
)
}
return stdout.trim() if (this.throwOnEmptyOutput && output.stdout.trim() === '') {
this.onError('Command produced empty output.').throw()
}
if (this.failOnError && output.stderr) {
this.onError(output.stderr).fail()
}
if (this.failOnEmptyOutput && output.stdout.trim() === '') {
this.onError('Command produced empty output.').fail()
}
return output
} catch (error) {
throw new Error((error as Error).message)
}
}
private onError(errorMessage: string): {
throw: () => never
fail: () => void
} {
core.error(`Error occurred: ${errorMessage}`)
return {
throw: () => {
throw new Error(errorMessage)
},
fail: () => {
core.setFailed(errorMessage)
}
}
} }
} }
// new Command('echo', ['hello', 'world'])
// .failOn.error()
// .execute()