Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore: avoid concurrent usage of t.FailNow#1683

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
johnstcn merged 3 commits intomainfromcj/ruleguard-testing-t-race
May 24, 2022

Conversation

johnstcn
Copy link
Member

@johnstcnjohnstcn commentedMay 23, 2022
edited
Loading

Context

https://pkg.go.dev/testing#T.FailNow

FailNow marks the function as having failed and stops its execution by calling runtime.Goexit(which then runs all deferred calls in the current goroutine). Execution will continue at thenext test or benchmark. FailNow must be called from the goroutine running the test orbenchmark function, not from other goroutines created during the test. Calling FailNowdoes not stop those other goroutines.

Problem

We make heavy use ofgithub.com/stretchr/testify/require in our unit test code. However, one "gotcha" about every method in therequire package is that it will callt.FailNow under the hood if any assertion fails. This isunsafe to do outside of the main testing goroutine. There's actually anopen issue about this behaviour.

For example, the following code can trigger a data race:

func Test_SomeOperationThatMightFail(t *testing.T) {    c := make(chan struct{})    go func() {         err := someOperationThatMightFail()        require.NoError(t, err)        c <- struct{}{}    }()    <-c}

Solution

  1. Replace existing concurrent usages ofrequire with the equivalentassert function (github.com/stretchr/testify/assert)
  2. Add a Ruleguard rule to detect concurrent usage ofgithub.com/stretchr/testify/assert.* ort.FailNow

(Explicitly tagging@Emyrk as our resident Ruleguard laywer)

Emyrk and kylecarbs reacted with thumbs up emoji
@johnstcnjohnstcn requested review fromEmyrk anda teamMay 23, 2022 19:48
@johnstcnjohnstcn self-assigned thisMay 23, 2022
@johnstcnjohnstcn changed the titlechore: avoid race conditions in unit test assertionschore: avoid concurrent usage of t.FailNowMay 23, 2022
@coadler
Copy link
Contributor

What about for cases where the test can't continue if a call fails?

@johnstcn
Copy link
MemberAuthor

What about for cases where the test can't continue if a call fails?

I'd expect we'd need a separate channel to signalt.FailNow from the main goroutine, plus the relevant scaffolding.
However, I would expect the worst case here is that you end up with a NPE or an AIOBE, which should still cause the test to fail.

@johnstcn
Copy link
MemberAuthor

johnstcn commentedMay 23, 2022
edited
Loading

For reference, here's what failed in the first lint run (not sure why it didn't show up in the runner output):

provisioner/terraform/parse_test.go:36:3: ruleguard: Do not call functions that may call t.FailNow in a goroutine, as this can cause data races (see testing.go:834) (gocritic)                require.NoError(t, err)                ^provisioner/terraform/provision_test.go:56:3: ruleguard: Do not call functions that may call t.FailNow in a goroutine, as this can cause data races (see testing.go:834) (gocritic)                require.NoError(t, err)                ^coderd/database/pubsub_test.go:51:4: ruleguard: Do not call functions that may call t.FailNow in a goroutine, as this can cause data races (see testing.go:834) (gocritic)                        require.NoError(t, err)                        ^make: *** [Makefile:56: lint] Error 1

Edit: these are fixed now, only show up on Linux

@Emyrk
Copy link
Member

I imagine we might get some weird logs from the test not stopping at the require in some cases, but using assert makes more sense in a lot of those go routines 👍

@kylecarbs
Copy link
Member

Is this the cause of weird test output occasionally? (can't find a sample, but I feel like you understand what I mean)

@johnstcn
Copy link
MemberAuthor

Is this the cause of weird test output occasionally? (can't find a sample, but I feel like you understand what I mean)

I think it's a contributing factor. At this stage I think it's basically a case of eliminating all of the possible sources of flakes to find that really annoying one, like that old phone you dropped down a sofa somewhere and has an alarm set.

Copy link
Member

@mafredrimafredri left a comment

Choose a reason for hiding this comment

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

This looks great!

Idea (for another PR): Considering we have this widespread pattern ofgo func()ing in tests where the goroutine may outlive the test, perhaps we could consider some kind of test helper that synchronizes the goroutine/cancellation to the test viat.Cleanup.

Usage could look like this (ctx will be cancelled at the end of test and test exit delayed until func exits):

testhelper.Go(t,func(ctx context.Context) {err:=cmd.ExecuteContext(ctx)(require|assert).NoError(err)})

Package name is obv. bad.. not sure what a good name would be.testutil,ctest,testy... 😄

johnstcn reacted with thumbs up emoji
@johnstcn
Copy link
MemberAuthor

This looks great!

Idea (for another PR): Considering we have this widespread pattern ofgo func()ing in tests where the goroutine may outlive the test, perhaps we could consider some kind of test helper that synchronizes the goroutine/cancellation to the test viat.Cleanup.

Usage could look like this (ctx will be cancelled at the end of test and test exit delayed until func exits):

testhelper.Go(t,func(ctx context.Context) {err:=cmd.ExecuteContext(ctx)(require|assert).NoError(err)})

Package name is obv. bad.. not sure what a good name would be.testutil,ctest,testy... 😄

Yeah this is deffo a goodt.Helper candidate

@johnstcnjohnstcn merged commitc2f74f3 intomainMay 24, 2022
@johnstcnjohnstcn deleted the cj/ruleguard-testing-t-race branchMay 24, 2022 07:58
kylecarbs pushed a commit that referenced this pull requestJun 10, 2022
* chore: golangci: add linter rule to report usage of t.FailNow inside goroutines* chore: avoid t.FailNow in goroutines to appease the race detector
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@mafredrimafredrimafredri approved these changes

@EmyrkEmyrkAwaiting requested review from Emyrk

Assignees

@johnstcnjohnstcn

Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

5 participants
@johnstcn@coadler@Emyrk@kylecarbs@mafredri

[8]ページ先頭

©2009-2025 Movatter.jp