@c9up/helix 0.1.4 → 0.1.6
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/dist/cli/coverage/diff/overlay.d.ts.map +1 -1
- package/dist/cli/coverage/diff/overlay.js +19 -6
- package/dist/cli/coverage/diff/overlay.js.map +1 -1
- package/dist/cli/pool.js +10 -0
- package/dist/cli/pool.js.map +1 -1
- package/dist/runtime/equals.d.ts.map +1 -1
- package/dist/runtime/equals.js +6 -2
- package/dist/runtime/equals.js.map +1 -1
- package/dist/runtime/suite.d.ts +26 -0
- package/dist/runtime/suite.d.ts.map +1 -1
- package/dist/runtime/suite.js +21 -18
- package/dist/runtime/suite.js.map +1 -1
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +2 -2
- package/src/cli/coverage/aggregate.ts +0 -231
- package/src/cli/coverage/collect.ts +0 -63
- package/src/cli/coverage/diff/base.ts +0 -46
- package/src/cli/coverage/diff/index.ts +0 -160
- package/src/cli/coverage/diff/overlay.ts +0 -62
- package/src/cli/coverage/diff/parse.ts +0 -121
- package/src/cli/coverage/diff/reporters.ts +0 -82
- package/src/cli/coverage/diff/types.ts +0 -46
- package/src/cli/coverage/filter.ts +0 -71
- package/src/cli/coverage/glob.ts +0 -0
- package/src/cli/coverage/index.ts +0 -126
- package/src/cli/coverage/reporters/json.ts +0 -40
- package/src/cli/coverage/reporters/lcov.ts +0 -54
- package/src/cli/coverage/reporters/text.ts +0 -48
- package/src/cli/coverage/thresholds.ts +0 -73
- package/src/cli/coverage/types.ts +0 -93
- package/src/cli/discover.ts +0 -174
- package/src/cli/native.ts +0 -104
- package/src/cli/pool.ts +0 -486
- package/src/cli/reporter.ts +0 -155
- package/src/cli/run.ts +0 -440
- package/src/cli/summary.ts +0 -42
- package/src/cli/watch/loop.ts +0 -159
- package/src/cli/watch/types.ts +0 -22
- package/src/cli/watch/watcher.ts +0 -145
- package/src/container/index.ts +0 -16
- package/src/container/override.ts +0 -86
- package/src/container/spy.ts +0 -25
- package/src/index.ts +0 -42
- package/src/runtime/assertion-error.ts +0 -38
- package/src/runtime/cli-worker.ts +0 -140
- package/src/runtime/equals.ts +0 -400
- package/src/runtime/expect.ts +0 -173
- package/src/runtime/index.ts +0 -50
- package/src/runtime/lifecycle.ts +0 -17
- package/src/runtime/matchers.ts +0 -452
- package/src/runtime/run.ts +0 -573
- package/src/runtime/suite.ts +0 -310
- package/src/runtime/test-context.ts +0 -59
- package/src/runtime/vi/fake-timers.ts +0 -410
- package/src/runtime/vi/index.ts +0 -254
- package/src/runtime/vi/spy.ts +0 -224
- package/src/runtime/vi/spyOn.ts +0 -155
- package/src/runtime/vi/system-time.ts +0 -121
- package/src/runtime/worker.ts +0 -239
- package/src/time/freeze.ts +0 -229
- package/src/time/index.ts +0 -16
package/src/runtime/run.ts
DELETED
|
@@ -1,573 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Execution engine — walks the suite tree collected by `suite.ts`,
|
|
3
|
-
* runs each test with its inherited hook chain, and produces a structured
|
|
4
|
-
* `FileResult` for the orchestrator.
|
|
5
|
-
*
|
|
6
|
-
* `.only` handling: if any test or suite is marked `only` anywhere in the
|
|
7
|
-
* tree, non-`only` paths are downgraded to `skip` before execution.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { AssertionError } from "./assertion-error.js";
|
|
11
|
-
import type { SuiteNode, TestFn, TestNode } from "./suite.js";
|
|
12
|
-
import { withTestContext } from "./test-context.js";
|
|
13
|
-
|
|
14
|
-
export interface TestResult {
|
|
15
|
-
name: string;
|
|
16
|
-
fullName: string;
|
|
17
|
-
status: "pass" | "fail" | "skip" | "todo";
|
|
18
|
-
durationMs: number;
|
|
19
|
-
error?: SerializedError;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface SuiteResult {
|
|
23
|
-
name: string;
|
|
24
|
-
fullName: string;
|
|
25
|
-
children: Array<SuiteResult | TestResult>;
|
|
26
|
-
status: "pass" | "fail" | "skip";
|
|
27
|
-
durationMs: number;
|
|
28
|
-
/** Hook errors attributed to this suite (beforeAll/afterAll). */
|
|
29
|
-
hookErrors: SerializedError[];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface SerializedError {
|
|
33
|
-
name: string;
|
|
34
|
-
message: string;
|
|
35
|
-
stack?: string;
|
|
36
|
-
actual?: unknown;
|
|
37
|
-
expected?: unknown;
|
|
38
|
-
operator?: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface FileResult {
|
|
42
|
-
file: string;
|
|
43
|
-
suites: SuiteResult[];
|
|
44
|
-
tests: TestResult[];
|
|
45
|
-
totals: {
|
|
46
|
-
pass: number;
|
|
47
|
-
fail: number;
|
|
48
|
-
skip: number;
|
|
49
|
-
todo: number;
|
|
50
|
-
};
|
|
51
|
-
durationMs: number;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface ExecuteOptions {
|
|
55
|
-
/**
|
|
56
|
-
* Per-test timeout in ms. `0` disables (default). When exceeded, the
|
|
57
|
-
* test is marked failed with a timeout error; the hanging promise is not
|
|
58
|
-
* awaited further.
|
|
59
|
-
*/
|
|
60
|
-
timeoutMs?: number;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function serializeError(err: unknown): SerializedError {
|
|
64
|
-
if (err instanceof AssertionError) {
|
|
65
|
-
return {
|
|
66
|
-
name: err.name,
|
|
67
|
-
message: err.message,
|
|
68
|
-
stack: err.stack,
|
|
69
|
-
actual: err.actual,
|
|
70
|
-
expected: err.expected,
|
|
71
|
-
operator: err.operator,
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
if (err instanceof Error) {
|
|
75
|
-
return { name: err.name, message: err.message, stack: err.stack };
|
|
76
|
-
}
|
|
77
|
-
return { name: "NonError", message: String(err) };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function hasOnly(node: SuiteNode | TestNode): boolean {
|
|
81
|
-
if (node.mode === "only") return true;
|
|
82
|
-
if (node.kind === "test") return false;
|
|
83
|
-
return node.children.some(hasOnly);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function ancestorHasOnly(node: SuiteNode | TestNode): boolean {
|
|
87
|
-
let cursor: SuiteNode | undefined = node.parent;
|
|
88
|
-
while (cursor) {
|
|
89
|
-
if (cursor.mode === "only") return true;
|
|
90
|
-
cursor = cursor.parent;
|
|
91
|
-
}
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function pathLeadsToOnly(node: SuiteNode | TestNode): boolean {
|
|
96
|
-
if (node.mode === "only") return true;
|
|
97
|
-
if (ancestorHasOnly(node)) return true;
|
|
98
|
-
if (node.kind === "suite") {
|
|
99
|
-
return node.children.some(hasOnly);
|
|
100
|
-
}
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function collectHookChain(
|
|
105
|
-
leaf: SuiteNode,
|
|
106
|
-
type: "beforeEach" | "afterEach",
|
|
107
|
-
): TestFn[] {
|
|
108
|
-
// beforeEach: outermost first; afterEach: innermost first.
|
|
109
|
-
const suites: SuiteNode[] = [];
|
|
110
|
-
let cursor: SuiteNode | undefined = leaf;
|
|
111
|
-
while (cursor) {
|
|
112
|
-
suites.push(cursor);
|
|
113
|
-
cursor = cursor.parent;
|
|
114
|
-
}
|
|
115
|
-
const ordered = type === "beforeEach" ? [...suites].reverse() : suites;
|
|
116
|
-
const chain: TestFn[] = [];
|
|
117
|
-
for (const s of ordered) {
|
|
118
|
-
for (const h of s.hooks) {
|
|
119
|
-
if (h.type === type) chain.push(h.fn);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return chain;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async function runHooks(hooks: TestFn[]): Promise<SerializedError | undefined> {
|
|
126
|
-
for (const h of hooks) {
|
|
127
|
-
try {
|
|
128
|
-
await h();
|
|
129
|
-
} catch (err) {
|
|
130
|
-
return serializeError(err);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return undefined;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function joinName(parent: string, name: string): string {
|
|
137
|
-
if (!parent) return name;
|
|
138
|
-
if (!name) return parent;
|
|
139
|
-
return `${parent} > ${name}`;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function combineErrors(
|
|
143
|
-
primary: SerializedError | undefined,
|
|
144
|
-
secondary: SerializedError | undefined,
|
|
145
|
-
): SerializedError | undefined {
|
|
146
|
-
if (!primary) return secondary;
|
|
147
|
-
if (!secondary) return primary;
|
|
148
|
-
return {
|
|
149
|
-
name: primary.name,
|
|
150
|
-
message: `${primary.message}\n+ teardown also failed: ${secondary.message}`,
|
|
151
|
-
stack: primary.stack,
|
|
152
|
-
actual: primary.actual,
|
|
153
|
-
expected: primary.expected,
|
|
154
|
-
operator: primary.operator,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
async function withTimeout<T>(
|
|
159
|
-
p: Promise<T> | T,
|
|
160
|
-
ms: number,
|
|
161
|
-
label: string,
|
|
162
|
-
): Promise<T> {
|
|
163
|
-
if (ms <= 0) return await p;
|
|
164
|
-
let timer: NodeJS.Timeout | undefined;
|
|
165
|
-
const timeout = new Promise<T>((_, reject) => {
|
|
166
|
-
timer = setTimeout(() => {
|
|
167
|
-
reject(new Error(`${label} exceeded ${ms}ms timeout`));
|
|
168
|
-
}, ms);
|
|
169
|
-
// Don't keep the event loop alive just for this watchdog.
|
|
170
|
-
timer.unref?.();
|
|
171
|
-
});
|
|
172
|
-
try {
|
|
173
|
-
return await Promise.race([Promise.resolve(p), timeout]);
|
|
174
|
-
} finally {
|
|
175
|
-
if (timer) clearTimeout(timer);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
interface RunCtx {
|
|
180
|
-
onlyActive: boolean;
|
|
181
|
-
flatTests: TestResult[];
|
|
182
|
-
timeoutMs: number;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async function runTest(
|
|
186
|
-
node: TestNode,
|
|
187
|
-
parentFullName: string,
|
|
188
|
-
ctx: RunCtx,
|
|
189
|
-
): Promise<TestResult> {
|
|
190
|
-
const fullName = joinName(parentFullName, node.name);
|
|
191
|
-
if (node.mode === "todo") {
|
|
192
|
-
const r: TestResult = {
|
|
193
|
-
name: node.name,
|
|
194
|
-
fullName,
|
|
195
|
-
status: "todo",
|
|
196
|
-
durationMs: 0,
|
|
197
|
-
};
|
|
198
|
-
ctx.flatTests.push(r);
|
|
199
|
-
return r;
|
|
200
|
-
}
|
|
201
|
-
if (node.mode === "skip" || (ctx.onlyActive && !pathLeadsToOnly(node))) {
|
|
202
|
-
const r: TestResult = {
|
|
203
|
-
name: node.name,
|
|
204
|
-
fullName,
|
|
205
|
-
status: "skip",
|
|
206
|
-
durationMs: 0,
|
|
207
|
-
};
|
|
208
|
-
ctx.flatTests.push(r);
|
|
209
|
-
return r;
|
|
210
|
-
}
|
|
211
|
-
const before = collectHookChain(node.parent, "beforeEach");
|
|
212
|
-
const after = collectHookChain(node.parent, "afterEach");
|
|
213
|
-
const start = Date.now();
|
|
214
|
-
// Wrap the whole cycle (beforeEach + body + afterEach) in a per-test
|
|
215
|
-
// frame so test-scoped cleanups (e.g. container overrides registered
|
|
216
|
-
// via `helix.override`) drain at the right time. Frame closes AFTER
|
|
217
|
-
// afterEach so user teardown can still observe test-scoped state.
|
|
218
|
-
const r = await withTestContext<TestResult>(async () => {
|
|
219
|
-
const beforeErr = await runHooks(before);
|
|
220
|
-
if (beforeErr) {
|
|
221
|
-
const afterErrBE = await runHooks(after);
|
|
222
|
-
return {
|
|
223
|
-
name: node.name,
|
|
224
|
-
fullName,
|
|
225
|
-
status: "fail",
|
|
226
|
-
durationMs: Date.now() - start,
|
|
227
|
-
error: combineErrors(beforeErr, afterErrBE),
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
let testErr: SerializedError | undefined;
|
|
231
|
-
try {
|
|
232
|
-
const result = node.fn?.();
|
|
233
|
-
if (
|
|
234
|
-
result &&
|
|
235
|
-
typeof (result as PromiseLike<unknown>).then === "function"
|
|
236
|
-
) {
|
|
237
|
-
await withTimeout(
|
|
238
|
-
result as Promise<unknown>,
|
|
239
|
-
ctx.timeoutMs,
|
|
240
|
-
`test "${fullName}"`,
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
} catch (err) {
|
|
244
|
-
testErr = serializeError(err);
|
|
245
|
-
}
|
|
246
|
-
const afterErr = await runHooks(after);
|
|
247
|
-
const finalErr = combineErrors(testErr, afterErr);
|
|
248
|
-
return {
|
|
249
|
-
name: node.name,
|
|
250
|
-
fullName,
|
|
251
|
-
status: finalErr ? "fail" : "pass",
|
|
252
|
-
durationMs: Date.now() - start,
|
|
253
|
-
error: finalErr,
|
|
254
|
-
};
|
|
255
|
-
});
|
|
256
|
-
ctx.flatTests.push(r);
|
|
257
|
-
return r;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Recursively mark every descendant test as failed due to an ancestor
|
|
262
|
-
* hook error. Ensures `flatTests` is complete and reporters can navigate
|
|
263
|
-
* the whole tree.
|
|
264
|
-
*/
|
|
265
|
-
function attributeHookFailure(
|
|
266
|
-
node: SuiteNode,
|
|
267
|
-
parentFullName: string,
|
|
268
|
-
err: SerializedError,
|
|
269
|
-
ctx: RunCtx,
|
|
270
|
-
): Array<SuiteResult | TestResult> {
|
|
271
|
-
const children: Array<SuiteResult | TestResult> = [];
|
|
272
|
-
const fullName = joinName(parentFullName, node.name);
|
|
273
|
-
for (const child of node.children) {
|
|
274
|
-
if (child.kind === "test") {
|
|
275
|
-
const r: TestResult = {
|
|
276
|
-
name: child.name,
|
|
277
|
-
fullName: joinName(fullName, child.name),
|
|
278
|
-
status: child.mode === "todo" ? "todo" : "fail",
|
|
279
|
-
durationMs: 0,
|
|
280
|
-
error: child.mode === "todo" ? undefined : err,
|
|
281
|
-
};
|
|
282
|
-
children.push(r);
|
|
283
|
-
ctx.flatTests.push(r);
|
|
284
|
-
} else {
|
|
285
|
-
const innerChildren = attributeHookFailure(child, fullName, err, ctx);
|
|
286
|
-
children.push({
|
|
287
|
-
name: child.name,
|
|
288
|
-
fullName: joinName(fullName, child.name),
|
|
289
|
-
children: innerChildren,
|
|
290
|
-
status: "fail",
|
|
291
|
-
durationMs: 0,
|
|
292
|
-
hookErrors: [],
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
return children;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
async function runSuite(
|
|
300
|
-
node: SuiteNode,
|
|
301
|
-
parentFullName: string,
|
|
302
|
-
ctx: RunCtx,
|
|
303
|
-
): Promise<SuiteResult> {
|
|
304
|
-
const fullName = joinName(parentFullName, node.name);
|
|
305
|
-
const start = Date.now();
|
|
306
|
-
|
|
307
|
-
// `.skip` takes precedence over descendant `.only` — matches Vitest.
|
|
308
|
-
// `.todo` likewise.
|
|
309
|
-
const skipEntire =
|
|
310
|
-
node.mode === "skip" ||
|
|
311
|
-
node.mode === "todo" ||
|
|
312
|
-
(ctx.onlyActive && !pathLeadsToOnly(node));
|
|
313
|
-
if (skipEntire) return runSkippedSuite(node, fullName, ctx);
|
|
314
|
-
|
|
315
|
-
const hookErrors: SerializedError[] = [];
|
|
316
|
-
|
|
317
|
-
// beforeAll: when one throws, every descendant test inherits the failure
|
|
318
|
-
// and the children list is replaced with those attributed results.
|
|
319
|
-
const attributed = await runBeforeAllHooks(
|
|
320
|
-
node,
|
|
321
|
-
parentFullName,
|
|
322
|
-
ctx,
|
|
323
|
-
hookErrors,
|
|
324
|
-
);
|
|
325
|
-
const beforeAllFailed = attributed !== null;
|
|
326
|
-
const children: Array<SuiteResult | TestResult> = attributed ?? [];
|
|
327
|
-
|
|
328
|
-
if (!beforeAllFailed) {
|
|
329
|
-
for (const child of node.children) {
|
|
330
|
-
children.push(
|
|
331
|
-
child.kind === "test"
|
|
332
|
-
? await runTest(child, fullName, ctx)
|
|
333
|
-
: await runSuite(child, fullName, ctx),
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
await runAfterAllHooks(node, hookErrors);
|
|
339
|
-
|
|
340
|
-
// Surface afterAll failures as a synthetic test so `totals.fail` reflects
|
|
341
|
-
// them and CI exits nonzero.
|
|
342
|
-
if (!beforeAllFailed && hookErrors.length > 0) {
|
|
343
|
-
const synthetic: TestResult = {
|
|
344
|
-
name: "afterAll",
|
|
345
|
-
fullName: joinName(fullName, "afterAll"),
|
|
346
|
-
status: "fail",
|
|
347
|
-
durationMs: 0,
|
|
348
|
-
error: hookErrors[hookErrors.length - 1],
|
|
349
|
-
};
|
|
350
|
-
children.push(synthetic);
|
|
351
|
-
ctx.flatTests.push(synthetic);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
return {
|
|
355
|
-
name: node.name,
|
|
356
|
-
fullName,
|
|
357
|
-
children,
|
|
358
|
-
status: suiteStatus(children, hookErrors),
|
|
359
|
-
durationMs: Date.now() - start,
|
|
360
|
-
hookErrors,
|
|
361
|
-
};
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/** Build skip/todo results for an entirely-skipped suite (no hooks run). */
|
|
365
|
-
async function runSkippedSuite(
|
|
366
|
-
node: SuiteNode,
|
|
367
|
-
fullName: string,
|
|
368
|
-
ctx: RunCtx,
|
|
369
|
-
): Promise<SuiteResult> {
|
|
370
|
-
const children: Array<SuiteResult | TestResult> = [];
|
|
371
|
-
for (const child of node.children) {
|
|
372
|
-
if (child.kind === "test") {
|
|
373
|
-
const r: TestResult = {
|
|
374
|
-
name: child.name,
|
|
375
|
-
fullName: joinName(fullName, child.name),
|
|
376
|
-
status: child.mode === "todo" ? "todo" : "skip",
|
|
377
|
-
durationMs: 0,
|
|
378
|
-
};
|
|
379
|
-
children.push(r);
|
|
380
|
-
ctx.flatTests.push(r);
|
|
381
|
-
} else {
|
|
382
|
-
children.push(await runSuiteSkip(child, fullName, ctx));
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
return {
|
|
386
|
-
name: node.name,
|
|
387
|
-
fullName,
|
|
388
|
-
children,
|
|
389
|
-
status: "skip",
|
|
390
|
-
durationMs: 0,
|
|
391
|
-
hookErrors: [],
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Run the suite's `beforeAll` hooks. On the first failure, push the serialized
|
|
397
|
-
* error to `hookErrors` and return the descendant tests attributed with that
|
|
398
|
-
* failure; return `null` when all hooks pass.
|
|
399
|
-
*/
|
|
400
|
-
async function runBeforeAllHooks(
|
|
401
|
-
node: SuiteNode,
|
|
402
|
-
parentFullName: string,
|
|
403
|
-
ctx: RunCtx,
|
|
404
|
-
hookErrors: SerializedError[],
|
|
405
|
-
): Promise<Array<SuiteResult | TestResult> | null> {
|
|
406
|
-
for (const h of node.hooks) {
|
|
407
|
-
if (h.type !== "beforeAll") continue;
|
|
408
|
-
try {
|
|
409
|
-
await h.fn();
|
|
410
|
-
} catch (err) {
|
|
411
|
-
const serialized = serializeError(err);
|
|
412
|
-
hookErrors.push(serialized);
|
|
413
|
-
// Attribute the error to every descendant test (direct AND nested).
|
|
414
|
-
return attributeHookFailure(node, parentFullName, serialized, ctx);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
return null;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Run `afterAll` hooks unconditionally so partial setup from a failed beforeAll
|
|
422
|
-
* can be released. Errors are captured into `hookErrors`, not thrown.
|
|
423
|
-
*/
|
|
424
|
-
async function runAfterAllHooks(
|
|
425
|
-
node: SuiteNode,
|
|
426
|
-
hookErrors: SerializedError[],
|
|
427
|
-
): Promise<void> {
|
|
428
|
-
for (const h of node.hooks) {
|
|
429
|
-
if (h.type !== "afterAll") continue;
|
|
430
|
-
try {
|
|
431
|
-
await h.fn();
|
|
432
|
-
} catch (err) {
|
|
433
|
-
hookErrors.push(serializeError(err));
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/** Roll a suite's child statuses + hook errors up into its own status. */
|
|
439
|
-
function suiteStatus(
|
|
440
|
-
children: Array<SuiteResult | TestResult>,
|
|
441
|
-
hookErrors: SerializedError[],
|
|
442
|
-
): "fail" | "skip" | "pass" {
|
|
443
|
-
if (hookErrors.length > 0 || children.some((c) => c.status === "fail")) {
|
|
444
|
-
return "fail";
|
|
445
|
-
}
|
|
446
|
-
if (children.length > 0 && children.every((c) => c.status === "skip")) {
|
|
447
|
-
return "skip";
|
|
448
|
-
}
|
|
449
|
-
return "pass";
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
async function runSuiteSkip(
|
|
453
|
-
node: SuiteNode,
|
|
454
|
-
parentFullName: string,
|
|
455
|
-
ctx: RunCtx,
|
|
456
|
-
): Promise<SuiteResult> {
|
|
457
|
-
const fullName = joinName(parentFullName, node.name);
|
|
458
|
-
const children: Array<SuiteResult | TestResult> = [];
|
|
459
|
-
for (const child of node.children) {
|
|
460
|
-
if (child.kind === "test") {
|
|
461
|
-
const r: TestResult = {
|
|
462
|
-
name: child.name,
|
|
463
|
-
fullName: joinName(fullName, child.name),
|
|
464
|
-
status: child.mode === "todo" ? "todo" : "skip",
|
|
465
|
-
durationMs: 0,
|
|
466
|
-
};
|
|
467
|
-
children.push(r);
|
|
468
|
-
ctx.flatTests.push(r);
|
|
469
|
-
} else {
|
|
470
|
-
children.push(await runSuiteSkip(child, fullName, ctx));
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
return {
|
|
474
|
-
name: node.name,
|
|
475
|
-
fullName,
|
|
476
|
-
children,
|
|
477
|
-
status: "skip",
|
|
478
|
-
durationMs: 0,
|
|
479
|
-
hookErrors: [],
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
export async function executeRoot(
|
|
484
|
-
root: SuiteNode,
|
|
485
|
-
file: string,
|
|
486
|
-
options: ExecuteOptions = {},
|
|
487
|
-
): Promise<FileResult> {
|
|
488
|
-
const start = Date.now();
|
|
489
|
-
const onlyActive = root.children.some(hasOnly);
|
|
490
|
-
const ctx: RunCtx = {
|
|
491
|
-
onlyActive,
|
|
492
|
-
flatTests: [],
|
|
493
|
-
timeoutMs: options.timeoutMs ?? 0,
|
|
494
|
-
};
|
|
495
|
-
const suites: SuiteResult[] = [];
|
|
496
|
-
|
|
497
|
-
// Root-level beforeAll: run once before anything, root-level afterAll:
|
|
498
|
-
// once after everything. Errors attribute to a synthetic test entry so
|
|
499
|
-
// they show up in totals.
|
|
500
|
-
const rootHookErrors: SerializedError[] = [];
|
|
501
|
-
let rootBeforeAllFailed = false;
|
|
502
|
-
for (const h of root.hooks) {
|
|
503
|
-
if (h.type !== "beforeAll") continue;
|
|
504
|
-
try {
|
|
505
|
-
await h.fn();
|
|
506
|
-
} catch (err) {
|
|
507
|
-
const serialized = serializeError(err);
|
|
508
|
-
rootHookErrors.push(serialized);
|
|
509
|
-
const synthetic: TestResult = {
|
|
510
|
-
name: "beforeAll",
|
|
511
|
-
fullName: "beforeAll",
|
|
512
|
-
status: "fail",
|
|
513
|
-
durationMs: 0,
|
|
514
|
-
error: serialized,
|
|
515
|
-
};
|
|
516
|
-
ctx.flatTests.push(synthetic);
|
|
517
|
-
rootBeforeAllFailed = true;
|
|
518
|
-
break;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
if (!rootBeforeAllFailed) {
|
|
523
|
-
for (const child of root.children) {
|
|
524
|
-
if (child.kind === "test") {
|
|
525
|
-
const tr = await runTest(child, "", ctx);
|
|
526
|
-
suites.push({
|
|
527
|
-
name: "",
|
|
528
|
-
fullName: "",
|
|
529
|
-
children: [tr],
|
|
530
|
-
status:
|
|
531
|
-
tr.status === "fail"
|
|
532
|
-
? "fail"
|
|
533
|
-
: tr.status === "pass"
|
|
534
|
-
? "pass"
|
|
535
|
-
: "skip",
|
|
536
|
-
durationMs: tr.durationMs,
|
|
537
|
-
hookErrors: [],
|
|
538
|
-
});
|
|
539
|
-
} else {
|
|
540
|
-
suites.push(await runSuite(child, "", ctx));
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Root afterAll — always try.
|
|
546
|
-
for (const h of root.hooks) {
|
|
547
|
-
if (h.type !== "afterAll") continue;
|
|
548
|
-
try {
|
|
549
|
-
await h.fn();
|
|
550
|
-
} catch (err) {
|
|
551
|
-
const serialized = serializeError(err);
|
|
552
|
-
rootHookErrors.push(serialized);
|
|
553
|
-
const synthetic: TestResult = {
|
|
554
|
-
name: "afterAll",
|
|
555
|
-
fullName: "afterAll",
|
|
556
|
-
status: "fail",
|
|
557
|
-
durationMs: 0,
|
|
558
|
-
error: serialized,
|
|
559
|
-
};
|
|
560
|
-
ctx.flatTests.push(synthetic);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const totals = { pass: 0, fail: 0, skip: 0, todo: 0 };
|
|
565
|
-
for (const t of ctx.flatTests) totals[t.status] += 1;
|
|
566
|
-
return {
|
|
567
|
-
file,
|
|
568
|
-
suites,
|
|
569
|
-
tests: ctx.flatTests,
|
|
570
|
-
totals,
|
|
571
|
-
durationMs: Date.now() - start,
|
|
572
|
-
};
|
|
573
|
-
}
|