// Copyright 2015 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 testing import ( "fmt" "os" "strconv" "strings" "sync" ) // matcher sanitizes, uniques, and filters names of subtests and subbenchmarks. type matcher struct { filter filterMatch skip filterMatch matchFunc func(pat, str string) (bool, error) mu sync.Mutex // subNames is used to deduplicate subtest names. // Each key is the subtest name joined to the deduplicated name of the parent test. // Each value is the count of the number of occurrences of the given subtest name // already seen. subNames map[string]int32 } type filterMatch interface { // matches checks the name against the receiver's pattern strings using the // given match function. matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) // verify checks that the receiver's pattern strings are valid filters by // calling the given match function. verify(name string, matchString func(pat, str string) (bool, error)) error } // simpleMatch matches a test name if all of the pattern strings match in // sequence. type simpleMatch []string // alternationMatch matches a test name if one of the alternations match. type alternationMatch []filterMatch // TODO: fix test_main to avoid race and improve caching, also allowing to // eliminate this Mutex. var matchMutex sync.Mutex func allMatcher() *matcher { return newMatcher(nil, "", "", "") } func newMatcher(matchString func(pat, str string) (bool, error), patterns, name, skips string) *matcher { var filter, skip filterMatch if patterns == "" { filter = simpleMatch{} // always partial true } else { filter = splitRegexp(patterns) if err := filter.verify(name, matchString); err != nil { fmt.Fprintf(os.Stderr, "testing: invalid regexp for %s\n", err) os.Exit(1) } } if skips == "" { skip = alternationMatch{} // always false } else { skip = splitRegexp(skips) if err := skip.verify("-test.skip", matchString); err != nil { fmt.Fprintf(os.Stderr, "testing: invalid regexp for %v\n", err) os.Exit(1) } } return &matcher{ filter: filter, skip: skip, matchFunc: matchString, subNames: map[string]int32{}, } } func (m *matcher) fullName(c *common, subname string) (name string, ok, partial bool) { name = subname m.mu.Lock() defer m.mu.Unlock() if c != nil && c.level > 0 { name = m.unique(c.name, rewrite(subname)) } matchMutex.Lock() defer matchMutex.Unlock() // We check the full array of paths each time to allow for the case that a pattern contains a '/'. elem := strings.Split(name, "/") // filter must match. // accept partial match that may produce full match later. ok, partial = m.filter.matches(elem, m.matchFunc) if !ok { return name, false, false } // skip must not match. // ignore partial match so we can get to more precise match later. skip, partialSkip := m.skip.matches(elem, m.matchFunc) if skip && !partialSkip { return name, false, false } return name, ok, partial } // clearSubNames clears the matcher's internal state, potentially freeing // memory. After this is called, T.Name may return the same strings as it did // for earlier subtests. func (m *matcher) clearSubNames() { m.mu.Lock() defer m.mu.Unlock() clear(m.subNames) } func (m simpleMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) { for i, s := range name { if i >= len(m) { break } if ok, _ := matchString(m[i], s); !ok { return false, false } } return true, len(name) < len(m) } func (m simpleMatch) verify(name string, matchString func(pat, str string) (bool, error)) error { for i, s := range m { m[i] = rewrite(s) } // Verify filters before doing any processing. for i, s := range m { if _, err := matchString(s, "non-empty"); err != nil { return fmt.Errorf("element %d of %s (%q): %s", i, name, s, err) } } return nil } func (m alternationMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) { for _, m := range m { if ok, partial = m.matches(name, matchString); ok { return ok, partial } } return false, false } func (m alternationMatch) verify(name string, matchString func(pat, str string) (bool, error)) error { for i, m := range m { if err := m.verify(name, matchString); err != nil { return fmt.Errorf("alternation %d of %s", i, err) } } return nil } func splitRegexp(s string) filterMatch { a := make(simpleMatch, 0, strings.Count(s, "/")) b := make(alternationMatch, 0, strings.Count(s, "|")) cs := 0 cp := 0 for i := 0; i < len(s); { switch s[i] { case '[': cs++ case ']': if cs--; cs < 0 { // An unmatched ']' is legal. cs = 0 } case '(': if cs == 0 { cp++ } case ')': if cs == 0 { cp-- } case '\\': i++ case '/': if cs == 0 && cp == 0 { a = append(a, s[:i]) s = s[i+1:] i = 0 continue } case '|': if cs == 0 && cp == 0 { a = append(a, s[:i]) s = s[i+1:] i = 0 b = append(b, a) a = make(simpleMatch, 0, len(a)) continue } } i++ } a = append(a, s) if len(b) == 0 { return a } return append(b, a) } // unique creates a unique name for the given parent and subname by affixing it // with one or more counts, if necessary. func (m *matcher) unique(parent, subname string) string { base := parent + "/" + subname for { n := m.subNames[base] if n < 0 { panic("subtest count overflow") } m.subNames[base] = n + 1 if n == 0 && subname != "" { prefix, nn := parseSubtestNumber(base) if len(prefix) < len(base) && nn < m.subNames[prefix] { // This test is explicitly named like "parent/subname#NN", // and #NN was already used for the NNth occurrence of "parent/subname". // Loop to add a disambiguating suffix. continue } return base } name := fmt.Sprintf("%s#%02d", base, n) if m.subNames[name] != 0 { // This is the nth occurrence of base, but the name "parent/subname#NN" // collides with the first occurrence of a subtest *explicitly* named // "parent/subname#NN". Try the next number. continue } return name } } // parseSubtestNumber splits a subtest name into a "#%02d"-formatted int32 // suffix (if present), and a prefix preceding that suffix (always). func parseSubtestNumber(s string) (prefix string, nn int32) { i := strings.LastIndex(s, "#") if i < 0 { return s, 0 } prefix, suffix := s[:i], s[i+1:] if len(suffix) < 2 || (len(suffix) > 2 && suffix[0] == '0') { // Even if suffix is numeric, it is not a possible output of a "%02" format // string: it has either too few digits or too many leading zeroes. return s, 0 } if suffix == "00" { if !strings.HasSuffix(prefix, "/") { // We only use "#00" as a suffix for subtests named with the empty // string — it isn't a valid suffix if the subtest name is non-empty. return s, 0 } } n, err := strconv.ParseInt(suffix, 10, 32) if err != nil || n < 0 { return s, 0 } return prefix, int32(n) } // rewrite rewrites a subname to having only printable characters and no white // space. func rewrite(s string) string { b := []byte{} for _, r := range s { switch { case isSpace(r): b = append(b, '_') case !strconv.IsPrint(r): s := strconv.QuoteRune(r) b = append(b, s[1:len(s)-1]...) default: b = append(b, string(r)...) } } return string(b) } func isSpace(r rune) bool { if r < 0x2000 { switch r { // Note: not the same as Unicode Z class. case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680: return true } } else { if r <= 0x200a { return true } switch r { case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000: return true } } return false }