Skip to content

Commit 876f7b5

Browse files
committed
use pr nodejs#54
Signed-off-by: Balakrishna Avulapati <ba@bavulapati.com>
1 parent d03e91c commit 876f7b5

4 files changed

Lines changed: 73 additions & 139 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export interface SpawnTestOptions {
2+
cwd?: string;
3+
nodeFlags?: string[];
4+
}
5+
6+
export interface SpawnTestResult {
7+
status: number | null;
8+
signal: NodeJS.Signals | null;
9+
stdout: string;
10+
stderr: string;
11+
}
12+
13+
export function spawnTest(
14+
filePath: string,
15+
options?: SpawnTestOptions
16+
): SpawnTestResult;

implementors/node/child_process.js

Lines changed: 31 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,40 @@
11
import { spawnSync } from "node:child_process";
22
import path from "node:path";
33

4-
const ROOT_PATH = path.resolve(import.meta.dirname, "..", "..");
5-
const FEATURES_MODULE_PATH = path.join(
6-
ROOT_PATH,
7-
"implementors",
8-
"node",
4+
const HARNESS_MODULE_PATHS = [
95
"features.js",
10-
);
11-
const ASSERT_MODULE_PATH = path.join(
12-
ROOT_PATH,
13-
"implementors",
14-
"node",
156
"assert.js",
16-
);
17-
const LOAD_ADDON_MODULE_PATH = path.join(
18-
ROOT_PATH,
19-
"implementors",
20-
"node",
217
"load-addon.js",
22-
);
23-
const GC_MODULE_PATH = path.join(ROOT_PATH, "implementors", "node", "gc.js");
24-
const MUST_CALL_MODULE_PATH = path.join(
25-
ROOT_PATH,
26-
"implementors",
27-
"node",
8+
"gc.js",
289
"must-call.js",
29-
);
30-
const CHILD_PROCESS_MODULE_PATH = path.join(
31-
ROOT_PATH,
32-
"implementors",
33-
"node",
3410
"child_process.js",
35-
);
11+
].map((file) => path.join(import.meta.dirname, file));
3612

37-
const spawnTest = (filePath, options = {}) => {
38-
const result = spawnSync(
39-
process.execPath,
40-
[
41-
"--expose-gc",
42-
"--import",
43-
"file://" + FEATURES_MODULE_PATH,
44-
"--import",
45-
"file://" + ASSERT_MODULE_PATH,
46-
"--import",
47-
"file://" + LOAD_ADDON_MODULE_PATH,
48-
"--import",
49-
"file://" + GC_MODULE_PATH,
50-
"--import",
51-
"file://" + MUST_CALL_MODULE_PATH,
52-
"--import",
53-
"file://" + CHILD_PROCESS_MODULE_PATH,
54-
filePath,
55-
],
56-
{ cwd: options.cwd || process.cwd() },
57-
);
13+
/**
14+
* Runs a test file in a fresh Node.js subprocess with the CTS harness globals
15+
* pre-loaded, and returns its exit status, signal, and captured output.
16+
*
17+
* @param {string} filePath - Path to the JS/MJS file to execute. Resolved
18+
* against `options.cwd` if relative.
19+
* @param {{ cwd?: string, nodeFlags?: string[] }} [options]
20+
* - `cwd`: working directory for the child; defaults to `process.cwd()`.
21+
* - `nodeFlags`: CLI flags passed to `node` before the `--import` chain
22+
* (e.g., `["--expose-gc"]`). Defaults to no flags so each caller declares
23+
* what its child needs.
24+
* @returns {{ status: number | null, signal: NodeJS.Signals | null, stdout: string, stderr: string }}
25+
*/
26+
export const spawnTest = (filePath, options = {}) => {
27+
const args = [...(options.nodeFlags ?? [])];
28+
for (const modulePath of HARNESS_MODULE_PATHS) {
29+
args.push("--import", "file://" + modulePath);
30+
}
31+
args.push(filePath);
32+
33+
const result = spawnSync(process.execPath, args, {
34+
cwd: options.cwd ?? process.cwd(),
35+
maxBuffer: 100 * 1024 * 1024,
36+
});
37+
if (result.error) throw result.error;
5838
return {
5939
status: result.status,
6040
signal: result.signal,
@@ -63,4 +43,7 @@ const spawnTest = (filePath, options = {}) => {
6343
};
6444
};
6545

46+
// This module is loaded in both contexts: imported by the parent test runner
47+
// (tests.ts) and `--import`ed into every spawned child. The side effect below
48+
// installs `spawnTest` on the child's globalThis so tests can call it directly.
6649
Object.assign(globalThis, { spawnTest });

implementors/node/tests.ts

Lines changed: 23 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,16 @@
11
import assert from "node:assert";
2-
import { spawn } from "node:child_process";
32
import fs from "node:fs";
43
import path from "node:path";
54

5+
import { spawnTest } from "./child_process.js";
6+
67
assert(
78
typeof import.meta.dirname === "string",
89
"Expecting a recent Node.js runtime API version",
910
);
1011

1112
const ROOT_PATH = path.resolve(import.meta.dirname, "..", "..");
1213
const TESTS_ROOT_PATH = path.join(ROOT_PATH, "tests");
13-
const FEATURES_MODULE_PATH = path.join(
14-
ROOT_PATH,
15-
"implementors",
16-
"node",
17-
"features.js",
18-
);
19-
const ASSERT_MODULE_PATH = path.join(
20-
ROOT_PATH,
21-
"implementors",
22-
"node",
23-
"assert.js",
24-
);
25-
const LOAD_ADDON_MODULE_PATH = path.join(
26-
ROOT_PATH,
27-
"implementors",
28-
"node",
29-
"load-addon.js",
30-
);
31-
const GC_MODULE_PATH = path.join(ROOT_PATH, "implementors", "node", "gc.js");
32-
const MUST_CALL_MODULE_PATH = path.join(
33-
ROOT_PATH,
34-
"implementors",
35-
"node",
36-
"must-call.js",
37-
);
38-
const CHILD_PROCESS_MODULE_PATH = path.join(
39-
ROOT_PATH,
40-
"implementors",
41-
"node",
42-
"child_process.js",
43-
);
4414

4515
export function listDirectoryEntries(dir: string) {
4616
const entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -61,63 +31,26 @@ export function listDirectoryEntries(dir: string) {
6131
return { directories, files };
6232
}
6333

64-
export function runFileInSubprocess(
65-
cwd: string,
66-
filePath: string,
67-
): Promise<void> {
68-
return new Promise((resolve, reject) => {
69-
const child = spawn(
70-
process.execPath,
71-
[
72-
// Using file scheme prefix when to enable imports on Windows
73-
"--expose-gc",
74-
"--import",
75-
"file://" + FEATURES_MODULE_PATH,
76-
"--import",
77-
"file://" + ASSERT_MODULE_PATH,
78-
"--import",
79-
"file://" + LOAD_ADDON_MODULE_PATH,
80-
"--import",
81-
"file://" + GC_MODULE_PATH,
82-
"--import",
83-
"file://" + MUST_CALL_MODULE_PATH,
84-
"--import",
85-
"file://" + CHILD_PROCESS_MODULE_PATH,
86-
filePath,
87-
],
88-
{ cwd },
89-
);
90-
91-
let stderrOutput = "";
92-
child.stderr.setEncoding("utf8");
93-
child.stderr.on("data", (chunk) => {
94-
stderrOutput += chunk;
95-
});
96-
97-
child.stdout.pipe(process.stdout);
98-
99-
child.on("error", reject);
100-
101-
child.on("close", (code, signal) => {
102-
if (code === 0) {
103-
resolve();
104-
return;
105-
}
106-
107-
const reason =
108-
code !== null ? `exit code ${code}` : `signal ${signal ?? "unknown"}`;
109-
const trimmedStderr = stderrOutput.trim();
110-
const stderrSuffix = trimmedStderr
111-
? `\n--- stderr ---\n${trimmedStderr}\n--- end stderr ---`
112-
: "";
113-
reject(
114-
new Error(
115-
`Test file ${path.relative(
116-
TESTS_ROOT_PATH,
117-
filePath,
118-
)} failed (${reason})${stderrSuffix}`,
119-
),
120-
);
121-
});
34+
export function runFileInSubprocess(cwd: string, filePath: string): void {
35+
const { status, signal, stdout, stderr } = spawnTest(filePath, {
36+
cwd,
37+
nodeFlags: ["--expose-gc"],
12238
});
39+
40+
if (stdout) process.stdout.write(stdout);
41+
42+
if (status === 0) return;
43+
44+
const reason =
45+
status !== null ? `exit code ${status}` : `signal ${signal ?? "unknown"}`;
46+
const trimmedStderr = stderr.trim();
47+
const stderrSuffix = trimmedStderr
48+
? `\n--- stderr ---\n${trimmedStderr}\n--- end stderr ---`
49+
: "";
50+
throw new Error(
51+
`Test file ${path.relative(
52+
TESTS_ROOT_PATH,
53+
path.join(cwd, filePath),
54+
)} failed (${reason})${stderrSuffix}`,
55+
);
12356
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// This test verifies that exceptions thrown from C finalizers during GC
22
// are propagated as uncaught exceptions (printed to stderr).
33
// It spawns a child process that triggers the finalizer and checks its stderr.
4-
const result = spawnTest("testFinalizerException_child.mjs");
4+
const result = spawnTest("testFinalizerException_child.mjs", {
5+
nodeFlags: ["--expose-gc"],
6+
});
57
assert.match(result.stderr, /Error during Finalize/);

0 commit comments

Comments
 (0)