mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 00:03:15 +01:00
fix(ci-carry): a per_file remainder file that skips here too is not owed (GST-1314) (#3985)
* fix(ci-carry): a per_file remainder file that skips here too is not owed (GST-1314); anchor per_file paths, --no-env-file, declare gstack's per_file runner Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: name the changelog fragment after the branch Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(evidence): a truncated per_file log never counts a remainder file not owed (GST-1314) Review-Finding: p1b/outside-voice#1 reader=outside-voice Review-Finding: p1b/red-team#3 reader=red-team Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(changelog): GST-1314 fragment names the truncated-log refusal Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(pregate): a per_file log cut at 2 MB still refuses (GST-1314) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
c1ace7483b
commit
068087f33c
6 files changed
+108
-30
No files matched your search
@@ -29,7 +29,7 @@
|
||||
"skipped": "bun --no-env-file --config=/dev/null -e 'import { isPaidTestFile } from \"./test/helpers/paid-test-set.ts\"; import * as fs from \"node:fs\"; for (const f of fs.readFileSync(process.env.PREGATE_FILES_FILE, \"utf8\").split(/\\r?\\n/)) if (f && isPaidTestFile(f)) console.log(f);'"
|
||||
},
|
||||
"ship_lanes_from": {
|
||||
"bun run test": { "profiles": ["ci-reuse/free-tests"], "inventory": "bun --no-env-file --config=/dev/null run scripts/test-free-shards.ts --list | sed -n 's/^ //p'" }
|
||||
"bun run test": { "profiles": ["ci-reuse/free-tests"], "inventory": "bun --no-env-file --config=/dev/null run scripts/test-free-shards.ts --list | sed -n 's/^ //p'", "per_file": "bun --no-env-file test --timeout=30000 {files}" }
|
||||
},
|
||||
"warn": [],
|
||||
"guards": [
|
||||
|
||||
+29
-10
@@ -138,7 +138,8 @@ interface EvidenceRecord {
|
||||
tests_unit?: "files";
|
||||
/** Why a carried lane was not owed at all: every job it names was path-skipped by an unchanged gate (record-ci, GST-1190). */
|
||||
not_owed?: string;
|
||||
skipped_here?: { label: string; files: string[]; tests_ran: number | "unknown" };
|
||||
/** The per_file run of the inventory files CI skipped entirely; not_owed: the ones that skipped every test here too (GST-1314). */
|
||||
skipped_here?: { label: string; files: string[]; tests_ran: number | "unknown"; not_owed?: string[] };
|
||||
}
|
||||
|
||||
/** Bytes of streamed output kept for the tests_ran parse (the 2MB log would truncate the summary). */
|
||||
@@ -184,6 +185,15 @@ const ANSI = /\u001B\[[0-?]*[ -/]*[@-~]/g;
|
||||
const strip = (l: string) => l.replace(ANSI, "").replace(/\r$/, "");
|
||||
const count = (text: string, word: string): number => Number(new RegExp(`(\\d+) ${word}\\b`).exec(text)?.[1] ?? 0);
|
||||
|
||||
/** The tests bun's last terminal summary in a run's log registered, skips included ("Ran N tests across M files"); 0 when the log or the summary is missing, or the log is incomplete (capped at LOG_MAX_BYTES or cut by a write failure), since its last summary need not be the run's. */
|
||||
function registeredBunTests(logPath: string | undefined): number {
|
||||
let text = "";
|
||||
try { text = logPath ? readFileSync(logPath, "utf8") : ""; } catch { return 0; }
|
||||
if (/^\[gstack-evidence: log (truncated at 2MB|ended early)/m.test(text)) return 0;
|
||||
const last = text.split("\n").map(strip).findLast((l) => /^Ran \d+ tests? across \d+ files?\. \[/.test(l));
|
||||
return Number(/^Ran (\d+)/.exec(last ?? "")?.[1] ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bun: the last "Ran N tests across M files" block; executed = its " N pass" +
|
||||
* " N fail" lines. When that block ends a `bun test --shard=i/N` process (bun's
|
||||
@@ -733,14 +743,23 @@ async function cmdRecordCi(argv: string[]): Promise<number> {
|
||||
let here: EvidenceRecord["skipped_here"];
|
||||
if (v.remainder?.length) {
|
||||
const hereLabel = `${label}.skipped`;
|
||||
console.error(`gstack-evidence record-ci: ${v.remainder.length} inventory file(s) skipped every test in CI; running them here (label=${hereLabel})`);
|
||||
const hereCommand = perFileCommand(m.per_file!, v.remainder);
|
||||
const exit = await cmdRun(["--label", hereLabel, "--", hereCommand]);
|
||||
if (exit !== 0) return none("skipped_failed", `the per_file run of ${v.remainder.length} skipped file(s) exited ${exit} (label=${hereLabel})`);
|
||||
let rows: EvidenceRecord[] = [];
|
||||
try { rows = readJsonl<EvidenceRecord>(ledgerPath().file); } catch { rows = []; }
|
||||
here = { label: hereLabel, files: v.remainder, tests_ran: rows.findLast((r) => r.label === hereLabel && r.source === undefined && r.cmd_sha256 === sha256(hereCommand) && r.wtree === tree)?.tests_ran ?? "unknown" };
|
||||
if (typeof here.tests_ran !== "number" || here.tests_ran <= 0) return none("skipped_zero_run", `the per_file run of ${v.remainder.length} skipped file(s) ran ${here.tests_ran} tests here (label=${hereLabel}); a lane passes on its runner count, never exit 0 alone`);
|
||||
console.error(`gstack-evidence record-ci: ${v.remainder.length} inventory file(s) skipped every test in CI; running them here one at a time (label=${hereLabel})`);
|
||||
let ran = 0;
|
||||
const notOwed: string[] = [];
|
||||
for (const file of v.remainder) {
|
||||
const hereCommand = perFileCommand(m.per_file!, [file]);
|
||||
const exit = await cmdRun(["--label", hereLabel, "--", hereCommand]);
|
||||
if (exit !== 0) return none("skipped_failed", `the per_file run of ${file} exited ${exit} (label=${hereLabel})`);
|
||||
let rows: EvidenceRecord[] = [];
|
||||
try { rows = readJsonl<EvidenceRecord>(ledgerPath().file); } catch { rows = []; }
|
||||
const row = rows.findLast((r) => r.label === hereLabel && r.source === undefined && r.cmd_sha256 === sha256(hereCommand) && r.wtree === tree);
|
||||
const n = row?.tests_ran ?? "unknown";
|
||||
if (typeof n === "number" && n > 0) { ran += n; continue; }
|
||||
if (n === 0 && registeredBunTests(row?.log_path) > 0) { notOwed.push(file); continue; }
|
||||
return none("skipped_zero_run", `the per_file run of ${file} ran ${n} tests here and registered none it skipped (label=${hereLabel}); a lane passes on its runner count, never exit 0 alone`);
|
||||
}
|
||||
here = { label: hereLabel, files: v.remainder, tests_ran: ran, ...(notOwed.length ? { not_owed: notOwed } : {}) };
|
||||
if (notOwed.length) console.error(`gstack-evidence record-ci: not owed here: ${notOwed.length} remainder file(s) skipped every test on CI and here too (env- or tool-gated by design): ${notOwed.join(", ")}`);
|
||||
for (const e of v.runs) {
|
||||
const changed = runChanged(check.deps, e.run_id, e.run_attempt);
|
||||
if (changed) return none("run_changed", changed);
|
||||
@@ -761,7 +780,7 @@ async function cmdRecordCi(argv: string[]): Promise<number> {
|
||||
commit: head, tree, dirty: false, wtree: tree, tests_ran: v.tests, source: "ci", runs, profiles: m.profiles, policy: loaded.label, ...(m.jobs ? { jobs: m.jobs } : {}), ...(v.unit ? { tests_unit: v.unit } : {}), ...(v.notOwed ? { not_owed: v.notOwed } : {}), ...(here ? { skipped_here: here } : {}),
|
||||
};
|
||||
appendJsonl(paths.file, rec, { mode: 0o600 });
|
||||
console.error(`gstack-evidence: recorded label=${label} exit=0 tests_ran=${v.tests}${v.unit ? ` unit=${v.unit}` : ""} log=- source=ci runs=${runs.join(",")} policy=${loaded.label}${here ? ` skipped_here=${here.files.length} tests_ran_here=${here.tests_ran}` : ""}`);
|
||||
console.error(`gstack-evidence: recorded label=${label} exit=0 tests_ran=${v.tests}${v.unit ? ` unit=${v.unit}` : ""} log=- source=ci runs=${runs.join(",")} policy=${loaded.label}${here ? ` skipped_here=${here.files.length} tests_ran_here=${here.tests_ran}${here.not_owed ? ` not_owed_here=${here.not_owed.length}` : ""}` : ""}`);
|
||||
if (v.notOwed) console.error(`gstack-evidence: not owed: ${v.notOwed}`);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
**CI carry no longer refuses a skipped file that skips here too (GST-1314).** When CI skipped every test in a file, `gstack-evidence record-ci` reruns it here through the lane's `per_file` runner, and a file that skipped everything again ended the carry with `skipped_zero_run`, so the whole free suite reran. Free Tests run 41754278952 had six such files, all env-gated or tool-gated by design (0 pass, 20 skip on a Capy machine), so every gstack /ship carry would have fallen back to the full lane.
|
||||
|
||||
### Fixed
|
||||
- **`gstack-evidence record-ci`:** runs the per_file remainder one file at a time. A file whose Bun summary registered tests and skipped every one here too is recorded as not owed (`skipped_here.not_owed` in the CI row, `not_owed_here=<n>` on the `LANE:` line) instead of refusing. A file that registers no test here still refuses with `skipped_zero_run`, even beside a file that ran, and so does a file whose log hit the 2 MB cap, since a cut log's last summary may not be the run's. A failing file's `skipped_failed` names it.
|
||||
- **`per_file` paths** reach the runner anchored with `./`, because Bun reads a bare relative path as a substring filter (`test/x.test.ts` also matched `browse/test/x.test.ts`).
|
||||
- **gstack's `bun run test` lane** declares `"per_file": "bun --no-env-file test --timeout=30000 {files}"`, so the runner is on main before the Free Tests writer starts listing skipped files (#3979). `--no-env-file` keeps a local `.env` from switching an env-gated file on.
|
||||
- **Docs:** `docs/pregate.md#skipped-files` describes the per-file runs, the not-owed case and the anchoring.
|
||||
|
||||
### Tests
|
||||
- `test/ship-ci-carry.test.ts`: run 41754278952's six remainder files, each printing its own skip-only Bun summary, carry with `not_owed_here=6` and reach the runner as `./<path>`; a remainder that mixes a file that ran with one that registers no test refuses with `skipped_zero_run` naming that file; a remainder file that registers no test refuses; a remainder file whose log passed 2 MB after a nested `Ran 5 tests` summary refuses instead of counting as not owed.
|
||||
+19
-11
@@ -775,20 +775,28 @@ once:
|
||||
them here. A /ship lane or guard whose inventory names one carries the
|
||||
covered files and runs only the skipped ones here, through the runner its
|
||||
`ship_lanes_from` entry declares as `per_file` (a command with `{files}`,
|
||||
such as `"per_file": "bun test --timeout=30000 {files}"`, the same form as the
|
||||
tests stage's `runner`). That run is recorded under `<label>.skipped`, the
|
||||
`LANE:` line adds `skipped_here=<n> file(s) tests_ran_here=<n>`, and the CI row
|
||||
is written only after four checks pass. In order, they are the remainder's
|
||||
exit (`skipped_failed`), its test count above zero (`skipped_zero_run`), every
|
||||
carried run again (`run_changed`), and the working tree once more
|
||||
such as `"per_file": "bun --no-env-file test --timeout=30000 {files}"`, the
|
||||
same form as the tests stage's `runner`). It runs one file at a time, each path
|
||||
anchored with `./` because Bun reads a bare relative path as a substring
|
||||
filter. Those runs are recorded under `<label>.skipped`, the `LANE:` line adds
|
||||
`skipped_here=<n> file(s) tests_ran_here=<n>`, and the CI row is written only
|
||||
after four checks pass. In order, they are each file's exit
|
||||
(`skipped_failed`), each file's test count above zero (`skipped_zero_run`),
|
||||
every carried run again (`run_changed`), and the working tree once more
|
||||
(`tree_changed`, for an edit made during those checks). An edit made while the
|
||||
remainder itself runs leaves its ledger row without a tree, so it shows as
|
||||
`skipped_zero_run`. Any of these refusals runs the whole lane here. An entry with no `per_file` refuses as
|
||||
`skipped_zero_run`. One exception to the count check (GST-1314): a file whose
|
||||
Bun summary registered tests and skipped every one of them here too, the way
|
||||
an env-gated or tool-gated file skips by design, is not owed. The CI row lists
|
||||
it under `skipped_here.not_owed` and the `LANE:` line adds
|
||||
`not_owed_here=<n>`. A file that registers no test here still refuses, and so
|
||||
does a file whose log hit the 2 MB cap, since a cut log's last summary may not
|
||||
be the run's. Any of these refusals runs the whole lane here. An entry with no `per_file` refuses as
|
||||
`inventory_skipped`, naming the files, and the lane runs here whole. A partly
|
||||
skipped file ran tests and stays covered. gstack's own Free Tests writer does
|
||||
not emit the list yet: it starts in the same follow-up pull request that
|
||||
declares `per_file` for gstack's `bun run test` lane, so gstack's carry does not
|
||||
change until both switch on together. A `tree.json`
|
||||
skipped file ran tests and stays covered. gstack's `bun run test` lane
|
||||
declares `per_file`, and its Free Tests writer starts emitting the list in a
|
||||
follow-up pull request, so the runner is in place on the base before any run
|
||||
lists a skipped file. A `tree.json`
|
||||
without the key (older writers, other repos) is read as before, with every
|
||||
listed file counted as run; add the list to your writer to close that gap.
|
||||
The remote gate's clearance does not use it: that gate runs on a Linux runner
|
||||
|
||||
+4
-4
@@ -164,14 +164,14 @@ export function recordCiResult(stderr: string): { runs?: string; tests?: string;
|
||||
const none = lines.findLast(x => x.startsWith('CARRY_NONE: '))?.slice('CARRY_NONE: '.length);
|
||||
if (at < 0) return none ? { none } : {};
|
||||
const got = / tests_ran=(\d+|n\/a)( unit=files)? log=- source=ci runs=(\S+)/.exec(lines[at]!);
|
||||
const here = / skipped_here=(\d+) tests_ran_here=(\S+)$/.exec(lines[at]!);
|
||||
const here = / skipped_here=(\d+) tests_ran_here=(\S+)( not_owed_here=\d+)?$/.exec(lines[at]!);
|
||||
const notOwed = lines.slice(at + 1).find(x => x.startsWith('gstack-evidence: not owed: '))?.slice('gstack-evidence: not owed: '.length);
|
||||
return { runs: got?.[3], tests: got?.[1], files: !!got?.[2], ...(notOwed ? { notOwed } : {}), ...(here ? { here: `skipped_here=${here[1]} file(s) tests_ran_here=${here[2]}` } : {}) };
|
||||
return { runs: got?.[3], tests: got?.[1], files: !!got?.[2], ...(notOwed ? { notOwed } : {}), ...(here ? { here: `skipped_here=${here[1]} file(s) tests_ran_here=${here[2]}${here[3] ?? ''}` } : {}) };
|
||||
}
|
||||
|
||||
/** The per_file runner command for `files`: `{files}` replaced by the paths, each single-quoted for bash. */
|
||||
/** The per_file runner command for `files`: `{files}` replaced by the paths, each anchored with ./ (Bun reads a bare relative path as a substring filter) and single-quoted for bash. */
|
||||
export const perFileCommand = (template: string, files: string[]): string =>
|
||||
template.replaceAll('{files}', files.map(f => `'${f.replace(/'/g, `'\\''`)}'`).join(' '));
|
||||
template.replaceAll('{files}', files.map(f => `'${(/^(\/|\.\/)/.test(f) ? f : `./${f}`).replace(/'/g, `'\\''`)}'`).join(' '));
|
||||
|
||||
/** `<path>: <message>` errors for `ship_lanes_from`; empty when valid. Shared with lib/pregate/config.ts. */
|
||||
export function shipLanesErrors(raw: unknown, profileNames: readonly string[]): Array<{ path: string; message: string }> {
|
||||
|
||||
@@ -526,7 +526,7 @@ describe('GST-1190: a lane carries the covered files and runs only the skipped r
|
||||
const out = preflight(f, [`free=${FREE}`]);
|
||||
expect(out.status, out.stdout + out.stderr).toBe(0);
|
||||
expect(lane(out.stdout, 'free')).toBe('LANE: free carried run=7/1 tests_ran=1 (files) skipped_here=1 file(s) tests_ran_here=1');
|
||||
expect([args(f), runs(f, 'free-runs')]).toEqual(['test/b.test.ts\n', 0]);
|
||||
expect([args(f), runs(f, 'free-runs')]).toEqual(['./test/b.test.ts\n', 0]);
|
||||
const all = rows(f);
|
||||
expect(all.find(r => r.source === 'ci')).toMatchObject({ label: 'free', tests_ran: 1, tests_unit: 'files', skipped_here: { label: 'free.skipped', files: ['test/b.test.ts'], tests_ran: 1 } });
|
||||
expect(all.find(r => r.label === 'free.skipped')).toMatchObject({ exit: 0, tests_ran: 1 });
|
||||
@@ -536,14 +536,14 @@ describe('GST-1190: a lane carries the covered files and runs only the skipped r
|
||||
const f = shipFixture(withRunner(1)); fixtures.push(f);
|
||||
world(f, { tree: { skipped_files: ['test/b.test.ts'] } });
|
||||
const rec = f.run(path.join(BIN, 'gstack-evidence'), ['record-ci', '--label', 'free', '--base', 'main', '--', FREE]);
|
||||
expect([rec.status, rec.stderr]).toEqual([1, expect.stringMatching(/^CARRY_NONE: skipped_failed: the per_file run of 1 skipped file\(s\) exited 1 \(label=free\.skipped\)$/m)]);
|
||||
expect([rec.status, rec.stderr]).toEqual([1, expect.stringMatching(/^CARRY_NONE: skipped_failed: the per_file run of test\/b\.test\.ts exited 1 \(label=free\.skipped\)$/m)]);
|
||||
expect(rows(f).filter(r => r.source === 'ci')).toEqual([]);
|
||||
expect(preflight(f, [`free=${FREE}`]).stdout).toContain('CI_CARRY: none label=free (skipped_failed');
|
||||
expect(runs(f, 'free-runs')).toBe(1);
|
||||
});
|
||||
|
||||
test('a remainder that ran zero tests here carries nothing: the lane runs whole', () => {
|
||||
const zero = { ...BASE, '.gstack/pregate.json': JSON.stringify({ run_profiles: { 'ci/free': PROFILE }, ship_lanes_from: { [FREE]: { profiles: ['ci/free'], inventory: "printf 'test/a.test.ts\\ntest/b.test.ts\\n'", per_file: "printf ' 0 pass\\n 1 skip\\n 0 fail\\nRan 1 test across 1 file. [1.00ms]\\n' >&2 # {files}" } } }, null, 2) + '\n' };
|
||||
test('a remainder file that registers no test here carries nothing: the lane runs whole', () => {
|
||||
const zero = { ...BASE, '.gstack/pregate.json': JSON.stringify({ run_profiles: { 'ci/free': PROFILE }, ship_lanes_from: { [FREE]: { profiles: ['ci/free'], inventory: "printf 'test/a.test.ts\\ntest/b.test.ts\\n'", per_file: "printf ' 0 pass\\n 0 fail\\nRan 0 tests across 1 file. [1.00ms]\\n' >&2 # {files}" } } }, null, 2) + '\n' };
|
||||
const f = shipFixture(zero); fixtures.push(f);
|
||||
world(f, { tree: { skipped_files: ['test/b.test.ts'] } });
|
||||
const rec = f.run(path.join(BIN, 'gstack-evidence'), ['record-ci', '--label', 'free', '--base', 'main', '--', FREE]);
|
||||
@@ -580,6 +580,47 @@ describe('GST-1190: a lane carries the covered files and runs only the skipped r
|
||||
expect(rows(f).filter(r => r.source === 'ci')).toEqual([]);
|
||||
});
|
||||
|
||||
// Value: protects=GST-1314: a remainder file that skips every test here too (env-gated or tool-gated by design) is recorded as not owed
|
||||
// instead of refusing the whole carry, while a file that registers no test, or a mixed remainder hiding one, still refuses;
|
||||
// fails_when=run 41754278952's six skipped files (0 pass, 20 skip locally) make every gstack carry end in skipped_zero_run again;
|
||||
// why_new=GST-1314 replay (L6, #3979); seam=fake gh, gstack-evidence record-ci, per_file runner printing bun's per-file summary
|
||||
const SIX: Array<[string, number]> = [["browse/test/security-bench.test.ts",3],["test/codex-resume-flag-semantics.test.ts",2],["test/cso-docker-integration.test.ts",5],["test/cso-node-lifecycle-integration.test.ts",1],["test/cso-stack-cold-integration.test.ts",1],["test/motion-pipeline.test.ts",8]];
|
||||
const bunSummary = (f: ShipFx, cases: Array<[string, string]>) => fs.writeFileSync(path.join(f.root, 'perfile.sh'), `printf '%s\\n' "$@" >> ../perfile-args\ncase "$1" in\n${cases.map(([pat, out]) => ` *${pat}*) printf '${out}' >&2;;\n`).join('')}esac\n`);
|
||||
const skipsOnly = (n: number) => ` 0 pass\\n ${n} skip\\n 0 fail\\nRan ${n} test${n === 1 ? '' : 's'} across 1 file. [1.00ms]\\n`;
|
||||
const policy = (files: string[]) => ({ ...BASE, '.gstack/pregate.json': JSON.stringify({ run_profiles: { 'ci/free': PROFILE }, ship_lanes_from: { [FREE]: { profiles: ['ci/free'], inventory: `printf '${['test/a.test.ts', ...files].join('\\n')}\\n'`, per_file: 'sh ../perfile.sh {files}' } } }, null, 2) + '\n' });
|
||||
|
||||
test('GST-1314: run 41754278952\'s six remainder files skip every test here too, so they are not owed and the lane carries', () => {
|
||||
const f = shipFixture(policy(SIX.map(([p]) => p))); fixtures.push(f);
|
||||
bunSummary(f, SIX.map(([p, n]) => [p, skipsOnly(n)]));
|
||||
world(f, { tree: { files: ['test/a.test.ts', ...SIX.map(([p]) => p)], skipped_files: SIX.map(([p]) => p) } });
|
||||
const rec = f.run(path.join(BIN, 'gstack-evidence'), ['record-ci', '--label', 'free', '--base', 'main', '--', FREE]);
|
||||
expect(rec.status, rec.stderr).toBe(0);
|
||||
expect(rec.stderr).toContain('not owed here: 6 remainder file(s) skipped every test on CI and here too');
|
||||
expect(args(f).trim().split('\n')).toEqual(SIX.map(([p]) => './' + p));
|
||||
const ci = rows(f).find(r => r.source === 'ci');
|
||||
expect(ci).toMatchObject({ label: 'free', tests_ran: 1, tests_unit: 'files', skipped_here: { label: 'free.skipped', files: SIX.map(([p]) => p), tests_ran: 0, not_owed: SIX.map(([p]) => p) } });
|
||||
expect(lane(preflight(f, [`free=${FREE}`]).stdout, 'free')).toBe('LANE: free carried run=7/1 tests_ran=1 (files) skipped_here=6 file(s) tests_ran_here=0 not_owed_here=6');
|
||||
});
|
||||
|
||||
test('GST-1314: a remainder that mixes a file that ran here with one that registers no test still refuses', () => {
|
||||
const f = shipFixture(policy(['test/b.test.ts', 'test/c.test.ts'])); fixtures.push(f);
|
||||
bunSummary(f, [['b.test', ' 2 pass\\n 0 fail\\nRan 2 tests across 1 file. [1.00ms]\\n'], ['c.test', ' 0 pass\\n 0 fail\\nRan 0 tests across 1 file. [1.00ms]\\n']]);
|
||||
world(f, { tree: { files: ['test/a.test.ts', 'test/b.test.ts', 'test/c.test.ts'], skipped_files: ['test/b.test.ts', 'test/c.test.ts'] } });
|
||||
const rec = f.run(path.join(BIN, 'gstack-evidence'), ['record-ci', '--label', 'free', '--base', 'main', '--', FREE]);
|
||||
expect([rec.status, rec.stderr]).toEqual([1, expect.stringMatching(/^CARRY_NONE: skipped_zero_run: the per_file run of test\/c\.test\.ts /m)]);
|
||||
expect(rows(f).filter(r => r.source === 'ci')).toEqual([]);
|
||||
});
|
||||
|
||||
test('GST-1314: a remainder file whose log hit the 2MB cap is never counted not owed from the truncated log', () => {
|
||||
const f = shipFixture(policy(['test/b.test.ts'])); fixtures.push(f);
|
||||
fs.writeFileSync(path.join(f.root, 'perfile.sh'), `printf '%s\\n' "$@" >> ../perfile-args\nprintf ' 0 pass\\n 5 skip\\n 0 fail\\nRan 5 tests across 1 file. [1.00ms]\\n' >&2\nhead -c 2200000 /dev/zero | tr '\\0' x >&2\nprintf '\\n 0 pass\\n 0 fail\\nRan 0 tests across 1 file. [1.00ms]\\n' >&2\n`);
|
||||
world(f, { tree: { files: ['test/a.test.ts', 'test/b.test.ts'], skipped_files: ['test/b.test.ts'] } });
|
||||
const rec = f.run('sh', ['-c', '"$0" "$@" 2>../record-ci.err', path.join(BIN, 'gstack-evidence'), 'record-ci', '--label', 'free', '--base', 'main', '--', FREE]);
|
||||
expect([rec.status, fs.readFileSync(path.join(f.root, 'record-ci.err'), 'utf-8')]).toEqual([1, expect.stringMatching(/^CARRY_NONE: skipped_zero_run: the per_file run of test\/b\.test\.ts ran 0 tests here /m)]);
|
||||
expect(fs.readFileSync(rows(f).findLast(r => r.label === 'free.skipped').log_path, 'utf-8')).toContain('log truncated at 2MB');
|
||||
expect(rows(f).filter(r => r.source === 'ci')).toEqual([]);
|
||||
});
|
||||
|
||||
test('per_file is validated: it needs {files} and an inventory entry', async () => {
|
||||
const { shipLanesErrors } = await import('../lib/ci-carry');
|
||||
expect(shipLanesErrors({ x: { profiles: ['p'], inventory: 'ls', per_file: 'bun test {files}' } }, ['p'])).toEqual([]);
|
||||
|
||||
Reference in new issue
Block a user