Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make detailed errors opt-in #15016

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

### Features

- `[jest-circus, jest-cli]` [**BREAKING**] Only include detailed errors when `--detailed-errors-in-results` is provided ([#15016](https://github.com/jestjs/jest/pull/15016))
- `[jest-circus, jest-cli, jest-config]` Add `waitNextEventLoopTurnForUnhandledRejectionEvents` flag to minimise performance impact of correct detection of unhandled promise rejections introduced in [#14315](https://github.com/jestjs/jest/pull/14315) ([#14681](https://github.com/jestjs/jest/pull/14681))
- `[jest-circus]` Add a `waitBeforeRetry` option to `jest.retryTimes` ([#14738](https://github.com/jestjs/jest/pull/14738))
- `[jest-circus]` Add a `retryImmediately` option to `jest.retryTimes` ([#14696](https://github.com/jestjs/jest/pull/14696))
Expand Down
10 changes: 10 additions & 0 deletions docs/CLI.md
Expand Up @@ -176,6 +176,16 @@ Indicates which provider should be used to instrument code for coverage. Allowed

Print debugging info about your Jest config.

### `--detailedErrorsInResults`

Populates `errorsDetailed` field in the test results. Useful to some tooling like WebStorm but can result in circular reference errors under some test failure conditions.

:::note

This flag should generally be provided by tools like WebStorm automatically; you don't need to specify it yourself.

:::

### `--detectOpenHandles`

Attempt to collect and print open handles preventing Jest from exiting cleanly. Use this in cases where you need to use `--forceExit` in order for Jest to exit to potentially track down the reason. This implies `--runInBand`, making tests run serially. Implemented using [`async_hooks`](https://nodejs.org/api/async_hooks.html). This option has a significant performance penalty and should only be used for debugging.
Expand Down
Expand Up @@ -16,7 +16,7 @@ exports[`defining tests and hooks asynchronously throws 1`] = `
14 | });
15 | });

at eventHandler (../../packages/jest-circus/build/jestAdapterInit.js:148:38)
at eventHandler (../../packages/jest-circus/build/jestAdapterInit.js:153:38)
at test (__tests__/asyncDefinition.test.js:12:5)

● Test suite failed to run
Expand All @@ -31,7 +31,7 @@ exports[`defining tests and hooks asynchronously throws 1`] = `
15 | });
16 |

at eventHandler (../../packages/jest-circus/build/jestAdapterInit.js:114:38)
at eventHandler (../../packages/jest-circus/build/jestAdapterInit.js:119:38)
at afterAll (__tests__/asyncDefinition.test.js:13:5)

● Test suite failed to run
Expand All @@ -46,7 +46,7 @@ exports[`defining tests and hooks asynchronously throws 1`] = `
20 | });
21 |

at eventHandler (../../packages/jest-circus/build/jestAdapterInit.js:148:38)
at eventHandler (../../packages/jest-circus/build/jestAdapterInit.js:153:38)
at test (__tests__/asyncDefinition.test.js:18:3)

● Test suite failed to run
Expand All @@ -60,6 +60,6 @@ exports[`defining tests and hooks asynchronously throws 1`] = `
20 | });
21 |

at eventHandler (../../packages/jest-circus/build/jestAdapterInit.js:114:38)
at eventHandler (../../packages/jest-circus/build/jestAdapterInit.js:119:38)
at afterAll (__tests__/asyncDefinition.test.js:19:3)"
`;
55 changes: 55 additions & 0 deletions e2e/__tests__/__snapshots__/failures.test.ts.snap
@@ -1,5 +1,60 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`cause support is fine when there are no detailed errors in the results 1`] = `
"FAIL __tests__/errorWithCause.test.js
✕ error with cause in test
describe block
✕ error with cause in describe/it
✕ error with string cause in describe/it

● error with cause in test

error during f

10 |
11 | function buildErrorWithCause(message: string, opts: {cause: unknown}): Error {
> 12 | const error = new Error(message, opts);
| ^
13 | if (opts.cause !== error.cause) {
14 | // Error with cause not supported in legacy versions of node, we just polyfill it
15 | Object.assign(error, opts);

at buildErrorWithCause (__tests__/errorWithCause.test.js:12:17)
at buildErrorWithCause (__tests__/errorWithCause.test.js:27:11)
at Object.f (__tests__/errorWithCause.test.js:32:3)

● describe block › error with cause in describe/it

error during f

10 |
11 | function buildErrorWithCause(message: string, opts: {cause: unknown}): Error {
> 12 | const error = new Error(message, opts);
| ^
13 | if (opts.cause !== error.cause) {
14 | // Error with cause not supported in legacy versions of node, we just polyfill it
15 | Object.assign(error, opts);

at buildErrorWithCause (__tests__/errorWithCause.test.js:12:17)
at buildErrorWithCause (__tests__/errorWithCause.test.js:27:11)
at Object.f (__tests__/errorWithCause.test.js:37:5)

● describe block › error with string cause in describe/it

with string cause

10 |
11 | function buildErrorWithCause(message: string, opts: {cause: unknown}): Error {
> 12 | const error = new Error(message, opts);
| ^
13 | if (opts.cause !== error.cause) {
14 | // Error with cause not supported in legacy versions of node, we just polyfill it
15 | Object.assign(error, opts);

at buildErrorWithCause (__tests__/errorWithCause.test.js:12:17)
at Object.buildErrorWithCause (__tests__/errorWithCause.test.js:41:11)"
`;

exports[`not throwing Error objects 1`] = `
"FAIL __tests__/throwNumber.test.js
● Test suite failed to run
Expand Down
1 change: 1 addition & 0 deletions e2e/__tests__/__snapshots__/showConfig.test.ts.snap
Expand Up @@ -20,6 +20,7 @@ exports[`--showConfig outputs config info and exits 1`] = `
"clover"
],
"cwd": "<<REPLACED_ROOT_DIR>>",
"detailedErrorsInResults": false,
"detectLeaks": false,
"detectOpenHandles": false,
"errorOnDeprecated": false,
Expand Down
1 change: 1 addition & 0 deletions e2e/__tests__/failureDetailsProperty.test.ts
Expand Up @@ -16,6 +16,7 @@ const removeStackTraces = (stdout: string) =>

test('that the failureDetails property is set', () => {
const {stdout, stderr} = runJest('failureDetails-property', [
'--detailed-errors-in-results',
'tests.test.js',
]);

Expand Down
17 changes: 15 additions & 2 deletions e2e/__tests__/failures.test.ts
Expand Up @@ -89,15 +89,28 @@ test('works with snapshot failures with hint', () => {
expect(result.slice(0, result.indexOf('Snapshot Summary'))).toMatchSnapshot();
});

test('works with error with cause', () => {
test('cause support is fine when there are no detailed errors in the results', () => {
const {stderr} = runJest(dir, ['errorWithCause.test.js']);
const summary = normalizeDots(cleanStderr(stderr));

expect(summary).toMatchSnapshot();
});

test('works with error with cause', () => {
const {stderr} = runJest(dir, [
'--detailed-errors-in-results',
'errorWithCause.test.js',
]);
const summary = normalizeDots(cleanStderr(stderr));

expect(summary).toMatchSnapshot();
});

test('works with error with cause thrown outside tests', () => {
const {stderr} = runJest(dir, ['errorWithCauseInDescribe.test.js']);
const {stderr} = runJest(dir, [
'--detailed-errors-in-results',
'errorWithCauseInDescribe.test.js',
]);
const summary = normalizeDots(cleanStderr(stderr));

const sanitizedSummary = summary
Expand Down
Expand Up @@ -176,6 +176,9 @@ const config: Config = {
// A path to a custom dependency extractor
// dependencyExtractor: undefined,

// Add detailed error information to the test results for tooling
// detailedErrorsInResults: false,

// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,

Expand Down Expand Up @@ -380,6 +383,9 @@ const config = {
// A path to a custom dependency extractor
// dependencyExtractor: undefined,

// Add detailed error information to the test results for tooling
// detailedErrorsInResults: false,

// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,

Expand Down Expand Up @@ -584,6 +590,9 @@ const config = {
// A path to a custom dependency extractor
// dependencyExtractor: undefined,

// Add detailed error information to the test results for tooling
// detailedErrorsInResults: false,

// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,

Expand Down
4 changes: 4 additions & 0 deletions packages/jest-circus/src/eventHandler.ts
Expand Up @@ -26,6 +26,10 @@ const eventHandler: Circus.EventHandler = (event, state) => {
state.includeTestLocationInResult = true;
break;
}
case 'include_detailed_errors_in_result': {
state.includeDetailedErrorsInResult = true;
break;
}
case 'hook_start': {
event.hook.seenDone = false;
break;
Expand Down
Expand Up @@ -110,6 +110,10 @@ export const initialize = async ({
await dispatch({name: 'include_test_location_in_result'});
}

if (config.detailedErrorsInResults) {
await dispatch({name: 'include_detailed_errors_in_result'});
}

// Jest tests snapshotSerializers in order preceding built-in serializers.
// Therefore, add in reverse because the last added is the first tested.
for (const path of [...config.snapshotSerializers].reverse())
Expand Down
1 change: 1 addition & 0 deletions packages/jest-circus/src/state.ts
Expand Up @@ -26,6 +26,7 @@ const createState = (): Circus.State => {
expand: undefined,
hasFocusedTests: false,
hasStarted: false,
includeDetailedErrorsInResult: false,
includeTestLocationInResult: false,
maxConcurrency: 5,
parentProcess: null,
Expand Down
5 changes: 3 additions & 2 deletions packages/jest-circus/src/utils.ts
Expand Up @@ -343,7 +343,8 @@ const getTestNamesPath = (test: Circus.TestEntry): Circus.TestNamesPath => {
export const makeSingleTestResult = (
test: Circus.TestEntry,
): Circus.TestResult => {
const {includeTestLocationInResult} = getState();
const {includeTestLocationInResult, includeDetailedErrorsInResult} =
getState();

const {status} = test;
invariant(status, 'Status should be present after tests are run.');
Expand Down Expand Up @@ -376,7 +377,7 @@ export const makeSingleTestResult = (
return {
duration: test.duration,
errors: errorsDetailed.map(getErrorStack),
errorsDetailed,
errorsDetailed: includeDetailedErrorsInResult ? errorsDetailed : [],
failing: test.failing,
invocations: test.invocations,
location,
Expand Down
5 changes: 5 additions & 0 deletions packages/jest-cli/src/args.ts
Expand Up @@ -236,6 +236,11 @@ export const options: {[key: string]: Options} = {
description: 'Print debugging info about your jest config.',
type: 'boolean',
},
detailedErrorsInResults: {
description:
'Add detailed error information to the test results for tooling',
type: 'boolean',
},
detectLeaks: {
description:
'**EXPERIMENTAL**: Detect memory leaks in tests. After executing a ' +
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/Defaults.ts
Expand Up @@ -26,6 +26,7 @@ const defaultOptions: Config.DefaultOptions = {
coveragePathIgnorePatterns: [NODE_MODULES_REGEXP],
coverageProvider: 'babel',
coverageReporters: ['json', 'text', 'lcov', 'clover'],
detailedErrorsInResults: false,
detectLeaks: false,
detectOpenHandles: false,
errorOnDeprecated: false,
Expand Down
2 changes: 2 additions & 0 deletions packages/jest-config/src/Descriptions.ts
Expand Up @@ -29,6 +29,8 @@ const descriptions: {[key in keyof Config.InitialOptions]: string} = {
coverageThreshold:
'An object that configures minimum threshold enforcement for coverage results',
dependencyExtractor: 'A path to a custom dependency extractor',
detailedErrorsInResults:
'Add detailed error information to the test results for tooling',
errorOnDeprecated:
'Make calling deprecated APIs throw helpful error messages',
fakeTimers: 'The default configuration for fake timers',
Expand Down
2 changes: 2 additions & 0 deletions packages/jest-config/src/ValidConfig.ts
Expand Up @@ -37,6 +37,7 @@ export const initialOptions: Config.InitialOptions = {
},
},
dependencyExtractor: '<rootDir>/dependencyExtractor.js',
detailedErrorsInResults: false,
detectLeaks: false,
detectOpenHandles: false,
displayName: multipleValidOptions('test-config', {
Expand Down Expand Up @@ -210,6 +211,7 @@ export const initialProjectOptions: Config.InitialProjectOptions = {
coveragePathIgnorePatterns: [NODE_MODULES_REGEXP],
coverageReporters: ['json', 'text', 'lcov', 'clover'],
dependencyExtractor: '<rootDir>/dependencyExtractor.js',
detailedErrorsInResults: false,
detectLeaks: false,
detectOpenHandles: false,
displayName: multipleValidOptions('test-config', {
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/index.ts
Expand Up @@ -158,6 +158,7 @@ const groupOptions = (
coverageReporters: options.coverageReporters,
cwd: options.cwd,
dependencyExtractor: options.dependencyExtractor,
detailedErrorsInResults: options.detailedErrorsInResults,
detectLeaks: options.detectLeaks,
detectOpenHandles: options.detectOpenHandles,
displayName: options.displayName,
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/normalize.ts
Expand Up @@ -898,6 +898,7 @@ export default async function normalize(
case 'coverageProvider':
case 'coverageReporters':
case 'coverageThreshold':
case 'detailedErrorsInResults':
case 'detectLeaks':
case 'detectOpenHandles':
case 'errorOnDeprecated':
Expand Down
Expand Up @@ -15,6 +15,7 @@ exports[`prints the config object 1`] = `
"coveragePathIgnorePatterns": [],
"coverageReporters": [],
"cwd": "/test_root_dir/",
"detailedErrorsInResults": false,
"detectLeaks": false,
"detectOpenHandles": false,
"errorOnDeprecated": false,
Expand Down
5 changes: 4 additions & 1 deletion packages/jest-jasmine2/src/reporter.ts
Expand Up @@ -160,7 +160,10 @@ export default class Jasmine2Reporter implements Reporter {
? this._addMissingMessageToStack(failed.stack, failed.message)
: failed.message || '';
results.failureMessages.push(message);
results.failureDetails.push(failed);

if (this._config.detailedErrorsInResults) {
results.failureDetails.push(failed);
}
}

return results;
Expand Down
1 change: 1 addition & 0 deletions packages/jest-schemas/src/raw-types.ts
Expand Up @@ -242,6 +242,7 @@ export const RawInitialOptions = Type.Partial(
coverageProvider: RawCoverageProvider,
coverageReporters: RawCoverageReporters,
coverageThreshold: RawCoverageThreshold,
detailedErrorsInResults: Type.Boolean(),
dependencyExtractor: Type.String(),
detectLeaks: Type.Boolean(),
detectOpenHandles: Type.Boolean(),
Expand Down