From d71c5c1f3aac1ccdcd3b1f70faf291dc01067904 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 8 May 2022 20:50:39 +0300 Subject: [PATCH 01/22] refactor(@jest/reporters): improve annotations --- .../annotationExample.test.js.snap | 5 + e2e/__tests__/annotationExample.test.js | 41 +++++ packages/jest-message-util/src/index.ts | 8 +- packages/jest-reporters/package.json | 2 + .../src/GitHubActionsReporter.ts | 73 ++++----- .../__tests__/GitHubActionsReporter.test.js | 118 -------------- .../__tests__/GitHubActionsReporter.test.ts | 148 ++++++++++++++++++ .../GitHubActionsReporter.test.js.snap | 7 - packages/jest-reporters/tsconfig.json | 1 + yarn.lock | 27 ++++ 10 files changed, 259 insertions(+), 171 deletions(-) create mode 100644 e2e/__tests__/__snapshots__/annotationExample.test.js.snap create mode 100644 e2e/__tests__/annotationExample.test.js delete mode 100644 packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.js create mode 100644 packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts delete mode 100644 packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.js.snap diff --git a/e2e/__tests__/__snapshots__/annotationExample.test.js.snap b/e2e/__tests__/__snapshots__/annotationExample.test.js.snap new file mode 100644 index 000000000000..f2daf9750556 --- /dev/null +++ b/e2e/__tests__/__snapshots__/annotationExample.test.js.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`failing snapshot example 1`] = `"some thing"`; + +exports[`passing snapshot example 1`] = `"some thing"`; diff --git a/e2e/__tests__/annotationExample.test.js b/e2e/__tests__/annotationExample.test.js new file mode 100644 index 000000000000..26ec12ea320c --- /dev/null +++ b/e2e/__tests__/annotationExample.test.js @@ -0,0 +1,41 @@ +test.todo('work in progress example'); + +test('passing example', () => { + expect(10).toBe(10); +}); + +test('passing snapshot example', () => { + expect('some thing').toMatchSnapshot(); +}); + +let i = 0; + +jest.retryTimes(3, {logErrorsBeforeRetry: true}); + +test('retryTimes example', () => { + i++; + if (i === 3) { + expect(true).toBeTruthy(); + } else { + expect(true).toBeFalsy(); + } +}); + +test('failing snapshot example', () => { + expect('nothing').toMatchSnapshot(); +}); + +test.skip('skipped example', () => { + expect(10).toBe(10); +}); + +test('failing example', () => { + expect(10).toBe(1); +}); + +describe('nested', () => { + test('failing example', () => { + // eslint-disable-next-line no-undef + expect(abc).toBe(1); + }); +}); diff --git a/packages/jest-message-util/src/index.ts b/packages/jest-message-util/src/index.ts index 0f6f38c58c47..7fbdc010ff3f 100644 --- a/packages/jest-message-util/src/index.ts +++ b/packages/jest-message-util/src/index.ts @@ -234,11 +234,11 @@ const removeInternalStackEntries = ( }); }; -const formatPaths = ( +export const formatPath = ( + line: string, config: StackTraceConfig, relativeTestPath: string | null, - line: string, -) => { +): string => { // Extract the file path from the trace line. const match = line.match(/(^\s*at .*?\(?)([^()]+)(:[0-9]+:[0-9]+\)?.*$)/); if (!match) { @@ -317,7 +317,7 @@ export const formatStackTrace = ( .filter(Boolean) .map( line => - STACK_INDENT + formatPaths(config, relativeTestPath, trimPaths(line)), + STACK_INDENT + formatPath(trimPaths(line), config, relativeTestPath), ) .join('\n'); diff --git a/packages/jest-reporters/package.json b/packages/jest-reporters/package.json index 2d6036b1d744..0985644a4bc6 100644 --- a/packages/jest-reporters/package.json +++ b/packages/jest-reporters/package.json @@ -12,6 +12,7 @@ "./package.json": "./package.json" }, "dependencies": { + "@actions/core": "^1.8.0", "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^28.1.0", "@jest/test-result": "^28.1.0", @@ -29,6 +30,7 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", + "jest-message-util": "^28.1.0", "jest-util": "^28.1.0", "jest-worker": "^28.1.0", "slash": "^3.0.0", diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 7cff405fe962..3c10318d013a 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -5,55 +5,44 @@ * LICENSE file in the root directory of this source tree. */ +import {error as errorAnnotation} from '@actions/core'; import stripAnsi = require('strip-ansi'); -import type { - AggregatedResult, - TestContext, - TestResult, -} from '@jest/test-result'; +import type {Test, TestCaseResult} from '@jest/test-result'; +import { + // formatPath, + getStackTraceLines, + getTopFrame, + separateMessageFromStack, +} from 'jest-message-util'; import BaseReporter from './BaseReporter'; -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); -} +const errorTitleSeparator = ' \u203A '; export default class GitHubActionsReporter extends BaseReporter { static readonly filename = __filename; - override onRunComplete( - _testContexts?: Set, - aggregatedResults?: AggregatedResult, + override onTestCaseResult( + test: Test, + {failureMessages, ancestorTitles, title}: TestCaseResult, ): void { - const messages = getMessages(aggregatedResults?.testResults); - - for (const message of messages) { - this.log(message); - } + failureMessages.forEach(failureMessage => { + const {message, stack} = separateMessageFromStack(failureMessage); + + const stackLines = getStackTraceLines(stack); + // const formattedLines = stackLines.map(line => + // formatPath(line, test.context.config, null), + // ); + const topFrame = getTopFrame(stackLines); + + const errorTitle = [...ancestorTitles, title].join(errorTitleSeparator); + const errorMessage = stripAnsi([message, ...stackLines].join('\n')); + + errorAnnotation(errorMessage, { + file: test.path, + startColumn: topFrame?.column, + startLine: topFrame?.line, + title: errorTitle, + }); + }); } } - -function getMessages(results: Array | 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]) => - `\n::error file=${testFilePath},line=${line},col=${col}::${message}`, - ), - ); -} diff --git a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.js b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.js deleted file mode 100644 index b7939b579458..000000000000 --- a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.js +++ /dev/null @@ -1,118 +0,0 @@ -/** - * 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. (/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 ()\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(); -}); diff --git a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts new file mode 100644 index 000000000000..fae5cd131310 --- /dev/null +++ b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts @@ -0,0 +1,148 @@ +/** + * 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 { + summary as actionsSummary, + error as errorAnnotation, +} from '@actions/core'; +import type {Test, TestCaseResult, TestContext} from '@jest/test-result'; +import GitHubActionsReporter from '../GitHubActionsReporter'; + +jest.spyOn(Date, 'now').mockReturnValue(6000); + +jest.mock('@actions/core', () => { + const addRaw = jest.fn(() => summary); + const addTable = jest.fn(() => summary); + const write = jest.fn(() => summary); + + const summary = { + addRaw, + addTable, + write, + } as unknown as jest.Mocked; + + return {error: jest.fn(), summary}; +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +const reporter = new GitHubActionsReporter(); + +const testMeta = { + context: {config: {rootDir: '/user/project'}}, + path: '/user/project/__tests__/quick.test.js', +} as Test; + +const expectationsErrorMessage = + 'Error: \x1B[2mexpect(\x1B[22m\x1B[31mreceived\x1B[39m\x1B[2m).\x1B[22mtoBe\x1B[2m(\x1B[22m\x1B[32mexpected\x1B[39m\x1B[2m) // Object.is equality\x1B[22m\n' + + '\n' + + 'Expected: \x1B[32m1\x1B[39m\n' + + 'Received: \x1B[31m10\x1B[39m\n' + + ' at Object.toBe (/user/project/__tests__/quick.test.js:20:14)\n' + + ' at Promise.then.completed (/user/project/jest/packages/jest-circus/build/utils.js:333:28)\n' + + ' at new Promise ()\n' + + ' at callAsyncCircusFn (/user/project/jest/packages/jest-circus/build/utils.js:259:10)\n' + + ' at _callCircusTest (/user/project/jest/packages/jest-circus/build/run.js:276:40)\n' + + ' at processTicksAndRejections (node:internal/process/task_queues:95:5)\n' + + ' at _runTest (/user/project/jest/packages/jest-circus/build/run.js:208:3)\n' + + ' at _runTestsForDescribeBlock (/user/project/jest/packages/jest-circus/build/run.js:96:9)\n' + + ' at run (/user/project/jest/packages/jest-circus/build/run.js:31:3)\n' + + ' at runAndTransformResultsToJestFormat (/user/project/jest/packages/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:135:21)'; + +const referenceErrorMessage = + 'ReferenceError: abc is not defined\n' + + ' at Object.abc (/user/project/__tests__/quick.test.js:25:12)\n' + + ' at Promise.then.completed (/user/project/jest/packages/jest-circus/build/utils.js:333:28)\n' + + ' at new Promise ()\n' + + ' at callAsyncCircusFn (/user/project/jest/packages/jest-circus/build/utils.js:259:10)\n' + + ' at _callCircusTest (/user/project/jest/packages/jest-circus/build/run.js:276:40)\n' + + ' at processTicksAndRejections (node:internal/process/task_queues:95:5)\n' + + ' at _runTest (/user/project/jest/packages/jest-circus/build/run.js:208:3)\n' + + ' at _runTestsForDescribeBlock (/user/project/jest/packages/jest-circus/build/run.js:96:9)\n' + + ' at _runTestsForDescribeBlock (/user/project/jest/packages/jest-circus/build/run.js:90:9)\n' + + ' at run (/user/project/jest/packages/jest-circus/build/run.js:31:3)'; + +const testCaseResult = { + ancestorTitles: [] as Array, + failureMessages: [expectationsErrorMessage], + title: 'some test', +} as TestCaseResult; + +const testContexts = new Set(); + +describe("passes test case report to '@actions/core'", () => { + test('when expect returns an error', () => { + reporter.onTestCaseResult(testMeta, { + ...testCaseResult, + failureMessages: [expectationsErrorMessage], + }); + + const expectedMessage = + 'expect(received).toBe(expected) // Object.is equality\n' + + '\n' + + 'Expected: 1\n' + + 'Received: 10\n' + + '\n' + + ' at Object.toBe (/user/project/__tests__/quick.test.js:20:14)'; + + expect(errorAnnotation).toBeCalledWith(expectedMessage, { + file: expect.any(String), + startColumn: 14, + startLine: 20, + title: expect.any(String), + }); + }); + + test('when a test has reference error', () => { + reporter.onTestCaseResult( + {...testMeta, path: '/user/project/__tests__/quick.test.js:25:12'}, + { + ...testCaseResult, + failureMessages: [referenceErrorMessage], + }, + ); + + const expectedMessage = + 'ReferenceError: abc is not defined\n' + + '\n' + + ' at Object.abc (/user/project/__tests__/quick.test.js:25:12)'; + + expect(errorAnnotation).toBeCalledWith(expectedMessage, { + file: expect.any(String), + startColumn: 12, + startLine: 25, + title: expect.any(String), + }); + }); + + test('when test is wrapped in describe block', () => { + reporter.onTestCaseResult(testMeta, { + ...testCaseResult, + ancestorTitles: ['describe'], + }); + + expect(errorAnnotation).toBeCalledWith(expect.any(String), { + file: '/user/project/__tests__/quick.test.js', + startColumn: 14, + startLine: 20, + title: 'describe \u203A some test', + }); + }); + + test('when test not is wrapped in describe block', () => { + reporter.onTestCaseResult(testMeta, testCaseResult); + + expect(errorAnnotation).toBeCalledWith(expect.any(String), { + file: '/user/project/__tests__/quick.test.js', + startColumn: 14, + startLine: 20, + title: 'some test', + }); + }); +}); diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.js.snap b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.js.snap deleted file mode 100644 index 0deced8ea4cf..000000000000 --- a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.js.snap +++ /dev/null @@ -1,7 +0,0 @@ -// 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. (/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 ()%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 -" -`; diff --git a/packages/jest-reporters/tsconfig.json b/packages/jest-reporters/tsconfig.json index 406051cc2489..813ba257479e 100644 --- a/packages/jest-reporters/tsconfig.json +++ b/packages/jest-reporters/tsconfig.json @@ -8,6 +8,7 @@ "exclude": ["./**/__tests__/**/*"], "references": [ {"path": "../jest-console"}, + {"path": "../jest-message-util"}, {"path": "../jest-resolve"}, {"path": "../jest-test-result"}, {"path": "../jest-transform"}, diff --git a/yarn.lock b/yarn.lock index a72d8e5cdedd..4f2c7f520f56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,24 @@ __metadata: version: 6 cacheKey: 8 +"@actions/core@npm:^1.8.0": + version: 1.8.0 + resolution: "@actions/core@npm:1.8.0" + dependencies: + "@actions/http-client": ^1.0.11 + checksum: 0634770fce3fdf56fd76338731921fde67dcea54cba7e5be2398d62445ada2ee8afca6694da1c6f72d9b4d257cbf3ed0dacbd58753adac712e06249f57156cf3 + languageName: node + linkType: hard + +"@actions/http-client@npm:^1.0.11": + version: 1.0.11 + resolution: "@actions/http-client@npm:1.0.11" + dependencies: + tunnel: 0.0.6 + checksum: 2c72834ec36a121ae95d2cb61fd28234eae2ab265a2aefe857a9eeb788ea77b284ad727ecd3c67fefd1920d5f2800b6c1ba6916b39d44f81f293b4b0020d367c + languageName: node + linkType: hard + "@algolia/autocomplete-core@npm:1.5.2": version: 1.5.2 resolution: "@algolia/autocomplete-core@npm:1.5.2" @@ -2739,6 +2757,7 @@ __metadata: version: 0.0.0-use.local resolution: "@jest/reporters@workspace:packages/jest-reporters" dependencies: + "@actions/core": ^1.8.0 "@bcoe/v8-coverage": ^0.2.3 "@jest/console": ^28.1.0 "@jest/test-result": ^28.1.0 @@ -2767,6 +2786,7 @@ __metadata: istanbul-lib-report: ^3.0.0 istanbul-lib-source-maps: ^4.0.0 istanbul-reports: ^3.1.3 + jest-message-util: ^28.1.0 jest-resolve: ^28.1.0 jest-util: ^28.1.0 jest-worker: ^28.1.0 @@ -21465,6 +21485,13 @@ __metadata: languageName: node linkType: hard +"tunnel@npm:0.0.6": + version: 0.0.6 + resolution: "tunnel@npm:0.0.6" + checksum: c362948df9ad34b649b5585e54ce2838fa583aa3037091aaed66793c65b423a264e5229f0d7e9a95513a795ac2bd4cb72cda7e89a74313f182c1e9ae0b0994fa + languageName: node + linkType: hard + "tweetnacl@npm:^0.14.3, tweetnacl@npm:~0.14.0": version: 0.14.5 resolution: "tweetnacl@npm:0.14.5" From 814db338f23b9fa580876228ef72e0bb2abb0237 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 8 May 2022 20:58:47 +0300 Subject: [PATCH 02/22] check paths --- packages/jest-reporters/src/GitHubActionsReporter.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 3c10318d013a..c141f4c65c50 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -35,7 +35,13 @@ export default class GitHubActionsReporter extends BaseReporter { const topFrame = getTopFrame(stackLines); const errorTitle = [...ancestorTitles, title].join(errorTitleSeparator); - const errorMessage = stripAnsi([message, ...stackLines].join('\n')); + let errorMessage = stripAnsi([message, ...stackLines].join('\n')); + + const rootDir = test.context.config.rootDir; + const testPath = test.path; + const cwd = process.cwd(); + + errorMessage = `rootDir: ${rootDir} | testPath: ${testPath} | cwd: ${cwd}`; errorAnnotation(errorMessage, { file: test.path, From 7c116a734c758418e54bc9ce90e5038efcb7810f Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 8 May 2022 21:26:59 +0300 Subject: [PATCH 03/22] remove @actions/core --- packages/jest-reporters/package.json | 1 - .../src/GitHubActionsReporter.ts | 29 ++++---- .../__tests__/GitHubActionsReporter.test.ts | 69 +++---------------- .../GitHubActionsReporter.test.ts.snap | 33 +++++++++ yarn.lock | 26 ------- 5 files changed, 56 insertions(+), 102 deletions(-) create mode 100644 packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap diff --git a/packages/jest-reporters/package.json b/packages/jest-reporters/package.json index 0985644a4bc6..4bb12026928a 100644 --- a/packages/jest-reporters/package.json +++ b/packages/jest-reporters/package.json @@ -12,7 +12,6 @@ "./package.json": "./package.json" }, "dependencies": { - "@actions/core": "^1.8.0", "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^28.1.0", "@jest/test-result": "^28.1.0", diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index c141f4c65c50..9c77095cff33 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -5,7 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -import {error as errorAnnotation} from '@actions/core'; import stripAnsi = require('strip-ansi'); import type {Test, TestCaseResult} from '@jest/test-result'; import { @@ -25,8 +24,8 @@ export default class GitHubActionsReporter extends BaseReporter { test: Test, {failureMessages, ancestorTitles, title}: TestCaseResult, ): void { - failureMessages.forEach(failureMessage => { - const {message, stack} = separateMessageFromStack(failureMessage); + failureMessages.forEach(failure => { + const {message, stack} = separateMessageFromStack(stripAnsi(failure)); const stackLines = getStackTraceLines(stack); // const formattedLines = stackLines.map(line => @@ -35,20 +34,18 @@ export default class GitHubActionsReporter extends BaseReporter { const topFrame = getTopFrame(stackLines); const errorTitle = [...ancestorTitles, title].join(errorTitleSeparator); - let errorMessage = stripAnsi([message, ...stackLines].join('\n')); + const errorMessage = escapeData([message, ...stackLines].join('\n')); - const rootDir = test.context.config.rootDir; - const testPath = test.path; - const cwd = process.cwd(); - - errorMessage = `rootDir: ${rootDir} | testPath: ${testPath} | cwd: ${cwd}`; - - errorAnnotation(errorMessage, { - file: test.path, - startColumn: topFrame?.column, - startLine: topFrame?.line, - title: errorTitle, - }); + this.log( + `\n::error file=${test.path},line=${topFrame?.line},title=${errorTitle}::${errorMessage}`, + ); }); } } + +function escapeData(command: string): string { + return command + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); +} diff --git a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts index fae5cd131310..b8766714e8f4 100644 --- a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts +++ b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts @@ -5,28 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import { - summary as actionsSummary, - error as errorAnnotation, -} from '@actions/core'; -import type {Test, TestCaseResult, TestContext} from '@jest/test-result'; +import type {Test, TestCaseResult} from '@jest/test-result'; import GitHubActionsReporter from '../GitHubActionsReporter'; -jest.spyOn(Date, 'now').mockReturnValue(6000); - -jest.mock('@actions/core', () => { - const addRaw = jest.fn(() => summary); - const addTable = jest.fn(() => summary); - const write = jest.fn(() => summary); - - const summary = { - addRaw, - addTable, - write, - } as unknown as jest.Mocked; - - return {error: jest.fn(), summary}; -}); +jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn()); afterEach(() => { jest.clearAllMocks(); @@ -74,8 +56,6 @@ const testCaseResult = { title: 'some test', } as TestCaseResult; -const testContexts = new Set(); - describe("passes test case report to '@actions/core'", () => { test('when expect returns an error', () => { reporter.onTestCaseResult(testMeta, { @@ -83,20 +63,8 @@ describe("passes test case report to '@actions/core'", () => { failureMessages: [expectationsErrorMessage], }); - const expectedMessage = - 'expect(received).toBe(expected) // Object.is equality\n' + - '\n' + - 'Expected: 1\n' + - 'Received: 10\n' + - '\n' + - ' at Object.toBe (/user/project/__tests__/quick.test.js:20:14)'; - - expect(errorAnnotation).toBeCalledWith(expectedMessage, { - file: expect.any(String), - startColumn: 14, - startLine: 20, - title: expect.any(String), - }); + expect(jest.mocked(process.stderr.write)).toBeCalledTimes(1); + expect(jest.mocked(process.stderr.write).mock.calls[0]).toMatchSnapshot(); }); test('when a test has reference error', () => { @@ -108,17 +76,8 @@ describe("passes test case report to '@actions/core'", () => { }, ); - const expectedMessage = - 'ReferenceError: abc is not defined\n' + - '\n' + - ' at Object.abc (/user/project/__tests__/quick.test.js:25:12)'; - - expect(errorAnnotation).toBeCalledWith(expectedMessage, { - file: expect.any(String), - startColumn: 12, - startLine: 25, - title: expect.any(String), - }); + expect(jest.mocked(process.stderr.write)).toBeCalledTimes(1); + expect(jest.mocked(process.stderr.write).mock.calls[0]).toMatchSnapshot(); }); test('when test is wrapped in describe block', () => { @@ -127,22 +86,14 @@ describe("passes test case report to '@actions/core'", () => { ancestorTitles: ['describe'], }); - expect(errorAnnotation).toBeCalledWith(expect.any(String), { - file: '/user/project/__tests__/quick.test.js', - startColumn: 14, - startLine: 20, - title: 'describe \u203A some test', - }); + expect(jest.mocked(process.stderr.write)).toBeCalledTimes(1); + expect(jest.mocked(process.stderr.write).mock.calls[0]).toMatchSnapshot(); }); test('when test not is wrapped in describe block', () => { reporter.onTestCaseResult(testMeta, testCaseResult); - expect(errorAnnotation).toBeCalledWith(expect.any(String), { - file: '/user/project/__tests__/quick.test.js', - startColumn: 14, - startLine: 20, - title: 'some test', - }); + expect(jest.mocked(process.stderr.write)).toBeCalledTimes(1); + expect(jest.mocked(process.stderr.write).mock.calls[0]).toMatchSnapshot(); }); }); diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap new file mode 100644 index 000000000000..851eaa8b25b3 --- /dev/null +++ b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`passes test case report to '@actions/core' when a test has reference error 1`] = ` +Array [ + " +::error file=/user/project/__tests__/quick.test.js:25:12,line=25,title=some test::ReferenceError: abc is not defined%0A%0A at Object.abc (/user/project/__tests__/quick.test.js:25:12) +", +] +`; + +exports[`passes test case report to '@actions/core' when expect returns an error 1`] = ` +Array [ + " +::error file=/user/project/__tests__/quick.test.js,line=20,title=some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (/user/project/__tests__/quick.test.js:20:14) +", +] +`; + +exports[`passes test case report to '@actions/core' when test is wrapped in describe block 1`] = ` +Array [ + " +::error file=/user/project/__tests__/quick.test.js,line=20,title=describe › some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (/user/project/__tests__/quick.test.js:20:14) +", +] +`; + +exports[`passes test case report to '@actions/core' when test not is wrapped in describe block 1`] = ` +Array [ + " +::error file=/user/project/__tests__/quick.test.js,line=20,title=some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (/user/project/__tests__/quick.test.js:20:14) +", +] +`; diff --git a/yarn.lock b/yarn.lock index 4f2c7f520f56..3a83ebaa4e38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,24 +5,6 @@ __metadata: version: 6 cacheKey: 8 -"@actions/core@npm:^1.8.0": - version: 1.8.0 - resolution: "@actions/core@npm:1.8.0" - dependencies: - "@actions/http-client": ^1.0.11 - checksum: 0634770fce3fdf56fd76338731921fde67dcea54cba7e5be2398d62445ada2ee8afca6694da1c6f72d9b4d257cbf3ed0dacbd58753adac712e06249f57156cf3 - languageName: node - linkType: hard - -"@actions/http-client@npm:^1.0.11": - version: 1.0.11 - resolution: "@actions/http-client@npm:1.0.11" - dependencies: - tunnel: 0.0.6 - checksum: 2c72834ec36a121ae95d2cb61fd28234eae2ab265a2aefe857a9eeb788ea77b284ad727ecd3c67fefd1920d5f2800b6c1ba6916b39d44f81f293b4b0020d367c - languageName: node - linkType: hard - "@algolia/autocomplete-core@npm:1.5.2": version: 1.5.2 resolution: "@algolia/autocomplete-core@npm:1.5.2" @@ -2757,7 +2739,6 @@ __metadata: version: 0.0.0-use.local resolution: "@jest/reporters@workspace:packages/jest-reporters" dependencies: - "@actions/core": ^1.8.0 "@bcoe/v8-coverage": ^0.2.3 "@jest/console": ^28.1.0 "@jest/test-result": ^28.1.0 @@ -21485,13 +21466,6 @@ __metadata: languageName: node linkType: hard -"tunnel@npm:0.0.6": - version: 0.0.6 - resolution: "tunnel@npm:0.0.6" - checksum: c362948df9ad34b649b5585e54ce2838fa583aa3037091aaed66793c65b423a264e5229f0d7e9a95513a795ac2bd4cb72cda7e89a74313f182c1e9ae0b0994fa - languageName: node - linkType: hard - "tweetnacl@npm:^0.14.3, tweetnacl@npm:~0.14.0": version: 0.14.5 resolution: "tweetnacl@npm:0.14.5" From ae036a77b9d55e11483c1022e7ccbcce5f01f4f4 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 8 May 2022 22:00:06 +0300 Subject: [PATCH 04/22] relative? --- packages/jest-message-util/src/index.ts | 6 ++--- .../src/GitHubActionsReporter.ts | 23 +++++++++++-------- .../__tests__/GitHubActionsReporter.test.ts | 12 ++++++---- .../GitHubActionsReporter.test.ts.snap | 8 +++---- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/packages/jest-message-util/src/index.ts b/packages/jest-message-util/src/index.ts index 7fbdc010ff3f..5ab1aa178d37 100644 --- a/packages/jest-message-util/src/index.ts +++ b/packages/jest-message-util/src/index.ts @@ -234,10 +234,10 @@ const removeInternalStackEntries = ( }); }; -export const formatPath = ( - line: string, +const formatPaths = ( config: StackTraceConfig, relativeTestPath: string | null, + line: string, ): string => { // Extract the file path from the trace line. const match = line.match(/(^\s*at .*?\(?)([^()]+)(:[0-9]+:[0-9]+\)?.*$)/); @@ -317,7 +317,7 @@ export const formatStackTrace = ( .filter(Boolean) .map( line => - STACK_INDENT + formatPath(trimPaths(line), config, relativeTestPath), + STACK_INDENT + formatPaths(config, relativeTestPath, trimPaths(line)), ) .join('\n'); diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 9c77095cff33..0a71d637784a 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -5,10 +5,10 @@ * LICENSE file in the root directory of this source tree. */ +import {relative} from 'path'; import stripAnsi = require('strip-ansi'); import type {Test, TestCaseResult} from '@jest/test-result'; import { - // formatPath, getStackTraceLines, getTopFrame, separateMessageFromStack, @@ -28,13 +28,18 @@ export default class GitHubActionsReporter extends BaseReporter { const {message, stack} = separateMessageFromStack(stripAnsi(failure)); const stackLines = getStackTraceLines(stack); - // const formattedLines = stackLines.map(line => - // formatPath(line, test.context.config, null), - // ); + + const relativeTestPath = relative('', test.path); + const relativeStackLines = stackLines.map(line => + line.replace(test.path, relativeTestPath), + ); + const topFrame = getTopFrame(stackLines); const errorTitle = [...ancestorTitles, title].join(errorTitleSeparator); - const errorMessage = escapeData([message, ...stackLines].join('\n')); + const errorMessage = escapeSymbols( + [message, ...relativeStackLines].join('\n'), + ); this.log( `\n::error file=${test.path},line=${topFrame?.line},title=${errorTitle}::${errorMessage}`, @@ -43,9 +48,7 @@ export default class GitHubActionsReporter extends BaseReporter { } } -function escapeData(command: string): string { - return command - .replace(/%/g, '%25') - .replace(/\r/g, '%0D') - .replace(/\n/g, '%0A'); +// copied from: https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts +function escapeSymbols(input: string): string { + return input.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A'); } diff --git a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts index b8766714e8f4..c49140bc8037 100644 --- a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts +++ b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts @@ -8,6 +8,10 @@ import type {Test, TestCaseResult} from '@jest/test-result'; import GitHubActionsReporter from '../GitHubActionsReporter'; +jest.mock('path', () => ({ + relative: () => '__tests__/example.test.js', +})); + jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn()); afterEach(() => { @@ -18,7 +22,7 @@ const reporter = new GitHubActionsReporter(); const testMeta = { context: {config: {rootDir: '/user/project'}}, - path: '/user/project/__tests__/quick.test.js', + path: '/user/project/__tests__/example.test.js', } as Test; const expectationsErrorMessage = @@ -26,7 +30,7 @@ const expectationsErrorMessage = '\n' + 'Expected: \x1B[32m1\x1B[39m\n' + 'Received: \x1B[31m10\x1B[39m\n' + - ' at Object.toBe (/user/project/__tests__/quick.test.js:20:14)\n' + + ' at Object.toBe (/user/project/__tests__/example.test.js:20:14)\n' + ' at Promise.then.completed (/user/project/jest/packages/jest-circus/build/utils.js:333:28)\n' + ' at new Promise ()\n' + ' at callAsyncCircusFn (/user/project/jest/packages/jest-circus/build/utils.js:259:10)\n' + @@ -39,7 +43,7 @@ const expectationsErrorMessage = const referenceErrorMessage = 'ReferenceError: abc is not defined\n' + - ' at Object.abc (/user/project/__tests__/quick.test.js:25:12)\n' + + ' at Object.abc (/user/project/__tests__/example.test.js:25:12)\n' + ' at Promise.then.completed (/user/project/jest/packages/jest-circus/build/utils.js:333:28)\n' + ' at new Promise ()\n' + ' at callAsyncCircusFn (/user/project/jest/packages/jest-circus/build/utils.js:259:10)\n' + @@ -69,7 +73,7 @@ describe("passes test case report to '@actions/core'", () => { test('when a test has reference error', () => { reporter.onTestCaseResult( - {...testMeta, path: '/user/project/__tests__/quick.test.js:25:12'}, + {...testMeta, path: '/user/project/__tests__/example.test.js:25:12'}, { ...testCaseResult, failureMessages: [referenceErrorMessage], diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap index 851eaa8b25b3..0e10f57108b1 100644 --- a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap +++ b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap @@ -3,7 +3,7 @@ exports[`passes test case report to '@actions/core' when a test has reference error 1`] = ` Array [ " -::error file=/user/project/__tests__/quick.test.js:25:12,line=25,title=some test::ReferenceError: abc is not defined%0A%0A at Object.abc (/user/project/__tests__/quick.test.js:25:12) +::error file=/user/project/__tests__/example.test.js:25:12,line=25,title=some test::ReferenceError: abc is not defined%0A%0A at Object.abc (__tests__/example.test.js) ", ] `; @@ -11,7 +11,7 @@ Array [ exports[`passes test case report to '@actions/core' when expect returns an error 1`] = ` Array [ " -::error file=/user/project/__tests__/quick.test.js,line=20,title=some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (/user/project/__tests__/quick.test.js:20:14) +::error file=/user/project/__tests__/example.test.js,line=20,title=some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (__tests__/example.test.js:20:14) ", ] `; @@ -19,7 +19,7 @@ Array [ exports[`passes test case report to '@actions/core' when test is wrapped in describe block 1`] = ` Array [ " -::error file=/user/project/__tests__/quick.test.js,line=20,title=describe › some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (/user/project/__tests__/quick.test.js:20:14) +::error file=/user/project/__tests__/example.test.js,line=20,title=describe › some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (__tests__/example.test.js:20:14) ", ] `; @@ -27,7 +27,7 @@ Array [ exports[`passes test case report to '@actions/core' when test not is wrapped in describe block 1`] = ` Array [ " -::error file=/user/project/__tests__/quick.test.js,line=20,title=some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (/user/project/__tests__/quick.test.js:20:14) +::error file=/user/project/__tests__/example.test.js,line=20,title=some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (__tests__/example.test.js:20:14) ", ] `; From e6c5d547d78ad65f97dcd6a70256207a9c48a86c Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 8 May 2022 22:19:04 +0300 Subject: [PATCH 05/22] slash and done? --- e2e/__tests__/annotationExample.test.js | 34 +------------------ packages/jest-message-util/src/index.ts | 2 +- .../src/GitHubActionsReporter.ts | 11 +++--- 3 files changed, 8 insertions(+), 39 deletions(-) diff --git a/e2e/__tests__/annotationExample.test.js b/e2e/__tests__/annotationExample.test.js index 26ec12ea320c..22d66aada2aa 100644 --- a/e2e/__tests__/annotationExample.test.js +++ b/e2e/__tests__/annotationExample.test.js @@ -1,41 +1,9 @@ -test.todo('work in progress example'); - -test('passing example', () => { - expect(10).toBe(10); -}); - -test('passing snapshot example', () => { - expect('some thing').toMatchSnapshot(); -}); - -let i = 0; - -jest.retryTimes(3, {logErrorsBeforeRetry: true}); - -test('retryTimes example', () => { - i++; - if (i === 3) { - expect(true).toBeTruthy(); - } else { - expect(true).toBeFalsy(); - } -}); - test('failing snapshot example', () => { expect('nothing').toMatchSnapshot(); }); -test.skip('skipped example', () => { - expect(10).toBe(10); -}); - -test('failing example', () => { - expect(10).toBe(1); -}); - describe('nested', () => { test('failing example', () => { - // eslint-disable-next-line no-undef - expect(abc).toBe(1); + expect(10).toBe(1); }); }); diff --git a/packages/jest-message-util/src/index.ts b/packages/jest-message-util/src/index.ts index 5ab1aa178d37..0f6f38c58c47 100644 --- a/packages/jest-message-util/src/index.ts +++ b/packages/jest-message-util/src/index.ts @@ -238,7 +238,7 @@ const formatPaths = ( config: StackTraceConfig, relativeTestPath: string | null, line: string, -): string => { +) => { // Extract the file path from the trace line. const match = line.match(/(^\s*at .*?\(?)([^()]+)(:[0-9]+:[0-9]+\)?.*$)/); if (!match) { diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 0a71d637784a..0cd01742e6cd 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -6,6 +6,7 @@ */ import {relative} from 'path'; +import slash = require('slash'); import stripAnsi = require('strip-ansi'); import type {Test, TestCaseResult} from '@jest/test-result'; import { @@ -29,16 +30,16 @@ export default class GitHubActionsReporter extends BaseReporter { const stackLines = getStackTraceLines(stack); - const relativeTestPath = relative('', test.path); - const relativeStackLines = stackLines.map(line => + const relativeTestPath = slash(relative('', test.path)); + const normalizedStackLines = stackLines.map(line => line.replace(test.path, relativeTestPath), ); const topFrame = getTopFrame(stackLines); const errorTitle = [...ancestorTitles, title].join(errorTitleSeparator); - const errorMessage = escapeSymbols( - [message, ...relativeStackLines].join('\n'), + const errorMessage = normalizeMessage( + [message, ...normalizedStackLines].join('\n'), ); this.log( @@ -49,6 +50,6 @@ export default class GitHubActionsReporter extends BaseReporter { } // copied from: https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts -function escapeSymbols(input: string): string { +function normalizeMessage(input: string): string { return input.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A'); } From 300e7eb00eab4708c6930cbb7d20b62ae35d0ff5 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 8 May 2022 22:29:41 +0300 Subject: [PATCH 06/22] remove examples --- e2e/__tests__/annotationExample.test.js | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 e2e/__tests__/annotationExample.test.js diff --git a/e2e/__tests__/annotationExample.test.js b/e2e/__tests__/annotationExample.test.js deleted file mode 100644 index 22d66aada2aa..000000000000 --- a/e2e/__tests__/annotationExample.test.js +++ /dev/null @@ -1,9 +0,0 @@ -test('failing snapshot example', () => { - expect('nothing').toMatchSnapshot(); -}); - -describe('nested', () => { - test('failing example', () => { - expect(10).toBe(1); - }); -}); From e594bcb89d66af086cb5f38cdd32f70e8b0f8b59 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 8 May 2022 22:33:54 +0300 Subject: [PATCH 07/22] clean up --- packages/jest-reporters/src/GitHubActionsReporter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 0cd01742e6cd..b89ba0a814f2 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -28,9 +28,8 @@ export default class GitHubActionsReporter extends BaseReporter { failureMessages.forEach(failure => { const {message, stack} = separateMessageFromStack(stripAnsi(failure)); - const stackLines = getStackTraceLines(stack); - const relativeTestPath = slash(relative('', test.path)); + const stackLines = getStackTraceLines(stack); const normalizedStackLines = stackLines.map(line => line.replace(test.path, relativeTestPath), ); From fadd5f2ea26e3c578c0dcdc83fc00f4f8699ac4b Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 8 May 2022 22:34:08 +0300 Subject: [PATCH 08/22] add change log entry --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5d779e22987..e0da518bb292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Features +- `[@jest/reporters]` Improve `GitHubActionsReporter`s annotation format ([#12826](https://github.com/facebook/jest/pull/12826)) + ### Fixes ### Chore & Maintenance @@ -12,7 +14,7 @@ ### Features -- `[jest-circus]` Add `failing` test modifier that inverts the behaviour of tests ([#12610](https://github.com/facebook/jest/pull/12610)) +- `[jest-circus]` Add `failing` test modifier that inverts the behavior of tests ([#12610](https://github.com/facebook/jest/pull/12610)) - `[jest-environment-node, jest-environment-jsdom]` Allow specifying `customExportConditions` ([#12774](https://github.com/facebook/jest/pull/12774)) ### Fixes From 10221304bc6d6a3a13ba86de03ce435ed3296ecb Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 8 May 2022 22:40:30 +0300 Subject: [PATCH 09/22] remove snap --- e2e/__tests__/__snapshots__/annotationExample.test.js.snap | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 e2e/__tests__/__snapshots__/annotationExample.test.js.snap diff --git a/e2e/__tests__/__snapshots__/annotationExample.test.js.snap b/e2e/__tests__/__snapshots__/annotationExample.test.js.snap deleted file mode 100644 index f2daf9750556..000000000000 --- a/e2e/__tests__/__snapshots__/annotationExample.test.js.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`failing snapshot example 1`] = `"some thing"`; - -exports[`passing snapshot example 1`] = `"some thing"`; From 172a41e3e4b1d21ff78d3afaa8a64ee90789463b Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Sun, 8 May 2022 22:50:18 +0300 Subject: [PATCH 10/22] Restart CI From f1a1e971db8fd405452e856ad263f7f4cc8a0723 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 07:34:34 +0300 Subject: [PATCH 11/22] handle more paths --- .../annotationExample.test.ts.snap | 3 +++ e2e/__tests__/annotationExample.test.ts | 26 +++++++++++++++++++ e2e/__tests__/annotationHelper.ts | 10 +++++++ packages/jest-message-util/src/index.ts | 10 +++---- .../src/GitHubActionsReporter.ts | 11 ++++---- 5 files changed, 49 insertions(+), 11 deletions(-) create mode 100644 e2e/__tests__/__snapshots__/annotationExample.test.ts.snap create mode 100644 e2e/__tests__/annotationExample.test.ts create mode 100644 e2e/__tests__/annotationHelper.ts diff --git a/e2e/__tests__/__snapshots__/annotationExample.test.ts.snap b/e2e/__tests__/__snapshots__/annotationExample.test.ts.snap new file mode 100644 index 000000000000..77be90150f4f --- /dev/null +++ b/e2e/__tests__/__snapshots__/annotationExample.test.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`failing snapshot example 1`] = `"one"`; diff --git a/e2e/__tests__/annotationExample.test.ts b/e2e/__tests__/annotationExample.test.ts new file mode 100644 index 000000000000..1798de4981fd --- /dev/null +++ b/e2e/__tests__/annotationExample.test.ts @@ -0,0 +1,26 @@ +/** + * 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 {helper} from './annotationHelper'; + +test('failing snapshot example', () => { + expect('two').toMatchSnapshot(); +}); + +describe('nested', () => { + test('failing example', () => { + expect(10).toBe(1); + }); +}); + +test('passing helper', () => { + expect(() => helper()).toThrow('Helper logged an error'); +}); + +test('failing helper', () => { + helper(); +}); diff --git a/e2e/__tests__/annotationHelper.ts b/e2e/__tests__/annotationHelper.ts new file mode 100644 index 000000000000..ae216fbe14d2 --- /dev/null +++ b/e2e/__tests__/annotationHelper.ts @@ -0,0 +1,10 @@ +/** + * 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. + */ + +export function helper() { + throw new Error('Helper threw an error'); +} diff --git a/packages/jest-message-util/src/index.ts b/packages/jest-message-util/src/index.ts index 0f6f38c58c47..cd1e2002e4c5 100644 --- a/packages/jest-message-util/src/index.ts +++ b/packages/jest-message-util/src/index.ts @@ -234,11 +234,11 @@ const removeInternalStackEntries = ( }); }; -const formatPaths = ( - config: StackTraceConfig, - relativeTestPath: string | null, +export const formatPath = ( line: string, -) => { + config: StackTraceConfig = {rootDir: '', testMatch: []}, + relativeTestPath: string | null = null, +): string => { // Extract the file path from the trace line. const match = line.match(/(^\s*at .*?\(?)([^()]+)(:[0-9]+:[0-9]+\)?.*$)/); if (!match) { @@ -317,7 +317,7 @@ export const formatStackTrace = ( .filter(Boolean) .map( line => - STACK_INDENT + formatPaths(config, relativeTestPath, trimPaths(line)), + STACK_INDENT + formatPath(trimPaths(line), config, relativeTestPath), ) .join('\n'); diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index b89ba0a814f2..1c3131308aef 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -5,11 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import {relative} from 'path'; -import slash = require('slash'); +// import {relative} from 'path'; +// import slash = require('slash'); import stripAnsi = require('strip-ansi'); import type {Test, TestCaseResult} from '@jest/test-result'; import { + formatPath, getStackTraceLines, getTopFrame, separateMessageFromStack, @@ -28,11 +29,9 @@ export default class GitHubActionsReporter extends BaseReporter { failureMessages.forEach(failure => { const {message, stack} = separateMessageFromStack(stripAnsi(failure)); - const relativeTestPath = slash(relative('', test.path)); + // const relativeTestPath = slash(relative('', test.path)); const stackLines = getStackTraceLines(stack); - const normalizedStackLines = stackLines.map(line => - line.replace(test.path, relativeTestPath), - ); + const normalizedStackLines = stackLines.map(line => formatPath(line)); const topFrame = getTopFrame(stackLines); From e95b5e54d989781f8875bb6aac6bca75f446d26a Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 07:44:25 +0300 Subject: [PATCH 12/22] fix ansi --- .../jest-reporters/src/GitHubActionsReporter.ts | 13 +++++++------ .../GitHubActionsReporter.test.ts.snap | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 1c3131308aef..70cdce0508a0 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -5,8 +5,6 @@ * LICENSE file in the root directory of this source tree. */ -// import {relative} from 'path'; -// import slash = require('slash'); import stripAnsi = require('strip-ansi'); import type {Test, TestCaseResult} from '@jest/test-result'; import { @@ -26,10 +24,9 @@ export default class GitHubActionsReporter extends BaseReporter { test: Test, {failureMessages, ancestorTitles, title}: TestCaseResult, ): void { - failureMessages.forEach(failure => { - const {message, stack} = separateMessageFromStack(stripAnsi(failure)); + failureMessages.forEach(failureMessage => { + const {message, stack} = separateMessageFromStack(failureMessage); - // const relativeTestPath = slash(relative('', test.path)); const stackLines = getStackTraceLines(stack); const normalizedStackLines = stackLines.map(line => formatPath(line)); @@ -49,5 +46,9 @@ export default class GitHubActionsReporter extends BaseReporter { // copied from: https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts function normalizeMessage(input: string): string { - return input.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A'); + const normalizedInput = input + .replace(/%/g, '%25') + .replace(/\r/g, '%0D') + .replace(/\n/g, '%0A'); + return stripAnsi(normalizedInput); } diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap index 0e10f57108b1..549e848c5af0 100644 --- a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap +++ b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap @@ -3,7 +3,7 @@ exports[`passes test case report to '@actions/core' when a test has reference error 1`] = ` Array [ " -::error file=/user/project/__tests__/example.test.js:25:12,line=25,title=some test::ReferenceError: abc is not defined%0A%0A at Object.abc (__tests__/example.test.js) +::error file=/user/project/__tests__/example.test.js:25:12,line=25,title=some test::ReferenceError: abc is not defined%0A%0A at Object.abc (__tests__/example.test.js:25:12) ", ] `; From ebd0bf8818292e4eedcbb2ec63520d596383f1de Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 07:53:49 +0300 Subject: [PATCH 13/22] fix filename --- packages/jest-reporters/src/GitHubActionsReporter.ts | 6 +++--- .../__snapshots__/GitHubActionsReporter.test.ts.snap | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 70cdce0508a0..f5232922e2bc 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -21,7 +21,7 @@ export default class GitHubActionsReporter extends BaseReporter { static readonly filename = __filename; override onTestCaseResult( - test: Test, + _test: Test, {failureMessages, ancestorTitles, title}: TestCaseResult, ): void { failureMessages.forEach(failureMessage => { @@ -38,14 +38,14 @@ export default class GitHubActionsReporter extends BaseReporter { ); this.log( - `\n::error file=${test.path},line=${topFrame?.line},title=${errorTitle}::${errorMessage}`, + `\n::error file=${topFrame?.file},line=${topFrame?.line},title=${errorTitle}::${errorMessage}`, ); }); } } -// copied from: https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts function normalizeMessage(input: string): string { + // copied from: https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts const normalizedInput = input .replace(/%/g, '%25') .replace(/\r/g, '%0D') diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap index 549e848c5af0..6618074fba93 100644 --- a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap +++ b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap @@ -3,7 +3,7 @@ exports[`passes test case report to '@actions/core' when a test has reference error 1`] = ` Array [ " -::error file=/user/project/__tests__/example.test.js:25:12,line=25,title=some test::ReferenceError: abc is not defined%0A%0A at Object.abc (__tests__/example.test.js:25:12) +::error file=/user/project/__tests__/example.test.js,line=25,title=some test::ReferenceError: abc is not defined%0A%0A at Object.abc (__tests__/example.test.js:25:12) ", ] `; From 7171272c3eee94be575eb3694ca822c70d8a43af Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 08:09:02 +0300 Subject: [PATCH 14/22] clean up --- .../annotationExample.test.ts.snap | 3 --- e2e/__tests__/annotationExample.test.ts | 26 ------------------- e2e/__tests__/annotationHelper.ts | 10 ------- .../src/GitHubActionsReporter.ts | 3 +-- 4 files changed, 1 insertion(+), 41 deletions(-) delete mode 100644 e2e/__tests__/__snapshots__/annotationExample.test.ts.snap delete mode 100644 e2e/__tests__/annotationExample.test.ts delete mode 100644 e2e/__tests__/annotationHelper.ts diff --git a/e2e/__tests__/__snapshots__/annotationExample.test.ts.snap b/e2e/__tests__/__snapshots__/annotationExample.test.ts.snap deleted file mode 100644 index 77be90150f4f..000000000000 --- a/e2e/__tests__/__snapshots__/annotationExample.test.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`failing snapshot example 1`] = `"one"`; diff --git a/e2e/__tests__/annotationExample.test.ts b/e2e/__tests__/annotationExample.test.ts deleted file mode 100644 index 1798de4981fd..000000000000 --- a/e2e/__tests__/annotationExample.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * 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 {helper} from './annotationHelper'; - -test('failing snapshot example', () => { - expect('two').toMatchSnapshot(); -}); - -describe('nested', () => { - test('failing example', () => { - expect(10).toBe(1); - }); -}); - -test('passing helper', () => { - expect(() => helper()).toThrow('Helper logged an error'); -}); - -test('failing helper', () => { - helper(); -}); diff --git a/e2e/__tests__/annotationHelper.ts b/e2e/__tests__/annotationHelper.ts deleted file mode 100644 index ae216fbe14d2..000000000000 --- a/e2e/__tests__/annotationHelper.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * 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. - */ - -export function helper() { - throw new Error('Helper threw an error'); -} diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index f5232922e2bc..5adf96260e56 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -28,10 +28,9 @@ export default class GitHubActionsReporter extends BaseReporter { const {message, stack} = separateMessageFromStack(failureMessage); const stackLines = getStackTraceLines(stack); - const normalizedStackLines = stackLines.map(line => formatPath(line)); - const topFrame = getTopFrame(stackLines); + const normalizedStackLines = stackLines.map(line => formatPath(line)); const errorTitle = [...ancestorTitles, title].join(errorTitleSeparator); const errorMessage = normalizeMessage( [message, ...normalizedStackLines].join('\n'), From cc02d0b1cd16f59502a53baf21d5876d03404077 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 08:16:26 +0300 Subject: [PATCH 15/22] Restart CI From 87fb02876b61cabcf09ead95abdecea3c9ead47e Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 14:48:27 +0300 Subject: [PATCH 16/22] annotate retried tests --- .../annotationsRetryExamples.test.ts | 22 +++++ .../annotationsRetrySilencedExamples.test.ts | 20 +++++ .../src/GitHubActionsReporter.ts | 82 +++++++++++++------ .../__tests__/GitHubActionsReporter.test.ts | 73 +++++++++++++---- .../GitHubActionsReporter.test.ts.snap | 16 ++-- 5 files changed, 160 insertions(+), 53 deletions(-) create mode 100644 e2e/__tests__/annotationsRetryExamples.test.ts create mode 100644 e2e/__tests__/annotationsRetrySilencedExamples.test.ts diff --git a/e2e/__tests__/annotationsRetryExamples.test.ts b/e2e/__tests__/annotationsRetryExamples.test.ts new file mode 100644 index 000000000000..0738cd63293c --- /dev/null +++ b/e2e/__tests__/annotationsRetryExamples.test.ts @@ -0,0 +1,22 @@ +/** + * 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. + */ + +let i = 0; + +jest.retryTimes(3, {logErrorsBeforeRetry: true}); + +describe('nested', () => { + test('logging retryTimes example', () => { + i++; + + if (i === 3) { + expect(true).toBeTruthy(); + } else { + expect(true).toBeFalsy(); + } + }); +}); diff --git a/e2e/__tests__/annotationsRetrySilencedExamples.test.ts b/e2e/__tests__/annotationsRetrySilencedExamples.test.ts new file mode 100644 index 000000000000..98a0a86824c5 --- /dev/null +++ b/e2e/__tests__/annotationsRetrySilencedExamples.test.ts @@ -0,0 +1,20 @@ +/** + * 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. + */ + +let i = 0; + +jest.retryTimes(3); + +test('silent retryTimes example', () => { + i++; + + if (i === 3) { + expect(true).toBeTruthy(); + } else { + expect(true).toBeFalsy(); + } +}); diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 5adf96260e56..3a984acd05db 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -6,7 +6,7 @@ */ import stripAnsi = require('strip-ansi'); -import type {Test, TestCaseResult} from '@jest/test-result'; +import type {Test, TestResult} from '@jest/test-result'; import { formatPath, getStackTraceLines, @@ -15,39 +15,67 @@ import { } from 'jest-message-util'; import BaseReporter from './BaseReporter'; -const errorTitleSeparator = ' \u203A '; +type AnnotationOptions = { + file?: string; + line?: number | string; + message: string; + title: string; + type: 'error' | 'warning'; +}; + +const titleSeparator = ' \u203A '; export default class GitHubActionsReporter extends BaseReporter { static readonly filename = __filename; - override onTestCaseResult( - _test: Test, - {failureMessages, ancestorTitles, title}: TestCaseResult, - ): void { - failureMessages.forEach(failureMessage => { - const {message, stack} = separateMessageFromStack(failureMessage); - - const stackLines = getStackTraceLines(stack); - const topFrame = getTopFrame(stackLines); - - const normalizedStackLines = stackLines.map(line => formatPath(line)); - const errorTitle = [...ancestorTitles, title].join(errorTitleSeparator); - const errorMessage = normalizeMessage( - [message, ...normalizedStackLines].join('\n'), + onTestFileResult(_test: Test, {testResults}: TestResult): void { + testResults.forEach(result => { + const title = [...result.ancestorTitles, result.title].join( + titleSeparator, ); - this.log( - `\n::error file=${topFrame?.file},line=${topFrame?.line},title=${errorTitle}::${errorMessage}`, - ); + result.failureMessages.forEach(failureMessage => { + this.#createAnnotation({ + ...this.#getMessageDetails(failureMessage), + title, + type: 'error', + }); + }); + + result.retryReasons?.forEach((retryReason, index) => { + this.#createAnnotation({ + ...this.#getMessageDetails(retryReason), + title: `[RETRY ${index + 1}] ${title}`, + type: 'warning', + }); + }); }); } -} -function normalizeMessage(input: string): string { - // copied from: https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts - const normalizedInput = input - .replace(/%/g, '%25') - .replace(/\r/g, '%0D') - .replace(/\n/g, '%0A'); - return stripAnsi(normalizedInput); + #getMessageDetails(failureMessage: string) { + const {message, stack} = separateMessageFromStack(failureMessage); + + const stackLines = getStackTraceLines(stack); + const topFrame = getTopFrame(stackLines); + + const normalizedStackLines = stackLines.map(line => formatPath(line)); + const messageText = [message, ...normalizedStackLines].join('\n'); + + return { + file: topFrame?.file, + line: topFrame?.line, + message: messageText, + }; + } + + #createAnnotation({file, line, message, title, type}: AnnotationOptions) { + message = stripAnsi( + // copied from: https://github.com/actions/toolkit/blob/main/packages/core/src/command.ts + message.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A'), + ); + + this.log( + `\n::${type} file=${file},line=${line},title=${title}::${message}`, + ); + } } diff --git a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts index c49140bc8037..18aaacdfe041 100644 --- a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts +++ b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import type {Test, TestCaseResult} from '@jest/test-result'; +import type {Test, TestCaseResult, TestResult} from '@jest/test-result'; import GitHubActionsReporter from '../GitHubActionsReporter'; jest.mock('path', () => ({ @@ -54,30 +54,53 @@ const referenceErrorMessage = ' at _runTestsForDescribeBlock (/user/project/jest/packages/jest-circus/build/run.js:90:9)\n' + ' at run (/user/project/jest/packages/jest-circus/build/run.js:31:3)'; +const retryErrorMessage = + 'Error: \x1B[2mexpect(\x1B[22m\x1B[31mreceived\x1B[39m\x1B[2m).\x1B[22mtoBeFalsy\x1B[2m()\x1B[22m\n' + + '\n' + + 'Received: \x1B[31mtrue\x1B[39m\n' + + ' at Object.toBeFalsy (/user/project/jest/__tests__/example.test.js:19:20)\n' + + ' at Promise.then.completed (/user/project/jest/packages/jest-circus/build/utils.js:333:28)\n' + + ' at new Promise ()\n' + + ' at callAsyncCircusFn (/user/project/jest/packages/jest-circus/build/utils.js:259:10)\n' + + ' at _callCircusTest (/user/project/jest/packages/jest-circus/build/run.js:276:40)\n' + + ' at _runTest (/user/project/jest/packages/jest-circus/build/run.js:208:3)\n' + + ' at _runTestsForDescribeBlock (/user/project/jest/packages/jest-circus/build/run.js:96:9)\n' + + ' at _runTestsForDescribeBlock (/user/project/jest/packages/jest-circus/build/run.js:90:9)\n' + + ' at run (/user/project/jest/packages/jest-circus/build/run.js:31:3)\n' + + ' at runAndTransformResultsToJestFormat (/user/project/jest/packages/jest-circus/build/legacy-code-todo-rewrite/jestAdapterInit.js:135:21)'; + const testCaseResult = { ancestorTitles: [] as Array, failureMessages: [expectationsErrorMessage], - title: 'some test', + title: 'example test', } as TestCaseResult; -describe("passes test case report to '@actions/core'", () => { - test('when expect returns an error', () => { - reporter.onTestCaseResult(testMeta, { - ...testCaseResult, - failureMessages: [expectationsErrorMessage], - }); +describe('logs error annotation', () => { + test('when an expectation fails to pass', () => { + reporter.onTestFileResult(testMeta, { + testResults: [ + { + ...testCaseResult, + failureMessages: [expectationsErrorMessage], + }, + ], + } as TestResult); expect(jest.mocked(process.stderr.write)).toBeCalledTimes(1); expect(jest.mocked(process.stderr.write).mock.calls[0]).toMatchSnapshot(); }); test('when a test has reference error', () => { - reporter.onTestCaseResult( + reporter.onTestFileResult( {...testMeta, path: '/user/project/__tests__/example.test.js:25:12'}, { - ...testCaseResult, - failureMessages: [referenceErrorMessage], - }, + testResults: [ + { + ...testCaseResult, + failureMessages: [referenceErrorMessage], + }, + ], + } as TestResult, ); expect(jest.mocked(process.stderr.write)).toBeCalledTimes(1); @@ -85,17 +108,31 @@ describe("passes test case report to '@actions/core'", () => { }); test('when test is wrapped in describe block', () => { - reporter.onTestCaseResult(testMeta, { - ...testCaseResult, - ancestorTitles: ['describe'], - }); + reporter.onTestFileResult(testMeta, { + testResults: [ + { + ...testCaseResult, + ancestorTitles: ['describe'], + }, + ], + } as TestResult); expect(jest.mocked(process.stderr.write)).toBeCalledTimes(1); expect(jest.mocked(process.stderr.write).mock.calls[0]).toMatchSnapshot(); }); +}); - test('when test not is wrapped in describe block', () => { - reporter.onTestCaseResult(testMeta, testCaseResult); +describe('logs warning annotation', () => { + test('when test result includes retry reasons', () => { + reporter.onTestFileResult(testMeta, { + testResults: [ + { + ...testCaseResult, + failureMessages: [], + retryReasons: [retryErrorMessage], + }, + ], + } as TestResult); expect(jest.mocked(process.stderr.write)).toBeCalledTimes(1); expect(jest.mocked(process.stderr.write).mock.calls[0]).toMatchSnapshot(); diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap index 6618074fba93..ff0ebc827b82 100644 --- a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap +++ b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap @@ -1,33 +1,33 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`passes test case report to '@actions/core' when a test has reference error 1`] = ` +exports[`logs error annotation when a test has reference error 1`] = ` Array [ " -::error file=/user/project/__tests__/example.test.js,line=25,title=some test::ReferenceError: abc is not defined%0A%0A at Object.abc (__tests__/example.test.js:25:12) +::error file=/user/project/__tests__/example.test.js,line=25,title=example test::ReferenceError: abc is not defined%0A%0A at Object.abc (__tests__/example.test.js:25:12) ", ] `; -exports[`passes test case report to '@actions/core' when expect returns an error 1`] = ` +exports[`logs error annotation when an expectation fails to pass 1`] = ` Array [ " -::error file=/user/project/__tests__/example.test.js,line=20,title=some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (__tests__/example.test.js:20:14) +::error file=/user/project/__tests__/example.test.js,line=20,title=example test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (__tests__/example.test.js:20:14) ", ] `; -exports[`passes test case report to '@actions/core' when test is wrapped in describe block 1`] = ` +exports[`logs error annotation when test is wrapped in describe block 1`] = ` Array [ " -::error file=/user/project/__tests__/example.test.js,line=20,title=describe › some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (__tests__/example.test.js:20:14) +::error file=/user/project/__tests__/example.test.js,line=20,title=describe › example test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (__tests__/example.test.js:20:14) ", ] `; -exports[`passes test case report to '@actions/core' when test not is wrapped in describe block 1`] = ` +exports[`logs warning annotation when test result includes retry reasons 1`] = ` Array [ " -::error file=/user/project/__tests__/example.test.js,line=20,title=some test::expect(received).toBe(expected) // Object.is equality%0A%0AExpected: 1%0AReceived: 10%0A%0A at Object.toBe (__tests__/example.test.js:20:14) +::warning file=/user/project/jest/__tests__/example.test.js,line=19,title=[RETRY 1] example test::expect(received).toBeFalsy()%0A%0AReceived: true%0A%0A at Object.toBeFalsy (__tests__/example.test.js:19:20) ", ] `; From 74c3f74239d06e8207dcf06fca69bef816c072ae Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 14:49:37 +0300 Subject: [PATCH 17/22] rootDir again --- packages/jest-reporters/src/GitHubActionsReporter.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 3a984acd05db..1e6644d5bbcb 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -28,7 +28,7 @@ const titleSeparator = ' \u203A '; export default class GitHubActionsReporter extends BaseReporter { static readonly filename = __filename; - onTestFileResult(_test: Test, {testResults}: TestResult): void { + onTestFileResult(test: Test, {testResults}: TestResult): void { testResults.forEach(result => { const title = [...result.ancestorTitles, result.title].join( titleSeparator, @@ -45,7 +45,9 @@ export default class GitHubActionsReporter extends BaseReporter { result.retryReasons?.forEach((retryReason, index) => { this.#createAnnotation({ ...this.#getMessageDetails(retryReason), - title: `[RETRY ${index + 1}] ${title}`, + title: `[RETRY ${index + 1}] ${title} | rootDir ${ + test.context.config.rootDir + }`, type: 'warning', }); }); From 8f60aa140832e96a07cb705652629e6972773d42 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 15:01:55 +0300 Subject: [PATCH 18/22] use rootDir --- e2e/__tests__/annotationsRetryExamples.test.ts | 2 +- packages/jest-message-util/src/index.ts | 2 +- .../jest-reporters/src/GitHubActionsReporter.ts | 15 ++++++++------- .../src/__tests__/GitHubActionsReporter.test.ts | 7 +------ .../GitHubActionsReporter.test.ts.snap | 2 +- 5 files changed, 12 insertions(+), 16 deletions(-) diff --git a/e2e/__tests__/annotationsRetryExamples.test.ts b/e2e/__tests__/annotationsRetryExamples.test.ts index 0738cd63293c..e9d1bc9b690f 100644 --- a/e2e/__tests__/annotationsRetryExamples.test.ts +++ b/e2e/__tests__/annotationsRetryExamples.test.ts @@ -7,7 +7,7 @@ let i = 0; -jest.retryTimes(3, {logErrorsBeforeRetry: true}); +jest.retryTimes(1, {logErrorsBeforeRetry: true}); describe('nested', () => { test('logging retryTimes example', () => { diff --git a/packages/jest-message-util/src/index.ts b/packages/jest-message-util/src/index.ts index cd1e2002e4c5..b26ab2310aa2 100644 --- a/packages/jest-message-util/src/index.ts +++ b/packages/jest-message-util/src/index.ts @@ -236,7 +236,7 @@ const removeInternalStackEntries = ( export const formatPath = ( line: string, - config: StackTraceConfig = {rootDir: '', testMatch: []}, + config: StackTraceConfig, relativeTestPath: string | null = null, ): string => { // Extract the file path from the trace line. diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 1e6644d5bbcb..5cab1695f9e0 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -7,6 +7,7 @@ import stripAnsi = require('strip-ansi'); import type {Test, TestResult} from '@jest/test-result'; +import type {Config} from '@jest/types'; import { formatPath, getStackTraceLines, @@ -36,7 +37,7 @@ export default class GitHubActionsReporter extends BaseReporter { result.failureMessages.forEach(failureMessage => { this.#createAnnotation({ - ...this.#getMessageDetails(failureMessage), + ...this.#getMessageDetails(failureMessage, test.context.config), title, type: 'error', }); @@ -44,23 +45,23 @@ export default class GitHubActionsReporter extends BaseReporter { result.retryReasons?.forEach((retryReason, index) => { this.#createAnnotation({ - ...this.#getMessageDetails(retryReason), - title: `[RETRY ${index + 1}] ${title} | rootDir ${ - test.context.config.rootDir - }`, + ...this.#getMessageDetails(retryReason, test.context.config), + title: `[RETRY ${index + 1}] ${title}`, type: 'warning', }); }); }); } - #getMessageDetails(failureMessage: string) { + #getMessageDetails(failureMessage: string, config: Config.ProjectConfig) { const {message, stack} = separateMessageFromStack(failureMessage); const stackLines = getStackTraceLines(stack); const topFrame = getTopFrame(stackLines); - const normalizedStackLines = stackLines.map(line => formatPath(line)); + const normalizedStackLines = stackLines.map(line => + formatPath(line, config), + ); const messageText = [message, ...normalizedStackLines].join('\n'); return { diff --git a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts index 18aaacdfe041..c1a4113adf10 100644 --- a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts +++ b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts @@ -8,10 +8,6 @@ import type {Test, TestCaseResult, TestResult} from '@jest/test-result'; import GitHubActionsReporter from '../GitHubActionsReporter'; -jest.mock('path', () => ({ - relative: () => '__tests__/example.test.js', -})); - jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn()); afterEach(() => { @@ -22,7 +18,6 @@ const reporter = new GitHubActionsReporter(); const testMeta = { context: {config: {rootDir: '/user/project'}}, - path: '/user/project/__tests__/example.test.js', } as Test; const expectationsErrorMessage = @@ -58,7 +53,7 @@ const retryErrorMessage = 'Error: \x1B[2mexpect(\x1B[22m\x1B[31mreceived\x1B[39m\x1B[2m).\x1B[22mtoBeFalsy\x1B[2m()\x1B[22m\n' + '\n' + 'Received: \x1B[31mtrue\x1B[39m\n' + - ' at Object.toBeFalsy (/user/project/jest/__tests__/example.test.js:19:20)\n' + + ' at Object.toBeFalsy (/user/project/__tests__/example.test.js:19:20)\n' + ' at Promise.then.completed (/user/project/jest/packages/jest-circus/build/utils.js:333:28)\n' + ' at new Promise ()\n' + ' at callAsyncCircusFn (/user/project/jest/packages/jest-circus/build/utils.js:259:10)\n' + diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap index ff0ebc827b82..e7bf77113fff 100644 --- a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap +++ b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap @@ -27,7 +27,7 @@ Array [ exports[`logs warning annotation when test result includes retry reasons 1`] = ` Array [ " -::warning file=/user/project/jest/__tests__/example.test.js,line=19,title=[RETRY 1] example test::expect(received).toBeFalsy()%0A%0AReceived: true%0A%0A at Object.toBeFalsy (__tests__/example.test.js:19:20) +::warning file=/user/project/__tests__/example.test.js,line=19,title=[RETRY 1] example test::expect(received).toBeFalsy()%0A%0AReceived: true%0A%0A at Object.toBeFalsy (__tests__/example.test.js:19:20) ", ] `; From 8e6e9ef78ca3dd11b96f64f9dd66276eede258ea Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 15:08:33 +0300 Subject: [PATCH 19/22] log retries first --- .../src/GitHubActionsReporter.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 5cab1695f9e0..9a41264194ce 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -29,25 +29,25 @@ const titleSeparator = ' \u203A '; export default class GitHubActionsReporter extends BaseReporter { static readonly filename = __filename; - onTestFileResult(test: Test, {testResults}: TestResult): void { + onTestFileResult({context}: Test, {testResults}: TestResult): void { testResults.forEach(result => { const title = [...result.ancestorTitles, result.title].join( titleSeparator, ); - result.failureMessages.forEach(failureMessage => { + result.retryReasons?.forEach((retryReason, index) => { this.#createAnnotation({ - ...this.#getMessageDetails(failureMessage, test.context.config), - title, - type: 'error', + ...this.#getMessageDetails(retryReason, context.config), + title: `[RETRY ${index + 1}] ${title}`, + type: 'warning', }); }); - result.retryReasons?.forEach((retryReason, index) => { + result.failureMessages.forEach(failureMessage => { this.#createAnnotation({ - ...this.#getMessageDetails(retryReason, test.context.config), - title: `[RETRY ${index + 1}] ${title}`, - type: 'warning', + ...this.#getMessageDetails(failureMessage, context.config), + title, + type: 'error', }); }); }); From fe273cd4fc6c18f88d77e5de0850ebcca87fc629 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 15:20:09 +0300 Subject: [PATCH 20/22] unit test --- .../src/__tests__/GitHubActionsReporter.test.ts | 10 +++++----- .../__snapshots__/GitHubActionsReporter.test.ts.snap | 11 +++++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts index c1a4113adf10..ddc81279417f 100644 --- a/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts +++ b/packages/jest-reporters/src/__tests__/GitHubActionsReporter.test.ts @@ -8,7 +8,7 @@ import type {Test, TestCaseResult, TestResult} from '@jest/test-result'; import GitHubActionsReporter from '../GitHubActionsReporter'; -jest.spyOn(process.stderr, 'write').mockImplementation(jest.fn()); +process.stderr.write = jest.fn(); afterEach(() => { jest.clearAllMocks(); @@ -117,19 +117,19 @@ describe('logs error annotation', () => { }); }); -describe('logs warning annotation', () => { +describe('logs warning annotation before logging errors', () => { test('when test result includes retry reasons', () => { reporter.onTestFileResult(testMeta, { testResults: [ { ...testCaseResult, - failureMessages: [], + failureMessages: [retryErrorMessage], retryReasons: [retryErrorMessage], }, ], } as TestResult); - expect(jest.mocked(process.stderr.write)).toBeCalledTimes(1); - expect(jest.mocked(process.stderr.write).mock.calls[0]).toMatchSnapshot(); + expect(jest.mocked(process.stderr.write)).toBeCalledTimes(2); + expect(jest.mocked(process.stderr.write).mock.calls).toMatchSnapshot(); }); }); diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap index e7bf77113fff..97227a70b656 100644 --- a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap +++ b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap @@ -24,10 +24,17 @@ Array [ ] `; -exports[`logs warning annotation when test result includes retry reasons 1`] = ` +exports[`logs warning annotation before logging errors when test result includes retry reasons 1`] = ` Array [ - " + Array [ + " ::warning file=/user/project/__tests__/example.test.js,line=19,title=[RETRY 1] example test::expect(received).toBeFalsy()%0A%0AReceived: true%0A%0A at Object.toBeFalsy (__tests__/example.test.js:19:20) ", + ], + Array [ + " +::error file=/user/project/__tests__/example.test.js,line=19,title=example test::expect(received).toBeFalsy()%0A%0AReceived: true%0A%0A at Object.toBeFalsy (__tests__/example.test.js:19:20) +", + ], ] `; From 40b847d8c9c39d63ccb59d9d4329a10e00d94970 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 15:36:21 +0300 Subject: [PATCH 21/22] reshape --- packages/jest-reporters/src/GitHubActionsReporter.ts | 2 +- .../__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jest-reporters/src/GitHubActionsReporter.ts b/packages/jest-reporters/src/GitHubActionsReporter.ts index 9a41264194ce..7d244d728852 100644 --- a/packages/jest-reporters/src/GitHubActionsReporter.ts +++ b/packages/jest-reporters/src/GitHubActionsReporter.ts @@ -38,7 +38,7 @@ export default class GitHubActionsReporter extends BaseReporter { result.retryReasons?.forEach((retryReason, index) => { this.#createAnnotation({ ...this.#getMessageDetails(retryReason, context.config), - title: `[RETRY ${index + 1}] ${title}`, + title: `RETRY ${index + 1}: ${title}`, type: 'warning', }); }); diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap index 97227a70b656..4201d36f388d 100644 --- a/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap +++ b/packages/jest-reporters/src/__tests__/__snapshots__/GitHubActionsReporter.test.ts.snap @@ -28,7 +28,7 @@ exports[`logs warning annotation before logging errors when test result includes Array [ Array [ " -::warning file=/user/project/__tests__/example.test.js,line=19,title=[RETRY 1] example test::expect(received).toBeFalsy()%0A%0AReceived: true%0A%0A at Object.toBeFalsy (__tests__/example.test.js:19:20) +::warning file=/user/project/__tests__/example.test.js,line=19,title=RETRY 1: example test::expect(received).toBeFalsy()%0A%0AReceived: true%0A%0A at Object.toBeFalsy (__tests__/example.test.js:19:20) ", ], Array [ From a5ac9e027afcd4c9b8b0609b3f78c63621fe8ee5 Mon Sep 17 00:00:00 2001 From: Tom Mrazauskas Date: Mon, 9 May 2022 15:38:12 +0300 Subject: [PATCH 22/22] clean up --- .../annotationsRetryExamples.test.ts | 22 ------------------- .../annotationsRetrySilencedExamples.test.ts | 20 ----------------- 2 files changed, 42 deletions(-) delete mode 100644 e2e/__tests__/annotationsRetryExamples.test.ts delete mode 100644 e2e/__tests__/annotationsRetrySilencedExamples.test.ts diff --git a/e2e/__tests__/annotationsRetryExamples.test.ts b/e2e/__tests__/annotationsRetryExamples.test.ts deleted file mode 100644 index e9d1bc9b690f..000000000000 --- a/e2e/__tests__/annotationsRetryExamples.test.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * 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. - */ - -let i = 0; - -jest.retryTimes(1, {logErrorsBeforeRetry: true}); - -describe('nested', () => { - test('logging retryTimes example', () => { - i++; - - if (i === 3) { - expect(true).toBeTruthy(); - } else { - expect(true).toBeFalsy(); - } - }); -}); diff --git a/e2e/__tests__/annotationsRetrySilencedExamples.test.ts b/e2e/__tests__/annotationsRetrySilencedExamples.test.ts deleted file mode 100644 index 98a0a86824c5..000000000000 --- a/e2e/__tests__/annotationsRetrySilencedExamples.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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. - */ - -let i = 0; - -jest.retryTimes(3); - -test('silent retryTimes example', () => { - i++; - - if (i === 3) { - expect(true).toBeTruthy(); - } else { - expect(true).toBeFalsy(); - } -});