Skip to content

Commit d473c10

Browse files
authored
Merge pull request #688 from zeromq/proxy-tests [skip ci]
2 parents 319032c + f52055c commit d473c10

File tree

9 files changed

+142
-115
lines changed

9 files changed

+142
-115
lines changed

.github/workflows/CI.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,9 @@ jobs:
158158
uses: nick-fields/retry@v3
159159
with:
160160
timeout_minutes: 5
161-
max_attempts: 1
161+
max_attempts: 2
162162
command: |
163163
pnpm run test.unit
164-
continue-on-error: true
165164
166165
- name: Clean Tmp
167166
run: rm -rf ./tmp
@@ -172,7 +171,7 @@ jobs:
172171
uses: nick-fields/retry@v3
173172
with:
174173
timeout_minutes: 5
175-
max_attempts: 1
174+
max_attempts: 2
176175
command: |
177176
pnpm run test.unit.compat
178177
continue-on-error: true
@@ -182,11 +181,11 @@ jobs:
182181
shell: bash
183182

184183
- name: Test Electron Windows/MacOS
185-
if: "${{ !matrix.dockerfile }}"
184+
if: "${{ !contains(matrix.os, 'ubuntu') && !matrix.dockerfile }}"
186185
uses: nick-fields/retry@v3
187186
with:
188187
timeout_minutes: 5
189-
max_attempts: 1
188+
max_attempts: 2
190189
command: |
191190
pnpm run test.electron.main
192191
continue-on-error: true
@@ -196,7 +195,7 @@ jobs:
196195
uses: nick-fields/retry@v3
197196
with:
198197
timeout_minutes: 5
199-
max_attempts: 1
198+
max_attempts: 2
200199
command: |
201200
sudo apt-get install xvfb
202201
xvfb-run --auto-servernum pnpm run test.electron.main

.mocharc.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ const config = {
66
"expose-gc": true,
77
"v8-expose-gc": true,
88
exit: true,
9-
parallel: true,
10-
timeout: 5000,
11-
retries: 1,
9+
parallel: false,
10+
timeout: 6000,
11+
retries: 3,
1212
fullTrace: true,
1313
bail: false,
1414
}

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
},
2121
"mochaExplorer.files": "test/unit/**/*-test.ts",
2222
"mochaExplorer.mochaPath": "./node_modules/mocha",
23+
"mochaExplorer.timeout": 6000,
2324
"files.exclude": {
2425
"**/.DS_Store": true,
2526
"**/Thumbs.db": true,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@
109109
"test.unit.debug": "run-s clean.temp build.debug && mocha ./test/unit/*-test.ts",
110110
"test.unit.compat": "run-s clean.temp build && cross-env INCLUDE_COMPAT_TESTS=true mocha ./test/unit/compat/*-test.ts",
111111
"test.unit.nogc": "run-s clean.temp build && cross-env SKIP_GC_TESTS=true mocha",
112-
"test.electron.main": "run-s clean.temp build && electron-mocha ./test/unit/*-test.ts",
112+
"test.electron.main": "run-s clean.temp build && cross-env SKIP_GC_TESTS=true electron-mocha ./test/unit/*-test.ts",
113113
"test.electron.renderer": "run-s build && electron-mocha --renderer ./test/unit/*-test.ts",
114114
"test.smoke": "bash ./script/smoke-test.bash",
115115
"format": "run-s format.prettier format.clang-format",

test/unit/helpers.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,18 @@ if (semver.satisfies(zmq.version, ">= 4.2")) {
1717
* Get a unique id to be used as a port number or IPC path.
1818
* This function is thread-safe and will use a lock file to ensure that the id is unique.
1919
*/
20-
let idFallback = 5000
20+
let idFallback = 6000
2121
async function getUniqueId() {
2222
const idPath = path.resolve(__dirname, "../../tmp/port-id.lock")
2323
await fs.promises.mkdir(path.dirname(idPath), {recursive: true})
2424

2525
try {
2626
// Create the file if it doesn't exist
2727
if (!fs.existsSync(idPath)) {
28-
await fs.promises.writeFile(idPath, "5000", "utf8")
28+
await fs.promises.writeFile(idPath, "6000", "utf8")
2929

3030
/* Windows cannot bind on a ports just above 1014; start higher to be safe. */
31-
return 5000
31+
return 6000
3232
}
3333

3434
await lockfile.lock(idPath, {retries: 10})
@@ -63,7 +63,7 @@ async function getUniqueId() {
6363
}
6464
}
6565

66-
type Proto = "ipc" | "tcp" | "udp" | "inproc"
66+
export type Proto = "ipc" | "tcp" | "udp" | "inproc"
6767

6868
export async function uniqAddress(proto: Proto) {
6969
const id = await getUniqueId()
@@ -84,6 +84,19 @@ export async function uniqAddress(proto: Proto) {
8484
}
8585
}
8686

87+
export async function cleanSocket(address: string) {
88+
const [proto, path] = address.split("://")[1]
89+
if (proto !== "ipc" || !path) {
90+
return
91+
}
92+
const exists = await fs.promises
93+
.access(path, fs.constants.F_OK)
94+
.catch(() => false)
95+
if (exists) {
96+
await fs.promises.rm(path)
97+
}
98+
}
99+
87100
export function testProtos(...requested: Proto[]) {
88101
const set = new Set(requested)
89102

test/unit/proxy-router-dealer-test.ts

Lines changed: 11 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,16 @@
1-
import * as semver from "semver"
2-
import * as zmq from "../../src"
3-
4-
import {assert} from "chai"
5-
import {testProtos, uniqAddress} from "./helpers"
1+
import {Worker} from "worker_threads"
2+
import {testProtos} from "./helpers"
63

74
for (const proto of testProtos("tcp", "ipc", "inproc")) {
8-
describe(`proxy with ${proto} router/dealer`, function () {
9-
/* ZMQ < 4.0.5 has no steerable proxy support. */
10-
if (semver.satisfies(zmq.version, "< 4.0.5")) {
11-
return
12-
}
13-
14-
let proxy: zmq.Proxy
15-
16-
let frontAddress: string
17-
let backAddress: string
18-
19-
let req: zmq.Request
20-
let rep: zmq.Reply
21-
22-
beforeEach(async function () {
23-
proxy = new zmq.Proxy(new zmq.Router(), new zmq.Dealer())
24-
25-
frontAddress = await uniqAddress(proto)
26-
backAddress = await uniqAddress(proto)
27-
28-
req = new zmq.Request()
29-
rep = new zmq.Reply()
30-
})
31-
32-
afterEach(function () {
33-
/* Closing proxy sockets is only necessary if run() fails. */
34-
proxy.frontEnd.close()
35-
proxy.backEnd.close()
36-
37-
req.close()
38-
rep.close()
39-
global.gc?.()
40-
})
41-
42-
describe("run", function () {
43-
it("should proxy messages", async function () {
44-
/* REQ -> foo -> ROUTER <-> DEALER -> foo -> REP
45-
<- foo <- <- foo <-
46-
-> bar -> -> bar ->
47-
<- bar <- <- bar <-
48-
pause
49-
resume
50-
-> baz -> -> baz ->
51-
<- baz <- <- baz <-
52-
-> qux -> -> qux ->
53-
<- qux <- <- qux <-
54-
*/
55-
56-
await proxy.frontEnd.bind(frontAddress)
57-
await proxy.backEnd.bind(backAddress)
58-
59-
const done = proxy.run()
60-
61-
const messages = ["foo", "bar", "baz", "qux"]
62-
const received: string[] = []
63-
64-
await req.connect(frontAddress)
65-
await rep.connect(backAddress)
66-
67-
const echo = async () => {
68-
for await (const msg of rep) {
69-
await rep.send(msg)
70-
}
71-
}
72-
73-
const send = async () => {
74-
for (const msg of messages) {
75-
if (received.length === 2) {
76-
proxy.pause()
77-
proxy.resume()
78-
}
79-
80-
await req.send(Buffer.from(msg))
81-
82-
const [res] = await req.receive()
83-
received.push(res.toString())
84-
if (received.length === messages.length) {
85-
break
86-
}
87-
}
88-
89-
rep.close()
90-
}
91-
92-
console.log(
93-
`waiting for messages for proxy with ${proto} router/dealer...`,
94-
)
95-
96-
await Promise.all([echo(), send()])
97-
assert.deepEqual(received, messages)
98-
99-
proxy.terminate()
100-
await done
101-
console.log(`Done proxying with ${proto} router/dealer`)
5+
describe(`proxy with ${proto} router/dealer`, () => {
6+
describe("run", () => {
7+
it("should proxy messages", async () => {
8+
const worker = new Worker(__filename, {
9+
workerData: {
10+
proto,
11+
},
12+
})
13+
await worker.terminate()
10214
})
10315
})
10416
})
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import {assert} from "chai"
2+
import * as semver from "semver"
3+
import * as zmq from "../../src"
4+
import type {Proto} from "./helpers"
5+
import {cleanSocket, uniqAddress} from "./helpers"
6+
import {workerData} from "worker_threads"
7+
8+
async function testProxyRouterDealer(proto: Proto) {
9+
/* ZMQ < 4.0.5 has no steerable proxy support. */
10+
if (semver.satisfies(zmq.version, "< 4.0.5")) {
11+
return
12+
}
13+
14+
const proxy = new zmq.Proxy(new zmq.Router(), new zmq.Dealer())
15+
16+
const frontAddress = await uniqAddress(proto)
17+
const backAddress = await uniqAddress(proto)
18+
19+
const req = new zmq.Request()
20+
const rep = new zmq.Reply()
21+
22+
try {
23+
/* REQ -> foo -> ROUTER <-> DEALER -> foo -> REP
24+
<- foo <- <- foo <-
25+
-> bar -> -> bar ->
26+
<- bar <- <- bar <-
27+
pause
28+
resume
29+
-> baz -> -> baz ->
30+
<- baz <- <- baz <-
31+
-> qux -> -> qux ->
32+
<- qux <- <- qux <-
33+
*/
34+
await proxy.frontEnd.bind(frontAddress)
35+
await proxy.backEnd.bind(backAddress)
36+
37+
const done = proxy.run()
38+
39+
const messages = ["foo", "bar", "baz", "qux"]
40+
const received: string[] = []
41+
42+
await req.connect(frontAddress)
43+
await rep.connect(backAddress)
44+
45+
const echo = async () => {
46+
for await (const msg of rep) {
47+
await rep.send(msg)
48+
}
49+
}
50+
51+
const send = async () => {
52+
for (const msg of messages) {
53+
if (received.length === 2) {
54+
proxy.pause()
55+
proxy.resume()
56+
}
57+
58+
await req.send(Buffer.from(msg))
59+
60+
const [res] = await req.receive()
61+
received.push(res.toString())
62+
if (received.length === messages.length) {
63+
break
64+
}
65+
}
66+
67+
rep.close()
68+
}
69+
70+
console.log(`waiting for messages for proxy with ${proto} router/dealer...`)
71+
72+
await Promise.all([echo(), send()])
73+
assert.deepEqual(received, messages)
74+
75+
proxy.terminate()
76+
await done
77+
console.log(`Done proxying with ${proto} router/dealer`)
78+
} catch (err) {
79+
/* Closing proxy sockets is only necessary if run() fails. */
80+
proxy.frontEnd.close()
81+
proxy.backEnd.close()
82+
throw err
83+
} finally {
84+
req.close()
85+
rep.close()
86+
global.gc?.()
87+
await Promise.all([cleanSocket(frontAddress), cleanSocket(backAddress)])
88+
}
89+
}
90+
91+
// Receive the proto from the main thread
92+
testProxyRouterDealer(workerData.proto as Proto).catch(err => {
93+
console.error(
94+
`Error testing proxy with ${workerData.proto} router/dealer:`,
95+
err,
96+
)
97+
process.exit(1)
98+
})

test/unit/proxy-run-test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as semver from "semver"
22
import * as zmq from "../../src"
33

44
import {assert} from "chai"
5-
import {testProtos, uniqAddress} from "./helpers"
5+
import {cleanSocket, testProtos, uniqAddress} from "./helpers"
66
import {isFullError} from "../../src/errors"
77

88
for (const proto of testProtos("tcp", "ipc", "inproc")) {

test/unit/typings-compatibility-test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,11 @@ describe("compatibility of typings for typescript versions", async function () {
129129
})
130130

131131
afterEach(async () => {
132-
await remove(tscTargetPath)
132+
try {
133+
await remove(tscTargetPath)
134+
} catch (err) {
135+
console.error(`Failed to remove ${tscTargetPath}:`, err)
136+
}
133137
})
134138
}
135139
})

0 commit comments

Comments
 (0)