cmd/go: fail fast across packages

Fixes #33038

Change-Id: I0b70c450be1c1cc59ddc1f3fddad227deccc7e14
GitHub-Last-Rev: 302ebd648a
GitHub-Pull-Request: golang/go#62714
Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest-race,gotip-windows-amd64-race,gotip-windows-amd64-longtest
Reviewed-on: https://go-review.googlesource.com/c/go/+/529198
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Bryan Mills <bcmills@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
This commit is contained in:
Alexander Yastrebov 2024-02-17 04:35:24 +00:00 committed by Gopher Robot
parent c07b9b0036
commit 806aeb1e76
3 changed files with 49 additions and 4 deletions

View file

@ -21,6 +21,7 @@ import (
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
"cmd/go/internal/base"
@ -540,6 +541,7 @@ var (
testC bool // -c flag
testCoverPkgs []*load.Package // -coverpkg flag
testCoverProfile string // -coverprofile flag
testFailFast bool // -failfast flag
testFuzz string // -fuzz flag
testJSON bool // -json flag
testList string // -list flag
@ -589,9 +591,10 @@ var (
testHelp bool // -help option passed to test via -args
testKillTimeout = 100 * 365 * 24 * time.Hour // backup alarm; defaults to about a century if no timeout is set
testWaitDelay time.Duration // how long to wait for output to close after a test binary exits; zero means unlimited
testCacheExpire time.Time // ignore cached test results before this time
testKillTimeout = 100 * 365 * 24 * time.Hour // backup alarm; defaults to about a century if no timeout is set
testWaitDelay time.Duration // how long to wait for output to close after a test binary exits; zero means unlimited
testCacheExpire time.Time // ignore cached test results before this time
testShouldFailFast atomic.Bool // signals pending tests to fail fast
testBlockProfile, testCPUProfile, testMemProfile, testMutexProfile, testTrace string // profiling flag that limits test to one package
@ -1355,6 +1358,11 @@ func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action)
// Wait for previous test to get started and print its first json line.
select {
case <-r.prev:
// If should fail fast then release next test and exit.
if testShouldFailFast.Load() {
close(r.next)
return nil
}
case <-base.Interrupted:
// We can't wait for the previous test action to complete: we don't start
// new actions after an interrupt, so if that action wasn't already running
@ -1631,6 +1639,10 @@ func (r *runTestActor) Act(b *work.Builder, ctx context.Context, a *work.Action)
fmt.Fprintf(cmd.Stdout, "ok \t%s\t%s%s%s\n", a.Package.ImportPath, t, coveragePercentage(out), norun)
r.c.saveOutput(a)
} else {
if testFailFast {
testShouldFailFast.Store(true)
}
base.SetExitStatus(1)
if cancelSignaled {
fmt.Fprintf(cmd.Stdout, "*** Test killed with %v: ran too long (%v).\n", base.SignalTrace, testKillTimeout)

View file

@ -48,7 +48,7 @@ func init() {
cf.Int("count", 0, "")
cf.String("cpu", "", "")
cf.StringVar(&testCPUProfile, "cpuprofile", "", "")
cf.Bool("failfast", false, "")
cf.BoolVar(&testFailFast, "failfast", false, "")
cf.StringVar(&testFuzz, "fuzz", "", "")
cf.Bool("fullpath", false, "")
cf.StringVar(&testList, "list", "", "")

View file

@ -48,6 +48,15 @@ stdout -count=1 'FAIL - '
! go test ./failfast_test.go -run='TestFatal[CD]' -failfast=false
stdout -count=2 'FAIL - '
# cross package failfast
! go test -p 1 -failfast ./a ./b ./c
stdout -count=1 'FAIL - '
stdout -count=1 'FAIL - TestFailingPkgA'
-- go.mod --
module m
go 1.21.0
-- failfast_test.go --
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
@ -111,3 +120,27 @@ func TestFatalC(t *testing.T) {
func TestFatalD(t *testing.T) {
t.Fatalf("FAIL - %s", t.Name())
}
-- a/a_test.go --
package a
import "testing"
func TestFailingPkgA(t *testing.T) {
t.Errorf("FAIL - %s", t.Name())
}
-- b/b_test.go --
package b
import "testing"
func TestFailingPkgB(t *testing.T) {
t.Errorf("FAIL - %s", t.Name())
}
-- c/c_test.go --
package c
import "testing"
func TestFailingPkgC(t *testing.T) {
t.Errorf("FAIL - %s", t.Name())
}