From cfa3117085ac4f2121698201c43643230f01edb4 Mon Sep 17 00:00:00 2001 From: Dan Rose Date: Tue, 11 Jun 2024 10:31:57 -0500 Subject: [PATCH 1/7] bump workflow versions Replace weak-napi with built in WeakRef --- CONTRIBUTING.md | 10 --- test/package.json | 6 -- test/pnpm-lock.yaml | 81 ------------------------ test/tsconfig.json | 15 ++++- test/unit/helpers.ts | 40 ++++++++++++ test/unit/socket-close-test.ts | 57 +++++------------ test/unit/socket-send-receive-test.ts | 91 ++++++++++----------------- tsconfig.json | 13 ++-- 8 files changed, 112 insertions(+), 201 deletions(-) delete mode 100644 test/package.json delete mode 100644 test/pnpm-lock.yaml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8e0d38f4..5d7bfc51 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,13 +38,3 @@ tool similar to Cmake. GYP was originally created to generate native IDE project files (Visual Studio, Xcode) for building Chromium. The `.gyp` file is structured as a Python dictionary. - -## Weak-napi - -https://www.npmjs.com/package/weak-napi On certain rarer occasions, you run into -the need to be notified when a JavaScript object is going to be garbage -collected. This feature is exposed to V8's C++ API, but not to JavaScript. - -That's where weak-napi comes in! This module exports the JS engine's GC tracking -functionality to JavaScript. This allows you to create weak references, and -optionally attach a callback function to any arbitrary JS object. diff --git a/test/package.json b/test/package.json deleted file mode 100644 index 755aa163..00000000 --- a/test/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "devDependencies": { - "@types/weak-napi": "^2.0.3", - "weak-napi": "https://github.com/aminya/weak-napi#ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f" - } -} diff --git a/test/pnpm-lock.yaml b/test/pnpm-lock.yaml deleted file mode 100644 index 78ff61a8..00000000 --- a/test/pnpm-lock.yaml +++ /dev/null @@ -1,81 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - devDependencies: - '@types/weak-napi': - specifier: ^2.0.3 - version: 2.0.3 - weak-napi: - specifier: https://github.com/aminya/weak-napi#ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f - version: https://codeload.github.com/aminya/weak-napi/tar.gz/ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f - -packages: - - '@types/node@20.14.2': - resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} - - '@types/weak-napi@2.0.3': - resolution: {integrity: sha512-hNh8wxRTaQC8gLT6BKkG5Kokwp4hEyWw4RshGTLwa1K4S1o6kX7G0bJ85kKr+2fK3fuF8HZuz1+Dy8YtICZDqg==} - - get-symbol-from-current-process-h@1.0.2: - resolution: {integrity: sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw==} - - get-uv-event-loop-napi-h@1.0.6: - resolution: {integrity: sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg==} - - node-addon-api@8.0.0: - resolution: {integrity: sha512-ipO7rsHEBqa9STO5C5T10fj732ml+5kLN1cAG8/jdHd56ldQeGj3Q7+scUS+VHK/qy1zLEwC4wMK5+yM0btPvw==} - engines: {node: ^18 || ^20 || >= 21} - - node-gyp-build@4.8.1: - resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} - hasBin: true - - setimmediate-napi@1.0.6: - resolution: {integrity: sha512-sdNXN15Av1jPXuSal4Mk4tEAKn0+8lfF9Z50/negaQMrAIO9c1qM0eiCh8fT6gctp0RiCObk+6/Xfn5RMGdZoA==} - - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - weak-napi@https://codeload.github.com/aminya/weak-napi/tar.gz/ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f: - resolution: {tarball: https://codeload.github.com/aminya/weak-napi/tar.gz/ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f} - version: 2.0.2 - -snapshots: - - '@types/node@20.14.2': - dependencies: - undici-types: 5.26.5 - - '@types/weak-napi@2.0.3': - dependencies: - '@types/node': 20.14.2 - - get-symbol-from-current-process-h@1.0.2: {} - - get-uv-event-loop-napi-h@1.0.6: - dependencies: - get-symbol-from-current-process-h: 1.0.2 - - node-addon-api@8.0.0: {} - - node-gyp-build@4.8.1: {} - - setimmediate-napi@1.0.6: - dependencies: - get-symbol-from-current-process-h: 1.0.2 - get-uv-event-loop-napi-h: 1.0.6 - - undici-types@5.26.5: {} - - weak-napi@https://codeload.github.com/aminya/weak-napi/tar.gz/ea97678fc4c9304eea7f3fb1ddc88eaf59cccc6f: - dependencies: - node-addon-api: 8.0.0 - node-gyp-build: 4.8.1 - setimmediate-napi: 1.0.6 diff --git a/test/tsconfig.json b/test/tsconfig.json index e516b7fa..e3ee4344 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,4 +1,17 @@ { "extends": "../tsconfig.json", - "include": ["**/*.ts"] + "include": [ + "**/*.ts" + ], + "ts-node": { + "files": true + }, + "compilerOptions": { + "types": [ + "mocha" + ], + "lib": [ + "ESNext" + ] + } } diff --git a/test/unit/helpers.ts b/test/unit/helpers.ts index 2b15e99d..dfe0c665 100644 --- a/test/unit/helpers.ts +++ b/test/unit/helpers.ts @@ -235,3 +235,43 @@ export async function captureEventsUntil( return events } + +// REAL typings for global.gc per +// https://github.com/nodejs/node/blob/v20.0.0/deps/v8/src/extensions/gc-extension.cc +interface GCFunction { + (options: { + execution?: "sync" + flavor?: "regular" | "last-resort" + type?: "major-snapshot" | "major" | "minor" + filename?: string + }): void + (options: { + execution?: "async" + flavor?: "regular" | "last-resort" + type?: "major-snapshot" | "major" | "minor" + filename?: string + }): Promise + (options: { + execution?: "async" | "sync" + flavor?: "regular" | "last-resort" + type?: "major-snapshot" | "major" | "minor" + filename?: string + }): void | Promise +} + +export function getGcOrSkipTest(test: Mocha.Context) { + if (process.env.SKIP_GC_TESTS) { + test.skip() + } + + const gc = globalThis.gc as undefined | GCFunction + if (typeof gc !== "function") { + throw new Error( + "Garbage collection is not exposed. It may be enabled by the node --expose-gc flag. To skip GC tests, set the environment variable `SKIP_GC_TESTS`", + ) + } + // https://github.com/nodejs/node/blob/v20.0.0/deps/v8/src/extensions/gc-extension.h + // per docs, we we're using use case 2 (Test that certain objects indeed are reclaimed) + const asyncMajorGc = () => gc({type: "major", execution: "async"}) + return asyncMajorGc +} diff --git a/test/unit/socket-close-test.ts b/test/unit/socket-close-test.ts index fd76ccf8..56faea64 100644 --- a/test/unit/socket-close-test.ts +++ b/test/unit/socket-close-test.ts @@ -2,7 +2,7 @@ import * as zmq from "../../src" import {assert} from "chai" -import {testProtos, uniqAddress} from "./helpers" +import {testProtos, uniqAddress, getGcOrSkipTest} from "./helpers" import {isFullError} from "../../src/errors" for (const proto of testProtos("tcp", "ipc", "inproc")) { @@ -107,75 +107,50 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should release reference to context", async function () { - if (process.env.SKIP_GC_TESTS === "true") { - this.skip() - } - if (global.gc === undefined) { - console.warn("gc is not exposed by the runtime") - this.skip() - } - + const gc = getGcOrSkipTest(this) this.slow(200) - const weak = require("weak-napi") as typeof import("weak-napi") + let weakRef: undefined | WeakRef - let released = false const task = async () => { let context: zmq.Context | undefined = new zmq.Context() const socket = new zmq.Dealer({context, linger: 0}) + weakRef = new WeakRef(context) - weak(context, () => { - released = true - }) - context = undefined - - global.gc!() socket.connect(await uniqAddress(proto)) await socket.send(Buffer.from("foo")) socket.close() } await task() - global.gc() - await new Promise(resolve => { - setTimeout(resolve, 5) - }) - assert.equal(released, true) + await gc() + + assert.isDefined(weakRef) + assert.isUndefined(weakRef!.deref()) }) }) describe("in gc finalizer", function () { it("should release reference to context", async function () { - if (process.env.SKIP_GC_TESTS === "true") { - this.skip() - } - if (global.gc === undefined) { - console.warn("gc is not exposed by the runtime") + const gc = getGcOrSkipTest(this) + if (process.env.SKIP_GC_FINALIZER_TESTS) { this.skip() } this.slow(200) - const weak = require("weak-napi") as typeof import("weak-napi") - - let released = false + let weakRef: undefined | WeakRef const task = async () => { let context: zmq.Context | undefined = new zmq.Context() const _dealer = new zmq.Dealer({context, linger: 0}) - - weak(context, () => { - released = true - }) - context = undefined - global.gc!() + weakRef = new WeakRef(context) } await task() - global.gc() - await new Promise(resolve => { - setTimeout(resolve, 5) - }) - assert.equal(released, true) + await gc() + + assert.isDefined(weakRef) + assert.isUndefined(weakRef!.deref()) }) }) }) diff --git a/test/unit/socket-send-receive-test.ts b/test/unit/socket-send-receive-test.ts index 45e4bbb2..2804b939 100644 --- a/test/unit/socket-send-receive-test.ts +++ b/test/unit/socket-send-receive-test.ts @@ -2,7 +2,7 @@ import * as zmq from "../../src" import {assert} from "chai" -import {testProtos, uniqAddress} from "./helpers" +import {testProtos, uniqAddress, getGcOrSkipTest} from "./helpers" import {isFullError} from "../../src/errors" for (const proto of testProtos("tcp", "ipc", "inproc")) { @@ -91,51 +91,36 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should copy and release small buffers", async function () { - if (process.env.SKIP_GC_TESTS) { - this.skip() - } - const weak = require("weak-napi") - - let released = false - sockA.connect(await uniqAddress(proto)) + const gc = getGcOrSkipTest(this) + let weakRef: undefined | WeakRef + sockA.connect(uniqAddress(proto)) const send = async (size: number) => { const msg = Buffer.alloc(size) - weak(msg, () => { - released = true - }) + weakRef = new WeakRef(msg) await sockA.send(msg) } await send(16) - global.gc?.() - await new Promise(resolve => { - setTimeout(resolve, 5) - }) - assert.equal(released, true) + await gc() + assert.isDefined(weakRef) + assert.isUndefined(weakRef!.deref()) }) it("should retain large buffers", async function () { - if (process.env.SKIP_GC_TESTS) { - this.skip() - } - const weak = require("weak-napi") + const gc = getGcOrSkipTest(this) + let weakRef: undefined | WeakRef - let released = false sockA.connect(await uniqAddress(proto)) const send = async (size: number) => { const msg = Buffer.alloc(size) - weak(msg, () => { - released = true - }) + weakRef = new WeakRef(msg) await sockA.send(msg) } await send(1025) - global.gc?.() - await new Promise(resolve => { - setTimeout(resolve, 5) - }) - assert.equal(released, false) + await gc() + assert.isDefined(weakRef) + assert.isDefined(weakRef.deref()) }) }) @@ -342,20 +327,16 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should release buffers", async function () { - if (process.env.SKIP_GC_TESTS) { - this.skip() - } - const weak = require("weak-napi") + const gc = await getGcOrSkipTest(this) + + const weakRefs: WeakRef[] = [] const n = 10 - let released = 0 const send = async (size: number) => { for (let i = 0; i < n; i++) { const msg = Buffer.alloc(size) - weak(msg, () => { - released++ - }) + weakRefs.push(new WeakRef(msg)) await sockA.send(msg) } } @@ -363,9 +344,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { const receive = async () => { for (let i = 0; i < n; i++) { const msg = await sockB.receive() - weak(msg, () => { - released++ - }) + weakRefs.push(new WeakRef(msg)) } } @@ -373,46 +352,41 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { /* Repeated GC to allow inproc messages from being collected. */ for (let i = 0; i < 5; i++) { - global.gc?.() + await gc() + await new Promise(resolve => { setTimeout(resolve, 2) }) } - assert.equal(released, n * 2) + assert.equal(weakRefs.length, n * 2) + const unreleased = weakRefs.filter(x => x.deref() !== undefined) + assert.isEmpty(unreleased) }) it("should release buffers after echo", async function () { - if (process.env.SKIP_GC_TESTS) { - this.skip() - } - const weak = require("weak-napi") + const gc = getGcOrSkipTest(this) + + const weakRefs: WeakRef[] = [] const n = 10 - let released = 0 const echo = async () => { for (let i = 0; i < n; i++) { const [msg] = await sockB.receive() await sockB.send(msg) - weak(msg, () => { - released++ - }) + weakRefs.push(new WeakRef(msg)) } } const send = async (size: number) => { for (let i = 0; i < n; i++) { const msg = Buffer.alloc(size) - weak(msg, () => { - released++ - }) + weakRefs.push(new WeakRef(msg)) await sockA.send(msg) const [rep] = await sockA.receive() - weak(rep, () => { - released++ - }) + weakRefs.push(new WeakRef(rep)) } sockA.close() @@ -423,13 +397,14 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { /* Repeated GC to allow inproc messages from being collected. */ for (let i = 0; i < 5; i++) { - global.gc?.() await new Promise(resolve => { setTimeout(resolve, 2) }) + await gc() } - assert.equal(released, n * 3) + assert.lengthOf(weakRefs, n * 3) + assert.isEmpty(weakRefs.filter(r => r.deref() !== undefined)) }) if (proto === "inproc") { diff --git a/tsconfig.json b/tsconfig.json index 02c2001c..cb71d643 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,12 @@ { "compilerOptions": { "allowJs": false, - "target": "es2020", + "target": "es2022", "declaration": true, "module": "commonjs", - "types": ["node", "mocha"], + "types": [ + "node", + ], "strictPropertyInitialization": false, // TODO "strict": true, "strictNullChecks": true, @@ -17,6 +19,9 @@ "incremental": true, "sourceMap": true, "esModuleInterop": true, - "lib": ["ES2020", "dom"] + "lib": [ + "ES2022", + "dom" + ] } -} +} \ No newline at end of file From 05bb48deb9174e581857f199079d72f8caa4b45a Mon Sep 17 00:00:00 2001 From: Dan Rose Date: Mon, 17 Jun 2024 16:22:44 -0500 Subject: [PATCH 2/7] lint --- test/unit/helpers.ts | 2 +- test/unit/socket-close-test.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test/unit/helpers.ts b/test/unit/helpers.ts index dfe0c665..c640fc32 100644 --- a/test/unit/helpers.ts +++ b/test/unit/helpers.ts @@ -260,7 +260,7 @@ interface GCFunction { } export function getGcOrSkipTest(test: Mocha.Context) { - if (process.env.SKIP_GC_TESTS) { + if (process.env.SKIP_GC_TESTS === "true") { test.skip() } diff --git a/test/unit/socket-close-test.ts b/test/unit/socket-close-test.ts index 56faea64..94443693 100644 --- a/test/unit/socket-close-test.ts +++ b/test/unit/socket-close-test.ts @@ -140,8 +140,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { let weakRef: undefined | WeakRef const task = async () => { - let context: zmq.Context | undefined = new zmq.Context() - + const context: zmq.Context | undefined = new zmq.Context() const _dealer = new zmq.Dealer({context, linger: 0}) weakRef = new WeakRef(context) } From 47c079720322ade98b0fa8748ffe4ee16961219a Mon Sep 17 00:00:00 2001 From: Dan Rose Date: Mon, 17 Jun 2024 17:35:09 -0500 Subject: [PATCH 3/7] revert bump of es target --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index cb71d643..8a0b6b27 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "allowJs": false, - "target": "es2022", + "target": "es2020", "declaration": true, "module": "commonjs", "types": [ From d7c74e95b1d808769fd87dde12b1bab515525d8e Mon Sep 17 00:00:00 2001 From: Dan Rose Date: Mon, 17 Jun 2024 17:52:50 -0500 Subject: [PATCH 4/7] fix test errors? --- test/tsconfig.json | 5 +---- test/unit/socket-send-receive-test.ts | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/test/tsconfig.json b/test/tsconfig.json index e3ee4344..c9a7909e 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -3,15 +3,12 @@ "include": [ "**/*.ts" ], - "ts-node": { - "files": true - }, "compilerOptions": { "types": [ "mocha" ], "lib": [ - "ESNext" + "ESNext", ] } } diff --git a/test/unit/socket-send-receive-test.ts b/test/unit/socket-send-receive-test.ts index 2804b939..88b3df0b 100644 --- a/test/unit/socket-send-receive-test.ts +++ b/test/unit/socket-send-receive-test.ts @@ -93,7 +93,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { it("should copy and release small buffers", async function () { const gc = getGcOrSkipTest(this) let weakRef: undefined | WeakRef - sockA.connect(uniqAddress(proto)) + sockA.connect(await uniqAddress(proto)) const send = async (size: number) => { const msg = Buffer.alloc(size) weakRef = new WeakRef(msg) From d00af4e36a652cfeaa6f805446486bc4246785e8 Mon Sep 17 00:00:00 2001 From: Dan Rose Date: Mon, 17 Jun 2024 17:53:30 -0500 Subject: [PATCH 5/7] lint --- test/tsconfig.json | 12 +++--------- test/unit/socket-close-test.ts | 2 +- tsconfig.json | 11 +++-------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/test/tsconfig.json b/test/tsconfig.json index c9a7909e..0c69a816 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,14 +1,8 @@ { "extends": "../tsconfig.json", - "include": [ - "**/*.ts" - ], + "include": ["**/*.ts"], "compilerOptions": { - "types": [ - "mocha" - ], - "lib": [ - "ESNext", - ] + "types": ["mocha"], + "lib": ["ESNext"] } } diff --git a/test/unit/socket-close-test.ts b/test/unit/socket-close-test.ts index 94443693..a12bf031 100644 --- a/test/unit/socket-close-test.ts +++ b/test/unit/socket-close-test.ts @@ -113,7 +113,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { let weakRef: undefined | WeakRef const task = async () => { - let context: zmq.Context | undefined = new zmq.Context() + const context: zmq.Context | undefined = new zmq.Context() const socket = new zmq.Dealer({context, linger: 0}) weakRef = new WeakRef(context) diff --git a/tsconfig.json b/tsconfig.json index 8a0b6b27..5a9785a9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,9 +4,7 @@ "target": "es2020", "declaration": true, "module": "commonjs", - "types": [ - "node", - ], + "types": ["node"], "strictPropertyInitialization": false, // TODO "strict": true, "strictNullChecks": true, @@ -19,9 +17,6 @@ "incremental": true, "sourceMap": true, "esModuleInterop": true, - "lib": [ - "ES2022", - "dom" - ] + "lib": ["ES2022", "dom"] } -} \ No newline at end of file +} From 7bf4af728e23395267d7ba72b330f516b9166dcd Mon Sep 17 00:00:00 2001 From: Dan Rose Date: Mon, 17 Jun 2024 17:56:08 -0500 Subject: [PATCH 6/7] revert tsconfig lib change --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 5a9785a9..1f41babe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,6 @@ "incremental": true, "sourceMap": true, "esModuleInterop": true, - "lib": ["ES2022", "dom"] + "lib": ["ES2020", "dom"] } } From 251dd0240aa3d8c6275c524e21d3da488d18e79d Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 17 Jun 2024 21:44:48 -0500 Subject: [PATCH 7/7] Remove changes to tsconfig (which aren't respected by ts-node loader in mocha) --- test/tsconfig.json | 8 +++----- test/unit/socket-close-test.ts | 2 ++ test/unit/socket-send-receive-test.ts | 2 ++ tsconfig.json | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/test/tsconfig.json b/test/tsconfig.json index 0c69a816..105efec3 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,8 +1,6 @@ { "extends": "../tsconfig.json", - "include": ["**/*.ts"], - "compilerOptions": { - "types": ["mocha"], - "lib": ["ESNext"] - } + "include": [ + "**/*.ts" + ] } diff --git a/test/unit/socket-close-test.ts b/test/unit/socket-close-test.ts index a12bf031..3f673fb8 100644 --- a/test/unit/socket-close-test.ts +++ b/test/unit/socket-close-test.ts @@ -1,3 +1,5 @@ +/// + /* eslint-disable @typescript-eslint/no-var-requires */ import * as zmq from "../../src" diff --git a/test/unit/socket-send-receive-test.ts b/test/unit/socket-send-receive-test.ts index 88b3df0b..2e6b1655 100644 --- a/test/unit/socket-send-receive-test.ts +++ b/test/unit/socket-send-receive-test.ts @@ -1,3 +1,5 @@ +/// + /* eslint-disable @typescript-eslint/no-var-requires */ import * as zmq from "../../src" diff --git a/tsconfig.json b/tsconfig.json index 1f41babe..163f04fe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,10 +1,11 @@ { + "root": true, "compilerOptions": { "allowJs": false, "target": "es2020", "declaration": true, "module": "commonjs", - "types": ["node"], + "types": ["node", "mocha"], "strictPropertyInitialization": false, // TODO "strict": true, "strictNullChecks": true,