mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-06 17:04:27 +01:00
* fix(timeline): reject a missing or malformed --limit with exit 2 (#1723) gstack-timeline-read parsed --limit with parseInt and fell back to 20, so '--limit 1abc' became 1, 'nope', 0 and -5 silently became the default, and a bare --limit crashed on an unbound $2. Validate the argument as a positive integer before the no-timeline early exit and exit 2 with an example. No flag still means 20. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(skill-start): echo SESSIONS, the live session-marker count (#1651) The sessions directory is touched and expired at skill start, but the count was never printed, so CLAUDE.md rules that read the preamble's session count had nothing to read. Echo SESSIONS: N (markers fresher than 120 minutes, this session included). Idea from @0xDevNinja's PR #1747, redone in the consolidated skill-start script. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(office-hours): pick the closing tier from the profile read before this session is logged (#2801) Phase 4.5 appended this session to the builder profile and Phase 6 then read the profile to choose its closing, so every run saw itself as history: a first-time user got welcome_back and was asked how the assignment issued minutes earlier went. Phase 4.5 now reads the profile before --log-session and records the prior tier, count and last-session fields in the transcript; Phase 6 uses those values (this session is prior + 1) and re-reads only the running totals. A failed read proceeds as a first session and says so. Tier headers now name the session numbers each tier actually covers. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(office-hours): print the full design doc inline before asking for approval (#879) Tool output collapses in Claude Code, so users were asked to approve a doc they could not see. Port of @RyanAlberts's PR #1116 into the carved design-and-handoff section. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(office-hours): report success only when this run wrote its design doc (#1049) Before telemetry, check the design doc this run wrote, by its exact path under the state root, never the newest file in the folder. Success needs a non-empty doc there; otherwise the outcome is abort or error with failed step design_doc. Rewrite of @walton-chris's PR #1049, which matched the newest global session marker and hardcoded ~/.gstack. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(plan reviews): skip the /office-hours prerequisite offer in spawned and headless sessions (#1958) The BENEFITS_FROM offer had no recommended option, so a spawned run had nothing defined to auto-pick and a headless run could only block. Skip it there and continue with the standard review. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore: regenerate SKILL.md files Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: office-hours tier order, failed-read fallback, inline doc, design-doc check and offer skip Rendered-block tests run the Phase 4.5 read and log on a temp state root (first session reads introduction, failed read is marked) and the pre-telemetry check against this run's doc, an empty doc, another session's doc and a path outside the state root. Parity caps take the measured growth. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore(release): v1.91.37.0 changelog and TODOS Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(browse): run watchdog test servers under a private HOME The headed-mode case starts a real server with BROWSE_HEADED=1, and its headed launch writes ~/.gstack/.auth.json for the extension. With the caller's HOME, a slow kill let that write reach the real home and failed the free runner's home-containment check (free-suite shard 5 on #3075). Give each spawned server a private HOME and GSTACK_HOME, keeping Playwright's browser cache path. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(evals): name the office-hours-auto-mode case so the PR lane can select it The PR lane runs each short probe with a Bun name pattern ending in its case id. The office-hours AskUserQuestion-blocked smoke had a descriptive test name and no CASE_TEST_NAMES label, so whenever office-hours changed its shard matched 0 tests and failed in 0 s (E2E Evals slice 6 on #3075). Name the test after its id, keep the description on the describe, and check that every literal-named PR probe has a test the pattern selects. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix(office-hours): skip a last-session greeting clause whose value is empty A profile backfilled from the timeline can carry a welcome_back or regular tier with empty LAST_ASSIGNMENT and LAST_PROJECT. The welcome_back and regular greetings now drop the "last time" clause and its question when the value it names is empty, and say only "Welcome back." 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>
274 lines
18 KiB
TypeScript
274 lines
18 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
import { spawnSync } from 'node:child_process';
|
|
import {
|
|
applyHollowShardGuard, buildRunManifest, computePaidCaseSelection, expectedPrCaseCount,
|
|
existingPromptSourceAliases,
|
|
collectorOutcomeCounts, formatProfileCoverage,
|
|
paidSelectionEnv, parseCliOptions, parseRunManifest, prProfileFileSelected,
|
|
prProfileTestNamePattern, runPaidShards, verifySliceResults,
|
|
type PaidCaseSelection, type SliceResult,
|
|
} from '../scripts/test-paid-shards';
|
|
import { PR_PROFILE_CASE_IDS, PR_PROFILE_FILES } from '../scripts/test-pr-profile';
|
|
import { fileCaseRegistration } from '../scripts/lib/paid-cases';
|
|
import { E2E_TOUCHFILES } from './helpers/touchfiles-data';
|
|
import { resolveModuleSelection } from './helpers/e2e-helpers';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '..');
|
|
const CEO_FILES = [
|
|
'test/skill-e2e-plan.test.ts', 'test/skill-e2e-ask-user-question-format-compliance.test.ts',
|
|
'test/skill-e2e-retro.test.ts', 'test/skill-llm-eval.test.ts', 'test/skill-e2e-plan-ceo-plan-mode.test.ts',
|
|
];
|
|
const ceoManifest = () => buildRunManifest({ tier: 'gate', profile: 'pr', sliceCount: 1,
|
|
evalsAll: false, env: {}, changedFiles: ['plan-ceo-review/SKILL.md.tmpl'], discovered: CEO_FILES });
|
|
|
|
describe('PR profile paid-runner integration', () => {
|
|
test('F5 fixture changes select exactly its three registered native gate cases', () => {
|
|
const cases = ['ship-local-hook-preservation', 'ship-managed-hook-refresh', 'ship-unmanaged-hook-consent'];
|
|
const selection = computePaidCaseSelection({ profile: 'full', env: {}, changedFiles: ['test/helpers/ship-hook-actor.ts'] });
|
|
expect(selection.selection.e2e?.slice().sort()).toEqual(cases);
|
|
expect(selection.selection.judges).toEqual([]);
|
|
for (const [file, expected] of [
|
|
['test/skill-e2e-ship-hook-refresh.test.ts', ['ship-managed-hook-refresh']],
|
|
['test/skill-e2e-ship-hook-consent.test.ts', ['ship-local-hook-preservation', 'ship-unmanaged-hook-consent']],
|
|
] as const) {
|
|
const selected = computePaidCaseSelection({ profile: 'full', env: {}, changedFiles: [file] });
|
|
expect(selected.selection.e2e?.slice().sort()).toEqual([...expected]);
|
|
expect(fs.existsSync(path.join(ROOT, file))).toBe(true);
|
|
const pr = computePaidCaseSelection({ profile: 'pr', env: {}, changedFiles: [file] });
|
|
expect(pr.selection.e2e?.slice().sort()).toEqual([...expected]);
|
|
expect(expectedPrCaseCount(file, pr.selection)).toBe(expected.length);
|
|
const pattern = new RegExp(prProfileTestNamePattern(file, pr.selection));
|
|
for (const id of cases) expect(pattern.test(id)).toBe(new Set<string>(expected).has(id));
|
|
}
|
|
});
|
|
|
|
test('CLI defaults remain full, explicit PR profile is gated and validated', () => {
|
|
expect(parseCliOptions([], {}).profile).toBe('full');
|
|
expect(parseCliOptions(['--profile', 'pr'], {}).profile).toBe('pr');
|
|
expect(parseCliOptions([], { EVALS_PROFILE: 'pr' }).profile).toBe('pr');
|
|
expect(() => parseCliOptions(['--profile', 'fast'], {})).toThrow('pr or full');
|
|
expect(() => parseCliOptions(['--profile'], {})).toThrow('pr or full');
|
|
expect(() => parseCliOptions(['--profile', 'pr', '--tier', 'periodic'], {})).toThrow('gate tier');
|
|
expect(() => parseCliOptions(['--profile', 'pr', '--files-per-shard', '2'], {})).toThrow('one file');
|
|
});
|
|
|
|
test('every short probe has exactly one audited file registered as a dependency', () => {
|
|
const ids = Object.values(PR_PROFILE_FILES).flat().sort();
|
|
expect(ids).toEqual([...PR_PROFILE_CASE_IDS].sort());
|
|
expect(new Set(ids).size).toBe(ids.length);
|
|
for (const [file, cases] of Object.entries(PR_PROFILE_FILES)) {
|
|
expect(fs.existsSync(path.join(ROOT, file)), file).toBe(true);
|
|
for (const id of cases) expect(E2E_TOUCHFILES[id], id).toContain(file);
|
|
}
|
|
});
|
|
|
|
test('every short probe with literal test names has a test the PR lane name pattern selects', () => {
|
|
for (const [file, cases] of Object.entries(PR_PROFILE_FILES)) {
|
|
const source = fs.readFileSync(path.join(ROOT, file), 'utf8');
|
|
if (fileCaseRegistration(file, source).computed) continue;
|
|
const titles = [...source.matchAll(/\b(?:describe\w*|test\w*|it)(?:\.\w+)?\(\s*(['"`])((?:(?!\1)[^\\]|\\.)*)\1/g)].map(m => m[2]!);
|
|
const names = titles.flatMap(outer => [outer, ...titles.map(inner => `${outer} ${inner}`)]);
|
|
for (const id of cases) {
|
|
const pattern = new RegExp(prProfileTestNamePattern(file, { e2e: [id], judges: [] }));
|
|
expect(names.some(name => pattern.test(name)), `${file} has no test named for ${id}`).toBe(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('planner binds E2E and judge IDs and plans the CEO plan-mode probe in the PR lane', () => {
|
|
const manifest = ceoManifest();
|
|
expect(manifest.profile).toBe('pr');
|
|
expect(manifest.selection?.e2e).toContain('auq-format-gate');
|
|
expect(manifest.selection?.e2e).toContain('plan-ceo-review-plan-mode');
|
|
expect(manifest.selection?.judges).toContain('plan-ceo-review/SKILL.md modes');
|
|
expect(manifest.entries.find(entry => entry.file.includes('skill-e2e-retro'))?.status).toBe('skipped-by-diff');
|
|
expect(manifest.entries.find(entry => entry.file.includes('ask-user-question'))?.status).toBe('planned');
|
|
expect(manifest.prCoverage?.deferred.some(item => item.id === 'plan-ceo-review-plan-mode')).toBe(false);
|
|
expect(manifest.entries.find(entry => entry.file === 'test/skill-e2e-plan-ceo-plan-mode.test.ts')?.status).toBe('planned');
|
|
expect(parseRunManifest(JSON.stringify(manifest))).toEqual(manifest);
|
|
const env = paidSelectionEnv('pr', manifest.selection!, manifest.selectionReason);
|
|
expect(JSON.parse(env.EVALS_SELECTION_JSON!).selected).toEqual(manifest.selection!.e2e);
|
|
expect(JSON.parse(env.EVALS_JUDGE_SELECTION_JSON!).selected).toEqual(manifest.selection!.judges);
|
|
});
|
|
|
|
test('unknown dependencies restore full gate while missing prompt coverage fails before execution', () => {
|
|
const fallback = computePaidCaseSelection({ profile: 'pr', env: {}, changedFiles: ['ETHOS.md'] });
|
|
expect(fallback.coverage?.mode).toBe('full-fallback');
|
|
expect(fallback.selection.e2e).toContain('qa-only-no-fix');
|
|
expect(fallback.selection.e2e).not.toContain('autoplan-dual-voice');
|
|
expect(fallback.coverage?.deferred.some(item => item.id === 'autoplan-dual-voice')).toBe(true);
|
|
expect(() => computePaidCaseSelection({ profile: 'pr', env: {}, changedFiles: ['unregistered/nested/SKILL.md'] })).toThrow('requires full validation');
|
|
});
|
|
|
|
test('source aliasing requires an actual template and preserves broad-only generated coverage', () => {
|
|
const aliases = existingPromptSourceAliases(['benchmark-models/SKILL.md', 'no-such-skill/SKILL.md']);
|
|
expect(aliases['benchmark-models/SKILL.md']).toBe('benchmark-models/SKILL.md.tmpl');
|
|
expect(aliases['no-such-skill/SKILL.md']).toBeUndefined();
|
|
const result = computePaidCaseSelection({ profile: 'pr', env: {}, changedFiles: ['benchmark-models/SKILL.md'] });
|
|
expect(result.coverage?.mode).toBe('pr');
|
|
expect(result.coverage?.deferredPromptFiles).toContain('benchmark-models/SKILL.md');
|
|
expect(result.coverage?.needsFullValidation).toBe(false);
|
|
});
|
|
|
|
test('F8 selects one judge and defers its two periodic readiness actors', () => {
|
|
const selection = computePaidCaseSelection({ profile: 'pr', env: {}, changedFiles: [
|
|
'sync-gbrain/SKILL.md.tmpl', 'bin/gstack-gbrain-read-capability.ts',
|
|
'test/helpers/sync-gbrain-readiness-fixture.ts',
|
|
] });
|
|
expect(selection.coverage?.mode).toBe('pr');
|
|
expect(selection.selection.judges).toEqual(['sync-gbrain/SKILL.md read-only readiness']);
|
|
expect(selection.selection.e2e).toEqual([]);
|
|
expect(selection.coverage?.deferred.map(({ id }) => id).filter(id => id.startsWith('sync-gbrain-read-')).sort()).toEqual([
|
|
'sync-gbrain-read-ready', 'sync-gbrain-read-unknown',
|
|
]);
|
|
expect(selection.coverage?.deferred.filter(({ id }) => !id.startsWith('sync-gbrain-read-')).every(({ id }) => id.startsWith('journey-'))).toBe(true);
|
|
expect(selection.coverage?.needsFullValidation).toBe(false);
|
|
});
|
|
|
|
test('version-only release changes are verified against the real merge-base before exemption', () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'pr-package-'));
|
|
const git = (args: string[]) => {
|
|
const result = spawnSync('git', args, { cwd: root, encoding: 'utf8', timeout: 10_000 });
|
|
expect(result.status, result.stderr).toBe(0);
|
|
};
|
|
try {
|
|
git(['init', '-b', 'main']);
|
|
git(['config', 'user.email', 'test@gstack.local']);
|
|
git(['config', 'user.name', 'Fixture']);
|
|
const file = path.join(root, 'package.json');
|
|
fs.writeFileSync(file, JSON.stringify({ version: '1.0.0', scripts: { test: 'bun test' } }));
|
|
git(['add', 'package.json']); git(['commit', '-m', 'seed']);
|
|
fs.writeFileSync(file, JSON.stringify({ version: '1.0.1', scripts: { test: 'bun test' } }));
|
|
const options = { profile: 'pr' as const, rootDir: root, env: { EVALS_BASE: 'main' }, changedFiles: ['package.json'] };
|
|
const versionOnly = computePaidCaseSelection(options);
|
|
expect(versionOnly.coverage?.mode).toBe('pr');
|
|
expect(versionOnly.selection).toEqual({ e2e: [], judges: [] });
|
|
fs.writeFileSync(file, JSON.stringify({ version: '1.0.1', scripts: { test: 'bun another-runner' } }));
|
|
expect(computePaidCaseSelection(options).coverage?.mode).toBe('full-fallback');
|
|
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
|
});
|
|
|
|
test('tampered profiles, broadened IDs and unplanned direct-describe files cannot import', () => {
|
|
const manifest = ceoManifest();
|
|
expect(() => parseRunManifest(JSON.stringify({ ...manifest, profile: 'quick' }))).toThrow('profile');
|
|
expect(() => parseRunManifest(JSON.stringify({ ...manifest, selection: undefined }))).toThrow('coverage/selection');
|
|
expect(() => parseRunManifest(JSON.stringify({ ...manifest, prCoverage: { ...manifest.prCoverage, missingCoverage: ['missing/SKILL.md'] } }))).toThrow('full validation');
|
|
const broad = structuredClone(manifest);
|
|
broad.selection!.e2e!.push('qa-only-no-fix'); broad.prCoverage!.e2e.push('qa-only-no-fix');
|
|
expect(() => parseRunManifest(JSON.stringify(broad))).toThrow('broad-only');
|
|
const injected = structuredClone(manifest);
|
|
injected.entries.find(entry => entry.file.includes('skill-e2e-retro'))!.status = 'planned';
|
|
injected.entries.find(entry => entry.file.includes('skill-e2e-retro'))!.slice = 1;
|
|
expect(() => parseRunManifest(JSON.stringify(injected))).toThrow('outside its PR case selection');
|
|
for (const action of ['remove', 'skip', 'duplicate'] as const) {
|
|
const missing = structuredClone(manifest);
|
|
// The CEO template's PR case runs from the AskUserQuestion format file.
|
|
const file = manifest.entries.find(entry => entry.status === 'planned' && entry.file.includes('ask-user-question'))!.file;
|
|
if (action === 'remove') missing.entries = missing.entries.filter(entry => entry.file !== file);
|
|
if (action === 'skip') missing.entries.find(entry => entry.file === file)!.status = 'skipped-by-diff';
|
|
if (action === 'duplicate') missing.entries.push({ ...missing.entries.find(entry => entry.file === file)! });
|
|
expect(() => parseRunManifest(JSON.stringify(missing)), action).toThrow('exactly one planned owning file');
|
|
}
|
|
const noJudge = structuredClone(manifest);
|
|
noJudge.entries = noJudge.entries.filter(entry => entry.file !== 'test/skill-llm-eval.test.ts');
|
|
expect(() => parseRunManifest(JSON.stringify(noJudge))).toThrow('exactly one planned owning file');
|
|
});
|
|
|
|
test('PR children reject absent or malformed selection rather than recomputing broad work', () => {
|
|
const previous = process.env.EVALS_PROFILE;
|
|
process.env.EVALS_PROFILE = 'pr';
|
|
let recomputed = 0;
|
|
try {
|
|
expect(() => resolveModuleSelection(undefined, () => { recomputed++; return null; })).toThrow('persisted');
|
|
expect(() => resolveModuleSelection('{"selected":42}', () => { recomputed++; return null; })).toThrow('valid persisted');
|
|
expect(recomputed).toBe(0);
|
|
} finally {
|
|
if (previous === undefined) delete process.env.EVALS_PROFILE; else process.env.EVALS_PROFILE = previous;
|
|
}
|
|
});
|
|
|
|
test('name filters handle literal labels and never select broad raw tests', () => {
|
|
const selection = { e2e: ['plan-review-report', 'auq-format-gate'], judges: ['plan-ceo-review/SKILL.md modes'] };
|
|
const report = new RegExp(prProfileTestNamePattern('test/skill-e2e-plan.test.ts', selection));
|
|
expect(report.test('Plan Review Report E2E /plan-eng-review writes GSTACK REVIEW REPORT to plan file')).toBe(true);
|
|
expect(report.test('Plan Review Report E2E plan-ceo-review')).toBe(false);
|
|
const auq = new RegExp(prProfileTestNamePattern('test/skill-e2e-ask-user-question-format-compliance.test.ts', selection));
|
|
expect(auq.test("/plan-ceo-review's first AskUserQuestion is a compliant decision brief (7/7 + substance)")).toBe(true);
|
|
const judge = new RegExp(prProfileTestNamePattern('test/skill-llm-eval.test.ts', selection));
|
|
expect(judge.test('LLM-as-judge plan-ceo-review/SKILL.md modes')).toBe(true);
|
|
expect(judge.test('LLM-as-judge plan-ceo-review/SKILLxmd modes')).toBe(false);
|
|
expect(prProfileFileSelected('test/skill-e2e-retro.test.ts', selection)).toBe(false);
|
|
});
|
|
|
|
test('real Bun child executes only the persisted case through the actual registered helper', async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'pr-profile-'));
|
|
const file = 'test/skill-e2e-plan.test.ts';
|
|
const receipt = path.join(root, 'receipt.txt');
|
|
const selection: PaidCaseSelection = { e2e: ['plan-review-report'], judges: [] };
|
|
try {
|
|
fs.mkdirSync(path.join(root, 'test'));
|
|
fs.writeFileSync(path.join(root, file), `
|
|
import { test } from 'bun:test';
|
|
import { appendFileSync } from 'node:fs';
|
|
import { describeIfSelected, testIfSelected } from ${JSON.stringify(path.join(ROOT, 'test/helpers/e2e-helpers.ts'))};
|
|
describeIfSelected('fixture', ['plan-review-report', 'plan-ceo-review'], () => {
|
|
test('/plan-eng-review writes GSTACK REVIEW REPORT to plan file', async () => { appendFileSync(${JSON.stringify(receipt)}, 'selected\\n'); }, 5000);
|
|
testIfSelected('plan-ceo-review', async () => { throw new Error('unselected model boundary executed'); }, 5000);
|
|
test('unexpected raw paid call', () => { throw new Error('raw model boundary executed'); });
|
|
});
|
|
`);
|
|
const options = {
|
|
rootDir: root, timeoutMs: 10_000, jobs: 1, log: () => {},
|
|
expectedCases: { [file]: 1 }, casePatterns: { [file]: prProfileTestNamePattern(file, selection) },
|
|
env: { PATH: path.dirname(process.execPath), ...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}),
|
|
HOME: root, GSTACK_HOME: path.join(root, 'state'), GSTACK_EVAL_DIR: path.join(root, 'evals'),
|
|
GSTACK_CLAUDE_CLI_VERSION: 'free-fixture', EVALS: '1', EVALS_TIER: 'gate', EVALS_PREFLIGHT_OK: '1',
|
|
...paidSelectionEnv('pr', selection, 'free helper execution proof') },
|
|
};
|
|
const result = await runPaidShards([[file]], options);
|
|
expect(result.outcomes[0].status).toBe('passed');
|
|
expect(fs.readFileSync(receipt, 'utf8')).toBe('selected\n');
|
|
// The same selected callback cannot cover a second promised case.
|
|
const incomplete = await runPaidShards([[file]], { ...options, expectedCases: { [file]: 2 } });
|
|
expect(incomplete.outcomes[0].status).toBe('failed');
|
|
} finally { fs.rmSync(root, { recursive: true, force: true }); }
|
|
}, 30_000);
|
|
|
|
test('report rejects missing selection binding and hollow or incomplete executed-case counts', () => {
|
|
const manifest = ceoManifest();
|
|
const result: SliceResult = {
|
|
version: 1, tier: 'gate', profile: 'pr', selection: manifest.selection, sliceIndex: 1, sliceCount: 1,
|
|
outcomes: manifest.entries.filter(entry => entry.status === 'planned').map(entry => ({
|
|
files: [entry.file], status: 'passed', exitCode: 0, elapsedMs: 1,
|
|
executedTests: expectedPrCaseCount(entry.file, manifest.selection!), skippedTests: 0,
|
|
...(entry.budget ? { budget: entry.budget } : {}),
|
|
})),
|
|
};
|
|
expect(verifySliceResults(manifest, [result]).ok).toBe(true);
|
|
expect(verifySliceResults(manifest, [{ ...result, selection: undefined }]).ok).toBe(false);
|
|
const hollow = structuredClone(result);
|
|
hollow.outcomes[0].skippedTests = hollow.outcomes[0].executedTests;
|
|
expect(verifySliceResults(manifest, [hollow]).ok).toBe(false);
|
|
const guarded = applyHollowShardGuard([{ ...hollow.outcomes[0], shard: 1, groupPid: null }], { evalsAll: false, requireExecuted: true });
|
|
expect(guarded[0].status).toBe('passed-empty');
|
|
});
|
|
|
|
test('report distinguishes retained/deferred coverage and counts every executed/reused record', () => {
|
|
const manifest = ceoManifest();
|
|
const lines = formatProfileCoverage(manifest).join('\n');
|
|
expect(lines).toContain('profile=pr mode=pr');
|
|
expect(lines).toContain(`selected E2E=${manifest.selection!.e2e!.length}`);
|
|
expect(lines).toContain('not PR passes');
|
|
expect(collectorOutcomeCounts([{ tests: [
|
|
{ name: 'retry', suite: 'judge', passed: false },
|
|
{ name: 'retry', suite: 'judge', passed: true, execution: 'executed' },
|
|
{ name: 'cached', suite: 'judge', passed: true, execution: 'reused' },
|
|
{ name: 'failed', suite: 'native', passed: false },
|
|
// Paid evals never retry: a later pass never replaces an earlier failed record.
|
|
] }])).toEqual({ executed: 3, reused: 1, passed: 2, failed: 2, manual_accepted: 0, attempts: 4 });
|
|
});
|
|
});
|