From 0fc027d3a13677cb45034f6a6cc0d8436d594b87 Mon Sep 17 00:00:00 2001 From: Webber Date: Sat, 26 Mar 2022 18:38:05 +0100 Subject: [PATCH] chore: multi-platform hooks and tests --- .husky/pre-commit | 8 ++-- package.json | 5 +- src/model/system.test.ts | 98 ++++++++++++++++++++++++---------------- src/model/system.ts | 4 ++ yarn.lock | 12 +++-- 5 files changed, 79 insertions(+), 48 deletions(-) diff --git a/.husky/pre-commit b/.husky/pre-commit index 0912390c..48f58f49 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,6 +1,8 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -yarn lint-staged --verbose -yarn build -git add dist +# Check changed files +yarn lint-staged + +# Compile the action +yarn build ; git add dist diff --git a/package.json b/package.json index 5dbee6ca..4fabe00b 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,11 @@ "author": "Webber ", "license": "MIT", "scripts": { + "prepare": "husky install", "prebuild": "yarn", "build": "tsc && ncc build lib --source-map --license licenses.txt", "lint": "prettier --check \"src/**/*.{js,ts}\" && eslint src/**/*.ts", "format": "prettier --write \"src/**/*.{js,ts}\"", - "prepare": "husky install", "cli": "yarn ts-node src/index.ts -m cli", "cli-aws": "cross-env cloudRunnerCluster=aws yarn run test-cli", "cli-k8s": "cross-env cloudRunnerCluster=k8s yarn run test-cli", @@ -66,8 +66,7 @@ "lint-staged": { "*.{js,jsx,ts,tsx}": [ "prettier --write", - "eslint", - "jest --findRelatedTests --passWithNoTests" + "eslint" ], "*.{json,md,yaml,yml}": [ "prettier --write" diff --git a/src/model/system.test.ts b/src/model/system.test.ts index 85349f54..20d3cbc2 100644 --- a/src/model/system.test.ts +++ b/src/model/system.test.ts @@ -1,57 +1,77 @@ import * as core from '@actions/core'; +import * as exec from '@actions/exec'; import System from './system'; jest.spyOn(core, 'debug').mockImplementation(() => {}); const info = jest.spyOn(core, 'info').mockImplementation(() => {}); jest.spyOn(core, 'warning').mockImplementation(() => {}); jest.spyOn(core, 'error').mockImplementation(() => {}); - -afterEach(() => { - jest.clearAllMocks(); -}); +const execSpy = jest.spyOn(exec, 'exec').mockImplementation(async () => 0); describe('System', () => { describe('run', () => { - it('runs a command successfully', async () => { - await expect(System.run('true')).resolves.not.toBeNull(); + describe('units', () => { + afterEach(() => jest.clearAllMocks()); + + it('passes the command to command line', async () => { + await expect(System.run('echo test')).resolves.not.toBeNull(); + await expect(execSpy).toHaveBeenLastCalledWith('echo test', expect.anything(), expect.anything()); + }); + + it('throws on when error code is not 0', async () => { + execSpy.mockImplementationOnce(async () => 1); + await expect(System.run('false')).rejects.toThrowError(); + }); + + it('throws when no command is given', async () => { + await expect(System.run('')).rejects.toThrowError(); + }); + + it('throws when command consists only of spaces', async () => { + await expect(System.run(' \t\n')).rejects.toThrowError(); + }); + + it('outputs info', async () => { + execSpy.mockImplementationOnce(async (input, _, options) => { + options?.listeners?.stdout?.(Buffer.from(input, 'utf8')); + return 0; + }); + + await expect(System.run('foo-bar')).resolves.not.toBeNull(); + expect(info).toHaveBeenCalledTimes(1); + expect(info).toHaveBeenLastCalledWith('foo-bar'); + }); }); - it('outputs results', async () => { - await expect(System.run('echo test')).resolves.toStrictEqual('test\n'); - }); + /** + * Not all shells (e.g. Powershell, sh) have a reference to `echo` binary (absent or alias). + * To ensure our integration with '@actions/exec' works as expected we run some specific tests in CI only + */ + if (process.env.CI) { + describe('integration', () => { + execSpy.mockRestore(); - it('throws on when error code is not 0', async () => { - await expect(System.run('false')).rejects.toThrowError(); - }); + it('runs a command successfully', async () => { + await expect(System.run('true')).resolves.not.toBeNull(); + }); - it('throws when no arguments are given', async () => { - await expect(System.run('')).rejects.toThrowError(); - }); + it('outputs results', async () => { + await expect(System.run('echo test')).resolves.toStrictEqual('test\n'); + }); - it('outputs info', async () => { - await expect(System.run('echo test')).resolves.not.toBeNull(); - expect(info).toHaveBeenLastCalledWith('test\n'); - }); + it('throws on when error code is not 0', async () => { + await expect(System.run('false')).rejects.toThrowError(); + }); - it('outputs info only once', async () => { - await expect(System.run('echo 1')).resolves.not.toBeNull(); - expect(info).toHaveBeenCalledTimes(1); - expect(info).toHaveBeenLastCalledWith('1\n'); - - info.mockClear(); - await expect(System.run('echo 2')).resolves.not.toBeNull(); - await expect(System.run('echo 3')).resolves.not.toBeNull(); - expect(info).toHaveBeenCalledTimes(2); - expect(info).toHaveBeenLastCalledWith('3\n'); - }); - - it('allows pipes using buffer', async () => { - await expect( - System.run('sh', undefined, { - input: Buffer.from('git tag --list --merged HEAD | grep v[0-9]* | wc -l'), - // eslint-disable-next-line github/no-then - }).then((result) => Number(result)), - ).resolves.not.toBeNaN(); - }); + it('allows pipes using buffer', async () => { + await expect( + System.run('sh', undefined, { + input: Buffer.from('git tag --list --merged HEAD | grep v[0-9]* | wc -l'), + // eslint-disable-next-line github/no-then + }).then((result) => Number(result)), + ).resolves.not.toBeNaN(); + }); + }); + } }); }); diff --git a/src/model/system.ts b/src/model/system.ts index c9f0c8bf..9f1cb676 100644 --- a/src/model/system.ts +++ b/src/model/system.ts @@ -45,6 +45,10 @@ class System { }; try { + if (command.trim() === '') { + throw new Error(`Failed to execute empty command`); + } + const exitCode = await exec(command, arguments_, { silent: true, listeners, ...options }); showOutput(); if (exitCode !== 0) { diff --git a/yarn.lock b/yarn.lock index 657f21d5..d04d4f26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3941,9 +3941,9 @@ lines-and-columns@^1.1.6: integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= lint-staged@^12.3.4: - version "12.3.4" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.3.4.tgz#4b1ff8c394c3e6da436aaec5afd4db18b5dac360" - integrity sha512-yv/iK4WwZ7/v0GtVkNb3R82pdL9M+ScpIbJLJNyCXkJ1FGaXvRCOg/SeL59SZtPpqZhE7BD6kPKFLIDUhDx2/w== + version "12.3.7" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.3.7.tgz#ad0e2014302f704f9cf2c0ebdb97ac63d0f17be0" + integrity sha512-/S4D726e2GIsDVWIk1XGvheCaDm1SJRQp8efamZFWJxQMVEbOwSysp7xb49Oo73KYCdy97mIWinhlxcoNqIfIQ== dependencies: cli-truncate "^3.1.0" colorette "^2.0.16" @@ -3955,6 +3955,7 @@ lint-staged@^12.3.4: micromatch "^4.0.4" normalize-path "^3.0.0" object-inspect "^1.12.0" + pidtree "^0.5.0" string-argv "^0.3.1" supports-color "^9.2.1" yaml "^1.10.2" @@ -4498,6 +4499,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz" integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== +pidtree@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.5.0.tgz#ad5fbc1de78b8a5f99d6fbdd4f6e4eee21d1aca1" + integrity sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA== + pify@^2.0.0: version "2.3.0" resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"