Skip to content

Commit 4ca03cd

Browse files
authored
Add --detectOpenHandles flag (#6130)
* why-running * add --detectOpenHandles flag * move processing of w-i-n-r into separate function * pretty-print open handles * docs * update snapshots * ensure empty when flag not used * please flow gods * add pretty colors * less if * use pluralize * Revert "less if" This reverts commit dd71548. * extract formatting of why-running * cmon flow, be nice * use code frame * Implement async_hooks manually * nudge users towards new feature * make handlers better match other error output * add tests * PR feeback * really fic PR feedback
1 parent 364e241 commit 4ca03cd

30 files changed

Lines changed: 382 additions & 34 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
### Features
44

5+
* `[jest-cli]` Add `--detectOpenHandles` flag which enables Jest to potentially
6+
track down handles keeping it open after tests are complete.
7+
([#6130](https://github.com/facebook/jest/pull/6130))
58
* `[jest-jasmine2]` Add data driven testing based on `jest-each`
69
([#6102](https://github.com/facebook/jest/pull/6102))
710
* `[jest-matcher-utils]` Change "suggest to equal" message to be more advisory

TestUtils.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const DEFAULT_GLOBAL_CONFIG: GlobalConfig = {
2222
coverageReporters: [],
2323
coverageThreshold: {global: {}},
2424
detectLeaks: false,
25+
detectOpenHandles: false,
2526
enabledTestsMap: null,
2627
expand: false,
2728
filter: null,
@@ -72,6 +73,7 @@ const DEFAULT_PROJECT_CONFIG: ProjectConfig = {
7273
coveragePathIgnorePatterns: [],
7374
cwd: '/test_root_dir/',
7475
detectLeaks: false,
76+
detectOpenHandles: false,
7577
displayName: undefined,
7678
filter: null,
7779
forceCoverageMatch: [],

docs/CLI.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,14 @@ output.
172172

173173
Print debugging info about your Jest config.
174174

175+
### `--detectOpenHandles`
176+
177+
Attempt to collect and print open handles preventing Jest from exiting cleanly.
178+
Use this in cases where you need to use `--forceExit` in order for Jest to exit
179+
to potentially track down the reason. Implemented using
180+
[`async_hooks`](https://nodejs.org/api/async_hooks.html), so it only works in
181+
Node 8 and newer.
182+
175183
### `--env=<environment>`
176184

177185
The test environment used for all tests. This can point to any file or node
@@ -196,7 +204,8 @@ resources set up by test code cannot be adequately cleaned up. _Note: This
196204
feature is an escape-hatch. If Jest doesn't exit at the end of a test run, it
197205
means external resources are still being held on to or timers are still pending
198206
in your code. It is advised to tear down external resources after each test to
199-
make sure Jest can shut down cleanly._
207+
make sure Jest can shut down cleanly. You can use `--detectOpenHandles` to help
208+
track it down._
200209

201210
### `--help`
202211

docs/Configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,7 @@ structure as the first argument and return it:
963963
"numPassedTests": number,
964964
"numFailedTests": number,
965965
"numPendingTests": number,
966+
"openHandles": Array<Error>,
966967
"testResults": [{
967968
"numFailingTests": number,
968969
"numPassingTests": number,

integration-tests/Utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ const extractSummary = (
156156

157157
let rest = cleanupStackTrace(
158158
// remove all timestamps
159-
stdout.slice(0, -match[0].length).replace(/\s*\(\d*\.?\d+m?s\)$/gm, ''),
159+
stdout.replace(match[0], '').replace(/\s*\(\d*\.?\d+m?s\)$/gm, ''),
160160
);
161161

162162
if (stripLocation) {

integration-tests/__tests__/__snapshots__/cli-handles-exact-filenames.test.js.snap

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ exports[`CLI accepts exact file names if matchers matched 1`] = `
44
"PASS foo/bar.spec.js
55
✓ foo
66
7-
"
7+
8+
Force exiting Jest
9+
10+
Have you considered using \`--detectOpenHandles\` to detect async operations that kept running after all tests finished?"
811
`;
912

1013
exports[`CLI accepts exact file names if matchers matched 2`] = `

integration-tests/__tests__/__snapshots__/console_log_output_when_run_in_band.test.js.snap

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ exports[`prints console.logs when run with forceExit 1`] = `
44
"PASS __tests__/a-banana.js
55
✓ banana
66
7-
"
7+
8+
Force exiting Jest
9+
10+
Have you considered using \`--detectOpenHandles\` to detect async operations that kept running after all tests finished?"
811
`;
912

1013
exports[`prints console.logs when run with forceExit 2`] = `
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`prints message about flag on forceExit 1`] = `
4+
"Force exiting Jest
5+
6+
Have you considered using \`--detectOpenHandles\` to detect async operations that kept running after all tests finished?"
7+
`;
8+
9+
exports[`prints message about flag on slow tests 1`] = `
10+
"Jest did not exit one second after the test run has completed.
11+
12+
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with \`--detectOpenHandles\` to troubleshoot this issue."
13+
`;
14+
15+
exports[`prints out info about open handlers 1`] = `
16+
"Jest has detected the following 1 open handle potentially keeping Jest from exiting:
17+
18+
● GETADDRINFOREQWRAP
19+
20+
5 | const app = new http.Server();
21+
6 |
22+
> 7 | app.listen({host: 'localhost', port: 0});
23+
| ^
24+
8 |
25+
26+
at Object.<anonymous> (server.js:7:5)
27+
at Object.<anonymous> (__tests__/test.js:3:1)"
28+
`;

integration-tests/__tests__/__snapshots__/show_config.test.js.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ exports[`--showConfig outputs config info and exits 1`] = `
1313
\\"/node_modules/\\"
1414
],
1515
\\"detectLeaks\\": false,
16+
\\"detectOpenHandles\\": false,
1617
\\"filter\\": null,
1718
\\"forceCoverageMatch\\": [],
1819
\\"globals\\": {},
@@ -79,6 +80,7 @@ exports[`--showConfig outputs config info and exits 1`] = `
7980
\\"clover\\"
8081
],
8182
\\"detectLeaks\\": false,
83+
\\"detectOpenHandles\\": false,
8284
\\"expand\\": false,
8385
\\"filter\\": null,
8486
\\"globalSetup\\": null,
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
'use strict';
10+
11+
const runJest = require('../runJest');
12+
13+
try {
14+
// $FlowFixMe: Node core
15+
require('async_hooks');
16+
} catch (e) {
17+
if (e.code === 'MODULE_NOT_FOUND') {
18+
// eslint-disable-next-line jest/no-focused-tests
19+
fit('skip test for unsupported nodes', () => {
20+
console.warn('Skipping test for node ' + process.version);
21+
});
22+
} else {
23+
throw e;
24+
}
25+
}
26+
27+
function getTextAfterTest(stderr) {
28+
return stderr.split('Ran all test suites.')[1].trim();
29+
}
30+
31+
it('prints message about flag on slow tests', async () => {
32+
const {stderr} = await runJest.until(
33+
'detect-open-handles',
34+
[],
35+
'Jest did not exit one second after the test run has completed.',
36+
);
37+
const textAfterTest = getTextAfterTest(stderr);
38+
39+
expect(textAfterTest).toMatchSnapshot();
40+
});
41+
42+
it('prints message about flag on forceExit', async () => {
43+
const {stderr} = await runJest.until(
44+
'detect-open-handles',
45+
['--forceExit'],
46+
'Force exiting Jest',
47+
);
48+
const textAfterTest = getTextAfterTest(stderr);
49+
50+
expect(textAfterTest).toMatchSnapshot();
51+
});
52+
53+
it('prints out info about open handlers', async () => {
54+
const {stderr} = await runJest.until(
55+
'detect-open-handles',
56+
['--detectOpenHandles'],
57+
'Jest has detected',
58+
);
59+
const textAfterTest = getTextAfterTest(stderr);
60+
61+
expect(textAfterTest).toMatchSnapshot();
62+
});

0 commit comments

Comments
 (0)