Skip to content

Commit ba80ae0

Browse files
committed
Add must-call harness helper
1 parent 02b384b commit ba80ae0

3 files changed

Lines changed: 121 additions & 0 deletions

File tree

implementors/node/must-call.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Wraps a function and returns a [wrapper, called] tuple.
3+
* - `wrapper` — call this in place of the original function
4+
* - `called` — a Promise that resolves (with the return value of fn) once
5+
* wrapper has been invoked
6+
*
7+
* If `fn` is omitted, a no-op function is used.
8+
*
9+
* Usage:
10+
* const [onResolve, resolved] = mustCall((result) => {
11+
* assert.strictEqual(result, 42);
12+
* });
13+
* promise.then(onResolve);
14+
* await resolved;
15+
*/
16+
const mustCall = (fn) => {
17+
let resolve;
18+
const called = new Promise((r) => { resolve = r; });
19+
const wrapper = (...args) => {
20+
const result = fn ? fn(...args) : undefined;
21+
resolve(result);
22+
return result;
23+
};
24+
return [wrapper, called];
25+
};
26+
27+
/**
28+
* Returns a function that throws immediately if called.
29+
*/
30+
const mustNotCall = (msg) => {
31+
return () => {
32+
throw new Error(msg || "mustNotCall function was called");
33+
};
34+
};
35+
36+
Object.assign(globalThis, { mustCall, mustNotCall });

implementors/node/tests.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ const GC_MODULE_PATH = path.join(
2828
"node",
2929
"gc.js"
3030
);
31+
const MUST_CALL_MODULE_PATH = path.join(
32+
ROOT_PATH,
33+
"implementors",
34+
"node",
35+
"must-call.js"
36+
);
3137

3238
export function listDirectoryEntries(dir: string) {
3339
const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -64,6 +70,8 @@ export function runFileInSubprocess(
6470
"file://" + LOAD_ADDON_MODULE_PATH,
6571
"--import",
6672
"file://" + GC_MODULE_PATH,
73+
"--import",
74+
"file://" + MUST_CALL_MODULE_PATH,
6775
filePath,
6876
],
6977
{ cwd }

tests/harness/must-call.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// mustCall is a function
2+
if (typeof mustCall !== 'function') {
3+
throw new Error('Expected a global mustCall function');
4+
}
5+
6+
// mustCall returns a [wrapper, called] tuple
7+
{
8+
const [wrapper, called] = mustCall();
9+
if (typeof wrapper !== 'function') {
10+
throw new Error('mustCall()[0] must be a function');
11+
}
12+
if (!(called instanceof Promise)) {
13+
throw new Error('mustCall()[1] must be a Promise');
14+
}
15+
wrapper();
16+
await called;
17+
}
18+
19+
// mustCall forwards arguments and return value
20+
{
21+
const [wrapper, called] = mustCall((a, b) => a + b);
22+
const result = wrapper(2, 3);
23+
assert.strictEqual(result, 5);
24+
const resolvedValue = await called;
25+
assert.strictEqual(resolvedValue, 5);
26+
}
27+
28+
// mustCall without fn argument works as a no-op wrapper
29+
{
30+
const [wrapper, called] = mustCall();
31+
const result = wrapper('ignored');
32+
assert.strictEqual(result, undefined);
33+
await called;
34+
}
35+
36+
// mustNotCall is a function
37+
if (typeof mustNotCall !== 'function') {
38+
throw new Error('Expected a global mustNotCall function');
39+
}
40+
41+
// mustNotCall returns a function
42+
{
43+
const fn = mustNotCall();
44+
if (typeof fn !== 'function') {
45+
throw new Error('mustNotCall() must return a function');
46+
}
47+
}
48+
49+
// mustNotCall() throws when called
50+
{
51+
const fn = mustNotCall();
52+
let threw = false;
53+
try {
54+
fn();
55+
} catch {
56+
threw = true;
57+
}
58+
if (!threw) throw new Error('mustNotCall() must throw when called');
59+
}
60+
61+
// mustNotCall(msg) includes the message
62+
{
63+
const fn = mustNotCall('custom message');
64+
let threw = false;
65+
try {
66+
fn();
67+
} catch (error) {
68+
threw = true;
69+
if (!(error instanceof Error)) {
70+
throw new Error('mustNotCall must throw an Error instance');
71+
}
72+
if (!error.message.includes('custom message')) {
73+
throw new Error(`mustNotCall error must include custom message, got: "${error.message}"`);
74+
}
75+
}
76+
if (!threw) throw new Error('mustNotCall(msg) must throw when called');
77+
}

0 commit comments

Comments
 (0)