blob: d0c52142ba70176ad6125f4cbc773f83d81b5c7e [file] [log] [blame]
// 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"
"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.
var matchMutex sync.Mutex
func newMatcher(matchString func(pat, str string) (bool, error), pattern, name string) *matcher {
// Verify filters before doing any processing.
if _, err := matchString(pattern, "non-empty"); err != nil {
fmt.Fprintf(os.Stderr, "testing: invalid regexp for %s: %s\n", name, err)
os.Exit(1)
}
return &matcher{
filter: pattern,
matchFunc: matchString,
subNames: map[string]int64{},
}
}
func (m *matcher) fullName(c *common, subname string) (name string, ok 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()
if c != nil && c.level == 0 {
if matched, _ := m.matchFunc(m.filter, subname); !matched {
return name, false
}
}
return name, true
}
// unique creates a unique name for the given parent and subname by affixing it
// with one ore 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
}