@elench/testkit 0.1.79 → 0.1.81
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/README.md +50 -35
- package/lib/cli/args.mjs +2 -14
- package/lib/cli/args.test.mjs +1 -17
- package/lib/cli/command-helpers.mjs +1 -20
- package/lib/cli/entrypoint.mjs +0 -4
- package/lib/cli/presentation/colors.mjs +1 -1
- package/lib/cli/presentation/failure-presentation.mjs +31 -0
- package/lib/cli/presentation/run-reporter.mjs +63 -93
- package/lib/cli/presentation/run-reporter.test.mjs +137 -26
- package/lib/cli/presentation/summary-box.mjs +45 -0
- package/lib/cli/presentation/summary-box.test.mjs +43 -0
- package/lib/cli/presentation/terminal-layout.mjs +43 -0
- package/lib/cli/presentation/terminal-layout.test.mjs +23 -0
- package/lib/cli/viewer.mjs +18 -19
- package/lib/config/index.mjs +6 -6
- package/lib/config/runtime.mjs +8 -8
- package/lib/config-api/index.d.ts +4 -4
- package/lib/package.test.mjs +4 -4
- package/lib/{known-failures → regressions}/github.mjs +39 -77
- package/lib/regressions/github.test.mjs +324 -0
- package/lib/regressions/index.d.ts +189 -0
- package/lib/{known-failures → regressions}/index.mjs +90 -93
- package/lib/{known-failures → regressions}/index.test.mjs +37 -48
- package/lib/runner/formatting.mjs +105 -103
- package/lib/runner/formatting.test.mjs +94 -131
- package/lib/runner/metadata.mjs +1 -1
- package/lib/runner/orchestrator.mjs +7 -8
- package/lib/runner/regressions.mjs +304 -0
- package/lib/runner/{triage.test.mjs → regressions.test.mjs} +50 -36
- package/lib/runner/reporting.mjs +2 -2
- package/lib/runner/reporting.test.mjs +2 -2
- package/lib/runner/run-finalization.mjs +18 -30
- package/lib/runner/template-steps.mjs +2 -2
- package/lib/runner/worker-loop.mjs +1 -0
- package/node_modules/@elench/next-analysis/package.json +1 -1
- package/node_modules/@elench/testkit-bridge/package.json +2 -2
- package/node_modules/@elench/testkit-protocol/package.json +1 -1
- package/node_modules/@elench/ts-analysis/package.json +1 -1
- package/package.json +12 -9
- package/lib/cli/commands/known-failures/render.mjs +0 -19
- package/lib/cli/commands/known-failures/validate.mjs +0 -20
- package/lib/cli/known-failures.mjs +0 -164
- package/lib/known-failures/github.test.mjs +0 -512
- package/lib/known-failures/index.d.ts +0 -192
- package/lib/runner/triage.mjs +0 -221
- /package/lib/{known-failures → regressions}/github-cache.mjs +0 -0
- /package/lib/{known-failures → regressions}/github-transport.mjs +0 -0
|
@@ -3,10 +3,10 @@ import os from "os";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { afterEach, describe, expect, it } from "vitest";
|
|
5
5
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
findMatchingRegressionEntries,
|
|
7
|
+
normalizeRegressionCatalogDocument,
|
|
8
|
+
renderRegressionCatalogMarkdown,
|
|
9
|
+
validateRegressionCatalogDocument,
|
|
10
10
|
} from "./index.mjs";
|
|
11
11
|
|
|
12
12
|
const tempDirs = [];
|
|
@@ -17,25 +17,22 @@ afterEach(() => {
|
|
|
17
17
|
}
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
describe("
|
|
20
|
+
describe("regression catalog", () => {
|
|
21
21
|
it("matches entries by service, type, path, and failure key", () => {
|
|
22
|
-
const document =
|
|
22
|
+
const document = normalizeRegressionCatalogDocument({
|
|
23
23
|
schemaVersion: 1,
|
|
24
24
|
entries: [
|
|
25
25
|
{
|
|
26
26
|
id: "bad-message",
|
|
27
|
-
title: "Bad message bug",
|
|
28
27
|
classification: "product_bug",
|
|
29
|
-
state: "open",
|
|
30
28
|
issue: {
|
|
31
29
|
repo: "acme/repo",
|
|
32
30
|
number: 12,
|
|
33
|
-
url: "https://github.com/acme/repo/issues/12",
|
|
34
31
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
lastReviewedAt: "2026-04
|
|
38
|
-
|
|
32
|
+
summary: "API returns the wrong message",
|
|
33
|
+
cause: "The payload is wrong.",
|
|
34
|
+
lastReviewedAt: "2026-05-04",
|
|
35
|
+
fingerprints: [
|
|
39
36
|
{
|
|
40
37
|
service: "api",
|
|
41
38
|
type: "int",
|
|
@@ -47,7 +44,7 @@ describe("known failures core", () => {
|
|
|
47
44
|
],
|
|
48
45
|
});
|
|
49
46
|
|
|
50
|
-
const matches =
|
|
47
|
+
const matches = findMatchingRegressionEntries(document, {
|
|
51
48
|
service: "api",
|
|
52
49
|
type: "int",
|
|
53
50
|
path: "src/api/routes/__testkit__/failing.int.testkit.ts",
|
|
@@ -59,24 +56,21 @@ describe("known failures core", () => {
|
|
|
59
56
|
expect(matches[0].id).toBe("bad-message");
|
|
60
57
|
});
|
|
61
58
|
|
|
62
|
-
it("matches failureKey against
|
|
63
|
-
const document =
|
|
59
|
+
it("matches fingerprint failureKey against detail title", () => {
|
|
60
|
+
const document = normalizeRegressionCatalogDocument({
|
|
64
61
|
schemaVersion: 1,
|
|
65
62
|
entries: [
|
|
66
63
|
{
|
|
67
64
|
id: "missing-route",
|
|
68
|
-
title: "Missing route bug",
|
|
69
65
|
classification: "product_bug",
|
|
70
|
-
state: "open",
|
|
71
66
|
issue: {
|
|
72
67
|
repo: "acme/repo",
|
|
73
68
|
number: 13,
|
|
74
|
-
url: "https://github.com/acme/repo/issues/13",
|
|
75
69
|
},
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
lastReviewedAt: "2026-04
|
|
79
|
-
|
|
70
|
+
summary: "Missing route returns the wrong status",
|
|
71
|
+
cause: "The route returns 404.",
|
|
72
|
+
lastReviewedAt: "2026-05-04",
|
|
73
|
+
fingerprints: [
|
|
80
74
|
{
|
|
81
75
|
service: "api",
|
|
82
76
|
type: "int",
|
|
@@ -88,7 +82,7 @@ describe("known failures core", () => {
|
|
|
88
82
|
],
|
|
89
83
|
});
|
|
90
84
|
|
|
91
|
-
const matches =
|
|
85
|
+
const matches = findMatchingRegressionEntries(document, {
|
|
92
86
|
service: "api",
|
|
93
87
|
type: "int",
|
|
94
88
|
path: "__testkit__/health/http-failure.int.testkit.ts",
|
|
@@ -105,31 +99,28 @@ describe("known failures core", () => {
|
|
|
105
99
|
expect(matches[0].id).toBe("missing-route");
|
|
106
100
|
});
|
|
107
101
|
|
|
108
|
-
it("validates status coverage and filesystem
|
|
109
|
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-
|
|
102
|
+
it("validates status coverage and filesystem fingerprints", () => {
|
|
103
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "testkit-regressions-"));
|
|
110
104
|
tempDirs.push(tempDir);
|
|
111
105
|
const testPath = path.join(tempDir, "src/api/routes/__testkit__/failing.int.testkit.ts");
|
|
112
106
|
fs.mkdirSync(path.dirname(testPath), { recursive: true });
|
|
113
107
|
fs.writeFileSync(testPath, "export default {};\n");
|
|
114
108
|
|
|
115
|
-
const document =
|
|
109
|
+
const document = normalizeRegressionCatalogDocument({
|
|
116
110
|
schemaVersion: 1,
|
|
117
111
|
issueRepo: "acme/repo",
|
|
118
112
|
entries: [
|
|
119
113
|
{
|
|
120
114
|
id: "bad-message",
|
|
121
|
-
title: "Bad message bug",
|
|
122
115
|
classification: "product_bug",
|
|
123
|
-
state: "open",
|
|
124
116
|
issue: {
|
|
125
117
|
repo: "acme/repo",
|
|
126
118
|
number: 12,
|
|
127
|
-
url: "https://github.com/acme/repo/issues/12",
|
|
128
119
|
},
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
lastReviewedAt: "2026-04
|
|
132
|
-
|
|
120
|
+
summary: "API returns the wrong message",
|
|
121
|
+
cause: "The payload is wrong.",
|
|
122
|
+
lastReviewedAt: "2026-05-04",
|
|
123
|
+
fingerprints: [
|
|
133
124
|
{
|
|
134
125
|
service: "api",
|
|
135
126
|
type: "int",
|
|
@@ -140,7 +131,7 @@ describe("known failures core", () => {
|
|
|
140
131
|
],
|
|
141
132
|
});
|
|
142
133
|
|
|
143
|
-
const result =
|
|
134
|
+
const result = validateRegressionCatalogDocument(document, {
|
|
144
135
|
productDir: tempDir,
|
|
145
136
|
statusArtifact: {
|
|
146
137
|
tests: [
|
|
@@ -162,37 +153,35 @@ describe("known failures core", () => {
|
|
|
162
153
|
|
|
163
154
|
expect(result.errors).toEqual([]);
|
|
164
155
|
expect(result.stats.failedTests).toBe(2);
|
|
165
|
-
expect(result.stats.
|
|
156
|
+
expect(result.stats.diagnosedFailedTests).toBe(1);
|
|
166
157
|
expect(result.warnings).toContain(
|
|
167
|
-
"
|
|
158
|
+
"New failing test not yet in regression catalog: src/api/routes/__testkit__/other.int.testkit.ts"
|
|
168
159
|
);
|
|
169
160
|
});
|
|
170
161
|
|
|
171
162
|
it("renders markdown from the canonical document", () => {
|
|
172
|
-
const markdown =
|
|
173
|
-
|
|
163
|
+
const markdown = renderRegressionCatalogMarkdown(
|
|
164
|
+
normalizeRegressionCatalogDocument({
|
|
174
165
|
schemaVersion: 1,
|
|
175
166
|
entries: [
|
|
176
167
|
{
|
|
177
168
|
id: "bad-message",
|
|
178
|
-
title: "Bad message bug",
|
|
179
169
|
classification: "product_bug",
|
|
180
|
-
state: "open",
|
|
181
170
|
issue: {
|
|
182
171
|
repo: "acme/repo",
|
|
183
172
|
number: 12,
|
|
184
|
-
url: "https://github.com/acme/repo/issues/12",
|
|
185
173
|
},
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
lastReviewedAt: "2026-04
|
|
189
|
-
|
|
174
|
+
summary: "API returns the wrong message",
|
|
175
|
+
cause: "The payload is wrong.",
|
|
176
|
+
lastReviewedAt: "2026-05-04",
|
|
177
|
+
fingerprints: [{ path: "src/api/routes/__testkit__/failing.int.testkit.ts" }],
|
|
190
178
|
},
|
|
191
179
|
],
|
|
192
180
|
})
|
|
193
181
|
);
|
|
194
182
|
|
|
195
|
-
expect(markdown).toContain("#
|
|
196
|
-
expect(markdown).toContain("
|
|
183
|
+
expect(markdown).toContain("# Regression Catalog");
|
|
184
|
+
expect(markdown).toContain("API returns the wrong message");
|
|
185
|
+
expect(markdown).toContain("#12");
|
|
197
186
|
});
|
|
198
187
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { findMatchingRegressionEntries } from "../regressions/index.mjs";
|
|
2
|
+
import { buildRegressionSyncSummaryLines } from "../regressions/github.mjs";
|
|
2
3
|
|
|
3
4
|
export function formatDuration(durationMs) {
|
|
4
5
|
const totalSeconds = Math.max(0, Math.round(durationMs / 1000));
|
|
@@ -48,57 +49,75 @@ export function formatSuiteFramework(framework) {
|
|
|
48
49
|
return label ? ` [${label}]` : "";
|
|
49
50
|
}
|
|
50
51
|
|
|
51
|
-
export function buildRunSummaryLines(results, durationMs,
|
|
52
|
-
return buildCompactRunSummaryLines(results, durationMs,
|
|
52
|
+
export function buildRunSummaryLines(results, durationMs, regressionReport = null) {
|
|
53
|
+
return buildCompactRunSummaryLines(results, durationMs, regressionReport);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function buildRunSummaryData(results, durationMs, regressionReport = null) {
|
|
57
|
+
const totals = summarizeResults(results);
|
|
58
|
+
const serviceErrors = collectServiceErrors(results);
|
|
59
|
+
const regressionSummary = regressionReport?.summary || null;
|
|
60
|
+
return {
|
|
61
|
+
result: totals.failedServices > 0 ? "FAILED" : "PASSED",
|
|
62
|
+
totalServices: totals.totalServices,
|
|
63
|
+
failedServices: totals.failedServices,
|
|
64
|
+
passed: totals.passedFiles,
|
|
65
|
+
failed: totals.failedFiles,
|
|
66
|
+
skipped: totals.skippedFiles,
|
|
67
|
+
notRun: totals.notRunFiles,
|
|
68
|
+
files: totals.totalFiles,
|
|
69
|
+
duration: formatDuration(durationMs),
|
|
70
|
+
serviceErrors: serviceErrors.length,
|
|
71
|
+
newRegressions: regressionSummary?.newRegressions || 0,
|
|
72
|
+
knownRegressions: regressionSummary?.knownRegressions || 0,
|
|
73
|
+
fixedKnownRegressions: regressionSummary?.fixedKnownRegressions || 0,
|
|
74
|
+
catalogStale: regressionSummary?.catalogStale || 0,
|
|
75
|
+
catalogSyncUnavailable: Boolean(regressionSummary?.catalogSyncUnavailable),
|
|
76
|
+
usedStaleCache: Boolean(regressionSummary?.usedStaleCache),
|
|
77
|
+
};
|
|
53
78
|
}
|
|
54
79
|
|
|
55
80
|
export function buildCompactRunSummaryLines(
|
|
56
81
|
results,
|
|
57
82
|
durationMs,
|
|
58
|
-
|
|
83
|
+
regressionReport = null
|
|
59
84
|
) {
|
|
60
|
-
const
|
|
85
|
+
const summary = buildRunSummaryData(results, durationMs, regressionReport);
|
|
61
86
|
const lines = [
|
|
62
87
|
"",
|
|
63
|
-
`Summary: ${
|
|
88
|
+
`Summary: ${summary.passed} passed, ${summary.failed} failed, ${summary.skipped} skipped, ${summary.notRun} not run across ${summary.files} ${pluralize(summary.files, "file", "files")} in ${summary.duration}`,
|
|
64
89
|
];
|
|
65
90
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
lines.push("", "Failures:");
|
|
69
|
-
for (const failure of failures) {
|
|
70
|
-
lines.push(` ${failure.file.path}`);
|
|
71
|
-
lines.push(` ${failure.primaryMessage}`);
|
|
72
|
-
for (const detail of failure.extraLines.slice(0, 3)) {
|
|
73
|
-
lines.push(` ${detail}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
91
|
+
if (summary.serviceErrors > 0) {
|
|
92
|
+
lines.push(`Runtime errors: ${summary.serviceErrors}`);
|
|
76
93
|
}
|
|
77
94
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
lines.push("", "Runtime Errors:");
|
|
81
|
-
for (const item of serviceErrors) {
|
|
82
|
-
lines.push(` ${item.service}`);
|
|
83
|
-
lines.push(` ${item.message}`);
|
|
84
|
-
}
|
|
95
|
+
if (summary.newRegressions > 0) {
|
|
96
|
+
lines.push(`New regressions: ${summary.newRegressions}`);
|
|
85
97
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
if (summary.knownRegressions > 0) {
|
|
99
|
+
lines.push(`Known regressions: ${summary.knownRegressions}`);
|
|
100
|
+
}
|
|
101
|
+
if (summary.fixedKnownRegressions > 0) {
|
|
102
|
+
lines.push(`Fixed known regressions: ${summary.fixedKnownRegressions}`);
|
|
103
|
+
}
|
|
104
|
+
if (summary.catalogStale > 0) {
|
|
105
|
+
lines.push(`Catalog stale: ${summary.catalogStale}`);
|
|
106
|
+
}
|
|
107
|
+
if (summary.catalogSyncUnavailable) {
|
|
108
|
+
lines.push("Catalog sync unavailable");
|
|
92
109
|
}
|
|
93
110
|
|
|
94
111
|
lines.push("");
|
|
95
112
|
lines.push(
|
|
96
|
-
|
|
113
|
+
summary.result === "FAILED"
|
|
114
|
+
? `Result: FAILED (${summary.failedServices}/${summary.totalServices} services failed)`
|
|
115
|
+
: "Result: PASSED"
|
|
97
116
|
);
|
|
98
117
|
return lines;
|
|
99
118
|
}
|
|
100
119
|
|
|
101
|
-
export function buildDebugRunSummaryLines(results, durationMs,
|
|
120
|
+
export function buildDebugRunSummaryLines(results, durationMs, regressionReport = null) {
|
|
102
121
|
const totalServices = results.length;
|
|
103
122
|
const executedServices = results.filter((result) => !result.skipped);
|
|
104
123
|
const skippedServices = results.filter((result) => result.skipped);
|
|
@@ -167,11 +186,19 @@ export function buildDebugRunSummaryLines(results, durationMs, knownFailureIssue
|
|
|
167
186
|
}
|
|
168
187
|
}
|
|
169
188
|
|
|
170
|
-
const
|
|
171
|
-
|
|
189
|
+
const regressionSyncLines = buildRegressionSyncSummaryLines(
|
|
190
|
+
regressionReport?.catalog?.configured ? {
|
|
191
|
+
summary: {
|
|
192
|
+
byCode: {
|
|
193
|
+
closed_but_failing: regressionReport.summary.catalogStale,
|
|
194
|
+
validation_unavailable: regressionReport.summary.catalogSyncUnavailable ? 1 : 0,
|
|
195
|
+
used_stale_cache: regressionReport.summary.usedStaleCache ? 1 : 0,
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
} : null
|
|
172
199
|
);
|
|
173
|
-
if (
|
|
174
|
-
lines.push(...
|
|
200
|
+
if (regressionSyncLines.length > 0) {
|
|
201
|
+
lines.push(...regressionSyncLines);
|
|
175
202
|
}
|
|
176
203
|
|
|
177
204
|
if (failedServices.length > 0) {
|
|
@@ -183,6 +210,43 @@ export function buildDebugRunSummaryLines(results, durationMs, knownFailureIssue
|
|
|
183
210
|
return lines;
|
|
184
211
|
}
|
|
185
212
|
|
|
213
|
+
export function buildFailurePresentation(fileSummary, regressionCatalog = null) {
|
|
214
|
+
const rankedDetails = rankFailureDetails(fileSummary.failureDetails || []);
|
|
215
|
+
const primaryDetail = rankedDetails[0] || null;
|
|
216
|
+
const fallbackMessages = rankedDetails
|
|
217
|
+
.map((detail) => detail.message || detail.title)
|
|
218
|
+
.filter(Boolean)
|
|
219
|
+
.map((message) => sanitizeErrorMessage(String(message).trim()));
|
|
220
|
+
const details = [];
|
|
221
|
+
|
|
222
|
+
const responseLine = formatFailureResponsePreview(primaryDetail);
|
|
223
|
+
if (responseLine) details.push(responseLine);
|
|
224
|
+
|
|
225
|
+
const regressionLine = formatInlineRegressionLine(fileSummary, regressionCatalog);
|
|
226
|
+
if (regressionLine) details.push(regressionLine);
|
|
227
|
+
|
|
228
|
+
const requestLine = formatFailureRequestHint(primaryDetail);
|
|
229
|
+
if (requestLine) details.push(requestLine);
|
|
230
|
+
|
|
231
|
+
for (const detail of rankedDetails.slice(primaryDetail ? 1 : 0)) {
|
|
232
|
+
const line = sanitizeInline(String(detail.message || detail.title || ""), 220);
|
|
233
|
+
if (line && !details.includes(line)) details.push(line);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
primary: sanitizeInline(
|
|
238
|
+
resolvePrimaryFailureMessage(
|
|
239
|
+
fileSummary,
|
|
240
|
+
{ error: fileSummary.suiteError || null },
|
|
241
|
+
primaryDetail,
|
|
242
|
+
fallbackMessages
|
|
243
|
+
),
|
|
244
|
+
240
|
|
245
|
+
),
|
|
246
|
+
details,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
186
250
|
function sanitizeErrorMessage(message) {
|
|
187
251
|
return message
|
|
188
252
|
.replace(/Command failed with exit code (\d+): .*?[\\/]vendor[\\/]k6 run\b/g, "Default runtime failed with exit code $1:")
|
|
@@ -203,46 +267,6 @@ function summarizeResults(results) {
|
|
|
203
267
|
};
|
|
204
268
|
}
|
|
205
269
|
|
|
206
|
-
function collectFailedFiles(results) {
|
|
207
|
-
const failures = [];
|
|
208
|
-
for (const result of results) {
|
|
209
|
-
for (const suite of result.suites || []) {
|
|
210
|
-
for (const file of suite.files || []) {
|
|
211
|
-
if (file.status !== "failed") continue;
|
|
212
|
-
const rankedDetails = rankFailureDetails(file.failureDetails || []);
|
|
213
|
-
const primaryDetail = rankedDetails[0] || null;
|
|
214
|
-
const fallbackMessages = rankedDetails
|
|
215
|
-
.map((detail) => detail.message || detail.title)
|
|
216
|
-
.filter(Boolean)
|
|
217
|
-
.map((message) => sanitizeErrorMessage(String(message).trim()));
|
|
218
|
-
const extraLines = [];
|
|
219
|
-
if (primaryDetail) {
|
|
220
|
-
const responseLine = formatFailureResponsePreview(primaryDetail);
|
|
221
|
-
if (responseLine) extraLines.push(responseLine);
|
|
222
|
-
const triageLine = formatTriageLine(file.triage || null);
|
|
223
|
-
if (triageLine) extraLines.push(triageLine);
|
|
224
|
-
const requestLine = formatFailureRequestHint(primaryDetail);
|
|
225
|
-
if (requestLine) extraLines.push(requestLine);
|
|
226
|
-
} else {
|
|
227
|
-
const triageLine = formatTriageLine(file.triage || null);
|
|
228
|
-
if (triageLine) extraLines.push(triageLine);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
for (const detail of rankedDetails.slice(primaryDetail ? 1 : 0)) {
|
|
232
|
-
const line = sanitizeErrorMessage(String(detail.message || detail.title || "").trim());
|
|
233
|
-
if (line && !extraLines.includes(line)) extraLines.push(line);
|
|
234
|
-
}
|
|
235
|
-
failures.push({
|
|
236
|
-
file,
|
|
237
|
-
primaryMessage: resolvePrimaryFailureMessage(file, suite, primaryDetail, fallbackMessages),
|
|
238
|
-
extraLines,
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return failures.sort((left, right) => left.file.path.localeCompare(right.file.path));
|
|
244
|
-
}
|
|
245
|
-
|
|
246
270
|
function resolvePrimaryFailureMessage(file, suite, primaryDetail, fallbackMessages) {
|
|
247
271
|
if (primaryDetail?.message) {
|
|
248
272
|
return sanitizeErrorMessage(String(primaryDetail.message).trim());
|
|
@@ -295,35 +319,13 @@ function formatFailureRequestHint(detail) {
|
|
|
295
319
|
return `request: ${method} ${path}`;
|
|
296
320
|
}
|
|
297
321
|
|
|
298
|
-
function
|
|
299
|
-
if (!
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const entry = triage.entries?.[0];
|
|
303
|
-
if (!entry?.issue) {
|
|
304
|
-
if (triage.status === "validation_unavailable") {
|
|
305
|
-
const reason = triage.availability?.reason ? ` (${triage.availability.reason})` : "";
|
|
306
|
-
return `triage: validation unavailable${reason}`;
|
|
307
|
-
}
|
|
308
|
-
return null;
|
|
309
|
-
}
|
|
322
|
+
function formatInlineRegressionLine(fileSummary, regressionCatalog) {
|
|
323
|
+
if (!regressionCatalog) return "regression: new";
|
|
324
|
+
const matches = findMatchingRegressionEntries(regressionCatalog, fileSummary);
|
|
325
|
+
if (matches.length === 0) return "regression: new";
|
|
310
326
|
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
if (validationStatus === "closed_but_failing") {
|
|
314
|
-
return `triage: known issue ${issueLabel} closed but still failing`;
|
|
315
|
-
}
|
|
316
|
-
if (validationStatus === "validation_unavailable") {
|
|
317
|
-
const reason = triage.availability?.mode === "cache" ? "cache" : "validation unavailable";
|
|
318
|
-
return `triage: known issue ${issueLabel} (${reason})`;
|
|
319
|
-
}
|
|
320
|
-
if (entry.github?.state === "open" || entry.state === "open") {
|
|
321
|
-
return `triage: known issue ${issueLabel} open`;
|
|
322
|
-
}
|
|
323
|
-
if (entry.github?.state === "closed" || entry.state === "closed") {
|
|
324
|
-
return `triage: known issue ${issueLabel} closed`;
|
|
325
|
-
}
|
|
326
|
-
return `triage: known issue ${issueLabel}`;
|
|
327
|
+
const entry = matches[0];
|
|
328
|
+
return `regression: known #${entry.issue.number} ${entry.classification}`;
|
|
327
329
|
}
|
|
328
330
|
|
|
329
331
|
function isThresholdWrapperMessage(message) {
|