Skip to content

Commit 00a2a94

Browse files
committed
testing: added name matcher and sanitizer
The matcher is responsible for sanitizing and uniquing the test and benchmark names and thus needs to be included before the API can be exposed. Matching currently uses the regexp to only match the top-level tests/benchmarks. Support for subtest matching is for another CL. Change-Id: I7c8464068faef7ebc179b03a7fe3d01122cc4f0b Reviewed-on: https://go-review.googlesource.com/18897 Reviewed-by: Russ Cox <rsc@golang.org> Run-TryBot: Marcel van Lohuizen <mpvl@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
1 parent 34699bc commit 00a2a94

File tree

5 files changed

+207
-26
lines changed

5 files changed

+207
-26
lines changed

src/testing/benchmark.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ func benchmarkName(name string, n int) string {
338338
}
339339

340340
type benchContext struct {
341+
match *matcher
342+
341343
maxLen int // The largest recorded benchmark name.
342344
extLen int // Maximum extension length.
343345
}
@@ -361,16 +363,12 @@ func runBenchmarksInternal(matchString func(pat, str string) (bool, error), benc
361363
}
362364
}
363365
ctx := &benchContext{
366+
match: newMatcher(matchString, *matchBenchmarks, "-test.bench"),
364367
extLen: len(benchmarkName("", maxprocs)),
365368
}
366369
var bs []InternalBenchmark
367370
for _, Benchmark := range benchmarks {
368-
matched, err := matchString(*matchBenchmarks, Benchmark.Name)
369-
if err != nil {
370-
fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.bench: %s\n", err)
371-
os.Exit(1)
372-
}
373-
if matched {
371+
if _, matched := ctx.match.fullName(nil, Benchmark.Name); matched {
374372
bs = append(bs, Benchmark)
375373
benchName := benchmarkName(Benchmark.Name, maxprocs)
376374
if l := len(benchName) + ctx.extLen + 1; l > ctx.maxLen {
@@ -443,13 +441,17 @@ func (b *B) runBench(name string, f func(b *B)) bool {
443441
benchmarkLock.Unlock()
444442
defer benchmarkLock.Lock()
445443

446-
if b.level > 0 {
447-
name = b.name + "/" + name
444+
benchName, ok := b.name, true
445+
if b.context != nil {
446+
benchName, ok = b.context.match.fullName(&b.common, name)
447+
}
448+
if !ok {
449+
return true
448450
}
449451
sub := &B{
450452
common: common{
451453
signal: make(chan bool),
452-
name: name,
454+
name: benchName,
453455
parent: &b.common,
454456
level: b.level + 1,
455457
},

src/testing/match.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2015 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package testing
6+
7+
import (
8+
"fmt"
9+
"os"
10+
"strconv"
11+
"sync"
12+
)
13+
14+
// matcher sanitizes, uniques, and filters names of subtests and subbenchmarks.
15+
type matcher struct {
16+
filter string
17+
matchFunc func(pat, str string) (bool, error)
18+
19+
mu sync.Mutex
20+
subNames map[string]int64
21+
}
22+
23+
// TODO: fix test_main to avoid race and improve caching.
24+
var matchMutex sync.Mutex
25+
26+
func newMatcher(matchString func(pat, str string) (bool, error), pattern, name string) *matcher {
27+
// Verify filters before doing any processing.
28+
if _, err := matchString(pattern, "non-empty"); err != nil {
29+
fmt.Fprintf(os.Stderr, "testing: invalid regexp for %s: %s\n", name, err)
30+
os.Exit(1)
31+
}
32+
return &matcher{
33+
filter: pattern,
34+
matchFunc: matchString,
35+
subNames: map[string]int64{},
36+
}
37+
}
38+
39+
func (m *matcher) fullName(c *common, subname string) (name string, ok bool) {
40+
name = subname
41+
42+
m.mu.Lock()
43+
defer m.mu.Unlock()
44+
45+
if c != nil && c.level > 0 {
46+
name = m.unique(c.name, rewrite(subname))
47+
}
48+
49+
matchMutex.Lock()
50+
defer matchMutex.Unlock()
51+
52+
if c != nil && c.level == 0 {
53+
if matched, _ := m.matchFunc(m.filter, subname); !matched {
54+
return name, false
55+
}
56+
}
57+
return name, true
58+
}
59+
60+
// unique creates a unique name for the given parent and subname by affixing it
61+
// with one ore more counts, if necessary.
62+
func (m *matcher) unique(parent, subname string) string {
63+
name := fmt.Sprintf("%s/%s", parent, subname)
64+
empty := subname == ""
65+
for {
66+
next, exists := m.subNames[name]
67+
if !empty && !exists {
68+
m.subNames[name] = 1 // next count is 1
69+
return name
70+
}
71+
// Name was already used. We increment with the count and append a
72+
// string with the count.
73+
m.subNames[name] = next + 1
74+
75+
// Add a count to guarantee uniqueness.
76+
name = fmt.Sprintf("%s#%02d", name, next)
77+
empty = false
78+
}
79+
}
80+
81+
// rewrite rewrites a subname to having only printable characters and no white
82+
// space.
83+
func rewrite(s string) string {
84+
b := []byte{}
85+
for _, r := range s {
86+
switch {
87+
case isSpace(r):
88+
b = append(b, '_')
89+
case !strconv.IsPrint(r):
90+
s := strconv.QuoteRune(r)
91+
b = append(b, s[1:len(s)-1]...)
92+
default:
93+
b = append(b, string(r)...)
94+
}
95+
}
96+
return string(b)
97+
}
98+
99+
func isSpace(r rune) bool {
100+
if r < 0x2000 {
101+
switch r {
102+
// Note: not the same as Unicode Z class.
103+
case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680:
104+
return true
105+
}
106+
} else {
107+
if r <= 0x200a {
108+
return true
109+
}
110+
switch r {
111+
case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000:
112+
return true
113+
}
114+
}
115+
return false
116+
}

src/testing/match_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2015 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package testing
6+
7+
import (
8+
"regexp"
9+
"unicode"
10+
)
11+
12+
// Verify that our IsSpace agrees with unicode.IsSpace.
13+
func TestIsSpace(t *T) {
14+
n := 0
15+
for r := rune(0); r <= unicode.MaxRune; r++ {
16+
if isSpace(r) != unicode.IsSpace(r) {
17+
t.Errorf("IsSpace(%U)=%t incorrect", r, isSpace(r))
18+
n++
19+
if n > 10 {
20+
return
21+
}
22+
}
23+
}
24+
}
25+
26+
func TestNaming(t *T) {
27+
m := newMatcher(regexp.MatchString, "", "")
28+
29+
parent := &common{name: "x", level: 1} // top-level test.
30+
31+
// Rig the matcher with some preloaded values.
32+
m.subNames["x/b"] = 1000
33+
34+
testCases := []struct {
35+
name, want string
36+
}{
37+
// Uniqueness
38+
{"", "x/#00"},
39+
{"", "x/#01"},
40+
41+
{"t", "x/t"},
42+
{"t", "x/t#01"},
43+
{"t", "x/t#02"},
44+
45+
{"a#01", "x/a#01"}, // user has subtest with this name.
46+
{"a", "x/a"}, // doesn't conflict with this name.
47+
{"a", "x/a#01#01"}, // conflict, add disambiguating string.
48+
{"a", "x/a#02"}, // This string is claimed now, so resume
49+
{"a", "x/a#03"}, // with counting.
50+
{"a#02", "x/a#02#01"},
51+
52+
{"b", "x/b#1000"}, // rigged, see above
53+
{"b", "x/b#1001"},
54+
55+
// // Sanitizing
56+
{"A:1 B:2", "x/A:1_B:2"},
57+
{"s\t\r\u00a0", "x/s___"},
58+
{"\x01", `x/\x01`},
59+
{"\U0010ffff", `x/\U0010ffff`},
60+
}
61+
62+
for i, tc := range testCases {
63+
if got, _ := m.fullName(parent, tc.name); got != tc.want {
64+
t.Errorf("%d:%s: got %q; want %q", i, tc.name, got, tc.want)
65+
}
66+
}
67+
}

src/testing/sub_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package testing
66

77
import (
88
"io/ioutil"
9+
"regexp"
910
"sync/atomic"
1011
"time"
1112
)
@@ -305,11 +306,12 @@ func TestTRun(t *T) {
305306
},
306307
}}
307308
for _, tc := range testCases {
308-
ctx := newTestContext(tc.maxPar)
309+
ctx := newTestContext(tc.maxPar, newMatcher(regexp.MatchString, "", ""))
309310
root := &T{
310311
common: common{
311-
barrier: make(chan bool),
312-
w: ioutil.Discard,
312+
signal: make(chan bool),
313+
name: "Test",
314+
w: ioutil.Discard,
313315
},
314316
context: ctx,
315317
}

src/testing/testing.go

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -551,9 +551,9 @@ func tRunner(t *T, fn func(t *T)) {
551551
// run runs f as a subtest of t called name. It reports whether f succeeded.
552552
// Run will block until all its parallel subtests have completed.
553553
func (t *T) run(name string, f func(t *T)) bool {
554-
testName := name
555-
if t.level > 0 {
556-
testName = t.name + "/" + name
554+
testName, ok := t.context.match.fullName(&t.common, name)
555+
if !ok {
556+
return true
557557
}
558558
t = &T{
559559
common: common{
@@ -583,6 +583,8 @@ func (t *T) run(name string, f func(t *T)) bool {
583583
// testContext holds all fields that are common to all tests. This includes
584584
// synchronization primitives to run at most *parallel tests.
585585
type testContext struct {
586+
match *matcher
587+
586588
mu sync.Mutex
587589

588590
// Channel used to signal tests that are ready to be run in parallel.
@@ -599,8 +601,9 @@ type testContext struct {
599601
maxParallel int
600602
}
601603

602-
func newTestContext(maxParallel int) *testContext {
604+
func newTestContext(maxParallel int, m *matcher) *testContext {
603605
return &testContext{
606+
match: m,
604607
startParallel: make(chan bool),
605608
maxParallel: maxParallel,
606609
running: 1, // Set the count to 1 for the main (sequential) test.
@@ -707,7 +710,7 @@ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalT
707710
}
708711
for _, procs := range cpuList {
709712
runtime.GOMAXPROCS(procs)
710-
ctx := newTestContext(*parallel)
713+
ctx := newTestContext(*parallel, newMatcher(matchString, *match, "-test.run"))
711714
t := &T{
712715
common: common{
713716
signal: make(chan bool),
@@ -718,15 +721,6 @@ func RunTests(matchString func(pat, str string) (bool, error), tests []InternalT
718721
}
719722
tRunner(t, func(t *T) {
720723
for _, test := range tests {
721-
// TODO: a version of this will be the Run method.
722-
matched, err := matchString(*match, test.Name)
723-
if err != nil {
724-
fmt.Fprintf(os.Stderr, "testing: invalid regexp for -test.run: %s\n", err)
725-
os.Exit(1)
726-
}
727-
if !matched {
728-
continue
729-
}
730724
t.run(test.Name, test.F)
731725
}
732726
// Run catching the signal rather than the tRunner as a separate

0 commit comments

Comments
 (0)