Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update jest-leak-detector package to be compatible with the new Node version #8686

Merged
merged 4 commits into from Aug 19, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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",
lh0x00 marked this conversation as resolved.
Show resolved Hide resolved
"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;
lh0x00 marked this conversation as resolved.
Show resolved Hide resolved
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
lh0x00 marked this conversation as resolved.
Show resolved Hide resolved
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