diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7e35396..4b4af99 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,7 @@ jobs: - run: npm test - uses: codecov/codecov-action@v4 + if: failure() || success() with: files: coverage/clover.xml,coverage/lcov.info flags: unittests diff --git a/__test__/pi-gen.test.ts b/__test__/pi-gen.test.ts index 1ce09c9..af1009c 100644 --- a/__test__/pi-gen.test.ts +++ b/__test__/pi-gen.test.ts @@ -221,17 +221,17 @@ describe('PiGen', () => { ) it.each([ - [false, 'no stage message', 'info', 0], - [true, 'no stage message', 'info', 1], - [false, '[00:00:00] stage message', 'info', 1], - [true, 'warning message', 'warning', 1], - [false, '#6 [1/3] FROM docker.io', 'warning', 0], - [true, '#7 [2/3] RUN', 'warning', 0], - [false, ' #6 [1/3] FROM docker.io', 'info', 0], - [true, ' #7 [2/3] RUN', 'info', 1] + ['no stage message', false, 'info', 0], + ['no stage message', true, 'info', 1], + ['[00:00:00] stage message', false, 'info', 0], + ['warning message', true, 'warning', 1], + ['#6 [1/3] FROM docker.io', false, 'warning', 0], + ['#7 [2/3] RUN', true, 'warning', 0], + [' #6 [1/3] FROM docker.io', false, 'info', 0], + [' #7 [2/3] RUN', true, 'info', 1] ])( - 'handles log messages if verbose = %s', - (verbose, line, stream, nCalls) => { + 'handles log message "%s" correctly if verbose = %s', + (line, verbose, stream, nCalls) => { jest.spyOn(core, 'info').mockImplementation(s => {}) jest.spyOn(core, 'warning').mockImplementation(s => {}) mockPiGenDependencies() @@ -248,6 +248,52 @@ describe('PiGen', () => { } ) + it.each([ + [['no stage message'], 0, 0, null], + [['[00:00:00] Begin stage-name'], 1, 0, ['stage-name']], + [['[00:00:00] End stage-name'], 0, 1, null] + ])( + 'opens and closes log groups according to pi-gen status messages', + (lines, startGroupCalls, endGroupCalls, startGroupValues) => { + jest.spyOn(core, 'startGroup').mockImplementation(_ => {}) + jest.spyOn(core, 'endGroup').mockImplementation(() => null) + mockPiGenDependencies() + + const piGenSut = new PiGen('pi-gen', { + ...DEFAULT_CONFIG, + stageList: ['stage0'] + }) + lines.forEach(line => piGenSut.logOutput(line, false, 'info')) + + if (startGroupCalls > 0) { + expect(core.startGroup).toHaveBeenCalledTimes(startGroupCalls) + startGroupValues?.forEach(groupName => + expect(core.startGroup).toHaveBeenCalledWith(groupName) + ) + } + + if (endGroupCalls > 0) { + expect(core.endGroup).toHaveBeenCalledTimes(endGroupCalls) + } + } + ) + + it('closes still open log groups if build crashes', async () => { + jest.spyOn(fs, 'realpathSync').mockReturnValue('/pi-gen/stage0') + jest.spyOn(core, 'endGroup') + jest + .spyOn(exec, 'getExecOutput') + .mockImplementationOnce((cmdLine, args) => + Promise.resolve({} as exec.ExecOutput) + ) + + const piGen = new PiGen('', {...DEFAULT_CONFIG, stageList: ['stage0']}) + piGen.openLogGroups = 2 + await piGen.build() + + expect(core.endGroup).toHaveBeenCalledTimes(2) + }) + it.each([ ['none', [] as string[], undefined], ['xz', ['foo.img.xz', 'bar.img.xz'], 'foo.img.xz'] diff --git a/src/pi-gen-config.ts b/src/pi-gen-config.ts index bc4e04a..a29b4d8 100644 --- a/src/pi-gen-config.ts +++ b/src/pi-gen-config.ts @@ -67,8 +67,12 @@ export async function writeToFile( : config[prop as keyof PiGenConfig] }"` ) - .join('\n') - return fs.writeFile(file, configContent) + + // We're adding this as a default to the config so that it's going to be + // picked up by pi-gen as well in order to reduce log volume + configContent.push('DEBIAN_FRONTEND=noninteractive') + + return fs.writeFile(file, configContent.join('\n')) } export async function validateConfig(config: PiGenConfig): Promise { diff --git a/src/pi-gen.ts b/src/pi-gen.ts index a5c1d3d..2ea7324 100644 --- a/src/pi-gen.ts +++ b/src/pi-gen.ts @@ -5,11 +5,11 @@ import * as glob from '@actions/glob' import {PiGenStages} from './pi-gen-stages' import {PiGenConfig, writeToFile} from './pi-gen-config' import path from 'path' -import * as colors from 'ansi-colors' export class PiGen { private configFilePath: string private piGenBuildLogPattern = /^\s*\[(?:\d{2}:?){3}\]/gm + openLogGroups: number = 0 constructor( private piGenDirectory: string, @@ -66,10 +66,8 @@ export class PiGen { )}` ) - return await exec.getExecOutput( - '"./build-docker.sh"', - ['-c', this.configFilePath], - { + return await exec + .getExecOutput('"./build-docker.sh"', ['-c', this.configFilePath], { cwd: this.piGenDirectory, env: { PIGEN_DOCKER_OPTS: dockerOpts, @@ -81,8 +79,13 @@ export class PiGen { }, silent: true, ignoreReturnCode: true - } - ) + }) + .finally(() => { + while (this.openLogGroups > 0) { + core.endGroup() + this.openLogGroups-- + } + }) } async getLastImagePath(): Promise { @@ -171,9 +174,23 @@ export class PiGen { logOutput(line: string, verbose: boolean, stream: 'info' | 'warning'): void { const isPiGenStatusMessage = this.piGenBuildLogPattern.test(line) - if (verbose || isPiGenStatusMessage) { - line = isPiGenStatusMessage ? colors.bold(colors.unstyle(line)) : line - + if (isPiGenStatusMessage) { + line = line + .replace(this.piGenBuildLogPattern, '') + .replace(`${process.env.GITHUB_WORKSPACE || ''}/`, '') + .replace(`${this.piGenDirectory}/`, '') + .trim() + + if (line.includes('Begin')) { + line = line.replace('Begin', '').trim() + this.openLogGroups++ + core.startGroup(line) + } else if (line.includes('End')) { + line = line.replace('End', '').trim() + this.openLogGroups-- + core.endGroup() + } + } else if (stream == 'warning' || verbose) { // Do not issue warning annotations for Docker BuildKit progress messages. // No clue how to better suppress/redirect them for now. stream === 'info' || line.match(/^\s*#\d+\s/m)