@empiricalrun/playwright-utils 0.58.3 → 0.58.4
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 +9 -0
- package/dist/generated/api-worker-openapi.d.ts +98 -0
- 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/package.json +3 -3
- package/upstream-oracle/run.mjs +211 -47
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"empirical-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/empirical-reporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACX,MAAM,2BAA2B,CAAC;AAuBnC,cAAM,iBAAkB,YAAW,QAAQ;IACzC,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,oBAAoB,CAG1B;IACF,OAAO,CAAC,sBAAsB,CAAuB;IACrD,OAAO,CAAC,YAAY,CAAgD;IACpE,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,gBAAgB,CAAkC;IAC1D,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,kBAAkB,CAA2B;IAGrD,OAAO,CAAC,YAAY,CAAqB;IAEzC,cAEC;IAED,OAAO,CAAC,MAAM,EAAE,UAAU,QAEzB;IAED,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,6BAA6B,CA2CnC;IAEF,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"empirical-reporter.d.ts","sourceRoot":"","sources":["../../src/reporter/empirical-reporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,UAAU,EACX,MAAM,2BAA2B,CAAC;AAuBnC,cAAM,iBAAkB,YAAW,QAAQ;IACzC,OAAO,CAAC,kBAAkB,CAAyB;IACnD,OAAO,CAAC,oBAAoB,CAG1B;IACF,OAAO,CAAC,sBAAsB,CAAuB;IACrD,OAAO,CAAC,YAAY,CAAgD;IACpE,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,gBAAgB,CAAkC;IAC1D,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,kBAAkB,CAA2B;IAGrD,OAAO,CAAC,YAAY,CAAqB;IAEzC,cAEC;IAED,OAAO,CAAC,MAAM,EAAE,UAAU,QAEzB;IAED,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,6BAA6B,CA2CnC;IAEF,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,QA+I3C;IAEK,KAAK,CAAC,MAAM,EAAE,UAAU,iBAiH7B;IAED,OAAO,CAAC,qBAAqB;YAkBf,gBAAgB;YAOhB,iBAAiB;CAqChC;eAEc,iBAAiB"}
|
|
@@ -150,6 +150,15 @@ class EmpiricalReporter {
|
|
|
150
150
|
stack: e.stack,
|
|
151
151
|
location: e.location,
|
|
152
152
|
})),
|
|
153
|
+
// Playwright seeds result.annotations with the full per-attempt
|
|
154
|
+
// set (static + suite + runtime-pushed); test.annotations is the
|
|
155
|
+
// fallback for older Playwright versions without it.
|
|
156
|
+
annotations: (result.annotations?.length
|
|
157
|
+
? result.annotations
|
|
158
|
+
: test.annotations).map((a) => ({
|
|
159
|
+
type: a.type,
|
|
160
|
+
description: a.description,
|
|
161
|
+
})),
|
|
153
162
|
},
|
|
154
163
|
projectName,
|
|
155
164
|
attachments: successfulAttachments,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@empiricalrun/playwright-utils",
|
|
3
|
-
"version": "0.58.
|
|
3
|
+
"version": "0.58.4",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"openapi-typescript": "7.6.1",
|
|
42
42
|
"playwright-core": "1.62.1",
|
|
43
43
|
"serve-handler": "^6.1.6",
|
|
44
|
-
"@empiricalrun/shared-types": "0.15.
|
|
44
|
+
"@empiricalrun/shared-types": "0.15.4"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@babel/code-frame": "^7.24.7",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"puppeteer-extra-plugin-recaptcha": "^3.6.8",
|
|
55
55
|
"rimraf": "^6.0.1",
|
|
56
56
|
"ts-morph": "^23.0.0",
|
|
57
|
-
"@empiricalrun/llm": "^0.27.0",
|
|
58
57
|
"@empiricalrun/cua": "^0.4.1",
|
|
58
|
+
"@empiricalrun/llm": "^0.27.0",
|
|
59
59
|
"@empiricalrun/r2-uploader": "^0.10.1",
|
|
60
60
|
"@empiricalrun/reporter": "^0.29.1"
|
|
61
61
|
},
|
package/upstream-oracle/run.mjs
CHANGED
|
@@ -9,15 +9,14 @@
|
|
|
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
|
-
* built-in blob reporter is safe to drop.
|
|
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.
|
|
17
16
|
*
|
|
18
17
|
* Usage:
|
|
19
18
|
* node run.mjs --pw-repo <upstream clone at the pinned tag>
|
|
20
|
-
* [--specs basic.spec.ts,stdio.spec.ts] [--workers N]
|
|
19
|
+
* [--specs basic.spec.ts,stdio.spec.ts] [--workers N] [--shard i/n]
|
|
21
20
|
*
|
|
22
21
|
* The clone must be bootstrapped (npm ci + npm run build). This script
|
|
23
22
|
* (re)applies the fixture patch idempotently on every run.
|
|
@@ -31,18 +30,68 @@ import { fileURLToPath } from "node:url";
|
|
|
31
30
|
|
|
32
31
|
const ORACLE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
33
32
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
// The default sweep is EVERY spec in upstream's tests/playwright-test —
|
|
34
|
+
// IBR is meant to become the default blob reporter, so it has to stand the
|
|
35
|
+
// whole runner suite. Only structurally out-of-scope specs are excluded,
|
|
36
|
+
// each with its reason printed at startup (no silent caps). First matching
|
|
37
|
+
// rule wins.
|
|
38
|
+
const EXCLUSION_RULES = [
|
|
39
|
+
[
|
|
40
|
+
(name) => name.startsWith("playwright.ct-"),
|
|
41
|
+
"component-testing build; no runner-report signal",
|
|
42
|
+
],
|
|
43
|
+
[
|
|
44
|
+
(name) => name.startsWith("playwright."),
|
|
45
|
+
"drives real browsers (not installed in the oracle env)",
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
(name) => name.startsWith("ui-mode-"),
|
|
49
|
+
"UI mode; needs trace viewer + browsers",
|
|
50
|
+
],
|
|
51
|
+
[
|
|
52
|
+
(name) =>
|
|
53
|
+
name === "to-have-screenshot.spec.ts" ||
|
|
54
|
+
name === "aria-snapshot-file.spec.ts" ||
|
|
55
|
+
name === "update-aria-snapshot.spec.ts",
|
|
56
|
+
"browser screenshots/aria snapshots",
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
(name) => name === "types.spec.ts" || name === "types-2.spec.ts",
|
|
60
|
+
"tsc-only; exercises no runner report",
|
|
61
|
+
],
|
|
62
|
+
[
|
|
63
|
+
(name) => name === "reporter-html.spec.ts",
|
|
64
|
+
"browser-driven HTML-report UI tests; no blob-format signal (merge " +
|
|
65
|
+
"semantics are covered by reporter-blob.spec.ts) and they busy-loop " +
|
|
66
|
+
"workers under load, wedging the sweep",
|
|
67
|
+
],
|
|
44
68
|
];
|
|
45
69
|
|
|
70
|
+
// Tests skipped by title via --grep-invert. Reserved for tests that WEDGE
|
|
71
|
+
// the sweep — a busy-looped worker no per-test timeout can interrupt — so
|
|
72
|
+
// exclusion is surgical (exact title match) and every entry carries the
|
|
73
|
+
// observed reason. Everything else in the spec still runs.
|
|
74
|
+
const HANGING_TESTS = [];
|
|
75
|
+
|
|
76
|
+
function discoverSpecs(pwRepo) {
|
|
77
|
+
const specDir = path.join(pwRepo, "tests", "playwright-test");
|
|
78
|
+
const all = fs
|
|
79
|
+
.readdirSync(specDir)
|
|
80
|
+
.filter((f) => f.endsWith(".spec.ts"))
|
|
81
|
+
.sort();
|
|
82
|
+
const included = [];
|
|
83
|
+
const excluded = [];
|
|
84
|
+
for (const name of all) {
|
|
85
|
+
const rule = EXCLUSION_RULES.find(([match]) => match(name));
|
|
86
|
+
if (rule) {
|
|
87
|
+
excluded.push([name, rule[1]]);
|
|
88
|
+
} else {
|
|
89
|
+
included.push(name);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { included, excluded };
|
|
93
|
+
}
|
|
94
|
+
|
|
46
95
|
const ADAPTER_PATH = path.join(ORACLE_DIR, "ibr-adapter.js");
|
|
47
96
|
const PATCH_PATH = path.join(ORACLE_DIR, "intermediate-mode-ibr.patch");
|
|
48
97
|
const DEFAULT_IBR_DIST = path.join(
|
|
@@ -51,7 +100,7 @@ const DEFAULT_IBR_DIST = path.join(
|
|
|
51
100
|
);
|
|
52
101
|
|
|
53
102
|
function parseArgs(argv) {
|
|
54
|
-
const args = { specs:
|
|
103
|
+
const args = { specs: null, workers: 4, shard: null };
|
|
55
104
|
// Normalize --flag=value into --flag value.
|
|
56
105
|
argv = argv.flatMap((a) =>
|
|
57
106
|
a.startsWith("--") && a.includes("=")
|
|
@@ -66,6 +115,15 @@ function parseArgs(argv) {
|
|
|
66
115
|
args.specs = argv[++i].split(",").filter(Boolean);
|
|
67
116
|
} else if (arg === "--workers") {
|
|
68
117
|
args.workers = Number(argv[++i]);
|
|
118
|
+
} else if (arg === "--shard") {
|
|
119
|
+
const match = /^(\d+)\/(\d+)$/.exec(argv[++i] ?? "");
|
|
120
|
+
if (!match) {
|
|
121
|
+
throw new Error("--shard expects i/n (e.g. --shard 2/4)");
|
|
122
|
+
}
|
|
123
|
+
args.shard = { index: Number(match[1]), total: Number(match[2]) };
|
|
124
|
+
if (args.shard.index < 1 || args.shard.index > args.shard.total) {
|
|
125
|
+
throw new Error(`--shard index out of range: ${argv[i]}`);
|
|
126
|
+
}
|
|
69
127
|
} else {
|
|
70
128
|
throw new Error(`Unknown argument: ${arg}`);
|
|
71
129
|
}
|
|
@@ -134,7 +192,19 @@ async function applyFixturePatch(pwRepo) {
|
|
|
134
192
|
}
|
|
135
193
|
}
|
|
136
194
|
|
|
137
|
-
|
|
195
|
+
// Hard cap per spec-mode run (milliseconds — Playwright's --global-timeout
|
|
196
|
+
// unit). Some upstream tests can busy-loop a worker under load (sync string
|
|
197
|
+
// processing that no per-test timeout can interrupt — the timeouts run on
|
|
198
|
+
// the same blocked event loop); which test wedges is timing-dependent, so
|
|
199
|
+
// title exclusion cannot converge. Playwright's global timeout fires from
|
|
200
|
+
// the main process and SIGKILLs through busy loops. Each spec runs as its
|
|
201
|
+
// own bounded invocation, so a wedge costs one spec's cap, names the spec,
|
|
202
|
+
// and the sweep continues — hangs become inventory instead of aborting the
|
|
203
|
+
// whole mode.
|
|
204
|
+
const SPEC_TIMEOUT_MS = 10 * 60 * 1_000;
|
|
205
|
+
|
|
206
|
+
async function runMode({ pwRepo, spec, workers, mode, ibrDist }) {
|
|
207
|
+
const startedAt = Date.now();
|
|
138
208
|
const jsonOut = path.join(
|
|
139
209
|
fs.mkdtempSync(path.join(os.tmpdir(), "ibr-oracle-")),
|
|
140
210
|
`${mode}.json`,
|
|
@@ -149,32 +219,68 @@ async function runMode({ pwRepo, specs, workers, mode, ibrDist }) {
|
|
|
149
219
|
env.PWTEST_IBR_ADAPTER = ADAPTER_PATH;
|
|
150
220
|
env.PWTEST_IBR_DIST = ibrDist;
|
|
151
221
|
}
|
|
222
|
+
// The inner run writes its JSON report at the end, so nothing streams to
|
|
223
|
+
// stdout while it runs. Heartbeat from the test-results dir (the outer
|
|
224
|
+
// harness creates one dir per executed test) so long modes show progress.
|
|
225
|
+
const testResultsDir = path.join(pwRepo, "test-results");
|
|
226
|
+
const heartbeat = setInterval(() => {
|
|
227
|
+
let dirs = 0;
|
|
228
|
+
try {
|
|
229
|
+
dirs = fs.readdirSync(testResultsDir).length;
|
|
230
|
+
} catch {
|
|
231
|
+
// Not created yet.
|
|
232
|
+
}
|
|
233
|
+
const minutes = Math.round((Date.now() - startedAt) / 60_000);
|
|
234
|
+
console.log(
|
|
235
|
+
` [${mode} ${spec}] ~${dirs} tests executed, ${minutes}m elapsed`,
|
|
236
|
+
);
|
|
237
|
+
}, 60_000);
|
|
152
238
|
const { code, output } = await run(
|
|
153
239
|
"npm",
|
|
154
240
|
[
|
|
155
241
|
"run",
|
|
156
242
|
"ttest",
|
|
157
243
|
"--",
|
|
158
|
-
|
|
244
|
+
spec,
|
|
159
245
|
`--workers=${workers}`,
|
|
160
246
|
// Absorbs outer-harness flake: a scenario that fails on IBR's real gaps
|
|
161
247
|
// fails every retry, while an infra hiccup passes on retry and is
|
|
162
248
|
// reported "flaky" (not counted as a failure).
|
|
163
249
|
"--retries=2",
|
|
250
|
+
`--global-timeout=${SPEC_TIMEOUT_MS}`,
|
|
251
|
+
// An empty --grep-invert would match (and thus drop) every test, so
|
|
252
|
+
// the flag only appears when there are titles to skip.
|
|
253
|
+
...(HANGING_TESTS.length
|
|
254
|
+
? [`--grep-invert=${HANGING_TESTS.map((h) => h.pattern).join("|")}`]
|
|
255
|
+
: []),
|
|
164
256
|
"--reporter=json",
|
|
165
257
|
],
|
|
166
258
|
{ cwd: pwRepo, env },
|
|
167
259
|
);
|
|
260
|
+
clearInterval(heartbeat);
|
|
261
|
+
const elapsedSeconds = Math.round((Date.now() - startedAt) / 1_000);
|
|
262
|
+
// A globally-timed-out run still writes a valid JSON report with the
|
|
263
|
+
// remaining tests marked "did not run", which would otherwise diff as a
|
|
264
|
+
// false green — report the hang instead of trusting the partial report.
|
|
265
|
+
if (/Timed out waiting .* for the test suite/.test(output)) {
|
|
266
|
+
return { hung: true, elapsedSeconds };
|
|
267
|
+
}
|
|
168
268
|
let report;
|
|
169
269
|
try {
|
|
170
270
|
report = JSON.parse(fs.readFileSync(jsonOut, "utf8"));
|
|
171
271
|
} catch {
|
|
172
272
|
console.error(output.slice(-4000));
|
|
173
273
|
throw new Error(
|
|
174
|
-
`${mode}: no JSON report produced (exit code ${code}) — harness broken, see output above`,
|
|
274
|
+
`${mode} ${spec}: no JSON report produced (exit code ${code}) — harness broken, see output above`,
|
|
175
275
|
);
|
|
176
276
|
}
|
|
177
|
-
|
|
277
|
+
const stats = report.stats ?? {};
|
|
278
|
+
const totalRun =
|
|
279
|
+
(stats.expected ?? 0) +
|
|
280
|
+
(stats.unexpected ?? 0) +
|
|
281
|
+
(stats.flaky ?? 0) +
|
|
282
|
+
(stats.skipped ?? 0);
|
|
283
|
+
return { failures: collectFailures(report), totalRun, elapsedSeconds };
|
|
178
284
|
}
|
|
179
285
|
|
|
180
286
|
const args = parseArgs(process.argv.slice(2));
|
|
@@ -188,41 +294,99 @@ if (!fs.existsSync(ibrDist)) {
|
|
|
188
294
|
|
|
189
295
|
await applyFixturePatch(pwRepo);
|
|
190
296
|
|
|
297
|
+
if (!args.specs) {
|
|
298
|
+
const { included, excluded } = discoverSpecs(pwRepo);
|
|
299
|
+
args.specs = included;
|
|
300
|
+
console.log(`Excluded specs (${excluded.length}):`);
|
|
301
|
+
for (const [name, reason] of excluded) {
|
|
302
|
+
console.log(` [excluded] ${name} — ${reason}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (args.shard) {
|
|
306
|
+
// Round-robin slice so slow specs spread across shards.
|
|
307
|
+
args.specs = args.specs.filter(
|
|
308
|
+
(_, i) => i % args.shard.total === args.shard.index - 1,
|
|
309
|
+
);
|
|
310
|
+
console.log(
|
|
311
|
+
`Shard ${args.shard.index}/${args.shard.total}: ${args.specs.length} specs`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
191
315
|
console.log(`Specs: ${args.specs.join(", ")}`);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
pwRepo,
|
|
195
|
-
specs: args.specs,
|
|
196
|
-
workers: args.workers,
|
|
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}`);
|
|
316
|
+
for (const { pattern, reason } of HANGING_TESTS) {
|
|
317
|
+
console.log(` [skipped-by-title] /${pattern}/ — ${reason}`);
|
|
204
318
|
}
|
|
319
|
+
// Each spec runs both modes back to back as its own bounded invocation
|
|
320
|
+
// (see SPEC_TIMEOUT_MS); a hang or count mismatch quarantines that spec and
|
|
321
|
+
// the sweep continues.
|
|
322
|
+
const ibrOnly = [];
|
|
323
|
+
const hungSpecs = [];
|
|
324
|
+
const mismatchedSpecs = [];
|
|
325
|
+
let baselineNoise = 0;
|
|
205
326
|
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
327
|
+
for (const spec of args.specs) {
|
|
328
|
+
const blob = await runMode({
|
|
329
|
+
pwRepo,
|
|
330
|
+
spec,
|
|
331
|
+
workers: args.workers,
|
|
332
|
+
mode: "blob",
|
|
333
|
+
});
|
|
334
|
+
if (blob.hung) {
|
|
335
|
+
console.log(` [hung] ${spec} (blob mode, ${blob.elapsedSeconds}s)`);
|
|
336
|
+
hungSpecs.push(`${spec} (blob mode)`);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const ibr = await runMode({
|
|
340
|
+
pwRepo,
|
|
341
|
+
spec,
|
|
342
|
+
workers: args.workers,
|
|
343
|
+
mode: "ibr",
|
|
344
|
+
ibrDist,
|
|
345
|
+
});
|
|
346
|
+
if (ibr.hung) {
|
|
347
|
+
console.log(` [hung] ${spec} (ibr mode, ${ibr.elapsedSeconds}s)`);
|
|
348
|
+
hungSpecs.push(`${spec} (ibr mode)`);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (blob.totalRun === 0 || blob.totalRun !== ibr.totalRun) {
|
|
352
|
+
console.log(
|
|
353
|
+
` [mismatch] ${spec}: blob ran ${blob.totalRun}, ibr ran ${ibr.totalRun}`,
|
|
354
|
+
);
|
|
355
|
+
mismatchedSpecs.push(spec);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const specIbrOnly = [...ibr.failures]
|
|
359
|
+
.filter((key) => !blob.failures.has(key))
|
|
360
|
+
.sort();
|
|
361
|
+
baselineNoise += blob.failures.size;
|
|
362
|
+
ibrOnly.push(...specIbrOnly);
|
|
363
|
+
console.log(
|
|
364
|
+
` ${spec}: ${blob.totalRun} tests, ` +
|
|
365
|
+
`${blob.failures.size} baseline noise, ${specIbrOnly.length} ibr-only ` +
|
|
366
|
+
`(blob ${blob.elapsedSeconds}s, ibr ${ibr.elapsedSeconds}s)`,
|
|
367
|
+
);
|
|
368
|
+
}
|
|
214
369
|
|
|
215
|
-
|
|
370
|
+
console.log(
|
|
371
|
+
`\nBaseline failures (env noise, cancelled in diff): ${baselineNoise}`,
|
|
372
|
+
);
|
|
216
373
|
console.log(`IBR-only failures: ${ibrOnly.length}`);
|
|
217
|
-
for (const key of ibrOnly) {
|
|
374
|
+
for (const key of ibrOnly.sort()) {
|
|
218
375
|
console.log(` [ibr-only] ${key}`);
|
|
219
376
|
}
|
|
377
|
+
for (const spec of hungSpecs) {
|
|
378
|
+
console.log(` [hung] ${spec}`);
|
|
379
|
+
}
|
|
380
|
+
for (const spec of mismatchedSpecs) {
|
|
381
|
+
console.log(` [mismatch] ${spec}`);
|
|
382
|
+
}
|
|
220
383
|
|
|
221
|
-
if (ibrOnly.length > 0) {
|
|
384
|
+
if (ibrOnly.length > 0 || hungSpecs.length > 0 || mismatchedSpecs.length > 0) {
|
|
222
385
|
console.error(
|
|
223
|
-
`\nFAIL: ${ibrOnly.length} upstream test(s) fail only through IBR
|
|
224
|
-
|
|
225
|
-
`
|
|
386
|
+
`\nFAIL: ${ibrOnly.length} upstream test(s) fail only through IBR, ` +
|
|
387
|
+
`${hungSpecs.length} spec run(s) hung, ${mismatchedSpecs.length} ` +
|
|
388
|
+
`spec(s) mismatched. The job stays red until IBR reaches blob parity ` +
|
|
389
|
+
`on these specs.`,
|
|
226
390
|
);
|
|
227
391
|
process.exit(1);
|
|
228
392
|
}
|