-
Notifications
You must be signed in to change notification settings - Fork 18.3k
Description
Important
The latest version of this proposal is in #73567 (comment), with a minor update in #73567 (comment)
This is a revised version of #67434, and an alternative to the (now-withdrawn) #73062.
One issue with the existing experimental synctest API is that it can interact poorly with the testing package. In particular:
T.Cleanup
executes cleanup functions registered in a bubble outside of that bubble. When the cleanup function is used to shut down bubbled goroutines, this can cause problems.T.Context
returns a context with a non-bubbled Done channel, which is probably not what you want to use inside a bubble.
See the introduction to #73062 for some more details.
The proposal: We replace synctest.Run
with synctest.Test
.
// Test executes f in a new goroutine.
//
// The new goroutine and any goroutines transitively started by it form
// an isolated "bubble".
// Test waits for all goroutines in the bubble to exit before returning.
//
// Goroutines in the bubble use a synthetic time implementation.
// The initial time is midnight UTC 2000-01-01.
//
// Time advances when every goroutine in the bubble is blocked.
// For example, a call to time.Sleep will block until all other
// goroutines are blocked and return after the bubble's clock has
// advanced. See [Wait] for the specific definition of blocked.
//
// If every goroutine is blocked and there are no timers scheduled,
// Test panics.
//
// Channels, time.Timers, and time.Tickers created within the bubble
// are associated with it. Operating on a bubbled channel, timer, or ticker
// from outside the bubble panics.
//
// The [*testing.T] provided to f has the following properties:
//
// - Functions registered with T.Cleanup run inside the bubble,
// immediately before Test returns.
// - The [context.Context] returned by T.Context has a Done channel
// associated with the bubble.
// - T.Run panics. Subtests may not be created within the bubble.
func Test(t *testing.T, f func(*testing.T))
// Wait is unchanged from the existing proposal.
func Wait()
To anticipate a few possible questions:
What about testing.B
?
Using synctest to run benchmarks is probably always a mistake, since the mechanism of establishing and maintaining the bubble will interfere with the code being benchmarked. Not supporting synctest within a benchmark is probably a positive.
What about testing.F
?
Perhaps we should have a synctest.Fuzz
as well. Alternatively, we could make synctest.Test
generic. If we don't decide to add synctest.Fuzz
now, this could be easily added in the future. (Making Test
generic would need to be done now, though.)
What about non-test cases?
All our current intended uses for synctest
are in tests, and I don't believe I've heard of any cases of it being used outside of a test function. We can add synctest.Run
back in the future if it turns out to be useful. Making it more inconvenient to use synctest
outside of tests (and more convenient to use it in tests) isn't a bad thing at this point in time.
Why not make this a method on testing.T
?
Keeping the entire API in the testing/synctest
package is good for documentation purposes: It avoids adding even more documentation to the testing
package (already quite large) and lets us put all the documentation for bubbles in a single place.
What happens to users of the current experimental API?
We'll keep Run
around when GOEXPERIMENT=synctest is set for at least one release cycle, to give people a chance to gracefully transition to Test
.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status