From 0e470004e0bd1f0cca97d0f44f85a6f840116f9e Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Thu, 8 Apr 2021 09:34:56 +0200 Subject: [PATCH] fix: use WeakRef for tracked handles --- CHANGELOG.md | 1 + packages/jest-core/src/cli/index.ts | 6 ++++-- packages/jest-core/src/collectHandles.ts | 26 +++++++++++++++++------- packages/jest-core/src/runJest.ts | 8 ++++---- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f621f66b734..7ca0e23943e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,6 +48,7 @@ - `[jest-config]` [**BREAKING**] Change default file extension order by moving json behind ts and tsx ([10572](https://github.com/facebook/jest/pull/10572)) - `[jest-console]` `console.dir` now respects the second argument correctly ([#10638](https://github.com/facebook/jest/pull/10638)) - `[jest-core]` Don't report PerformanceObserver as open handle ([#11123](https://github.com/facebook/jest/pull/11123)) +- `[jest-core]` Use `WeakRef` to hold timers when detecting open handles ([#11277](https://github.com/facebook/jest/pull/11277)) - `[jest-each]` [**BREAKING**] Ignore excess words in headings ([#8766](https://github.com/facebook/jest/pull/8766)) - `[jest-environment]` [**BREAKING**] Drop support for `runScript` for test environments ([#11155](https://github.com/facebook/jest/pull/11155)) - `[jest-environment-jsdom]` Use inner realm’s `ArrayBuffer` constructor ([#10885](https://github.com/facebook/jest/pull/10885)) diff --git a/packages/jest-core/src/cli/index.ts b/packages/jest-core/src/cli/index.ts index 639b277129ce..8d57af3a0742 100644 --- a/packages/jest-core/src/cli/index.ts +++ b/packages/jest-core/src/cli/index.ts @@ -32,7 +32,7 @@ import watch from '../watch'; const {print: preRunMessagePrint} = preRunMessage; -type OnCompleteCallback = (results: AggregatedResult) => void; +type OnCompleteCallback = (results: AggregatedResult) => void | undefined; export async function runCLI( argv: Config.Argv, @@ -89,7 +89,9 @@ export async function runCLI( configsOfProjectsToRun, hasDeprecationWarnings, outputStream, - r => (results = r), + r => { + results = r; + }, ); if (argv.watch || argv.watchAll) { diff --git a/packages/jest-core/src/collectHandles.ts b/packages/jest-core/src/collectHandles.ts index 13a8181ec3b4..7bdf62b5ccb7 100644 --- a/packages/jest-core/src/collectHandles.ts +++ b/packages/jest-core/src/collectHandles.ts @@ -13,6 +13,8 @@ import type {Config} from '@jest/types'; import {formatExecError} from 'jest-message-util'; import {ErrorWithStack} from 'jest-util'; +export type HandleCollectionResult = () => Array; + function stackIsFromUser(stack: string) { // Either the test file, or something required by it if (stack.includes('Runtime.requireModule')) { @@ -37,13 +39,15 @@ function stackIsFromUser(stack: string) { const alwaysActive = () => true; +const hasWeakRef = typeof WeakRef === 'function'; + // Inspired by https://github.com/mafintosh/why-is-node-running/blob/master/index.js // Extracted as we want to format the result ourselves -export default function collectHandles(): () => Array { - const activeHandles: Map< +export default function collectHandles(): HandleCollectionResult { + const activeHandles = new Map< number, {error: Error; isActive: () => boolean} - > = new Map(); + >(); const hook = asyncHooks.createHook({ destroy(asyncId) { activeHandles.delete(asyncId); @@ -68,10 +72,18 @@ export default function collectHandles(): () => Array { let isActive: () => boolean; if (type === 'Timeout' || type === 'Immediate') { + // Timer that supports hasRef (Node v11+) if ('hasRef' in resource) { - // Timer that supports hasRef (Node v11+) - // @ts-expect-error: doesn't exist in v10 typings - isActive = resource.hasRef.bind(resource); + if (hasWeakRef) { + const ref = new WeakRef(resource); + isActive = () => { + // @ts-expect-error: doesn't exist in v10 typings + return ref.deref()?.hasRef() ?? false; + }; + } else { + // @ts-expect-error: doesn't exist in v10 typings + isActive = resource.hasRef.bind(resource); + } } else { // Timer that doesn't support hasRef isActive = alwaysActive; @@ -88,7 +100,7 @@ export default function collectHandles(): () => Array { hook.enable(); - return (): Array => { + return () => { hook.disable(); // Get errors for every async resource still referenced at this moment diff --git a/packages/jest-core/src/runJest.ts b/packages/jest-core/src/runJest.ts index 008f102a4a4e..65f191802eac 100644 --- a/packages/jest-core/src/runJest.ts +++ b/packages/jest-core/src/runJest.ts @@ -26,7 +26,7 @@ import type FailedTestsCache from './FailedTestsCache'; import SearchSource from './SearchSource'; import TestScheduler, {TestSchedulerContext} from './TestScheduler'; import type TestWatcher from './TestWatcher'; -import collectNodeHandles from './collectHandles'; +import collectNodeHandles, {HandleCollectionResult} from './collectHandles'; import getNoTestsFoundMessage from './getNoTestsFoundMessage'; import runGlobalHook from './runGlobalHook'; import type {Filter, TestRunData} from './types'; @@ -70,7 +70,7 @@ type ProcessResultOptions = Pick< Config.GlobalConfig, 'json' | 'outputFile' | 'testResultsProcessor' > & { - collectHandles?: () => Array; + collectHandles?: HandleCollectionResult; onComplete?: (result: AggregatedResult) => void; outputStream: NodeJS.WriteStream; }; @@ -111,7 +111,7 @@ const processResults = ( } } - return onComplete && onComplete(runResults); + onComplete?.(runResults); }; const testSchedulerContext: TestSchedulerContext = { @@ -278,7 +278,7 @@ export default async function runJest({ await runGlobalHook({allTests, globalConfig, moduleName: 'globalTeardown'}); } - await processResults(results, { + processResults(results, { collectHandles, json: globalConfig.json, onComplete,