Skip to content

proposal: testing: run tests in parallel by default #73805

Open
@CAFxX

Description

@CAFxX

Proposal Details

#21214 proposed the same but was declined -- mostly because at the time we had no good way of rolling out such a change safely. As this is not the case anymore, and the benefits keep increasing with time (core counts are increasing, codebases and test suites are growing, etc.), I think it would be worth reconsidering it.

The idea would be to make parallel execution the default for modules that specify a go version (not toolchain version) higher than v1.X. Modules with go version below v1.X would still default to serial execution, as today. This would mean that behaviour would not change until a module go version (not toolchain version) is manually updated to v1.X or later.

Individual tests would be given a way to opt-out of parallel execution, similar to how today we allow individual tests to opt-in to parallel execution.

I encourage reviewers to focus on the goal when discussing this proposal, i.e. I welcome alternative ways/solutions to achieve the same goal, and will gladly update the proposal if a better solution is suggested. Some discussion is happening in https://gophers.slack.com/archives/C0VP8EF3R/p1747791714490489.

Proposal

  • Add testing.T.Serial() to allow individual tests to opt out of the new default. Mention that Serial was the default before go v1.X. No-op if the default is serial execution. Panics or fails the test if called after Parallel().
  • Update the doc for testing.T.Parallel() to mention that this is the default starting with go v1.X. No-op if the default is parallel execution. Panics or fails the test if called after Serial().
  • For modules that specify in go.mod a go version equal or greater than v1.X, run the tests in parallel by default; modules that specify an older version would still run tests serially be default.
  • Linters and other tools could start flagging t.Parallel() calls as unneeded for modules with a go.mod go version equal or greater than v1.X. Migration assistance tools can be built (possibly even in go fix) that would add Serial() calls to any test that does not have a Parallel() call, and remove Parallel() calls from any test that has it. Possibly such tools could either be advertised (or even ran automatically, since the transformation is trivially reversible) when running go mod edit -go=... or similar commands that change/update the module's go version.

FAQ

Will I be forced to rewrite my tests to be runnable in parallel?

No.

A migration tool would automatically turn existing tests like:

func TestA(t *testing.T) {
  // ...
}

func TestB(t *testing.T) {
  t.Parallel()
  // ...
}

into:

func TestA(t *testing.T) {
  t.Serial()
  // ...
}

func TestB(t *testing.T) {
  // ...
}

thus maintaining the current semantics, even after the migration to the new go version.

Users could then, over time, drop the t.Serial from tests that do not really need them.

Worth pointing out that, even without version control, this change is trivially reversible.

Why should tests be executed in parallel by default?

Because serial tests:

  • can accidentally hide hidden dependencies between tests
  • can accidentally hide data races
  • implicitly endorse the use of global state
  • are slower to run when part of a large test suite1

Parallel tests being the default nudge the whole ecosystem in a better direction, without forcing anyone to have to write parallel tests.

Isn't this going to be disruptive?

Statistics indicate that the vast majority of modules do not specify the most recent go version (1.24.*), or even just a go module version that was released in the last year.

go.mod go version module count2
1.24 58.1k
1.23 179k
1.22 177k
1.21 150k
1.20 101k
1.19 88.1k
1.18 90.6k
1.17 82.9k
1.16 92.2k
1.15 90.1k
1.14 68.9k
1.13 77.3k
1.12 42.0k
1.11 4.4k
1.10 0.2k
1.9 0.2k
1.8 <0.1k

This would seem to suggest that most existing modules will not be migrated to a go module version where the default is parallel execution. For modules that are migrated, the automated migration would make sure that existing semantics are maintained, therefore not requiring maintainers to spend any extra effort during the migration or later - unless they want to make use of parallel test execution (but this is orthogonal to the change, as it would be the case even if this proposal is not enacted).

I can acknowledge that some users that do not follow go development may be surprised by the new t.Serial() calls added by the migration tool, but I would suggest that a quick online search would almost certainly lead them to an article that explains the rationale.

All the above considered, I would argue that the change may be at most characterized as "surprising", but not really "disruptive".

Footnotes

  1. This is further made relevant by the increasing core counts of modern infrastructure. Making use of the available cores to reduce test duration is much easier if most/all tests are executed in parallel. While it is true that go test is normally already able to parallelize test execution across packages, this does not really help much in a number of scenarios, e.g. when there are few packages to test (common in edit/test cycles), or when a package takes significantly longer to be tested than all others.

  2. Module count obtained on 2025-05-29 by searching on public Github repos using the search query language:"Go Module" /^go 1\.x/ where x is replaced by the go minor version (e.g. 24 for 1.24)

Metadata

Metadata

Assignees

No one assigned

    Labels

    LibraryProposalIssues describing a requested change to the Go standard library or x/ libraries, but not to a toolPerformanceProposal

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions