From 982e0a20af25b056f560085aa160180be7e5031b Mon Sep 17 00:00:00 2001 From: Rob Herley Date: Sun, 7 Apr 2024 17:44:23 -0400 Subject: [PATCH] add omit input, deprecate others --- __tests__/helpers.ts | 3 +- __tests__/inputs.test.ts | 83 +++++++++++++++++++++++++++ __tests__/renderer.test.ts | 60 +++++++++++++++----- __tests__/runner.test.ts | 26 --------- src/inputs.ts | 112 +++++++++++++++++++++++++++++++++++++ src/renderer.ts | 58 +++++++++++-------- src/runner.ts | 75 +++++-------------------- 7 files changed, 293 insertions(+), 124 deletions(-) create mode 100644 __tests__/inputs.test.ts create mode 100644 src/inputs.ts diff --git a/__tests__/helpers.ts b/__tests__/helpers.ts index 113e1f0..3fa89ff 100644 --- a/__tests__/helpers.ts +++ b/__tests__/helpers.ts @@ -35,8 +35,7 @@ export const removeSummaryFile = async () => { export const setupActionsInputs = () => { process.env['INPUT_MODULEDIRECTORY'] = testModuleDirectory process.env['INPUT_TESTARGUMENTS'] = testArguments - process.env['INPUT_OMITUNTESTEDPACKAGES'] = 'false' - process.env['INPUT_OMITPIE'] = 'false' + process.env['INPUT_OMIT'] = '' } export const createFakeGoModule = async () => { diff --git a/__tests__/inputs.test.ts b/__tests__/inputs.test.ts new file mode 100644 index 0000000..3bd03b2 --- /dev/null +++ b/__tests__/inputs.test.ts @@ -0,0 +1,83 @@ +import * as core from '@actions/core' +import { OmitOption, getInputs } from '../src/inputs' + +jest.mock('@actions/core') + +const mockGetInput = core.getInput as jest.MockedFunction +const mockGetBooleanInput = core.getBooleanInput as jest.MockedFunction< + typeof core.getBooleanInput +> + +const mockInput = (name: string, value: string) => { + mockGetInput.mockImplementation((n: string) => (n === name ? value : '')) +} + +describe('renderer', () => { + beforeEach(() => { + jest.resetAllMocks() + }) + + it('uses default values', () => { + mockGetInput.mockReturnValue('') + const inputs = getInputs() + + expect(inputs).toEqual({ + moduleDirectory: '.', + testArguments: ['./...'], + fromJSONFile: null, + omit: new Set(), + }) + }) + + it('parses moduleDirectory', () => { + mockInput('moduleDirectory', 'foo') + const inputs = getInputs() + + expect(inputs.moduleDirectory).toEqual('foo') + }) + + it('parses testArguments', () => { + mockInput('testArguments', 'foo bar') + const inputs = getInputs() + + expect(inputs.testArguments).toEqual(['foo', 'bar']) + }) + + it('parses fromJSONFile', () => { + mockInput('fromJSONFile', 'foo.json') + const inputs = getInputs() + + expect(inputs.fromJSONFile).toEqual('foo.json') + }) + + it('parses omit', () => { + mockInput( + 'omit', + [...Object.values(OmitOption), 'foo', 'bar', 'baz'].join('\n') + ) + const inputs = getInputs() + + expect(inputs.omit).toEqual(new Set(Object.values(OmitOption))) + }) + + it('supports deprecated inputs', () => { + mockGetInput.mockImplementation((name: string) => { + switch (name) { + case 'omitUntestedPackages': + case 'omitSuccessfulPackages': + case 'omitPie': + return 'true' + default: + return '' + } + }) + + mockGetBooleanInput.mockReturnValue(true) + + const inputs = getInputs() + expect(inputs.omit).toEqual( + new Set([OmitOption.Skipped, OmitOption.Successful, OmitOption.Pie]) + ) + expect(core.warning).toHaveBeenCalled() + }) +}) diff --git a/__tests__/renderer.test.ts b/__tests__/renderer.test.ts index c564e61..cafdfe7 100644 --- a/__tests__/renderer.test.ts +++ b/__tests__/renderer.test.ts @@ -12,6 +12,7 @@ import { import { parseTestEvents } from '../src/events' import Renderer from '../src/renderer' import { SummaryTableCell } from '@actions/core/lib/summary' +import { OmitOption } from '../src/inputs' const loadSummaryHTML = async (): Promise => { const file = await fs.readFile(testSummaryFilePath, { encoding: 'utf8' }) @@ -26,9 +27,7 @@ const getRenderer = async (): Promise => { 'github.com/robherley/go-test-example', testEvents, '', // stderr - false, // omitUntestedPackages - false, // omitSuccessfulPackages - false // omitPie + new Set() ) } @@ -105,9 +104,7 @@ describe('renderer', () => { 'github.com/robherley/empty-module', [], '', - false, - false, - false + new Set() ) await renderer.writeSummary() @@ -169,9 +166,9 @@ describe('renderer', () => { expect($.text()).toContain(pieData) }) - it('does not render pie when omitPie is true', async () => { + it('does not render pie when pie in omit', async () => { const renderer = await getRenderer() - renderer.omitPie = true + renderer.omit.add(OmitOption.Pie) await renderer.writeSummary() const $ = await loadSummaryHTML() @@ -201,22 +198,22 @@ describe('renderer', () => { }) }) - it('renders correct number of table rows when omitUntestedPackages is true', async () => { + it('renders correct number of table rows when skipped is in omit', async () => { const renderer = await getRenderer() - renderer.omitUntestedPackages = true + renderer.omit.add(OmitOption.Skipped) await renderer.writeSummary() const $ = await loadSummaryHTML() expect($('tr')).toHaveLength(7) }) - it('renders correct number of table rows when omitUntestedPackages is true', async () => { + it('renders correct number of table rows when successful is in omit', async () => { const renderer = await getRenderer() - renderer.omitUntestedPackages = true + renderer.omit.add(OmitOption.Successful) await renderer.writeSummary() const $ = await loadSummaryHTML() - expect($('tr')).toHaveLength(7) + expect($('tr')).toHaveLength(5) }) it('does not render stderr when empty', async () => { @@ -237,4 +234,41 @@ describe('renderer', () => { expect($('summary:contains(Standard Error Output)')).toHaveLength(1) expect($('details:contains(hello world)')).toHaveLength(1) }) + + it('does not render stderr when in omit', async () => { + const renderer = await getRenderer() + renderer.omit.add(OmitOption.Stderr) + renderer.stderr = 'i should not be rendered' + await renderer.writeSummary() + const $ = await loadSummaryHTML() + + expect($('summary:contains(Standard Error Output)')).toHaveLength(0) + }) + + it('renders package test and output list', async () => { + const renderer = await getRenderer() + await renderer.writeSummary() + const $ = await loadSummaryHTML() + + expect($('summary:contains(🧪 Tests)')).toHaveLength(4) + expect($('summary:contains(🖨️ Output)')).toHaveLength(4) + }) + + it('does not render package test list when in omit', async () => { + const renderer = await getRenderer() + renderer.omit.add(OmitOption.PackageTests) + await renderer.writeSummary() + const $ = await loadSummaryHTML() + + expect($('summary:contains(🧪 Tests)')).toHaveLength(0) + }) + + it('does not render package output list when in omit', async () => { + const renderer = await getRenderer() + renderer.omit.add(OmitOption.PackageOutput) + await renderer.writeSummary() + const $ = await loadSummaryHTML() + + expect($('summary:contains(🖨️ Output)')).toHaveLength(0) + }) }) diff --git a/__tests__/runner.test.ts b/__tests__/runner.test.ts index 741e2ae..3754e5c 100644 --- a/__tests__/runner.test.ts +++ b/__tests__/runner.test.ts @@ -23,32 +23,6 @@ describe('runner', () => { setupActionsInputs() }) - it("sets defaults if inputs aren't set", async () => { - delete process.env['INPUT_MODULEDIRECTORY'] - delete process.env['INPUT_TESTARGUMENTS'] - delete process.env['INPUT_OMITUNTESTEDPACKAGES'] - delete process.env['INPUT_OMITPIE'] - - const runner = new Runner() - expect(runner.moduleDirectory).toBe('.') - expect(runner.testArguments).toEqual(['./...']) - expect(runner.omitUntestedPackages).toEqual(false) - expect(runner.omitPie).toEqual(false) - }) - - it('uses inputs if they are set', async () => { - process.env['INPUT_MODULEDIRECTORY'] = '/some/random/directory' - process.env['INPUT_TESTARGUMENTS'] = '-foo -bar\t-baz' - process.env['INPUT_OMITUNTESTEDPACKAGES'] = 'true' - process.env['INPUT_OMITPIE'] = 'true' - - const runner = new Runner() - expect(runner.moduleDirectory).toBe('/some/random/directory') - expect(runner.testArguments).toEqual(['-foo', '-bar', '-baz']) - expect(runner.omitUntestedPackages).toEqual(true) - expect(runner.omitPie).toEqual(true) - }) - it('resolves module name from go.mod', async () => { const runner = new Runner() const modName = await runner.findModuleName() diff --git a/src/inputs.ts b/src/inputs.ts new file mode 100644 index 0000000..692a3f1 --- /dev/null +++ b/src/inputs.ts @@ -0,0 +1,112 @@ +import * as core from '@actions/core' + +export interface Inputs { + moduleDirectory: string + testArguments: string[] + fromJSONFile: string | null + omit: Set +} + +export enum OmitOption { + // Omit untested packages from the summary + Skipped = 'skipped', + // Omit successful packages from the summary + Successful = 'successful', + // Omit the pie chart from the summary + Pie = 'pie', + // Omit the package test output + PackageOutput = 'pkg-output', + // Omit the package test list + PackageTests = 'pkg-tests', + // Omit stderr + Stderr = 'stderr', +} + +export const defaultInputs = (): Inputs => ({ + moduleDirectory: '.', + testArguments: ['./...'], + fromJSONFile: null, + omit: new Set(), +}) + +/** + * Parses the action inputs from the environment + * @returns the parsed inputs + */ +export function getInputs(): Inputs { + const inputs = defaultInputs() + + getDeprecatedOmitInputs().forEach(option => { + inputs.omit.add(option) + }) + + const moduleDirectory = core.getInput('moduleDirectory') + if (moduleDirectory) { + inputs.moduleDirectory = moduleDirectory + } + + const testArguments = core.getInput('testArguments') + if (testArguments) { + inputs.testArguments = testArguments.split(/\s/).filter(arg => arg.length) + } + + const fromJSONFile = core.getInput('fromJSONFile') + if (fromJSONFile) { + inputs.fromJSONFile = fromJSONFile + } + + const omit = core.getInput('omit') + if (omit) { + omit + .split(/\s/) + .filter(option => + Object.values(OmitOption).includes(option as OmitOption) + ) + .forEach(option => inputs.omit.add(option as OmitOption)) + } + + return inputs +} + +/** + * Parses the deprecated omit inputs + * @returns the parsed omit options + */ +function getDeprecatedOmitInputs(): OmitOption[] { + const omitOptions: OmitOption[] = [] + const usedDeprecated: string[] = [] + + const omitUntestedPackages = core.getInput('omitUntestedPackages') + if (omitUntestedPackages) { + usedDeprecated.push('omitUntestedPackages') + if (core.getBooleanInput('omitUntestedPackages')) { + omitOptions.push(OmitOption.Skipped) + } + } + + const omitSuccessfulPackages = core.getInput('omitSuccessfulPackages') + if (omitSuccessfulPackages) { + usedDeprecated.push('omitSuccessfulPackages') + if (core.getBooleanInput('omitSuccessfulPackages')) { + omitOptions.push(OmitOption.Successful) + } + } + + const omitPie = core.getInput('omitPie') + if (omitPie) { + usedDeprecated.push('omitPie') + if (core.getBooleanInput('omitPie')) { + omitOptions.push(OmitOption.Pie) + } + } + + if (usedDeprecated.length > 0) { + core.warning( + `The following inputs are deprecated and will be removed in the next major version: ${Array.from( + usedDeprecated + ).join(', ')}. Please use the \`omit\` input instead.` + ) + } + + return omitOptions +} diff --git a/src/renderer.ts b/src/renderer.ts index c7e5cb4..4cb372d 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -3,6 +3,7 @@ import type { SummaryTableRow } from '@actions/core/lib/summary' import type { ConclusionResults } from './results' import PackageResult from './results' +import { OmitOption } from './inputs' import type { TestEvent, @@ -15,9 +16,7 @@ class Renderer { moduleName: string | null testEvents: TestEvent[] stderr: string - omitUntestedPackages: boolean - omitSuccessfulPackages: boolean - omitPie: boolean + omit: Set packageResults: PackageResult[] headers: SummaryTableRow = [ { data: '📦 Package', header: true }, @@ -36,16 +35,12 @@ class Renderer { moduleName: string | null, testEvents: TestEvent[], stderr: string, - omitUntestedPackages: boolean, - omitSuccessfulPackages: boolean, - omitPie: boolean + omit: Set ) { this.moduleName = moduleName this.testEvents = testEvents this.stderr = stderr - this.omitUntestedPackages = omitUntestedPackages - this.omitSuccessfulPackages = omitSuccessfulPackages - this.omitPie = omitPie + this.omit = omit this.packageResults = this.calculatePackageResults() } @@ -55,9 +50,13 @@ class Renderer { */ async writeSummary() { const resultsToRender = this.packageResults - .filter(result => (this.omitUntestedPackages ? result.hasTests() : true)) .filter(result => - this.omitSuccessfulPackages ? result.justSuccessfulTests() : true + this.omit.has(OmitOption.Skipped) ? result.hasTests() : true + ) + .filter(result => + this.omit.has(OmitOption.Successful) + ? result.justSuccessfulTests() + : true ) if (resultsToRender.length === 0) { @@ -182,13 +181,19 @@ class Renderer { testList += '' } - const detailsForOutput = `
🖨️ Output
${
-      packageResult.output() || '(none)'
-    }
` + let details = '' - const detailsForTests = `
🧪 Tests${ - testList || '(none)' - }
` + if (!this.omit.has(OmitOption.PackageTests)) { + details += `
🧪 Tests${ + testList || '(none)' + }
` + } + + if (!this.omit.has(OmitOption.PackageOutput)) { + details += `
🖨️ Output
${
+        packageResult.output() || '(none)'
+      }
` + } const pkgName = `${this.emojiFor( packageResult.packageEvent.action @@ -196,7 +201,7 @@ class Renderer { packageResult.packageEvent.package === this.moduleName ? ' (main)' : '' }` - return [ + const packageRows: SummaryTableRow[] = [ [ pkgName, packageResult.conclusions.pass.toString(), @@ -204,8 +209,13 @@ class Renderer { packageResult.conclusions.skip.toString(), `${(packageResult.packageEvent.elapsed || 0) * 1000}ms`, ], - [{ data: detailsForTests + detailsForOutput, colspan: '5' }], ] + + if (details) { + packageRows.push([{ data: details, colspan: '5' }]) + } + + return packageRows } /** @@ -213,7 +223,7 @@ class Renderer { * @returns stringified markdown for mermaid.js pie chart */ private renderPie(): string { - if (this.omitPie) { + if (this.omit.has(OmitOption.Pie)) { return '

' // just return double break instead } @@ -257,8 +267,11 @@ ${pieData} } private renderStderr(): string { - return this.stderr - ? `
+ if (this.omit.has(OmitOption.Stderr) || !this.stderr) { + return '' + } + + return `
🚨 Standard Error Output \`\`\` @@ -266,7 +279,6 @@ ${this.stderr} \`\`\`
` - : '' } } diff --git a/src/runner.ts b/src/runner.ts index a1c820d..2b72573 100644 --- a/src/runner.ts +++ b/src/runner.ts @@ -5,19 +5,14 @@ import * as core from '@actions/core' import { exec } from '@actions/exec' import Renderer from './renderer' - import { parseTestEvents } from './events' +import { Inputs, getInputs } from './inputs' class Runner { - moduleDirectory = '.' - testArguments = ['./...'] - omitUntestedPackages = false - omitSuccessfulPackages = false - omitPie = false - fromJSONFile: string | null = null + inputs: Inputs constructor() { - this.getInputs() + this.inputs = getInputs() } /** @@ -26,21 +21,18 @@ class Runner { async run() { const moduleName = await this.findModuleName() - if (this.fromJSONFile) { - const stdout = await readFile(this.fromJSONFile) + if (this.inputs.fromJSONFile) { + const stdout = await readFile(this.inputs.fromJSONFile) const testEvents = parseTestEvents(stdout.toString()) const renderer = new Renderer( moduleName, testEvents, '', - this.omitUntestedPackages, - this.omitSuccessfulPackages, - this.omitPie + this.inputs.omit ) - + await renderer.writeSummary() - process.exit(0) } else { const { retCode, stdout, stderr } = await this.goTest() @@ -54,13 +46,10 @@ class Runner { moduleName, testEvents, stderr, - this.omitUntestedPackages, - this.omitSuccessfulPackages, - this.omitPie + this.inputs.omit ) - + await renderer.writeSummary() - process.exit(retCode) } } @@ -70,7 +59,10 @@ class Runner { * @returns go module name */ async findModuleName(): Promise { - const modulePath = path.join(path.resolve(this.moduleDirectory), 'go.mod') + const modulePath = path.join( + path.resolve(this.inputs.moduleDirectory), + 'go.mod' + ) try { const contents = await readFile(modulePath) @@ -98,7 +90,7 @@ class Runner { let stderr = '' const opts = { - cwd: this.moduleDirectory, + cwd: this.inputs.moduleDirectory, ignoreReturnCode: true, listeners: { stdout: (data: Buffer) => { @@ -112,7 +104,7 @@ class Runner { const retCode = await exec( 'go', - ['test', '-json', ...this.testArguments], + ['test', '-json', ...this.inputs.testArguments], opts ) @@ -122,43 +114,6 @@ class Runner { stderr, } } - - /** - * Parses GitHub Actions inputs from environment - */ - private getInputs() { - const moduleDirectory = core.getInput('moduleDirectory') - if (moduleDirectory) { - this.moduleDirectory = moduleDirectory - } - - const testArguments = core.getInput('testArguments') - if (testArguments) { - this.testArguments = testArguments.split(/\s/).filter(arg => arg.length) - } - - const omitUntestedPackages = core.getInput('omitUntestedPackages') - if (omitUntestedPackages) { - this.omitUntestedPackages = core.getBooleanInput('omitUntestedPackages') - } - - const omitSuccessfulPackages = core.getInput('omitSuccessfulPackages') - if (omitSuccessfulPackages) { - this.omitSuccessfulPackages = core.getBooleanInput( - 'omitSuccessfulPackages' - ) - } - - const omitPie = core.getInput('omitPie') - if (omitPie) { - this.omitPie = core.getBooleanInput('omitPie') - } - - const fromJSONFile = core.getInput('fromJSONFile') - if (fromJSONFile) { - this.fromJSONFile = fromJSONFile - } - } } export default Runner