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
5package testing
6
7import (
8	"fmt"
9	"os"
10	"strconv"
11	"strings"
12	"sync"
13)
14
15// matcher sanitizes, uniques, and filters names of subtests and subbenchmarks.
16type matcher struct {
17	filter    filterMatch
18	skip      filterMatch
19	matchFunc func(pat, str string) (bool, error)
20
21	mu sync.Mutex
22
23	// subNames is used to deduplicate subtest names.
24	// Each key is the subtest name joined to the deduplicated name of the parent test.
25	// Each value is the count of the number of occurrences of the given subtest name
26	// already seen.
27	subNames map[string]int32
28}
29
30type filterMatch interface {
31	// matches checks the name against the receiver's pattern strings using the
32	// given match function.
33	matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool)
34
35	// verify checks that the receiver's pattern strings are valid filters by
36	// calling the given match function.
37	verify(name string, matchString func(pat, str string) (bool, error)) error
38}
39
40// simpleMatch matches a test name if all of the pattern strings match in
41// sequence.
42type simpleMatch []string
43
44// alternationMatch matches a test name if one of the alternations match.
45type alternationMatch []filterMatch
46
47// TODO: fix test_main to avoid race and improve caching, also allowing to
48// eliminate this Mutex.
49var matchMutex sync.Mutex
50
51func allMatcher() *matcher {
52	return newMatcher(nil, "", "", "")
53}
54
55func 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 {
62			fmt.Fprintf(os.Stderr, "testing: invalid regexp for %s\n", err)
63			os.Exit(1)
64		}
65	}
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	}
75	return &matcher{
76		filter:    filter,
77		skip:      skip,
78		matchFunc: matchString,
79		subNames:  map[string]int32{},
80	}
81}
82
83func (m *matcher) fullName(c *common, subname string) (name string, ok, partial bool) {
84	name = subname
85
86	m.mu.Lock()
87	defer m.mu.Unlock()
88
89	if c != nil && c.level > 0 {
90		name = m.unique(c.name, rewrite(subname))
91	}
92
93	matchMutex.Lock()
94	defer matchMutex.Unlock()
95
96	// We check the full array of paths each time to allow for the case that a pattern contains a '/'.
97	elem := strings.Split(name, "/")
98
99	// filter must match.
100	// accept partial match that may produce full match later.
101	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
113	return name, ok, partial
114}
115
116// clearSubNames clears the matcher's internal state, potentially freeing
117// memory. After this is called, T.Name may return the same strings as it did
118// for earlier subtests.
119func (m *matcher) clearSubNames() {
120	m.mu.Lock()
121	defer m.mu.Unlock()
122	clear(m.subNames)
123}
124
125func (m simpleMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) {
126	for i, s := range name {
127		if i >= len(m) {
128			break
129		}
130		if ok, _ := matchString(m[i], s); !ok {
131			return false, false
132		}
133	}
134	return true, len(name) < len(m)
135}
136
137func (m simpleMatch) verify(name string, matchString func(pat, str string) (bool, error)) error {
138	for i, s := range m {
139		m[i] = rewrite(s)
140	}
141	// Verify filters before doing any processing.
142	for i, s := range m {
143		if _, err := matchString(s, "non-empty"); err != nil {
144			return fmt.Errorf("element %d of %s (%q): %s", i, name, s, err)
145		}
146	}
147	return nil
148}
149
150func (m alternationMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) {
151	for _, m := range m {
152		if ok, partial = m.matches(name, matchString); ok {
153			return ok, partial
154		}
155	}
156	return false, false
157}
158
159func (m alternationMatch) verify(name string, matchString func(pat, str string) (bool, error)) error {
160	for i, m := range m {
161		if err := m.verify(name, matchString); err != nil {
162			return fmt.Errorf("alternation %d of %s", i, err)
163		}
164	}
165	return nil
166}
167
168func splitRegexp(s string) filterMatch {
169	a := make(simpleMatch, 0, strings.Count(s, "/"))
170	b := make(alternationMatch, 0, strings.Count(s, "|"))
171	cs := 0
172	cp := 0
173	for i := 0; i < len(s); {
174		switch s[i] {
175		case '[':
176			cs++
177		case ']':
178			if cs--; cs < 0 { // An unmatched ']' is legal.
179				cs = 0
180			}
181		case '(':
182			if cs == 0 {
183				cp++
184			}
185		case ')':
186			if cs == 0 {
187				cp--
188			}
189		case '\\':
190			i++
191		case '/':
192			if cs == 0 && cp == 0 {
193				a = append(a, s[:i])
194				s = s[i+1:]
195				i = 0
196				continue
197			}
198		case '|':
199			if cs == 0 && cp == 0 {
200				a = append(a, s[:i])
201				s = s[i+1:]
202				i = 0
203				b = append(b, a)
204				a = make(simpleMatch, 0, len(a))
205				continue
206			}
207		}
208		i++
209	}
210
211	a = append(a, s)
212	if len(b) == 0 {
213		return a
214	}
215	return append(b, a)
216}
217
218// unique creates a unique name for the given parent and subname by affixing it
219// with one or more counts, if necessary.
220func (m *matcher) unique(parent, subname string) string {
221	base := parent + "/" + subname
222
223	for {
224		n := m.subNames[base]
225		if n < 0 {
226			panic("subtest count overflow")
227		}
228		m.subNames[base] = n + 1
229
230		if n == 0 && subname != "" {
231			prefix, nn := parseSubtestNumber(base)
232			if len(prefix) < len(base) && nn < m.subNames[prefix] {
233				// This test is explicitly named like "parent/subname#NN",
234				// and #NN was already used for the NNth occurrence of "parent/subname".
235				// Loop to add a disambiguating suffix.
236				continue
237			}
238			return base
239		}
240
241		name := fmt.Sprintf("%s#%02d", base, n)
242		if m.subNames[name] != 0 {
243			// This is the nth occurrence of base, but the name "parent/subname#NN"
244			// collides with the first occurrence of a subtest *explicitly* named
245			// "parent/subname#NN". Try the next number.
246			continue
247		}
248
249		return name
250	}
251}
252
253// parseSubtestNumber splits a subtest name into a "#%02d"-formatted int32
254// suffix (if present), and a prefix preceding that suffix (always).
255func parseSubtestNumber(s string) (prefix string, nn int32) {
256	i := strings.LastIndex(s, "#")
257	if i < 0 {
258		return s, 0
259	}
260
261	prefix, suffix := s[:i], s[i+1:]
262	if len(suffix) < 2 || (len(suffix) > 2 && suffix[0] == '0') {
263		// Even if suffix is numeric, it is not a possible output of a "%02" format
264		// string: it has either too few digits or too many leading zeroes.
265		return s, 0
266	}
267	if suffix == "00" {
268		if !strings.HasSuffix(prefix, "/") {
269			// We only use "#00" as a suffix for subtests named with the empty
270			// string — it isn't a valid suffix if the subtest name is non-empty.
271			return s, 0
272		}
273	}
274
275	n, err := strconv.ParseInt(suffix, 10, 32)
276	if err != nil || n < 0 {
277		return s, 0
278	}
279	return prefix, int32(n)
280}
281
282// rewrite rewrites a subname to having only printable characters and no white
283// space.
284func rewrite(s string) string {
285	b := []byte{}
286	for _, r := range s {
287		switch {
288		case isSpace(r):
289			b = append(b, '_')
290		case !strconv.IsPrint(r):
291			s := strconv.QuoteRune(r)
292			b = append(b, s[1:len(s)-1]...)
293		default:
294			b = append(b, string(r)...)
295		}
296	}
297	return string(b)
298}
299
300func isSpace(r rune) bool {
301	if r < 0x2000 {
302		switch r {
303		// Note: not the same as Unicode Z class.
304		case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680:
305			return true
306		}
307	} else {
308		if r <= 0x200a {
309			return true
310		}
311		switch r {
312		case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000:
313			return true
314		}
315	}
316	return false
317}
318