diff --git a/e2e/__tests__/__snapshots__/detectOpenHandles.ts.snap b/e2e/__tests__/__snapshots__/detectOpenHandles.ts.snap index adde74db4e47..b507a15e4a95 100644 --- a/e2e/__tests__/__snapshots__/detectOpenHandles.ts.snap +++ b/e2e/__tests__/__snapshots__/detectOpenHandles.ts.snap @@ -9,7 +9,7 @@ This usually means that there are asynchronous operations that weren't stopped i `; exports[`prints out info about open handlers 1`] = ` -Jest has detected the following 1 open handle potentially keeping Jest from exiting: +Jest has detected the following 1 open handle potentially preventing Jest from exiting: ● GETADDRINFOREQWRAP @@ -24,7 +24,7 @@ Jest has detected the following 1 open handle potentially keeping Jest from exit `; exports[`prints out info about open handlers from inside tests 1`] = ` -Jest has detected the following 1 open handle potentially keeping Jest from exiting: +Jest has detected the following 1 open handle potentially preventing Jest from exiting: ● Timeout diff --git a/packages/jest-core/src/cli/index.ts b/packages/jest-core/src/cli/index.ts index 49d0b990c7cf..335501528759 100644 --- a/packages/jest-core/src/cli/index.ts +++ b/packages/jest-core/src/cli/index.ts @@ -102,7 +102,7 @@ export async function runCLI( const message = chalk.red( - `\nJest has detected the following ${openHandlesString} potentially keeping Jest from exiting:\n\n`, + `\nJest has detected the following ${openHandlesString} potentially preventing Jest from exiting:\n\n`, ) + formatted.join('\n\n'); console.error(message); diff --git a/packages/jest-core/src/collectHandles.ts b/packages/jest-core/src/collectHandles.ts index bbb1d92f5819..ec219db1264a 100644 --- a/packages/jest-core/src/collectHandles.ts +++ b/packages/jest-core/src/collectHandles.ts @@ -11,28 +11,6 @@ import {formatExecError} from 'jest-message-util'; import {ErrorWithStack} from 'jest-util'; import stripAnsi = require('strip-ansi'); -function stackIsFromUser(stack: string) { - // Either the test file, or something required by it - if (stack.includes('Runtime.requireModule')) { - return true; - } - - // jest-jasmine it or describe call - if (stack.includes('asyncJestTest') || stack.includes('asyncJestLifecycle')) { - return true; - } - - // An async function call from within circus - if (stack.includes('callAsyncCircusFn')) { - // jest-circus it or describe call - return ( - stack.includes('_callCircusTest') || stack.includes('_callCircusHook') - ); - } - - return false; -} - const alwaysActive = () => true; // Inspired by https://github.com/mafintosh/why-is-node-running/blob/master/index.js @@ -57,7 +35,7 @@ export default function collectHandles(): () => Array { } const error = new ErrorWithStack(type, initHook); - if (stackIsFromUser(error.stack || '')) { + if ((error.stack || '').trim().length > 0) { let isActive: () => boolean; if (type === 'Timeout' || type === 'Immediate') { @@ -125,5 +103,7 @@ export function formatHandleErrors( return true; }) + // only keep stacks with at least one frame. `length === 1` means just the heading (normally meaning node internal), which is useless + .filter(stack => stack.trim().split('\n').length > 1) ); } diff --git a/packages/jest-core/src/runJest.ts b/packages/jest-core/src/runJest.ts index c8fbb8754fc3..d630dcaf7042 100644 --- a/packages/jest-core/src/runJest.ts +++ b/packages/jest-core/src/runJest.ts @@ -260,6 +260,11 @@ export default async function runJest({ await runGlobalHook({allTests, globalConfig, moduleName: 'globalTeardown'}); } + if (collectHandles) { + // wait 100ms to allow some handles to be cleaned up, including Jest's internal timeouts + await new Promise(resolve => setTimeout(resolve, 100)); + } + await processResults(results, { collectHandles, json: globalConfig.json, diff --git a/packages/jest-message-util/src/index.ts b/packages/jest-message-util/src/index.ts index f83136f197e0..1ef76e971f1f 100644 --- a/packages/jest-message-util/src/index.ts +++ b/packages/jest-message-util/src/index.ts @@ -23,10 +23,23 @@ type Path = Config.Path; // stack utils tries to create pretty stack by making paths relative. const stackUtils = new StackUtils({cwd: 'something which does not exist'}); -let nodeInternals: Array = []; +let nodeInternals: ReadonlyArray = []; try { - nodeInternals = StackUtils.nodeInternals(); + // `reduce` is workaround for https://github.com/tapjs/stack-utils/issues/48 + nodeInternals = StackUtils.nodeInternals().reduce>( + (internals, internal) => { + const sourceWithoutParens = internal.source + // remove leading and trailing parens (which are escaped) and the $ + .slice(2, internal.source.length - 3); + + return internals.concat( + internal, + new RegExp(`at ${sourceWithoutParens}$`), + ); + }, + [], + ); } catch (e) { // `StackUtils.nodeInternals()` fails in browsers. We don't need to remove // node internals in the browser though, so no issue.