Skip to content

Commit 2b4c2df

Browse files
kraenhansenclaude
andcommitted
feat: extend harness with assert methods, gcUntil, and --expose-gc
Adds the following to the Node.js implementor and harness in preparation for porting the easy js-native-api tests: - assert.js: expose assert.ok, .strictEqual, .notStrictEqual, .deepStrictEqual, and .throws as methods on the global assert object - gc.js: new module providing a global gcUntil(name, condition) helper that drives GC until a condition is met (needed for finalizer tests) - tests.ts: inject --expose-gc and gc.js into every test subprocess - CMakeLists.txt: broaden add_node_api_cts_addon() to accept multiple source files via ARGN (needed for multi-file addons) - tests/harness/assert.js: exercise all new assert methods - tests/harness/gc.js: exercise gcUntil pass and failure paths Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent a5f27b4 commit 2b4c2df

File tree

6 files changed

+121
-6
lines changed

6 files changed

+121
-6
lines changed

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ if(MSVC)
1313
execute_process(COMMAND ${CMAKE_AR} /def:${NODE_API_DEF} /out:${NODE_API_LIB} ${CMAKE_STATIC_LINKER_FLAGS})
1414
endif()
1515

16-
function(add_node_api_cts_addon ADDON_NAME SRC)
17-
add_library(${ADDON_NAME} SHARED ${SRC})
16+
function(add_node_api_cts_addon ADDON_NAME)
17+
add_library(${ADDON_NAME} SHARED ${ARGN})
1818
set_target_properties(${ADDON_NAME} PROPERTIES
1919
PREFIX ""
2020
SUFFIX ".node"

implementors/node/assert.js

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
11

2-
import { ok } from "node:assert/strict";
2+
import {
3+
ok,
4+
strictEqual,
5+
notStrictEqual,
6+
deepStrictEqual,
7+
throws,
8+
} from "node:assert/strict";
39

4-
const assert = (value, message) => {
5-
ok(value, message);
6-
};
10+
const assert = Object.assign(
11+
(value, message) => ok(value, message),
12+
{
13+
ok: (value, message) => ok(value, message),
14+
strictEqual: (actual, expected, message) =>
15+
strictEqual(actual, expected, message),
16+
notStrictEqual: (actual, expected, message) =>
17+
notStrictEqual(actual, expected, message),
18+
deepStrictEqual: (actual, expected, message) =>
19+
deepStrictEqual(actual, expected, message),
20+
throws: (fn, error, message) => throws(fn, error, message),
21+
},
22+
);
723

824
Object.assign(globalThis, { assert });

implementors/node/gc.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const gcUntil = async (name, condition) => {
2+
let count = 0;
3+
while (!condition()) {
4+
await new Promise((resolve) => setImmediate(resolve));
5+
if (++count < 10) {
6+
globalThis.gc();
7+
} else {
8+
throw new Error(`GC test "${name}" failed after ${count} attempts`);
9+
}
10+
}
11+
};
12+
13+
Object.assign(globalThis, { gcUntil });

implementors/node/tests.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ const LOAD_ADDON_MODULE_PATH = path.join(
2222
"node",
2323
"load-addon.js"
2424
);
25+
const GC_MODULE_PATH = path.join(
26+
ROOT_PATH,
27+
"implementors",
28+
"node",
29+
"gc.js"
30+
);
2531

2632
export function listDirectoryEntries(dir: string) {
2733
const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -51,10 +57,13 @@ export function runFileInSubprocess(
5157
process.execPath,
5258
[
5359
// Using file scheme prefix when to enable imports on Windows
60+
"--expose-gc",
5461
"--import",
5562
"file://" + ASSERT_MODULE_PATH,
5663
"--import",
5764
"file://" + LOAD_ADDON_MODULE_PATH,
65+
"--import",
66+
"file://" + GC_MODULE_PATH,
5867
filePath,
5968
],
6069
{ cwd }

tests/harness/assert.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,53 @@ try {
3131
if (!threw) {
3232
throw new Error('Global assert(false, message) must throw');
3333
}
34+
35+
// assert.ok
36+
if (typeof assert.ok !== 'function') {
37+
throw new Error('Expected assert.ok to be a function');
38+
}
39+
assert.ok(true);
40+
threw = false;
41+
try { assert.ok(false); } catch { threw = true; }
42+
if (!threw) throw new Error('assert.ok(false) must throw');
43+
44+
// assert.strictEqual
45+
if (typeof assert.strictEqual !== 'function') {
46+
throw new Error('Expected assert.strictEqual to be a function');
47+
}
48+
assert.strictEqual(1, 1);
49+
assert.strictEqual('a', 'a');
50+
assert.strictEqual(NaN, NaN); // uses Object.is semantics
51+
threw = false;
52+
try { assert.strictEqual(1, 2); } catch { threw = true; }
53+
if (!threw) throw new Error('assert.strictEqual(1, 2) must throw');
54+
55+
// assert.notStrictEqual
56+
if (typeof assert.notStrictEqual !== 'function') {
57+
throw new Error('Expected assert.notStrictEqual to be a function');
58+
}
59+
assert.notStrictEqual(1, 2);
60+
assert.notStrictEqual('a', 'b');
61+
threw = false;
62+
try { assert.notStrictEqual(1, 1); } catch { threw = true; }
63+
if (!threw) throw new Error('assert.notStrictEqual(1, 1) must throw');
64+
65+
// assert.deepStrictEqual
66+
if (typeof assert.deepStrictEqual !== 'function') {
67+
throw new Error('Expected assert.deepStrictEqual to be a function');
68+
}
69+
assert.deepStrictEqual({ a: 1 }, { a: 1 });
70+
assert.deepStrictEqual([1, 2, 3], [1, 2, 3]);
71+
threw = false;
72+
try { assert.deepStrictEqual({ a: 1 }, { a: 2 }); } catch { threw = true; }
73+
if (!threw) throw new Error('assert.deepStrictEqual({ a: 1 }, { a: 2 }) must throw');
74+
75+
// assert.throws
76+
if (typeof assert.throws !== 'function') {
77+
throw new Error('Expected assert.throws to be a function');
78+
}
79+
assert.throws(() => { throw new Error('oops'); }, /oops/);
80+
assert.throws(() => { throw new TypeError('bad'); }, TypeError);
81+
threw = false;
82+
try { assert.throws(() => { /* does not throw */ }); } catch { threw = true; }
83+
if (!threw) throw new Error('assert.throws must throw when fn does not throw');

tests/harness/gc.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
if (typeof gcUntil !== 'function') {
2+
throw new Error('Expected a global gcUntil function');
3+
}
4+
5+
// gcUntil should resolve once the condition becomes true
6+
let count = 0;
7+
await gcUntil('test-passes', () => {
8+
count++;
9+
return count >= 2;
10+
});
11+
if (count < 2) {
12+
throw new Error(`Expected condition to be checked at least twice, got ${count}`);
13+
}
14+
15+
// gcUntil should throw after exhausting retries when condition never becomes true
16+
let threw = false;
17+
try {
18+
await gcUntil('test-fails', () => false);
19+
} catch (error) {
20+
threw = true;
21+
if (!error.message.includes('test-fails')) {
22+
throw new Error(`Expected error message to include 'test-fails' but got: ${error.message}`);
23+
}
24+
}
25+
if (!threw) {
26+
throw new Error('gcUntil must throw when the condition never becomes true');
27+
}

0 commit comments

Comments
 (0)