Skip to content

test(formatters): FailNow on the running subtest, not the parent#759

Open
nikolauspschuetz wants to merge 1 commit into
cucumber:mainfrom
nikolauspschuetz:test/fmt-output-subtest-failnow
Open

test(formatters): FailNow on the running subtest, not the parent#759
nikolauspschuetz wants to merge 1 commit into
cucumber:mainfrom
nikolauspschuetz:test/fmt-output-subtest-failnow

Conversation

@nikolauspschuetz

Copy link
Copy Markdown

Summary

Test_FmtOutput runs each formatter/feature combination as a t.Run(...) subtest, and the scenario/step hooks registered in fmtOutputTest call assert.FailNow(tT, ...) when they find unexpected attachments. But tT is a package-level *testing.T set once to the parent Test_FmtOutput:

var tT *testing.T

func Test_FmtOutput(t *testing.T) {
    tT = t
    ...
    t.Run(testName, fmtOutputTest(...))   // hooks inside call FailNow(tT)

So a hook running inside a subtest calls FailNow on the parent test, from the subtest's goroutine. testing documents that FailNow must be called from the goroutine running the test it belongs to; calling it on the parent from a child goroutine can misattribute the failure or mishandle the Goexit, rather than cleanly failing the specific subtest that broke.

Fix

Assign tT to the current subtest's *testing.T at the start of each returned test function, so FailNow targets the subtest that is actually running, on its own goroutine:

return func(t *testing.T) {
    tT = t
    ...

Subtests here run serially (no t.Parallel()) and godog runs the scenarios serially (no Options.Concurrency), so the shared tT is only ever written/read for the one subtest in flight — no data race.

Verification

  • go test ./internal/formatters/ — passes (ok ... 0.764s); no behaviour change on the passing path.
  • gofmt / go vet clean.

This is a correctness fix on the failure path (a mis-targeted FailNow only manifests when a hook assertion actually fails), so it does not change the green-path output; the value is that a real failure now fails the right subtest cleanly.

Test_FmtOutput set the package-level tT to the parent *testing.T once, but
the scenario/step hooks in fmtOutputTest call assert.FailNow(tT, ...) while
running inside each t.Run subtest (on the subtest's goroutine). Calling
FailNow on the parent test from a subtest's goroutine is unsafe per the
testing docs and can misattribute or mishandle a failure.

Assign tT to the current subtest's *testing.T at the start of each returned
test function instead, so FailNow targets the subtest that is actually
running, on its own goroutine.

Signed-off-by: Nikolaus Schuetz <nikolauspschuetz@gmail.com>
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.

1 participant