-
Notifications
You must be signed in to change notification settings - Fork 4k
tests: avoid t.Parallel() when the top test has defers #37072
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
tbg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed 2 of 2 files at r1.
Reviewable status:complete! 0 of 0 LGTMs obtained (waiting on @knz and @RaduBerinde)
pkg/rpc/context_test.go, line 803 at r1 (raw file):
pName := fmt.Sprintf("client%sserver,server%sclient", connIcon(c.partitionC2S), connIcon(c.partitionS2C)) t.Run(kaName+"/"+pName, func(t *testing.T) { // TODO(knz): t.Parallel disabled until leaktest.AfterTest learns
Hmm, that's a bummer. Test takes 28s (up from <4s with enough parallelism). I think here I'd rip out the subtests and start them as goroutines with a waitgroup, writing into an error channel. That way we get even better parallelism than with t.Parallel and the main test will wait. Or, if we decide that sometimes use of t.Parallel is ok and we don't want to lint it away, we can keep using it and we'll just add the waitgroup to the test.
var wg sync.WaitGroup
wg.Add(len(testCases))
errCh := make(chan error, len(testCases))
for _, c := range testCases {
go func(c testCase) {
errCh <- errors.Wrapf(runTestCase(c), "%+v", c)
}(c)
}
wg.Wait()
close(errCh)
for err := range errCh {
t.Errorf("%+v", err)
}pkg/sql/physical_props_test.go, line 363 at r1 (raw file):
// how to wait for all sub-tests to complete. // // t.Parallel()
This one doesn't change the duration of the test (I verified), so I'd just nuke this.
pkg/sql/physical_props_test.go, line 572 at r1 (raw file):
// how to wait for all sub-tests to complete. // // t.Parallel()
Verified that this can be nuked as well.
pkg/sql/physical_props_test.go, line 769 at r1 (raw file):
tc := testCases[i] t.Run(tc.name, func(t *testing.T) { // TODO(knz): t.Parallel disabled until leaktest.AfterTest learns
Not tested, but pretty sure all the tests in this file don't need t.Parallel.
RaduBerinde
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. Seems like there is a missing API in test that we should file an issue for - either a function to register a cleanup to happen after the parent waits for child subtests, or a function to wait for all subtests.
We could use a wait group though, replacing every t.Parallel() call with the block wg.Add(1); defer wg.Done(); t.Parallel()
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @knz and @RaduBerinde)
RaduBerinde
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @knz and @tbg)
pkg/rpc/context_test.go, line 803 at r1 (raw file):
Previously, tbg (Tobias Grieger) wrote…
Hmm, that's a bummer. Test takes 28s (up from <4s with enough parallelism). I think here I'd rip out the subtests and start them as goroutines with a waitgroup, writing into an error channel. That way we get even better parallelism than with
t.Paralleland the main test will wait. Or, if we decide that sometimes use oft.Parallelis ok and we don't want to lint it away, we can keep using it and we'll just add the waitgroup to the test.var wg sync.WaitGroup wg.Add(len(testCases)) errCh := make(chan error, len(testCases)) for _, c := range testCases { go func(c testCase) { errCh <- errors.Wrapf(runTestCase(c), "%+v", c) }(c) } wg.Wait() close(errCh) for err := range errCh { t.Errorf("%+v", err) } </blockquote></details> Ah, I made the same suggestion. Why do we need the error channel though? The test infrastructure should still work, we just need the `Wait()`. <!-- Sent from Reviewable.io -->
tbg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @knz and @RaduBerinde)
pkg/rpc/context_test.go, line 803 at r1 (raw file):
Previously, RaduBerinde wrote…
Ah, I made the same suggestion. Why do we need the error channel though? The test infrastructure should still work, we just need the
Wait().
Using testing.T on a goroutine is not supported by the testing framework, and will cause problems. In particular, t.Fatal off the test goroutine will result in an uncaught runtime.Goexit. Note how runTestCase doesn't even get to use the t. That's to avoid this problem in the first place. Using t.Error is fine btw, but that's not a pattern I want to put out there.
|
pkg/rpc/context_test.go, line 803 at r1 (raw file): Previously, tbg (Tobias Grieger) wrote…
But you don't need a goroutine. Just call |
|
Raphael points out that the subtests block inside |
knz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RFAL
Reviewable status:
complete! 0 of 0 LGTMs obtained (waiting on @tbg)
pkg/rpc/context_test.go, line 803 at r1 (raw file):
Previously, RaduBerinde wrote…
But you don't need a goroutine. Just call
Runlike before and putwg.Done()inside that.Runwill return when the inner test callsParallel(it has to, or the parent can't continue to call moreRuns).
Modified to use Tobias' suggestion.
pkg/sql/physical_props_test.go, line 363 at r1 (raw file):
Previously, tbg (Tobias Grieger) wrote…
This one doesn't change the duration of the test (I verified), so I'd just nuke this.
Done.
pkg/sql/physical_props_test.go, line 572 at r1 (raw file):
Previously, tbg (Tobias Grieger) wrote…
Verified that this can be nuked as well.
Done.
pkg/sql/physical_props_test.go, line 769 at r1 (raw file):
Previously, tbg (Tobias Grieger) wrote…
Not tested, but pretty sure all the tests in this file don't need
t.Parallel.
Done.
tbg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional suggestion: add a lint against t.Parallel following the example here (ironic that there would be a call to t.Parallel() in that same snippet):
cockroach/pkg/testutils/lint/lint_test.go
Lines 715 to 726 in e3cbf33
| t.Parallel() | |
| cmd, stderr, filter, err := dirCmd( | |
| pkgDir, | |
| "git", | |
| "grep", | |
| "-nE", | |
| `\.Clone\([^)]`, | |
| "--", | |
| "*.go", | |
| ":!util/protoutil/clone_test.go", | |
| ":!util/protoutil/clone.go", | |
| ) |
Reviewed 2 of 2 files at r2.
Reviewable status:complete! 1 of 0 LGTMs obtained (waiting on @knz and @RaduBerinde)
pkg/rpc/context_test.go, line 803 at r1 (raw file):
Previously, knz (kena) wrote…
Modified to use Tobias' suggestion.
Optional nit: move this out of the test so that it doesn't have access to a *testing.T, or do t := (*testing.T)(nil) here to reliably catch any accidental calls in the method.
knz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
optional suggestion: add a lint against t.Parallel
Good idea. Done. Will merge this PR once #36952 has merged, as this will remove the one remaining call to t.Parallel().
Reviewable status:
complete! 0 of 0 LGTMs obtained (and 1 stale) (waiting on @tbg)
pkg/rpc/context_test.go, line 803 at r1 (raw file):
Previously, tbg (Tobias Grieger) wrote…
Optional nit: move this out of the test so that it doesn't have access to a
*testing.T, or dot := (*testing.T)(nil)here to reliably catch any accidental calls in the method.
Done. This was a good call -- there was a remaining use of t inside the function.
tbg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed 1 of 1 files at r3, 4 of 4 files at r4.
Reviewable status:complete! 1 of 0 LGTMs obtained (waiting on @knz)
pkg/rpc/context_test.go, line 803 at r1 (raw file):
Previously, knz (kena) wrote…
Done. This was a good call -- there was a remaining use of
tinside the function.
🐛 🔨
pkg/testutils/lint/lint_test.go, line 775 at r4 (raw file):
stream.GrepNot(`// SAFE FOR TESTING`), ), func(s string) { t.Errorf("\n%s <- forbidden, use a sync.WaitGroup instead", s)
Refer to golang/go#31651 in the message to satisfy the curious.
knz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewable status:
complete! 1 of 0 LGTMs obtained (waiting on @tbg)
pkg/testutils/lint/lint_test.go, line 775 at r4 (raw file):
Previously, tbg (Tobias Grieger) wrote…
Refer to golang/go#31651 in the message to satisfy the curious.
Done.
tbg
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed 1 of 1 files at r5.
Reviewable status:complete! 1 of 0 LGTMs obtained
Sub-tests that invoke `t.Parallel()` get to run concurrently with their parent test, and may be delayed arbitrarily past beyond the end of the termination of the parent test (including beyond the execution of its `defer` calls). This means it's unsafe to call `t.Parallel()` with e.g. `leaktest.AfterTest()`. This patch removes `t.Parallel` from the test in `sql/physical_props_test.go` and modifies `pkg/rpc/TestGRPCKeepaliveFailureFailsInflightRPCs` to use a wait group instead. Release note: None
…ightRPCs Since this now runs on its own goroutine, it is invalid for it to access the surrounding `testing.T`. This patch moves the code so that `t` is clearly out of scope. Release note: None
This suggests using `sync.WaitGroup` instead. Release note: None
|
bors r=tbg,RaduBerinde |
37072: tests: avoid t.Parallel() when the top test has defers r=tbg,RaduBerinde a=knz Sub-tests that invoke `t.Parallel()` get to run concurrently with their parent test, and may be delayed arbitrarily past beyond the end of the termination of the parent test (including beyond the execution of its `defer` calls). This means it's unsafe to call `t.Parallel()` with e.g. `leaktest.AfterTest()`. Release note: None Co-authored-by: Raphael 'kena' Poss <knz@cockroachlabs.com>
Build succeeded |
Sub-tests that invoke
t.Parallel()get to run concurrently withtheir parent test, and may be delayed arbitrarily past beyond the end
of the termination of the parent test (including beyond the execution
of its
defercalls).This means it's unsafe to call
t.Parallel()withe.g.
leaktest.AfterTest().Release note: None