From 4bb7a2d16f370f4ea5d7b022b93c1ebdee9a91bc Mon Sep 17 00:00:00 2001 From: Hieu Lam Date: Mon, 19 Aug 2019 17:37:32 +0700 Subject: [PATCH] Use `weak-napi` instead of `weak` in `jest-leak-detector` --- CHANGELOG.md | 1 + packages/jest-leak-detector/README.md | 21 ++++--- packages/jest-leak-detector/package.json | 6 +- .../src/__tests__/index.test.ts | 56 +++++++++++++------ packages/jest-leak-detector/src/index.ts | 25 ++------- packages/jest-runner/src/runTest.ts | 5 +- yarn.lock | 47 ++++++++++++---- 7 files changed, 98 insertions(+), 63 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 61373cdbbccd..e17da7a10ed0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - `[expect]` Display expectedDiff more carefully in toBeCloseTo ([#8389](https://github.com/facebook/jest/pull/8389)) - `[jest-fake-timers]` `getTimerCount` will not include cancelled immediates ([#8764](https://github.com/facebook/jest/pull/8764)) +- `[jest-leak-detector]` [**BREAKING**] Use `weak-napi` instead of `weak` package ([#8686](https://github.com/facebook/jest/pull/8686)) - `[jest-snapshot]` Remove only the added newlines in multiline snapshots ([#8859](https://github.com/facebook/jest/pull/8859)) ### Chore & Maintenance diff --git a/packages/jest-leak-detector/README.md b/packages/jest-leak-detector/README.md index 6f14d8699281..c1e060c28eb9 100644 --- a/packages/jest-leak-detector/README.md +++ b/packages/jest-leak-detector/README.md @@ -7,16 +7,21 @@ Internally creates a weak reference to the object, and forces garbage collection ## Example ```javascript -let reference = {}; +(async function() { + let reference = {}; + let isLeaking; -const detector = new LeakDetector(reference); + const detector = new LeakDetector(reference); -// Reference is held in memory. -console.log(detector.isLeaking()); // true + // Reference is held in memory. + isLeaking = await detector.isLeaking(); + console.log(isLeaking); // true -// We destroy the only reference to the object. -reference = null; + // We destroy the only reference to the object. + reference = null; -// Reference is gone. -console.log(detector.isLeaking()); // false + // Reference is gone. + isLeaking = await detector.isLeaking(); + console.log(isLeaking); // false +})(); ``` diff --git a/packages/jest-leak-detector/package.json b/packages/jest-leak-detector/package.json index 6883b20ebcde..7ada18f6b592 100644 --- a/packages/jest-leak-detector/package.json +++ b/packages/jest-leak-detector/package.json @@ -11,11 +11,11 @@ "types": "build/index.d.ts", "dependencies": { "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" + "pretty-format": "^24.9.0", + "weak-napi": "^1.0.3" }, "devDependencies": { - "@types/weak": "^1.0.0", - "weak": "^1.0.1" + "@types/weak-napi": "^1.0.0" }, "engines": { "node": ">= 8" diff --git a/packages/jest-leak-detector/src/__tests__/index.test.ts b/packages/jest-leak-detector/src/__tests__/index.test.ts index 6a017162021e..5c2cc780064f 100644 --- a/packages/jest-leak-detector/src/__tests__/index.test.ts +++ b/packages/jest-leak-detector/src/__tests__/index.test.ts @@ -28,39 +28,42 @@ it('complains if the value is a primitive', () => { expect(() => new LeakDetector(NaN)).toThrowErrorMatchingSnapshot(); }); -it('does not show the GC if hidden', () => { +it('does not show the GC if hidden', async () => { const detector = new LeakDetector({}); // @ts-ignore: purposefully removed global.gc = undefined; - detector.isLeaking(); + await detector.isLeaking(); expect(global.gc).not.toBeDefined(); }); -it('does not hide the GC if visible', () => { +it('does not hide the GC if visible', async () => { const detector = new LeakDetector({}); global.gc = () => {}; - detector.isLeaking(); + await detector.isLeaking(); expect(global.gc).toBeDefined(); }); -it('correctly checks simple leaks', () => { +it('correctly checks simple leaks', async () => { let reference: unknown = {}; + let isLeaking: boolean; const detector = new LeakDetector(reference); // Reference is still held in memory. - expect(detector.isLeaking()).toBe(true); + isLeaking = await detector.isLeaking(); + expect(isLeaking).toBe(true); // We destroy the only reference to the object we had. reference = null; // Reference should be gone. - expect(detector.isLeaking()).toBe(false); + isLeaking = await detector.isLeaking(); + expect(isLeaking).toBe(false); }); -it('tests different objects', () => { +it('tests different objects', async () => { const refs = [ function() {}, () => {}, @@ -73,12 +76,20 @@ it('tests different objects', () => { const detectors = refs.map(ref => new LeakDetector(ref)); - detectors.forEach(detector => expect(detector.isLeaking()).toBe(true)); - refs.forEach((_, i) => (refs[i] = null)); - detectors.forEach(detector => expect(detector.isLeaking()).toBe(false)); + let isLeaking: boolean; + for (const i in detectors) { + isLeaking = await detectors[i].isLeaking(); + expect(isLeaking).toBe(true); + refs[i] = null; + } + + for (const i in detectors) { + isLeaking = await detectors[i].isLeaking(); + expect(isLeaking).toBe(false); + } }); -it('correctly checks more complex leaks', () => { +it('correctly checks more complex leaks', async () => { let ref1: any = {}; let ref2: any = {}; @@ -89,21 +100,30 @@ it('correctly checks more complex leaks', () => { const detector1 = new LeakDetector(ref1); const detector2 = new LeakDetector(ref2); + let isLeaking1: boolean; + let isLeaking2: boolean; + // References are still held in memory. - expect(detector1.isLeaking()).toBe(true); - expect(detector2.isLeaking()).toBe(true); + isLeaking1 = await detector1.isLeaking(); + expect(isLeaking1).toBe(true); + isLeaking2 = await detector2.isLeaking(); + expect(isLeaking2).toBe(true); // We destroy the reference to ref1. ref1 = null; // It will still be referenced by ref2, so both references are still leaking. - expect(detector1.isLeaking()).toBe(true); - expect(detector2.isLeaking()).toBe(true); + isLeaking1 = await detector1.isLeaking(); + expect(isLeaking1).toBe(true); + isLeaking2 = await detector2.isLeaking(); + expect(isLeaking2).toBe(true); // We destroy the reference to ref2. ref2 = null; // Now both references should be gone (yay mark & sweep!). - expect(detector1.isLeaking()).toBe(false); - expect(detector2.isLeaking()).toBe(false); + isLeaking1 = await detector1.isLeaking(); + expect(isLeaking1).toBe(false); + isLeaking2 = await detector2.isLeaking(); + expect(isLeaking2).toBe(false); }); diff --git a/packages/jest-leak-detector/src/index.ts b/packages/jest-leak-detector/src/index.ts index e1ed5c46f132..e520a3c52973 100644 --- a/packages/jest-leak-detector/src/index.ts +++ b/packages/jest-leak-detector/src/index.ts @@ -7,6 +7,7 @@ import {setFlagsFromString} from 'v8'; import {runInNewContext} from 'vm'; +import weak from 'weak-napi'; import prettyFormat from 'pretty-format'; import {isPrimitive} from 'jest-get-type'; @@ -23,33 +24,19 @@ export default class { ); } - let weak; - - try { - // eslint-disable-next-line import/no-extraneous-dependencies - weak = require('weak'); - } catch (err) { - if (!err || err.code !== 'MODULE_NOT_FOUND') { - throw err; - } - - throw new Error( - 'The leaking detection mechanism requires the "weak" package to be installed and work. ' + - 'Please install it as a dependency on your main project', - ); - } - - weak(value, () => (this._isReferenceBeingHeld = false)); + weak(value as object, () => (this._isReferenceBeingHeld = false)); this._isReferenceBeingHeld = true; // Ensure value is not leaked by the closure created by the "weak" callback. value = null; } - isLeaking(): boolean { + isLeaking(): Promise { this._runGarbageCollector(); - return this._isReferenceBeingHeld; + return new Promise(resolve => + setImmediate(() => resolve(this._isReferenceBeingHeld)), + ); } private _runGarbageCollector() { diff --git a/packages/jest-runner/src/runTest.ts b/packages/jest-runner/src/runTest.ts index 7742cc9c09cf..05045fa52bad 100644 --- a/packages/jest-runner/src/runTest.ts +++ b/packages/jest-runner/src/runTest.ts @@ -297,11 +297,8 @@ export default async function runTest( ); if (leakDetector) { - // We wanna allow a tiny but time to pass to allow last-minute cleanup - await new Promise(resolve => setTimeout(resolve, 100)); - // Resolve leak detector, outside the "runTestInternal" closure. - result.leaks = leakDetector.isLeaking(); + result.leaks = await leakDetector.isLeaking(); } else { result.leaks = false; } diff --git a/yarn.lock b/yarn.lock index db498e5a2b58..816fa170db5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2539,10 +2539,10 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d" integrity sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg== -"@types/weak@^1.0.0": +"@types/weak-napi@^1.0.0": version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/weak/-/weak-1.0.0.tgz#7b3bf891c4b53e2b8a144b7e41f4b0f6e78c2ba8" - integrity sha512-6WXZpeAac3vj5+OfQvlqYEtc88oOgvkcxbrnmBw53Da6gA+MGztL+Hns3BpnyUevgz+4DxsJblgAew1A/tkcng== + resolved "https://registry.yarnpkg.com/@types/weak-napi/-/weak-napi-1.0.0.tgz#b0977c0737cb62d028c4eda76f4e295bb3ae3c49" + integrity sha512-viW/kPA1gpeoNdUge025WqmThQ2lnnHzZWZJM5KlH8w9E5YehOh3GnDjW5w/sAEC91VOlePEiFSQmbnX7VVyLw== dependencies: "@types/node" "*" @@ -3616,7 +3616,7 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== -bindings@^1.2.1: +bindings@^1.3.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== @@ -6924,6 +6924,18 @@ get-stream@^4.0.0, get-stream@^4.1.0: dependencies: pump "^3.0.0" +get-symbol-from-current-process-h@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.1.tgz#7e4809087e7d2f3a78a785b36f787e2183ba4c5d" + integrity sha512-QvP1+tCDjgTiu+akjdEYd8eK8MFYy6nRCRNjfiCeQB9RJEHQZpN+WE+CVqPRNqjIVMwSqd0WiD008B+b7iIdaA== + +get-uv-event-loop-napi-h@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/get-uv-event-loop-napi-h/-/get-uv-event-loop-napi-h-1.0.5.tgz#1904a1dc1fa6df7487c9e8eaf87302bcc9e33e47" + integrity sha512-uWDHId313vRTyqeLhlLWJS0CJOP8QXY5en/9Pv14dnPvAlRfKBfD6h2EDtoy7jxxOIWB9QgzYK16VCN3pCZOBg== + dependencies: + get-symbol-from-current-process-h "^1.0.1" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -9850,7 +9862,7 @@ mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@^2.0.5, nan@^2.12.1: +nan@^2.12.1: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" integrity sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg== @@ -9912,6 +9924,11 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-addon-api@^1.1.0: + version "1.6.3" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.3.tgz#3998d4593e2dca2ea82114670a4eb003386a9fe1" + integrity sha512-FXWH6mqjWgU8ewuahp4spec8LkroFZK2NicOv6bNwZC3kcwZUI8LeZdG80UzTSLLhK4T7MsgNwlYDVRlDdfTDg== + node-environment-flags@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.5.tgz#fa930275f5bf5dae188d6192b24b4c8bbac3d76a" @@ -12531,6 +12548,13 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" +setimmediate-napi@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setimmediate-napi/-/setimmediate-napi-1.0.3.tgz#f5ef99da0d9b7a1036dd375b35a687cfb483c172" + integrity sha512-ah02BktAAJJ1eHANtD93ZdvKZrCXJwSHXww5arS1YcihOlpJlwsVkns4BXh6sRJNAyWTLl6TkjVx8CjKV9qwcQ== + dependencies: + get-uv-event-loop-napi-h "^1.0.2" + setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -14046,13 +14070,14 @@ wcwidth@^1.0.0: dependencies: defaults "^1.0.3" -weak@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/weak/-/weak-1.0.1.tgz#ab99aab30706959aa0200cb8cf545bb9cb33b99e" - integrity sha1-q5mqswcGlZqgIAy4z1RbucszuZ4= +weak-napi@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/weak-napi/-/weak-napi-1.0.3.tgz#ff4dfa818db1c509ba4166530b42414ef74cbba6" + integrity sha512-cyqeMaYA5qI7RoZKAKvIHwEROEKDNxK7jXj3u56nF2rGBh+HFyhYmBb1/wAN4RqzRmkYKVVKQyqHpBoJjqtGUA== dependencies: - bindings "^1.2.1" - nan "^2.0.5" + bindings "^1.3.0" + node-addon-api "^1.1.0" + setimmediate-napi "^1.0.3" webidl-conversions@^4.0.2: version "4.0.2"