| | |
| | |
| | |
| |
|
| | package testing |
| |
|
| | import ( |
| | "fmt" |
| | "os" |
| | "strconv" |
| | "strings" |
| | "sync" |
| | ) |
| |
|
| | |
| | type matcher struct { |
| | filter filterMatch |
| | skip filterMatch |
| | matchFunc func(pat, str string) (bool, error) |
| |
|
| | mu sync.Mutex |
| |
|
| | |
| | |
| | |
| | |
| | subNames map[string]int32 |
| | } |
| |
|
| | type filterMatch interface { |
| | |
| | |
| | matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) |
| |
|
| | |
| | |
| | verify(name string, matchString func(pat, str string) (bool, error)) error |
| | } |
| |
|
| | |
| | |
| | type simpleMatch []string |
| |
|
| | |
| | type alternationMatch []filterMatch |
| |
|
| | |
| | |
| | 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{} |
| | } 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{} |
| | } 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() |
| |
|
| | |
| | elem := strings.Split(name, "/") |
| |
|
| | |
| | |
| | ok, partial = m.filter.matches(elem, m.matchFunc) |
| | if !ok { |
| | return name, false, false |
| | } |
| |
|
| | |
| | |
| | skip, partialSkip := m.skip.matches(elem, m.matchFunc) |
| | if skip && !partialSkip { |
| | return name, false, false |
| | } |
| |
|
| | return name, ok, partial |
| | } |
| |
|
| | |
| | |
| | |
| | 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) |
| | } |
| | |
| | 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 { |
| | 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) |
| | } |
| |
|
| | |
| | |
| | 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] { |
| | |
| | |
| | |
| | continue |
| | } |
| | return base |
| | } |
| |
|
| | name := fmt.Sprintf("%s#%02d", base, n) |
| | if m.subNames[name] != 0 { |
| | |
| | |
| | |
| | continue |
| | } |
| |
|
| | return name |
| | } |
| | } |
| |
|
| | |
| | |
| | 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') { |
| | |
| | |
| | return s, 0 |
| | } |
| | if suffix == "00" { |
| | if !strings.HasSuffix(prefix, "/") { |
| | |
| | |
| | return s, 0 |
| | } |
| | } |
| |
|
| | n, err := strconv.ParseInt(suffix, 10, 32) |
| | if err != nil || n < 0 { |
| | return s, 0 |
| | } |
| | return prefix, int32(n) |
| | } |
| |
|
| | |
| | |
| | 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 { |
| | |
| | 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 |
| | } |
| |
|