@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
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { normalizeRegressionCatalogDocument } from "../regressions/index.mjs";
|
|
3
|
+
import { applyRegressionAnalysisToArtifacts } from "./regressions.mjs";
|
|
4
4
|
|
|
5
|
-
describe("runner
|
|
6
|
-
it("
|
|
7
|
-
const
|
|
5
|
+
describe("runner regressions", () => {
|
|
6
|
+
it("classifies known failed regressions and enriches both artifacts", () => {
|
|
7
|
+
const regressionCatalog = normalizeRegressionCatalogDocument({
|
|
8
8
|
schemaVersion: 1,
|
|
9
9
|
issueRepo: "acme/repo",
|
|
10
10
|
entries: [
|
|
11
11
|
{
|
|
12
12
|
id: "bad-message",
|
|
13
|
-
title: "Bad message bug",
|
|
14
13
|
classification: "product_bug",
|
|
15
|
-
state: "open",
|
|
16
14
|
issue: {
|
|
17
15
|
repo: "acme/repo",
|
|
18
16
|
number: 12,
|
|
19
|
-
url: "https://github.com/acme/repo/issues/12",
|
|
20
17
|
},
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
lastReviewedAt: "2026-04
|
|
24
|
-
|
|
18
|
+
summary: "API returns the wrong message",
|
|
19
|
+
cause: "The endpoint payload is wrong.",
|
|
20
|
+
lastReviewedAt: "2026-05-04",
|
|
21
|
+
fingerprints: [
|
|
25
22
|
{
|
|
26
23
|
service: "api",
|
|
27
24
|
type: "int",
|
|
@@ -80,36 +77,49 @@ describe("runner triage", () => {
|
|
|
80
77
|
],
|
|
81
78
|
};
|
|
82
79
|
|
|
83
|
-
const enriched =
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
const enriched = applyRegressionAnalysisToArtifacts(
|
|
81
|
+
runArtifact,
|
|
82
|
+
statusArtifact,
|
|
83
|
+
regressionCatalog,
|
|
84
|
+
{
|
|
85
|
+
entries: [
|
|
86
|
+
{
|
|
87
|
+
id: "bad-message",
|
|
88
|
+
status: "open_and_failing",
|
|
89
|
+
github: {
|
|
90
|
+
state: "OPEN",
|
|
91
|
+
url: "https://github.com/acme/repo/issues/12",
|
|
92
|
+
cached: false,
|
|
93
|
+
},
|
|
94
|
+
findings: [],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
findings: [],
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(enriched.statusArtifact.tests[0].diagnosis).toMatchObject({
|
|
102
|
+
status: "known_regression",
|
|
86
103
|
classifications: ["product_bug"],
|
|
87
104
|
});
|
|
88
|
-
expect(enriched.runArtifact.services[0].suites[0].files[0].
|
|
105
|
+
expect(enriched.runArtifact.services[0].suites[0].files[0].diagnosis.entries[0]).toMatchObject({
|
|
89
106
|
id: "bad-message",
|
|
90
107
|
issue: {
|
|
91
108
|
number: 12,
|
|
92
109
|
},
|
|
93
110
|
});
|
|
94
|
-
expect(enriched.statusArtifact.
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
entries: {
|
|
104
|
-
total: 1,
|
|
105
|
-
matchedByFailedTests: 1,
|
|
106
|
-
unmatched: 0,
|
|
107
|
-
},
|
|
111
|
+
expect(enriched.statusArtifact.regressions.summary).toEqual({
|
|
112
|
+
newRegressions: 0,
|
|
113
|
+
knownRegressions: 1,
|
|
114
|
+
fixedKnownRegressions: 0,
|
|
115
|
+
catalogStale: 0,
|
|
116
|
+
catalogSyncUnavailable: false,
|
|
117
|
+
usedStaleCache: false,
|
|
108
118
|
});
|
|
109
119
|
});
|
|
110
120
|
|
|
111
|
-
it("marks unmatched failed tests as
|
|
112
|
-
const enriched =
|
|
121
|
+
it("marks unmatched failed tests as new regressions and emits drafts", () => {
|
|
122
|
+
const enriched = applyRegressionAnalysisToArtifacts(
|
|
113
123
|
{
|
|
114
124
|
services: [
|
|
115
125
|
{
|
|
@@ -140,15 +150,19 @@ describe("runner triage", () => {
|
|
|
140
150
|
},
|
|
141
151
|
],
|
|
142
152
|
},
|
|
143
|
-
|
|
153
|
+
normalizeRegressionCatalogDocument({
|
|
144
154
|
schemaVersion: 1,
|
|
145
155
|
entries: [],
|
|
146
|
-
})
|
|
156
|
+
}),
|
|
157
|
+
null
|
|
147
158
|
);
|
|
148
159
|
|
|
149
|
-
expect(enriched.statusArtifact.tests[0].
|
|
150
|
-
status: "
|
|
160
|
+
expect(enriched.statusArtifact.tests[0].diagnosis).toEqual({
|
|
161
|
+
status: "new_regression",
|
|
162
|
+
classifications: [],
|
|
151
163
|
entries: [],
|
|
152
164
|
});
|
|
165
|
+
expect(enriched.statusArtifact.regressions.summary.newRegressions).toBe(1);
|
|
166
|
+
expect(enriched.statusArtifact.regressions.drafts.newRegressions).toHaveLength(1);
|
|
153
167
|
});
|
|
154
168
|
});
|
package/lib/runner/reporting.mjs
CHANGED
|
@@ -80,7 +80,7 @@ export function buildStatusArtifact({
|
|
|
80
80
|
scope.serviceFilter === null;
|
|
81
81
|
|
|
82
82
|
return {
|
|
83
|
-
schemaVersion:
|
|
83
|
+
schemaVersion: 7,
|
|
84
84
|
source: "testkit",
|
|
85
85
|
notice: "Generated file. Do not edit manually.",
|
|
86
86
|
product: {
|
|
@@ -134,7 +134,7 @@ export function buildRunArtifact({
|
|
|
134
134
|
const dbBackend = summarizeDbBackend(results);
|
|
135
135
|
|
|
136
136
|
return {
|
|
137
|
-
schemaVersion:
|
|
137
|
+
schemaVersion: 9,
|
|
138
138
|
source: "testkit",
|
|
139
139
|
generatedAt: new Date(finishedAt).toISOString(),
|
|
140
140
|
product: {
|
|
@@ -79,7 +79,7 @@ describe("runner reporting", () => {
|
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
expect(artifact.product.name).toBe("my-product");
|
|
82
|
-
expect(artifact.schemaVersion).toBe(
|
|
82
|
+
expect(artifact.schemaVersion).toBe(9);
|
|
83
83
|
expect(artifact.run).toMatchObject({
|
|
84
84
|
workers: 2,
|
|
85
85
|
fileTimeoutSeconds: 60,
|
|
@@ -233,7 +233,7 @@ describe("runner reporting", () => {
|
|
|
233
233
|
});
|
|
234
234
|
|
|
235
235
|
expect(status).toEqual({
|
|
236
|
-
schemaVersion:
|
|
236
|
+
schemaVersion: 7,
|
|
237
237
|
source: "testkit",
|
|
238
238
|
notice: "Generated file. Do not edit manually.",
|
|
239
239
|
product: {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { buildRunArtifact, buildStatusArtifact } from "./reporting.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { applyRegressionAnalysisToArtifacts } from "./regressions.mjs";
|
|
3
3
|
import { writeRunArtifact, writeStatusArtifact } from "./artifacts.mjs";
|
|
4
4
|
import { summarizeDbBackend } from "./results.mjs";
|
|
5
5
|
import { formatError } from "./formatting.mjs";
|
|
6
6
|
import { uploadTelemetryArtifact } from "../telemetry/index.mjs";
|
|
7
7
|
import { loadHistory, saveHistory, updateHistoryFromRunArtifact } from "../history/index.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import { shouldFailRegressionSync, validateRegressionIssues } from "../regressions/github.mjs";
|
|
9
9
|
|
|
10
10
|
export async function finalizeRunArtifacts({
|
|
11
11
|
productDir,
|
|
@@ -20,8 +20,8 @@ export async function finalizeRunArtifacts({
|
|
|
20
20
|
metadata,
|
|
21
21
|
logRegistry,
|
|
22
22
|
setupRegistry,
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
regressionCatalog,
|
|
24
|
+
regressionSyncConfig,
|
|
25
25
|
telemetry,
|
|
26
26
|
reporter,
|
|
27
27
|
writeStatus,
|
|
@@ -60,24 +60,19 @@ export async function finalizeRunArtifacts({
|
|
|
60
60
|
metadata,
|
|
61
61
|
})
|
|
62
62
|
: null;
|
|
63
|
-
const
|
|
64
|
-
const knownFailureIssueValidation = await validateKnownFailureIssues({
|
|
63
|
+
const regressionSync = await validateRegressionIssues({
|
|
65
64
|
productDir,
|
|
66
|
-
document:
|
|
67
|
-
runArtifact
|
|
68
|
-
statusArtifact
|
|
69
|
-
config:
|
|
65
|
+
document: regressionCatalog,
|
|
66
|
+
runArtifact,
|
|
67
|
+
statusArtifact,
|
|
68
|
+
config: regressionSyncConfig,
|
|
70
69
|
gitMetadata: metadata.git,
|
|
71
70
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
attachKnownFailureIssueValidation(
|
|
78
|
-
enrichedArtifacts.runArtifact,
|
|
79
|
-
enrichedArtifacts.statusArtifact,
|
|
80
|
-
knownFailureIssueValidation
|
|
71
|
+
const enrichedArtifacts = applyRegressionAnalysisToArtifacts(
|
|
72
|
+
runArtifact,
|
|
73
|
+
statusArtifact,
|
|
74
|
+
regressionCatalog,
|
|
75
|
+
regressionSync
|
|
81
76
|
);
|
|
82
77
|
|
|
83
78
|
writeRunArtifact(productDir, enrichedArtifacts.runArtifact);
|
|
@@ -91,14 +86,15 @@ export async function finalizeRunArtifacts({
|
|
|
91
86
|
);
|
|
92
87
|
saveHistory(productDir, nextHistory);
|
|
93
88
|
|
|
94
|
-
reporter?.runSummary?.(results, finishedAt - startedAt,
|
|
89
|
+
reporter?.runSummary?.(results, finishedAt - startedAt, enrichedArtifacts.regressionReport);
|
|
95
90
|
await reportTelemetry(telemetry, enrichedArtifacts.runArtifact, reporter);
|
|
96
91
|
|
|
97
92
|
return {
|
|
98
93
|
runArtifact: enrichedArtifacts.runArtifact,
|
|
99
94
|
statusArtifact: enrichedArtifacts.statusArtifact,
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
regressionReport: enrichedArtifacts.regressionReport,
|
|
96
|
+
regressionSync,
|
|
97
|
+
shouldFailRegressionSync: shouldFailRegressionSync(regressionSync),
|
|
102
98
|
};
|
|
103
99
|
}
|
|
104
100
|
|
|
@@ -122,11 +118,3 @@ async function reportTelemetry(telemetry, artifact, reporter = null) {
|
|
|
122
118
|
reporter?.telemetry?.(`Telemetry: upload failed (${formatError(error)})`);
|
|
123
119
|
}
|
|
124
120
|
}
|
|
125
|
-
|
|
126
|
-
function attachKnownFailureIssueValidation(runArtifact, statusArtifact, validation) {
|
|
127
|
-
if (!validation) return;
|
|
128
|
-
runArtifact.knownFailuresIssueValidation = validation;
|
|
129
|
-
if (statusArtifact) {
|
|
130
|
-
statusArtifact.knownFailuresIssueValidation = validation;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
@@ -35,7 +35,7 @@ const PLAYWRIGHT_ENTRY = path.join(PACKAGE_ROOT, "lib", "playwright", "index.mjs
|
|
|
35
35
|
const RUNTIME_ENTRY = path.join(PACKAGE_ROOT, "lib", "runtime", "index.mjs");
|
|
36
36
|
const VITEST_ENTRY = path.join(PACKAGE_ROOT, "lib", "vitest", "index.mjs");
|
|
37
37
|
const DISCOVERY_ENTRY = path.join(PACKAGE_ROOT, "lib", "discovery", "index.mjs");
|
|
38
|
-
const
|
|
38
|
+
const REGRESSIONS_ENTRY = path.join(PACKAGE_ROOT, "lib", "regressions", "index.mjs");
|
|
39
39
|
const MODULE_RUNNER_ENTRY = path.join(
|
|
40
40
|
PACKAGE_ROOT,
|
|
41
41
|
"lib",
|
|
@@ -263,7 +263,7 @@ function resolvePackageSubpath(specifier) {
|
|
|
263
263
|
if (subpath === "/runtime") return RUNTIME_ENTRY;
|
|
264
264
|
if (subpath === "/vitest") return VITEST_ENTRY;
|
|
265
265
|
if (subpath === "/discovery") return DISCOVERY_ENTRY;
|
|
266
|
-
if (subpath === "/
|
|
266
|
+
if (subpath === "/regressions") return REGRESSIONS_ENTRY;
|
|
267
267
|
|
|
268
268
|
throw new Error(`Unsupported @elench/testkit import "${specifier}" while loading template step`);
|
|
269
269
|
}
|
|
@@ -81,6 +81,7 @@ export async function runWorker(
|
|
|
81
81
|
} catch (error) {
|
|
82
82
|
const message = formatError(error);
|
|
83
83
|
errors.push(message);
|
|
84
|
+
reporter?.runtimeError?.(task, message);
|
|
84
85
|
recordGraphError(trackers, { targetNames: lease?.context?.targetNames || [task.targetName] }, message);
|
|
85
86
|
await runtimeManager.release(lease, {
|
|
86
87
|
invalidate: lease !== null,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.81",
|
|
4
4
|
"description": "Browser bridge helpers for testkit",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@elench/testkit-protocol": "0.1.
|
|
25
|
+
"@elench/testkit-protocol": "0.1.81"
|
|
26
26
|
},
|
|
27
27
|
"private": false
|
|
28
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elench/testkit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.81",
|
|
4
4
|
"description": "CLI for discovering and running local HTTP, DAL, and Playwright test suites",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"types": "./lib/discovery/index.d.ts",
|
|
41
41
|
"default": "./lib/discovery/index.mjs"
|
|
42
42
|
},
|
|
43
|
-
"./
|
|
44
|
-
"types": "./lib/
|
|
45
|
-
"default": "./lib/
|
|
43
|
+
"./regressions": {
|
|
44
|
+
"types": "./lib/regressions/index.d.ts",
|
|
45
|
+
"default": "./lib/regressions/index.mjs"
|
|
46
46
|
},
|
|
47
47
|
"./package.json": "./package.json"
|
|
48
48
|
},
|
|
@@ -81,10 +81,10 @@
|
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"@babel/code-frame": "^7.29.0",
|
|
84
|
-
"@elench/next-analysis": "0.1.
|
|
85
|
-
"@elench/testkit-bridge": "0.1.
|
|
86
|
-
"@elench/testkit-protocol": "0.1.
|
|
87
|
-
"@elench/ts-analysis": "0.1.
|
|
84
|
+
"@elench/next-analysis": "0.1.81",
|
|
85
|
+
"@elench/testkit-bridge": "0.1.81",
|
|
86
|
+
"@elench/testkit-protocol": "0.1.81",
|
|
87
|
+
"@elench/ts-analysis": "0.1.81",
|
|
88
88
|
"@oclif/core": "^4.10.6",
|
|
89
89
|
"esbuild": "^0.25.11",
|
|
90
90
|
"execa": "^9.5.0",
|
|
@@ -92,7 +92,10 @@
|
|
|
92
92
|
"ink": "^7.0.1",
|
|
93
93
|
"picocolors": "^1.1.1",
|
|
94
94
|
"react": "^19.2.5",
|
|
95
|
-
"
|
|
95
|
+
"string-width": "^8.1.0",
|
|
96
|
+
"strip-ansi": "^7.1.2",
|
|
97
|
+
"typescript": "^5.9.3",
|
|
98
|
+
"wrap-ansi": "^10.0.0"
|
|
96
99
|
},
|
|
97
100
|
"engines": {
|
|
98
101
|
"node": ">=18"
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Command } from "@oclif/core";
|
|
2
|
-
import { makeKnownFailuresFlags } from "../../command-helpers.mjs";
|
|
3
|
-
import { runKnownFailuresRenderCommand } from "../../known-failures.mjs";
|
|
4
|
-
|
|
5
|
-
export default class KnownFailuresRenderCommand extends Command {
|
|
6
|
-
static summary = "Render known failures markdown";
|
|
7
|
-
|
|
8
|
-
static enableJsonFlag = true;
|
|
9
|
-
|
|
10
|
-
static flags = makeKnownFailuresFlags();
|
|
11
|
-
|
|
12
|
-
async run() {
|
|
13
|
-
const { flags } = await this.parse(KnownFailuresRenderCommand);
|
|
14
|
-
await runKnownFailuresRenderCommand({
|
|
15
|
-
...flags,
|
|
16
|
-
});
|
|
17
|
-
return { ok: true };
|
|
18
|
-
}
|
|
19
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Command } from "@oclif/core";
|
|
2
|
-
import { makeKnownFailuresFlags } from "../../command-helpers.mjs";
|
|
3
|
-
import { runKnownFailuresValidateCommand } from "../../known-failures.mjs";
|
|
4
|
-
|
|
5
|
-
export default class KnownFailuresValidateCommand extends Command {
|
|
6
|
-
static summary = "Validate known failures against the latest status artifact";
|
|
7
|
-
|
|
8
|
-
static enableJsonFlag = true;
|
|
9
|
-
|
|
10
|
-
static flags = makeKnownFailuresFlags();
|
|
11
|
-
|
|
12
|
-
async run() {
|
|
13
|
-
const { flags } = await this.parse(KnownFailuresValidateCommand);
|
|
14
|
-
await runKnownFailuresValidateCommand({
|
|
15
|
-
...flags,
|
|
16
|
-
issueMode: flags["issue-mode"] || null,
|
|
17
|
-
});
|
|
18
|
-
return { ok: true };
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { resolveProductDir } from "../config/index.mjs";
|
|
4
|
-
import { loadTestkitConfig } from "../config/config-loader.mjs";
|
|
5
|
-
import {
|
|
6
|
-
buildKnownFailureIssueValidationSummaryLines,
|
|
7
|
-
normalizeKnownFailureIssueValidationConfig,
|
|
8
|
-
shouldFailKnownFailureIssueValidation,
|
|
9
|
-
validateKnownFailureIssues,
|
|
10
|
-
} from "../known-failures/github.mjs";
|
|
11
|
-
import {
|
|
12
|
-
loadKnownFailuresDocument,
|
|
13
|
-
renderKnownFailuresMarkdown,
|
|
14
|
-
validateKnownFailuresDocument,
|
|
15
|
-
} from "../known-failures/index.mjs";
|
|
16
|
-
import { collectGitMetadata } from "../runner/metadata.mjs";
|
|
17
|
-
|
|
18
|
-
const DEFAULT_ISSUE_VALIDATION = {
|
|
19
|
-
provider: "github",
|
|
20
|
-
mode: "error",
|
|
21
|
-
cacheTtlSeconds: 900,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export async function runKnownFailuresRenderCommand(options = {}) {
|
|
25
|
-
const context = await resolveKnownFailuresContext(options);
|
|
26
|
-
const document = loadKnownFailuresDocument(context.knownFailuresPath, context.knownFailuresRelativePath);
|
|
27
|
-
const markdown = renderKnownFailuresMarkdown(document);
|
|
28
|
-
|
|
29
|
-
if (options.output) {
|
|
30
|
-
const outputPath = path.resolve(context.productDir, options.output);
|
|
31
|
-
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
32
|
-
fs.writeFileSync(outputPath, markdown);
|
|
33
|
-
console.log(`Wrote ${path.relative(context.productDir, outputPath)}`);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
process.stdout.write(markdown);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function runKnownFailuresValidateCommand(options = {}) {
|
|
41
|
-
const context = await resolveKnownFailuresContext(options);
|
|
42
|
-
const document = loadKnownFailuresDocument(context.knownFailuresPath, context.knownFailuresRelativePath);
|
|
43
|
-
const statusArtifact = context.statusArtifactPath
|
|
44
|
-
? readJsonIfExists(context.statusArtifactPath)
|
|
45
|
-
: undefined;
|
|
46
|
-
|
|
47
|
-
const documentValidation = validateKnownFailuresDocument(document, {
|
|
48
|
-
productDir: context.productDir,
|
|
49
|
-
statusArtifact,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const issueValidation = await validateKnownFailureIssues({
|
|
53
|
-
productDir: context.productDir,
|
|
54
|
-
document,
|
|
55
|
-
statusArtifact,
|
|
56
|
-
config: context.issueValidationConfig,
|
|
57
|
-
gitMetadata: collectGitMetadata(context.productDir),
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
const issueErrors = collectIssueMessages(issueValidation, "error");
|
|
61
|
-
const issueWarnings = collectIssueMessages(issueValidation, "warning");
|
|
62
|
-
const hasErrors =
|
|
63
|
-
documentValidation.errors.length > 0 ||
|
|
64
|
-
shouldFailKnownFailureIssueValidation(issueValidation) ||
|
|
65
|
-
issueErrors.length > 0;
|
|
66
|
-
|
|
67
|
-
if (hasErrors) {
|
|
68
|
-
console.error("Known failures validation failed:");
|
|
69
|
-
for (const error of documentValidation.errors) {
|
|
70
|
-
console.error(`- ${error}`);
|
|
71
|
-
}
|
|
72
|
-
for (const error of issueErrors) {
|
|
73
|
-
console.error(`- ${error}`);
|
|
74
|
-
}
|
|
75
|
-
if (documentValidation.warnings.length > 0 || issueWarnings.length > 0) {
|
|
76
|
-
console.error("");
|
|
77
|
-
for (const warning of documentValidation.warnings) {
|
|
78
|
-
console.error(`! ${warning}`);
|
|
79
|
-
}
|
|
80
|
-
for (const warning of issueWarnings) {
|
|
81
|
-
console.error(`! ${warning}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
for (const line of buildKnownFailureIssueValidationSummaryLines(issueValidation)) {
|
|
85
|
-
console.error(line);
|
|
86
|
-
}
|
|
87
|
-
process.exitCode = 1;
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
console.log(
|
|
92
|
-
`Known failures valid: ${documentValidation.stats.entries} entries, ${documentValidation.stats.matches} matches`
|
|
93
|
-
);
|
|
94
|
-
if (documentValidation.stats.failedTests > 0) {
|
|
95
|
-
console.log(
|
|
96
|
-
`Status coverage: ${documentValidation.stats.triagedFailedTests}/${documentValidation.stats.failedTests} failed tests triaged`
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
for (const line of buildKnownFailureIssueValidationSummaryLines(issueValidation)) {
|
|
100
|
-
console.log(line);
|
|
101
|
-
}
|
|
102
|
-
for (const warning of [...documentValidation.warnings, ...issueWarnings]) {
|
|
103
|
-
console.warn(`! ${warning}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function resolveKnownFailuresContext(options) {
|
|
108
|
-
const productDir = resolveProductDir(process.cwd(), options.dir);
|
|
109
|
-
const { config } = await loadTestkitConfig(productDir);
|
|
110
|
-
const reporting = config?.reporting || {};
|
|
111
|
-
|
|
112
|
-
const knownFailuresRelativePath = normalizeOptionalString(options.input)
|
|
113
|
-
|| normalizeOptionalString(reporting.knownFailuresFile);
|
|
114
|
-
if (!knownFailuresRelativePath) {
|
|
115
|
-
throw new Error(
|
|
116
|
-
"Known failures file not configured. Set reporting.knownFailuresFile in testkit.config.ts or pass --input."
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const statusRelativePath = normalizeOptionalString(options.status) || "testkit.status.json";
|
|
121
|
-
const configuredIssueValidation = normalizeKnownFailureIssueValidationConfig(reporting.issueValidation)
|
|
122
|
-
|| DEFAULT_ISSUE_VALIDATION;
|
|
123
|
-
const issueMode = normalizeOptionalString(options.issueMode) || "error";
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
productDir,
|
|
127
|
-
knownFailuresRelativePath,
|
|
128
|
-
knownFailuresPath: path.resolve(productDir, knownFailuresRelativePath),
|
|
129
|
-
statusArtifactPath: statusRelativePath ? path.resolve(productDir, statusRelativePath) : null,
|
|
130
|
-
issueValidationConfig: {
|
|
131
|
-
...configuredIssueValidation,
|
|
132
|
-
mode: issueMode,
|
|
133
|
-
},
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function collectIssueMessages(validation, severity) {
|
|
138
|
-
if (!validation) return [];
|
|
139
|
-
|
|
140
|
-
const messages = [
|
|
141
|
-
...validation.findings
|
|
142
|
-
.filter((finding) => finding.severity === severity)
|
|
143
|
-
.map((finding) => finding.message),
|
|
144
|
-
];
|
|
145
|
-
for (const entry of validation.entries) {
|
|
146
|
-
for (const finding of entry.findings) {
|
|
147
|
-
if (finding.severity === severity) {
|
|
148
|
-
messages.push(finding.message);
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return [...new Set(messages)];
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function normalizeOptionalString(value) {
|
|
156
|
-
if (typeof value !== "string") return null;
|
|
157
|
-
const normalized = value.trim();
|
|
158
|
-
return normalized.length > 0 ? normalized : null;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function readJsonIfExists(filePath) {
|
|
162
|
-
if (!fs.existsSync(filePath)) return undefined;
|
|
163
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
164
|
-
}
|