From a130b4c4c22ccb09dfbd3cef2df5a8f97478abd8 Mon Sep 17 00:00:00 2001 From: Ryan Ling Date: Wed, 17 Nov 2021 08:29:57 +1100 Subject: [PATCH 1/2] Report on test coverage --- src/cli/test/reporters/coverage.ts | 79 ++++++++++++++++++++++++++ src/cli/test/reporters/github/index.ts | 25 +++++++- 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 src/cli/test/reporters/coverage.ts diff --git a/src/cli/test/reporters/coverage.ts b/src/cli/test/reporters/coverage.ts new file mode 100644 index 000000000..23023d9d5 --- /dev/null +++ b/src/cli/test/reporters/coverage.ts @@ -0,0 +1,79 @@ +import type { AggregatedResult } from '@jest/test-result'; +import istanbulReport from 'istanbul-lib-report'; +import istanbulReports from 'istanbul-reports'; + +export const createWriteOverride = () => { + const chunks: Uint8Array[] = []; + + const output = () => Buffer.concat(chunks).toString('utf8'); + + const write = ( + buffer: Uint8Array | string, + encodingOrCb?: BufferEncoding | ((err?: Error) => void), + cb?: (err?: Error) => void, + ) => { + chunks.push( + typeof buffer === 'string' + ? Buffer.from( + buffer, + typeof encodingOrCb === 'string' ? encodingOrCb : undefined, + ) + : buffer, + ); + + if (typeof encodingOrCb === 'function') { + encodingOrCb(); + } else { + cb?.(); + } + + return true; + }; + + return { + output, + write, + }; +}; + +/** + * Renders out test coverage using the Istanbul `text` reporter. + * + * This is unfortunately hacky in a couple of ways; + * + * 1. Jest does not support custom coverage reporters (facebook/jest#9112), so + * we rely on the default `CoverageReporter` running before us and use the + * `coverageMap` that it places on the aggregated result state. + * + * 2. `istanbul-reports` does not support writing to a custom stream, so we need + * to temporarily override `process.stdout.write` 😱. + * + * {@link https://github.com/facebook/jest/blob/v27.3.1/packages/jest-reporters/src/CoverageReporter.ts#L103} + // + */ +export const renderCoverageText = ( + coverageMap: AggregatedResult['coverageMap'], +) => { + if (!coverageMap) { + // Coverage was not stored on the aggregated result by `CoverageReporter`. + // Maybe `collectCoverage` / `--coverage` was not specified. + return; + } + + const reportContext = istanbulReport.createContext({ + coverageMap, + }); + + const overrideWrite = createWriteOverride(); + + const originalWrite = process.stdout.write.bind(this); + process.stdout.write = overrideWrite.write; + + try { + istanbulReports.create('text').execute(reportContext); + } finally { + process.stdout.write = originalWrite; + } + + return overrideWrite.output(); +}; diff --git a/src/cli/test/reporters/github/index.ts b/src/cli/test/reporters/github/index.ts index f85992cbe..654649389 100644 --- a/src/cli/test/reporters/github/index.ts +++ b/src/cli/test/reporters/github/index.ts @@ -1,25 +1,30 @@ import type { Context, Reporter } from '@jest/reporters'; import type { AggregatedResult } from '@jest/test-result'; +import stripAnsi from 'strip-ansi'; +import * as Buildkite from '../../../../api/buildkite'; import * as GitHub from '../../../../api/github'; import { buildNameFromEnvironment, enabledFromEnvironment, } from '../../../../api/github/environment'; import { log } from '../../../../utils/logging'; +import { renderCoverageText } from '../coverage'; import { generateAnnotationEntries } from './annotations'; export default class GitHubReporter implements Pick { async onRunComplete( _contexts: Set, - { testResults }: AggregatedResult, + { coverageMap, testResults }: AggregatedResult, ): Promise { if (!enabledFromEnvironment()) { return; } try { + const coverage = renderCoverageText(coverageMap); + const entries = generateAnnotationEntries(testResults); const build = buildNameFromEnvironment(); @@ -35,11 +40,29 @@ export default class GitHubReporter implements Pick { ? '`skuba test` passed.' : '`skuba test` found issues that require triage.'; + if (coverage) { + const buildkiteOutput: string = [ + '`skuba test` coverage:', + Buildkite.md.terminal(coverage), + ].join('\n\n'); + + await Buildkite.annotate(buildkiteOutput, { + context: `skuba-test-${ + displayName ? `-${Buffer.from(displayName).toString('hex')}` : '' + }`, + scopeContextToStep: true, + style: isOk ? 'success' : 'error', + }); + } + await GitHub.createCheckRun({ name, annotations, conclusion: isOk ? 'success' : 'failure', summary, + text: coverage + ? Buildkite.md.terminal(stripAnsi(coverage)) + : undefined, title: `${build} ${isOk ? 'passed' : 'failed'}`, }); } From c6478d22a81b9667bd4f47a2ad7859d7df933086 Mon Sep 17 00:00:00 2001 From: Ryan Ling Date: Fri, 26 Nov 2021 02:24:32 +1100 Subject: [PATCH 2/2] Remove Buildkite for now --- src/cli/test/reporters/github/index.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/cli/test/reporters/github/index.ts b/src/cli/test/reporters/github/index.ts index 654649389..cf5d06582 100644 --- a/src/cli/test/reporters/github/index.ts +++ b/src/cli/test/reporters/github/index.ts @@ -40,21 +40,6 @@ export default class GitHubReporter implements Pick { ? '`skuba test` passed.' : '`skuba test` found issues that require triage.'; - if (coverage) { - const buildkiteOutput: string = [ - '`skuba test` coverage:', - Buildkite.md.terminal(coverage), - ].join('\n\n'); - - await Buildkite.annotate(buildkiteOutput, { - context: `skuba-test-${ - displayName ? `-${Buffer.from(displayName).toString('hex')}` : '' - }`, - scopeContextToStep: true, - style: isOk ? 'success' : 'error', - }); - } - await GitHub.createCheckRun({ name, annotations,