From aac014876fc1173a312e7ce9c77b5a8c0fe7ccc8 Mon Sep 17 00:00:00 2001 From: Graham Knop Date: Sat, 14 Sep 2024 08:36:12 +0200 Subject: [PATCH] add test of action directly, mocking @actions/core --- action.yml | 2 +- dist/{main.js => action.js} | 47 ++++++++++++++++++-------------- package-lock.json | 11 ++++++++ package.json | 3 ++- src/action.mjs | 3 +++ src/main.mjs | 21 +++++++++------ test/action.test.mjs | 53 +++++++++++++++++++++++++++++++++++++ 7 files changed, 110 insertions(+), 30 deletions(-) rename dist/{main.js => action.js} (99%) create mode 100644 src/action.mjs create mode 100644 test/action.test.mjs diff --git a/action.yml b/action.yml index c6ff610..223e1cc 100644 --- a/action.yml +++ b/action.yml @@ -34,4 +34,4 @@ outputs: runs: using: 'node20' - main: 'dist/main.js' + main: 'dist/action.js' diff --git a/dist/main.js b/dist/action.js similarity index 99% rename from dist/main.js rename to dist/action.js index 157f06b..16f4b38 100644 --- a/dist/main.js +++ b/dist/action.js @@ -18862,7 +18862,7 @@ var require_core = __commonJS({ process.env["PATH"] = `${inputPath}${path.delimiter}${process.env["PATH"]}`; } exports2.addPath = addPath; - function getInput2(name, options) { + function getInput(name, options) { const val = process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || ""; if (options && options.required && !val) { throw new Error(`Input required and not supplied: ${name}`); @@ -18872,9 +18872,9 @@ var require_core = __commonJS({ } return val.trim(); } - exports2.getInput = getInput2; + exports2.getInput = getInput; function getMultilineInput(name, options) { - const inputs = getInput2(name, options).split("\n").filter((x) => x !== ""); + const inputs = getInput(name, options).split("\n").filter((x) => x !== ""); if (options && options.trimWhitespace === false) { return inputs; } @@ -18884,7 +18884,7 @@ var require_core = __commonJS({ function getBooleanInput(name, options) { const trueValue = ["true", "True", "TRUE"]; const falseValue = ["false", "False", "FALSE"]; - const val = getInput2(name, options); + const val = getInput(name, options); if (trueValue.includes(val)) return true; if (falseValue.includes(val)) @@ -18893,7 +18893,7 @@ var require_core = __commonJS({ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); } exports2.getBooleanInput = getBooleanInput; - function setOutput2(name, value) { + function setOutput(name, value) { const filePath = process.env["GITHUB_OUTPUT"] || ""; if (filePath) { return file_command_1.issueFileCommand("OUTPUT", file_command_1.prepareKeyValueMessage(name, value)); @@ -18901,16 +18901,16 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); process.stdout.write(os.EOL); command_1.issueCommand("set-output", { name }, utils_1.toCommandValue(value)); } - exports2.setOutput = setOutput2; + exports2.setOutput = setOutput; function setCommandEcho(enabled) { command_1.issue("echo", enabled ? "on" : "off"); } exports2.setCommandEcho = setCommandEcho; - function setFailed2(message) { + function setFailed(message) { process.exitCode = ExitCode.Failure; error(message); } - exports2.setFailed = setFailed2; + exports2.setFailed = setFailed; function isDebug() { return process.env["RUNNER_DEBUG"] === "1"; } @@ -18996,7 +18996,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); }); // src/main.mjs -var core2 = __toESM(require_core(), 1); +var import_core = __toESM(require_core(), 1); // src/get-prereqs.mjs var import_promises = __toESM(require("node:fs/promises"), 1); @@ -25394,10 +25394,14 @@ var getPrereqs = async ({ // src/main.mjs var run = async () => { - const phases = core2.getInput("phases").split(/\s+/); - const relationships = core2.getInput("relationships").split(/\s+/); - const features = core2.getInput("features").split(/\s+/); - const sources = core2.getInput("sources").split(/\s+/); + const phasesInput = import_core.default.getInput("phases"); + const relationshipsInput = import_core.default.getInput("relationships"); + const featuresInput = import_core.default.getInput("features"); + const sourcesInput = import_core.default.getInput("sources"); + const phases = new Set(phasesInput.split(/\s+/)); + const relationships = new Set(relationshipsInput.split(/\s+/)); + const features = new Set(featuresInput.split(/\s+/)); + const sources = sourcesInput.split(/\s+/); const { perl, ...prereqs } = await getPrereqs({ phases, relationships, @@ -25405,23 +25409,26 @@ var run = async () => { sources }); if (perl) { - core2.setOutput("perl", dottedVersion(perl)); + import_core.default.setOutput("perl", dottedVersion(perl)); } const prereqString = Object.keys(prereqs).map((module2) => `${module2} `).join(""); const prereqVersionString = Object.entries(prereqs).map(([module2, version2]) => `${module2}${cpanmVersion(version2)} `).join(""); - core2.setOutput("prereqs", prereqVersionString); - core2.setOutput("prereqs-no-version", prereqString); - core2.setOutput("prereqsJSON", JSON.stringify(prereqs)); + import_core.default.setOutput("prereqs", prereqVersionString); + import_core.default.setOutput("prereqs-no-version", prereqString); + import_core.default.setOutput("prereqsJSON", JSON.stringify(prereqs)); }; -(async () => { +var main_default = async () => { try { await run(); } catch (error) { - core2.setFailed(error.message); + import_core.default.setFailed(error.message); } -})(); +}; + +// src/action.mjs +main_default(); /*! Bundled license information: undici/lib/fetch/body.js: diff --git a/package-lock.json b/package-lock.json index 71ff3b0..130aace 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "esbuild": "^0.23.1", "eslint": "^9.9.1", "eslint-plugin-mocha": "^10.5.0", + "esmock": "^2.6.7", "globals": "^15.9.0", "mocha": "^10.7.3", "peggy": "^4.0.3" @@ -1548,6 +1549,16 @@ "node": "*" } }, + "node_modules/esmock": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/esmock/-/esmock-2.6.7.tgz", + "integrity": "sha512-4DmjZ0qQIG+NQV1njHvWrua/cZEuJq56A3pSELT2BjNuol1aads7BluofCbLErdO41Ic1XCd2UMepVLpjL64YQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14.16.0" + } + }, "node_modules/espree": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", diff --git a/package.json b/package.json index 38f76a5..61fc9fa 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "build-peg": "npm run build-peg:ini && npm run build-peg:cpanfile", "build-peg:cpanfile": "peggy src/parser/cpanfile-peg.pegjs --format es -o src/parser/cpanfile-peg.mjs", "build-peg:ini": "peggy src/parser/ini-peg.pegjs --format es -o src/parser/ini-peg.mjs", - "build": "npm run build-peg && esbuild src/main.mjs --outdir=dist/ --bundle --platform=node --target=node20" + "build": "npm run build-peg && esbuild src/action.mjs --outdir=dist/ --bundle --platform=node --target=node20" }, "repository": { "type": "git", @@ -34,6 +34,7 @@ "esbuild": "^0.23.1", "eslint": "^9.9.1", "eslint-plugin-mocha": "^10.5.0", + "esmock": "^2.6.7", "globals": "^15.9.0", "mocha": "^10.7.3", "peggy": "^4.0.3" diff --git a/src/action.mjs b/src/action.mjs new file mode 100644 index 0000000..6c4a38e --- /dev/null +++ b/src/action.mjs @@ -0,0 +1,3 @@ +import main from './main.mjs'; + +main(); diff --git a/src/main.mjs b/src/main.mjs index ca2ca27..1e78e29 100644 --- a/src/main.mjs +++ b/src/main.mjs @@ -1,12 +1,17 @@ -import * as core from '@actions/core'; +import core from '@actions/core'; import { getPrereqs } from './get-prereqs.mjs'; import { cpanmVersion, dottedVersion } from './cpan-versions.mjs'; -const run = async () => { - const phases = core.getInput('phases').split(/\s+/); - const relationships = core.getInput('relationships').split(/\s+/); - const features = core.getInput('features').split(/\s+/); - const sources = core.getInput('sources').split(/\s+/); +export const run = async () => { + const phasesInput = core.getInput('phases'); + const relationshipsInput = core.getInput('relationships'); + const featuresInput = core.getInput('features'); + const sourcesInput = core.getInput('sources'); + + const phases = new Set(phasesInput.split(/\s+/)); + const relationships = new Set(relationshipsInput.split(/\s+/)); + const features = new Set(featuresInput.split(/\s+/)); + const sources = sourcesInput.split(/\s+/); const { perl, ...prereqs } = await getPrereqs({ phases, @@ -31,11 +36,11 @@ const run = async () => { core.setOutput('prereqsJSON', JSON.stringify(prereqs)); }; -(async () => { +export default async () => { try { await run(); } catch (error) { core.setFailed(error.message); } -})(); +}; diff --git a/test/action.test.mjs b/test/action.test.mjs new file mode 100644 index 0000000..79dfc05 --- /dev/null +++ b/test/action.test.mjs @@ -0,0 +1,53 @@ +import { describe, it } from 'mocha'; +import { expect } from 'chai'; + +import { dirname, join as joinPath } from 'node:path'; +import { fileURLToPath } from 'url'; + +import esmock from 'esmock'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +const mockAction = async (module) => { + let outputs; + let inputs; + let failed; + const main = await esmock(module, { + '@actions/core': { + getInput: key => (inputs[key] || '').trim(), + getMultilineInput: key => (inputs[key] || '').split(/\n/).map(t => t.trim()), + setOutput: (key, value) => { outputs[key] = value; }, + setFailed: (message) => { failed = message; }, + }, + }); + return async (input) => { + outputs = {}; + inputs = input; + failed = undefined; + await main(); + if (failed) { + throw new Error(failed); + } + return { + outputs, + }; + }; +}; + +const main = await mockAction('../src/main.mjs'); + +describe('GitHub action', function () { + it('runs', async function () { + const { outputs } = await main({ + sources: joinPath(__dirname, 'corpus', 'META.json'), + phases: 'test', + relationships: 'recommends', + features: '', + }); + expect(outputs).to.be.deep.equal({ + 'prereqs': 'CPAN::Meta~2.120900\n', + 'prereqs-no-version': 'CPAN::Meta\n', + 'prereqsJSON': '{"CPAN::Meta":">=2.120900"}', + }); + }); +});