Skip to content

Commit

Permalink
fix: improve open handles detection
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed Feb 20, 2020
1 parent 56565cc commit b868ad1
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 28 deletions.
4 changes: 2 additions & 2 deletions e2e/__tests__/__snapshots__/detectOpenHandles.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-core/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
26 changes: 3 additions & 23 deletions packages/jest-core/src/collectHandles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -57,7 +35,7 @@ export default function collectHandles(): () => Array<Error> {
}
const error = new ErrorWithStack(type, initHook);

if (stackIsFromUser(error.stack || '')) {
if ((error.stack || '').trim().length > 0) {
let isActive: () => boolean;

if (type === 'Timeout' || type === 'Immediate') {
Expand Down Expand Up @@ -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)
);
}
5 changes: 5 additions & 0 deletions packages/jest-core/src/runJest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
17 changes: 15 additions & 2 deletions packages/jest-message-util/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RegExp> = [];
let nodeInternals: ReadonlyArray<RegExp> = [];

try {
nodeInternals = StackUtils.nodeInternals();
// `reduce` is workaround for https://github.com/tapjs/stack-utils/issues/48
nodeInternals = StackUtils.nodeInternals().reduce<Array<RegExp>>(
(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.
Expand Down

0 comments on commit b868ad1

Please sign in to comment.