oh-my-codex 0.18.9 → 0.18.11
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/README.md +6 -6
- package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts +2 -0
- package/dist/autopilot/__tests__/deep-interview-gate.test.d.ts.map +1 -0
- package/dist/autopilot/__tests__/deep-interview-gate.test.js +215 -0
- package/dist/autopilot/__tests__/deep-interview-gate.test.js.map +1 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js +148 -0
- package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
- package/dist/autopilot/deep-interview-gate.d.ts.map +1 -1
- package/dist/autopilot/deep-interview-gate.js +140 -0
- package/dist/autopilot/deep-interview-gate.js.map +1 -1
- package/dist/catalog/__tests__/schema.test.js +6 -0
- package/dist/catalog/__tests__/schema.test.js.map +1 -1
- package/dist/cli/__tests__/auth.test.js +36 -3
- package/dist/cli/__tests__/auth.test.js.map +1 -1
- package/dist/cli/__tests__/codex-feature-probe.test.d.ts +2 -0
- package/dist/cli/__tests__/codex-feature-probe.test.d.ts.map +1 -0
- package/dist/cli/__tests__/codex-feature-probe.test.js +46 -0
- package/dist/cli/__tests__/codex-feature-probe.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-spark-routing.test.d.ts +2 -0
- package/dist/cli/__tests__/doctor-spark-routing.test.d.ts.map +1 -0
- package/dist/cli/__tests__/doctor-spark-routing.test.js +79 -0
- package/dist/cli/__tests__/doctor-spark-routing.test.js.map +1 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js +2 -0
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/explore.test.js +47 -1175
- package/dist/cli/__tests__/explore.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +276 -6
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/launch-fallback.test.js +19 -5
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/nested-help-routing.test.js +1 -1
- package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +19 -6
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +6 -2
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-packaging.test.js +45 -2
- package/dist/cli/__tests__/sparkshell-packaging.test.js.map +1 -1
- package/dist/cli/__tests__/team-decompose.test.js +10 -5
- package/dist/cli/__tests__/team-decompose.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +45 -1
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +75 -0
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +25 -1
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/codex-feature-probe.d.ts +5 -2
- package/dist/cli/codex-feature-probe.d.ts.map +1 -1
- package/dist/cli/codex-feature-probe.js +25 -9
- package/dist/cli/codex-feature-probe.js.map +1 -1
- package/dist/cli/doctor.d.ts +17 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +121 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/explore.d.ts +2 -21
- package/dist/cli/explore.d.ts.map +1 -1
- package/dist/cli/explore.js +25 -624
- package/dist/cli/explore.js.map +1 -1
- package/dist/cli/index.d.ts +34 -2
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +166 -89
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +9 -1
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/team.d.ts +4 -0
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +43 -4
- package/dist/cli/team.js.map +1 -1
- package/dist/cli/ultragoal.d.ts.map +1 -1
- package/dist/cli/ultragoal.js +29 -0
- package/dist/cli/ultragoal.js.map +1 -1
- package/dist/hooks/__tests__/agents-overlay.test.js +8 -10
- package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js +15 -0
- package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/deep-interview-contract.test.js +16 -0
- package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/explore-routing.test.js +5 -8
- package/dist/hooks/__tests__/explore-routing.test.js.map +1 -1
- package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js +6 -6
- package/dist/hooks/__tests__/explore-sparkshell-guidance-contract.test.js.map +1 -1
- package/dist/hooks/__tests__/skill-guidance-contract.test.js +14 -5
- package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
- package/dist/hooks/agents-overlay.d.ts.map +1 -1
- package/dist/hooks/agents-overlay.js +2 -1
- package/dist/hooks/agents-overlay.js.map +1 -1
- package/dist/hooks/explore-routing.d.ts +1 -1
- package/dist/hooks/explore-routing.d.ts.map +1 -1
- package/dist/hooks/explore-routing.js +3 -8
- package/dist/hooks/explore-routing.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +112 -1
- package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
- package/dist/hooks/extensibility/plugin-runner-stdin.d.ts +2 -0
- package/dist/hooks/extensibility/plugin-runner-stdin.d.ts.map +1 -0
- package/dist/hooks/extensibility/plugin-runner-stdin.js +16 -0
- package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -0
- package/dist/hooks/extensibility/plugin-runner.js +2 -4
- package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +23 -2
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +367 -1
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +118 -7
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/constants.d.ts +10 -0
- package/dist/hud/constants.d.ts.map +1 -1
- package/dist/hud/constants.js +19 -0
- package/dist/hud/constants.js.map +1 -1
- package/dist/hud/index.d.ts +6 -1
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +12 -3
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts +10 -2
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +78 -30
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/tmux.d.ts +14 -1
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +129 -15
- package/dist/hud/tmux.js.map +1 -1
- package/dist/mcp/wiki-server.d.ts +3 -3
- package/dist/ralplan/consensus-gate.js +9 -1
- package/dist/ralplan/consensus-gate.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +170 -17
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/run-test-files.test.js +115 -1
- package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +74 -11
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
- package/dist/scripts/notify-hook/team-worker-stop.js +54 -21
- package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
- package/dist/scripts/run-test-files.js +218 -160
- package/dist/scripts/run-test-files.js.map +1 -1
- package/dist/state/__tests__/operations.test.js +463 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/team/__tests__/delivery-log.test.js +18 -0
- package/dist/team/__tests__/delivery-log.test.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +48 -0
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +107 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.js +14 -2
- package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
- package/dist/team/__tests__/tmux-test-fixture.test.js +1 -0
- package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
- package/dist/team/__tests__/worker-bootstrap.test.js +54 -1
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/delivery-log.d.ts +1 -1
- package/dist/team/delivery-log.d.ts.map +1 -1
- package/dist/team/delivery-log.js.map +1 -1
- package/dist/team/repo-aware-decomposition.d.ts +4 -0
- package/dist/team/repo-aware-decomposition.d.ts.map +1 -1
- package/dist/team/repo-aware-decomposition.js.map +1 -1
- package/dist/team/runtime.d.ts.map +1 -1
- package/dist/team/runtime.js +78 -9
- package/dist/team/runtime.js.map +1 -1
- package/dist/team/tmux-session.d.ts +1 -0
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +16 -5
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/team/ultragoal-context.d.ts +12 -0
- package/dist/team/ultragoal-context.d.ts.map +1 -1
- package/dist/team/ultragoal-context.js +32 -8
- package/dist/team/ultragoal-context.js.map +1 -1
- package/dist/utils/__tests__/paths.test.js +23 -0
- package/dist/utils/__tests__/paths.test.js.map +1 -1
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +4 -2
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/toml.d.ts +4 -0
- package/dist/utils/toml.d.ts.map +1 -0
- package/dist/utils/toml.js +75 -0
- package/dist/utils/toml.js.map +1 -0
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/autopilot/SKILL.md +3 -0
- package/plugins/oh-my-codex/skills/deep-interview/SKILL.md +34 -0
- package/plugins/oh-my-codex/skills/ultrawork/SKILL.md +32 -17
- package/skills/autopilot/SKILL.md +3 -0
- package/skills/deep-interview/SKILL.md +34 -0
- package/skills/ultrawork/SKILL.md +32 -17
- package/src/scripts/__tests__/codex-native-hook.test.ts +218 -28
- package/src/scripts/__tests__/run-test-files.test.ts +138 -2
- package/src/scripts/codex-native-hook.ts +80 -10
- package/src/scripts/notify-hook/team-worker-stop.ts +58 -18
- package/src/scripts/run-test-files.ts +229 -150
- package/templates/AGENTS.md +39 -199
- package/templates/catalog-manifest.json +7 -0
|
@@ -6,14 +6,24 @@ const DEFAULT_TEST_TIMEOUT_MS = 0;
|
|
|
6
6
|
const DEFAULT_RUNNER_TIMEOUT_MS = 30 * 60 * 1_000;
|
|
7
7
|
const DEFAULT_FORCE_EXIT_GRACE_MS = 30_000;
|
|
8
8
|
const DEFAULT_CI_TEST_CONCURRENCY = 1;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
|
|
9
|
+
const DEFAULT_LOCAL_TEST_CONCURRENCY = 1;
|
|
10
|
+
const PRESERVED_TEST_ENV_KEYS = new Set([
|
|
11
|
+
'OMX_EXPLORE_BIN',
|
|
12
|
+
'OMX_NODE_TEST_CONCURRENCY',
|
|
13
|
+
'OMX_NODE_TEST_FORCE_EXIT',
|
|
14
|
+
'OMX_NODE_TEST_FORCE_EXIT_GRACE_MS',
|
|
15
|
+
'OMX_NODE_TEST_PRESERVE_RUNTIME_ENV',
|
|
16
|
+
'OMX_NODE_TEST_RUNNER_TIMEOUT_MS',
|
|
17
|
+
'OMX_NODE_TEST_TIMEOUT_MS',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
type TestRunResult = {
|
|
21
|
+
status: number | null;
|
|
22
|
+
signal: NodeJS.Signals | null;
|
|
23
|
+
errorMessage?: string;
|
|
24
|
+
timedOut: boolean;
|
|
25
|
+
abnormal: boolean;
|
|
26
|
+
};
|
|
17
27
|
|
|
18
28
|
function parseBooleanEnv(value: string | undefined): boolean {
|
|
19
29
|
if (!value) return false;
|
|
@@ -60,7 +70,36 @@ function parseTestConcurrency(env: NodeJS.ProcessEnv): number | undefined {
|
|
|
60
70
|
return undefined;
|
|
61
71
|
}
|
|
62
72
|
|
|
63
|
-
return env.CI === 'true' || env.GITHUB_ACTIONS === 'true'
|
|
73
|
+
return env.CI === 'true' || env.GITHUB_ACTIONS === 'true'
|
|
74
|
+
? DEFAULT_CI_TEST_CONCURRENCY
|
|
75
|
+
: DEFAULT_LOCAL_TEST_CONCURRENCY;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function shouldScrubRuntimeEnvKey(key: string): boolean {
|
|
79
|
+
if (PRESERVED_TEST_ENV_KEYS.has(key)) return false;
|
|
80
|
+
return (
|
|
81
|
+
key.startsWith('OMX_') ||
|
|
82
|
+
key.startsWith('OMXBOX_') ||
|
|
83
|
+
key.startsWith('CODEX_') ||
|
|
84
|
+
key === 'USE_OMX_EXPLORE_CMD' ||
|
|
85
|
+
key === 'SESSION_ID' ||
|
|
86
|
+
key === 'TMUX' ||
|
|
87
|
+
key === 'TMUX_PANE'
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function buildChildEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
|
92
|
+
const childEnv = { ...env };
|
|
93
|
+
delete childEnv.NODE_TEST_CONTEXT;
|
|
94
|
+
if (!parseBooleanEnv(env.OMX_NODE_TEST_PRESERVE_RUNTIME_ENV)) {
|
|
95
|
+
for (const key of Object.keys(childEnv)) {
|
|
96
|
+
if (shouldScrubRuntimeEnvKey(key)) {
|
|
97
|
+
delete childEnv[key];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
childEnv.OMX_TEST_RELAX_TMUX_TIMEOUT = '1';
|
|
102
|
+
return childEnv;
|
|
64
103
|
}
|
|
65
104
|
|
|
66
105
|
const roots = process.argv.slice(2);
|
|
@@ -82,41 +121,33 @@ const runnerTimeoutMs = parseTimeoutMs(process.env.OMX_NODE_TEST_RUNNER_TIMEOUT_
|
|
|
82
121
|
const forceExitGraceMs = parseTimeoutMs(process.env.OMX_NODE_TEST_FORCE_EXIT_GRACE_MS, DEFAULT_FORCE_EXIT_GRACE_MS);
|
|
83
122
|
const testConcurrency = parseTestConcurrency(process.env);
|
|
84
123
|
const forceExit = parseBooleanEnv(process.env.OMX_NODE_TEST_FORCE_EXIT);
|
|
85
|
-
const
|
|
124
|
+
const sharedTestArgs = ['--test'];
|
|
86
125
|
if (testTimeoutMs > 0) {
|
|
87
|
-
|
|
126
|
+
sharedTestArgs.push(`--test-timeout=${testTimeoutMs}`);
|
|
88
127
|
}
|
|
89
128
|
if (testConcurrency) {
|
|
90
|
-
|
|
129
|
+
sharedTestArgs.push(`--test-concurrency=${testConcurrency}`);
|
|
91
130
|
}
|
|
92
131
|
if (forceExit) {
|
|
93
|
-
|
|
132
|
+
sharedTestArgs.push('--test-force-exit', '--test-reporter=tap');
|
|
94
133
|
}
|
|
95
|
-
testArgs.push(...files);
|
|
96
134
|
|
|
97
135
|
console.error(
|
|
98
136
|
`[run-test-files] running ${files.length} test file(s) from ${targets.join(', ')}${
|
|
99
137
|
testTimeoutMs > 0 ? ` with per-test timeout ${testTimeoutMs}ms` : ' with per-test timeout disabled'
|
|
100
138
|
}${testConcurrency ? `, test concurrency ${testConcurrency}` : ', default test concurrency'}${
|
|
101
139
|
forceExit ? `, force exit enabled with ${forceExitGraceMs}ms completion grace` : ', force exit disabled'
|
|
102
|
-
}${runnerTimeoutMs > 0 ? `, and runner timeout ${runnerTimeoutMs}ms` : ', and runner timeout disabled'}`,
|
|
140
|
+
}${runnerTimeoutMs > 0 ? `, and runner timeout ${runnerTimeoutMs}ms` : ', and runner timeout disabled'}, with per-file process isolation`,
|
|
103
141
|
);
|
|
104
142
|
|
|
105
|
-
const childEnv =
|
|
106
|
-
delete childEnv.NODE_TEST_CONTEXT;
|
|
107
|
-
childEnv.OMX_TEST_RELAX_TMUX_TIMEOUT = '1';
|
|
108
|
-
if (!parseBooleanEnv(process.env.OMX_NODE_TEST_PRESERVE_RUNTIME_ENV)) {
|
|
109
|
-
for (const key of RUNTIME_STATE_ENV_KEYS) {
|
|
110
|
-
delete childEnv[key];
|
|
111
|
-
}
|
|
112
|
-
}
|
|
143
|
+
const childEnv = buildChildEnv(process.env);
|
|
113
144
|
|
|
114
|
-
function reportAbnormalExit(signal: NodeJS.Signals | null, errorMessage?: string): void {
|
|
145
|
+
function reportAbnormalExit(file: string, signal: NodeJS.Signals | null, errorMessage?: string): void {
|
|
115
146
|
if (errorMessage) {
|
|
116
|
-
console.error(`[run-test-files] node --test error: ${errorMessage}`);
|
|
147
|
+
console.error(`[run-test-files] node --test error for ${file}: ${errorMessage}`);
|
|
117
148
|
}
|
|
118
149
|
console.error(
|
|
119
|
-
`[run-test-files] node --test did not exit normally${signal ? ` (signal: ${signal})` : ''}. `
|
|
150
|
+
`[run-test-files] node --test did not exit normally for ${file}${signal ? ` (signal: ${signal})` : ''}. `
|
|
120
151
|
+ `Roots: ${targets.join(', ')}. Test files: ${files.length}. `
|
|
121
152
|
+ `Per-test timeout: ${testTimeoutMs > 0 ? `${testTimeoutMs}ms` : 'disabled'}. `
|
|
122
153
|
+ `Test concurrency: ${testConcurrency ?? 'default'}. `
|
|
@@ -146,155 +177,203 @@ function terminateChild(child: ChildProcess): void {
|
|
|
146
177
|
signalChild(child, 'SIGKILL');
|
|
147
178
|
}
|
|
148
179
|
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
180
|
+
function runFileWithCompletionForceExit(file: string): Promise<TestRunResult> {
|
|
181
|
+
return new Promise((resolveRun) => {
|
|
182
|
+
let finished = false;
|
|
183
|
+
let sawFailure = false;
|
|
184
|
+
let lastTapOk = 0;
|
|
185
|
+
let tapTests: number | undefined;
|
|
186
|
+
let tapPass: number | undefined;
|
|
187
|
+
let tapFail = 0;
|
|
188
|
+
let tapCancelled = 0;
|
|
189
|
+
let completedFromSummary = false;
|
|
190
|
+
let completionTimer: NodeJS.Timeout | undefined;
|
|
191
|
+
let runnerTimer: NodeJS.Timeout | undefined;
|
|
192
|
+
let stdoutRemainder = '';
|
|
193
|
+
let stderrRemainder = '';
|
|
194
|
+
|
|
195
|
+
const child = spawn(process.execPath, [...sharedTestArgs, file], {
|
|
196
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
197
|
+
env: childEnv,
|
|
198
|
+
detached: !isWindows(),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
function finish(status: number | null, signal: NodeJS.Signals | null, reason: string, terminate: boolean): void {
|
|
202
|
+
if (finished) return;
|
|
203
|
+
finished = true;
|
|
204
|
+
if (completionTimer) clearTimeout(completionTimer);
|
|
205
|
+
if (runnerTimer) clearTimeout(runnerTimer);
|
|
206
|
+
console.error(`[run-test-files] ${file}: ${reason}; status ${status ?? 'unknown'}`);
|
|
207
|
+
if (terminate) {
|
|
208
|
+
terminateChild(child);
|
|
209
|
+
}
|
|
210
|
+
child.stdout?.destroy();
|
|
211
|
+
child.stderr?.destroy();
|
|
212
|
+
child.unref();
|
|
213
|
+
resolveRun({
|
|
214
|
+
status,
|
|
215
|
+
signal,
|
|
216
|
+
timedOut: reason.startsWith('runner timeout'),
|
|
217
|
+
abnormal: status === null || signal !== null || reason.startsWith('node --test failed to spawn'),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
181
220
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
221
|
+
function markFailure(): void {
|
|
222
|
+
sawFailure = true;
|
|
223
|
+
if (completionTimer) {
|
|
224
|
+
clearTimeout(completionTimer);
|
|
225
|
+
completionTimer = undefined;
|
|
226
|
+
}
|
|
187
227
|
}
|
|
188
|
-
}
|
|
189
228
|
|
|
190
|
-
|
|
191
|
-
if (sawFailure) return;
|
|
192
|
-
if (completionTimer) clearTimeout(completionTimer);
|
|
193
|
-
completionTimer = setTimeout(() => {
|
|
229
|
+
function armCompletionTimer(reason: string): void {
|
|
194
230
|
if (sawFailure) return;
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (tapTests === undefined || tapPass === undefined) return false;
|
|
201
|
-
return tapTests === tapPass && tapFail === 0 && tapCancelled === 0;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function parseTapLine(line: string): void {
|
|
205
|
-
if (/^(?:not ok|Bail out!)/.test(line)) {
|
|
206
|
-
markFailure();
|
|
207
|
-
return;
|
|
231
|
+
if (completionTimer) clearTimeout(completionTimer);
|
|
232
|
+
completionTimer = setTimeout(() => {
|
|
233
|
+
if (sawFailure) return;
|
|
234
|
+
finish(0, null, reason, true);
|
|
235
|
+
}, forceExitGraceMs);
|
|
208
236
|
}
|
|
209
237
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (summary[1] === 'tests') tapTests = count;
|
|
214
|
-
if (summary[1] === 'pass') tapPass = count;
|
|
215
|
-
if (summary[1] === 'fail') tapFail = count;
|
|
216
|
-
if (summary[1] === 'cancelled') tapCancelled = count;
|
|
217
|
-
if ((summary[1] === 'fail' || summary[1] === 'cancelled') && count > 0) markFailure();
|
|
218
|
-
return;
|
|
238
|
+
function sawCleanTapSummary(): boolean {
|
|
239
|
+
if (tapTests === undefined || tapPass === undefined) return false;
|
|
240
|
+
return tapTests === tapPass && tapFail === 0 && tapCancelled === 0;
|
|
219
241
|
}
|
|
220
242
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
armCompletionTimer(`force-exit completion grace elapsed after TAP ok ${lastTapOk} with no later failures`);
|
|
243
|
+
function parseTapLine(line: string): void {
|
|
244
|
+
if (/^(?:not ok|Bail out!)/.test(line)) {
|
|
245
|
+
markFailure();
|
|
246
|
+
return;
|
|
226
247
|
}
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
248
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
249
|
+
const summary = line.match(/^# (tests|pass|fail|cancelled) (\d+)$/);
|
|
250
|
+
if (summary) {
|
|
251
|
+
const count = Number(summary[2]);
|
|
252
|
+
if (summary[1] === 'tests') tapTests = count;
|
|
253
|
+
if (summary[1] === 'pass') tapPass = count;
|
|
254
|
+
if (summary[1] === 'fail') tapFail = count;
|
|
255
|
+
if (summary[1] === 'cancelled') tapCancelled = count;
|
|
256
|
+
if ((summary[1] === 'fail' || summary[1] === 'cancelled') && count > 0) markFailure();
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
235
259
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
260
|
+
const ok = line.match(/^ok (\d+)\b/);
|
|
261
|
+
if (ok) {
|
|
262
|
+
lastTapOk = Number(ok[1]);
|
|
263
|
+
if (lastTapOk >= files.length) {
|
|
264
|
+
armCompletionTimer(`force-exit completion grace elapsed after TAP ok ${lastTapOk} with no later failures`);
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
241
268
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
combined = lines.pop() ?? '';
|
|
248
|
-
if (isStdout) {
|
|
249
|
-
stdoutRemainder = combined;
|
|
250
|
-
} else {
|
|
251
|
-
stderrRemainder = combined;
|
|
252
|
-
}
|
|
253
|
-
for (const line of lines) parseTapLine(line);
|
|
254
|
-
}
|
|
269
|
+
const plan = line.match(/^1\.\.(\d+)$/);
|
|
270
|
+
if (plan && Number(plan[1]) === lastTapOk && !sawFailure) {
|
|
271
|
+
armCompletionTimer(`force-exit completion grace elapsed after TAP plan ${line}`);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
255
274
|
|
|
256
|
-
|
|
257
|
-
|
|
275
|
+
if (/^# duration_ms /.test(line) && sawCleanTapSummary()) {
|
|
276
|
+
completedFromSummary = true;
|
|
277
|
+
armCompletionTimer('force-exit completion grace elapsed after clean TAP summary');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
258
280
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
281
|
+
function handleOutput(chunk: Buffer, stream: NodeJS.WriteStream, isStdout: boolean): void {
|
|
282
|
+
stream.write(chunk);
|
|
283
|
+
const text = chunk.toString('utf8');
|
|
284
|
+
let combined = (isStdout ? stdoutRemainder : stderrRemainder) + text;
|
|
285
|
+
const lines = combined.split(/\r?\n/);
|
|
286
|
+
combined = lines.pop() ?? '';
|
|
287
|
+
if (isStdout) {
|
|
288
|
+
stdoutRemainder = combined;
|
|
289
|
+
} else {
|
|
290
|
+
stderrRemainder = combined;
|
|
291
|
+
}
|
|
292
|
+
for (const line of lines) parseTapLine(line);
|
|
293
|
+
}
|
|
263
294
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
finish(
|
|
270
|
-
|
|
295
|
+
child.stdout?.on('data', (chunk: Buffer) => handleOutput(chunk, process.stdout, true));
|
|
296
|
+
child.stderr?.on('data', (chunk: Buffer) => handleOutput(chunk, process.stderr, false));
|
|
297
|
+
|
|
298
|
+
child.on('error', (error) => {
|
|
299
|
+
reportAbnormalExit(file, null, error.message);
|
|
300
|
+
finish(null, null, 'node --test failed to spawn', true);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
child.on('exit', (status, signal) => {
|
|
304
|
+
if (finished) return;
|
|
305
|
+
if (stdoutRemainder) parseTapLine(stdoutRemainder);
|
|
306
|
+
if (stderrRemainder) parseTapLine(stderrRemainder);
|
|
307
|
+
if (typeof status === 'number') {
|
|
308
|
+
finish(status, null, `node --test exited normally${completedFromSummary ? ' after clean TAP summary' : ''}`, false);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
reportAbnormalExit(file, signal);
|
|
312
|
+
finish(null, signal, 'node --test exited without a numeric status', true);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (runnerTimeoutMs > 0) {
|
|
316
|
+
runnerTimer = setTimeout(() => {
|
|
317
|
+
reportAbnormalExit(file, null);
|
|
318
|
+
finish(1, null, `runner timeout ${runnerTimeoutMs}ms elapsed`, true);
|
|
319
|
+
}, runnerTimeoutMs);
|
|
271
320
|
}
|
|
272
|
-
reportAbnormalExit(signal);
|
|
273
|
-
finish(1, 'node --test exited without a numeric status');
|
|
274
321
|
});
|
|
275
|
-
|
|
276
|
-
if (runnerTimeoutMs > 0) {
|
|
277
|
-
runnerTimer = setTimeout(() => {
|
|
278
|
-
reportAbnormalExit(null);
|
|
279
|
-
finish(1, `runner timeout ${runnerTimeoutMs}ms elapsed`);
|
|
280
|
-
}, runnerTimeoutMs);
|
|
281
|
-
}
|
|
282
322
|
}
|
|
283
323
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
} else {
|
|
287
|
-
const result = spawnSync(process.execPath, testArgs, {
|
|
324
|
+
function runFileSync(file: string): TestRunResult {
|
|
325
|
+
const result = spawnSync(process.execPath, [...sharedTestArgs, file], {
|
|
288
326
|
stdio: 'inherit',
|
|
289
327
|
env: childEnv,
|
|
290
328
|
timeout: runnerTimeoutMs > 0 ? runnerTimeoutMs : undefined,
|
|
291
329
|
killSignal: 'SIGTERM',
|
|
292
330
|
});
|
|
293
331
|
|
|
294
|
-
|
|
295
|
-
|
|
332
|
+
const timedOut = (result.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT';
|
|
333
|
+
if (result.status !== 0 && (result.error || typeof result.status !== 'number')) {
|
|
334
|
+
reportAbnormalExit(file, result.signal, result.error?.message);
|
|
296
335
|
}
|
|
297
336
|
|
|
298
|
-
|
|
299
|
-
|
|
337
|
+
return {
|
|
338
|
+
status: result.status,
|
|
339
|
+
signal: result.signal,
|
|
340
|
+
errorMessage: result.error?.message,
|
|
341
|
+
timedOut,
|
|
342
|
+
abnormal: Boolean(result.error || typeof result.status !== 'number'),
|
|
343
|
+
};
|
|
300
344
|
}
|
|
345
|
+
|
|
346
|
+
async function main(): Promise<void> {
|
|
347
|
+
const failedFiles: string[] = [];
|
|
348
|
+
let abnormalExit = false;
|
|
349
|
+
let runnerTimedOut = false;
|
|
350
|
+
|
|
351
|
+
for (const file of files) {
|
|
352
|
+
const result = forceExit ? await runFileWithCompletionForceExit(file) : runFileSync(file);
|
|
353
|
+
|
|
354
|
+
if (result.status === 0) {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
failedFiles.push(file);
|
|
359
|
+
abnormalExit = abnormalExit || result.abnormal;
|
|
360
|
+
runnerTimedOut = runnerTimedOut || result.timedOut;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (failedFiles.length > 0 || runnerTimedOut) {
|
|
364
|
+
const failureSummary = failedFiles.length === 0 && runnerTimedOut
|
|
365
|
+
? `runner timed out with 0 of ${files.length} test file(s) failed`
|
|
366
|
+
: `${failedFiles.length} of ${files.length} test file(s) failed${abnormalExit ? ' or timed out' : ''}${runnerTimedOut ? '; runner timed out' : ''}`;
|
|
367
|
+
console.error(
|
|
368
|
+
`[run-test-files] ${failureSummary}:`,
|
|
369
|
+
);
|
|
370
|
+
for (const file of failedFiles) {
|
|
371
|
+
console.error(`[run-test-files] ${file}`);
|
|
372
|
+
}
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
process.exit(0);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
await main();
|