proby 0.21.0 → 0.22.0
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/lib/bin.js +10 -3
- package/lib/run.d.ts +4 -1
- package/lib/run.js +377 -77
- package/package.json +6 -6
- package/lib/worker.d.ts +0 -2
- package/lib/worker.js +0 -29
package/lib/bin.js
CHANGED
|
@@ -31,19 +31,26 @@ if (!is.defined(env.try("PROBY_RELAUNCHED"))) {
|
|
|
31
31
|
runtime.exit(0);
|
|
32
32
|
}
|
|
33
33
|
import run from "#run";
|
|
34
|
-
const
|
|
34
|
+
const verbosity_flags = {
|
|
35
|
+
"-v": 1,
|
|
36
|
+
"--verbose": 1,
|
|
37
|
+
"-vv": 2,
|
|
38
|
+
"--very-verbose": 2,
|
|
39
|
+
};
|
|
40
|
+
const verbose = runtime.args.reduce((max, arg) => Math.max(max, verbosity_flags[arg] ?? 0), 0);
|
|
41
|
+
const [file, group] = runtime.args.filter(arg => !(arg in verbosity_flags));
|
|
35
42
|
if (monorepo) {
|
|
36
43
|
for (const repo of await root.join(packages).list({
|
|
37
44
|
filter: info => info.type === "directory",
|
|
38
45
|
})) {
|
|
39
46
|
for (const dir of include) {
|
|
40
|
-
await run(repo.join(dir), repo.name, file, group);
|
|
47
|
+
await run(repo.join(dir), repo.name, file, group, verbose);
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
else {
|
|
45
52
|
for (const dir of include) {
|
|
46
|
-
await run(root.join(dir), undefined, file, group);
|
|
53
|
+
await run(root.join(dir), undefined, file, group, verbose);
|
|
47
54
|
}
|
|
48
55
|
}
|
|
49
56
|
//# sourceMappingURL=bin.js.map
|
package/lib/run.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { FileRef } from "@rcompat/fs";
|
|
2
|
+
export type VerbosityLevel = 0 | 1 | 2;
|
|
3
|
+
export declare function reset_totals(): void;
|
|
4
|
+
export declare function print_summary(): void;
|
|
2
5
|
export default _default;
|
|
3
|
-
declare function _default(root: FileRef, subrepo?: string, target?: string, group?: string): Promise<void>;
|
|
6
|
+
declare function _default(root: FileRef, subrepo?: string, target?: string, group?: string, verbose?: VerbosityLevel): Promise<void>;
|
|
4
7
|
//# sourceMappingURL=run.d.ts.map
|
package/lib/run.js
CHANGED
|
@@ -3,7 +3,6 @@ import cli from "@rcompat/cli";
|
|
|
3
3
|
import fs from "@rcompat/fs";
|
|
4
4
|
import is from "@rcompat/is";
|
|
5
5
|
import repository from "@rcompat/test/repository";
|
|
6
|
-
import { Worker } from "node:worker_threads";
|
|
7
6
|
const extensions = [".spec.ts", ".spec.js"];
|
|
8
7
|
const base_scalars = ["boolean", "number", "string", "symbol"];
|
|
9
8
|
function stringify_scalar(x) {
|
|
@@ -16,8 +15,9 @@ function stringify_scalar(x) {
|
|
|
16
15
|
return x.toString();
|
|
17
16
|
if (is.bigint(x))
|
|
18
17
|
return x.toString() + "n";
|
|
19
|
-
if (is.function(x))
|
|
18
|
+
if (is.function(x)) {
|
|
20
19
|
return `[Function${is.text(x.name) ? `: ${x.name}` : ""}]`;
|
|
20
|
+
}
|
|
21
21
|
}
|
|
22
22
|
function stringify(x) {
|
|
23
23
|
const scalar = stringify_scalar(x);
|
|
@@ -36,101 +36,401 @@ function stringify(x) {
|
|
|
36
36
|
}
|
|
37
37
|
return String(x);
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
39
|
+
function format_duration(ms) {
|
|
40
|
+
return `[${ms.toFixed(2)}ms]`;
|
|
41
|
+
}
|
|
42
|
+
function format_ms(ms) {
|
|
43
|
+
return `${ms.toFixed(2)}ms`;
|
|
44
|
+
}
|
|
45
|
+
function print_captured_output(output, indent = "") {
|
|
46
|
+
for (const entry of output) {
|
|
47
|
+
const label = entry.stream === "stdout" ? "Log" : "Error";
|
|
48
|
+
const lines = entry.text.split("\n").filter(line => line.length > 0);
|
|
49
|
+
if (lines.length > 0)
|
|
50
|
+
cli.print("\n");
|
|
51
|
+
for (const line of lines)
|
|
52
|
+
cli.print(`${indent}${label}: ${line}\n`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function print_expected_actual(expected, actual) {
|
|
56
|
+
const expected_beginning = cli.fg.dim("Expected");
|
|
57
|
+
const expected_end = cli.fg.dim(stringify(expected));
|
|
58
|
+
const actual_beginning = cli.fg.red("Actual");
|
|
59
|
+
const actual_end = cli.fg.red(stringify(actual));
|
|
60
|
+
cli.print(`${expected_beginning} ${expected_end}\n`);
|
|
61
|
+
cli.print(`${actual_beginning} ${actual_end}\n`);
|
|
62
|
+
}
|
|
63
|
+
// Patches process.stdout/stderr and console.* to buffer output, runs the
|
|
64
|
+
// async iterator's next step, then restores. This lets us attribute each
|
|
65
|
+
// test's console output to that test rather than letting it leak to the top.
|
|
66
|
+
async function next_test_with_std_output(iter) {
|
|
67
|
+
const output = [];
|
|
68
|
+
const orig_stdout = process.stdout.write.bind(process.stdout);
|
|
69
|
+
const orig_stderr = process.stderr.write.bind(process.stderr);
|
|
70
|
+
const orig_log = console.log;
|
|
71
|
+
const orig_info = console.info;
|
|
72
|
+
const orig_warn = console.warn;
|
|
73
|
+
const orig_error = console.error;
|
|
74
|
+
const push = (stream, args) => {
|
|
75
|
+
output.push({ stream, text: args.map(String).join(" ") });
|
|
76
|
+
};
|
|
77
|
+
process.stdout.write = (chunk) => {
|
|
78
|
+
push("stdout", [chunk]);
|
|
79
|
+
return true;
|
|
80
|
+
};
|
|
81
|
+
process.stderr.write = (chunk) => {
|
|
82
|
+
push("stderr", [chunk]);
|
|
83
|
+
return true;
|
|
84
|
+
};
|
|
85
|
+
console.log = (...args) => push("stdout", args);
|
|
86
|
+
console.info = (...args) => push("stdout", args);
|
|
87
|
+
console.warn = (...args) => push("stderr", args);
|
|
88
|
+
console.error = (...args) => push("stderr", args);
|
|
89
|
+
let result;
|
|
90
|
+
try {
|
|
91
|
+
result = await iter.next();
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
process.stdout.write = orig_stdout;
|
|
95
|
+
process.stderr.write = orig_stderr;
|
|
96
|
+
console.log = orig_log;
|
|
97
|
+
console.info = orig_info;
|
|
98
|
+
console.warn = orig_warn;
|
|
99
|
+
console.error = orig_error;
|
|
100
|
+
}
|
|
101
|
+
return { done: result.done ?? false, value: result.value, output };
|
|
102
|
+
}
|
|
103
|
+
const INDENT = " "; // two spaces per nesting level
|
|
104
|
+
function indent_at(depth) {
|
|
105
|
+
return INDENT.repeat(depth);
|
|
106
|
+
}
|
|
107
|
+
const grand_totals = {
|
|
108
|
+
passed: 0,
|
|
109
|
+
failed: 0,
|
|
110
|
+
duration: 0,
|
|
111
|
+
};
|
|
112
|
+
let summary_printed = false;
|
|
113
|
+
export function reset_totals() {
|
|
114
|
+
grand_totals.passed = 0;
|
|
115
|
+
grand_totals.failed = 0;
|
|
116
|
+
grand_totals.duration = 0;
|
|
117
|
+
summary_printed = false;
|
|
118
|
+
}
|
|
119
|
+
function print_totals_line(passed, failed, duration) {
|
|
120
|
+
const passed_color = failed > 0 ? cli.fg.dim : cli.fg.green;
|
|
121
|
+
const failed_color = failed > 0 ? cli.fg.red : cli.fg.dim;
|
|
122
|
+
const passed_text = passed_color(passed + " pass");
|
|
123
|
+
const failed_text = failed_color(failed + " fail");
|
|
124
|
+
const dim = cli.fg.dim;
|
|
125
|
+
const duration_text = dim(format_ms(duration));
|
|
126
|
+
const dim_comma = dim(",");
|
|
127
|
+
const passed_with_comma = `${passed_text}${dim_comma}`;
|
|
128
|
+
const failed_with_comma = `${failed_text}${dim_comma}`;
|
|
129
|
+
const passed_and_failed = `${passed_with_comma} ${failed_with_comma}`;
|
|
130
|
+
cli.print(`${dim("[")}${passed_and_failed} ${duration_text}${dim("]")}`);
|
|
131
|
+
}
|
|
132
|
+
// Used for the per-file summary line at plain verbose (-v) level, where we
|
|
133
|
+
// only want to show timing, not pass/fail counts (those are already
|
|
134
|
+
// visible in the package/summary totals).
|
|
135
|
+
function print_duration_only(duration) {
|
|
136
|
+
const dim = cli.fg.dim;
|
|
137
|
+
cli.print(`${dim("[")}${dim(format_ms(duration))}${dim("]")}`);
|
|
138
|
+
}
|
|
139
|
+
// only the first call actually prints anything.
|
|
140
|
+
export function print_summary() {
|
|
141
|
+
if (summary_printed)
|
|
142
|
+
return;
|
|
143
|
+
if (grand_totals.passed + grand_totals.failed === 0)
|
|
144
|
+
return;
|
|
145
|
+
summary_printed = true;
|
|
146
|
+
cli.print("\n");
|
|
147
|
+
const passed_color = grand_totals.failed > 0 ? cli.fg.dim : cli.fg.green;
|
|
148
|
+
const failed_color = grand_totals.failed > 0 ? cli.fg.red : cli.fg.dim;
|
|
149
|
+
const total_color = grand_totals.failed > 0 ? cli.bg.red : cli.bg.green;
|
|
150
|
+
const formatted_duration = format_ms(grand_totals.duration);
|
|
151
|
+
const total_text = total_color(" SUMMARY ");
|
|
152
|
+
const passed = passed_color("Passed: " + grand_totals.passed);
|
|
153
|
+
const failed = failed_color("Failed: " + grand_totals.failed);
|
|
154
|
+
const duration = cli.fg.dim("Duration: " + formatted_duration);
|
|
155
|
+
cli.print(`${total_text}\n${passed}\n${failed}\n${duration}\n`);
|
|
156
|
+
cli.print("\n");
|
|
157
|
+
cli.print("\n");
|
|
158
|
+
}
|
|
159
|
+
// Guarantees the grand total is printed exactly once, after every subrepo
|
|
160
|
+
// has been processed, with no dependency on the caller remembering to call
|
|
161
|
+
// print_summary() itself.
|
|
162
|
+
process.on("exit", () => {
|
|
163
|
+
print_summary();
|
|
164
|
+
});
|
|
165
|
+
function group_failed(node) {
|
|
166
|
+
return node.tests.some(t => t.results.some(r => !r.passed))
|
|
167
|
+
|| node.children.some(group_failed);
|
|
168
|
+
}
|
|
169
|
+
async function process_file(file, group) {
|
|
170
|
+
const result = {
|
|
171
|
+
file,
|
|
172
|
+
passed: 0,
|
|
173
|
+
failed: 0,
|
|
174
|
+
duration: 0,
|
|
175
|
+
items: [],
|
|
176
|
+
};
|
|
177
|
+
const env_file = await file.sibling(file.name.replace(/\.spec\.(ts|js)$/, ".env.ts")).or(() => null);
|
|
178
|
+
const mock_file = await file.sibling(file.name.replace(/\.spec\.(ts|js)$/, ".mock.$1")).or(() => null);
|
|
179
|
+
let env_module;
|
|
180
|
+
if (!is.null(env_file)) {
|
|
181
|
+
env_module = assert.shape((await import(env_file.path)).default, {
|
|
182
|
+
globals: "function",
|
|
183
|
+
setup: "function?",
|
|
184
|
+
teardown: "function?",
|
|
54
185
|
});
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
186
|
+
}
|
|
187
|
+
repository.suite(file);
|
|
188
|
+
const suite = repository.next().next().value;
|
|
189
|
+
// Captured inside the try (where `context` is inferred from setup),
|
|
190
|
+
// invoked from finally so cleanup runs even when suite iteration throws.
|
|
191
|
+
let cleanup;
|
|
192
|
+
try {
|
|
193
|
+
if (!is.null(mock_file))
|
|
194
|
+
await mock_file.import();
|
|
195
|
+
await file.import();
|
|
196
|
+
const context = await env_module?.setup?.();
|
|
197
|
+
// Apply the env's globals to globalThis for the duration of this suite,
|
|
198
|
+
// then restore the previous values afterwards. This replaces the free
|
|
199
|
+
// process-level isolation the old per-spec worker provided; in-process,
|
|
200
|
+
// all spec files share one globalThis, so without restore the globals
|
|
201
|
+
// would leak into subsequent spec files (order-dependent failures).
|
|
202
|
+
const globals = env_module?.globals(context);
|
|
203
|
+
const applied_globals = is.defined(globals)
|
|
204
|
+
? Object.keys(globals).map(k => [k, globalThis[k]])
|
|
205
|
+
: undefined;
|
|
206
|
+
if (is.defined(globals)) {
|
|
207
|
+
Object.assign(globalThis, globals);
|
|
208
|
+
}
|
|
209
|
+
cleanup = async () => {
|
|
210
|
+
if (is.defined(applied_globals)) {
|
|
211
|
+
for (const [k, v] of applied_globals) {
|
|
212
|
+
if (v === undefined) {
|
|
213
|
+
delete globalThis[k];
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
globalThis[k] = v;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
59
219
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
220
|
+
await env_module?.teardown?.(context);
|
|
221
|
+
};
|
|
222
|
+
// Each entry tracks a group name, its own tests, and any child groups
|
|
223
|
+
// that were flushed into it from the stack below.
|
|
224
|
+
const stack = [];
|
|
225
|
+
function flush_top() {
|
|
226
|
+
const node = stack.pop();
|
|
227
|
+
if (stack.length > 0) {
|
|
228
|
+
// Attach this node as a child of the new top.
|
|
229
|
+
stack.at(-1).children.push(node);
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
// Root group — record it in arrival order.
|
|
233
|
+
result.items.push({ kind: "group", node });
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const iter = suite.run()[Symbol.asyncIterator]();
|
|
237
|
+
let doneIterating = false;
|
|
238
|
+
while (is.falsy(doneIterating)) {
|
|
239
|
+
const { done, value, output } = await next_test_with_std_output(iter);
|
|
240
|
+
doneIterating = is.truthy(done) || !is.defined(value);
|
|
241
|
+
if (doneIterating)
|
|
242
|
+
break;
|
|
243
|
+
const { test, duration } = value;
|
|
244
|
+
if (is.defined(group) && test.group !== group)
|
|
245
|
+
continue;
|
|
246
|
+
const testEntry = {
|
|
247
|
+
name: test.name,
|
|
248
|
+
group: test.group,
|
|
249
|
+
duration,
|
|
250
|
+
output,
|
|
251
|
+
results: test.results,
|
|
252
|
+
};
|
|
253
|
+
// Counted per test.case, not per assert — a test.case with several
|
|
254
|
+
// asserts still contributes exactly one pass or one fail.
|
|
255
|
+
const test_failed = testEntry.results.some(r => !r.passed);
|
|
256
|
+
if (test_failed) {
|
|
257
|
+
result.failed++;
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
result.passed++;
|
|
261
|
+
}
|
|
262
|
+
result.duration += duration;
|
|
263
|
+
if (is.defined(test.group)) {
|
|
264
|
+
if (stack.length === 0 || stack.at(-1).name !== test.group) {
|
|
265
|
+
stack.push({ name: test.group, tests: [], children: [] });
|
|
66
266
|
}
|
|
267
|
+
stack.at(-1).tests.push(testEntry);
|
|
268
|
+
continue;
|
|
67
269
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
270
|
+
// Ungrouped test — flush everything on the stack first, to preserve
|
|
271
|
+
// arrival order relative to any group that came before it.
|
|
272
|
+
while (stack.length > 0) {
|
|
273
|
+
flush_top();
|
|
274
|
+
}
|
|
275
|
+
result.items.push({ kind: "test", test: testEntry });
|
|
276
|
+
}
|
|
277
|
+
// Flush any remaining groups at end of file.
|
|
278
|
+
while (stack.length > 0) {
|
|
279
|
+
flush_top();
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
finally {
|
|
283
|
+
// Restore any globals we applied, then tear down the env context.
|
|
284
|
+
// Runs even when suite iteration throws, so globals never leak across
|
|
285
|
+
// spec files and teardown always pairs with setup. `repository.reset()`
|
|
286
|
+
// is nested so a throwing teardown can't skip it.
|
|
287
|
+
try {
|
|
288
|
+
await cleanup?.();
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
repository.reset();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
function print_group(node, depth, show_all) {
|
|
297
|
+
const failed = group_failed(node);
|
|
298
|
+
const isPassingEntireGroup = !show_all && !failed;
|
|
299
|
+
if (isPassingEntireGroup)
|
|
300
|
+
return;
|
|
301
|
+
const group_indent = indent_at(depth + 1);
|
|
302
|
+
const test_indent = indent_at(depth + 2);
|
|
303
|
+
const icon = failed ? cli.fg.red("✗") : cli.fg.green("✓");
|
|
304
|
+
const total_duration = node.tests.reduce((n, t) => n + t.duration, 0) +
|
|
305
|
+
node.children.reduce((n, c) => n + c.tests.reduce((m, t) => m + t.duration, 0), 0);
|
|
306
|
+
const group_label = cli.fg.dim(node.name);
|
|
307
|
+
const group_time = cli.fg.dim(format_duration(total_duration));
|
|
308
|
+
const group_label_after_check = failed
|
|
309
|
+
? `${group_label} ${group_time}`
|
|
310
|
+
: `${cli.fg.dim(group_label)} ${group_time}`;
|
|
311
|
+
cli.print(`${group_indent}${icon} ${group_label_after_check}\n`);
|
|
312
|
+
// Print tests that belong to this group come first (before any child
|
|
313
|
+
// group), then child groups are printed at depth+1. Each test.case is
|
|
314
|
+
// printed as a single line, regardless of how many asserts it contains.
|
|
315
|
+
for (const test of node.tests) {
|
|
316
|
+
const time = format_duration(test.duration);
|
|
317
|
+
const test_failed = test.results.some(r => !r.passed);
|
|
318
|
+
if (test_failed) {
|
|
319
|
+
const beginning = `${test_indent}${cli.fg.red("✗")}`;
|
|
320
|
+
cli.print(`${beginning} ${test.name} ${cli.fg.dim(time)}\n`);
|
|
321
|
+
print_captured_output(test.output);
|
|
322
|
+
cli.print("\n");
|
|
323
|
+
for (const r of test.results) {
|
|
324
|
+
if (!r.passed) {
|
|
325
|
+
print_expected_actual(r.expected, r.actual);
|
|
326
|
+
cli.print("\n");
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
if (!show_all)
|
|
332
|
+
continue; // hide passing lines unless very verbose
|
|
333
|
+
const beginning = `${test_indent}${cli.fg.green("✓")}`;
|
|
334
|
+
cli.print(`${beginning} ${cli.fg.dim(`${test.name} ${time}\n`)}`);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
for (const child of node.children) {
|
|
338
|
+
if (!show_all && !group_failed(child))
|
|
339
|
+
continue;
|
|
340
|
+
print_group(child, depth + 1, show_all);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function print_ungrouped_test(test, show_all) {
|
|
344
|
+
const time = format_duration(test.duration);
|
|
345
|
+
const test_failed = test.results.some(r => !r.passed);
|
|
346
|
+
if (!show_all && !test_failed)
|
|
347
|
+
return;
|
|
348
|
+
if (test_failed) {
|
|
349
|
+
cli.print(` ${cli.fg.red("✗")} ${test.name} ${time}\n`);
|
|
350
|
+
print_captured_output(test.output, INDENT);
|
|
351
|
+
cli.print("\n");
|
|
352
|
+
for (const r of test.results) {
|
|
353
|
+
if (!r.passed) {
|
|
354
|
+
print_expected_actual(r.expected, r.actual);
|
|
355
|
+
cli.print("\n");
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
const test_name_and_time = cli.fg.dim(test.name + " " + time);
|
|
361
|
+
cli.print(` ${cli.fg.green("✓")} ${test_name_and_time}\n`);
|
|
362
|
+
}
|
|
75
363
|
}
|
|
76
|
-
|
|
364
|
+
function render_file_items(items, show_all) {
|
|
365
|
+
for (const item of items) {
|
|
366
|
+
if (item.kind === "group")
|
|
367
|
+
print_group(item.node, 0, show_all);
|
|
368
|
+
else
|
|
369
|
+
print_ungrouped_test(item.test, show_all);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
export default async (root, subrepo, target, group, verbose = 0) => {
|
|
373
|
+
const show_all = verbose >= 2;
|
|
77
374
|
const resolved = is.defined(target) ? fs.resolve(target).path : undefined;
|
|
78
375
|
const files = await root.list({
|
|
79
376
|
recursive: true,
|
|
80
377
|
filter: info => {
|
|
81
378
|
const path = info.path;
|
|
82
|
-
if (is.undefined(resolved))
|
|
379
|
+
if (is.undefined(resolved)) {
|
|
83
380
|
return extensions.some(e => path.endsWith(e));
|
|
84
|
-
|
|
381
|
+
}
|
|
382
|
+
if (extensions.some(e => resolved.endsWith(e))) {
|
|
85
383
|
return path.endsWith(resolved);
|
|
86
|
-
|
|
384
|
+
}
|
|
385
|
+
return info.path.startsWith(resolved) &&
|
|
386
|
+
extensions.some(e => path.endsWith(e));
|
|
87
387
|
},
|
|
88
388
|
});
|
|
89
389
|
if (files.length === 0)
|
|
90
390
|
return;
|
|
91
|
-
|
|
92
|
-
|
|
391
|
+
// Phase 1: run every file's tests and collect results — nothing is
|
|
392
|
+
// printed yet, so we can know each file's (and the subrepo's) totals
|
|
393
|
+
// before printing anything for it.
|
|
394
|
+
const file_results = [];
|
|
93
395
|
for (const file of files) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
396
|
+
file_results.push(await process_file(file, group));
|
|
397
|
+
}
|
|
398
|
+
const subrepo_passed = file_results.reduce((n, f) => n + f.passed, 0);
|
|
399
|
+
const subrepo_failed = file_results.reduce((n, f) => n + f.failed, 0);
|
|
400
|
+
const subrepo_duration = file_results.reduce((n, f) => n + f.duration, 0);
|
|
401
|
+
grand_totals.passed += subrepo_passed;
|
|
402
|
+
grand_totals.failed += subrepo_failed;
|
|
403
|
+
grand_totals.duration += subrepo_duration;
|
|
404
|
+
// Phase 2: render. Subrepo header + totals only apply when this call is
|
|
405
|
+
// actually part of a monorepo (i.e. `subrepo` was passed in) — otherwise
|
|
406
|
+
// it would just repeat the grand total.
|
|
407
|
+
const show_subrepo = is.defined(subrepo) &&
|
|
408
|
+
(verbose >= 1 || subrepo_failed > 0);
|
|
409
|
+
if (show_subrepo) {
|
|
410
|
+
cli.print("\n");
|
|
411
|
+
cli.print(`${cli.bg.blue(" PACKAGE ")} ${subrepo} `);
|
|
412
|
+
if (verbose >= 1) {
|
|
413
|
+
print_totals_line(subrepo_passed, subrepo_failed, subrepo_duration);
|
|
98
414
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
cli.print(cli.fg.green("o"));
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
failed.push([test, result]);
|
|
116
|
-
cli.print(cli.fg.red("x"));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
await suite.end();
|
|
121
|
-
if (failed.length > 0) {
|
|
122
|
-
cli.print("\n");
|
|
123
|
-
for (const [test, result] of failed) {
|
|
124
|
-
cli.print(`${suite.file.debase(root)} ${cli.fg.red(test.name)} \n`);
|
|
125
|
-
cli.print(` expected ${stringify(result.expected)}\n`);
|
|
126
|
-
cli.print(` actual ${stringify(result.actual)}\n`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
415
|
+
cli.print("\n\n");
|
|
416
|
+
}
|
|
417
|
+
for (const result of file_results) {
|
|
418
|
+
const show_file = verbose >= 1 || result.failed > 0;
|
|
419
|
+
if (!show_file)
|
|
420
|
+
continue;
|
|
421
|
+
cli.print(cli.fg.gray(`${result.file.debase(root)} `));
|
|
422
|
+
// At plain verbose (-v) we only show timing on the file line — pass/
|
|
423
|
+
// fail counts are already visible in the package/summary totals. At
|
|
424
|
+
// very verbose (-vv) we show the full breakdown, since individual
|
|
425
|
+
// test.case results are printed underneath.
|
|
426
|
+
if (show_all) {
|
|
427
|
+
print_totals_line(result.passed, result.failed, result.duration);
|
|
129
428
|
}
|
|
130
|
-
|
|
131
|
-
|
|
429
|
+
else if (verbose >= 1) {
|
|
430
|
+
print_duration_only(result.duration);
|
|
132
431
|
}
|
|
432
|
+
cli.print("\n");
|
|
433
|
+
render_file_items(result.items, show_all);
|
|
133
434
|
}
|
|
134
|
-
cli.print("\n");
|
|
135
435
|
};
|
|
136
436
|
//# sourceMappingURL=run.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proby",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "Standard library test runner",
|
|
5
5
|
"bugs": "https://github.com/rcompat/rcompat/issues",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,18 +16,18 @@
|
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@rcompat/assert": "^0.14.0",
|
|
19
|
-
"@rcompat/cli": "^0.25.0",
|
|
20
|
-
"@rcompat/fs": "^0.35.1",
|
|
21
19
|
"@rcompat/env": "^0.23.0",
|
|
20
|
+
"@rcompat/fs": "^0.35.1",
|
|
21
|
+
"@rcompat/cli": "^0.25.0",
|
|
22
22
|
"@rcompat/io": "^0.11.0",
|
|
23
|
-
"@rcompat/
|
|
24
|
-
"@rcompat/
|
|
23
|
+
"@rcompat/runtime": "^0.17.0",
|
|
24
|
+
"@rcompat/is": "^0.12.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@rcompat/type": "^0.18.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
|
-
"@rcompat/test": "^0.
|
|
30
|
+
"@rcompat/test": "^0.20.0"
|
|
31
31
|
},
|
|
32
32
|
"peerDependenciesMeta": {
|
|
33
33
|
"@rcompat/test": {
|
package/lib/worker.d.ts
DELETED
package/lib/worker.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import assert from "@rcompat/assert";
|
|
2
|
-
import fs from "@rcompat/fs";
|
|
3
|
-
import repository from "@rcompat/test/repository";
|
|
4
|
-
import { parentPort, workerData } from "node:worker_threads";
|
|
5
|
-
const { spec, env, context } = workerData;
|
|
6
|
-
const env_module = assert.shape((await import(env)).default, {
|
|
7
|
-
globals: "function",
|
|
8
|
-
setup: "function?",
|
|
9
|
-
teardown: "function?",
|
|
10
|
-
});
|
|
11
|
-
Object.assign(globalThis, env_module.globals(context));
|
|
12
|
-
repository.suite(fs.ref(spec));
|
|
13
|
-
await import(spec);
|
|
14
|
-
const results = [];
|
|
15
|
-
for (const suite of repository.next()) {
|
|
16
|
-
for await (const test of suite.run()) {
|
|
17
|
-
for (const result of test.results) {
|
|
18
|
-
results.push({
|
|
19
|
-
name: test.name,
|
|
20
|
-
passed: result.passed,
|
|
21
|
-
expected: result.expected,
|
|
22
|
-
actual: result.actual,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
await suite.end();
|
|
27
|
-
}
|
|
28
|
-
parentPort.postMessage({ results });
|
|
29
|
-
//# sourceMappingURL=worker.js.map
|