as-test 1.0.11 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -1
- package/README.md +15 -0
- package/as-test.config.schema.json +1 -3
- package/assembly/__fuzz__/seed-perf.fuzz.ts +68 -0
- package/assembly/src/fuzz.ts +156 -55
- package/assembly/util/wipc.ts +30 -1
- package/bin/commands/fuzz-core.js +110 -15
- package/bin/commands/fuzz.js +2 -1
- package/bin/commands/init-core.js +0 -1
- package/bin/commands/run-core.js +321 -7
- package/bin/commands/run.js +2 -1
- package/bin/commands/test.js +3 -1
- package/bin/index.js +102 -29
- package/bin/reporters/default.js +61 -52
- package/bin/types.js +1 -1
- package/bin/util.js +2 -2
- package/package.json +10 -8
- package/transform/lib/builder.js +14 -15
- package/transform/lib/coverage.js +11 -12
- package/transform/lib/index.js +0 -1
- package/transform/lib/linker.js +3 -4
- package/transform/lib/location.js +0 -1
- package/transform/lib/log.js +0 -1
- package/transform/lib/mock.js +15 -9
- package/transform/lib/range.js +0 -1
- package/transform/lib/types.js +0 -1
- package/transform/lib/util.js +0 -1
- package/transform/lib/visitor.js +64 -65
package/bin/commands/run-core.js
CHANGED
|
@@ -327,6 +327,9 @@ function formatReadableLog(file, suites, modeName, buildCommand, runCommand, sna
|
|
|
327
327
|
lines.push("", "Failures:");
|
|
328
328
|
for (const failure of failures) {
|
|
329
329
|
lines.push(`FAIL ${failure.title}${failure.where.length ? ` (${failure.where})` : ""}`);
|
|
330
|
+
if (failure.suitePath.length) {
|
|
331
|
+
lines.push(`Repro: ${buildSuiteReproCommand(file, failure.suitePath, modeName)}`);
|
|
332
|
+
}
|
|
330
333
|
if (failure.message.length)
|
|
331
334
|
lines.push(`Message: ${failure.message}`);
|
|
332
335
|
if (failure.left.length)
|
|
@@ -352,6 +355,139 @@ function formatInvocation(invocation) {
|
|
|
352
355
|
.map((part) => (/[\s"'\\]/.test(part) ? JSON.stringify(part) : part))
|
|
353
356
|
.join(" ");
|
|
354
357
|
}
|
|
358
|
+
function filterSelectedSuites(suites, selectors, file, modeName) {
|
|
359
|
+
const annotated = annotateSuitePaths(suites, []);
|
|
360
|
+
const matches = resolveSuiteSelectionMatches(annotated, selectors, file);
|
|
361
|
+
const selected = new Set(matches.map((match) => match.resolvedPath));
|
|
362
|
+
return cloneSelectedSuites(annotated, selected, file, modeName);
|
|
363
|
+
}
|
|
364
|
+
function annotateSuitePaths(suites, pathParts) {
|
|
365
|
+
return suites.map((suite) => annotateSuiteNode(suite, pathParts));
|
|
366
|
+
}
|
|
367
|
+
function annotateSuiteNode(suite, pathParts) {
|
|
368
|
+
const description = String(suite?.description ?? "unknown");
|
|
369
|
+
const slug = slugifySelectorSegment(description);
|
|
370
|
+
const nextParts = [...pathParts, slug];
|
|
371
|
+
const nextSuites = Array.isArray(suite?.suites)
|
|
372
|
+
? suite.suites
|
|
373
|
+
: [];
|
|
374
|
+
const annotatedSuites = annotateSuitePaths(nextSuites, nextParts);
|
|
375
|
+
return {
|
|
376
|
+
...suite,
|
|
377
|
+
path: nextParts.join("/"),
|
|
378
|
+
suites: annotatedSuites,
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function resolveSuiteSelectionMatches(suites, selectors, file) {
|
|
382
|
+
const matches = [];
|
|
383
|
+
for (const selector of selectors) {
|
|
384
|
+
const normalized = selector.trim();
|
|
385
|
+
if (!normalized.length)
|
|
386
|
+
continue;
|
|
387
|
+
if (normalized.includes("/")) {
|
|
388
|
+
const resolved = resolveExplicitSuitePath(suites, normalized);
|
|
389
|
+
if (!resolved) {
|
|
390
|
+
throw new Error(`No suites matched "${selector}" in ${path.basename(file)}.`);
|
|
391
|
+
}
|
|
392
|
+
matches.push({
|
|
393
|
+
kind: "path",
|
|
394
|
+
raw: selector,
|
|
395
|
+
resolvedPath: resolved.path,
|
|
396
|
+
depth: resolved.depth,
|
|
397
|
+
});
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const resolved = resolveBareSuiteSelector(suites, normalized);
|
|
401
|
+
if (!resolved) {
|
|
402
|
+
throw new Error(`No suites matched "${selector}" in ${path.basename(file)}.`);
|
|
403
|
+
}
|
|
404
|
+
matches.push({
|
|
405
|
+
kind: "bare",
|
|
406
|
+
raw: selector,
|
|
407
|
+
resolvedPath: resolved.path,
|
|
408
|
+
depth: resolved.depth,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
return matches;
|
|
412
|
+
}
|
|
413
|
+
function resolveExplicitSuitePath(suites, selector) {
|
|
414
|
+
const normalized = selector
|
|
415
|
+
.split("/")
|
|
416
|
+
.map((part) => slugifySelectorSegment(part))
|
|
417
|
+
.filter((part) => part.length)
|
|
418
|
+
.join("/");
|
|
419
|
+
if (!normalized.length)
|
|
420
|
+
return null;
|
|
421
|
+
let match = null;
|
|
422
|
+
walkSuites(suites, (suite, depth) => {
|
|
423
|
+
if (suite.path == normalized) {
|
|
424
|
+
match = { path: suite.path, depth };
|
|
425
|
+
return true;
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
428
|
+
});
|
|
429
|
+
return match;
|
|
430
|
+
}
|
|
431
|
+
function resolveBareSuiteSelector(suites, selector) {
|
|
432
|
+
const slug = slugifySelectorSegment(selector);
|
|
433
|
+
if (!slug.length)
|
|
434
|
+
return null;
|
|
435
|
+
const matches = [];
|
|
436
|
+
walkSuites(suites, (suite, depth) => {
|
|
437
|
+
const leaf = String(suite.path ?? "").split("/").pop() ?? "";
|
|
438
|
+
if (leaf == slug) {
|
|
439
|
+
matches.push({ path: String(suite.path), depth });
|
|
440
|
+
}
|
|
441
|
+
return false;
|
|
442
|
+
});
|
|
443
|
+
if (!matches.length)
|
|
444
|
+
return null;
|
|
445
|
+
matches.sort((a, b) => a.depth - b.depth || a.path.localeCompare(b.path));
|
|
446
|
+
const shallowest = matches[0];
|
|
447
|
+
const ambiguous = matches.filter((match) => match.depth == shallowest.depth);
|
|
448
|
+
if (ambiguous.length > 1) {
|
|
449
|
+
throw new Error(`Suite selector "${selector}" is ambiguous. Matches: ${ambiguous.map((match) => match.path).join(", ")}`);
|
|
450
|
+
}
|
|
451
|
+
return shallowest;
|
|
452
|
+
}
|
|
453
|
+
function walkSuites(suites, visitor, depth = 0) {
|
|
454
|
+
for (const suite of suites) {
|
|
455
|
+
if (visitor(suite, depth))
|
|
456
|
+
return true;
|
|
457
|
+
const childSuites = Array.isArray(suite?.suites) ? suite.suites : [];
|
|
458
|
+
if (walkSuites(childSuites, visitor, depth + 1))
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
function cloneSelectedSuites(suites, selected, file, modeName) {
|
|
464
|
+
const out = [];
|
|
465
|
+
for (const suite of suites) {
|
|
466
|
+
const childSuites = Array.isArray(suite.suites) ? suite.suites : [];
|
|
467
|
+
const selectedChildren = cloneSelectedSuites(childSuites, selected, file, modeName);
|
|
468
|
+
const keep = selected.has(String(suite.path ?? "")) || selectedChildren.length > 0;
|
|
469
|
+
if (!keep)
|
|
470
|
+
continue;
|
|
471
|
+
out.push({
|
|
472
|
+
...suite,
|
|
473
|
+
file,
|
|
474
|
+
modeName,
|
|
475
|
+
suites: selectedChildren,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
return out;
|
|
479
|
+
}
|
|
480
|
+
function slugifySelectorSegment(value) {
|
|
481
|
+
return value
|
|
482
|
+
.trim()
|
|
483
|
+
.toLowerCase()
|
|
484
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
485
|
+
.replace(/^-+|-+$/g, "");
|
|
486
|
+
}
|
|
487
|
+
function buildSuiteReproCommand(file, suitePath, modeName) {
|
|
488
|
+
const modeArg = modeName && modeName != "default" ? ` --mode ${modeName}` : "";
|
|
489
|
+
return `ast run ${file}${modeArg} --suite ${suitePath}`;
|
|
490
|
+
}
|
|
355
491
|
function collectReadableFailures(suites, file, pathParts) {
|
|
356
492
|
const out = [];
|
|
357
493
|
for (const suite of suites) {
|
|
@@ -372,6 +508,7 @@ function collectReadableFailures(suites, file, pathParts) {
|
|
|
372
508
|
message: String(test.message ?? ""),
|
|
373
509
|
left: JSON.stringify(test.left ?? ""),
|
|
374
510
|
right: JSON.stringify(test.right ?? ""),
|
|
511
|
+
suitePath: String(suiteAny.path ?? ""),
|
|
375
512
|
});
|
|
376
513
|
}
|
|
377
514
|
const childSuites = Array.isArray(suiteAny.suites)
|
|
@@ -492,6 +629,9 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
492
629
|
throw new Error(`Failed to run ${path.basename(file)} in mode ${modeLabel} with ${details}`);
|
|
493
630
|
}
|
|
494
631
|
const normalized = normalizeReport(report);
|
|
632
|
+
const selectedSuites = options.suiteSelectors?.length
|
|
633
|
+
? filterSelectedSuites(normalized.suites, options.suiteSelectors, file, options.modeName ?? "default")
|
|
634
|
+
: normalized.suites;
|
|
495
635
|
snapshotStore.flush();
|
|
496
636
|
snapshotSummary.matched += snapshotStore.matched;
|
|
497
637
|
snapshotSummary.created += snapshotStore.created;
|
|
@@ -500,7 +640,7 @@ export async function run(flags = {}, configPath = DEFAULT_CONFIG_PATH, selector
|
|
|
500
640
|
reports.push({
|
|
501
641
|
file,
|
|
502
642
|
modeName: options.modeName ?? "default",
|
|
503
|
-
suites:
|
|
643
|
+
suites: selectedSuites,
|
|
504
644
|
coverage: normalized.coverage,
|
|
505
645
|
runCommand: runCommandForLog,
|
|
506
646
|
snapshotSummary: {
|
|
@@ -1483,6 +1623,31 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1483
1623
|
let stderrPendingLine = "";
|
|
1484
1624
|
let stdoutBuffer = "";
|
|
1485
1625
|
let spawnError = null;
|
|
1626
|
+
let sawChannelClose = false;
|
|
1627
|
+
const runtimeEvents = {
|
|
1628
|
+
sawFileStart: false,
|
|
1629
|
+
sawFileEnd: false,
|
|
1630
|
+
fileName: path.basename(specFile),
|
|
1631
|
+
fileVerdict: "none",
|
|
1632
|
+
fileTime: "",
|
|
1633
|
+
suiteStarts: 0,
|
|
1634
|
+
suiteEnds: 0,
|
|
1635
|
+
assertionFails: 0,
|
|
1636
|
+
warnings: 0,
|
|
1637
|
+
logs: 0,
|
|
1638
|
+
};
|
|
1639
|
+
const reportStream = {
|
|
1640
|
+
dataFrames: 0,
|
|
1641
|
+
dataBytes: 0,
|
|
1642
|
+
sawChunkStart: false,
|
|
1643
|
+
sawChunkEnd: false,
|
|
1644
|
+
chunkCountExpected: 0,
|
|
1645
|
+
chunkBytesExpected: 0,
|
|
1646
|
+
chunkTotalBytesExpected: 0,
|
|
1647
|
+
chunkFramesReceived: 0,
|
|
1648
|
+
chunkBytesReceived: 0,
|
|
1649
|
+
chunks: [],
|
|
1650
|
+
};
|
|
1486
1651
|
child.on("error", (error) => {
|
|
1487
1652
|
spawnError = error;
|
|
1488
1653
|
});
|
|
@@ -1512,6 +1677,7 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1512
1677
|
const event = msg;
|
|
1513
1678
|
const kind = String(event.kind ?? "");
|
|
1514
1679
|
if (kind === "event:assert-fail") {
|
|
1680
|
+
runtimeEvents.assertionFails++;
|
|
1515
1681
|
reporter.onAssertionFail?.({
|
|
1516
1682
|
key: String(event.key ?? ""),
|
|
1517
1683
|
instr: String(event.instr ?? ""),
|
|
@@ -1522,6 +1688,8 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1522
1688
|
return;
|
|
1523
1689
|
}
|
|
1524
1690
|
if (kind === "event:file-start") {
|
|
1691
|
+
runtimeEvents.sawFileStart = true;
|
|
1692
|
+
runtimeEvents.fileName = String(event.file ?? runtimeEvents.fileName);
|
|
1525
1693
|
reporter.onFileStart?.({
|
|
1526
1694
|
file: String(event.file ?? "unknown"),
|
|
1527
1695
|
depth: 0,
|
|
@@ -1531,6 +1699,10 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1531
1699
|
return;
|
|
1532
1700
|
}
|
|
1533
1701
|
if (kind === "event:file-end") {
|
|
1702
|
+
runtimeEvents.sawFileEnd = true;
|
|
1703
|
+
runtimeEvents.fileName = String(event.file ?? runtimeEvents.fileName);
|
|
1704
|
+
runtimeEvents.fileVerdict = String(event.verdict ?? "none");
|
|
1705
|
+
runtimeEvents.fileTime = String(event.time ?? "");
|
|
1534
1706
|
reporter.onFileEnd?.({
|
|
1535
1707
|
file: String(event.file ?? "unknown"),
|
|
1536
1708
|
depth: 0,
|
|
@@ -1542,6 +1714,7 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1542
1714
|
return;
|
|
1543
1715
|
}
|
|
1544
1716
|
if (kind === "event:suite-start") {
|
|
1717
|
+
runtimeEvents.suiteStarts++;
|
|
1545
1718
|
reporter.onSuiteStart?.({
|
|
1546
1719
|
file: String(event.file ?? "unknown"),
|
|
1547
1720
|
depth: Number(event.depth ?? 0),
|
|
@@ -1551,6 +1724,7 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1551
1724
|
return;
|
|
1552
1725
|
}
|
|
1553
1726
|
if (kind === "event:suite-end") {
|
|
1727
|
+
runtimeEvents.suiteEnds++;
|
|
1554
1728
|
reporter.onSuiteEnd?.({
|
|
1555
1729
|
file: String(event.file ?? "unknown"),
|
|
1556
1730
|
depth: Number(event.depth ?? 0),
|
|
@@ -1561,12 +1735,14 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1561
1735
|
return;
|
|
1562
1736
|
}
|
|
1563
1737
|
if (kind === "event:warn") {
|
|
1738
|
+
runtimeEvents.warnings++;
|
|
1564
1739
|
reporter.onWarning?.({
|
|
1565
1740
|
message: String(event.message ?? ""),
|
|
1566
1741
|
});
|
|
1567
1742
|
return;
|
|
1568
1743
|
}
|
|
1569
1744
|
if (kind === "event:log") {
|
|
1745
|
+
runtimeEvents.logs++;
|
|
1570
1746
|
reporter.onLog?.({
|
|
1571
1747
|
file: String(event.file ?? "unknown"),
|
|
1572
1748
|
depth: Number(event.depth ?? 0),
|
|
@@ -1584,16 +1760,43 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1584
1760
|
this.send(MessageType.CALL, Buffer.from(`${result.ok ? "1" : "0"}\n${result.expected}`, "utf8"));
|
|
1585
1761
|
return;
|
|
1586
1762
|
}
|
|
1763
|
+
if (kind === "report:start") {
|
|
1764
|
+
reportStream.sawChunkStart = true;
|
|
1765
|
+
reportStream.sawChunkEnd = false;
|
|
1766
|
+
reportStream.chunkCountExpected = Number(event.chunkCount ?? 0);
|
|
1767
|
+
reportStream.chunkBytesExpected = Number(event.chunkBytes ?? 0);
|
|
1768
|
+
reportStream.chunkTotalBytesExpected = Number(event.totalBytes ?? 0);
|
|
1769
|
+
reportStream.chunkFramesReceived = 0;
|
|
1770
|
+
reportStream.chunkBytesReceived = 0;
|
|
1771
|
+
reportStream.chunks = [];
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
if (kind === "report:end") {
|
|
1775
|
+
reportStream.sawChunkEnd = true;
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1587
1778
|
this.sendJSON(MessageType.CALL, { ok: true, expected: "" });
|
|
1588
1779
|
}
|
|
1589
1780
|
onDataMessage(data) {
|
|
1781
|
+
reportStream.dataFrames++;
|
|
1782
|
+
reportStream.dataBytes += data.length;
|
|
1783
|
+
if (reportStream.sawChunkStart && !reportStream.sawChunkEnd) {
|
|
1784
|
+
reportStream.chunkFramesReceived++;
|
|
1785
|
+
reportStream.chunkBytesReceived += data.length;
|
|
1786
|
+
reportStream.chunks.push(data.toString("utf8"));
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1590
1789
|
try {
|
|
1591
1790
|
report = JSON.parse(data.toString("utf8"));
|
|
1791
|
+
parseError = null;
|
|
1592
1792
|
}
|
|
1593
1793
|
catch (error) {
|
|
1594
1794
|
parseError = String(error);
|
|
1595
1795
|
}
|
|
1596
1796
|
}
|
|
1797
|
+
onClose() {
|
|
1798
|
+
sawChannelClose = true;
|
|
1799
|
+
}
|
|
1597
1800
|
}
|
|
1598
1801
|
const _channel = new TestChannel(child.stdout, child.stdin);
|
|
1599
1802
|
const code = await new Promise((resolve) => {
|
|
@@ -1602,8 +1805,9 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1602
1805
|
if (stderrPendingLine.length && !shouldSuppressWasiWarningLine(stderrPendingLine)) {
|
|
1603
1806
|
stderrBuffer += stderrPendingLine;
|
|
1604
1807
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1808
|
+
const processSpawnError = spawnError;
|
|
1809
|
+
if (processSpawnError) {
|
|
1810
|
+
const errorText = processSpawnError.stack ?? processSpawnError.message;
|
|
1607
1811
|
persistCrashRecord(crashDir, {
|
|
1608
1812
|
kind: "test",
|
|
1609
1813
|
file: specFile,
|
|
@@ -1614,29 +1818,93 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1614
1818
|
});
|
|
1615
1819
|
return createRuntimeFailureReport(specFile, modeName, "failed to start test runtime", errorText, stdoutBuffer, stderrBuffer);
|
|
1616
1820
|
}
|
|
1821
|
+
if (reportStream.sawChunkStart) {
|
|
1822
|
+
if (!reportStream.sawChunkEnd) {
|
|
1823
|
+
parseError =
|
|
1824
|
+
parseError ??
|
|
1825
|
+
"missing report:end marker for chunked report payload";
|
|
1826
|
+
}
|
|
1827
|
+
else {
|
|
1828
|
+
const chunkedPayload = reportStream.chunks.join("");
|
|
1829
|
+
try {
|
|
1830
|
+
report = JSON.parse(chunkedPayload);
|
|
1831
|
+
parseError = null;
|
|
1832
|
+
}
|
|
1833
|
+
catch (error) {
|
|
1834
|
+
parseError = `could not parse chunked report payload: ${String(error)}`;
|
|
1835
|
+
}
|
|
1836
|
+
if (reportStream.chunkCountExpected > 0 &&
|
|
1837
|
+
reportStream.chunkFramesReceived !== reportStream.chunkCountExpected) {
|
|
1838
|
+
parseError =
|
|
1839
|
+
parseError ??
|
|
1840
|
+
`chunk count mismatch: expected ${reportStream.chunkCountExpected}, received ${reportStream.chunkFramesReceived}`;
|
|
1841
|
+
}
|
|
1842
|
+
if (reportStream.chunkTotalBytesExpected > 0 &&
|
|
1843
|
+
reportStream.chunkBytesReceived !== reportStream.chunkTotalBytesExpected) {
|
|
1844
|
+
parseError =
|
|
1845
|
+
parseError ??
|
|
1846
|
+
`chunk size mismatch: expected ${reportStream.chunkTotalBytesExpected} bytes, received ${reportStream.chunkBytesReceived}`;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1617
1850
|
if (parseError) {
|
|
1618
1851
|
const errorText = `could not parse report payload: ${parseError}`;
|
|
1852
|
+
const diagnostics = buildRuntimeReportDiagnostics(code, sawChannelClose, reportStream, runtimeEvents);
|
|
1853
|
+
const fullError = `${errorText}\n${diagnostics}`;
|
|
1619
1854
|
persistCrashRecord(crashDir, {
|
|
1620
1855
|
kind: "test",
|
|
1621
1856
|
file: specFile,
|
|
1622
1857
|
mode: modeName ?? "default",
|
|
1623
|
-
error:
|
|
1858
|
+
error: fullError,
|
|
1624
1859
|
stdout: stdoutBuffer,
|
|
1625
1860
|
stderr: stderrBuffer,
|
|
1626
1861
|
});
|
|
1627
|
-
return createRuntimeFailureReport(specFile, modeName, "runtime returned an invalid report payload",
|
|
1862
|
+
return createRuntimeFailureReport(specFile, modeName, "runtime returned an invalid report payload", fullError, stdoutBuffer, stderrBuffer);
|
|
1628
1863
|
}
|
|
1629
1864
|
if (!report) {
|
|
1865
|
+
const synthesized = synthesizeReportFromRuntimeEvents(specFile, runtimeEvents);
|
|
1866
|
+
if (synthesized) {
|
|
1867
|
+
reporter.onWarning?.({
|
|
1868
|
+
message: "runtime report payload missing; reconstructed result from streamed lifecycle events",
|
|
1869
|
+
});
|
|
1870
|
+
if (code !== 0 || hasMeaningfulRuntimeOutput(stderrBuffer)) {
|
|
1871
|
+
const errorParts = [];
|
|
1872
|
+
if (code !== 0) {
|
|
1873
|
+
errorParts.push(`child process exited with code ${code}`);
|
|
1874
|
+
}
|
|
1875
|
+
const stderrText = normalizeRuntimeOutput(stderrBuffer);
|
|
1876
|
+
if (stderrText.length) {
|
|
1877
|
+
errorParts.push(stderrText);
|
|
1878
|
+
}
|
|
1879
|
+
const diagnostics = buildRuntimeReportDiagnostics(code, sawChannelClose, reportStream, runtimeEvents);
|
|
1880
|
+
errorParts.push(diagnostics);
|
|
1881
|
+
const errorText = errorParts.join("\n\n");
|
|
1882
|
+
persistCrashRecord(crashDir, {
|
|
1883
|
+
kind: "test",
|
|
1884
|
+
file: specFile,
|
|
1885
|
+
mode: modeName ?? "default",
|
|
1886
|
+
error: errorText || "runtime reported an unknown error",
|
|
1887
|
+
stdout: stdoutBuffer,
|
|
1888
|
+
stderr: stderrBuffer,
|
|
1889
|
+
});
|
|
1890
|
+
return appendRuntimeFailureReport(synthesized, specFile, modeName, code !== 0
|
|
1891
|
+
? `test runtime failed with exit code ${code}`
|
|
1892
|
+
: "test runtime wrote to stderr", errorText, stdoutBuffer, stderrBuffer);
|
|
1893
|
+
}
|
|
1894
|
+
return synthesized;
|
|
1895
|
+
}
|
|
1630
1896
|
const errorText = "missing report payload from test runtime";
|
|
1897
|
+
const diagnostics = buildRuntimeReportDiagnostics(code, sawChannelClose, reportStream, runtimeEvents);
|
|
1898
|
+
const fullError = `${errorText}\n${diagnostics}`;
|
|
1631
1899
|
persistCrashRecord(crashDir, {
|
|
1632
1900
|
kind: "test",
|
|
1633
1901
|
file: specFile,
|
|
1634
1902
|
mode: modeName ?? "default",
|
|
1635
|
-
error:
|
|
1903
|
+
error: fullError,
|
|
1636
1904
|
stdout: stdoutBuffer,
|
|
1637
1905
|
stderr: stderrBuffer,
|
|
1638
1906
|
});
|
|
1639
|
-
return createRuntimeFailureReport(specFile, modeName, "test runtime exited without sending a report",
|
|
1907
|
+
return createRuntimeFailureReport(specFile, modeName, "test runtime exited without sending a report", fullError, stdoutBuffer, stderrBuffer);
|
|
1640
1908
|
}
|
|
1641
1909
|
if (code !== 0 || hasMeaningfulRuntimeOutput(stderrBuffer)) {
|
|
1642
1910
|
const errorParts = [];
|
|
@@ -1662,6 +1930,52 @@ async function runProcess(invocation, specFile, crashDir, modeName, snapshots, s
|
|
|
1662
1930
|
}
|
|
1663
1931
|
return report;
|
|
1664
1932
|
}
|
|
1933
|
+
function synthesizeReportFromRuntimeEvents(specFile, runtimeEvents) {
|
|
1934
|
+
if (!runtimeEvents.sawFileEnd &&
|
|
1935
|
+
runtimeEvents.suiteStarts <= 0 &&
|
|
1936
|
+
runtimeEvents.suiteEnds <= 0) {
|
|
1937
|
+
return null;
|
|
1938
|
+
}
|
|
1939
|
+
let verdict = runtimeEvents.fileVerdict;
|
|
1940
|
+
if (verdict == "none" && runtimeEvents.assertionFails > 0) {
|
|
1941
|
+
verdict = "fail";
|
|
1942
|
+
}
|
|
1943
|
+
else if (verdict == "none" && runtimeEvents.sawFileEnd) {
|
|
1944
|
+
verdict = "ok";
|
|
1945
|
+
}
|
|
1946
|
+
return {
|
|
1947
|
+
suites: [
|
|
1948
|
+
{
|
|
1949
|
+
file: specFile,
|
|
1950
|
+
description: runtimeEvents.fileName || path.basename(specFile),
|
|
1951
|
+
depth: 0,
|
|
1952
|
+
kind: "file",
|
|
1953
|
+
verdict,
|
|
1954
|
+
time: {
|
|
1955
|
+
start: 0,
|
|
1956
|
+
end: 0,
|
|
1957
|
+
},
|
|
1958
|
+
suites: [],
|
|
1959
|
+
logs: [],
|
|
1960
|
+
tests: [],
|
|
1961
|
+
},
|
|
1962
|
+
],
|
|
1963
|
+
coverage: {
|
|
1964
|
+
total: 0,
|
|
1965
|
+
covered: 0,
|
|
1966
|
+
uncovered: 0,
|
|
1967
|
+
percent: 100,
|
|
1968
|
+
points: [],
|
|
1969
|
+
},
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
function buildRuntimeReportDiagnostics(code, sawChannelClose, reportStream, runtimeEvents) {
|
|
1973
|
+
return [
|
|
1974
|
+
`runtime diagnostics: exitCode=${code}, channelClose=${sawChannelClose ? "yes" : "no"}`,
|
|
1975
|
+
`report stream: dataFrames=${reportStream.dataFrames}, dataBytes=${reportStream.dataBytes}, chunked=${reportStream.sawChunkStart ? "yes" : "no"}, chunkStart=${reportStream.sawChunkStart ? "yes" : "no"}, chunkEnd=${reportStream.sawChunkEnd ? "yes" : "no"}, chunkFrames=${reportStream.chunkFramesReceived}, expectedChunkFrames=${reportStream.chunkCountExpected}, chunkBytes=${reportStream.chunkBytesReceived}, expectedChunkBytes=${reportStream.chunkTotalBytesExpected}`,
|
|
1976
|
+
`runtime events: fileStart=${runtimeEvents.sawFileStart ? "yes" : "no"}, fileEnd=${runtimeEvents.sawFileEnd ? "yes" : "no"}, fileVerdict=${runtimeEvents.fileVerdict}, suiteStarts=${runtimeEvents.suiteStarts}, suiteEnds=${runtimeEvents.suiteEnds}, assertionFails=${runtimeEvents.assertionFails}, warnings=${runtimeEvents.warnings}, logs=${runtimeEvents.logs}`,
|
|
1977
|
+
].join("\n");
|
|
1978
|
+
}
|
|
1665
1979
|
function createRuntimeFailureReport(specFile, modeName, title, details, stdout, stderr) {
|
|
1666
1980
|
return appendRuntimeFailureReport({
|
|
1667
1981
|
suites: [],
|
package/bin/commands/run.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { createRunReporter, run } from "./run-core.js";
|
|
2
2
|
export async function executeRunCommand(rawArgs, flags, configPath, selectedModes, deps) {
|
|
3
3
|
const commandArgs = deps.resolveCommandArgs(rawArgs, "run");
|
|
4
|
+
const suiteSelectors = deps.resolveSuiteSelectors(rawArgs, "run");
|
|
4
5
|
const listFlags = deps.resolveListFlags(rawArgs, "run");
|
|
5
6
|
const featureToggles = deps.resolveFeatureToggles(rawArgs, "run");
|
|
6
7
|
const runFlags = {
|
|
@@ -20,5 +21,5 @@ export async function executeRunCommand(rawArgs, flags, configPath, selectedMode
|
|
|
20
21
|
await deps.listExecutionPlan("run", configPath, commandArgs, modeTargets, listFlags);
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
|
-
await deps.runRuntimeModes(runFlags, configPath, commandArgs, modeTargets);
|
|
24
|
+
await deps.runRuntimeModes(runFlags, configPath, commandArgs, suiteSelectors, modeTargets);
|
|
24
25
|
}
|
package/bin/commands/test.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export async function executeTestCommand(rawArgs, flags, configPath, selectedModes, deps) {
|
|
2
2
|
const commandArgs = deps.resolveCommandArgs(rawArgs, "test");
|
|
3
|
+
const suiteSelectors = deps.resolveSuiteSelectors(rawArgs, "test");
|
|
4
|
+
const fuzzerSelectors = deps.resolveFuzzerSelectors(rawArgs, "test");
|
|
3
5
|
const listFlags = deps.resolveListFlags(rawArgs, "test");
|
|
4
6
|
const featureToggles = deps.resolveFeatureToggles(rawArgs, "test");
|
|
5
7
|
const buildFeatureToggles = {
|
|
@@ -25,5 +27,5 @@ export async function executeTestCommand(rawArgs, flags, configPath, selectedMod
|
|
|
25
27
|
await deps.listExecutionPlan("test", configPath, commandArgs, modeTargets, listFlags, fuzzEnabled);
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
28
|
-
await deps.runTestModes(runFlags, configPath, commandArgs, modeTargets, buildFeatureToggles, fuzzEnabled, fuzzOverrides);
|
|
30
|
+
await deps.runTestModes(runFlags, configPath, commandArgs, suiteSelectors, fuzzerSelectors, modeTargets, buildFeatureToggles, fuzzEnabled, fuzzOverrides);
|
|
29
31
|
}
|