Skip to content

Commit

Permalink
fix: handle open handles from inside tests
Browse files Browse the repository at this point in the history
  • Loading branch information
SimenB committed May 27, 2018
1 parent 2163e61 commit 5f8e7b0
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Fixes

* `[jest-cli]` Show open handles from inside test files as well ([#6263](https://github.com/facebook/jest/pull/6263))
* `[pretty-format]` Serialize inverse asymmetric matchers correctly ([#6272](https://github.com/facebook/jest/pull/6272))

## 23.0.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,18 @@ exports[`prints message about flag on slow tests 1`] = `
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with \`--detectOpenHandles\` to troubleshoot this issue."
`;

exports[`prints out info about open handlers from inside tests 1`] = `
"Jest has detected the following 1 open handle potentially keeping Jest from exiting:
● Timeout
1 | test('something', () => {
> 2 | setTimeout(() => {}, 30000);
| ^
3 | expect(true).toBe(true);
4 | });
5 |
at Object.<anonymous>.test (__tests__/inside.js:2:3)"
`;
21 changes: 16 additions & 5 deletions integration-tests/__tests__/detect_open_handles.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ try {
}

function getTextAfterTest(stderr) {
return stderr.split('Ran all test suites.')[1].trim();
return stderr.split(/Ran all test suites(.*)\n/)[2].trim();
}

it('prints message about flag on slow tests', async () => {
const {stderr} = await runJest.until(
'detect-open-handles',
[],
['outside'],
'Jest did not exit one second after the test run has completed.',
);
const textAfterTest = getTextAfterTest(stderr);
Expand All @@ -42,7 +42,7 @@ it('prints message about flag on slow tests', async () => {
it('prints message about flag on forceExit', async () => {
const {stderr} = await runJest.until(
'detect-open-handles',
['--forceExit'],
['outside', '--forceExit'],
'Force exiting Jest',
);
const textAfterTest = getTextAfterTest(stderr);
Expand All @@ -53,7 +53,7 @@ it('prints message about flag on forceExit', async () => {
it('prints out info about open handlers', async () => {
const {stderr} = await runJest.until(
'detect-open-handles',
['--detectOpenHandles'],
['outside', '--detectOpenHandles'],
'Jest has detected',
);
const textAfterTest = getTextAfterTest(stderr);
Expand All @@ -70,7 +70,18 @@ it('prints out info about open handlers', async () => {
8 |
at Object.<anonymous> (server.js:7:5)
at Object.<anonymous> (__tests__/test.js:1:1)
at Object.<anonymous> (__tests__/outside.js:1:1)
`.trim(),
);
});

it('prints out info about open handlers from inside tests', async () => {
const {stderr} = await runJest.until(
'detect-open-handles',
['inside', '--detectOpenHandles'],
'Jest has detected',
);
const textAfterTest = getTextAfterTest(stderr);

expect(textAfterTest).toMatchSnapshot();
});
4 changes: 4 additions & 0 deletions integration-tests/detect-open-handles/__tests__/inside.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
test('something', () => {
setTimeout(() => {}, 30000);
expect(true).toBe(true);
});
34 changes: 32 additions & 2 deletions packages/jest-cli/src/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {validateCLIOptions} from 'jest-validate';
import {readConfig, deprecationEntries} from 'jest-config';
import * as args from './args';
import chalk from 'chalk';
import stripAnsi from 'strip-ansi';
import createContext from '../lib/create_context';
import exit from 'exit';
import getChangedFilesPromise from '../get_changed_files_promise';
Expand Down Expand Up @@ -103,12 +104,41 @@ export const runCLI = async (
const {openHandles} = results;

if (openHandles && openHandles.length) {
const openHandlesString = pluralize('open handle', openHandles.length, 's');
const formatted = formatHandleErrors(openHandles, configs[0]);

const stacks = new Set();

// E.g. timeouts might give multiple traces to the same line of code
// This hairy filtering tries to remove entries with duplicate stack traces
const uniqueErrors = formatted.filter(handle => {
const ansiFree: string = stripAnsi(handle);

const match = ansiFree.match(/\s+at(.*)/);

if (!match || match.length < 2) {
return true;
}

const stack = ansiFree.substr(ansiFree.indexOf(match[1])).trim();

if (stacks.has(stack)) {
return false;
}

stacks.add(stack);

return true;
});
const openHandlesString = pluralize(
'open handle',
uniqueErrors.length,
's',
);

const message =
chalk.red(
`\nJest has detected the following ${openHandlesString} potentially keeping Jest from exiting:\n\n`,
) + formatHandleErrors(openHandles, configs[0]).join('\n\n');
) + uniqueErrors.join('\n\n');

console.error(message);
}
Expand Down
6 changes: 5 additions & 1 deletion packages/jest-cli/src/get_node_handles.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@ export default function collectHandles(): () => Array<Error> {
Error.captureStackTrace(error, initHook);
}

if (error.stack.includes('Runtime.requireModule')) {
if (
error.stack.includes('Runtime.requireModule') ||
error.stack.includes('asyncJestTest') ||
error.stack.includes('asyncJestLifecycle')
) {
activeHandles.set(asyncId, error);
}
}
Expand Down
8 changes: 4 additions & 4 deletions packages/jest-jasmine2/src/jasmine_async.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function promisifyLifeCycleFunction(originalFn, env) {

// We make *all* functions async and run `done` right away if they
// didn't return a promise.
const asyncFn = function(done) {
const asyncJestLifecycle = function(done) {
const wrappedFn = isGeneratorFn(fn) ? co.wrap(fn) : fn;
const returnValue = wrappedFn.call({});

Expand All @@ -57,7 +57,7 @@ function promisifyLifeCycleFunction(originalFn, env) {
}
};

return originalFn.call(env, asyncFn, timeout);
return originalFn.call(env, asyncJestLifecycle, timeout);
};
}

Expand All @@ -79,7 +79,7 @@ function promisifyIt(originalFn, env) {

const extraError = new Error();

const asyncFn = function(done) {
const asyncJestTest = function(done) {
const wrappedFn = isGeneratorFn(fn) ? co.wrap(fn) : fn;
const returnValue = wrappedFn.call({});

Expand All @@ -103,7 +103,7 @@ function promisifyIt(originalFn, env) {
}
};

return originalFn.call(env, specName, asyncFn, timeout);
return originalFn.call(env, specName, asyncJestTest, timeout);
};
}

Expand Down

0 comments on commit 5f8e7b0

Please sign in to comment.