From d1ab5c3629bd9c358c7517bf0f60cff544b47866 Mon Sep 17 00:00:00 2001 From: liuxingbaoyu <30521560+liuxingbaoyu@users.noreply.github.com> Date: Mon, 10 Oct 2022 09:37:50 +0800 Subject: [PATCH 1/6] improve --- .../__snapshots__/detectOpenHandles.ts.snap | 2 +- .../src/__tests__/collectHandles.test.js | 5 +- packages/jest-core/src/collectHandles.ts | 84 ++++++++++--------- 3 files changed, 50 insertions(+), 41 deletions(-) diff --git a/e2e/__tests__/__snapshots__/detectOpenHandles.ts.snap b/e2e/__tests__/__snapshots__/detectOpenHandles.ts.snap index c6f4f2ec3518..f9da6c8306a0 100644 --- a/e2e/__tests__/__snapshots__/detectOpenHandles.ts.snap +++ b/e2e/__tests__/__snapshots__/detectOpenHandles.ts.snap @@ -13,7 +13,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: - ● TCPSERVERWRAP + ● DNSCHANNEL,TCPSERVERWRAP 12 | const app = new Server(); 13 | diff --git a/packages/jest-core/src/__tests__/collectHandles.test.js b/packages/jest-core/src/__tests__/collectHandles.test.js index db56aeff2184..f98fe5d41ce4 100644 --- a/packages/jest-core/src/__tests__/collectHandles.test.js +++ b/packages/jest-core/src/__tests__/collectHandles.test.js @@ -45,9 +45,12 @@ describe('collectHandles', () => { it('should not collect the DNSCHANNEL open handle', async () => { const handleCollector = collectHandles(); - const resolver = new dns.Resolver(); + let resolver = new dns.Resolver(); resolver.getServers(); + // We must drop references to it + resolver = null; + const openHandles = await handleCollector(); expect(openHandles).not.toContainEqual( diff --git a/packages/jest-core/src/collectHandles.ts b/packages/jest-core/src/collectHandles.ts index f5e79eafed9e..16a727832569 100644 --- a/packages/jest-core/src/collectHandles.ts +++ b/packages/jest-core/src/collectHandles.ts @@ -83,16 +83,7 @@ export default function collectHandles(): HandleCollectionResult { // Skip resources that should not generally prevent the process from // exiting, not last a meaningfully long time, or otherwise shouldn't be // tracked. - if ( - type === 'PROMISE' || - type === 'TIMERWRAP' || - type === 'ELDHISTOGRAM' || - type === 'PerformanceObserver' || - type === 'RANDOMBYTESREQUEST' || - type === 'DNSCHANNEL' || - type === 'ZLIB' || - type === 'SIGNREQUEST' - ) { + if (type === 'PROMISE') { return; } const error = new ErrorWithStack(type, initHook, 100); @@ -141,14 +132,18 @@ export default function collectHandles(): HandleCollectionResult { // For example, Node.js TCP Servers are not destroyed until *after* their // `close` callback runs. If someone finishes a test from the `close` // callback, we will not yet have seen the resource be destroyed here. - await asyncSleep(100); + await asyncSleep(0); if (activeHandles.size > 0) { - // For some special objects such as `TLSWRAP`. - // Ref: https://github.com/facebook/jest/issues/11665 - runGC(); + await asyncSleep(30); - await asyncSleep(0); + if (activeHandles.size > 0) { + // For some special objects such as `TLSWRAP`. + // Ref: https://github.com/facebook/jest/issues/11665 + runGC(); + + await asyncSleep(0); + } } hook.disable(); @@ -167,33 +162,44 @@ export function formatHandleErrors( errors: Array, config: Config.ProjectConfig, ): Array { - const stacks = new Set(); - - return ( - errors - .map(err => - formatExecError(err, config, {noStackTrace: false}, undefined, true), - ) - // E.g. timeouts might give multiple traces to the same line of code - // This hairy filtering tries to remove entries with duplicate stack traces - .filter(handle => { - const ansiFree: string = stripAnsi(handle); - - const match = ansiFree.match(/\s+at(.*)/); - - if (!match || match.length < 2) { - return true; - } + const stacks = new Map}>(); + + errors.forEach(err => { + const formatted = formatExecError( + err, + config, + {noStackTrace: false}, + undefined, + true, + ); - const stack = ansiFree.substr(ansiFree.indexOf(match[1])).trim(); + // E.g. timeouts might give multiple traces to the same line of code + // This hairy filtering tries to remove entries with duplicate stack traces - if (stacks.has(stack)) { - return false; - } + const ansiFree: string = stripAnsi(formatted); + const match = ansiFree.match(/\s+at(.*)/); + if (!match || match.length < 2) { + return; + } - stacks.add(stack); + const stackText = ansiFree.substr(ansiFree.indexOf(match[1])).trim(); + + const name = ansiFree.match(/(?<=● {2}).*$/m); + if (!name?.length) { + return; + } + + const stack = stacks.get(stackText) || { + names: new Set(), + stack: formatted.replace(name[0], '%%OBJECT_NAME%%'), + }; + + stack.names.add(name[0]); + + stacks.set(stackText, stack); + }); - return true; - }) + return Array.from(stacks.values()).map(({stack, names}) => + stack.replace('%%OBJECT_NAME%%', Array.from(names).join(',')), ); } From 04fd4ba1d290ea8f422ca6d97384747e387f2a3a Mon Sep 17 00:00:00 2001 From: liuxingbaoyu <30521560+liuxingbaoyu@users.noreply.github.com> Date: Tue, 11 Oct 2022 09:53:41 +0800 Subject: [PATCH 2/6] test ci --- packages/jest-core/src/__tests__/collectHandles.test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/jest-core/src/__tests__/collectHandles.test.js b/packages/jest-core/src/__tests__/collectHandles.test.js index f98fe5d41ce4..d6a2a262203d 100644 --- a/packages/jest-core/src/__tests__/collectHandles.test.js +++ b/packages/jest-core/src/__tests__/collectHandles.test.js @@ -31,8 +31,11 @@ describe('collectHandles', () => { it('should not collect the PerformanceObserver open handle', async () => { const handleCollector = collectHandles(); - const obs = new PerformanceObserver((list, observer) => {}); + + let obs = new PerformanceObserver((list, observer) => {}); obs.observe({entryTypes: ['mark']}); + obs.disconnect(); + obs = null; const openHandles = await handleCollector(); From 57323287b776159f38005976ac8ed212f3b476eb Mon Sep 17 00:00:00 2001 From: liuxingbaoyu <30521560+liuxingbaoyu@users.noreply.github.com> Date: Tue, 11 Oct 2022 10:05:26 +0800 Subject: [PATCH 3/6] fix test --- packages/jest-core/src/__tests__/collectHandles.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/jest-core/src/__tests__/collectHandles.test.js b/packages/jest-core/src/__tests__/collectHandles.test.js index d6a2a262203d..35730c8b298e 100644 --- a/packages/jest-core/src/__tests__/collectHandles.test.js +++ b/packages/jest-core/src/__tests__/collectHandles.test.js @@ -42,7 +42,6 @@ describe('collectHandles', () => { expect(openHandles).not.toContainEqual( expect.objectContaining({message: 'PerformanceObserver'}), ); - obs.disconnect(); }); it('should not collect the DNSCHANNEL open handle', async () => { From 751eec5f89d1025cb3b64018cc31b524dd24d2f7 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 19 Sep 2023 10:47:09 +0200 Subject: [PATCH 4/6] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18ca2d18dbda..4ed82b06d8f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### Features +- `[@jest/core]` [**BREAKING**] Group together open handles with the same stack trace ([#13417](https://github.com/jestjs/jest/pull/13417), & [#14543](https://github.com/jestjs/jest/pull/14543)) - `[@jest/core, @jest/test-sequencer]` [**BREAKING**] Exposes `globalConfig` & `contexts` to `TestSequencer` ([#14535](https://github.com/jestjs/jest/pull/14535), & [#14543](https://github.com/jestjs/jest/pull/14543)) ### Fixes From b0d7c51ff72de3cb0478efce030579e115eeba77 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 19 Sep 2023 10:47:16 +0200 Subject: [PATCH 5/6] no deprecated method --- packages/jest-core/src/collectHandles.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jest-core/src/collectHandles.ts b/packages/jest-core/src/collectHandles.ts index 4055777daaf9..78f960bc0c97 100644 --- a/packages/jest-core/src/collectHandles.ts +++ b/packages/jest-core/src/collectHandles.ts @@ -182,10 +182,10 @@ export function formatHandleErrors( return; } - const stackText = ansiFree.substr(ansiFree.indexOf(match[1])).trim(); + const stackText = ansiFree.slice(ansiFree.indexOf(match[1])).trim(); const name = ansiFree.match(/(?<=● {2}).*$/m); - if (!name?.length) { + if (name == null || name.length === 0) { return; } From e569539878f75491203f7b2e5c866e3340cdf8ad Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Tue, 19 Sep 2023 17:38:53 +0200 Subject: [PATCH 6/6] lint --- packages/jest-core/src/collectHandles.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/jest-core/src/collectHandles.ts b/packages/jest-core/src/collectHandles.ts index 78f960bc0c97..087255f8106b 100644 --- a/packages/jest-core/src/collectHandles.ts +++ b/packages/jest-core/src/collectHandles.ts @@ -164,7 +164,7 @@ export function formatHandleErrors( ): Array { const stacks = new Map}>(); - errors.forEach(err => { + for (const err of errors) { const formatted = formatExecError( err, config, @@ -179,14 +179,14 @@ export function formatHandleErrors( const ansiFree: string = stripAnsi(formatted); const match = ansiFree.match(/\s+at(.*)/); if (!match || match.length < 2) { - return; + continue; } const stackText = ansiFree.slice(ansiFree.indexOf(match[1])).trim(); const name = ansiFree.match(/(?<=● {2}).*$/m); if (name == null || name.length === 0) { - return; + continue; } const stack = stacks.get(stackText) || { @@ -197,7 +197,7 @@ export function formatHandleErrors( stack.names.add(name[0]); stacks.set(stackText, stack); - }); + } return Array.from(stacks.values()).map(({stack, names}) => stack.replace('%%OBJECT_NAME%%', Array.from(names).join(',')),