Skip to content

Commit

Permalink
Use weak-napi instead of weak in jest-leak-detector
Browse files Browse the repository at this point in the history
  • Loading branch information
lh0x00 authored and SimenB committed Aug 19, 2019
1 parent 4832947 commit 87c5cac
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 63 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -10,6 +10,7 @@

- `[*]` [**BREAKING**] Drop support for Node 6 ([#8455](https://github.com/facebook/jest/pull/8455))
- `[docs]` Fix broken link pointing to legacy JS file in "Snapshot Testing".
- `[jest-leak-detector]` [**BREAKING**] Use `weak-napi` instead of `weak` package ([#8686](https://github.com/facebook/jest/pull/8686))

### Performance

Expand Down
21 changes: 13 additions & 8 deletions packages/jest-leak-detector/README.md
Expand Up @@ -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
})();
```
6 changes: 3 additions & 3 deletions packages/jest-leak-detector/package.json
Expand Up @@ -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"
Expand Down
56 changes: 38 additions & 18 deletions packages/jest-leak-detector/src/__tests__/index.test.ts
Expand Up @@ -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() {},
() => {},
Expand All @@ -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 = {};

Expand All @@ -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);
});
25 changes: 6 additions & 19 deletions packages/jest-leak-detector/src/index.ts
Expand Up @@ -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';

Expand All @@ -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() {
Expand Down
5 changes: 1 addition & 4 deletions packages/jest-runner/src/runTest.ts
Expand Up @@ -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;
}
Expand Down
47 changes: 36 additions & 11 deletions yarn.lock
Expand Up @@ -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" "*"

Expand Down Expand Up @@ -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==
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -9855,7 +9867,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==
Expand Down Expand Up @@ -9917,6 +9929,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"
Expand Down Expand Up @@ -12536,6 +12553,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"
Expand Down Expand Up @@ -14051,13 +14075,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"
Expand Down

0 comments on commit 87c5cac

Please sign in to comment.