-
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
proposal: testing: support running examples in parallel #35852
Comments
One interesting possibility for the
This crops up in examples for concurrency libraries, such as in |
That already exists as |
The proposal here focuses on a way to signal that an example should be run in parallel. I don't see an obvious path forward here other than write examples that run quicker. |
You raise a good point about At the same time, I find "just write faster examples" to not be a satisfactory solution here. It feels akin to "just write faster tests" instead of writing parallel tests. For example, I have a package which drives Chrome browsers; examples are self-contained, so they must start a new Chrome process and talk to it to perform some example actions. There is absolutely no way I can make those examples run faster. All the other alternatives I've listed before are similarly unsatisfactory. Are we sure there are no other ways to run current examples in parallel? Since they modify global state by design, how about running them as separate processes, kind of like how |
The mapping of the It's true that if the code under test prints to So one option to preserve copy-and-pasteability might be to scope the func ExampleFoo(e *testing.E) {
fmt := e.MockFmt()
e.Parallel()
// Example:
fmt.Println(42)
// Output:
// 42
} |
I still think that separate processes is an option, but I'd be fine with @bcmills' solution as well. Existing examples would behave the same, while those wanting parallelism would need to add a couple of lines with |
It's also not only os.Stdout and os.Stderr. Examples are just meant to be run one at a time. I admit the problem here but it was just a failure of vision in the design. I don't see any way to fix it, and I'm not convinced it's particularly important. The difference between "write faster examples" and "write faster tests" is that examples are meant to be trivial illustrations, while tests are not. This seems like a (reluctant) likely decline. Leaving open for a week for final comments. |
If you mean owning the entire process and global state, what I briefly mentioned in #35852 (comment) was to run parallel examples by exec-ing the current test binary, in a way that it only runs the example, capturing its output. For those tests that do more work and need to be parallel, the overhead wouldn't matter. Existing examples wouldn't be affected. That seems like a fine solution to me. I don't think "examples are meant to be trivial" is a good outcome here. If we're in a world where examples are only for trivial stuff, where am I supposed to put non-trivial runnable examples? As I explained in the original post, all alternatives are terrible in their own way. |
If there is startup code that runs for a while, then re-execing the binary will duplicate all of that by introducing more startup time. I don't think that's a viable solution here. There's just not a path forward here short of completely redefining what examples are. I'm sorry about that, but pressure to keep examples short is not all bad. No change in consensus, so declining. |
Summary
Examples that run with
go test
always do so sequentially with all other examples and tests. I think this doesn't scale well at all with the size of packages or amount of examples, and encourages developers to only write a few simple examples per package.I propose two solutions below; I lean towards the second one, which is more powerful.
Description
We can have examples which show up in the godoc but aren't runnable, and runnable examples which are run as part of
go test
. This is enough for most use cases.However, take the case where one has more than a handful of examples in a single package, and they each take a non-trivial amount of time to run, such as 200ms. Since they cannot run in parallel with themselves or any other test within the same package,
go test
can take multiple seconds than it really needs to.The root of the problem is that there's no way to do the equivalent of a test's
t.Parallel()
for a test.Below are some workarounds I considered:
go test ./...
faster, as there's built-in parallelism between packages being tested. However, this splits up godoc pages, and it's not unreasonable to have more than a handful of examples in a single package.go test
, and it's easier for the user to see what it does and how to run it.I propose two solutions to this.
Solution one: a variant of
// Output:
A different special comment that marks the example as parallel. For example:
The syntax here is up to bikeshedding, but the idea behind the parentheses are to allow more "modes" in the future, to mirror more
testing.T
methods likeParallel()
.Solution two: add
testing.E
To mirror
testing.T
, offering a subset of the methods such asParallel()
. An example could optionally take it as a parameter:If we don't want
e.Parallel()
to be part of the example code, we could add another special comment to specify when the example code actually starts:This change is more invasive, but it's also more consistent with tests, and allows to more easily extend examples in the future. For example, we could also use this solution to solve #31310 via a
testing.E.Skipf
method.We could also even fix other examples issues like #21111; that one could be done via
e.Error
ore.Fatal
, which I'd say is significantly better thanlog.Fatal
orpanic
.testing.E
could implementtesting.TB
if we want, but I don't think it's necessary.I prefer this second solution, because it's more powerful, and doesn't add more syntax to the special
// Output:
comment.The text was updated successfully, but these errors were encountered: