@jterrazz/test 12.2.0 → 13.0.0
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 +12 -14
- package/dist/checker.js +554 -48
- package/dist/index.d.ts +102 -88
- package/dist/index.js +16 -14
- package/dist/literate.js +156 -164
- package/dist/match.js +60 -180
- package/dist/oxlint.cjs +83 -11
- package/dist/oxlint.js +83 -11
- package/dist/queue.js +1 -1
- package/dist/spec-document.js +536 -0
- package/dist/vitest.d.ts +3 -3
- package/dist/vitest.js +16 -15
- package/package.json +6 -4
- package/schema/spec.schema.json +179 -0
package/dist/literate.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as SpecSyntaxError, l as updateSpecFile, s as readSpecFile } from "./spec-document.js";
|
|
2
|
+
import { a as copyPlan, n as Matcher, r as TOKEN_KINDS, t as CaptureScope } from "./match.js";
|
|
2
3
|
import { cpSync, existsSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import {
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
5
|
import { execSync, spawn } from "node:child_process";
|
|
5
6
|
import { readdir } from "node:fs/promises";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
@@ -238,6 +239,20 @@ function textEquals(expected, actual, scope) {
|
|
|
238
239
|
return placeholderStringMatches(expected, actual, scope);
|
|
239
240
|
}
|
|
240
241
|
/**
|
|
242
|
+
* Substring comparison with `{{token}}` support — the `contains` half of the
|
|
243
|
+
* `files:` assertions of a spec document. Without placeholders this is
|
|
244
|
+
* `String.includes`; with them the fixture text becomes an UNANCHORED pattern,
|
|
245
|
+
* so `wrote {{path}}` is found inside a larger file, and its `#ref` captures are
|
|
246
|
+
* recorded exactly as a whole-text comparison records them.
|
|
247
|
+
*/
|
|
248
|
+
function textContains(expected, actual, scope) {
|
|
249
|
+
if (!hasPlaceholders(expected)) return actual.includes(expected);
|
|
250
|
+
const parsed = parsePlaceholderString(expected, scope);
|
|
251
|
+
const found = new RegExp(parsed.source).exec(actual);
|
|
252
|
+
if (!found) return false;
|
|
253
|
+
return parsed.refs.every((entry) => !entry.ref || recordRef(entry.ref, found[entry.index + 1], scope));
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
241
256
|
* Render an expected value for failure diffs and serialization: Matcher
|
|
242
257
|
* instances become their placeholder text, everything else is untouched.
|
|
243
258
|
*/
|
|
@@ -1288,77 +1303,6 @@ function shouldUpdateSnapshots() {
|
|
|
1288
1303
|
/** Standard hint appended to missing-fixture errors. */
|
|
1289
1304
|
const UPDATE_HINT = "Run with TEST_UPDATE=1 (or vitest -u) to create it.";
|
|
1290
1305
|
//#endregion
|
|
1291
|
-
//#region src/core/specification/shared/fixtures.ts
|
|
1292
|
-
/**
|
|
1293
|
-
* Fixture path resolution + copy semantics for the `cli` facet's `.fixture()`.
|
|
1294
|
-
*
|
|
1295
|
-
* A fixture path is one of two shapes:
|
|
1296
|
-
*
|
|
1297
|
-
* - `$FIXTURES/<rest>` — the shared pool at `<specs-root>/fixtures/<rest>`,
|
|
1298
|
-
* where `<specs-root>` is the nearest ancestor directory named `specs`.
|
|
1299
|
-
* - `<path>` (no marker) — feature-local, at `<test-dir>/fixtures/<path>`.
|
|
1300
|
-
*
|
|
1301
|
-
* Any other `$`-prefixed marker is a usage error (listed against the known
|
|
1302
|
-
* markers). Copy semantics mirror rsync's trailing-slash rule (see
|
|
1303
|
-
* {@link copyPlan}).
|
|
1304
|
-
*/
|
|
1305
|
-
/**
|
|
1306
|
-
* Markers understood in a `.fixture()` path. Extend here + in
|
|
1307
|
-
* {@link resolveFixtureSource}. Exported so the lint layer (rule
|
|
1308
|
-
* `jterrazz/b2-known-fixture-marker`) validates literals against the same list.
|
|
1309
|
-
*/
|
|
1310
|
-
const KNOWN_FIXTURE_MARKERS = ["$FIXTURES"];
|
|
1311
|
-
/**
|
|
1312
|
-
* Walk up from `startDir` to the nearest ancestor directory named `specs`.
|
|
1313
|
-
* Throws with guidance if none is found up to the filesystem root — the
|
|
1314
|
-
* `$FIXTURES` marker is meaningless without a specs root.
|
|
1315
|
-
*/
|
|
1316
|
-
function discoverSpecsRoot(startDir) {
|
|
1317
|
-
let dir = startDir;
|
|
1318
|
-
for (;;) {
|
|
1319
|
-
if (basename(dir) === "specs") return dir;
|
|
1320
|
-
const parent = dirname(dir);
|
|
1321
|
-
if (parent === dir) break;
|
|
1322
|
-
dir = parent;
|
|
1323
|
-
}
|
|
1324
|
-
throw new Error(`.fixture(): the $FIXTURES marker resolves to <specs-root>/fixtures, but no directory named "specs" was found walking up from ${startDir}. Move the specification under a specs/ directory, or use a feature-local fixtures/ path (no $FIXTURES marker).`);
|
|
1325
|
-
}
|
|
1326
|
-
/**
|
|
1327
|
-
* Resolve the absolute source path of a fixture, applying marker resolution.
|
|
1328
|
-
* The trailing slash (spread semantics) is preserved by callers via
|
|
1329
|
-
* {@link copyPlan} — it is irrelevant to source resolution.
|
|
1330
|
-
*/
|
|
1331
|
-
function resolveFixtureSource(path, testDir) {
|
|
1332
|
-
const clean = path.replace(/\/+$/, "");
|
|
1333
|
-
if (clean === "$FIXTURES" || path.startsWith("$FIXTURES/")) {
|
|
1334
|
-
const rest = clean.slice(9).replace(/^\/+/, "");
|
|
1335
|
-
return resolve(discoverSpecsRoot(testDir), "fixtures", rest);
|
|
1336
|
-
}
|
|
1337
|
-
if (path.startsWith("$")) {
|
|
1338
|
-
const marker = clean.split("/")[0];
|
|
1339
|
-
throw new Error(`.fixture("${path}"): unknown marker "${marker}". Known markers: ${KNOWN_FIXTURE_MARKERS.join(", ")}. A path without a marker is feature-local (resolved under <test-dir>/fixtures/).`);
|
|
1340
|
-
}
|
|
1341
|
-
return resolve(testDir, "fixtures", clean);
|
|
1342
|
-
}
|
|
1343
|
-
/**
|
|
1344
|
-
* Compute the source + destination for copying a fixture into the working
|
|
1345
|
-
* directory. Copy semantics follow rsync's trailing-slash rule:
|
|
1346
|
-
*
|
|
1347
|
-
* - `dir/` (trailing slash) — the CONTENTS are spread into the cwd.
|
|
1348
|
-
* - `dir` (no slash, a directory) — the directory is copied as `<cwd>/<basename>`.
|
|
1349
|
-
* - `file` — copied as `<cwd>/<basename>`.
|
|
1350
|
-
*
|
|
1351
|
-
* Chained `.fixture()` calls layer in order: a later fixture overwrites files
|
|
1352
|
-
* written by an earlier one.
|
|
1353
|
-
*/
|
|
1354
|
-
function copyPlan(path, testDir, workDir) {
|
|
1355
|
-
const src = resolveFixtureSource(path, testDir);
|
|
1356
|
-
return {
|
|
1357
|
-
dest: /\/+$/.test(path) ? workDir : resolve(workDir, basename(src)),
|
|
1358
|
-
src
|
|
1359
|
-
};
|
|
1360
|
-
}
|
|
1361
|
-
//#endregion
|
|
1362
1306
|
//#region src/core/specification/website/serve.adapter.ts
|
|
1363
1307
|
/** Grace period between SIGTERM and the SIGKILL escalation. */
|
|
1364
1308
|
const KILL_GRACE_MS = 2e3;
|
|
@@ -1515,21 +1459,14 @@ var ServeAdapter = class {
|
|
|
1515
1459
|
//#endregion
|
|
1516
1460
|
//#region src/core/specification/cli/literate.ts
|
|
1517
1461
|
/**
|
|
1518
|
-
* The form a stream is compared in: ANSI stripped (rule D6)
|
|
1519
|
-
* `transform` applied
|
|
1520
|
-
*
|
|
1521
|
-
*
|
|
1462
|
+
* The form a stream is compared in: ANSI stripped (rule D6) and the runner's
|
|
1463
|
+
* `transform` applied. Nothing else — a block scalar states its own final
|
|
1464
|
+
* newline (`|` keeps it, `|-` drops it), so the comparison is byte-exact and
|
|
1465
|
+
* has no normalisation to hide behind.
|
|
1522
1466
|
*/
|
|
1523
1467
|
function comparable(raw, transform) {
|
|
1524
1468
|
const stripped = stripAnsiCodes(raw);
|
|
1525
|
-
return
|
|
1526
|
-
}
|
|
1527
|
-
function narrative(spec) {
|
|
1528
|
-
return [
|
|
1529
|
-
`test: ${spec.header.test}`,
|
|
1530
|
-
`given: ${spec.header.given}`,
|
|
1531
|
-
`then: ${spec.header.then}`
|
|
1532
|
-
].join("\n");
|
|
1469
|
+
return transform ? transform(stripped) : stripped;
|
|
1533
1470
|
}
|
|
1534
1471
|
/**
|
|
1535
1472
|
* Line equality AS THE COMPARISON JUDGED IT — a `{{url}}` line that matched is
|
|
@@ -1540,85 +1477,133 @@ function narrative(spec) {
|
|
|
1540
1477
|
function tokenAwareEquals(scope) {
|
|
1541
1478
|
return (expected, actual) => textEquals(expected, actual, new CaptureScope(scope.workdir));
|
|
1542
1479
|
}
|
|
1480
|
+
/** A path under the working directory, refusing anything that would escape it. */
|
|
1481
|
+
function resolveUnderWorkDir(path, workDir, context) {
|
|
1482
|
+
const target = resolve(workDir, path);
|
|
1483
|
+
const inside = relative(workDir, target);
|
|
1484
|
+
if (isAbsolute(path) || inside.startsWith("..") || isAbsolute(inside)) throw new Error(`${context.displayPath}: files: "${path}" leaves the working directory — a files: key is a relative path under the cwd`);
|
|
1485
|
+
return target;
|
|
1486
|
+
}
|
|
1487
|
+
/** A one-line preview of a file's content for the failure rendering. */
|
|
1488
|
+
function preview(text) {
|
|
1489
|
+
return JSON.stringify(text.length > 160 ? `${text.slice(0, 160)}…` : text);
|
|
1490
|
+
}
|
|
1491
|
+
function readIfPresent(path) {
|
|
1492
|
+
try {
|
|
1493
|
+
return statSync(path).isFile() ? readFileSync(path, "utf8") : null;
|
|
1494
|
+
} catch {
|
|
1495
|
+
return null;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
/** Check one `files:` entry and render its failure, or `null` when it holds. */
|
|
1499
|
+
function checkFile(assertion, workDir, context, scope) {
|
|
1500
|
+
const target = resolveUnderWorkDir(assertion.path, workDir, context);
|
|
1501
|
+
let exists;
|
|
1502
|
+
try {
|
|
1503
|
+
exists = statSync(target) !== void 0;
|
|
1504
|
+
} catch {
|
|
1505
|
+
exists = false;
|
|
1506
|
+
}
|
|
1507
|
+
if (assertion.kind === "absent") return exists ? `${assertion.path}: expected absent, got a file` : null;
|
|
1508
|
+
if (!exists) return `${assertion.path}: expected to exist, got nothing at that path`;
|
|
1509
|
+
if (assertion.kind === "exists") return null;
|
|
1510
|
+
const content = readIfPresent(target);
|
|
1511
|
+
if (content === null) return `${assertion.path}: expected a readable file, got a directory`;
|
|
1512
|
+
for (const needle of assertion.contains) if (!textContains(needle.text, content, scope)) return `${assertion.path}: expected to contain ${preview(needle.text)}, got ${preview(content)}`;
|
|
1513
|
+
if (assertion.equals !== null && !textEquals(assertion.equals.text, content, scope)) return `${assertion.path}: expected ${preview(assertion.equals.text)}, got ${preview(content)}`;
|
|
1514
|
+
return null;
|
|
1515
|
+
}
|
|
1543
1516
|
/**
|
|
1544
|
-
* The failure a mismatching
|
|
1545
|
-
* the
|
|
1546
|
-
*
|
|
1517
|
+
* The failure a mismatching run throws: the description, the command that ran,
|
|
1518
|
+
* every detail the comparison rejected, and where to open the document. The
|
|
1519
|
+
* update hint names exactly what a rewrite touches, so it cannot be read as
|
|
1520
|
+
* "this will re-generate my `files:`".
|
|
1547
1521
|
*
|
|
1548
|
-
* The stack is REPLACED by the
|
|
1522
|
+
* The stack is REPLACED by the run's own line. Left alone it would carry the
|
|
1549
1523
|
* engine's frames and end inside the module the plugin generates — pointing the
|
|
1550
|
-
* reader at
|
|
1551
|
-
*
|
|
1524
|
+
* reader at a line of a file they never wrote. One frame, on the `command:` key,
|
|
1525
|
+
* is the whole truth here.
|
|
1552
1526
|
*/
|
|
1553
|
-
function failure(
|
|
1554
|
-
return
|
|
1555
|
-
|
|
1527
|
+
function failure(document, run, context, details) {
|
|
1528
|
+
return atLine(new Error([
|
|
1529
|
+
"Spec mismatch",
|
|
1556
1530
|
"",
|
|
1557
|
-
|
|
1531
|
+
document.description,
|
|
1558
1532
|
"",
|
|
1559
|
-
`$ ${run.
|
|
1533
|
+
`$ ${run.command}`,
|
|
1560
1534
|
"",
|
|
1561
|
-
|
|
1535
|
+
details.join("\n\n"),
|
|
1562
1536
|
"",
|
|
1563
|
-
|
|
1564
|
-
|
|
1537
|
+
`${context.displayPath}:${run.commandLine}`,
|
|
1538
|
+
"Run with TEST_UPDATE=1 to rewrite the runs — the exit code and the streams, and nothing else."
|
|
1539
|
+
].join("\n")), context.filePath, run.commandLine);
|
|
1565
1540
|
}
|
|
1566
1541
|
/**
|
|
1567
|
-
* Point an error's stack at one frame: the line of the `.
|
|
1568
|
-
* the failure. The framework's own frames carry nothing a reader of a
|
|
1569
|
-
*
|
|
1542
|
+
* Point an error's stack at one frame: the line of the `.spec.yaml` that owns
|
|
1543
|
+
* the failure. The framework's own frames carry nothing a reader of a spec
|
|
1544
|
+
* document can act on, and the generated module's frame actively misleads.
|
|
1570
1545
|
*/
|
|
1571
|
-
function
|
|
1546
|
+
function atLine(error, filePath, line) {
|
|
1572
1547
|
error.stack = `${error.name}: ${error.message}\n at ${filePath}:${line}:1`;
|
|
1573
1548
|
return error;
|
|
1574
1549
|
}
|
|
1575
|
-
/** Compare one
|
|
1576
|
-
function
|
|
1577
|
-
const { actual,
|
|
1578
|
-
|
|
1579
|
-
if (actual.exitCode !== block.exitCode) throw failure(spec, run, context, [
|
|
1550
|
+
/** Compare one run against what the command actually did. */
|
|
1551
|
+
function assertRun(document, outcome, context, workDir, scope) {
|
|
1552
|
+
const { actual, run } = outcome;
|
|
1553
|
+
if (actual.exitCode !== run.exitCode) throw failure(document, run, context, [[
|
|
1580
1554
|
"exit code mismatch",
|
|
1581
|
-
` expected: ${
|
|
1555
|
+
` expected: ${run.exitCode}`,
|
|
1582
1556
|
` received: ${actual.exitCode}`,
|
|
1583
|
-
...
|
|
1557
|
+
...outcome.stderr.length > 0 ? [
|
|
1584
1558
|
"",
|
|
1585
1559
|
"stderr was:",
|
|
1586
|
-
|
|
1560
|
+
outcome.stderr.replace(/\n$/, "")
|
|
1587
1561
|
] : []
|
|
1562
|
+
].join("\n")]);
|
|
1563
|
+
const equals = tokenAwareEquals(scope);
|
|
1564
|
+
const details = [];
|
|
1565
|
+
for (const [name, expected, received] of [[
|
|
1566
|
+
"stdout",
|
|
1567
|
+
run.stdout?.text ?? "",
|
|
1568
|
+
outcome.stdout
|
|
1569
|
+
], [
|
|
1570
|
+
"stderr",
|
|
1571
|
+
run.stderr?.text ?? "",
|
|
1572
|
+
outcome.stderr
|
|
1573
|
+
]]) if (!textEquals(expected, received, scope)) details.push(formatStdoutDiff(name, expected, received, { equals }));
|
|
1574
|
+
const fileFailures = run.files.map((assertion) => checkFile(assertion, workDir, context, scope)).filter((line) => line !== null);
|
|
1575
|
+
if (fileFailures.length > 0) details.push([
|
|
1576
|
+
"files mismatch",
|
|
1577
|
+
"",
|
|
1578
|
+
...fileFailures.map((line) => ` ${line}`)
|
|
1588
1579
|
].join("\n"));
|
|
1589
|
-
if (
|
|
1590
|
-
const expectedStderr = block.stderr ?? "";
|
|
1591
|
-
if (!textEquals(expectedStderr, run.stderr, scope)) throw failure(spec, run, context, formatStdoutDiff("stderr", expectedStderr, run.stderr, { equals }));
|
|
1580
|
+
if (details.length > 0) throw failure(document, run, context, details);
|
|
1592
1581
|
}
|
|
1593
1582
|
/**
|
|
1594
|
-
* The rewritten
|
|
1595
|
-
*
|
|
1596
|
-
*
|
|
1583
|
+
* The rewritten runs: exit codes and streams from the run, placeholders of the
|
|
1584
|
+
* previous golden preserved by pattern (rule D5). A stream that ends up empty
|
|
1585
|
+
* loses its key, so a rewritten document passes the next run.
|
|
1597
1586
|
*/
|
|
1598
|
-
function
|
|
1599
|
-
return
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
stderr: mergedStderr.length > 0 ? mergedStderr : null,
|
|
1605
|
-
stdout: mergeTextPreservingPlaceholders(block.stdout, stdout, scope)
|
|
1606
|
-
};
|
|
1607
|
-
});
|
|
1587
|
+
function updates(outcomes, scope) {
|
|
1588
|
+
return outcomes.map(({ actual, run, stderr, stdout }) => ({
|
|
1589
|
+
exitCode: actual.exitCode,
|
|
1590
|
+
stderr: mergeTextPreservingPlaceholders(run.stderr?.text ?? null, stderr, scope),
|
|
1591
|
+
stdout: mergeTextPreservingPlaceholders(run.stdout?.text ?? null, stdout, scope)
|
|
1592
|
+
}));
|
|
1608
1593
|
}
|
|
1609
1594
|
/**
|
|
1610
|
-
* Start every server the
|
|
1611
|
-
*
|
|
1612
|
-
*
|
|
1595
|
+
* Start every server the document names, in declaration order, before the first
|
|
1596
|
+
* run. They all stay live for the whole file and are stopped together — one
|
|
1597
|
+
* document is one scenario, and its servers are part of its ground.
|
|
1613
1598
|
*/
|
|
1614
|
-
async function startServers(
|
|
1599
|
+
async function startServers(document, config, displayPath) {
|
|
1615
1600
|
const registry = config.serveRegistry ?? {};
|
|
1616
1601
|
const started = [];
|
|
1617
1602
|
const env = {};
|
|
1618
1603
|
const stop = async () => {
|
|
1619
1604
|
for (const adapter of started.toReversed()) await adapter.stop();
|
|
1620
1605
|
};
|
|
1621
|
-
for (const entry of
|
|
1606
|
+
for (const entry of document.serve) {
|
|
1622
1607
|
const registration = registry[entry.name];
|
|
1623
1608
|
if (!registration) {
|
|
1624
1609
|
await stop();
|
|
@@ -1643,16 +1628,16 @@ async function startServers(spec, config, displayPath) {
|
|
|
1643
1628
|
stop
|
|
1644
1629
|
};
|
|
1645
1630
|
}
|
|
1646
|
-
/** Resolve the
|
|
1647
|
-
function
|
|
1631
|
+
/** Resolve the document's `env:` entries against the registered sets. `$WORKDIR` expands. */
|
|
1632
|
+
function documentEnv(document, config, displayPath) {
|
|
1648
1633
|
const sets = config.envSets ?? {};
|
|
1649
1634
|
const env = {};
|
|
1650
|
-
for (const token of
|
|
1635
|
+
for (const token of document.env) {
|
|
1651
1636
|
if (token.kind === "set") {
|
|
1652
1637
|
const declared = sets[token.name];
|
|
1653
1638
|
if (!declared) {
|
|
1654
1639
|
const known = Object.keys(sets);
|
|
1655
|
-
throw new Error(`${displayPath}: env: "${token.name}" is not a registered env set — ${known.length === 0 ? "specification.cli() declares no `env` option." : `registered sets: ${known.join(", ")}.`}`);
|
|
1640
|
+
throw new Error(`${displayPath}:${token.line}: env: "${token.name}" is not a registered env set — ${known.length === 0 ? "specification.cli() declares no `env` option." : `registered sets: ${known.join(", ")}.`}`);
|
|
1656
1641
|
}
|
|
1657
1642
|
Object.assign(env, declared);
|
|
1658
1643
|
continue;
|
|
@@ -1667,43 +1652,50 @@ function expandWorkdir(env, workDir) {
|
|
|
1667
1652
|
return resolved;
|
|
1668
1653
|
}
|
|
1669
1654
|
/**
|
|
1670
|
-
* Run one
|
|
1655
|
+
* Run one spec document end to end and resolve with the LAST run's result —
|
|
1671
1656
|
* the handle a `.test.ts` adds its own assertion to (a directory golden, a
|
|
1672
|
-
* grep) after `cli.run('<case>.
|
|
1657
|
+
* grep) after `cli.run('<case>.spec.yaml')`.
|
|
1673
1658
|
*
|
|
1674
|
-
* Under `TEST_UPDATE=1` nothing is asserted: every
|
|
1675
|
-
*
|
|
1676
|
-
* placeholders preserved.
|
|
1659
|
+
* Under `TEST_UPDATE=1` nothing is asserted: every run executes, and each one's
|
|
1660
|
+
* exit code and streams are rewritten from the actual output with the previous
|
|
1661
|
+
* golden's placeholders preserved.
|
|
1677
1662
|
*/
|
|
1678
|
-
async function
|
|
1663
|
+
async function runSpecDocument(options) {
|
|
1679
1664
|
const { config, displayPath, filePath, testDir, workDir } = options;
|
|
1680
|
-
if (!config.command) throw new Error(".run():
|
|
1681
|
-
let
|
|
1665
|
+
if (!config.command) throw new Error(".run(): spec documents require a command adapter (use specification.cli())");
|
|
1666
|
+
let file;
|
|
1682
1667
|
try {
|
|
1683
|
-
|
|
1668
|
+
file = readSpecFile(readFileSync(filePath, "utf8"), displayPath);
|
|
1684
1669
|
} catch (error) {
|
|
1685
|
-
throw error instanceof
|
|
1670
|
+
throw error instanceof SpecSyntaxError ? atLine(error, filePath, error.line) : error;
|
|
1686
1671
|
}
|
|
1672
|
+
const document = file.document;
|
|
1687
1673
|
const fileDir = filePath.replace(/[/\\][^/\\]*$/, "");
|
|
1688
|
-
for (const
|
|
1689
|
-
const { dest, src } = copyPlan(path, fileDir, workDir);
|
|
1674
|
+
for (const fixture of document.fixtures) {
|
|
1675
|
+
const { dest, src } = copyPlan(fixture.path, fileDir, workDir);
|
|
1690
1676
|
cpSync(src, dest, { recursive: true });
|
|
1691
1677
|
}
|
|
1692
|
-
const servers = await startServers(
|
|
1678
|
+
const servers = await startServers(document, config, displayPath);
|
|
1693
1679
|
const scope = new CaptureScope(safeRealpath(workDir));
|
|
1694
1680
|
const env = expandWorkdir({
|
|
1695
1681
|
...options.baseEnv,
|
|
1696
1682
|
...servers.env,
|
|
1697
|
-
...
|
|
1683
|
+
...documentEnv(document, config, displayPath)
|
|
1698
1684
|
}, workDir);
|
|
1699
|
-
let
|
|
1685
|
+
let outcomes;
|
|
1700
1686
|
try {
|
|
1701
|
-
|
|
1702
|
-
for (const
|
|
1703
|
-
const actual = await config.command.exec(
|
|
1704
|
-
|
|
1687
|
+
outcomes = [];
|
|
1688
|
+
for (const run of document.runs) {
|
|
1689
|
+
const actual = run.waitFor === null ? await config.command.exec(run.command, workDir, env, {
|
|
1690
|
+
stdin: run.stdin ?? void 0,
|
|
1691
|
+
timeout: run.timeout ?? void 0
|
|
1692
|
+
}) : await config.command.watch(run.command, workDir, {
|
|
1693
|
+
timeout: run.timeout ?? void 0,
|
|
1694
|
+
waitFor: run.waitFor
|
|
1695
|
+
}, env);
|
|
1696
|
+
outcomes.push({
|
|
1705
1697
|
actual,
|
|
1706
|
-
|
|
1698
|
+
run,
|
|
1707
1699
|
stderr: comparable(actual.stderr, config.transform),
|
|
1708
1700
|
stdout: comparable(actual.stdout, config.transform)
|
|
1709
1701
|
});
|
|
@@ -1711,13 +1703,13 @@ async function runLiterateSpec(options) {
|
|
|
1711
1703
|
} finally {
|
|
1712
1704
|
await servers.stop();
|
|
1713
1705
|
}
|
|
1714
|
-
if (shouldUpdateSnapshots() && options.frozen !== true) writeFileSync(filePath,
|
|
1715
|
-
else for (const
|
|
1706
|
+
if (shouldUpdateSnapshots() && options.frozen !== true) writeFileSync(filePath, updateSpecFile(file, updates(outcomes, scope)));
|
|
1707
|
+
else for (const outcome of outcomes) assertRun(document, outcome, {
|
|
1716
1708
|
displayPath,
|
|
1717
1709
|
filePath
|
|
1718
|
-
}, scope);
|
|
1710
|
+
}, workDir, scope);
|
|
1719
1711
|
return new CliResult({
|
|
1720
|
-
commandOutput:
|
|
1712
|
+
commandOutput: outcomes.at(-1).actual,
|
|
1721
1713
|
config,
|
|
1722
1714
|
dockerConfig: config.dockerConfig,
|
|
1723
1715
|
testDir,
|
|
@@ -1726,9 +1718,9 @@ async function runLiterateSpec(options) {
|
|
|
1726
1718
|
workDir
|
|
1727
1719
|
});
|
|
1728
1720
|
}
|
|
1729
|
-
/** The title a transformed `.
|
|
1730
|
-
function
|
|
1731
|
-
return
|
|
1721
|
+
/** The title a transformed `.spec.yaml` module runs under — the `description:` line. */
|
|
1722
|
+
function specDescription(content, displayPath) {
|
|
1723
|
+
return readSpecFile(content, displayPath).document.description;
|
|
1732
1724
|
}
|
|
1733
1725
|
//#endregion
|
|
1734
|
-
export {
|
|
1726
|
+
export { placeholderPatternSource as A, formatResponseDiff as C, hasPlaceholders as D, formatTableDiff as E, structuralEquals as M, structuralSubset as N, mergePreservingPlaceholders as O, textEquals as P, formatDirectoryDiff as S, formatStdoutDiff as T, getCallerDir as _, shouldUpdateSnapshots as a, diffDirectories as b, BaseResult as c, inspectContainer as d, removeContainers as f, text as g, TextAccessor as h, UPDATE_HINT as i, renderExpected as j, mergeTextPreservingPlaceholders as k, validateDatabaseOption as l, JsonAccessor as m, specDescription as n, ContainerAccessor as o, TableAccessor as p, ServeAdapter as r, CliResult as s, runSpecDocument as t, findContainersByLabel as u, FilesystemAccessor as v, formatStartupReport as w, walkDirectory as x, DirectoryAccessor as y };
|