New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
toolrunner should which tool #220
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,8 +9,7 @@ | |
"format-check": "prettier --check packages/**/*.ts", | ||
"lint": "eslint packages/**/*.ts", | ||
"new-package": "scripts/create-package", | ||
"test": "jest", | ||
"test-ci": "jest --testPathIgnorePatterns=\"<rootDir>/packages/exec/__tests__/exec.test.ts\"" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we dont need this command anymore, this was to skip the failing tests on windows |
||
"test": "jest" | ||
}, | ||
"devDependencies": { | ||
"@types/jest": "^24.0.11", | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,13 +48,10 @@ await exec.exec('node', ['index.js', 'foo=bar'], options); | |
|
||
#### Exec tools not in the PATH | ||
|
||
You can use it in conjunction with the `which` function from `@actions/io` to execute tools that are not in the PATH: | ||
You can specify the full path for tools not in the PATH: | ||
|
||
```js | ||
const exec = require('@actions/exec'); | ||
const io = require('@actions/io'); | ||
|
||
const pythonPath: string = await io.which('python', true) | ||
|
||
await exec.exec(`"${pythonPath}"`, ['main.py']); | ||
await exec.exec('"/path/to/my-tool"', ['arg1']); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the previous example did the exact opposite of the description :( |
||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -121,6 +121,38 @@ describe('@actions/exec', () => { | |
} | ||
}) | ||
|
||
it('Runs exec successfully with command from PATH', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. coverage for existing functionality |
||
const execOptions = getExecOptions() | ||
const outStream = new StringStream() | ||
execOptions.outStream = outStream | ||
let output = '' | ||
execOptions.listeners = { | ||
stdout: (data: Buffer) => { | ||
output += data.toString() | ||
} | ||
} | ||
|
||
let exitCode = 1 | ||
let tool: string | ||
let args: string[] | ||
if (IS_WINDOWS) { | ||
tool = 'cmd' | ||
args = ['/c', 'echo', 'hello'] | ||
} else { | ||
tool = 'sh' | ||
args = ['-c', 'echo hello'] | ||
} | ||
|
||
exitCode = await exec.exec(tool, args, execOptions) | ||
|
||
expect(exitCode).toBe(0) | ||
const rootedTool = await io.which(tool, true) | ||
expect(outStream.getContents().split(os.EOL)[0]).toBe( | ||
`[command]${rootedTool} ${args.join(' ')}` | ||
) | ||
expect(output.trim()).toBe(`hello`) | ||
}) | ||
|
||
it('Exec fails with error on bad call', async () => { | ||
const _testExecOptions = getExecOptions() | ||
|
||
|
@@ -418,6 +450,134 @@ describe('@actions/exec', () => { | |
fs.unlinkSync(semaphorePath) | ||
}) | ||
|
||
it('Exec roots relative tool path using unrooted options.cwd', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tests to make sure we're not breaking existing behavior also this confirms that extensions are resolved on windows, which the previous code did not do (but should have done) |
||
let exitCode: number | ||
let command: string | ||
if (IS_WINDOWS) { | ||
command = './print-args-cmd' // let ToolRunner resolve the extension | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note, without extension, toolrunner should resolve the extension There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding a |
||
} else { | ||
command = './print-args-sh.sh' | ||
} | ||
const execOptions = getExecOptions() | ||
execOptions.cwd = 'scripts' | ||
const outStream = new StringStream() | ||
execOptions.outStream = outStream | ||
let output = '' | ||
execOptions.listeners = { | ||
stdout: (data: Buffer) => { | ||
output += data.toString() | ||
} | ||
} | ||
|
||
const originalCwd = process.cwd() | ||
try { | ||
process.chdir(__dirname) | ||
exitCode = await exec.exec(`${command} hello world`, [], execOptions) | ||
} catch (err) { | ||
process.chdir(originalCwd) | ||
throw err | ||
} | ||
|
||
expect(exitCode).toBe(0) | ||
const toolPath = path.resolve( | ||
__dirname, | ||
execOptions.cwd, | ||
`${command}${IS_WINDOWS ? '.cmd' : ''}` | ||
) | ||
if (IS_WINDOWS) { | ||
expect(outStream.getContents().split(os.EOL)[0]).toBe( | ||
`[command]${process.env.ComSpec} /D /S /C "${toolPath} hello world"` | ||
) | ||
} else { | ||
expect(outStream.getContents().split(os.EOL)[0]).toBe( | ||
`[command]${toolPath} hello world` | ||
) | ||
} | ||
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`) | ||
}) | ||
|
||
it('Exec roots relative tool path using rooted options.cwd', async () => { | ||
let command: string | ||
if (IS_WINDOWS) { | ||
command = './print-args-cmd' // let ToolRunner resolve the extension | ||
} else { | ||
command = './print-args-sh.sh' | ||
} | ||
const execOptions = getExecOptions() | ||
execOptions.cwd = path.join(__dirname, 'scripts') | ||
const outStream = new StringStream() | ||
execOptions.outStream = outStream | ||
let output = '' | ||
execOptions.listeners = { | ||
stdout: (data: Buffer) => { | ||
output += data.toString() | ||
} | ||
} | ||
|
||
const exitCode = await exec.exec(`${command} hello world`, [], execOptions) | ||
|
||
expect(exitCode).toBe(0) | ||
const toolPath = path.resolve( | ||
__dirname, | ||
execOptions.cwd, | ||
`${command}${IS_WINDOWS ? '.cmd' : ''}` | ||
) | ||
if (IS_WINDOWS) { | ||
expect(outStream.getContents().split(os.EOL)[0]).toBe( | ||
`[command]${process.env.ComSpec} /D /S /C "${toolPath} hello world"` | ||
) | ||
} else { | ||
expect(outStream.getContents().split(os.EOL)[0]).toBe( | ||
`[command]${toolPath} hello world` | ||
) | ||
} | ||
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`) | ||
}) | ||
|
||
it('Exec roots relative tool path using process.cwd', async () => { | ||
let exitCode: number | ||
let command: string | ||
if (IS_WINDOWS) { | ||
command = 'scripts/print-args-cmd' // let ToolRunner resolve the extension | ||
} else { | ||
command = 'scripts/print-args-sh.sh' | ||
} | ||
const execOptions = getExecOptions() | ||
const outStream = new StringStream() | ||
execOptions.outStream = outStream | ||
let output = '' | ||
execOptions.listeners = { | ||
stdout: (data: Buffer) => { | ||
output += data.toString() | ||
} | ||
} | ||
|
||
const originalCwd = process.cwd() | ||
try { | ||
process.chdir(__dirname) | ||
exitCode = await exec.exec(`${command} hello world`, [], execOptions) | ||
} catch (err) { | ||
process.chdir(originalCwd) | ||
throw err | ||
} | ||
|
||
expect(exitCode).toBe(0) | ||
const toolPath = path.resolve( | ||
__dirname, | ||
`${command}${IS_WINDOWS ? '.cmd' : ''}` | ||
) | ||
if (IS_WINDOWS) { | ||
expect(outStream.getContents().split(os.EOL)[0]).toBe( | ||
`[command]${process.env.ComSpec} /D /S /C "${toolPath} hello world"` | ||
) | ||
} else { | ||
expect(outStream.getContents().split(os.EOL)[0]).toBe( | ||
`[command]${toolPath} hello world` | ||
) | ||
} | ||
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`) | ||
}) | ||
|
||
if (IS_WINDOWS) { | ||
// Win specific quoting tests | ||
it('execs .exe with verbatim args (Windows)', async () => { | ||
|
@@ -572,6 +732,42 @@ describe('@actions/exec', () => { | |
) | ||
}) | ||
|
||
it('execs .cmd from path (Windows)', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here is the test related to the bug fix. toolrunner should which the tool before attempting to invoke. otherwise simple commands like |
||
// this test validates whether a .cmd is resolved from the PATH when the extension is not specified | ||
const cmd = 'print-args-cmd' // note, not print-args-cmd.cmd | ||
const cmdPath = path.join(__dirname, 'scripts', `${cmd}.cmd`) | ||
const args: string[] = ['my arg 1', 'my arg 2'] | ||
const outStream = new StringStream() | ||
let output = '' | ||
const options = { | ||
outStream: <stream.Writable>outStream, | ||
listeners: { | ||
stdout: (data: Buffer) => { | ||
output += data.toString() | ||
} | ||
} | ||
} | ||
|
||
const originalPath = process.env['Path'] | ||
try { | ||
process.env['Path'] = `${originalPath};${path.dirname(cmdPath)}` | ||
const exitCode = await exec.exec(`${cmd}`, args, options) | ||
expect(exitCode).toBe(0) | ||
expect(outStream.getContents().split(os.EOL)[0]).toBe( | ||
`[command]${ | ||
process.env.ComSpec | ||
} /D /S /C "${cmdPath} "my arg 1" "my arg 2""` | ||
) | ||
expect(output.trim()).toBe( | ||
'args[0]: "<quote>my arg 1<quote>"\r\n' + | ||
'args[1]: "<quote>my arg 2<quote>"' | ||
) | ||
} catch (err) { | ||
process.env['Path'] = originalPath | ||
throw err | ||
} | ||
}) | ||
|
||
it('execs .cmd with arg quoting (Windows)', async () => { | ||
// this test validates .cmd quoting rules are applied, not the default libuv rules | ||
const cmdPath = path.join( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
@echo off | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the naming of these script files is a little bit weird, but it's related to the conventions set forth by the existing test case scripts There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i just copied one of the existing cmd files (but it had spaces in the path, and i didnt want that) this script basically just echoes the args, e.g.
|
||
setlocal | ||
set index=0 | ||
|
||
:check_arg | ||
set arg=%1 | ||
if not defined arg goto :eof | ||
set "arg=%arg:"=<quote>%" | ||
echo args[%index%]: "%arg%" | ||
set /a index=%index%+1 | ||
shift | ||
goto check_arg |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#!/usr/bin/env bash | ||
|
||
# store arguments in a special array | ||
args=("$@") | ||
# get number of elements | ||
ELEMENTS=${#args[@]} | ||
|
||
# echo each element | ||
for (( i=0;i<$ELEMENTS;i++)); do | ||
echo "args[$i]: \"${args[${i}]}\"" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same deal here, this script just echos the args |
||
done |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,7 +32,7 @@ | |
"bugs": { | ||
"url": "https://github.com/actions/toolkit/issues" | ||
}, | ||
"devDependencies": { | ||
"dependencies": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. need to call |
||
"@actions/io": "^1.0.1" | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leverage matrix instead of duplicate 3 times