Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a92dae2

Browse files
committedMay 27, 2018
fix: handle open handles from inside tests
1 parent f527647 commit a92dae2

File tree

8 files changed

+78
-14
lines changed

8 files changed

+78
-14
lines changed
 

‎CHANGELOG.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
### Fixes
44

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

88
## 23.0.0
99

‎integration-tests/__tests__/__snapshots__/detect_open_handles.js.snap

+15
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,18 @@ exports[`prints message about flag on slow tests 1`] = `
1111
1212
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with \`--detectOpenHandles\` to troubleshoot this issue."
1313
`;
14+
15+
exports[`prints out info about open handlers from inside tests 1`] = `
16+
"Jest has detected the following 1 open handle potentially keeping Jest from exiting:
17+
18+
● Timeout
19+
20+
1 | test('something', () => {
21+
> 2 | setTimeout(() => {}, 30000);
22+
| ^
23+
3 | expect(true).toBe(true);
24+
4 | });
25+
5 |
26+
27+
at Object.<anonymous>.test (__tests__/inside.js:2:3)"
28+
`;

‎integration-tests/__tests__/detect_open_handles.js

+16-5
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ try {
2525
}
2626

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

3131
it('prints message about flag on slow tests', async () => {
3232
const {stderr} = await runJest.until(
3333
'detect-open-handles',
34-
[],
34+
['outside'],
3535
'Jest did not exit one second after the test run has completed.',
3636
);
3737
const textAfterTest = getTextAfterTest(stderr);
@@ -42,7 +42,7 @@ it('prints message about flag on slow tests', async () => {
4242
it('prints message about flag on forceExit', async () => {
4343
const {stderr} = await runJest.until(
4444
'detect-open-handles',
45-
['--forceExit'],
45+
['outside', '--forceExit'],
4646
'Force exiting Jest',
4747
);
4848
const textAfterTest = getTextAfterTest(stderr);
@@ -53,7 +53,7 @@ it('prints message about flag on forceExit', async () => {
5353
it('prints out info about open handlers', async () => {
5454
const {stderr} = await runJest.until(
5555
'detect-open-handles',
56-
['--detectOpenHandles'],
56+
['outside', '--detectOpenHandles'],
5757
'Jest has detected',
5858
);
5959
const textAfterTest = getTextAfterTest(stderr);
@@ -70,7 +70,18 @@ it('prints out info about open handlers', async () => {
7070
8 |
7171
7272
at Object.<anonymous> (server.js:7:5)
73-
at Object.<anonymous> (__tests__/test.js:1:1)
73+
at Object.<anonymous> (__tests__/outside.js:1:1)
7474
`.trim(),
7575
);
7676
});
77+
78+
it('prints out info about open handlers from inside tests', async () => {
79+
const {stderr} = await runJest.until(
80+
'detect-open-handles',
81+
['inside', '--detectOpenHandles'],
82+
'Jest has detected',
83+
);
84+
const textAfterTest = getTextAfterTest(stderr);
85+
86+
expect(textAfterTest).toMatchSnapshot();
87+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
test('something', () => {
2+
setTimeout(() => {}, 30000);
3+
expect(true).toBe(true);
4+
});

‎packages/jest-cli/src/cli/index.js

+32-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {validateCLIOptions} from 'jest-validate';
1616
import {readConfig, deprecationEntries} from 'jest-config';
1717
import * as args from './args';
1818
import chalk from 'chalk';
19+
import stripAnsi from 'strip-ansi';
1920
import createContext from '../lib/create_context';
2021
import exit from 'exit';
2122
import getChangedFilesPromise from '../get_changed_files_promise';
@@ -103,12 +104,41 @@ export const runCLI = async (
103104
const {openHandles} = results;
104105

105106
if (openHandles && openHandles.length) {
106-
const openHandlesString = pluralize('open handle', openHandles.length, 's');
107+
const formatted = formatHandleErrors(openHandles, configs[0]);
108+
109+
const stacks = new Set();
110+
111+
// E.g. timeouts might give multiple traces to the same line of code
112+
// This hairy filtering tries to remove entries with duplicate stack traces
113+
const uniqueErrors = formatted.filter(handle => {
114+
const ansiFree: string = stripAnsi(handle);
115+
116+
const match = ansiFree.match(/\s+at(.*)/);
117+
118+
if (!match || match.length < 2) {
119+
return true;
120+
}
121+
122+
const stack = ansiFree.substr(ansiFree.indexOf(match[1])).trim();
123+
124+
if (stacks.has(stack)) {
125+
return false;
126+
}
127+
128+
stacks.add(stack);
129+
130+
return true;
131+
});
132+
const openHandlesString = pluralize(
133+
'open handle',
134+
uniqueErrors.length,
135+
's',
136+
);
107137

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

113143
console.error(message);
114144
}

‎packages/jest-cli/src/get_node_handles.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ export default function collectHandles(): () => Array<Error> {
2323
Error.captureStackTrace(error, initHook);
2424
}
2525

26-
if (error.stack.includes('Runtime.requireModule')) {
26+
if (
27+
error.stack.includes('Runtime.requireModule') ||
28+
error.stack.includes('asyncJestTest') ||
29+
error.stack.includes('asyncJestLifecycle')
30+
) {
2731
activeHandles.set(asyncId, error);
2832
}
2933
}

‎packages/jest-jasmine2/src/jasmine_async.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function promisifyLifeCycleFunction(originalFn, env) {
3939

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

@@ -57,7 +57,7 @@ function promisifyLifeCycleFunction(originalFn, env) {
5757
}
5858
};
5959

60-
return originalFn.call(env, asyncFn, timeout);
60+
return originalFn.call(env, asyncJestLifecycle, timeout);
6161
};
6262
}
6363

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

8080
const extraError = new Error();
8181

82-
const asyncFn = function(done) {
82+
const asyncJestTest = function(done) {
8383
const wrappedFn = isGeneratorFn(fn) ? co.wrap(fn) : fn;
8484
const returnValue = wrappedFn.call({});
8585

@@ -103,7 +103,7 @@ function promisifyIt(originalFn, env) {
103103
}
104104
};
105105

106-
return originalFn.call(env, specName, asyncFn, timeout);
106+
return originalFn.call(env, specName, asyncJestTest, timeout);
107107
};
108108
}
109109

0 commit comments

Comments
 (0)