@empiricalrun/playwright-utils 0.58.3 → 0.58.5
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/CHANGELOG.md +20 -0
- package/dist/generated/api-worker-openapi.d.ts +98 -88
- package/dist/generated/api-worker-openapi.d.ts.map +1 -1
- package/dist/reporter/empirical-reporter.d.ts.map +1 -1
- package/dist/reporter/empirical-reporter.js +9 -0
- package/dist/reporter/harness.d.ts +4 -0
- package/dist/reporter/harness.d.ts.map +1 -1
- package/dist/reporter/harness.js +20 -8
- package/dist/reporter/ibr-utils.d.ts +3 -0
- package/dist/reporter/ibr-utils.d.ts.map +1 -1
- package/dist/reporter/ibr-utils.js +25 -18
- package/dist/reporter/incremental-blob-reporter.d.ts +1 -0
- package/dist/reporter/incremental-blob-reporter.d.ts.map +1 -1
- package/dist/reporter/incremental-blob-reporter.js +5 -4
- package/dist/reporter/lifecycle-events.d.ts +15 -5
- package/dist/reporter/lifecycle-events.d.ts.map +1 -1
- package/dist/reporter/lifecycle-events.js +91 -64
- package/dist/reporter/parity-utils.d.ts +21 -0
- package/dist/reporter/parity-utils.d.ts.map +1 -1
- package/dist/reporter/parity-utils.js +73 -1
- package/dist/reporter/uploader.d.ts.map +1 -1
- package/dist/reporter/uploader.js +22 -1
- package/package.json +4 -4
- package/upstream-oracle/README.md +36 -7
- package/upstream-oracle/ibr-adapter.js +60 -5
- package/upstream-oracle/run.mjs +288 -47
package/upstream-oracle/run.mjs
CHANGED
|
@@ -9,15 +9,17 @@
|
|
|
9
9
|
* format. The fixture patch (intermediate-mode-ibr.patch) lets
|
|
10
10
|
* PWTEST_IBR_ADAPTER swap IBR in as that reporter.
|
|
11
11
|
*
|
|
12
|
-
* Both modes run the same
|
|
13
|
-
*
|
|
14
|
-
* until IBR reaches parity
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* Both modes run the same spec files; environment noise cancels in the
|
|
13
|
+
* baseline diff. Any IBR-only failure fails the run — the job stays red
|
|
14
|
+
* until IBR reaches parity on these specs. It staying green is the signal
|
|
15
|
+
* that the built-in blob reporter is safe to drop. The one carve-out is
|
|
16
|
+
* EXPECTED_IBR_ONLY below: failures caused by injecting an external reporter
|
|
17
|
+
* rather than by the blob format, each with the reason it cannot pass, and
|
|
18
|
+
* each policed by a staleness check so the list cannot rot.
|
|
17
19
|
*
|
|
18
20
|
* Usage:
|
|
19
21
|
* node run.mjs --pw-repo <upstream clone at the pinned tag>
|
|
20
|
-
* [--specs basic.spec.ts,stdio.spec.ts] [--workers N]
|
|
22
|
+
* [--specs basic.spec.ts,stdio.spec.ts] [--workers N] [--shard i/n]
|
|
21
23
|
*
|
|
22
24
|
* The clone must be bootstrapped (npm ci + npm run build). This script
|
|
23
25
|
* (re)applies the fixture patch idempotently on every run.
|
|
@@ -31,18 +33,111 @@ import { fileURLToPath } from "node:url";
|
|
|
31
33
|
|
|
32
34
|
const ORACLE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
33
35
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
// The default sweep is EVERY spec in upstream's tests/playwright-test —
|
|
37
|
+
// IBR is meant to become the default blob reporter, so it has to stand the
|
|
38
|
+
// whole runner suite. Only structurally out-of-scope specs are excluded,
|
|
39
|
+
// each with its reason printed at startup (no silent caps). First matching
|
|
40
|
+
// rule wins.
|
|
41
|
+
const EXCLUSION_RULES = [
|
|
42
|
+
[
|
|
43
|
+
(name) => name.startsWith("playwright.ct-"),
|
|
44
|
+
"component-testing build; no runner-report signal",
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
(name) => name.startsWith("playwright."),
|
|
48
|
+
"drives real browsers (not installed in the oracle env)",
|
|
49
|
+
],
|
|
50
|
+
[
|
|
51
|
+
(name) => name.startsWith("ui-mode-"),
|
|
52
|
+
"UI mode; needs trace viewer + browsers",
|
|
53
|
+
],
|
|
54
|
+
[
|
|
55
|
+
(name) =>
|
|
56
|
+
name === "to-have-screenshot.spec.ts" ||
|
|
57
|
+
name === "aria-snapshot-file.spec.ts" ||
|
|
58
|
+
name === "update-aria-snapshot.spec.ts",
|
|
59
|
+
"browser screenshots/aria snapshots",
|
|
60
|
+
],
|
|
61
|
+
[
|
|
62
|
+
(name) => name === "types.spec.ts" || name === "types-2.spec.ts",
|
|
63
|
+
"tsc-only; exercises no runner report",
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
(name) => name === "reporter-html.spec.ts",
|
|
67
|
+
"browser-driven HTML-report UI tests; no blob-format signal (merge " +
|
|
68
|
+
"semantics are covered by reporter-blob.spec.ts) and they busy-loop " +
|
|
69
|
+
"workers under load, wedging the sweep",
|
|
70
|
+
],
|
|
71
|
+
// The two specs below are SLOW, not wedged — measured, they make steady
|
|
72
|
+
// progress and simply do not fit in SPEC_TIMEOUT_MS. The oracle runs one
|
|
73
|
+
// spec per invocation and Playwright keeps a file's tests on a single
|
|
74
|
+
// worker (fullyParallel defaults to false), so a long file runs strictly
|
|
75
|
+
// serially however many --workers it is given. Both were already
|
|
76
|
+
// contributing no signal, just a 10-minute timeout each.
|
|
77
|
+
[
|
|
78
|
+
(name) => name === "only-changed.spec.ts",
|
|
79
|
+
"git-based run selection (--only-changed), not report format; 13 tests " +
|
|
80
|
+
"that each shell out to git take ~13min serially here",
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
(name) => name === "web-server.spec.ts",
|
|
84
|
+
"webServer config and lifecycle, not report format; 39 tests that each " +
|
|
85
|
+
"boot and tear down a server take longer still",
|
|
86
|
+
],
|
|
44
87
|
];
|
|
45
88
|
|
|
89
|
+
// Upstream tests that fail through IBR for reasons OUTSIDE the blob format —
|
|
90
|
+
// artifacts of injecting an external reporter into upstream's harness, not
|
|
91
|
+
// gaps in what IBR writes. They are reported and subtracted from the failure
|
|
92
|
+
// count instead of turning the job red.
|
|
93
|
+
//
|
|
94
|
+
// Kept honest by the staleness check at the end: an entry whose spec ran and
|
|
95
|
+
// did NOT fail is reported and fails the run, so a fixed test cannot sit here
|
|
96
|
+
// pretending to be unfixable. Add an entry only with the reason it can never
|
|
97
|
+
// pass; anything that could be fixed belongs in the failure list.
|
|
98
|
+
const EXPECTED_IBR_ONLY = [
|
|
99
|
+
{
|
|
100
|
+
key:
|
|
101
|
+
"cache.spec.ts:53 › should automatically clean cached versions of a " +
|
|
102
|
+
"changed file",
|
|
103
|
+
reason:
|
|
104
|
+
"asserts the transform cache holds exactly one directory; loading IBR " +
|
|
105
|
+
"from a path runs its bundle through the same transform pipeline, so " +
|
|
106
|
+
"the cache gains a directory per module (32 observed). The built-in " +
|
|
107
|
+
"blob reporter ships inside Playwright and never enters that cache.",
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
/** Spec file name out of a failure key ("cache.spec.ts:53 › title"). */
|
|
112
|
+
function specOfKey(key) {
|
|
113
|
+
return key.slice(0, key.indexOf(":"));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Tests skipped by title via --grep-invert. Reserved for tests that WEDGE
|
|
117
|
+
// the sweep — a busy-looped worker no per-test timeout can interrupt — so
|
|
118
|
+
// exclusion is surgical (exact title match) and every entry carries the
|
|
119
|
+
// observed reason. Everything else in the spec still runs.
|
|
120
|
+
const HANGING_TESTS = [];
|
|
121
|
+
|
|
122
|
+
function discoverSpecs(pwRepo) {
|
|
123
|
+
const specDir = path.join(pwRepo, "tests", "playwright-test");
|
|
124
|
+
const all = fs
|
|
125
|
+
.readdirSync(specDir)
|
|
126
|
+
.filter((f) => f.endsWith(".spec.ts"))
|
|
127
|
+
.sort();
|
|
128
|
+
const included = [];
|
|
129
|
+
const excluded = [];
|
|
130
|
+
for (const name of all) {
|
|
131
|
+
const rule = EXCLUSION_RULES.find(([match]) => match(name));
|
|
132
|
+
if (rule) {
|
|
133
|
+
excluded.push([name, rule[1]]);
|
|
134
|
+
} else {
|
|
135
|
+
included.push(name);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { included, excluded };
|
|
139
|
+
}
|
|
140
|
+
|
|
46
141
|
const ADAPTER_PATH = path.join(ORACLE_DIR, "ibr-adapter.js");
|
|
47
142
|
const PATCH_PATH = path.join(ORACLE_DIR, "intermediate-mode-ibr.patch");
|
|
48
143
|
const DEFAULT_IBR_DIST = path.join(
|
|
@@ -51,7 +146,7 @@ const DEFAULT_IBR_DIST = path.join(
|
|
|
51
146
|
);
|
|
52
147
|
|
|
53
148
|
function parseArgs(argv) {
|
|
54
|
-
const args = { specs:
|
|
149
|
+
const args = { specs: null, workers: 4, shard: null };
|
|
55
150
|
// Normalize --flag=value into --flag value.
|
|
56
151
|
argv = argv.flatMap((a) =>
|
|
57
152
|
a.startsWith("--") && a.includes("=")
|
|
@@ -66,6 +161,15 @@ function parseArgs(argv) {
|
|
|
66
161
|
args.specs = argv[++i].split(",").filter(Boolean);
|
|
67
162
|
} else if (arg === "--workers") {
|
|
68
163
|
args.workers = Number(argv[++i]);
|
|
164
|
+
} else if (arg === "--shard") {
|
|
165
|
+
const match = /^(\d+)\/(\d+)$/.exec(argv[++i] ?? "");
|
|
166
|
+
if (!match) {
|
|
167
|
+
throw new Error("--shard expects i/n (e.g. --shard 2/4)");
|
|
168
|
+
}
|
|
169
|
+
args.shard = { index: Number(match[1]), total: Number(match[2]) };
|
|
170
|
+
if (args.shard.index < 1 || args.shard.index > args.shard.total) {
|
|
171
|
+
throw new Error(`--shard index out of range: ${argv[i]}`);
|
|
172
|
+
}
|
|
69
173
|
} else {
|
|
70
174
|
throw new Error(`Unknown argument: ${arg}`);
|
|
71
175
|
}
|
|
@@ -134,7 +238,19 @@ async function applyFixturePatch(pwRepo) {
|
|
|
134
238
|
}
|
|
135
239
|
}
|
|
136
240
|
|
|
137
|
-
|
|
241
|
+
// Hard cap per spec-mode run (milliseconds — Playwright's --global-timeout
|
|
242
|
+
// unit). Some upstream tests can busy-loop a worker under load (sync string
|
|
243
|
+
// processing that no per-test timeout can interrupt — the timeouts run on
|
|
244
|
+
// the same blocked event loop); which test wedges is timing-dependent, so
|
|
245
|
+
// title exclusion cannot converge. Playwright's global timeout fires from
|
|
246
|
+
// the main process and SIGKILLs through busy loops. Each spec runs as its
|
|
247
|
+
// own bounded invocation, so a wedge costs one spec's cap, names the spec,
|
|
248
|
+
// and the sweep continues — hangs become inventory instead of aborting the
|
|
249
|
+
// whole mode.
|
|
250
|
+
const SPEC_TIMEOUT_MS = 10 * 60 * 1_000;
|
|
251
|
+
|
|
252
|
+
async function runMode({ pwRepo, spec, workers, mode, ibrDist }) {
|
|
253
|
+
const startedAt = Date.now();
|
|
138
254
|
const jsonOut = path.join(
|
|
139
255
|
fs.mkdtempSync(path.join(os.tmpdir(), "ibr-oracle-")),
|
|
140
256
|
`${mode}.json`,
|
|
@@ -149,32 +265,68 @@ async function runMode({ pwRepo, specs, workers, mode, ibrDist }) {
|
|
|
149
265
|
env.PWTEST_IBR_ADAPTER = ADAPTER_PATH;
|
|
150
266
|
env.PWTEST_IBR_DIST = ibrDist;
|
|
151
267
|
}
|
|
268
|
+
// The inner run writes its JSON report at the end, so nothing streams to
|
|
269
|
+
// stdout while it runs. Heartbeat from the test-results dir (the outer
|
|
270
|
+
// harness creates one dir per executed test) so long modes show progress.
|
|
271
|
+
const testResultsDir = path.join(pwRepo, "test-results");
|
|
272
|
+
const heartbeat = setInterval(() => {
|
|
273
|
+
let dirs = 0;
|
|
274
|
+
try {
|
|
275
|
+
dirs = fs.readdirSync(testResultsDir).length;
|
|
276
|
+
} catch {
|
|
277
|
+
// Not created yet.
|
|
278
|
+
}
|
|
279
|
+
const minutes = Math.round((Date.now() - startedAt) / 60_000);
|
|
280
|
+
console.log(
|
|
281
|
+
` [${mode} ${spec}] ~${dirs} tests executed, ${minutes}m elapsed`,
|
|
282
|
+
);
|
|
283
|
+
}, 60_000);
|
|
152
284
|
const { code, output } = await run(
|
|
153
285
|
"npm",
|
|
154
286
|
[
|
|
155
287
|
"run",
|
|
156
288
|
"ttest",
|
|
157
289
|
"--",
|
|
158
|
-
|
|
290
|
+
spec,
|
|
159
291
|
`--workers=${workers}`,
|
|
160
292
|
// Absorbs outer-harness flake: a scenario that fails on IBR's real gaps
|
|
161
293
|
// fails every retry, while an infra hiccup passes on retry and is
|
|
162
294
|
// reported "flaky" (not counted as a failure).
|
|
163
295
|
"--retries=2",
|
|
296
|
+
`--global-timeout=${SPEC_TIMEOUT_MS}`,
|
|
297
|
+
// An empty --grep-invert would match (and thus drop) every test, so
|
|
298
|
+
// the flag only appears when there are titles to skip.
|
|
299
|
+
...(HANGING_TESTS.length
|
|
300
|
+
? [`--grep-invert=${HANGING_TESTS.map((h) => h.pattern).join("|")}`]
|
|
301
|
+
: []),
|
|
164
302
|
"--reporter=json",
|
|
165
303
|
],
|
|
166
304
|
{ cwd: pwRepo, env },
|
|
167
305
|
);
|
|
306
|
+
clearInterval(heartbeat);
|
|
307
|
+
const elapsedSeconds = Math.round((Date.now() - startedAt) / 1_000);
|
|
308
|
+
// A globally-timed-out run still writes a valid JSON report with the
|
|
309
|
+
// remaining tests marked "did not run", which would otherwise diff as a
|
|
310
|
+
// false green — report the hang instead of trusting the partial report.
|
|
311
|
+
if (/Timed out waiting .* for the test suite/.test(output)) {
|
|
312
|
+
return { hung: true, elapsedSeconds };
|
|
313
|
+
}
|
|
168
314
|
let report;
|
|
169
315
|
try {
|
|
170
316
|
report = JSON.parse(fs.readFileSync(jsonOut, "utf8"));
|
|
171
317
|
} catch {
|
|
172
318
|
console.error(output.slice(-4000));
|
|
173
319
|
throw new Error(
|
|
174
|
-
`${mode}: no JSON report produced (exit code ${code}) — harness broken, see output above`,
|
|
320
|
+
`${mode} ${spec}: no JSON report produced (exit code ${code}) — harness broken, see output above`,
|
|
175
321
|
);
|
|
176
322
|
}
|
|
177
|
-
|
|
323
|
+
const stats = report.stats ?? {};
|
|
324
|
+
const totalRun =
|
|
325
|
+
(stats.expected ?? 0) +
|
|
326
|
+
(stats.unexpected ?? 0) +
|
|
327
|
+
(stats.flaky ?? 0) +
|
|
328
|
+
(stats.skipped ?? 0);
|
|
329
|
+
return { failures: collectFailures(report), totalRun, elapsedSeconds };
|
|
178
330
|
}
|
|
179
331
|
|
|
180
332
|
const args = parseArgs(process.argv.slice(2));
|
|
@@ -188,41 +340,130 @@ if (!fs.existsSync(ibrDist)) {
|
|
|
188
340
|
|
|
189
341
|
await applyFixturePatch(pwRepo);
|
|
190
342
|
|
|
343
|
+
if (!args.specs) {
|
|
344
|
+
const { included, excluded } = discoverSpecs(pwRepo);
|
|
345
|
+
args.specs = included;
|
|
346
|
+
console.log(`Excluded specs (${excluded.length}):`);
|
|
347
|
+
for (const [name, reason] of excluded) {
|
|
348
|
+
console.log(` [excluded] ${name} — ${reason}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (args.shard) {
|
|
352
|
+
// Round-robin slice so slow specs spread across shards.
|
|
353
|
+
args.specs = args.specs.filter(
|
|
354
|
+
(_, i) => i % args.shard.total === args.shard.index - 1,
|
|
355
|
+
);
|
|
356
|
+
console.log(
|
|
357
|
+
`Shard ${args.shard.index}/${args.shard.total}: ${args.specs.length} specs`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
191
361
|
console.log(`Specs: ${args.specs.join(", ")}`);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
mode: "blob",
|
|
198
|
-
});
|
|
199
|
-
console.log(
|
|
200
|
-
`Baseline failures (env noise, cancelled in diff): ${blobFailures.size}`,
|
|
201
|
-
);
|
|
202
|
-
for (const key of [...blobFailures].sort()) {
|
|
203
|
-
console.log(` [baseline] ${key}`);
|
|
362
|
+
for (const { pattern, reason } of HANGING_TESTS) {
|
|
363
|
+
console.log(` [skipped-by-title] /${pattern}/ — ${reason}`);
|
|
364
|
+
}
|
|
365
|
+
for (const { key, reason } of EXPECTED_IBR_ONLY) {
|
|
366
|
+
console.log(` [expected-ibr-only] ${key} — ${reason}`);
|
|
204
367
|
}
|
|
368
|
+
// Each spec runs both modes back to back as its own bounded invocation
|
|
369
|
+
// (see SPEC_TIMEOUT_MS); a hang or count mismatch quarantines that spec and
|
|
370
|
+
// the sweep continues.
|
|
371
|
+
const ibrOnly = [];
|
|
372
|
+
const hungSpecs = [];
|
|
373
|
+
const mismatchedSpecs = [];
|
|
374
|
+
const expectedByKey = new Map(EXPECTED_IBR_ONLY.map((e) => [e.key, e]));
|
|
375
|
+
const matchedExpected = new Set();
|
|
376
|
+
const comparedSpecs = new Set();
|
|
377
|
+
let baselineNoise = 0;
|
|
205
378
|
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
379
|
+
for (const spec of args.specs) {
|
|
380
|
+
const blob = await runMode({
|
|
381
|
+
pwRepo,
|
|
382
|
+
spec,
|
|
383
|
+
workers: args.workers,
|
|
384
|
+
mode: "blob",
|
|
385
|
+
});
|
|
386
|
+
if (blob.hung) {
|
|
387
|
+
console.log(` [hung] ${spec} (blob mode, ${blob.elapsedSeconds}s)`);
|
|
388
|
+
hungSpecs.push(`${spec} (blob mode)`);
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
const ibr = await runMode({
|
|
392
|
+
pwRepo,
|
|
393
|
+
spec,
|
|
394
|
+
workers: args.workers,
|
|
395
|
+
mode: "ibr",
|
|
396
|
+
ibrDist,
|
|
397
|
+
});
|
|
398
|
+
if (ibr.hung) {
|
|
399
|
+
console.log(` [hung] ${spec} (ibr mode, ${ibr.elapsedSeconds}s)`);
|
|
400
|
+
hungSpecs.push(`${spec} (ibr mode)`);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (blob.totalRun === 0 || blob.totalRun !== ibr.totalRun) {
|
|
404
|
+
console.log(
|
|
405
|
+
` [mismatch] ${spec}: blob ran ${blob.totalRun}, ibr ran ${ibr.totalRun}`,
|
|
406
|
+
);
|
|
407
|
+
mismatchedSpecs.push(spec);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
comparedSpecs.add(spec);
|
|
411
|
+
const specIbrOnly = [...ibr.failures]
|
|
412
|
+
.filter((key) => !blob.failures.has(key))
|
|
413
|
+
.sort();
|
|
414
|
+
baselineNoise += blob.failures.size;
|
|
415
|
+
const specExpected = specIbrOnly.filter((key) => expectedByKey.has(key));
|
|
416
|
+
for (const key of specExpected) {
|
|
417
|
+
matchedExpected.add(key);
|
|
418
|
+
}
|
|
419
|
+
const specUnexpected = specIbrOnly.filter((key) => !expectedByKey.has(key));
|
|
420
|
+
ibrOnly.push(...specUnexpected);
|
|
421
|
+
console.log(
|
|
422
|
+
` ${spec}: ${blob.totalRun} tests, ` +
|
|
423
|
+
`${blob.failures.size} baseline noise, ${specUnexpected.length} ibr-only` +
|
|
424
|
+
(specExpected.length ? `, ${specExpected.length} expected` : "") +
|
|
425
|
+
` (blob ${blob.elapsedSeconds}s, ibr ${ibr.elapsedSeconds}s)`,
|
|
426
|
+
);
|
|
427
|
+
}
|
|
214
428
|
|
|
215
|
-
|
|
429
|
+
console.log(
|
|
430
|
+
`\nBaseline failures (env noise, cancelled in diff): ${baselineNoise}`,
|
|
431
|
+
);
|
|
432
|
+
console.log(
|
|
433
|
+
`Expected IBR-only failures (not counted): ${matchedExpected.size}`,
|
|
434
|
+
);
|
|
216
435
|
console.log(`IBR-only failures: ${ibrOnly.length}`);
|
|
217
|
-
for (const key of ibrOnly) {
|
|
436
|
+
for (const key of ibrOnly.sort()) {
|
|
218
437
|
console.log(` [ibr-only] ${key}`);
|
|
219
438
|
}
|
|
439
|
+
for (const spec of hungSpecs) {
|
|
440
|
+
console.log(` [hung] ${spec}`);
|
|
441
|
+
}
|
|
442
|
+
for (const spec of mismatchedSpecs) {
|
|
443
|
+
console.log(` [mismatch] ${spec}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// An expected failure whose spec ran and passed means the list is stale —
|
|
447
|
+
// that is a fix to celebrate by deleting the entry, not a silent pass.
|
|
448
|
+
const staleExpected = EXPECTED_IBR_ONLY.filter(
|
|
449
|
+
({ key }) => comparedSpecs.has(specOfKey(key)) && !matchedExpected.has(key),
|
|
450
|
+
);
|
|
451
|
+
for (const { key } of staleExpected) {
|
|
452
|
+
console.log(` [stale-expected] ${key} — now passes; drop it from the list`);
|
|
453
|
+
}
|
|
220
454
|
|
|
221
|
-
if (
|
|
455
|
+
if (
|
|
456
|
+
ibrOnly.length > 0 ||
|
|
457
|
+
hungSpecs.length > 0 ||
|
|
458
|
+
mismatchedSpecs.length > 0 ||
|
|
459
|
+
staleExpected.length > 0
|
|
460
|
+
) {
|
|
222
461
|
console.error(
|
|
223
|
-
`\nFAIL: ${ibrOnly.length} upstream test(s) fail only through IBR
|
|
224
|
-
|
|
225
|
-
`
|
|
462
|
+
`\nFAIL: ${ibrOnly.length} upstream test(s) fail only through IBR, ` +
|
|
463
|
+
`${hungSpecs.length} spec run(s) hung, ${mismatchedSpecs.length} ` +
|
|
464
|
+
`spec(s) mismatched, ${staleExpected.length} stale expected-failure ` +
|
|
465
|
+
`entr${staleExpected.length === 1 ? "y" : "ies"}. The job stays red ` +
|
|
466
|
+
`until IBR reaches blob parity on these specs.`,
|
|
226
467
|
);
|
|
227
468
|
process.exit(1);
|
|
228
469
|
}
|