oh-my-codex 0.21.37 → 0.21.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +53 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +2 -2
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +55 -4
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/session-search-help.test.js +2 -2
- package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +122 -0
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +2 -2
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +17 -12
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/config/__tests__/models.test.js +2 -1
- package/dist/config/__tests__/models.test.js.map +1 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +25 -4
- package/dist/config/generator.js.map +1 -1
- package/dist/config/models.d.ts +1 -1
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +1 -1
- package/dist/config/models.js.map +1 -1
- package/dist/scripts/__tests__/run-test-files.test.js +192 -2
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/run-test-files.js +33 -14
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +32 -4
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/state-root.test.js +5 -5
- package/dist/team/__tests__/state-root.test.js.map +1 -1
- package/dist/vscode/__tests__/index.test.js +82 -20
- package/dist/vscode/__tests__/index.test.js.map +1 -1
- package/dist/vscode/index.d.ts +2 -0
- package/dist/vscode/index.d.ts.map +1 -1
- package/dist/vscode/index.js +3 -3
- package/dist/vscode/index.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/src/scripts/__tests__/run-test-files.test.ts +193 -2
- package/src/scripts/run-test-files.ts +31 -14
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { mkdirSync, mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
|
-
import { join } from 'node:path';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
5
|
import { describe, it } from 'node:test';
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
6
7
|
import assert from 'node:assert/strict';
|
|
8
|
+
import { computeOmxPluginCacheClaimDigest } from '../../cli/plugin-marketplace.js';
|
|
7
9
|
|
|
8
10
|
function runCompiledRunner(root: string, envOverrides: Record<string, string> = {}, timeoutMs = 15_000) {
|
|
9
11
|
return spawnSync(process.execPath, ['dist/scripts/run-test-files.js', root], {
|
|
@@ -18,6 +20,193 @@ function runCompiledRunner(root: string, envOverrides: Record<string, string> =
|
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
describe('run-test-files diagnostics', () => {
|
|
23
|
+
it('isolates personal config and plugin caches per file without changing protected inputs or tool access', async () => {
|
|
24
|
+
const wd = mkdtempSync(join(tmpdir(), 'omx-run-test-files-'));
|
|
25
|
+
try {
|
|
26
|
+
const personalHome = realpathSync(mkdtempSync(join(wd, 'personal-home-')));
|
|
27
|
+
const codexHome = join(personalHome, '.codex');
|
|
28
|
+
const pluginCache = join(codexHome, 'plugins', 'cache', 'test-marketplace', 'oh-my-codex', '1.0.0');
|
|
29
|
+
mkdirSync(join(pluginCache, '.codex-plugin'), { recursive: true });
|
|
30
|
+
const protectedFiles = new Map([
|
|
31
|
+
[join(codexHome, '.omx-config.json'), JSON.stringify({ agentReasoning: { explore: 'high' }, agentModels: { explore: 'personal-model' } })],
|
|
32
|
+
[join(pluginCache, '.codex-plugin', 'plugin.json'), JSON.stringify({ name: 'oh-my-codex', version: '1.0.0' })],
|
|
33
|
+
]);
|
|
34
|
+
for (const [path, content] of protectedFiles) writeFileSync(path, content);
|
|
35
|
+
const markerPath = join(pluginCache, '.omx-complete');
|
|
36
|
+
const marker = `${JSON.stringify({ version: '1.0.0', claimDigest: await computeOmxPluginCacheClaimDigest(pluginCache) })}\n`;
|
|
37
|
+
writeFileSync(markerPath, marker);
|
|
38
|
+
protectedFiles.set(markerPath, marker);
|
|
39
|
+
|
|
40
|
+
const modelsUrl = pathToFileURL(resolve('dist/config/models.js')).href;
|
|
41
|
+
const pathsUrl = pathToFileURL(resolve('dist/utils/paths.js')).href;
|
|
42
|
+
const pluginUrl = pathToFileURL(resolve('dist/cli/plugin-marketplace.js')).href;
|
|
43
|
+
const testsDir = join(wd, '__tests__');
|
|
44
|
+
mkdirSync(testsDir);
|
|
45
|
+
for (const name of ['a', 'b']) {
|
|
46
|
+
writeFileSync(join(testsDir, `${name}.test.js`), [
|
|
47
|
+
"import { test } from 'node:test';",
|
|
48
|
+
"import assert from 'node:assert/strict';",
|
|
49
|
+
"import { writeFileSync, mkdirSync } from 'node:fs';",
|
|
50
|
+
"import { homedir } from 'node:os';",
|
|
51
|
+
"import { join } from 'node:path';",
|
|
52
|
+
"import { spawnSync } from 'node:child_process';",
|
|
53
|
+
`import { getAgentReasoningOverride, getAgentModelOverride } from ${JSON.stringify(modelsUrl)};`,
|
|
54
|
+
`import { codexHome } from ${JSON.stringify(pathsUrl)};`,
|
|
55
|
+
`import { discoverOmxPluginCacheDirs } from ${JSON.stringify(pluginUrl)};`,
|
|
56
|
+
"test('home is isolated before imports and remains overridable by fixtures', async () => {",
|
|
57
|
+
` const personalCodexHome = ${JSON.stringify(codexHome)};`,
|
|
58
|
+
" assert.equal(getAgentReasoningOverride('explore', personalCodexHome), 'high');",
|
|
59
|
+
" assert.equal(getAgentModelOverride('explore', personalCodexHome), 'personal-model');",
|
|
60
|
+
` assert.deepEqual(await discoverOmxPluginCacheDirs(personalCodexHome), [${JSON.stringify(pluginCache)}]);`,
|
|
61
|
+
` assert.notEqual(homedir(), ${JSON.stringify(personalHome)});`,
|
|
62
|
+
" assert.equal(process.env.HOME, homedir());",
|
|
63
|
+
" assert.equal(process.env.USERPROFILE, homedir());",
|
|
64
|
+
" assert.equal(getAgentReasoningOverride('explore'), undefined);",
|
|
65
|
+
" assert.equal(getAgentModelOverride('explore'), undefined);",
|
|
66
|
+
" assert.deepEqual(await discoverOmxPluginCacheDirs(codexHome()), []);",
|
|
67
|
+
` assert.equal(process.env.PATH, ${JSON.stringify(process.env.PATH)});`,
|
|
68
|
+
` assert.equal(process.env.TMPDIR, ${JSON.stringify(process.env.TMPDIR)});`,
|
|
69
|
+
` const tool = spawnSync(process.execPath, ['-e', ${JSON.stringify("process.stdout.write('tool reachable')")}], { encoding: 'utf8' });`,
|
|
70
|
+
" assert.equal(tool.status, 0, tool.stderr);",
|
|
71
|
+
" assert.equal(tool.stdout, 'tool reachable');",
|
|
72
|
+
` writeFileSync(${JSON.stringify(join(wd, `${name}-home.txt`))}, homedir());`,
|
|
73
|
+
" mkdirSync(codexHome(), { recursive: true });",
|
|
74
|
+
" writeFileSync(join(codexHome(), '.omx-config.json'), JSON.stringify({ agentReasoning: { explore: 'low' } }));",
|
|
75
|
+
" assert.equal(getAgentReasoningOverride('explore'), 'low');",
|
|
76
|
+
` process.env.HOME = ${JSON.stringify(personalHome)};`,
|
|
77
|
+
` process.env.USERPROFILE = ${JSON.stringify(personalHome)};`,
|
|
78
|
+
" assert.equal(getAgentReasoningOverride('explore'), 'high');",
|
|
79
|
+
"});",
|
|
80
|
+
'',
|
|
81
|
+
].join('\n'));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const forceExit of ['0', '1']) {
|
|
85
|
+
const result = runCompiledRunner(testsDir, { HOME: personalHome, USERPROFILE: personalHome, OMX_NODE_TEST_FORCE_EXIT: forceExit });
|
|
86
|
+
assert.equal(result.status, 0, `${result.stderr}\n${result.stdout}`);
|
|
87
|
+
const homes = ['a', 'b'].map((name) => readFileSync(join(wd, `${name}-home.txt`), 'utf8'));
|
|
88
|
+
assert.notEqual(homes[0], homes[1]);
|
|
89
|
+
for (const home of homes) assert.equal(existsSync(home), false, 'completed test homes must be removed');
|
|
90
|
+
for (const [path, content] of protectedFiles) assert.equal(readFileSync(path, 'utf8'), content);
|
|
91
|
+
}
|
|
92
|
+
} finally {
|
|
93
|
+
rmSync(wd, { recursive: true, force: true });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('removes isolated homes after failing test files in both runner modes', () => {
|
|
98
|
+
const wd = mkdtempSync(join(tmpdir(), 'omx-run-test-files-'));
|
|
99
|
+
try {
|
|
100
|
+
const testPath = join(wd, 'failure.test.js');
|
|
101
|
+
const homeRecord = join(wd, 'home.txt');
|
|
102
|
+
writeFileSync(testPath, [
|
|
103
|
+
"import { test } from 'node:test';",
|
|
104
|
+
"import assert from 'node:assert/strict';",
|
|
105
|
+
"import { writeFileSync } from 'node:fs';",
|
|
106
|
+
"import { homedir } from 'node:os';",
|
|
107
|
+
`writeFileSync(${JSON.stringify(homeRecord)}, homedir());`,
|
|
108
|
+
"test('fails', () => assert.fail('expected fixture failure'));",
|
|
109
|
+
'',
|
|
110
|
+
].join('\n'));
|
|
111
|
+
for (const forceExit of ['0', '1']) {
|
|
112
|
+
const result = runCompiledRunner(testPath, { OMX_NODE_TEST_FORCE_EXIT: forceExit });
|
|
113
|
+
assert.notEqual(result.status, 0, `${result.stderr}\n${result.stdout}`);
|
|
114
|
+
assert.match(result.stdout, /expected fixture failure/);
|
|
115
|
+
assert.equal(existsSync(readFileSync(homeRecord, 'utf8')), false);
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
rmSync(wd, { recursive: true, force: true });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('keeps the isolated home until a force-terminated child closes', () => {
|
|
123
|
+
const wd = mkdtempSync(join(tmpdir(), 'omx-run-test-files-'));
|
|
124
|
+
try {
|
|
125
|
+
const recordPath = join(wd, 'child-close.json');
|
|
126
|
+
const preloadPath = join(wd, 'observe-close.mjs');
|
|
127
|
+
writeFileSync(preloadPath, [
|
|
128
|
+
"import childProcess from 'node:child_process';",
|
|
129
|
+
"import { existsSync, writeFileSync } from 'node:fs';",
|
|
130
|
+
"import { syncBuiltinESMExports } from 'node:module';",
|
|
131
|
+
'const originalSpawn = childProcess.spawn;',
|
|
132
|
+
'childProcess.spawn = (...args) => {',
|
|
133
|
+
' const child = originalSpawn(...args);',
|
|
134
|
+
' const home = args[2].env.HOME;',
|
|
135
|
+
' child.once(\'close\', () => {',
|
|
136
|
+
` writeFileSync(${JSON.stringify(recordPath)}, JSON.stringify({ home, exists: existsSync(home) }));`,
|
|
137
|
+
' });',
|
|
138
|
+
' return child;',
|
|
139
|
+
'};',
|
|
140
|
+
'syncBuiltinESMExports();',
|
|
141
|
+
'',
|
|
142
|
+
].join('\n'));
|
|
143
|
+
const testPath = join(wd, 'exit-block.test.js');
|
|
144
|
+
writeFileSync(testPath, [
|
|
145
|
+
"import { test } from 'node:test';",
|
|
146
|
+
"test('passes but cannot exit normally', () => {",
|
|
147
|
+
" process.on('SIGTERM', () => {});",
|
|
148
|
+
" process.on('exit', () => { while (true) {} });",
|
|
149
|
+
'});',
|
|
150
|
+
'',
|
|
151
|
+
].join('\n'));
|
|
152
|
+
const result = spawnSync(process.execPath, ['--import', pathToFileURL(preloadPath).href, 'dist/scripts/run-test-files.js', testPath], {
|
|
153
|
+
encoding: 'utf8',
|
|
154
|
+
env: {
|
|
155
|
+
...process.env,
|
|
156
|
+
OMX_NODE_TEST_FORCE_EXIT: '1',
|
|
157
|
+
OMX_NODE_TEST_FORCE_EXIT_GRACE_MS: '100',
|
|
158
|
+
OMX_NODE_TEST_RUNNER_TIMEOUT_MS: '2000',
|
|
159
|
+
},
|
|
160
|
+
timeout: 5_000,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
assert.equal(result.status, 0, `${result.stderr}\n${result.stdout}`);
|
|
164
|
+
assert.match(result.stderr, /force-exit completion grace elapsed/);
|
|
165
|
+
assert.equal(existsSync(recordPath), true, 'runner must observe child close before exiting');
|
|
166
|
+
const record = JSON.parse(readFileSync(recordPath, 'utf8')) as { home: string; exists: boolean };
|
|
167
|
+
assert.equal(record.exists, true, 'isolated home must remain available until child close');
|
|
168
|
+
assert.equal(existsSync(record.home), false, 'isolated home must be removed after child close');
|
|
169
|
+
} finally {
|
|
170
|
+
rmSync(wd, { recursive: true, force: true });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('can preserve the caller home for explicit diagnostics independently of runtime env', () => {
|
|
175
|
+
const wd = mkdtempSync(join(tmpdir(), 'omx-run-test-files-'));
|
|
176
|
+
try {
|
|
177
|
+
const home = join(wd, 'personal-home');
|
|
178
|
+
mkdirSync(home);
|
|
179
|
+
const markerPath = join(home, 'protected-input.txt');
|
|
180
|
+
writeFileSync(markerPath, 'unchanged input\n');
|
|
181
|
+
const testPath = join(wd, 'home-preserve.test.js');
|
|
182
|
+
writeFileSync(testPath, [
|
|
183
|
+
"import { test } from 'node:test';",
|
|
184
|
+
"import assert from 'node:assert/strict';",
|
|
185
|
+
"import { homedir } from 'node:os';",
|
|
186
|
+
"test('home diagnostic opt-in does not preserve runtime env', () => {",
|
|
187
|
+
` assert.equal(homedir(), ${JSON.stringify(home)});`,
|
|
188
|
+
` assert.equal(process.env.HOME, ${JSON.stringify(home)});`,
|
|
189
|
+
` assert.equal(process.env.USERPROFILE, ${JSON.stringify(home)});`,
|
|
190
|
+
" assert.equal(process.env.CODEX_HOME, undefined);",
|
|
191
|
+
" assert.equal(process.env.OMX_ROOT, undefined);",
|
|
192
|
+
"});",
|
|
193
|
+
'',
|
|
194
|
+
].join('\n'));
|
|
195
|
+
|
|
196
|
+
const result = runCompiledRunner(testPath, {
|
|
197
|
+
HOME: home,
|
|
198
|
+
USERPROFILE: home,
|
|
199
|
+
OMX_NODE_TEST_PRESERVE_HOME: '1',
|
|
200
|
+
CODEX_HOME: '/tmp/live-codex-home',
|
|
201
|
+
OMX_ROOT: '/tmp/live-omx-root',
|
|
202
|
+
});
|
|
203
|
+
assert.equal(result.status, 0, `${result.stderr}\n${result.stdout}`);
|
|
204
|
+
assert.equal(readFileSync(markerPath, 'utf8'), 'unchanged input\n');
|
|
205
|
+
} finally {
|
|
206
|
+
rmSync(wd, { recursive: true, force: true });
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
21
210
|
it('applies a bounded node --test timeout so hanging tests fail with file context', () => {
|
|
22
211
|
const wd = mkdtempSync(join(tmpdir(), 'omx-run-test-files-'));
|
|
23
212
|
try {
|
|
@@ -398,6 +587,7 @@ describe('run-test-files diagnostics', () => {
|
|
|
398
587
|
"import assert from 'node:assert/strict';",
|
|
399
588
|
"test('runner env allowlist is narrow', () => {",
|
|
400
589
|
" assert.equal(process.env.OMX_EXPLORE_BIN, '/tmp/fake-explore');",
|
|
590
|
+
" assert.equal(process.env.OMX_RUNTIME_BINARY, '/tmp/fake-runtime');",
|
|
401
591
|
" assert.equal(process.env.OMX_NODE_TEST_CONCURRENCY, '1');",
|
|
402
592
|
" assert.equal(process.env.OMX_ROOT, undefined);",
|
|
403
593
|
" assert.equal(process.env.CODEX_HOME, undefined);",
|
|
@@ -408,6 +598,7 @@ describe('run-test-files diagnostics', () => {
|
|
|
408
598
|
|
|
409
599
|
const result = runCompiledRunner(wd, {
|
|
410
600
|
OMX_EXPLORE_BIN: '/tmp/fake-explore',
|
|
601
|
+
OMX_RUNTIME_BINARY: '/tmp/fake-runtime',
|
|
411
602
|
OMX_NODE_TEST_CONCURRENCY: '1',
|
|
412
603
|
OMX_ROOT: '/tmp/live-omx-root',
|
|
413
604
|
CODEX_HOME: '/tmp/live-codex-home',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn, spawnSync, type ChildProcess } from 'node:child_process';
|
|
2
|
-
import { readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { mkdtempSync, readdirSync, rmSync, statSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
3
4
|
import { join, resolve } from 'node:path';
|
|
4
5
|
|
|
5
6
|
const DEFAULT_TEST_TIMEOUT_MS = 0;
|
|
@@ -13,6 +14,7 @@ const PRESERVED_TEST_ENV_KEYS = new Set([
|
|
|
13
14
|
'OMX_NODE_TEST_CONCURRENCY',
|
|
14
15
|
'OMX_NODE_TEST_FORCE_EXIT',
|
|
15
16
|
'OMX_NODE_TEST_FORCE_EXIT_GRACE_MS',
|
|
17
|
+
'OMX_NODE_TEST_PRESERVE_HOME',
|
|
16
18
|
'OMX_NODE_TEST_PRESERVE_RUNTIME_ENV',
|
|
17
19
|
'OMX_NODE_TEST_RUNNER_TIMEOUT_MS',
|
|
18
20
|
'OMX_NODE_TEST_TIMEOUT_MS',
|
|
@@ -123,6 +125,7 @@ const runnerTimeoutMs = parseTimeoutMs(process.env.OMX_NODE_TEST_RUNNER_TIMEOUT_
|
|
|
123
125
|
const forceExitGraceMs = parseTimeoutMs(process.env.OMX_NODE_TEST_FORCE_EXIT_GRACE_MS, DEFAULT_FORCE_EXIT_GRACE_MS);
|
|
124
126
|
const testConcurrency = parseTestConcurrency(process.env);
|
|
125
127
|
const forceExit = parseBooleanEnv(process.env.OMX_NODE_TEST_FORCE_EXIT);
|
|
128
|
+
const preserveHome = parseBooleanEnv(process.env.OMX_NODE_TEST_PRESERVE_HOME);
|
|
126
129
|
const sharedTestArgs = ['--test'];
|
|
127
130
|
if (testTimeoutMs > 0) {
|
|
128
131
|
sharedTestArgs.push(`--test-timeout=${testTimeoutMs}`);
|
|
@@ -139,7 +142,7 @@ console.error(
|
|
|
139
142
|
testTimeoutMs > 0 ? ` with per-test timeout ${testTimeoutMs}ms` : ' with per-test timeout disabled'
|
|
140
143
|
}${testConcurrency ? `, test concurrency ${testConcurrency}` : ', default test concurrency'}${
|
|
141
144
|
forceExit ? `, force exit enabled with ${forceExitGraceMs}ms completion grace` : ', force exit disabled'
|
|
142
|
-
}${runnerTimeoutMs > 0 ? `, and runner timeout ${runnerTimeoutMs}ms` : ', and runner timeout disabled'}, with per-file process isolation`,
|
|
145
|
+
}${runnerTimeoutMs > 0 ? `, and runner timeout ${runnerTimeoutMs}ms` : ', and runner timeout disabled'}, with per-file process isolation and ${preserveHome ? 'caller home preserved' : 'isolated homes'}`,
|
|
143
146
|
);
|
|
144
147
|
|
|
145
148
|
const childEnv = buildChildEnv(process.env);
|
|
@@ -179,7 +182,7 @@ function terminateChild(child: ChildProcess): void {
|
|
|
179
182
|
signalChild(child, 'SIGKILL');
|
|
180
183
|
}
|
|
181
184
|
|
|
182
|
-
function runFileWithCompletionForceExit(file: string): Promise<TestRunResult> {
|
|
185
|
+
function runFileWithCompletionForceExit(file: string, env: NodeJS.ProcessEnv): Promise<TestRunResult> {
|
|
183
186
|
return new Promise((resolveRun) => {
|
|
184
187
|
let finished = false;
|
|
185
188
|
let sawFailure = false;
|
|
@@ -196,7 +199,7 @@ function runFileWithCompletionForceExit(file: string): Promise<TestRunResult> {
|
|
|
196
199
|
|
|
197
200
|
const child = spawn(process.execPath, [...sharedTestArgs, file], {
|
|
198
201
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
199
|
-
env
|
|
202
|
+
env,
|
|
200
203
|
detached: !isWindows(),
|
|
201
204
|
});
|
|
202
205
|
|
|
@@ -206,18 +209,22 @@ function runFileWithCompletionForceExit(file: string): Promise<TestRunResult> {
|
|
|
206
209
|
if (completionTimer) clearTimeout(completionTimer);
|
|
207
210
|
if (runnerTimer) clearTimeout(runnerTimer);
|
|
208
211
|
console.error(`[run-test-files] ${file}: ${reason}; status ${status ?? 'unknown'}`);
|
|
212
|
+
// Signals start termination; close confirms this child and its stdio are
|
|
213
|
+
// finished before main removes the file's isolated home.
|
|
214
|
+
child.once('close', () => {
|
|
215
|
+
child.unref();
|
|
216
|
+
resolveRun({
|
|
217
|
+
status,
|
|
218
|
+
signal,
|
|
219
|
+
timedOut: reason.startsWith('runner timeout'),
|
|
220
|
+
abnormal: status === null || signal !== null || reason.startsWith('node --test failed to spawn'),
|
|
221
|
+
});
|
|
222
|
+
});
|
|
209
223
|
if (terminate) {
|
|
210
224
|
terminateChild(child);
|
|
211
225
|
}
|
|
212
226
|
child.stdout?.destroy();
|
|
213
227
|
child.stderr?.destroy();
|
|
214
|
-
child.unref();
|
|
215
|
-
resolveRun({
|
|
216
|
-
status,
|
|
217
|
-
signal,
|
|
218
|
-
timedOut: reason.startsWith('runner timeout'),
|
|
219
|
-
abnormal: status === null || signal !== null || reason.startsWith('node --test failed to spawn'),
|
|
220
|
-
});
|
|
221
228
|
}
|
|
222
229
|
|
|
223
230
|
function markFailure(): void {
|
|
@@ -323,10 +330,10 @@ function runFileWithCompletionForceExit(file: string): Promise<TestRunResult> {
|
|
|
323
330
|
});
|
|
324
331
|
}
|
|
325
332
|
|
|
326
|
-
function runFileSync(file: string): TestRunResult {
|
|
333
|
+
function runFileSync(file: string, env: NodeJS.ProcessEnv): TestRunResult {
|
|
327
334
|
const result = spawnSync(process.execPath, [...sharedTestArgs, file], {
|
|
328
335
|
stdio: 'inherit',
|
|
329
|
-
env
|
|
336
|
+
env,
|
|
330
337
|
timeout: runnerTimeoutMs > 0 ? runnerTimeoutMs : undefined,
|
|
331
338
|
killSignal: 'SIGTERM',
|
|
332
339
|
});
|
|
@@ -351,7 +358,17 @@ async function main(): Promise<void> {
|
|
|
351
358
|
let runnerTimedOut = false;
|
|
352
359
|
|
|
353
360
|
for (const file of files) {
|
|
354
|
-
|
|
361
|
+
// Runtime env scrubbing does not isolate personal config or plugin caches.
|
|
362
|
+
// Keep tool paths and temporary-directory settings, but start each file with
|
|
363
|
+
// an empty home unless the caller explicitly opts into home diagnostics.
|
|
364
|
+
const home = preserveHome ? undefined : mkdtempSync(join(tmpdir(), 'omx-node-test-home-'));
|
|
365
|
+
const env = home ? { ...childEnv, HOME: home, USERPROFILE: home } : childEnv;
|
|
366
|
+
let result: TestRunResult;
|
|
367
|
+
try {
|
|
368
|
+
result = forceExit ? await runFileWithCompletionForceExit(file, env) : runFileSync(file, env);
|
|
369
|
+
} finally {
|
|
370
|
+
if (home) rmSync(home, { recursive: true, force: true });
|
|
371
|
+
}
|
|
355
372
|
|
|
356
373
|
if (result.status === 0) {
|
|
357
374
|
continue;
|