Skip to content

Commit e4a2c38

Browse files
committed
cmd/go, testing: add go test -skip flag
For proposal #41583, add a new 'go test -skip' flag to make it easy to disable specific tests, benchmarks, examples, or fuzz targets. Fixes #41583. Change-Id: Id12a6575f505dafdce4a149aedc454a002e93afa Reviewed-on: https://go-review.googlesource.com/c/go/+/421439 TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Russ Cox <[email protected]> Reviewed-by: Bryan Mills <[email protected]>
1 parent 819e339 commit e4a2c38

File tree

12 files changed

+177
-59
lines changed

12 files changed

+177
-59
lines changed

src/cmd/go/alldocs.go

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/go/internal/test/flagdefs.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cmd/go/internal/test/test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ control the execution of any test:
306306
run too, so that -run=X/Y matches and runs and reports the result
307307
of all tests matching X, even those without sub-tests matching Y,
308308
because it must run them to look for those sub-tests.
309+
See also -skip.
309310
310311
-short
311312
Tell long-running tests to shorten their run time.
@@ -320,6 +321,14 @@ control the execution of any test:
320321
integer N, then N will be used as the seed value. In both cases,
321322
the seed will be reported for reproducibility.
322323
324+
-skip regexp
325+
Run only those tests, examples, fuzz tests, and benchmarks that
326+
do not match the regular expression. Like for -run and -bench,
327+
for tests and benchmarks, the regular expression is split by unbracketed
328+
slash (/) characters into a sequence of regular expressions, and each
329+
part of a test's identifier must match the corresponding element in
330+
the sequence, if any.
331+
323332
-timeout d
324333
If a test binary runs longer than duration d, panic.
325334
If d is 0, the timeout is disabled.

src/cmd/go/internal/test/testflag.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func init() {
6666
cf.Int("parallel", 0, "")
6767
cf.String("run", "", "")
6868
cf.Bool("short", false, "")
69+
cf.String("skip", "", "")
6970
cf.DurationVar(&testTimeout, "timeout", 10*time.Minute, "")
7071
cf.String("fuzztime", "", "")
7172
cf.String("fuzzminimizetime", "", "")
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
go test -v -run Test -skip T skip_test.go
2+
! stdout RUN
3+
stdout '^ok.*\[no tests to run\]'
4+
5+
go test -v -skip T skip_test.go
6+
! stdout RUN
7+
8+
go test -v -skip 1 skip_test.go
9+
! stdout Test1
10+
stdout RUN.*Test2
11+
stdout RUN.*Test2/3
12+
13+
go test -v -skip 2/3 skip_test.go
14+
stdout RUN.*Test1
15+
stdout RUN.*Test2
16+
! stdout Test2/3
17+
18+
go test -v -skip 2/4 skip_test.go
19+
stdout RUN.*Test1
20+
stdout RUN.*Test2
21+
stdout RUN.*Test2/3
22+
23+
24+
-- skip_test.go --
25+
package skip_test
26+
27+
import "testing"
28+
29+
func Test1(t *testing.T) {
30+
}
31+
32+
func Test2(t *testing.T) {
33+
t.Run("3", func(t *testing.T) {})
34+
}

src/testing/benchmark.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ func runBenchmarks(importPath string, matchString func(pat, str string) (bool, e
536536
}
537537
}
538538
ctx := &benchContext{
539-
match: newMatcher(matchString, *matchBenchmarks, "-test.bench"),
539+
match: newMatcher(matchString, *matchBenchmarks, "-test.bench", *skip),
540540
extLen: len(benchmarkName("", maxprocs)),
541541
}
542542
var bs []InternalBenchmark

src/testing/fuzz.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -471,12 +471,12 @@ func runFuzzTests(deps testDeps, fuzzTests []InternalFuzzTarget, deadline time.T
471471
if len(fuzzTests) == 0 || *isFuzzWorker {
472472
return ran, ok
473473
}
474-
m := newMatcher(deps.MatchString, *match, "-test.run")
474+
m := newMatcher(deps.MatchString, *match, "-test.run", *skip)
475475
tctx := newTestContext(*parallel, m)
476476
tctx.deadline = deadline
477477
var mFuzz *matcher
478478
if *matchFuzz != "" {
479-
mFuzz = newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz")
479+
mFuzz = newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip)
480480
}
481481
fctx := &fuzzContext{deps: deps, mode: seedCorpusOnly}
482482
root := common{w: os.Stdout} // gather output in one place
@@ -532,7 +532,7 @@ func runFuzzing(deps testDeps, fuzzTests []InternalFuzzTarget) (ok bool) {
532532
if len(fuzzTests) == 0 || *matchFuzz == "" {
533533
return true
534534
}
535-
m := newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz")
535+
m := newMatcher(deps.MatchString, *matchFuzz, "-test.fuzz", *skip)
536536
tctx := newTestContext(1, m)
537537
tctx.isFuzzing = true
538538
fctx := &fuzzContext{

src/testing/helper_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111

1212
func TestTBHelper(t *T) {
1313
var buf strings.Builder
14-
ctx := newTestContext(1, newMatcher(regexp.MatchString, "", ""))
14+
ctx := newTestContext(1, allMatcher())
1515
t1 := &T{
1616
common: common{
1717
signal: make(chan bool),
@@ -55,7 +55,7 @@ helperfuncs_test.go:67: 10
5555

5656
func TestTBHelperParallel(t *T) {
5757
var buf strings.Builder
58-
ctx := newTestContext(1, newMatcher(regexp.MatchString, "", ""))
58+
ctx := newTestContext(1, newMatcher(regexp.MatchString, "", "", ""))
5959
t1 := &T{
6060
common: common{
6161
signal: make(chan bool),
@@ -81,7 +81,7 @@ func (nw *noopWriter) Write(b []byte) (int, error) { return len(b), nil }
8181

8282
func BenchmarkTBHelper(b *B) {
8383
w := noopWriter(0)
84-
ctx := newTestContext(1, newMatcher(regexp.MatchString, "", ""))
84+
ctx := newTestContext(1, allMatcher())
8585
t1 := &T{
8686
common: common{
8787
signal: make(chan bool),

src/testing/match.go

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
// matcher sanitizes, uniques, and filters names of subtests and subbenchmarks.
1616
type matcher struct {
1717
filter filterMatch
18+
skip filterMatch
1819
matchFunc func(pat, str string) (bool, error)
1920

2021
mu sync.Mutex
@@ -47,17 +48,33 @@ type alternationMatch []filterMatch
4748
// eliminate this Mutex.
4849
var matchMutex sync.Mutex
4950

50-
func newMatcher(matchString func(pat, str string) (bool, error), patterns, name string) *matcher {
51-
var impl filterMatch
52-
if patterns != "" {
53-
impl = splitRegexp(patterns)
54-
if err := impl.verify(name, matchString); err != nil {
51+
func allMatcher() *matcher {
52+
return newMatcher(nil, "", "", "")
53+
}
54+
55+
func newMatcher(matchString func(pat, str string) (bool, error), patterns, name, skips string) *matcher {
56+
var filter, skip filterMatch
57+
if patterns == "" {
58+
filter = simpleMatch{} // always partial true
59+
} else {
60+
filter = splitRegexp(patterns)
61+
if err := filter.verify(name, matchString); err != nil {
5562
fmt.Fprintf(os.Stderr, "testing: invalid regexp for %s\n", err)
5663
os.Exit(1)
5764
}
5865
}
66+
if skips == "" {
67+
skip = alternationMatch{} // always false
68+
} else {
69+
skip = splitRegexp(skips)
70+
if err := skip.verify("-test.skip", matchString); err != nil {
71+
fmt.Fprintf(os.Stderr, "testing: invalid regexp for %v\n", err)
72+
os.Exit(1)
73+
}
74+
}
5975
return &matcher{
60-
filter: impl,
76+
filter: filter,
77+
skip: skip,
6178
matchFunc: matchString,
6279
subNames: map[string]int32{},
6380
}
@@ -76,14 +93,23 @@ func (m *matcher) fullName(c *common, subname string) (name string, ok, partial
7693
matchMutex.Lock()
7794
defer matchMutex.Unlock()
7895

79-
if m.filter == nil {
80-
return name, true, false
81-
}
82-
83-
// We check the full array of paths each time to allow for the case that
84-
// a pattern contains a '/'.
96+
// We check the full array of paths each time to allow for the case that a pattern contains a '/'.
8597
elem := strings.Split(name, "/")
98+
99+
// filter must match.
100+
// accept partial match that may produce full match later.
86101
ok, partial = m.filter.matches(elem, m.matchFunc)
102+
if !ok {
103+
return name, false, false
104+
}
105+
106+
// skip must not match.
107+
// ignore partial match so we can get to more precise match later.
108+
skip, partialSkip := m.skip.matches(elem, m.matchFunc)
109+
if skip && !partialSkip {
110+
return name, false, false
111+
}
112+
87113
return name, ok, partial
88114
}
89115

src/testing/match_test.go

Lines changed: 60 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import (
1212
"unicode"
1313
)
1414

15+
func init() {
16+
testingTesting = true
17+
}
18+
1519
// Verify that our IsSpace agrees with unicode.IsSpace.
1620
func TestIsSpace(t *T) {
1721
n := 0
@@ -89,54 +93,75 @@ func TestSplitRegexp(t *T) {
8993
func TestMatcher(t *T) {
9094
testCases := []struct {
9195
pattern string
96+
skip string
9297
parent, sub string
9398
ok bool
9499
partial bool
95100
}{
96101
// Behavior without subtests.
97-
{"", "", "TestFoo", true, false},
98-
{"TestFoo", "", "TestFoo", true, false},
99-
{"TestFoo/", "", "TestFoo", true, true},
100-
{"TestFoo/bar/baz", "", "TestFoo", true, true},
101-
{"TestFoo", "", "TestBar", false, false},
102-
{"TestFoo/", "", "TestBar", false, false},
103-
{"TestFoo/bar/baz", "", "TestBar/bar/baz", false, false},
102+
{"", "", "", "TestFoo", true, false},
103+
{"TestFoo", "", "", "TestFoo", true, false},
104+
{"TestFoo/", "", "", "TestFoo", true, true},
105+
{"TestFoo/bar/baz", "", "", "TestFoo", true, true},
106+
{"TestFoo", "", "", "TestBar", false, false},
107+
{"TestFoo/", "", "", "TestBar", false, false},
108+
{"TestFoo/bar/baz", "", "", "TestBar/bar/baz", false, false},
109+
{"", "TestBar", "", "TestFoo", true, false},
110+
{"", "TestBar", "", "TestBar", false, false},
111+
112+
// Skipping a non-existent test doesn't change anything.
113+
{"", "TestFoo/skipped", "", "TestFoo", true, false},
114+
{"TestFoo", "TestFoo/skipped", "", "TestFoo", true, false},
115+
{"TestFoo/", "TestFoo/skipped", "", "TestFoo", true, true},
116+
{"TestFoo/bar/baz", "TestFoo/skipped", "", "TestFoo", true, true},
117+
{"TestFoo", "TestFoo/skipped", "", "TestBar", false, false},
118+
{"TestFoo/", "TestFoo/skipped", "", "TestBar", false, false},
119+
{"TestFoo/bar/baz", "TestFoo/skipped", "", "TestBar/bar/baz", false, false},
104120

105121
// with subtests
106-
{"", "TestFoo", "x", true, false},
107-
{"TestFoo", "TestFoo", "x", true, false},
108-
{"TestFoo/", "TestFoo", "x", true, false},
109-
{"TestFoo/bar/baz", "TestFoo", "bar", true, true},
122+
{"", "", "TestFoo", "x", true, false},
123+
{"TestFoo", "", "TestFoo", "x", true, false},
124+
{"TestFoo/", "", "TestFoo", "x", true, false},
125+
{"TestFoo/bar/baz", "", "TestFoo", "bar", true, true},
126+
127+
{"", "TestFoo/skipped", "TestFoo", "x", true, false},
128+
{"TestFoo", "TestFoo/skipped", "TestFoo", "x", true, false},
129+
{"TestFoo", "TestFoo/skipped", "TestFoo", "skipped", false, false},
130+
{"TestFoo/", "TestFoo/skipped", "TestFoo", "x", true, false},
131+
{"TestFoo/bar/baz", "TestFoo/skipped", "TestFoo", "bar", true, true},
132+
110133
// Subtest with a '/' in its name still allows for copy and pasted names
111134
// to match.
112-
{"TestFoo/bar/baz", "TestFoo", "bar/baz", true, false},
113-
{"TestFoo/bar/baz", "TestFoo/bar", "baz", true, false},
114-
{"TestFoo/bar/baz", "TestFoo", "x", false, false},
115-
{"TestFoo", "TestBar", "x", false, false},
116-
{"TestFoo/", "TestBar", "x", false, false},
117-
{"TestFoo/bar/baz", "TestBar", "x/bar/baz", false, false},
135+
{"TestFoo/bar/baz", "", "TestFoo", "bar/baz", true, false},
136+
{"TestFoo/bar/baz", "TestFoo/bar/baz", "TestFoo", "bar/baz", false, false},
137+
{"TestFoo/bar/baz", "TestFoo/bar/baz/skip", "TestFoo", "bar/baz", true, false},
138+
{"TestFoo/bar/baz", "", "TestFoo/bar", "baz", true, false},
139+
{"TestFoo/bar/baz", "", "TestFoo", "x", false, false},
140+
{"TestFoo", "", "TestBar", "x", false, false},
141+
{"TestFoo/", "", "TestBar", "x", false, false},
142+
{"TestFoo/bar/baz", "", "TestBar", "x/bar/baz", false, false},
118143

119-
{"A/B|C/D", "TestA", "B", true, false},
120-
{"A/B|C/D", "TestC", "D", true, false},
121-
{"A/B|C/D", "TestA", "C", false, false},
144+
{"A/B|C/D", "", "TestA", "B", true, false},
145+
{"A/B|C/D", "", "TestC", "D", true, false},
146+
{"A/B|C/D", "", "TestA", "C", false, false},
122147

123148
// subtests only
124-
{"", "TestFoo", "x", true, false},
125-
{"/", "TestFoo", "x", true, false},
126-
{"./", "TestFoo", "x", true, false},
127-
{"./.", "TestFoo", "x", true, false},
128-
{"/bar/baz", "TestFoo", "bar", true, true},
129-
{"/bar/baz", "TestFoo", "bar/baz", true, false},
130-
{"//baz", "TestFoo", "bar/baz", true, false},
131-
{"//", "TestFoo", "bar/baz", true, false},
132-
{"/bar/baz", "TestFoo/bar", "baz", true, false},
133-
{"//foo", "TestFoo", "bar/baz", false, false},
134-
{"/bar/baz", "TestFoo", "x", false, false},
135-
{"/bar/baz", "TestBar", "x/bar/baz", false, false},
149+
{"", "", "TestFoo", "x", true, false},
150+
{"/", "", "TestFoo", "x", true, false},
151+
{"./", "", "TestFoo", "x", true, false},
152+
{"./.", "", "TestFoo", "x", true, false},
153+
{"/bar/baz", "", "TestFoo", "bar", true, true},
154+
{"/bar/baz", "", "TestFoo", "bar/baz", true, false},
155+
{"//baz", "", "TestFoo", "bar/baz", true, false},
156+
{"//", "", "TestFoo", "bar/baz", true, false},
157+
{"/bar/baz", "", "TestFoo/bar", "baz", true, false},
158+
{"//foo", "", "TestFoo", "bar/baz", false, false},
159+
{"/bar/baz", "", "TestFoo", "x", false, false},
160+
{"/bar/baz", "", "TestBar", "x/bar/baz", false, false},
136161
}
137162

138163
for _, tc := range testCases {
139-
m := newMatcher(regexp.MatchString, tc.pattern, "-test.run")
164+
m := newMatcher(regexp.MatchString, tc.pattern, "-test.run", tc.skip)
140165

141166
parent := &common{name: tc.parent}
142167
if tc.parent != "" {
@@ -184,7 +209,7 @@ var namingTestCases = []struct{ name, want string }{
184209
}
185210

186211
func TestNaming(t *T) {
187-
m := newMatcher(regexp.MatchString, "", "")
212+
m := newMatcher(regexp.MatchString, "", "", "")
188213
parent := &common{name: "x", level: 1} // top-level test.
189214

190215
for i, tc := range namingTestCases {
@@ -202,7 +227,7 @@ func FuzzNaming(f *F) {
202227
var m *matcher
203228
var seen map[string]string
204229
reset := func() {
205-
m = newMatcher(regexp.MatchString, "", "")
230+
m = allMatcher()
206231
seen = make(map[string]string)
207232
}
208233
reset()

src/testing/sub_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ func TestTRun(t *T) {
476476
}}
477477
for _, tc := range testCases {
478478
t.Run(tc.desc, func(t *T) {
479-
ctx := newTestContext(tc.maxPar, newMatcher(regexp.MatchString, "", ""))
479+
ctx := newTestContext(tc.maxPar, allMatcher())
480480
buf := &strings.Builder{}
481481
root := &T{
482482
common: common{
@@ -775,7 +775,7 @@ func TestRacyOutput(t *T) {
775775
var wg sync.WaitGroup
776776
root := &T{
777777
common: common{w: &funcWriter{raceDetector}},
778-
context: newTestContext(1, newMatcher(regexp.MatchString, "", "")),
778+
context: newTestContext(1, allMatcher()),
779779
}
780780
root.chatty = newChattyPrinter(root.w)
781781
root.Run("", func(t *T) {
@@ -798,7 +798,7 @@ func TestRacyOutput(t *T) {
798798

799799
// The late log message did not include the test name. Issue 29388.
800800
func TestLogAfterComplete(t *T) {
801-
ctx := newTestContext(1, newMatcher(regexp.MatchString, "", ""))
801+
ctx := newTestContext(1, allMatcher())
802802
var buf bytes.Buffer
803803
t1 := &T{
804804
common: common{

0 commit comments

Comments
 (0)