Skip to content

proposal: testing: support running examples in parallel #35852

@mvdan

Description

@mvdan

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:

  • Rewriting some of the examples as tests. Not good, because they are lost in the godoc, and hidden from the users.
  • Splitting the examples in multiple packages. This makes 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.
  • Make some of the examples non-runnable. Perhaps the best option today, but still not great. A runnable example is always verified by 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:

func ExampleFoo() {
    ...

    // Output (parallel):
    // ...
}

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 like Parallel().

Solution two: add testing.E

To mirror testing.T, offering a subset of the methods such as Parallel(). An example could optionally take it as a parameter:

func ExampleFoo(e *testing.E) {
    e.Parallel()
    ...

    // Output:
    // ...
}

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:

func ExampleFoo(e *testing.E) {
    e.Parallel()

    // Example:
    ...

    // Output:
    // ...
}

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 or e.Fatal, which I'd say is significantly better than log.Fatal or panic.

testing.E could implement testing.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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions