diff options
Diffstat (limited to 'src/testing/match.go')
-rw-r--r-- | src/testing/match.go | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/src/testing/match.go b/src/testing/match.go new file mode 100644 index 0000000..b18c6e7 --- /dev/null +++ b/src/testing/match.go @@ -0,0 +1,168 @@ +// 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 []string + matchFunc func(pat, str string) (bool, error) + + mu sync.Mutex + subNames map[string]int64 +} + +// TODO: fix test_main to avoid race and improve caching, also allowing to +// eliminate this Mutex. +var matchMutex sync.Mutex + +func newMatcher(matchString func(pat, str string) (bool, error), patterns, name string) *matcher { + var filter []string + if patterns != "" { + filter = splitRegexp(patterns) + for i, s := range filter { + filter[i] = rewrite(s) + } + // Verify filters before doing any processing. + for i, s := range filter { + if _, err := matchString(s, "non-empty"); err != nil { + fmt.Fprintf(os.Stderr, "testing: invalid regexp for element %d of %s (%q): %s\n", i, name, s, err) + os.Exit(1) + } + } + } + return &matcher{ + filter: filter, + matchFunc: matchString, + subNames: map[string]int64{}, + } +} + +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, "/") + for i, s := range elem { + if i >= len(m.filter) { + break + } + if ok, _ := m.matchFunc(m.filter[i], s); !ok { + return name, false, false + } + } + return name, true, len(elem) < len(m.filter) +} + +func splitRegexp(s string) []string { + a := make([]string, 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 + } + } + i++ + } + return append(a, s) +} + +// 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 { + name := fmt.Sprintf("%s/%s", parent, subname) + empty := subname == "" + for { + next, exists := m.subNames[name] + if !empty && !exists { + m.subNames[name] = 1 // next count is 1 + return name + } + // Name was already used. We increment with the count and append a + // string with the count. + m.subNames[name] = next + 1 + + // Add a count to guarantee uniqueness. + name = fmt.Sprintf("%s#%02d", name, next) + empty = false + } +} + +// 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 +} |