fix(test-runner): strip GHA ::group:: wrappers before file attribution

On GitHub Actions bun wraps each file's log section in ::group::. The
un-stripped header failed FILE_HEADER_RE, failures attributed to the
PREVIOUS file, and the terminal recap's re-printed (fail) lines landed
under a phantom second file — the first Linux run reported 5 real
failures as 10 across 2 files (one of them innocent). Strip the prefix
before matching; the existing file+test dedupe then absorbs the recap.
This commit is contained in:
Garry Tan 2026-08-15 17:40:54 -07:00
parent 4466af6a50
commit dedcd3f4f0
No known key found for this signature in database
GPG Key ID: C1F69E85C74EFE1D
2 changed files with 29 additions and 1 deletions

View File

@ -638,7 +638,12 @@ export class FreeRunReporter {
}
private handleLine(rawLine: string, origin: StreamOrigin): void {
const line = stripAnsiLine(rawLine);
// GitHub Actions: bun wraps each file's section in ::group::<header>.
// Without stripping, the real header fails FILE_HEADER_RE, failures get
// attributed to the PREVIOUS file, and the terminal recap's re-printed
// (fail) lines land under a second phantom file (observed on the first
// Linux run: 5 real failures reported as 10 across 2 files).
const line = stripAnsiLine(rawLine).replace(/^::group::/, '');
let visible = false;
const header = FILE_HEADER_RE.exec(line);

View File

@ -484,6 +484,29 @@ describe('test-free-shards: output contract (log capture, quiet console, failure
});
});
describe('test-free-shards: GitHub Actions log-group attribution', () => {
const failLine = (name: string) => `(fail) ${name} [1.00ms]`;
// On GHA (GITHUB_ACTIONS=1) bun wraps each file's section in ::group::.
// Unstripped, the real header fails FILE_HEADER_RE, failures attribute to
// the PREVIOUS file, and the terminal recap's re-printed (fail) lines land
// under a phantom second file — the first Linux run reported 5 real
// failures as 10 across 2 files.
test('::group::-wrapped headers attribute failures to the right file, once', () => {
const reporter = new FreeRunReporter(['test/a.test.ts', 'test/b.test.ts']);
reporter.write('::group::test/a.test.ts:\n', 'stderr');
reporter.write('::endgroup::\n', 'stderr');
reporter.write('::group::test/b.test.ts:\n', 'stderr');
reporter.write(`${failLine('planted')}\n`, 'stderr');
reporter.write('::endgroup::\n', 'stderr');
// Terminal recap re-prints the failing file header + result line.
reporter.write('1 tests failed:\n', 'stderr');
reporter.write('::group::test/b.test.ts:\n', 'stderr');
reporter.write(`${failLine('planted')}\n`, 'stderr');
reporter.end();
expect(reporter.report().failures).toEqual([{ file: 'test/b.test.ts', testName: 'planted' }]);
});
});
describe('test-free-shards: curated-list census pins', () => {
// A renamed test file must FAIL here, not silently drop its serialization
// (a phantom TREE_MUTATING key means the reader races regenerating shards