Skip to content

Commit

Permalink
feat: Add GitHub Actions Reporter (jestjs#11320)
Browse files Browse the repository at this point in the history
Co-authored-by: Marius Gundersen <marius.gundersen@clave.no>
Co-authored-by: Stefan Buck <stefanb@brandwatch.com>
  • Loading branch information
3 people authored and F3n67u committed Apr 1, 2022
1 parent b0d6340 commit b20c324
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -30,6 +30,7 @@
- `[jest-mock]` [**BREAKING**] Improve the usage of `jest.fn` generic type argument ([#12489](https://github.com/facebook/jest/pull/12489))
- `[jest-mock]` Add support for auto-mocking async generator functions ([#11080](https://github.com/facebook/jest/pull/11080))
- `[jest-mock]` Add `contexts` member to mock functions ([#12601](https://github.com/facebook/jest/pull/12601))
- `[jest-reporters]` Add GitHub Actions reporter ([#11320](https://github.com/facebook/jest/pull/11320))
- `[jest-resolve]` [**BREAKING**] Add support for `package.json` `exports` ([#11961](https://github.com/facebook/jest/pull/11961), [#12373](https://github.com/facebook/jest/pull/12373))
- `[jest-resolve, jest-runtime]` Add support for `data:` URI import and mock ([#12392](https://github.com/facebook/jest/pull/12392))
- `[jest-resolve, jest-runtime]` Add support for async resolver ([#11540](https://github.com/facebook/jest/pull/11540))
Expand Down
54 changes: 54 additions & 0 deletions packages/jest-reporters/src/GitHubActionsReporter.ts
@@ -0,0 +1,54 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import stripAnsi = require('strip-ansi');
import type {AggregatedResult, TestResult} from '@jest/test-result';
import BaseReporter from './BaseReporter';
import type {Context} from './types';

const lineAndColumnInStackTrace = /^.*?:([0-9]+):([0-9]+).*$/;

function replaceEntities(s: string): string {
// https://github.com/actions/toolkit/blob/b4639928698a6bfe1c4bdae4b2bfdad1cb75016d/packages/core/src/command.ts#L80-L85
const substitutions: Array<[RegExp, string]> = [
[/%/g, '%25'],
[/\r/g, '%0D'],
[/\n/g, '%0A'],
];
return substitutions.reduce((acc, sub) => acc.replace(...sub), s);
}

export default class GitHubActionsReporter extends BaseReporter {
onRunComplete(
_contexts?: Set<Context>,
aggregatedResults?: AggregatedResult,
): void {
const messages = getMessages(aggregatedResults?.testResults);

for (const message of messages) {
this.log(message);
}
}
}

function getMessages(results: Array<TestResult> | undefined) {
if (!results) return [];

return results.flatMap(({testFilePath, testResults}) =>
testResults
.filter(r => r.status === 'failed')
.flatMap(r => r.failureMessages)
.map(m => stripAnsi(m))
.map(m => replaceEntities(m))
.map(m => lineAndColumnInStackTrace.exec(m))
.filter((m): m is RegExpExecArray => m !== null)
.map(
([message, line, col]) =>
`::error file=${testFilePath},line=${line},col=${col}::${message}`,
),
);
}
118 changes: 118 additions & 0 deletions packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.js
@@ -0,0 +1,118 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';

let GitHubActionsReporter;

const write = process.stderr.write;
const globalConfig = {
rootDir: 'root',
watch: false,
};

let results = [];

function requireReporter() {
jest.isolateModules(() => {
GitHubActionsReporter = require('../GitHubActionsReporter').default;
});
}

beforeEach(() => {
process.stderr.write = result => results.push(result);
});

afterEach(() => {
results = [];
process.stderr.write = write;
});

const aggregatedResults = {
numFailedTestSuites: 1,
numFailedTests: 1,
numPassedTestSuites: 0,
numTotalTestSuites: 1,
numTotalTests: 1,
snapshot: {
added: 0,
didUpdate: false,
failure: false,
filesAdded: 0,
filesRemoved: 0,
filesRemovedList: [],
filesUnmatched: 0,
filesUpdated: 0,
matched: 0,
total: 0,
unchecked: 0,
uncheckedKeysByFile: [],
unmatched: 0,
updated: 0,
},
startTime: 0,
success: false,
testResults: [
{
numFailingTests: 1,
numPassingTests: 0,
numPendingTests: 0,
numTodoTests: 0,
openHandles: [],
perfStats: {
end: 1234,
runtime: 1234,
slow: false,
start: 0,
},
skipped: false,
snapshot: {
added: 0,
fileDeleted: false,
matched: 0,
unchecked: 0,
uncheckedKeys: [],
unmatched: 0,
updated: 0,
},
testFilePath: '/home/runner/work/jest/jest/some.test.js',
testResults: [
{
ancestorTitles: [Array],
duration: 7,
failureDetails: [Array],
failureMessages: [
`
Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n
\n
Expected: \u001b[32m\"b\"\u001b[39m\n
Received: \u001b[31m\"a\"\u001b[39m\n
at Object.<anonymous> (/home/runner/work/jest/jest/some.test.js:4:17)\n
at Object.asyncJestTest (/home/runner/work/jest/jest/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)\n
at /home/runner/work/jest/jest/node_modules/jest-jasmine2/build/queueRunner.js:45:12\n
at new Promise (<anonymous>)\n
at mapper (/home/runner/work/jest/jest/node_modules/jest-jasmine2/build/queueRunner.js:28:19)\n
at /home/runner/work/jest/jest/node_modules/jest-jasmine2/build/queueRunner.js:75:41\n
at processTicksAndRejections (internal/process/task_queues.js:93:5)
`,
],
fullName: 'asserts that a === b',
location: null,
numPassingAsserts: 0,
status: 'failed',
title: 'asserts that a === b',
},
],
},
],
};

test('reporter extracts the correct filename, line, and column', () => {
requireReporter();
const testReporter = new GitHubActionsReporter(globalConfig);
testReporter.onRunComplete(new Set(), aggregatedResults);
expect(results.join('').replace(/\\/g, '/')).toMatchSnapshot();
});
@@ -0,0 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`reporter extracts the correct filename, line, and column 1`] = `
"::error file=/home/runner/work/jest/jest/some.test.js,line=4,col=17::%0A Error: expect(received).toBe(expected) // Object.is equality%0A%0A %0A%0A Expected: "b"%0A%0A Received: "a"%0A%0A at Object.<anonymous> (/home/runner/work/jest/jest/some.test.js:4:17)%0A%0A at Object.asyncJestTest (/home/runner/work/jest/jest/node_modules/jest-jasmine2/build/jasmineAsyncInstall.js:106:37)%0A%0A at /home/runner/work/jest/jest/node_modules/jest-jasmine2/build/queueRunner.js:45:12%0A%0A at new Promise (<anonymous>)%0A%0A at mapper (/home/runner/work/jest/jest/node_modules/jest-jasmine2/build/queueRunner.js:28:19)%0A%0A at /home/runner/work/jest/jest/node_modules/jest-jasmine2/build/queueRunner.js:75:41%0A%0A at processTicksAndRejections (internal/process/task_queues.js:93:5)%0A
"
`;
1 change: 1 addition & 0 deletions packages/jest-reporters/src/index.ts
Expand Up @@ -26,6 +26,7 @@ export {default as DefaultReporter} from './DefaultReporter';
export {default as NotifyReporter} from './NotifyReporter';
export {default as SummaryReporter} from './SummaryReporter';
export {default as VerboseReporter} from './VerboseReporter';
export {default as GitHubActionsReporter} from './GitHubActionsReporter';
export type {
Context,
Reporter,
Expand Down

0 comments on commit b20c324

Please sign in to comment.