summaryrefslogtreecommitdiffstats
path: root/src/testing/match.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:23:18 +0000
commit43a123c1ae6613b3efeed291fa552ecd909d3acf (patch)
treefd92518b7024bc74031f78a1cf9e454b65e73665 /src/testing/match.go
parentInitial commit. (diff)
downloadgolang-1.20-upstream.tar.xz
golang-1.20-upstream.zip
Adding upstream version 1.20.14.upstream/1.20.14upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/testing/match.go')
-rw-r--r--src/testing/match.go319
1 files changed, 319 insertions, 0 deletions
diff --git a/src/testing/match.go b/src/testing/match.go
new file mode 100644
index 0000000..92b7dc6
--- /dev/null
+++ b/src/testing/match.go
@@ -0,0 +1,319 @@
+// 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()
+ for key := range m.subNames {
+ delete(m.subNames, key)
+ }
+}
+
+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
+}