Commit 153e4096 authored by Inanc Gumus's avatar Inanc Gumus Committed by Russ Cox

testing: add -failfast to go test

When -test.failfast flag is provided to go test,
no new tests get started after the first failure.

Fixes #21700

Change-Id: I0092e72f25847af05e7c8e1b811dcbb65a00cbe7
Reviewed-on: https://go-review.googlesource.com/74450
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarRuss Cox <rsc@golang.org>
parent 4a483ce2
...@@ -1537,6 +1537,9 @@ ...@@ -1537,6 +1537,9 @@
// benchmarks should be executed. The default is the current value // benchmarks should be executed. The default is the current value
// of GOMAXPROCS. // of GOMAXPROCS.
// //
// -failfast
// Do not start new tests after the first test failure.
//
// -list regexp // -list regexp
// List tests, benchmarks, or examples matching the regular expression. // List tests, benchmarks, or examples matching the regular expression.
// No tests, benchmarks or examples will be run. This will only // No tests, benchmarks or examples will be run. This will only
......
...@@ -5199,3 +5199,47 @@ func TestGoTestJSON(t *testing.T) { ...@@ -5199,3 +5199,47 @@ func TestGoTestJSON(t *testing.T) {
} }
t.Fatalf("did not see JSON output") t.Fatalf("did not see JSON output")
} }
func TestFailFast(t *testing.T) {
tg := testgo(t)
defer tg.cleanup()
tests := []struct {
run string
failfast bool
nfail int
}{
{"TestFailingA", true, 1},
{"TestFailing[AB]", true, 1},
{"TestFailing[AB]", false, 2},
// mix with non-failing tests:
{"TestA|TestFailing[AB]", true, 1},
{"TestA|TestFailing[AB]", false, 2},
// mix with parallel tests:
{"TestFailingB|TestParallelFailingA", true, 2},
{"TestFailingB|TestParallelFailingA", false, 2},
{"TestFailingB|TestParallelFailing[AB]", true, 3},
{"TestFailingB|TestParallelFailing[AB]", false, 3},
// mix with parallel sub-tests
{"TestFailingB|TestParallelFailing[AB]|TestParallelFailingSubtestsA", true, 3},
{"TestFailingB|TestParallelFailing[AB]|TestParallelFailingSubtestsA", false, 5},
{"TestParallelFailingSubtestsA", true, 1},
// only parallels:
{"TestParallelFailing[AB]", false, 2},
// non-parallel subtests:
{"TestFailingSubtestsA", true, 1},
{"TestFailingSubtestsA", false, 2},
}
for _, tt := range tests {
t.Run(tt.run, func(t *testing.T) {
tg.runFail("test", "./testdata/src/failfast_test.go", "-run="+tt.run, "-failfast="+strconv.FormatBool(tt.failfast))
nfail := strings.Count(tg.getStdout(), "FAIL - ")
if nfail != tt.nfail {
t.Errorf("go test -run=%s -failfast=%t printed %d FAILs, want %d", tt.run, tt.failfast, nfail, tt.nfail)
}
})
}
}
...@@ -187,7 +187,7 @@ control the execution of any test: ...@@ -187,7 +187,7 @@ control the execution of any test:
const testFlag2 = ` const testFlag2 = `
-bench regexp -bench regexp
Run only those benchmarks matching a regular expression. Run only those benchmarks matching a regular expression.
By default, no benchmarks are run. By default, no benchmarks are run.
To run all benchmarks, use '-bench .' or '-bench=.'. To run all benchmarks, use '-bench .' or '-bench=.'.
The regular expression is split by unbracketed slash (/) The regular expression is split by unbracketed slash (/)
characters into a sequence of regular expressions, and each characters into a sequence of regular expressions, and each
...@@ -237,6 +237,9 @@ const testFlag2 = ` ...@@ -237,6 +237,9 @@ const testFlag2 = `
benchmarks should be executed. The default is the current value benchmarks should be executed. The default is the current value
of GOMAXPROCS. of GOMAXPROCS.
-failfast
Do not start new tests after the first test failure.
-list regexp -list regexp
List tests, benchmarks, or examples matching the regular expression. List tests, benchmarks, or examples matching the regular expression.
No tests, benchmarks or examples will be run. This will only No tests, benchmarks or examples will be run. This will only
......
...@@ -40,15 +40,16 @@ var testFlagDefn = []*cmdflag.Defn{ ...@@ -40,15 +40,16 @@ var testFlagDefn = []*cmdflag.Defn{
{Name: "bench", PassToTest: true}, {Name: "bench", PassToTest: true},
{Name: "benchmem", BoolVar: new(bool), PassToTest: true}, {Name: "benchmem", BoolVar: new(bool), PassToTest: true},
{Name: "benchtime", PassToTest: true}, {Name: "benchtime", PassToTest: true},
{Name: "blockprofile", PassToTest: true},
{Name: "blockprofilerate", PassToTest: true},
{Name: "count", PassToTest: true}, {Name: "count", PassToTest: true},
{Name: "coverprofile", PassToTest: true}, {Name: "coverprofile", PassToTest: true},
{Name: "cpu", PassToTest: true}, {Name: "cpu", PassToTest: true},
{Name: "cpuprofile", PassToTest: true}, {Name: "cpuprofile", PassToTest: true},
{Name: "failfast", BoolVar: new(bool), PassToTest: true},
{Name: "list", PassToTest: true}, {Name: "list", PassToTest: true},
{Name: "memprofile", PassToTest: true}, {Name: "memprofile", PassToTest: true},
{Name: "memprofilerate", PassToTest: true}, {Name: "memprofilerate", PassToTest: true},
{Name: "blockprofile", PassToTest: true},
{Name: "blockprofilerate", PassToTest: true},
{Name: "mutexprofile", PassToTest: true}, {Name: "mutexprofile", PassToTest: true},
{Name: "mutexprofilefraction", PassToTest: true}, {Name: "mutexprofilefraction", PassToTest: true},
{Name: "outputdir", PassToTest: true}, {Name: "outputdir", PassToTest: true},
......
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package failfast
import "testing"
func TestA(t *testing.T) {
// Edge-case testing, mixing unparallel tests too
t.Logf("LOG: %s", t.Name())
}
func TestFailingA(t *testing.T) {
t.Errorf("FAIL - %s", t.Name())
}
func TestB(t *testing.T) {
// Edge-case testing, mixing unparallel tests too
t.Logf("LOG: %s", t.Name())
}
func TestParallelFailingA(t *testing.T) {
t.Parallel()
t.Errorf("FAIL - %s", t.Name())
}
func TestParallelFailingB(t *testing.T) {
t.Parallel()
t.Errorf("FAIL - %s", t.Name())
}
func TestParallelFailingSubtestsA(t *testing.T) {
t.Parallel()
t.Run("TestFailingSubtestsA1", func(t *testing.T) {
t.Errorf("FAIL - %s", t.Name())
})
t.Run("TestFailingSubtestsA2", func(t *testing.T) {
t.Errorf("FAIL - %s", t.Name())
})
}
func TestFailingSubtestsA(t *testing.T) {
t.Run("TestFailingSubtestsA1", func(t *testing.T) {
t.Errorf("FAIL - %s", t.Name())
})
t.Run("TestFailingSubtestsA2", func(t *testing.T) {
t.Errorf("FAIL - %s", t.Name())
})
}
func TestFailingB(t *testing.T) {
t.Errorf("FAIL - %s", t.Name())
}
...@@ -242,6 +242,9 @@ var ( ...@@ -242,6 +242,9 @@ var (
// full test of the package. // full test of the package.
short = flag.Bool("test.short", false, "run smaller test suite to save time") short = flag.Bool("test.short", false, "run smaller test suite to save time")
// The failfast flag requests that test execution stop after the first test failure.
failFast = flag.Bool("test.failfast", false, "do not start new tests after the first test failure")
// The directory in which to create profile files and the like. When run from // The directory in which to create profile files and the like. When run from
// "go test", the binary always runs in the source directory for the package; // "go test", the binary always runs in the source directory for the package;
// this flag lets "go test" tell the binary to write the files in the directory where // this flag lets "go test" tell the binary to write the files in the directory where
...@@ -269,6 +272,8 @@ var ( ...@@ -269,6 +272,8 @@ var (
haveExamples bool // are there examples? haveExamples bool // are there examples?
cpuList []int cpuList []int
numFailed uint32 // number of test failures
) )
// common holds the elements common between T and B and // common holds the elements common between T and B and
...@@ -767,6 +772,10 @@ func tRunner(t *T, fn func(t *T)) { ...@@ -767,6 +772,10 @@ func tRunner(t *T, fn func(t *T)) {
t.start = time.Now() t.start = time.Now()
t.raceErrors = -race.Errors() t.raceErrors = -race.Errors()
fn(t) fn(t)
if t.failed {
atomic.AddUint32(&numFailed, 1)
}
t.finished = true t.finished = true
} }
...@@ -779,7 +788,7 @@ func tRunner(t *T, fn func(t *T)) { ...@@ -779,7 +788,7 @@ func tRunner(t *T, fn func(t *T)) {
func (t *T) Run(name string, f func(t *T)) bool { func (t *T) Run(name string, f func(t *T)) bool {
atomic.StoreInt32(&t.hasSub, 1) atomic.StoreInt32(&t.hasSub, 1)
testName, ok, _ := t.context.match.fullName(&t.common, name) testName, ok, _ := t.context.match.fullName(&t.common, name)
if !ok { if !ok || shouldFailFast() {
return true return true
} }
t = &T{ t = &T{
...@@ -1021,6 +1030,9 @@ func runTests(matchString func(pat, str string) (bool, error), tests []InternalT ...@@ -1021,6 +1030,9 @@ func runTests(matchString func(pat, str string) (bool, error), tests []InternalT
for _, procs := range cpuList { for _, procs := range cpuList {
runtime.GOMAXPROCS(procs) runtime.GOMAXPROCS(procs)
for i := uint(0); i < *count; i++ { for i := uint(0); i < *count; i++ {
if shouldFailFast() {
break
}
ctx := newTestContext(*parallel, newMatcher(matchString, *match, "-test.run")) ctx := newTestContext(*parallel, newMatcher(matchString, *match, "-test.run"))
t := &T{ t := &T{
common: common{ common: common{
...@@ -1209,3 +1221,7 @@ func parseCpuList() { ...@@ -1209,3 +1221,7 @@ func parseCpuList() {
cpuList = append(cpuList, runtime.GOMAXPROCS(-1)) cpuList = append(cpuList, runtime.GOMAXPROCS(-1))
} }
} }
func shouldFailFast() bool {
return *failFast && atomic.LoadUint32(&numFailed) > 0
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment