From d78194c7f1521045659fb38b213657f88b310517 Mon Sep 17 00:00:00 2001
From: Hieu Lam <lamhieu.vk@gmail.com>
Date: Mon, 19 Aug 2019 17:37:32 +0700
Subject: [PATCH] Use `weak-napi` instead of `weak` in `jest-leak-detector`
 (#8686)

---
 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 a56e43cd0d93..d8152476dec8 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 = require('weak-napi');
 import prettyFormat = require('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<boolean> {
     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 04bf195afdf4..a238c743bfaf 100644
--- a/packages/jest-runner/src/runTest.ts
+++ b/packages/jest-runner/src/runTest.ts
@@ -295,11 +295,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"