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