-
Notifications
You must be signed in to change notification settings - Fork 17.8k
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
testing: reconsider adding Context method to testing.T #36532
Comments
Did you intend for your sample |
@seh yup, fixed, thanks! |
I'm having second thoughts about this proposal. Given that it's so simple to define your own test-context function, does I believe the function below does almost exactly the same thing as the
|
I think the changes to In particular, that change would allow you to do something like: func TestFoo(t *testing.T) {
g, ctx := errgroup.New(context.Background())
t.Cleanup(g.Stop)
g.Go(func() error {
defer wg.Done()
doSomething(ctx)
return nil
})
} |
Thinking about this some more: I think a Some users would likely assume that it is (only) cancelled if and when the test is marked as failed. Others might assume that it is cancelled at start of the Since there is no single “intuitive” interpretation, we should avoid the ambiguous name. Perhaps we could find a less ambiguous name, but given that the explicit code isn't much longer I'm not sure that's worth the API weight. |
After some discussion with @mvdan, I realised that my original proposal here was not sufficient for his use case (he wants to start tearing things down immediately there's a test error). I don't agree with tearing everything down on any call to Calling wdyt? |
The approach in that We don't need to couple cancellation to Note that one of the changes in that |
I like that thought. The only issue is that you'd have to be careful to add the corresponding code to every goroutine, I guess. That might turn out to be a pain (and it's error-prone). We'd still need to drop the "FailNow must be called from the goroutine running the test or benchmark function, not from other goroutines created during the test." sentence from the docs. Does that seem reasonable to you? |
That seems reasonable. I would probably replace that sentence with a more explicit one, rather than dropping it outright. Perhaps something like:
And in the comment for
And for
|
I am interested in continuing this discussion, can we add this to the proposal rotation, or was there something blocking the discussion? |
What I've done for couple of projects was to introduce the following function: const (
// Arbitrary amount of time to let tests exit cleanly before main process terminates.
timeoutGracePeriod = 10 * time.Second
)
// contextWithDeadline returns context with will timeout before t.Deadline().
func contextWithDeadline(t *testing.T) context.Context {
t.Helper()
deadline, ok := t.Deadline()
if !ok {
return context.Background()
}
ctx, cancel := context.WithDeadline(context.Background(), deadline.Truncate(timeoutGracePeriod))
t.Cleanup(cancel)
return ctx
} This allows to properly respect gracePeriod := time.Second
ctx := t.ContextWithDeadline(gracePeriod) We could also validate that |
I am strongly in favor of adding this. It seems like every single test that I write begins with: func TestXxx(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel() I have also found that tests which use contexts and don't begin with that preamble usually leak resources or contain other context related bugs. I think making this functionality built-in would help encourage better testing. |
Is this proposal in a review? Looks like it's tagged but inactive. |
This proposal has been added to the active column of the proposals project |
Added this proposal back to minutes due to the ping, but it looks like the conversation meandered quite a bit at the start. It sounds like the current proposal (the only one I see that wasn't recanted) is to have each test basically implicitly start with
and then t.Context() returns ctx. There was some discussion about cancelling the context during FailNow, and about documenting when FailNow is allowed to be called. We can handle the documentation in a separate discussion. Does anyone think the 'defer cancel' should be replaced / augmented by cancel during FailNow? It seems like FailNow will run the deferred cancel eventually anyway. It also seems like writing the two lines above is pretty clear and maybe preferable to library magic that you have to guess at when it happens. It seems like cancel should run before cleanups, so that cleanups can wait for resources, and that happens naturally with the defer as written. I will try to grep for numbers about how often code in tests uses this pattern explicitly. |
I looked into what existing tests do. There are many many with
but I was surprised how many of those do not do I extracted one example line from one version of each distinct module where I found a match, shuffled the matches, and posted the results at https://swtch.com/tmp/cancel.html. Taking the first 25 as a random sample, I found that 15 of them (numbers 2, 5, 6, 10, 11, 13, 14, 15, 16, 18, 20, 22, 23, 24, 25) would not work with an implicit cancel at the end of the test. Instead they are all doing things like canceling midway and then checking that cancelation worked properly. Given that approximately 60% of the uses of |
Another category to consider are tests which use Tests which use |
@iangudger Hmm, that will be much harder to look for. For example I have written tests recently that use context.Background() and pass that context into various routines, but none of those routines leave any goroutines behind. So it's perfectly fine that I didn't use WithCancel and didn't cancel the context. I suspect that the majority of context.Background() without WithCancel are perfectly fine in this way. I'm reluctant to add new API to fix what may well be a fairly rare problem. |
I'd argue that any test which uses I've worked at two startups and joined when the companies did not have very many tests. During my tenure at both startups, we wrote a lot of tests. As the number of tests in each package expanded, the issues associated with leaking resources started to show up. At both companies, it happened at least once that running all of the tests in a package would no longer work after adding another test similar to the tests already in the package. Adding a cancelable context with a defer at the beginning of each test fixed the issue at both companies. Further, after converting all of the tests to use cancelable contexts, new tests would occasionally slip in with uncancelable contexts and need to be fixed. My claim is that:
|
I write too many tests using context.Background() out of laziness, since I’m trying to focus on the rest of the test, and my whole team does the same. t.Context() would be very helpful to us. Ian’s points describe our situation perfectly. |
Adds a new Context method to testing.T, that returns a context, that is canceled before the end of its test function. For golang#36532.
Adds a new Context method to testing.T, that returns a context, that is canceled before the end of its test function. Fixes golang#36532.
Change https://go.dev/cl/603959 mentions this issue: |
Adds a new Context method to testing.T, that returns a context, that is canceled before the end of its test function. Fixes golang#36532.
No change in consensus, so accepted. 🎉
|
Adds a new Context method to testing.T, that returns a context, that is canceled before the end of its test function. Don't inherit parent's text context. Add release notes. Fixes golang#36532.
Adds a new Context method to testing.T, that returns a context, that is canceled before the end of its test function. Don't inherit parent's text context. Add release notes. Fixes golang#36532.
Adds a new Context method to testing.T, that returns a context, that is canceled before the end of its test function. Don't inherit parent's text context. Add release notes. Fixes golang#36532.
See #70480 for a follow-up. |
Change https://go.dev/cl/637236 mentions this issue: |
CL 603959 added T.Context for #36532. The discussion on the proposal only mentions t.Context. However, the implementation of CL 603959 also added B.Context and F.Context. They were added to the API listing, and B.Context was mentioned in the release notes. Unfortunately, the new B.Context and F.Context methods always returned nil, rather than a context.Context value. This change adds a working implementation of B.Context and F.Context. For #36532 Fixes #70866 Change-Id: I8a44e6649fb658e4f641ffb7efd08b4374f578ef Reviewed-on: https://go-review.googlesource.com/c/go/+/637236 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: David Chase <drchase@google.com> Reviewed-by: Damien Neil <dneil@google.com>
CL 603959 also added @ianlancetaylor brought up the additional API at proposal review today and we did a mini-review and signed off on the addition. (Honestly, I'm surprised that, as far as I can tell, this never came up in the original discussion!) Obviously we should endeavor to not bypass the usual process like this, but mistakes happen, and I'm glad we caught this before the 1.24 release. I think this is also a great demonstration of why we should have a formalized process for small proposals, which is something I plan on working on soon. Thanks everyone! |
Do you think that is possible to add in the changelog a command (gofmt?) to bulk replace the |
I'm not aware of a tool that will do that today. And it's not obviously better. The real advantage of |
I've added a mention of this to the "modernizers" issue: #70815 (comment) |
There have been various previous proposals (#16221 #18182 #18199 #18368) to add a Context method to testing.T. #16221 was even accepted and implemented but then reverted.
By my understanding, the principal argument against adding a Context method was that Context provides a way to tell things to shut down, but no way to wait for things to actually finish, so adding this functionality doesn't actually provide a good way to wait for tests to gracefully shut down, because they might fail and resources might continue to be used even after the testing package has marked the tests as successful.
For example @bcmills wrote:
Similarly from @niemeyer here:
However, @dsnet, who reverted the code, suggested that the decision wasn't necessarily final:
I propose that the recently added T.Cleanup method can now provide the wait mechanism that's needed - the other half of
Context.Done
. We can define the semantics such that the the testing context is cancelled just before invoking the Cleanup-registered functions. That way, it's easy to both listen for the test being done, and also to wait for asynchronous operations to finish when it is.By hooking into the
Cleanup
mechanism, we don't presuppose any particular kind of waiting - this can hook into whatever mechanism your infrastructure provides for graceful shutdown.A possible API description:
Example usage:
The text was updated successfully, but these errors were encountered: