as-test 1.1.5 → 1.1.7
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 +16 -1
- package/README.md +4 -9
- package/assembly/index.ts +10 -15
- package/assembly/src/expectation.ts +11 -11
- package/assembly/src/fuzz.ts +11 -7
- package/assembly/src/log.ts +2 -2
- package/assembly/src/suite.ts +5 -5
- package/assembly/src/tests.ts +8 -8
- package/assembly/util/wipc.ts +5 -1
- package/bin/build-worker-pool.js +146 -142
- package/bin/build-worker.js +37 -34
- package/bin/commands/build-core.js +577 -465
- package/bin/commands/build.js +49 -29
- package/bin/commands/clean-core.js +120 -113
- package/bin/commands/clean.js +14 -8
- package/bin/commands/doctor-core.js +288 -289
- package/bin/commands/doctor.js +1 -1
- package/bin/commands/fuzz-core.js +467 -414
- package/bin/commands/fuzz.js +27 -10
- package/bin/commands/init-core.js +905 -794
- package/bin/commands/init.js +2 -2
- package/bin/commands/run-core.js +2675 -2344
- package/bin/commands/run.js +43 -25
- package/bin/commands/test.js +56 -32
- package/bin/commands/web-runner-source.js +1 -1
- package/bin/commands/web-session.js +516 -525
- package/bin/coverage-points.js +363 -341
- package/bin/crash-store.js +56 -66
- package/bin/index.js +4092 -3150
- package/bin/reporters/default.js +1090 -890
- package/bin/reporters/tap.js +319 -325
- package/bin/types.js +67 -67
- package/bin/util.js +1290 -1239
- package/bin/wipc.js +70 -73
- package/lib/build/index.d.ts +3 -1
- package/lib/build/index.js +1039 -1034
- package/lib/build/web-runner/client.js +1 -1
- package/lib/build/web-runner/html.js +1 -1
- package/lib/build/web-runner/worker.js +1 -1
- package/package.json +6 -3
- package/transform/lib/coverage.js +4 -4
- package/transform/lib/log.js +9 -5
- package/assembly/util/json.ts +0 -112
package/bin/reporters/tap.js
CHANGED
|
@@ -1,378 +1,372 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
2
|
import { mkdirSync, writeFileSync } from "fs";
|
|
3
3
|
export function createTapReporter(context, config = {}) {
|
|
4
|
-
|
|
4
|
+
return new TapReporter(context, normalizeTapConfig(config));
|
|
5
5
|
}
|
|
6
6
|
export const createReporter = (context) => {
|
|
7
|
-
|
|
7
|
+
return createTapReporter(context);
|
|
8
8
|
};
|
|
9
9
|
class TapReporter {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
constructor(context, config) {
|
|
11
|
+
this.context = context;
|
|
12
|
+
this.config = config;
|
|
13
|
+
this.pendingRunEvent = null;
|
|
14
|
+
this.pendingFuzzEvent = null;
|
|
15
|
+
this.pendingFuzzPoints = [];
|
|
16
|
+
}
|
|
17
|
+
onRunComplete(event) {
|
|
18
|
+
this.pendingRunEvent = event;
|
|
19
|
+
}
|
|
20
|
+
onFuzzComplete(event) {
|
|
21
|
+
this.pendingFuzzEvent = event;
|
|
22
|
+
}
|
|
23
|
+
onFuzzFileComplete(event) {
|
|
24
|
+
this.pendingFuzzPoints.push(
|
|
25
|
+
...collectFuzzTapPoints({
|
|
26
|
+
results: event.results,
|
|
27
|
+
time: event.results.reduce((sum, item) => sum + item.time, 0),
|
|
28
|
+
buildTime: event.results.reduce((sum, item) => sum + item.buildTime, 0),
|
|
29
|
+
fuzzingSummary: { failed: 0, skipped: 0, total: 0 },
|
|
30
|
+
suiteSummary: { failed: 0, skipped: 0, total: 0 },
|
|
31
|
+
modeSummary: { failed: 0, skipped: 0, total: 0 },
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
flush() {
|
|
36
|
+
if (!this.pendingRunEvent && !this.pendingFuzzEvent) return;
|
|
37
|
+
const points = [];
|
|
38
|
+
if (this.pendingRunEvent) {
|
|
39
|
+
points.push(...collectTapPoints(this.pendingRunEvent.reports));
|
|
19
40
|
}
|
|
20
|
-
|
|
21
|
-
|
|
41
|
+
if (this.pendingFuzzPoints.length) {
|
|
42
|
+
points.push(...this.pendingFuzzPoints);
|
|
43
|
+
} else if (this.pendingFuzzEvent) {
|
|
44
|
+
points.push(...collectFuzzTapPoints(this.pendingFuzzEvent));
|
|
22
45
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
fuzzingSummary: { failed: 0, skipped: 0, total: 0 },
|
|
29
|
-
suiteSummary: { failed: 0, skipped: 0, total: 0 },
|
|
30
|
-
modeSummary: { failed: 0, skipped: 0, total: 0 },
|
|
31
|
-
}));
|
|
46
|
+
const output = buildTapDocument(points);
|
|
47
|
+
this.context.stdout.write(output);
|
|
48
|
+
for (const point of points) {
|
|
49
|
+
if (point.status != "fail") continue;
|
|
50
|
+
emitGitHubAnnotation(this.context, point);
|
|
32
51
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
else if (this.pendingFuzzEvent) {
|
|
44
|
-
points.push(...collectFuzzTapPoints(this.pendingFuzzEvent));
|
|
45
|
-
}
|
|
46
|
-
const output = buildTapDocument(points);
|
|
47
|
-
this.context.stdout.write(output);
|
|
48
|
-
for (const point of points) {
|
|
49
|
-
if (point.status != "fail")
|
|
50
|
-
continue;
|
|
51
|
-
emitGitHubAnnotation(this.context, point);
|
|
52
|
-
}
|
|
53
|
-
this.writeArtifacts(points, output);
|
|
54
|
-
this.pendingRunEvent = null;
|
|
55
|
-
this.pendingFuzzEvent = null;
|
|
56
|
-
this.pendingFuzzPoints = [];
|
|
52
|
+
this.writeArtifacts(points, output);
|
|
53
|
+
this.pendingRunEvent = null;
|
|
54
|
+
this.pendingFuzzEvent = null;
|
|
55
|
+
this.pendingFuzzPoints = [];
|
|
56
|
+
}
|
|
57
|
+
writeArtifacts(points, output) {
|
|
58
|
+
if (this.config.mode == "per-file") {
|
|
59
|
+
this.writePerFileArtifacts(points);
|
|
60
|
+
return;
|
|
57
61
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
const outFile = path.resolve(process.cwd(), this.config.outFile);
|
|
63
|
+
mkdirSync(path.dirname(outFile), { recursive: true });
|
|
64
|
+
writeFileSync(outFile, output);
|
|
65
|
+
}
|
|
66
|
+
writePerFileArtifacts(points) {
|
|
67
|
+
const groups = new Map();
|
|
68
|
+
for (const point of points) {
|
|
69
|
+
const key = point.file?.length ? point.file : "unknown";
|
|
70
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
71
|
+
groups.get(key).push(point);
|
|
66
72
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
mkdirSync(outDir, { recursive: true });
|
|
78
|
-
for (const [fileKey, filePoints] of groups) {
|
|
79
|
-
const fileName = fileKey == "unknown"
|
|
80
|
-
? `unknown-${++unknownIndex}.tap`
|
|
81
|
-
: toTapFileName(fileKey);
|
|
82
|
-
const outFile = path.join(outDir, fileName);
|
|
83
|
-
writeFileSync(outFile, buildTapDocument(filePoints));
|
|
84
|
-
}
|
|
73
|
+
let unknownIndex = 0;
|
|
74
|
+
const outDir = path.resolve(process.cwd(), this.config.outDir);
|
|
75
|
+
mkdirSync(outDir, { recursive: true });
|
|
76
|
+
for (const [fileKey, filePoints] of groups) {
|
|
77
|
+
const fileName =
|
|
78
|
+
fileKey == "unknown"
|
|
79
|
+
? `unknown-${++unknownIndex}.tap`
|
|
80
|
+
: toTapFileName(fileKey);
|
|
81
|
+
const outFile = path.join(outDir, fileName);
|
|
82
|
+
writeFileSync(outFile, buildTapDocument(filePoints));
|
|
85
83
|
}
|
|
84
|
+
}
|
|
86
85
|
}
|
|
87
86
|
function normalizeTapConfig(config) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
87
|
+
const mode = config.mode == "per-file" ? "per-file" : "single-file";
|
|
88
|
+
const outDir =
|
|
89
|
+
typeof config.outDir == "string" && config.outDir.trim().length
|
|
90
|
+
? config.outDir.trim()
|
|
91
|
+
: "./.as-test/reports";
|
|
92
|
+
const outFile =
|
|
93
|
+
typeof config.outFile == "string" && config.outFile.trim().length
|
|
94
|
+
? config.outFile.trim()
|
|
95
|
+
: path.join(outDir, "report.tap");
|
|
96
|
+
return {
|
|
97
|
+
mode,
|
|
98
|
+
outDir,
|
|
99
|
+
outFile,
|
|
100
|
+
};
|
|
100
101
|
}
|
|
101
102
|
function toTapFileName(file) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
const normalized = file
|
|
104
|
+
.replace(/^[.\\/]+/, "")
|
|
105
|
+
.replace(/[\\/]/g, "__")
|
|
106
|
+
.replace(/\.[^.]+$/, "");
|
|
107
|
+
return `${normalized}.tap`;
|
|
107
108
|
}
|
|
108
109
|
function buildTapDocument(points) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
if (point.status == "skip") {
|
|
128
|
-
totals.skip++;
|
|
129
|
-
lines.push(`ok ${id} - ${name} # SKIP`);
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
totals.pass++;
|
|
133
|
-
lines.push(`ok ${id} - ${name}`);
|
|
110
|
+
const totals = {
|
|
111
|
+
pass: 0,
|
|
112
|
+
fail: 0,
|
|
113
|
+
skip: 0,
|
|
114
|
+
};
|
|
115
|
+
const lines = [];
|
|
116
|
+
lines.push("TAP version 13");
|
|
117
|
+
lines.push(`1..${points.length}`);
|
|
118
|
+
for (let i = 0; i < points.length; i++) {
|
|
119
|
+
const point = points[i];
|
|
120
|
+
const id = i + 1;
|
|
121
|
+
const name = sanitizeTap(point.name.length ? point.name : `test ${id}`);
|
|
122
|
+
if (point.status == "fail") {
|
|
123
|
+
totals.fail++;
|
|
124
|
+
lines.push(`not ok ${id} - ${name}`);
|
|
125
|
+
lines.push(...buildFailDetails(point));
|
|
126
|
+
continue;
|
|
134
127
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
128
|
+
if (point.status == "skip") {
|
|
129
|
+
totals.skip++;
|
|
130
|
+
lines.push(`ok ${id} - ${name} # SKIP`);
|
|
131
|
+
continue;
|
|
139
132
|
}
|
|
140
|
-
|
|
141
|
-
|
|
133
|
+
totals.pass++;
|
|
134
|
+
lines.push(`ok ${id} - ${name}`);
|
|
135
|
+
}
|
|
136
|
+
lines.push(`# tests ${points.length}`);
|
|
137
|
+
lines.push(`# pass ${totals.pass}`);
|
|
138
|
+
if (totals.skip) {
|
|
139
|
+
lines.push(`# skip ${totals.skip}`);
|
|
140
|
+
}
|
|
141
|
+
lines.push(`# fail ${totals.fail}`);
|
|
142
|
+
return lines.join("\n") + "\n";
|
|
142
143
|
}
|
|
143
144
|
function buildFailDetails(point) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
145
|
+
const lines = [
|
|
146
|
+
" ---",
|
|
147
|
+
` message: ${JSON.stringify(point.message ?? "assertion failed")}`,
|
|
148
|
+
];
|
|
149
|
+
if (point.file) {
|
|
150
|
+
lines.push(` file: ${JSON.stringify(point.file)}`);
|
|
151
|
+
}
|
|
152
|
+
if (point.line) {
|
|
153
|
+
lines.push(` line: ${point.line}`);
|
|
154
|
+
}
|
|
155
|
+
if (point.column) {
|
|
156
|
+
lines.push(` column: ${point.column}`);
|
|
157
|
+
}
|
|
158
|
+
if (point.matcher) {
|
|
159
|
+
lines.push(` matcher: ${JSON.stringify(point.matcher)}`);
|
|
160
|
+
}
|
|
161
|
+
if (point.expected != null) {
|
|
162
|
+
lines.push(` expected: ${JSON.stringify(point.expected)}`);
|
|
163
|
+
}
|
|
164
|
+
if (point.actual != null) {
|
|
165
|
+
lines.push(` actual: ${JSON.stringify(point.actual)}`);
|
|
166
|
+
}
|
|
167
|
+
if (point.durationMs != null) {
|
|
168
|
+
lines.push(` duration_ms: ${Math.round(point.durationMs * 1000) / 1000}`);
|
|
169
|
+
}
|
|
170
|
+
lines.push(" ...");
|
|
171
|
+
return lines;
|
|
171
172
|
}
|
|
172
173
|
function collectTapPoints(reports) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
: [];
|
|
182
|
-
for (const suite of suites) {
|
|
183
|
-
collectTapPointsFromSuite(suite, file, [], points);
|
|
184
|
-
}
|
|
174
|
+
const points = [];
|
|
175
|
+
if (!Array.isArray(reports)) return points;
|
|
176
|
+
for (const report of reports) {
|
|
177
|
+
const reportAny = report;
|
|
178
|
+
const file = String(reportAny.file ?? "");
|
|
179
|
+
const suites = Array.isArray(reportAny.suites) ? reportAny.suites : [];
|
|
180
|
+
for (const suite of suites) {
|
|
181
|
+
collectTapPointsFromSuite(suite, file, [], points);
|
|
185
182
|
}
|
|
186
|
-
|
|
183
|
+
}
|
|
184
|
+
return points;
|
|
187
185
|
}
|
|
188
186
|
function collectFuzzTapPoints(event) {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
187
|
+
const points = [];
|
|
188
|
+
for (const result of event.results) {
|
|
189
|
+
const durationMs = result.time;
|
|
190
|
+
if (!result.fuzzers.length) {
|
|
191
|
+
points.push({
|
|
192
|
+
name: `fuzz ${path.basename(result.file)}`,
|
|
193
|
+
status: result.crashes > 0 ? "fail" : "ok",
|
|
194
|
+
file: result.file,
|
|
195
|
+
message:
|
|
196
|
+
result.crashes > 0
|
|
197
|
+
? buildFuzzMessage(result.runs, result.seed, result.crashFiles[0])
|
|
198
|
+
: `fuzz passed after ${result.runs} runs (seed ${result.seed})`,
|
|
199
|
+
durationMs,
|
|
200
|
+
});
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
for (const fuzzer of result.fuzzers) {
|
|
204
|
+
const crashed = fuzzer.crashed > 0 || result.crashes > 0;
|
|
205
|
+
const failed = crashed || fuzzer.failed > 0;
|
|
206
|
+
points.push({
|
|
207
|
+
name: `fuzz ${path.basename(result.file)} > ${fuzzer.name}`,
|
|
208
|
+
status: failed ? "fail" : "ok",
|
|
209
|
+
file: result.file,
|
|
210
|
+
message: failed
|
|
211
|
+
? buildFuzzerFailureMessage(result, fuzzer)
|
|
212
|
+
: `fuzz passed after ${fuzzer.runs} runs (seed ${result.seed})`,
|
|
213
|
+
durationMs: fuzzer.time.end - fuzzer.time.start,
|
|
214
|
+
});
|
|
217
215
|
}
|
|
218
|
-
|
|
216
|
+
}
|
|
217
|
+
return points;
|
|
219
218
|
}
|
|
220
219
|
function buildFuzzerFailureMessage(result, fuzzer) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
220
|
+
if (fuzzer.crashed > 0 || result.crashes > 0) {
|
|
221
|
+
return buildFuzzMessage(result.runs, result.seed, result.crashFiles[0]);
|
|
222
|
+
}
|
|
223
|
+
const failureSeeds = fuzzer.failures?.length
|
|
224
|
+
? `, failing seeds ${fuzzer.failures.map((failure) => failure.seed).join(", ")}`
|
|
225
|
+
: "";
|
|
226
|
+
if (fuzzer.failure?.message?.length) {
|
|
227
|
+
return `${fuzzer.failure.message} (runs ${fuzzer.runs}, seed ${result.seed}${failureSeeds})`;
|
|
228
|
+
}
|
|
229
|
+
return `fuzz failed after ${fuzzer.runs} runs (seed ${result.seed}${failureSeeds})`;
|
|
231
230
|
}
|
|
232
231
|
function buildFuzzMessage(runs, seed, crashFile) {
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
const crashSuffix = crashFile?.length ? `, crash ${crashFile}` : "";
|
|
233
|
+
return `fuzz crashed after ${runs} runs (seed ${seed}${crashSuffix})`;
|
|
235
234
|
}
|
|
236
235
|
function collectTapPointsFromSuite(suite, file, pathStack, points) {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
else if (childSuites.length == 0 &&
|
|
276
|
-
(suiteKind == "test" ||
|
|
277
|
-
suiteKind == "it" ||
|
|
278
|
-
suiteKind == "xtest" ||
|
|
279
|
-
suiteKind == "xit")) {
|
|
280
|
-
points.push({
|
|
281
|
-
name: fullPath.join(" > "),
|
|
282
|
-
status: normalizeStatus(suiteAny.verdict),
|
|
283
|
-
file: localFile,
|
|
284
|
-
durationMs,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
for (const child of childSuites) {
|
|
288
|
-
collectTapPointsFromSuite(child, localFile, fullPath, points);
|
|
236
|
+
const suiteAny = suite;
|
|
237
|
+
const description = String(suiteAny.description ?? "suite");
|
|
238
|
+
const fullPath = [...pathStack, description];
|
|
239
|
+
const localFile = suiteAny.file ? String(suiteAny.file) : file;
|
|
240
|
+
const childSuites = Array.isArray(suiteAny.suites) ? suiteAny.suites : [];
|
|
241
|
+
const tests = Array.isArray(suiteAny.tests) ? suiteAny.tests : [];
|
|
242
|
+
const suiteKind = String(suiteAny.kind ?? "");
|
|
243
|
+
const durationMs = suiteDuration(suiteAny.time);
|
|
244
|
+
if (tests.length > 0) {
|
|
245
|
+
for (let i = 0; i < tests.length; i++) {
|
|
246
|
+
const test = tests[i];
|
|
247
|
+
const location = parseLocation(test.location);
|
|
248
|
+
const name =
|
|
249
|
+
tests.length > 1
|
|
250
|
+
? `${fullPath.join(" > ")} #${i + 1}`
|
|
251
|
+
: fullPath.join(" > ");
|
|
252
|
+
const status = normalizeStatus(test.verdict);
|
|
253
|
+
const matcher = stringifyValue(test.instr);
|
|
254
|
+
const expected = stringifyValue(test.right);
|
|
255
|
+
const actual = stringifyValue(test.left);
|
|
256
|
+
const message = buildFailureMessage(
|
|
257
|
+
stringifyValue(test.message),
|
|
258
|
+
matcher,
|
|
259
|
+
expected,
|
|
260
|
+
actual,
|
|
261
|
+
);
|
|
262
|
+
points.push({
|
|
263
|
+
name,
|
|
264
|
+
status,
|
|
265
|
+
file: localFile,
|
|
266
|
+
line: location.line,
|
|
267
|
+
column: location.column,
|
|
268
|
+
matcher,
|
|
269
|
+
expected,
|
|
270
|
+
actual,
|
|
271
|
+
message,
|
|
272
|
+
durationMs,
|
|
273
|
+
});
|
|
289
274
|
}
|
|
275
|
+
} else if (
|
|
276
|
+
childSuites.length == 0 &&
|
|
277
|
+
(suiteKind == "test" ||
|
|
278
|
+
suiteKind == "it" ||
|
|
279
|
+
suiteKind == "xtest" ||
|
|
280
|
+
suiteKind == "xit")
|
|
281
|
+
) {
|
|
282
|
+
points.push({
|
|
283
|
+
name: fullPath.join(" > "),
|
|
284
|
+
status: normalizeStatus(suiteAny.verdict),
|
|
285
|
+
file: localFile,
|
|
286
|
+
durationMs,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
for (const child of childSuites) {
|
|
290
|
+
collectTapPointsFromSuite(child, localFile, fullPath, points);
|
|
291
|
+
}
|
|
290
292
|
}
|
|
291
293
|
function suiteDuration(value) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
return end - start;
|
|
294
|
+
const time = value;
|
|
295
|
+
if (!time) return undefined;
|
|
296
|
+
const start = Number(time.start ?? 0);
|
|
297
|
+
const end = Number(time.end ?? 0);
|
|
298
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
return end - start;
|
|
301
302
|
}
|
|
302
303
|
function parseLocation(value) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
};
|
|
304
|
+
const text = String(value ?? "").trim();
|
|
305
|
+
if (!text.length) return {};
|
|
306
|
+
const match = /^(\d+)(?::(\d+))?$/.exec(text);
|
|
307
|
+
if (!match) return {};
|
|
308
|
+
const line = Number(match[1]);
|
|
309
|
+
const column = match[2] ? Number(match[2]) : undefined;
|
|
310
|
+
return {
|
|
311
|
+
line: Number.isFinite(line) && line > 0 ? line : undefined,
|
|
312
|
+
column:
|
|
313
|
+
typeof column == "number" && Number.isFinite(column) && column > 0
|
|
314
|
+
? column
|
|
315
|
+
: undefined,
|
|
316
|
+
};
|
|
317
317
|
}
|
|
318
318
|
function normalizeStatus(verdict) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
return "ok";
|
|
324
|
-
return "skip";
|
|
319
|
+
const value = String(verdict ?? "none");
|
|
320
|
+
if (value == "fail") return "fail";
|
|
321
|
+
if (value == "ok") return "ok";
|
|
322
|
+
return "skip";
|
|
325
323
|
}
|
|
326
324
|
function stringifyValue(value) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
catch {
|
|
335
|
-
return String(value);
|
|
336
|
-
}
|
|
325
|
+
if (value == null) return "";
|
|
326
|
+
if (typeof value == "string") return value;
|
|
327
|
+
try {
|
|
328
|
+
return JSON.stringify(value);
|
|
329
|
+
} catch {
|
|
330
|
+
return String(value);
|
|
331
|
+
}
|
|
337
332
|
}
|
|
338
333
|
function buildFailureMessage(message, matcher, expected, actual) {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return `${matcher} failed`;
|
|
346
|
-
return "assertion failed";
|
|
334
|
+
if (message.length) return message;
|
|
335
|
+
if (matcher.length && expected.length && actual.length) {
|
|
336
|
+
return `${matcher} expected ${expected} but received ${actual}`;
|
|
337
|
+
}
|
|
338
|
+
if (matcher.length) return `${matcher} failed`;
|
|
339
|
+
return "assertion failed";
|
|
347
340
|
}
|
|
348
341
|
function sanitizeTap(name) {
|
|
349
|
-
|
|
342
|
+
return name.replace(/\s+/g, " ").replace(/#/g, "\\#").trim();
|
|
350
343
|
}
|
|
351
344
|
function emitGitHubAnnotation(context, point) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
345
|
+
if (process.env.GITHUB_ACTIONS != "true" || point.status != "fail") return;
|
|
346
|
+
const properties = [];
|
|
347
|
+
if (point.file) {
|
|
348
|
+
properties.push(`file=${escapeGithubValue(point.file, true)}`);
|
|
349
|
+
}
|
|
350
|
+
if (point.line) {
|
|
351
|
+
properties.push(`line=${point.line}`);
|
|
352
|
+
}
|
|
353
|
+
if (point.column) {
|
|
354
|
+
properties.push(`col=${point.column}`);
|
|
355
|
+
}
|
|
356
|
+
properties.push(`title=${escapeGithubValue("as-test", true)}`);
|
|
357
|
+
const message = point.message?.length ? point.message : "assertion failed";
|
|
358
|
+
const detail = `${message} | test=${point.name}`;
|
|
359
|
+
context.stdout.write(
|
|
360
|
+
`::error ${properties.join(",")}::${escapeGithubValue(detail)}\n`,
|
|
361
|
+
);
|
|
368
362
|
}
|
|
369
363
|
function escapeGithubValue(value, property = false) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
364
|
+
let output = value
|
|
365
|
+
.replace(/%/g, "%25")
|
|
366
|
+
.replace(/\r/g, "%0D")
|
|
367
|
+
.replace(/\n/g, "%0A");
|
|
368
|
+
if (property) {
|
|
369
|
+
output = output.replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
370
|
+
}
|
|
371
|
+
return output;
|
|
378
372
|
}
|