Skip to content

test(engine): tighten test-suite hermeticity and parent-aware subtest references#149

Merged
Aleksei Sviridkin (lexfrei) merged 3 commits into
mainfrom
fix/test-hermeticity-followups
May 8, 2026
Merged

test(engine): tighten test-suite hermeticity and parent-aware subtest references#149
Aleksei Sviridkin (lexfrei) merged 3 commits into
mainfrom
fix/test-hermeticity-followups

Conversation

@lexfrei

Copy link
Copy Markdown
Contributor

What changed

Two follow-ups deferred from the apply-path regression PR:

  1. TestNoWorkflowLeakageInRepoSource is now hermetic. The previous walk used filepath.WalkDir over the module root, so any working-tree artefact (notes, build scratch, generated YAML, plan files) that happened to contain a banned phrase failed go test ./... even when committed source was clean. The new committedTextFiles helper shells out to git ls-files -z so the guard is a function of the index, not the working tree. The test t.Skips when .git is absent (release tarballs, vendored copies) instead of fataling.
  2. TestNoDanglingSubtestReferencesInSource is now parent-aware. The previous flat map[slug]struct{} collector accepted a citation TestX/wrong as long as ANY test in the package had a subtest named wrong — the wrong-parent drift the guard claims to catch slipped through. The new findDanglingSubtestReferences helper parses test files via go/parser, collects t.Run literals scoped to each parent test function, and returns dangling refs with the file path so a future failure points at the exact source location.

Tests

Two focused unit tests with synthetic fixtures pin the contracts:

  • TestCommittedTextFilesIgnoresUntrackedArtefacts builds a tiny temporary git repo with one tracked + one untracked file and asserts the helper returns only the tracked one.
  • TestFindDanglingSubtestReferencesIsParentAware constructs a synthetic test source with two parent tests and asserts the same-parent exact match passes, the same-parent prefix match passes, and the cross-parent slug match is flagged as dangling. The synthetic source is assembled via string concatenation (tag := "Tes" + "t", runFn := "t.Ru" + "n") so this test file does not embed literals the production scanners would otherwise flag against this package's real test list.

Both helpers are documented with the literal-only t.Run regex caveat (table-driven t.Run(tt.name, ...) subtests are invisible to the collector), so a future maintainer who hits a false positive on a runtime-slug citation knows where to look.

Closes #137.

… references

Two follow-ups deferred from the apply-path regression PR:

1. Scope the workflow-leakage scan to git-tracked files. The previous
   filepath.WalkDir-based walk failed go test ./... on any working-tree
   artefact (notes, build scratch, generated YAML) that happened to
   contain a banned phrase, even when no committed source violated the
   rule. The new committedTextFiles helper shells out to git ls-files
   so the guard is hermetic with respect to working-tree state.

2. Make TestNoDanglingSubtestReferencesInSource parent-aware. The
   previous flat map[slug]struct{} collector accepted a citation
   TestX/wrong as long as ANY test in the package had a subtest named
   "wrong" — the wrong-parent drift the guard claims to catch slipped
   through silently. The new findDanglingSubtestReferences helper
   parses test files via go/parser, collects t.Run literals scoped to
   each parent test function, and rejects citations whose slug does
   not match a subtest under that specific parent (exact or prefix
   match within the same parent).

Both helpers get focused unit tests with synthetic fixtures so the
contracts survive future refactors:

- TestCommittedTextFilesIgnoresUntrackedArtefacts builds a tiny
  temporary git repo with one tracked + one untracked file and
  asserts the helper returns only the tracked one.
- TestFindDanglingSubtestReferencesIsParentAware constructs a
  synthetic test source with two parent tests, asserts the same-parent
  exact match passes, the same-parent prefix match passes, and the
  cross-parent slug match is flagged as dangling.

The synthetic test source in (2) is assembled via string concatenation
so engine_test.go itself does not embed the literal Test<Name>/slug
patterns the production scanner would otherwise flag against this
package's real test list.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@coderabbitai

coderabbitai Bot commented May 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Rate limit exceeded

@lexfrei has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 55 minutes and 25 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 000bb713-135b-4225-a195-847818b92c48

📥 Commits

Reviewing files that changed from the base of the PR and between 009104b and 24f31f6.

📒 Files selected for processing (1)
  • pkg/engine/engine_test.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/test-hermeticity-followups

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves the reliability of the test suite by ensuring scans only target committed files and by implementing scope-aware detection of dangling subtest references. Key additions include the committedTextFiles helper, which utilizes git ls-files, and findDanglingSubtestReferences, which uses AST parsing to verify subtest citations. Review feedback suggests refining the t.Run regex to account for whitespace and optimizing the file parsing loop to avoid redundant disk I/O and FileSet allocations.

Comment thread pkg/engine/engine_test.go Outdated
// citation, lift the slug into a literal at the t.Run call site or
// extend the collector to walk tt.name resolutions.
func findDanglingSubtestReferences(testFiles []string) ([]danglingSubtestRef, error) {
subtestRe := regexp.MustCompile(`t\.Run\("([^"\\]+)"`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current regex for capturing t.Run calls is quite restrictive. It will miss calls that include whitespace or newlines between the function name, the parenthesis, and the string literal (e.g., t.Run( "name", ...) or t.Run(\n"name", ...)). This can lead to false positive "dangling reference" errors when the scanner fails to identify valid subtests. Consider allowing optional whitespace in the pattern.

Suggested change
subtestRe := regexp.MustCompile(`t\.Run\("([^"\\]+)"`)
subtestRe := regexp.MustCompile(`t\.Run\s*\(\s*"([^"\\]+)"`)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in a8cb031subtestRe is now t\.Run\s*\(\s*"([^"\\]+)". gofmt-conformant Go has no whitespace there, but the helper now also handles hand-formatted or alternate-formatter sources without false-positive dangling-ref errors.

Comment thread pkg/engine/engine_test.go
Comment on lines +106 to +116
for _, path := range testFiles {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
return nil, errors.Wrapf(err, "parse %s", path)
}
body, err := os.ReadFile(path)
if err != nil {
return nil, errors.Wrapf(err, "read %s", path)
}
bodies[path] = body

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This loop currently reads each test file twice: once implicitly via parser.ParseFile (when the source argument is nil) and once explicitly via os.ReadFile. Additionally, it recreates the token.FileSet for every file. You can optimize this by reading the file once and passing the content to the parser, and by reusing a single FileSet for the entire scan. This reduces syscalls and memory allocations during the repo-wide scan.

Suggested change
for _, path := range testFiles {
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
if err != nil {
return nil, errors.Wrapf(err, "parse %s", path)
}
body, err := os.ReadFile(path)
if err != nil {
return nil, errors.Wrapf(err, "read %s", path)
}
bodies[path] = body
fset := token.NewFileSet()
for _, path := range testFiles {
body, err := os.ReadFile(path)
if err != nil {
return nil, errors.Wrapf(err, "read %s", path)
}
bodies[path] = body
file, err := parser.ParseFile(fset, path, body, parser.ParseComments)
if err != nil {
return nil, errors.Wrapf(err, "parse %s", path)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applied in 24f31f6os.ReadFile(path) runs once per file and the bytes are passed to parser.ParseFile(fset, path, body, parser.ParseComments) so the parser does not reopen the file. The token.FileSet is hoisted out of the loop, so a single fset covers the whole walk.

@lexfrei Aleksei Sviridkin (lexfrei) marked this pull request as ready for review May 8, 2026 07:54
Address review feedback from gemini-code-assist on
pkg/engine/engine_test.go:95: tighten subtestRe to allow optional
whitespace between t.Run, the parenthesis, and the string literal
(t\.Run\s*\(\s*"...). gofmt-conformant Go has no such whitespace, but
the helper now also handles hand-formatted or alternate-formatter
sources that another consumer might feed it.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Address review feedback from gemini-code-assist on
pkg/engine/engine_test.go:116: pass the file bytes to parser.ParseFile
so the file is read once per scan instead of twice (the previous form
let parser.ParseFile reopen the file when src=nil and then os.ReadFile
read it again). Hoist the token.FileSet out of the loop so a single
fset covers the whole walk instead of allocating one per file.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@lexfrei Aleksei Sviridkin (lexfrei) merged commit a2abbf9 into main May 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test(engine): tighten test-suite hermeticity and parent-aware subtest references

2 participants