diff --git a/.eslintrc b/.eslintrc index ff805017..9ed2942f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,9 +1,20 @@ { - "plugins": ["prettier"], - "extends": ["plugin:prettier/recommended", "eslint-config-atomic"], + "plugins": [ + "prettier" + ], + "extends": [ + "eslint-config-atomic" + ], "rules": { - "@typescript-eslint/quotes": ["error", "double"], - "require-await": "off" + "@typescript-eslint/quotes": [ + "error", + "double" + ], + "require-await": "off", + "@typescript-eslint/strict-boolean-expressions": "off", + "@typescript-eslint/no-explicit-any": "off", + "no-await-in-loop": "off", + "class-methods-use-this": "off" }, "ignorePatterns": [ "node_modules/", @@ -15,6 +26,8 @@ "script/*.js", "script/*.d.ts", "docs/", - "docs-raw/" + "docs-raw/", + "test/unit/compat/", + "test/bench/" ] } diff --git a/.mocharc.js b/.mocharc.js index e1f933cf..5f29897d 100644 --- a/.mocharc.js +++ b/.mocharc.js @@ -1,10 +1,18 @@ -"use strict" - -module.exports = { +/** + * @type {import('mocha').MochaOptions} + */ +const config = { require: ["ts-node/register", "rocha"], - spec: ["test/unit/*-test.ts", "test/unit/compat/*-test.{ts,js}"], + spec: [ + "test/unit/*-test.ts", + "test/unit/compat/*-test.js", + ], "expose-gc": true, "v8-expose-gc": true, "experimental-worker": true, recursive: true, + exit: true, + parallel: true, } + +module.exports = config diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..f6215f40 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + "recommendations": [ + "hbenl.vscode-mocha-test-adapter", + "hbenl.vscode-test-explorer", + "llvm-vs-code-extensions.vscode-clangd", + "xadillax.gyp", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..42452ae7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "mochaExplorer.parallel": true, + "mochaExplorer.globImplementation": "vscode" +} diff --git a/examples/majordomo/broker.ts b/examples/majordomo/broker.ts index 0ad02483..3380a7bb 100644 --- a/examples/majordomo/broker.ts +++ b/examples/majordomo/broker.ts @@ -18,7 +18,7 @@ export class Broker { await this.socket.bind(this.address) const loop = async () => { - for await (const [sender, blank, header, ...rest] of this.socket) { + for await (const [sender, _blank, header, ...rest] of this.socket) { switch (header.toString()) { case Header.Client: this.handleClient(sender, ...rest) @@ -32,7 +32,7 @@ export class Broker { } } - loop() + return loop() } async stop() { @@ -56,8 +56,10 @@ export class Broker { } case Message.Reply: { - const [client, blank, ...rep] = rest - this.dispatchReply(worker, client, ...rep) + const [client, _blank, ...rep] = rest + this.dispatchReply(worker, client, ...rep).catch(err => { + console.error(err) + }) break } @@ -85,7 +87,7 @@ export class Broker { dispatchReply(worker: Buffer, client: Buffer, ...rep: Buffer[]) { const service = this.getWorkerService(worker) - this.getService(service).dispatchReply(worker, client, ...rep) + return this.getService(service).dispatchReply(worker, client, ...rep) } deregister(worker: Buffer) { diff --git a/examples/majordomo/index.ts b/examples/majordomo/index.ts index 83660c2f..1d1e4874 100644 --- a/examples/majordomo/index.ts +++ b/examples/majordomo/index.ts @@ -4,7 +4,9 @@ import {Broker} from "./broker" import {Worker} from "./worker" async function sleep(msec: number) { - return new Promise(resolve => setTimeout(resolve, msec)) + return new Promise(resolve => { + setTimeout(resolve, msec) + }) } class TeaWorker extends Worker { @@ -40,7 +42,7 @@ async function request( await socket.send(["MDPC01", service, ...req]) try { - const [blank, header, ...res] = await socket.receive() + const [_blank, _header, ...res] = await socket.receive() console.log(`received '${res.join(", ")}' from '${service}'`) return res } catch (err) { @@ -50,9 +52,9 @@ async function request( async function main() { for (const worker of workers) { - worker.start() + await worker.start() } - broker.start() + await broker.start() /* Requests are issued in parallel. */ await Promise.all([ @@ -68,9 +70,9 @@ async function main() { ]) for (const worker of workers) { - worker.stop() + await worker.stop() } - broker.stop() + await broker.stop() } main().catch(err => { diff --git a/examples/majordomo/service.ts b/examples/majordomo/service.ts index d4d43bae..c01d078b 100644 --- a/examples/majordomo/service.ts +++ b/examples/majordomo/service.ts @@ -15,7 +15,7 @@ export class Service { dispatchRequest(client: Buffer, ...req: Buffer[]) { this.requests.push([client, req]) - this.dispatchPending() + return this.dispatchPending() } async dispatchReply(worker: Buffer, client: Buffer, ...rep: Buffer[]) { @@ -28,12 +28,15 @@ export class Service { await this.socket.send([client, null, Header.Client, this.name, ...rep]) - this.dispatchPending() + return this.dispatchPending() } async dispatchPending() { while (this.workers.size && this.requests.length) { - const [key, worker] = this.workers.entries().next().value! + const [key, worker] = this.workers.entries().next().value as [ + string, + Buffer, + ] this.workers.delete(key) const [client, req] = this.requests.shift()! @@ -42,6 +45,7 @@ export class Service { `${client.toString("hex")} req -> ${worker.toString("hex")}`, ) + // eslint-disable-next-line no-await-in-loop await this.socket.send([ worker, null, @@ -59,7 +63,7 @@ export class Service { `registered worker ${worker.toString("hex")} for '${this.name}'`, ) this.workers.set(worker.toString("hex"), worker) - this.dispatchPending() + return this.dispatchPending() } deregister(worker: Buffer) { @@ -67,6 +71,6 @@ export class Service { `deregistered worker ${worker.toString("hex")} for '${this.name}'`, ) this.workers.delete(worker.toString("hex")) - this.dispatchPending() + return this.dispatchPending() } } diff --git a/examples/majordomo/worker.ts b/examples/majordomo/worker.ts index 558bebff..12efb755 100644 --- a/examples/majordomo/worker.ts +++ b/examples/majordomo/worker.ts @@ -16,8 +16,14 @@ export class Worker { await this.socket.send([null, Header.Worker, Message.Ready, this.service]) const loop = async () => { - for await (const [blank1, header, type, client, blank2, ...req] of this - .socket) { + for await (const [ + _blank1, + _header, + _type, + client, + _blank2, + ...req + ] of this.socket) { const rep = await this.process(...req) try { await this.socket.send([ @@ -34,7 +40,7 @@ export class Worker { } } - loop() + return loop() } async stop() { diff --git a/examples/threaded-worker/processor.ts b/examples/threaded-worker/processor.ts index 1e5b1baa..156218b8 100644 --- a/examples/threaded-worker/processor.ts +++ b/examples/threaded-worker/processor.ts @@ -22,7 +22,9 @@ export class Processor { this.input.bind("inproc://input"), this.output.bind("inproc://output"), this.signal.bind("inproc://signal"), - new Promise(resolve => setTimeout(resolve, 100)), + new Promise(resolve => { + setTimeout(resolve, 100) + }), ]) this.exit = Promise.all([ThreadedWorker.spawn(this.threads)]) diff --git a/examples/threaded-worker/threaded-worker.ts b/examples/threaded-worker/threaded-worker.ts index 211bb739..625797a0 100644 --- a/examples/threaded-worker/threaded-worker.ts +++ b/examples/threaded-worker/threaded-worker.ts @@ -43,12 +43,14 @@ export class ThreadedWorker { const listen = async () => { for await (const [sig] of this.signal) { if (sig.toString() === "stop") { - this.stop() + await this.stop() } } } - listen() + listen().catch(err => { + throw err + }) } async stop() { diff --git a/package.json b/package.json index 77b007ea..9d9d6a98 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@types/fs-extra": "^9.0.13", "@types/mocha": "^10.0.6", "@types/node": "^18.19.34", + "@types/proper-lockfile": "^4.1.4", "@types/semver": "^7.5.8", "@types/shelljs": "^0.8.15", "@types/which": "^2.0.2", @@ -49,6 +50,8 @@ "npm-run-all2": "^6.2.0", "prebuildify": "^6.0.1", "prettier": "^2.8.8", + "proper-lockfile": "^4.1.2", + "random-words": "^1", "rocha": "^2.5.10", "semver": "^7.6.2", "ts-node": "~10.9.2", @@ -100,7 +103,7 @@ "format": "prettier --write .", "test.electron.renderer": "run-s build && electron-mocha --renderer", "lint.clang-format": "clang-format -i -style=file ./src/*.cc ./src/*.h ./src/util/*.h", - "lint-test.eslint": "eslint **/*.{ts,tsx,js,jsx,cjs,mjs,json,yaml} --no-error-on-unmatched-pattern --cache --cache-location ./.cache/eslint/", + "lint-test.eslint": "eslint ./**/*.{ts,tsx,js,jsx,cjs,mjs,json,yaml} --no-error-on-unmatched-pattern --cache --cache-location ./.cache/eslint/", "lint.eslint": "pnpm run lint-test.eslint --fix", "lint": "run-p lint.eslint lint.clang-format", "lint-test": "run-s lint-test.eslint", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f3300ebb..84661212 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,9 @@ importers: '@types/node': specifier: ^18.19.34 version: 18.19.34 + '@types/proper-lockfile': + specifier: ^4.1.4 + version: 4.1.4 '@types/semver': specifier: ^7.5.8 version: 7.5.8 @@ -100,6 +103,12 @@ importers: prettier: specifier: ^2.8.8 version: 2.8.8 + proper-lockfile: + specifier: ^4.1.2 + version: 4.1.2 + random-words: + specifier: ^1 + version: 1.3.0 rocha: specifier: ^2.5.10 version: 2.5.10 @@ -473,9 +482,15 @@ packages: '@types/normalize-package-data@2.4.1': resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + '@types/proper-lockfile@4.1.4': + resolution: {integrity: sha512-uo2ABllncSqg9F1D4nugVl9v93RmjxF6LJzQLMLDdPaXCUIDPeOJ21Gbqi43xNKzBi/WQ0Q0dICqufzQbMjipQ==} + '@types/responselike@1.0.0': resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} + '@types/retry@0.12.5': + resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==} + '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} @@ -3006,6 +3021,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} @@ -3025,6 +3043,9 @@ packages: ramda@0.25.0: resolution: {integrity: sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==} + random-words@1.3.0: + resolution: {integrity: sha512-brwCGe+DN9DqZrAQVNj1Tct1Lody6GrYL/7uei5wfjeQdacFyFd2h/51LNlOoBMzIKMS9xohuL4+wlF/z1g/xg==} + randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -3168,6 +3189,9 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + seedrandom@3.0.5: + resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} + semver-diff@2.1.0: resolution: {integrity: sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==} engines: {node: '>=0.10.0'} @@ -4243,10 +4267,16 @@ snapshots: '@types/normalize-package-data@2.4.1': {} + '@types/proper-lockfile@4.1.4': + dependencies: + '@types/retry': 0.12.5 + '@types/responselike@1.0.0': dependencies: '@types/node': 18.19.34 + '@types/retry@0.12.5': {} + '@types/semver@7.5.8': {} '@types/shelljs@0.8.15': @@ -7241,6 +7271,12 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proper-lockfile@4.1.2: + dependencies: + graceful-fs: 4.2.10 + retry: 0.12.0 + signal-exit: 3.0.7 + pseudomap@1.0.2: {} pump@3.0.0: @@ -7258,6 +7294,10 @@ snapshots: ramda@0.25.0: {} + random-words@1.3.0: + dependencies: + seedrandom: 3.0.5 + randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -7439,6 +7479,8 @@ snapshots: safer-buffer@2.1.2: optional: true + seedrandom@3.0.5: {} + semver-diff@2.1.0: dependencies: semver: 5.7.1 diff --git a/src/.eslintrc b/src/.eslintrc index dda6c0d1..58517a5b 100644 --- a/src/.eslintrc +++ b/src/.eslintrc @@ -1,3 +1,3 @@ { - "extends": ["../.eslintrc", "eslint-config-atomic/strict"] + "extends": ["../.eslintrc"] } diff --git a/src/compat.ts b/src/compat.ts index 0d6d440d..815ed175 100644 --- a/src/compat.ts +++ b/src/compat.ts @@ -1,6 +1,3 @@ -/* eslint-disable @typescript-eslint/camelcase */ -/* eslint-disable @typescript-eslint/no-var-requires */ - /* The API of the compatibility layer and parts of the implementation has been adapted from the original ZeroMQ.js version (up to 5.x) for which the license and copyright notice is reproduced below. diff --git a/test/.eslintrc b/test/.eslintrc index a1f8dc0f..7af362f7 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -1,5 +1,10 @@ { "extends": "../.eslintrc", + "ignorePatterns": [ + "unit/compat/", + "bench/", + "node_modules" + ], "rules": { "no-invalid-this": "off", "no-inner-declarations": "off", @@ -11,6 +16,8 @@ "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/explicit-function-return-type": "off", "no-await-in-loop": "off", + "no-shadow": "off", + "@typescript-eslint/no-shadow": "off", "require-await": "off", "import/no-extraneous-dependencies": "off" } diff --git a/test/typings-compatibility/template/package.json b/test/typings-compatibility/template/package.json index d3b7f408..1f66d800 100644 --- a/test/typings-compatibility/template/package.json +++ b/test/typings-compatibility/template/package.json @@ -8,6 +8,7 @@ }, "license": "MIT", "devDependencies": { - "typescript": "x.x.x" + "typescript": "x.x.x", + "@types/node": "y.y.y" } } diff --git a/test/unit/context-process-exit-test.ts b/test/unit/context-process-exit-test.ts index 4f21b705..bc04665a 100644 --- a/test/unit/context-process-exit-test.ts +++ b/test/unit/context-process-exit-test.ts @@ -30,13 +30,16 @@ describe("context process exit", function () { it("should not occur when sockets are open and polling", async function () { this.slow(1000) - const {code} = await createProcess(() => { + const {code, is_timeout} = await createProcess(() => { const socket1 = new zmq.Dealer() socket1.connect("inproc://foo") - socket1.receive() + socket1.receive().catch(err => { + throw err + }) }) - assert.equal(code, 1) + assert.equal(code, -1) + assert.equal(is_timeout, true) }) it("should produce warning when messages are queued with blocky", async function () { @@ -45,7 +48,9 @@ describe("context process exit", function () { zmq.context.blocky = true const socket1 = new zmq.Dealer({linger: 1000}) socket1.connect("tcp://127.0.0.1:4567") - socket1.send(null) + socket1.send(null).catch(err => { + throw err + }) }) if (semver.satisfies(zmq.version, ">= 4.2")) { @@ -67,7 +72,9 @@ describe("context process exit", function () { zmq.context.blocky = false const socket1 = new zmq.Dealer({linger: 1000}) socket1.connect("tcp://127.0.0.1:4567") - socket1.send(null) + socket1.send(null).catch(err => { + throw err + }) }) assert.match( @@ -82,7 +89,9 @@ describe("context process exit", function () { zmq.context.blocky = true const socket1 = new zmq.Dealer({linger: 50}) socket1.connect("tcp://127.0.0.1:4567") - socket1.send(null) + socket1.send(null).catch(err => { + throw err + }) }) assert.equal(stderr.toString(), "") @@ -134,14 +143,15 @@ describe("context process exit", function () { it("should not occur when sockets are open and polling", async function () { this.slow(1000) - const {code} = await createProcess(() => { + const {code, is_timeout} = await createProcess(() => { const context = new zmq.Context() const socket1 = new zmq.Dealer({context}) socket1.connect("inproc://foo") socket1.receive() }) - assert.equal(code, 1) + assert.equal(code, -1) + assert.equal(is_timeout, true) }) }) }) diff --git a/test/unit/helpers.ts b/test/unit/helpers.ts index 5c49282f..e1d90f63 100644 --- a/test/unit/helpers.ts +++ b/test/unit/helpers.ts @@ -1,6 +1,7 @@ import * as path from "path" import * as semver from "semver" import * as fs from "fs" +import * as lockfile from "proper-lockfile" import {spawn} from "child_process" @@ -12,18 +13,63 @@ if (semver.satisfies(zmq.version, ">= 4.2")) { zmq.context.blocky = false } -/* Windows cannot bind on a ports just above 1014; start higher to be safe. */ -let seq = 5000 +/** + * Get a unique id to be used as a port number or IPC path. + * This function is thread-safe and will use a lock file to ensure that the id is unique. + */ +let idFallback = 5000 +async function getUniqueId() { + const idPath = path.resolve(__dirname, "../../tmp/id") + await fs.promises.mkdir(path.dirname(idPath), {recursive: true}) + + try { + // Create the file if it doesn't exist + if (!fs.existsSync(idPath)) { + await fs.promises.writeFile(idPath, "5000", "utf8") + + /* Windows cannot bind on a ports just above 1014; start higher to be safe. */ + return 5000 + } + + await lockfile.lock(idPath, {retries: 10}) + + // Read the current number from the file + const idString = await fs.promises.readFile(idPath, "utf8") + let id = parseInt(idString, 10) + + // Increment the number + id++ + + // Ensure the number is within the valid port range + if (id > 65535) { + idFallback++ + id = idFallback + } + + // Write the new number back to the file + await fs.promises.writeFile(idPath, id.toString(), "utf8") + + return id + } catch (err) { + console.error(`Error getting unique id via id file: ${err}`) + return idFallback++ + } finally { + // Release the lock + try { + await lockfile.unlock(idPath) + } catch { + // ignore + } + } +} type Proto = "ipc" | "tcp" | "udp" | "inproc" -export function uniqAddress(proto: Proto) { - const id = seq++ +export async function uniqAddress(proto: Proto) { + const id = await getUniqueId() switch (proto) { case "ipc": { const sock = path.resolve(__dirname, `../../tmp/${proto}-${id}`) - // create the directory - fs.mkdirSync(path.dirname(sock), {recursive: true}) return `${proto}://${sock}` } @@ -42,7 +88,7 @@ export function testProtos(...requested: Proto[]) { const set = new Set(requested) /* Do not test with ipc if unsupported. */ - if (!zmq.capability.ipc) { + if (zmq.capability.ipc !== true) { set.delete("ipc") } @@ -102,13 +148,19 @@ interface Result { code: number stdout: Buffer stderr: Buffer + is_timeout: boolean } export function createProcess(fn: () => void): Promise { const src = ` const zmq = require(${JSON.stringify(path.resolve(__dirname, "../.."))}) const fn = ${fn.toString()} - fn() + const result = fn() + if (result instanceof Promise) { + result.catch(err => { + throw err + }) + } ` const child = spawn(process.argv[0], ["--expose_gc"]) @@ -127,14 +179,27 @@ export function createProcess(fn: () => void): Promise { return new Promise((resolve, reject) => { child.on("close", (code: number, signal: string) => { if (signal) { - reject(new Error(`Child exited with ${signal}`)) + reject( + new Error( + `Child exited with ${signal}:\n${stdout.toString()}\n${stderr.toString()}`, + ), + ) } else { - resolve({code, stdout, stderr}) + if (code !== 0) { + console.error( + `Child exited with code ${code}:\n${stdout.toString()}\n${stderr.toString()}`, + ) + } + + resolve({code, stdout, stderr, is_timeout: false}) } }) setTimeout(() => { - resolve({code: -1, stdout, stderr}) + resolve({code: -1, stdout, stderr, is_timeout: true}) + console.error( + `Child timed out\n${stdout.toString()}\n${stderr.toString()}`, + ) child.kill() }, 750) }) @@ -144,7 +209,9 @@ export function captureEvent( socket: zmq.Socket, type: E, ): Promise> { - return new Promise(resolve => socket.events.on(type, resolve)) + return new Promise(resolve => { + socket.events.on(type, resolve) + }) } export async function captureEventsUntil( diff --git a/test/unit/proxy-router-dealer-test.ts b/test/unit/proxy-router-dealer-test.ts index 889cee58..f5d9a67f 100644 --- a/test/unit/proxy-router-dealer-test.ts +++ b/test/unit/proxy-router-dealer-test.ts @@ -22,8 +22,8 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { beforeEach(async function () { proxy = new zmq.Proxy(new zmq.Router(), new zmq.Dealer()) - frontAddress = uniqAddress(proto) - backAddress = uniqAddress(proto) + frontAddress = await uniqAddress(proto) + backAddress = await uniqAddress(proto) req = new zmq.Request() rep = new zmq.Reply() diff --git a/test/unit/proxy-run-test.ts b/test/unit/proxy-run-test.ts index f73011c9..7f2955e3 100644 --- a/test/unit/proxy-run-test.ts +++ b/test/unit/proxy-run-test.ts @@ -25,7 +25,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should fail if front end is not bound or connected", async function () { - await proxy.backEnd.bind(uniqAddress(proto)) + await proxy.backEnd.bind(await uniqAddress(proto)) try { await proxy.run() @@ -39,8 +39,8 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should fail if front end is not open", async function () { - await proxy.frontEnd.bind(uniqAddress(proto)) - await proxy.backEnd.bind(uniqAddress(proto)) + await proxy.frontEnd.bind(await uniqAddress(proto)) + await proxy.backEnd.bind(await uniqAddress(proto)) proxy.frontEnd.close() try { @@ -55,7 +55,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should fail if back end is not bound or connected", async function () { - await proxy.frontEnd.bind(uniqAddress(proto)) + await proxy.frontEnd.bind(await uniqAddress(proto)) try { await proxy.run() @@ -71,8 +71,8 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should fail if back end is not open", async function () { - await proxy.frontEnd.bind(uniqAddress(proto)) - await proxy.backEnd.bind(uniqAddress(proto)) + await proxy.frontEnd.bind(await uniqAddress(proto)) + await proxy.backEnd.bind(await uniqAddress(proto)) proxy.backEnd.close() try { diff --git a/test/unit/proxy-terminate-test.ts b/test/unit/proxy-terminate-test.ts index 80b89d9b..93cce818 100644 --- a/test/unit/proxy-terminate-test.ts +++ b/test/unit/proxy-terminate-test.ts @@ -24,17 +24,9 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { global.gc?.() }) - it("should throw if called after termination", async function () { - await proxy.frontEnd.bind(uniqAddress(proto)) - await proxy.backEnd.bind(uniqAddress(proto)) - - const sleep_ms = 50 - - setTimeout(() => proxy.terminate(), sleep_ms) - await proxy.run() - + const terminator_test = () => { try { - await proxy.terminate() + proxy.terminate() assert.ok(false) } catch (err) { if (!isFullError(err)) { @@ -44,6 +36,27 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { assert.equal(err.code, "EBADF") assert.typeOf(err.errno, "number") } + } + + it("should throw if not started yet", async function () { + await proxy.frontEnd.bind(await uniqAddress(proto)) + await proxy.backEnd.bind(await uniqAddress(proto)) + + terminator_test() + }) + + it("should throw if called after termination", async function () { + await proxy.frontEnd.bind(await uniqAddress(proto)) + await proxy.backEnd.bind(await uniqAddress(proto)) + + setTimeout(() => { + proxy.terminate() + }, 50) + + await proxy.run() + // TODO throws Operation not supported + + terminator_test() }) }) } diff --git a/test/unit/socket-bind-unbind-test.ts b/test/unit/socket-bind-unbind-test.ts index 81bcabda..e235ef31 100644 --- a/test/unit/socket-bind-unbind-test.ts +++ b/test/unit/socket-bind-unbind-test.ts @@ -19,12 +19,12 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { describe("bind", function () { it("should resolve", async function () { - await sock.bind(uniqAddress(proto)) + await sock.bind(await uniqAddress(proto)) assert.ok(true) }) it("should throw error if not bound to endpoint", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) try { await sock.unbind(address) assert.ok(false) @@ -72,8 +72,11 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { it("should fail during other bind", async function () { let promise try { - promise = sock.bind(uniqAddress(proto)) - await sock.bind(uniqAddress(proto)) + const address = await uniqAddress(proto) + const address2 = await uniqAddress(proto) + + promise = sock.bind(address) + await sock.bind(address2) assert.ok(false) } catch (err) { if (!isFullError(err)) { @@ -92,7 +95,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { describe("unbind", function () { it("should unbind", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) await sock.bind(address) await sock.unbind(address) assert.ok(true) @@ -130,7 +133,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { it("should fail during other unbind", async function () { let promise - const address = uniqAddress(proto) + const address = await uniqAddress(proto) await sock.bind(address) try { promise = sock.unbind(address) diff --git a/test/unit/socket-close-test.ts b/test/unit/socket-close-test.ts index dc2cc173..fd76ccf8 100644 --- a/test/unit/socket-close-test.ts +++ b/test/unit/socket-close-test.ts @@ -60,7 +60,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should close after successful bind", async function () { - const promise = sock.bind(uniqAddress(proto)) + const promise = sock.bind(await uniqAddress(proto)) sock.close() assert.equal(sock.closed, false) await promise @@ -68,7 +68,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should close after unsuccessful bind", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) await sock.bind(address) const promise = sock.bind(address) sock.close() @@ -83,7 +83,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should close after successful unbind", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) await sock.bind(address) const promise = sock.unbind(address) sock.close() @@ -93,7 +93,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should close after unsuccessful unbind", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const promise = sock.unbind(address) sock.close() assert.equal(sock.closed, false) @@ -130,7 +130,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { context = undefined global.gc!() - socket.connect(uniqAddress(proto)) + socket.connect(await uniqAddress(proto)) await socket.send(Buffer.from("foo")) socket.close() } diff --git a/test/unit/socket-connect-disconnect-test.ts b/test/unit/socket-connect-disconnect-test.ts index 3d9a39b4..812056a1 100644 --- a/test/unit/socket-connect-disconnect-test.ts +++ b/test/unit/socket-connect-disconnect-test.ts @@ -52,7 +52,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { if (semver.satisfies(zmq.version, ">= 4.1")) { it("should allow setting routing id on router", async function () { sock = new zmq.Router({mandatory: true, linger: 0}) - await sock.connect(uniqAddress(proto), {routingId: "remoteId"}) + await sock.connect(await uniqAddress(proto), {routingId: "remoteId"}) await sock.send(["remoteId", "hi"]) }) } @@ -60,7 +60,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { describe("disconnect", function () { it("should throw error if not connected to endpoint", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) try { await sock.disconnect(address) assert.ok(false) diff --git a/test/unit/socket-construction-test.ts b/test/unit/socket-construction-test.ts index fe09d6dd..75952ec3 100644 --- a/test/unit/socket-construction-test.ts +++ b/test/unit/socket-construction-test.ts @@ -159,15 +159,16 @@ describe("socket construction", function () { ) }) - if (!zmq.capability.draft) { - it("should throw with draft type", function () { - assert.throws( - () => new (zmq.Socket as any)(14), - Error, - "Invalid argument", - ) - }) - } + it("should throw with draft type", function () { + if (zmq.capability.draft === true) { + this.skip() + } + assert.throws( + () => new (zmq.Socket as any)(14), + Error, + "Invalid argument", + ) + }) it("should throw error on file descriptor limit", async function () { const context = new zmq.Context({maxSockets: 10}) diff --git a/test/unit/socket-curve-send-receive-test.ts b/test/unit/socket-curve-send-receive-test.ts index 8b22ecf1..8e86bd9d 100644 --- a/test/unit/socket-curve-send-receive-test.ts +++ b/test/unit/socket-curve-send-receive-test.ts @@ -5,7 +5,7 @@ import {testProtos, uniqAddress} from "./helpers" for (const proto of testProtos("tcp", "ipc", "inproc")) { describe(`socket with ${proto} curve send/receive`, function () { - if (!zmq.capability.curve) { + if (zmq.capability.curve !== true) { return } @@ -39,7 +39,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { describe("when connected", function () { beforeEach(async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) await sockB.bind(address) await sockA.connect(address) }) diff --git a/test/unit/socket-draft-dgram-test.ts b/test/unit/socket-draft-dgram-test.ts index 02b10312..7fa0e94a 100644 --- a/test/unit/socket-draft-dgram-test.ts +++ b/test/unit/socket-draft-dgram-test.ts @@ -5,60 +5,61 @@ import {assert} from "chai" import {createSocket} from "dgram" import {testProtos, uniqAddress} from "./helpers" -if (zmq.capability.draft) { - for (const proto of testProtos("udp")) { - describe(`draft socket with ${proto} dgram`, function () { - let dgram: draft.Datagram +for (const proto of testProtos("udp")) { + describe(`draft socket with ${proto} dgram`, function () { + if (zmq.capability.draft !== true) { + if (process.env.ZMQ_DRAFT === "true") { + throw new Error("Draft API requested but not available at runtime.") + } + return + } - beforeEach(function () { - dgram = new draft.Datagram() - }) + let dgram: draft.Datagram - afterEach(function () { - dgram.close() - global.gc?.() - }) + beforeEach(function () { + dgram = new draft.Datagram() + }) - describe("send/receive", function () { - it("should deliver messages", async function () { - const messages = ["foo", "bar", "baz", "qux"] - const address = uniqAddress(proto) - const port = parseInt(address.split(":").pop()!, 10) + afterEach(function () { + dgram.close() + global.gc?.() + }) - await dgram.bind(address) + describe("send/receive", function () { + it("should deliver messages", async function () { + const messages = ["foo", "bar", "baz", "qux"] + const address = await uniqAddress(proto) + const port = parseInt(address.split(":").pop()!, 10) - const echo = async () => { - for await (const [id, msg] of dgram) { - await dgram.send([id, msg]) - } - } + await dgram.bind(address) - const received: string[] = [] - const send = async () => { - for (const msg of messages) { - const client = createSocket("udp4") - await new Promise(resolve => { - client.on("message", res => { - received.push(res.toString()) - client.close() - resolve(undefined) - }) + const echo = async () => { + for await (const [id, msg] of dgram) { + await dgram.send([id, msg]) + } + } - client.send(msg, port, "localhost") + const received: string[] = [] + const send = async () => { + for (const msg of messages) { + const client = createSocket("udp4") + await new Promise(resolve => { + client.on("message", res => { + received.push(res.toString()) + client.close() + resolve(undefined) }) - } - dgram.close() + client.send(msg, port, "localhost") + }) } - await Promise.all([echo(), send()]) - assert.deepEqual(received, messages) - }) + dgram.close() + } + + await Promise.all([echo(), send()]) + assert.deepEqual(received, messages) }) }) - } -} else { - if (process.env.ZMQ_DRAFT === "true") { - throw new Error("Draft API requested but not available at runtime.") - } + }) } diff --git a/test/unit/socket-draft-radio-dish-test.ts b/test/unit/socket-draft-radio-dish-test.ts index 11c887b8..7a8eaaea 100644 --- a/test/unit/socket-draft-radio-dish-test.ts +++ b/test/unit/socket-draft-radio-dish-test.ts @@ -4,123 +4,124 @@ import * as draft from "../../src/draft" import {assert} from "chai" import {testProtos, uniqAddress} from "./helpers" -if (zmq.capability.draft) { - for (const proto of testProtos("tcp", "ipc", "inproc", "udp")) { - describe(`draft socket with ${proto} radio/dish`, function () { - let radio: draft.Radio - let dish: draft.Dish - - beforeEach(function () { - radio = new draft.Radio() - dish = new draft.Dish() - }) +for (const proto of testProtos("tcp", "ipc", "inproc", "udp")) { + describe(`draft socket with ${proto} radio/dish`, function () { + if (zmq.capability.draft !== true) { + if (process.env.ZMQ_DRAFT === "true") { + throw new Error("Draft API requested but not available at runtime.") + } + return + } + + let radio: draft.Radio + let dish: draft.Dish + + beforeEach(function () { + radio = new draft.Radio() + dish = new draft.Dish() + }) - afterEach(function () { - global.gc?.() - radio.close() - dish.close() - global.gc?.() - }) + afterEach(function () { + global.gc?.() + radio.close() + dish.close() + global.gc?.() + }) - describe("send/receive", function () { - it("should deliver messages", async function () { - /* RADIO -> foo -> DISH + describe("send/receive", function () { + it("should deliver messages", async function () { + /* RADIO -> foo -> DISH -> bar -> joined all -> baz -> -> qux -> */ - const address = uniqAddress(proto) - const messages = ["foo", "bar", "baz", "qux"] + const address = await uniqAddress(proto) + const messages = ["foo", "bar", "baz", "qux"] - /* Max 15 non-null bytes. */ - const uuid = Buffer.from([ - 0xf6, 0x46, 0x1f, 0x03, 0xd2, 0x0d, 0xc8, 0x66, 0xe5, 0x5f, 0xf5, - 0xa1, 0x65, 0x62, 0xb2, - ]) + /* Max 15 non-null bytes. */ + const uuid = Buffer.from([ + 0xf6, 0x46, 0x1f, 0x03, 0xd2, 0x0d, 0xc8, 0x66, 0xe5, 0x5f, 0xf5, + 0xa1, 0x65, 0x62, 0xb2, + ]) - const received: string[] = [] + const received: string[] = [] - dish.join(uuid) + dish.join(uuid) - await dish.bind(address) - await radio.connect(address) + await dish.bind(address) + await radio.connect(address) - const send = async () => { - /* Wait briefly before publishing to avoid slow joiner syndrome. */ - await new Promise(resolve => { - setTimeout(resolve, 25) - }) - for (const msg of messages) { - await radio.send(msg, {group: uuid}) - } + const send = async () => { + /* Wait briefly before publishing to avoid slow joiner syndrome. */ + await new Promise(resolve => { + setTimeout(resolve, 25) + }) + for (const msg of messages) { + await radio.send(msg, {group: uuid}) } - - const receive = async () => { - for await (const [msg, {group}] of dish) { - assert.instanceOf(msg, Buffer) - assert.instanceOf(group, Buffer) - assert.deepEqual(group, uuid) - received.push(msg.toString()) - if (received.length === messages.length) { - break - } + } + + const receive = async () => { + for await (const [msg, {group}] of dish) { + assert.instanceOf(msg, Buffer) + assert.instanceOf(group, Buffer) + assert.deepEqual(group, uuid) + received.push(msg.toString()) + if (received.length === messages.length) { + break } } + } - await Promise.all([send(), receive()]) - assert.deepEqual(received, messages) - }) + await Promise.all([send(), receive()]) + assert.deepEqual(received, messages) }) + }) - describe("join/leave", function () { - it("should filter messages", async function () { - /* RADIO -> foo -X DISH + describe("join/leave", function () { + it("should filter messages", async function () { + /* RADIO -> foo -X DISH -> bar -> joined "ba" -> baz -> -> qux -X */ - const address = uniqAddress(proto) - const messages = ["foo", "bar", "baz", "qux"] - const received: string[] = [] + const address = await uniqAddress(proto) + const messages = ["foo", "bar", "baz", "qux"] + const received: string[] = [] - /* Everything after null byte should be ignored. */ - dish.join(Buffer.from("fo\x00ba"), Buffer.from("ba\x00fo")) - dish.leave(Buffer.from("fo")) + /* Everything after null byte should be ignored. */ + dish.join(Buffer.from("fo\x00ba"), Buffer.from("ba\x00fo")) + dish.leave(Buffer.from("fo")) - await dish.bind(address) - await radio.connect(address) + await dish.bind(address) + await radio.connect(address) - const send = async () => { - /* Wait briefly before publishing to avoid slow joiner syndrome. */ - await new Promise(resolve => { - setTimeout(resolve, 25) - }) - for (const msg of messages) { - await radio.send(msg, {group: msg.slice(0, 2)}) - } + const send = async () => { + /* Wait briefly before publishing to avoid slow joiner syndrome. */ + await new Promise(resolve => { + setTimeout(resolve, 25) + }) + for (const msg of messages) { + await radio.send(msg, {group: msg.slice(0, 2)}) } - - const receive = async () => { - for await (const [msg, {group}] of dish) { - assert.instanceOf(msg, Buffer) - assert.deepEqual(group, msg.slice(0, 2)) - received.push(msg.toString()) - if (received.length === 2) { - break - } + } + + const receive = async () => { + for await (const [msg, {group}] of dish) { + assert.instanceOf(msg, Buffer) + assert.deepEqual(group, msg.slice(0, 2)) + received.push(msg.toString()) + if (received.length === 2) { + break } } + } - await Promise.all([send(), receive()]) - assert.deepEqual(received, ["bar", "baz"]) - }) + await Promise.all([send(), receive()]) + assert.deepEqual(received, ["bar", "baz"]) }) }) - } -} else { - if (process.env.ZMQ_DRAFT === "true") { - throw new Error("Draft API requested but not available at runtime.") - } + }) } diff --git a/test/unit/socket-draft-scatter-gather-test.ts b/test/unit/socket-draft-scatter-gather-test.ts index dcfaa369..d97e6910 100644 --- a/test/unit/socket-draft-scatter-gather-test.ts +++ b/test/unit/socket-draft-scatter-gather-test.ts @@ -4,38 +4,73 @@ import * as draft from "../../src/draft" import {assert} from "chai" import {testProtos, uniqAddress} from "./helpers" -if (zmq.capability.draft) { - for (const proto of testProtos("tcp", "ipc", "inproc")) { - describe(`socket with ${proto} scatter/gather`, function () { - let scatter: draft.Scatter - let gather: draft.Gather - - beforeEach(function () { - scatter = new draft.Scatter() - gather = new draft.Gather() - }) +for (const proto of testProtos("tcp", "ipc", "inproc")) { + describe(`socket with ${proto} scatter/gather`, function () { + if (zmq.capability.draft !== true) { + if (process.env.ZMQ_DRAFT === "true") { + throw new Error("Draft API requested but not available at runtime.") + } + return + } + + let scatter: draft.Scatter + let gather: draft.Gather + + beforeEach(function () { + scatter = new draft.Scatter() + gather = new draft.Gather() + }) - afterEach(function () { - scatter.close() - gather.close() - global.gc?.() - }) + afterEach(function () { + scatter.close() + gather.close() + global.gc?.() + }) - describe("send/receive", function () { - it("should deliver messages", async function () { - /* SCATTER -> foo -> GATHER + describe("send/receive", function () { + it("should deliver messages", async function () { + /* SCATTER -> foo -> GATHER -> bar -> -> baz -> -> qux -> */ - const address = uniqAddress(proto) + const address = await uniqAddress(proto) + const messages = ["foo", "bar", "baz", "qux"] + const received: string[] = [] + + await gather.bind(address) + await scatter.connect(address) + + for (const msg of messages) { + await scatter.send(msg) + } + + for await (const [msg] of gather) { + assert.instanceOf(msg, Buffer) + received.push(msg.toString()) + if (received.length === messages.length) { + break + } + } + + assert.deepEqual(received, messages) + }) + + if (proto !== "inproc") { + it("should deliver messages with immediate", async function () { + const address = await uniqAddress(proto) const messages = ["foo", "bar", "baz", "qux"] const received: string[] = [] await gather.bind(address) + + scatter.immediate = true await scatter.connect(address) + /* Never connected, without immediate: true it would cause lost msgs. */ + await scatter.connect(await uniqAddress(proto)) + for (const msg of messages) { await scatter.send(msg) } @@ -50,41 +85,7 @@ if (zmq.capability.draft) { assert.deepEqual(received, messages) }) - - if (proto !== "inproc") { - it("should deliver messages with immediate", async function () { - const address = uniqAddress(proto) - const messages = ["foo", "bar", "baz", "qux"] - const received: string[] = [] - - await gather.bind(address) - - scatter.immediate = true - await scatter.connect(address) - - /* Never connected, without immediate: true it would cause lost msgs. */ - await scatter.connect(uniqAddress(proto)) - - for (const msg of messages) { - await scatter.send(msg) - } - - for await (const [msg] of gather) { - assert.instanceOf(msg, Buffer) - received.push(msg.toString()) - if (received.length === messages.length) { - break - } - } - - assert.deepEqual(received, messages) - }) - } - }) + } }) - } -} else { - if (process.env.ZMQ_DRAFT === "true") { - throw new Error("Draft API requested but not available at runtime.") - } + }) } diff --git a/test/unit/socket-draft-server-client-test.ts b/test/unit/socket-draft-server-client-test.ts index 2ab34347..37544562 100644 --- a/test/unit/socket-draft-server-client-test.ts +++ b/test/unit/socket-draft-server-client-test.ts @@ -5,91 +5,92 @@ import {assert} from "chai" import {testProtos, uniqAddress} from "./helpers" import {isFullError} from "../../src/errors" -if (zmq.capability.draft) { - for (const proto of testProtos("tcp", "ipc", "inproc")) { - describe(`draft socket with ${proto} server/client`, function () { - let server: draft.Server - let clientA: draft.Client - let clientB: draft.Client +for (const proto of testProtos("tcp", "ipc", "inproc")) { + describe(`draft socket with ${proto} server/client`, function () { + if (zmq.capability.draft !== true) { + if (process.env.ZMQ_DRAFT === "true") { + throw new Error("Draft API requested but not available at runtime.") + } + return + } - beforeEach(function () { - server = new draft.Server() - clientA = new draft.Client() - clientB = new draft.Client() - }) + let server: draft.Server + let clientA: draft.Client + let clientB: draft.Client - afterEach(function () { - server.close() - clientA.close() - clientB.close() - global.gc?.() - }) + beforeEach(function () { + server = new draft.Server() + clientA = new draft.Client() + clientB = new draft.Client() + }) - describe("send/receive", function () { - it("should deliver messages", async function () { - const address = uniqAddress(proto) - const messages = ["foo", "bar", "baz", "qux"] - const receivedA: string[] = [] - const receivedB: string[] = [] + afterEach(function () { + server.close() + clientA.close() + clientB.close() + global.gc?.() + }) - await server.bind(address) - clientA.connect(address) - clientB.connect(address) + describe("send/receive", function () { + it("should deliver messages", async function () { + const address = await uniqAddress(proto) + const messages = ["foo", "bar", "baz", "qux"] + const receivedA: string[] = [] + const receivedB: string[] = [] - const echo = async () => { - for await (const [msg, {routingId}] of server) { - assert.typeOf(routingId, "number") - await server.send(msg, {routingId}) - } + await server.bind(address) + clientA.connect(address) + clientB.connect(address) + + const echo = async () => { + for await (const [msg, {routingId}] of server) { + assert.typeOf(routingId, "number") + await server.send(msg, {routingId}) } + } - const send = async () => { - for (const msg of messages) { - await clientA.send(msg) - await clientB.send(msg) - } + const send = async () => { + for (const msg of messages) { + await clientA.send(msg) + await clientB.send(msg) + } - for await (const msg of clientA) { - receivedA.push(msg.toString()) - if (receivedA.length === messages.length) { - break - } + for await (const msg of clientA) { + receivedA.push(msg.toString()) + if (receivedA.length === messages.length) { + break } + } - for await (const msg of clientB) { - receivedB.push(msg.toString()) - if (receivedB.length === messages.length) { - break - } + for await (const msg of clientB) { + receivedB.push(msg.toString()) + if (receivedB.length === messages.length) { + break } - - server.close() } - await Promise.all([echo(), send()]) - assert.deepEqual(receivedA, messages) - assert.deepEqual(receivedB, messages) - }) + server.close() + } - it("should fail with unroutable message", async function () { - try { - await server.send("foo", {routingId: 12345}) - assert.ok(false) - } catch (err) { - if (!isFullError(err)) { - throw err - } + await Promise.all([echo(), send()]) + assert.deepEqual(receivedA, messages) + assert.deepEqual(receivedB, messages) + }) - assert.equal(err.message, "Host unreachable") - assert.equal(err.code, "EHOSTUNREACH") - assert.typeOf(err.errno, "number") + it("should fail with unroutable message", async function () { + try { + await server.send("foo", {routingId: 12345}) + assert.ok(false) + } catch (err) { + if (!isFullError(err)) { + throw err } - }) + + assert.equal(err.message, "Host unreachable") + assert.equal(err.code, "EHOSTUNREACH") + assert.typeOf(err.errno, "number") + } }) }) - } -} else { - if (process.env.ZMQ_DRAFT === "true") { - throw new Error("Draft API requested but not available at runtime.") - } + }) } diff --git a/test/unit/socket-events-test.ts b/test/unit/socket-events-test.ts index 91b24190..bf294a60 100644 --- a/test/unit/socket-events-test.ts +++ b/test/unit/socket-events-test.ts @@ -42,7 +42,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { if (proto !== "inproc") { it("should receive bind events", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const [event] = await Promise.all([ captureEvent(sockA, "bind"), @@ -55,7 +55,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { it("should receive connect events", async function () { this.slow(250) - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const [event] = await Promise.all([ captureEvent(sockB, "connect"), @@ -69,7 +69,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { if (proto === "tcp") { it("should receive error events", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) await sockA.bind(address) const [event] = await Promise.all([ @@ -88,7 +88,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { } it("should receive events with emitter", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const events: zmq.Event[] = [] sockA.events.on("bind", event => { diff --git a/test/unit/socket-options-test.ts b/test/unit/socket-options-test.ts index 8b164adc..4e4e2d90 100644 --- a/test/unit/socket-options-test.ts +++ b/test/unit/socket-options-test.ts @@ -194,7 +194,7 @@ describe("socket options", function () { process.on("warning", warning => warnings.push(warning)) const sock = new zmq.Dealer() - sock.connect(uniqAddress("inproc")) + sock.connect(await uniqAddress("inproc")) sock.routingId = "asdf" await new Promise(process.nextTick) @@ -212,7 +212,7 @@ describe("socket options", function () { process.on("warning", warning => warnings.push(warning)) const sock = new zmq.Dealer() - const promise = sock.bind(uniqAddress("inproc")) + const promise = sock.bind(await uniqAddress("inproc")) sock.routingId = "asdf" await new Promise(process.nextTick) @@ -231,7 +231,7 @@ describe("socket options", function () { process.on("warning", warning => warnings.push(warning)) const sock = new zmq.Dealer() - await sock.bind(uniqAddress("inproc")) + await sock.bind(await uniqAddress("inproc")) sock.routingId = "asdf" await new Promise(process.nextTick) diff --git a/test/unit/socket-pair-test.ts b/test/unit/socket-pair-test.ts index df66fdcd..866312e6 100644 --- a/test/unit/socket-pair-test.ts +++ b/test/unit/socket-pair-test.ts @@ -31,7 +31,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { <- qux <- */ - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const messages = ["foo", "bar", "baz", "qux"] const received: string[] = [] diff --git a/test/unit/socket-pub-sub-test.ts b/test/unit/socket-pub-sub-test.ts index 4ac853cb..c4c3df8c 100644 --- a/test/unit/socket-pub-sub-test.ts +++ b/test/unit/socket-pub-sub-test.ts @@ -27,7 +27,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { -> qux -> */ - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const messages = ["foo", "bar", "baz", "qux"] const received: string[] = [] @@ -70,7 +70,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { -> qux -X */ - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const messages = ["foo", "bar", "baz", "qux"] const received: string[] = [] diff --git a/test/unit/socket-push-pull-test.ts b/test/unit/socket-push-pull-test.ts index c0e5c95a..30068900 100644 --- a/test/unit/socket-push-pull-test.ts +++ b/test/unit/socket-push-pull-test.ts @@ -27,7 +27,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { -> qux -> */ - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const messages = ["foo", "bar", "baz", "qux"] const received: string[] = [] @@ -51,7 +51,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { if (proto !== "inproc") { it("should deliver messages with immediate", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const messages = ["foo", "bar", "baz", "qux"] const received: string[] = [] @@ -61,7 +61,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { await push.connect(address) /* Never connected, without immediate: true it would cause lost msgs. */ - await push.connect(uniqAddress(proto)) + await push.connect(await uniqAddress(proto)) for (const msg of messages) { await push.send(msg) diff --git a/test/unit/socket-req-rep-test.ts b/test/unit/socket-req-rep-test.ts index b3690067..719393a8 100644 --- a/test/unit/socket-req-rep-test.ts +++ b/test/unit/socket-req-rep-test.ts @@ -32,7 +32,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { <- qux <- */ - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const messages = ["foo", "bar", "baz", "qux"] const received: string[] = [] @@ -69,7 +69,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { <- foo <- */ - const address = uniqAddress(proto) + const address = await uniqAddress(proto) /* FIXME: Also trigger EFSM without setting timeout. */ req.sendTimeout = 2 diff --git a/test/unit/socket-router-dealer-test.ts b/test/unit/socket-router-dealer-test.ts index d564ac56..35d52216 100644 --- a/test/unit/socket-router-dealer-test.ts +++ b/test/unit/socket-router-dealer-test.ts @@ -26,7 +26,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { describe("send/receive", function () { it("should deliver messages", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const messages = ["foo", "bar", "baz", "qux"] const receivedA: string[] = [] const receivedB: string[] = [] diff --git a/test/unit/socket-send-receive-test.ts b/test/unit/socket-send-receive-test.ts index 419611aa..45e4bbb2 100644 --- a/test/unit/socket-send-receive-test.ts +++ b/test/unit/socket-send-receive-test.ts @@ -63,7 +63,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { describe("when not connected", function () { beforeEach(async function () { sockA.sendHighWaterMark = 1 - await sockA.connect(uniqAddress(proto)) + await sockA.connect(await uniqAddress(proto)) }) it("should be writable", async function () { @@ -97,7 +97,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { const weak = require("weak-napi") let released = false - sockA.connect(uniqAddress(proto)) + sockA.connect(await uniqAddress(proto)) const send = async (size: number) => { const msg = Buffer.alloc(size) weak(msg, () => { @@ -121,7 +121,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { const weak = require("weak-napi") let released = false - sockA.connect(uniqAddress(proto)) + sockA.connect(await uniqAddress(proto)) const send = async (size: number) => { const msg = Buffer.alloc(size) weak(msg, () => { @@ -141,7 +141,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { describe("when connected", function () { beforeEach(async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) await sockB.bind(address) await sockA.connect(address) }) @@ -248,7 +248,6 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { it("should deliver messages coercible to string", async function () { const messages = [ null, - /* eslint-disable-next-line @typescript-eslint/no-empty-function */ function () {}, 16.19, true, @@ -256,7 +255,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { Promise.resolve(), ] for (const msg of messages) { - await sockA.send(msg as any) + await sockA.send(msg as zmq.MessageLike) } const received: string[] = [] @@ -440,10 +439,10 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { const echo = async (sock: zmq.Pair) => { const msg = await sock.receive() - sock.send(msg) + await sock.send(msg) } - echo(sockB) + await echo(sockB) const [final] = await sockA.receive() final.writeUInt8(0x40, 0) @@ -497,7 +496,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { if (proto !== "inproc") { describe("when connected after send/receive", function () { it("should deliver message", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const sent = "foo" const promise = Promise.all([sockB.receive(), sockA.send(sent)]) diff --git a/test/unit/socket-stream-test.ts b/test/unit/socket-stream-test.ts index 0d56bdf5..ec927e01 100644 --- a/test/unit/socket-stream-test.ts +++ b/test/unit/socket-stream-test.ts @@ -19,7 +19,7 @@ for (const proto of testProtos("tcp")) { describe("send/receive as server", function () { it("should deliver messages", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) await stream.bind(address) @@ -61,7 +61,7 @@ for (const proto of testProtos("tcp")) { describe("send/receive as client", function () { it("should deliver messages", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const port = parseInt(address.split(":").pop()!, 10) const server = await new Promise(resolve => { diff --git a/test/unit/socket-thread-test.ts b/test/unit/socket-thread-test.ts index f0038c91..290847b3 100644 --- a/test/unit/socket-thread-test.ts +++ b/test/unit/socket-thread-test.ts @@ -18,7 +18,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { describe("when connected within thread", function () { it("should deliver messages", async function () { - const data = {address: uniqAddress(proto)} + const data = {address: await uniqAddress(proto)} const recv = await createWorker(data, async ({address}) => { const sockA = new zmq.Pair({linger: 0}) const sockB = new zmq.Pair({linger: 0}) @@ -38,7 +38,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { describe("when connected to thread", function () { it("should deliver messages", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const sockA = new zmq.Pair({linger: 0}) await sockA.bind(address) @@ -64,7 +64,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { describe("when connected between threads", function () { it("should deliver messages", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const worker1 = createWorker({address}, async ({address}) => { const sockA = new zmq.Pair({linger: 0}) diff --git a/test/unit/socket-xpub-xsub-test.ts b/test/unit/socket-xpub-xsub-test.ts index effbe4fe..0085cddb 100644 --- a/test/unit/socket-xpub-xsub-test.ts +++ b/test/unit/socket-xpub-xsub-test.ts @@ -34,8 +34,8 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { -> qux -> */ - const address1 = uniqAddress(proto) - const address2 = uniqAddress(proto) + const address1 = await uniqAddress(proto) + const address2 = await uniqAddress(proto) const messages = ["foo", "bar", "baz", "qux"] const received: string[] = [] @@ -103,8 +103,8 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { -> qux -X */ - const address1 = uniqAddress(proto) - const address2 = uniqAddress(proto) + const address1 = await uniqAddress(proto) + const address2 = await uniqAddress(proto) const messages = ["foo", "bar", "baz", "qux"] const received: string[] = [] @@ -167,7 +167,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { describe("verbosity", function () { it("should deduplicate subscriptions/unsubscriptions", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const subs: Buffer[] = [] @@ -206,7 +206,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { }) it("should forward all subscriptions", async function () { - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const subs: Buffer[] = [] @@ -253,7 +253,7 @@ for (const proto of testProtos("tcp", "ipc", "inproc")) { this.skip() } - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const subs: Buffer[] = [] diff --git a/test/unit/socket-zap-test.ts b/test/unit/socket-zap-test.ts index d0aeb58d..113c23f4 100644 --- a/test/unit/socket-zap-test.ts +++ b/test/unit/socket-zap-test.ts @@ -8,7 +8,7 @@ for (const proto of testProtos("tcp", "ipc")) { describe(`socket with ${proto} zap`, function () { let sockA: zmq.Pair let sockB: zmq.Pair - let handler: ZapHandler + let handler: ZapHandler | undefined beforeEach(function () { sockA = new zmq.Pair() @@ -41,7 +41,7 @@ for (const proto of testProtos("tcp", "ipc")) { assert.equal(sockA.securityMechanism, "plain") assert.equal(sockB.securityMechanism, "plain") - const address = uniqAddress(proto) + const address = await uniqAddress(proto) await sockA.bind(address) await sockB.connect(address) @@ -72,7 +72,7 @@ for (const proto of testProtos("tcp", "ipc")) { sockB.plainUsername = "user" sockB.plainPassword = "BAD PASS" - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const [eventA, eventB] = await Promise.all([ captureEvent(sockA, "handshake:error:auth"), @@ -114,7 +114,7 @@ for (const proto of testProtos("tcp", "ipc")) { sockB.plainUsername = "user" - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const [eventA] = await Promise.all([ captureEvent(sockA, "handshake:error:protocol"), sockA.bind(address), @@ -143,7 +143,7 @@ for (const proto of testProtos("tcp", "ipc")) { sockB.plainUsername = "user" - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const [eventA] = await Promise.all([ captureEvent(sockA, "handshake:error:protocol"), sockA.bind(address), @@ -172,7 +172,7 @@ for (const proto of testProtos("tcp", "ipc")) { sockA.plainServer = true sockB.curveServer = true - const address = uniqAddress(proto) + const address = await uniqAddress(proto) const [eventA, eventB] = await Promise.all([ captureEvent(sockA, "handshake:error:protocol"), captureEvent(sockB, "handshake:error:protocol"), @@ -230,7 +230,9 @@ class ValidatingZapHandler extends ZapHandler { constructor(details: ZapDetails) { super() this.details = details - this.run() + this.run().catch(err => { + throw err + }) } handle(request: Buffer[]) { @@ -274,6 +276,8 @@ class CustomZapHandler extends ZapHandler { constructor(handler: ZapHandler["handle"]) { super() this.handle = handler - this.run() + this.run().catch(err => { + throw err + }) } } diff --git a/test/unit/typings-compatibility-test.ts b/test/unit/typings-compatibility-test.ts index 4b9675f2..86af58d1 100644 --- a/test/unit/typings-compatibility-test.ts +++ b/test/unit/typings-compatibility-test.ts @@ -20,16 +20,30 @@ import {assert} from "chai" * EXCLUDE_TYPINGS_COMPAT_TESTS=true */ -type TestDef = {version: string; minTarget: string; requiredLibs?: string[]} +type TestDef = { + tscVersion: string + nodeTypesVersion: string + minTarget: string + requiredLibs?: string[] +} const tsVersions: TestDef[] = [ + // the oldest supported version + {tscVersion: "3.7.x", nodeTypesVersion: "10.x", minTarget: "es3"}, + + // 4.0 + {tscVersion: "4.0", nodeTypesVersion: "14.x", minTarget: "es5"}, + // 4.x - {version: "4.x", minTarget: "es3"}, + {tscVersion: "4.x", nodeTypesVersion: "18.x", minTarget: "es6"}, + + // 5.x + {tscVersion: "5.x", nodeTypesVersion: "22.x", minTarget: "ES2022"}, ] // use ./typings-test.ts for tsc test, but change the import location for zmq // to be used from `test/typings-compatibility/ts-x.x.x/typings-test.ts`: -const zmqImportLoc = "../../../" +const zmqImportLoc = "../../../lib" const srcFile = path.resolve(__dirname, "typings-test.ts") const srcStr = readFile(srcFile, "utf8").then(content => { // replace import statement `import * as zmq from ...`: @@ -41,9 +55,9 @@ const srcStr = readFile(srcFile, "utf8").then(content => { const tscTestBasePath = path.resolve(__dirname, "..", "typings-compatibility") const templateSrcPath = path.resolve(tscTestBasePath, "template") -function addLibs(libs: string[], targetList: string[]): string[] { - if (!targetList) { - targetList = libs +function addLibs(libs: string[], targetList: string[] | undefined): string[] { + if (targetList === undefined) { + return libs } else { libs.forEach(l => { if (!targetList.find(e => e.toLowerCase() === l.toLowerCase())) { @@ -71,9 +85,9 @@ async function run( } function getItLabelDetails(tsVer: TestDef): string { - const lbl = `v${tsVer.version} for (minimal) compile target ${JSON.stringify( - tsVer.minTarget, - )}` + const lbl = `v${ + tsVer.tscVersion + } for (minimal) compile target ${JSON.stringify(tsVer.minTarget)}` if (!tsVer.requiredLibs || tsVer.requiredLibs.length === 0) { return lbl } @@ -98,12 +112,15 @@ describe("compatibility of typings for typescript versions", async function () { // the typescript package to complete this.timeout(30000) - const tscTargetPath = path.resolve(tscTestBasePath, `ts-${tsVer.version}`) + const tscTargetPath = path.resolve( + tscTestBasePath, + `ts-${tsVer.tscVersion}`, + ) it(`it should compile successfully with typescript version ${ - tsVer.version + tsVer.tscVersion // eslint-disable-next-line no-loop-func - }, tsc ${getItLabelDetails(tsVer)}`, async function () { + }, tsc ${getItLabelDetails(tsVer)}`, async () => { await prepareTestPackage(tscTargetPath, tsVer, execCmd) const cmd = ["npm", "pnpm"].includes(execCmd) ? `${execCmd} run` : execCmd @@ -136,7 +153,7 @@ async function prepareTestPackage( if (tsVer.requiredLibs) { tsConfig.compilerOptions.lib = addLibs( tsVer.requiredLibs, - tsConfig.compilerOptions.lib as string[], + tsConfig.compilerOptions.lib as string[] | undefined, ) } return writeJson(path.resolve(tscTargetPath, "tsconfig.json"), tsConfig) @@ -146,8 +163,9 @@ async function prepareTestPackage( path.resolve(templateSrcPath, "package.json"), ) - pkgJson.name = `test-typings-ts-${tsVer.version}` - pkgJson.devDependencies.typescript = `${tsVer.version}` + pkgJson.name = `test-typings-ts-${tsVer.tscVersion}` + pkgJson.devDependencies.typescript = `${tsVer.tscVersion}` + pkgJson.devDependencies["@types/node"] = tsVer.nodeTypesVersion return writeJson(path.resolve(tscTargetPath, "package.json"), pkgJson) })(), (async () => { diff --git a/test/unit/typings-test.ts b/test/unit/typings-test.ts index 906ae599..721929a6 100644 --- a/test/unit/typings-test.ts +++ b/test/unit/typings-test.ts @@ -5,8 +5,7 @@ describe("typings", function () { /* To test the TypeScript typings this file should compile successfully. We don't actually execute the code in this function. */ - /* @ts-ignore unused function */ - function test() { + function _test() { const version: string = zmq.version console.log(version) @@ -143,7 +142,9 @@ describe("typings", function () { proxy.backEnd.close() } - exec() + exec().catch(err => { + throw err + }) } }) }) diff --git a/test/unit/zmq-draft-test.ts b/test/unit/zmq-draft-test.ts index c1568ac2..0bee4ad5 100644 --- a/test/unit/zmq-draft-test.ts +++ b/test/unit/zmq-draft-test.ts @@ -3,27 +3,28 @@ import * as draft from "../../src/draft" import {assert} from "chai" -if (zmq.capability.draft) { - describe("zmq draft", function () { - describe("exports", function () { - it("should include functions and constructors", function () { - const expected = [ - /* Specific socket constructors. */ - "Server", - "Client", - "Radio", - "Dish", - "Gather", - "Scatter", - "Datagram", - ] +describe("zmq draft", function () { + if (zmq.capability.draft !== true) { + if (process.env.ZMQ_DRAFT === "true") { + throw new Error("Draft API requested but not available at runtime.") + } + return + } + + describe("exports", function () { + it("should include functions and constructors", function () { + const expected = [ + /* Specific socket constructors. */ + "Server", + "Client", + "Radio", + "Dish", + "Gather", + "Scatter", + "Datagram", + ] - assert.sameMembers(Object.keys(draft), expected) - }) + assert.sameMembers(Object.keys(draft), expected) }) }) -} else { - if (process.env.ZMQ_DRAFT === "true") { - throw new Error("Draft API requested but not available at runtime.") - } -} +}) diff --git a/test/unit/zmq-test.ts b/test/unit/zmq-test.ts index 23ee0687..96cad2c1 100644 --- a/test/unit/zmq-test.ts +++ b/test/unit/zmq-test.ts @@ -47,7 +47,7 @@ describe("zmq", function () { describe("version", function () { it("should return version string", function () { - if (process.env.ZMQ_VERSION) { + if (typeof process.env.ZMQ_VERSION === "string") { assert.equal(zmq.version, process.env.ZMQ_VERSION) } else { assert.match(zmq.version, /^\d+\.\d+\.\d+$/) @@ -66,7 +66,7 @@ describe("zmq", function () { describe("curve keypair", function () { beforeEach(function () { - if (!zmq.capability.curve) { + if (zmq.capability.curve !== true) { this.skip() } })