github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/testing/match.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package testing
     6  
     7  import (
     8  	"fmt"
     9  	"os"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  )
    14  
    15  // matcher sanitizes, uniques, and filters names of subtests and subbenchmarks.
    16  type matcher struct {
    17  	filter    filterMatch
    18  	skip      filterMatch
    19  	matchFunc func(pat, str string) (bool, error)
    20  
    21  	mu sync.Mutex
    22  
    23  	// subNames is used to deduplicate subtest names.
    24  	// Each key is the subtest name joined to the deduplicated name of the parent test.
    25  	// Each value is the count of the number of occurrences of the given subtest name
    26  	// already seen.
    27  	subNames map[string]int32
    28  }
    29  
    30  type filterMatch interface {
    31  	// matches checks the name against the receiver's pattern strings using the
    32  	// given match function.
    33  	matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool)
    34  
    35  	// verify checks that the receiver's pattern strings are valid filters by
    36  	// calling the given match function.
    37  	verify(name string, matchString func(pat, str string) (bool, error)) error
    38  }
    39  
    40  // simpleMatch matches a test name if all of the pattern strings match in
    41  // sequence.
    42  type simpleMatch []string
    43  
    44  // alternationMatch matches a test name if one of the alternations match.
    45  type alternationMatch []filterMatch
    46  
    47  // TODO: fix test_main to avoid race and improve caching, also allowing to
    48  // eliminate this Mutex.
    49  var matchMutex sync.Mutex
    50  
    51  func allMatcher() *matcher {
    52  	return newMatcher(nil, "", "", "")
    53  }
    54  
    55  func newMatcher(matchString func(pat, str string) (bool, error), patterns, name, skips string) *matcher {
    56  	var filter, skip filterMatch
    57  	if patterns == "" {
    58  		filter = simpleMatch{} // always partial true
    59  	} else {
    60  		filter = splitRegexp(patterns)
    61  		if err := filter.verify(name, matchString); err != nil {
    62  			fmt.Fprintf(os.Stderr, "testing: invalid regexp for %s\n", err)
    63  			os.Exit(1)
    64  		}
    65  	}
    66  	if skips == "" {
    67  		skip = alternationMatch{} // always false
    68  	} else {
    69  		skip = splitRegexp(skips)
    70  		if err := skip.verify("-test.skip", matchString); err != nil {
    71  			fmt.Fprintf(os.Stderr, "testing: invalid regexp for %v\n", err)
    72  			os.Exit(1)
    73  		}
    74  	}
    75  	return &matcher{
    76  		filter:    filter,
    77  		skip:      skip,
    78  		matchFunc: matchString,
    79  		subNames:  map[string]int32{},
    80  	}
    81  }
    82  
    83  func (m *matcher) fullName(c *common, subname string) (name string, ok, partial bool) {
    84  	name = subname
    85  
    86  	m.mu.Lock()
    87  	defer m.mu.Unlock()
    88  
    89  	if c != nil && c.level > 0 {
    90  		name = m.unique(c.name, rewrite(subname))
    91  	}
    92  
    93  	matchMutex.Lock()
    94  	defer matchMutex.Unlock()
    95  
    96  	// We check the full array of paths each time to allow for the case that a pattern contains a '/'.
    97  	elem := strings.Split(name, "/")
    98  
    99  	// filter must match.
   100  	// accept partial match that may produce full match later.
   101  	ok, partial = m.filter.matches(elem, m.matchFunc)
   102  	if !ok {
   103  		return name, false, false
   104  	}
   105  
   106  	// skip must not match.
   107  	// ignore partial match so we can get to more precise match later.
   108  	skip, partialSkip := m.skip.matches(elem, m.matchFunc)
   109  	if skip && !partialSkip {
   110  		return name, false, false
   111  	}
   112  
   113  	return name, ok, partial
   114  }
   115  
   116  // clearSubNames clears the matcher's internal state, potentially freeing
   117  // memory. After this is called, T.Name may return the same strings as it did
   118  // for earlier subtests.
   119  func (m *matcher) clearSubNames() {
   120  	m.mu.Lock()
   121  	defer m.mu.Unlock()
   122  	for key := range m.subNames {
   123  		delete(m.subNames, key)
   124  	}
   125  }
   126  
   127  func (m simpleMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) {
   128  	for i, s := range name {
   129  		if i >= len(m) {
   130  			break
   131  		}
   132  		if ok, _ := matchString(m[i], s); !ok {
   133  			return false, false
   134  		}
   135  	}
   136  	return true, len(name) < len(m)
   137  }
   138  
   139  func (m simpleMatch) verify(name string, matchString func(pat, str string) (bool, error)) error {
   140  	for i, s := range m {
   141  		m[i] = rewrite(s)
   142  	}
   143  	// Verify filters before doing any processing.
   144  	for i, s := range m {
   145  		if _, err := matchString(s, "non-empty"); err != nil {
   146  			return fmt.Errorf("element %d of %s (%q): %s", i, name, s, err)
   147  		}
   148  	}
   149  	return nil
   150  }
   151  
   152  func (m alternationMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) {
   153  	for _, m := range m {
   154  		if ok, partial = m.matches(name, matchString); ok {
   155  			return ok, partial
   156  		}
   157  	}
   158  	return false, false
   159  }
   160  
   161  func (m alternationMatch) verify(name string, matchString func(pat, str string) (bool, error)) error {
   162  	for i, m := range m {
   163  		if err := m.verify(name, matchString); err != nil {
   164  			return fmt.Errorf("alternation %d of %s", i, err)
   165  		}
   166  	}
   167  	return nil
   168  }
   169  
   170  func splitRegexp(s string) filterMatch {
   171  	a := make(simpleMatch, 0, strings.Count(s, "/"))
   172  	b := make(alternationMatch, 0, strings.Count(s, "|"))
   173  	cs := 0
   174  	cp := 0
   175  	for i := 0; i < len(s); {
   176  		switch s[i] {
   177  		case '[':
   178  			cs++
   179  		case ']':
   180  			if cs--; cs < 0 { // An unmatched ']' is legal.
   181  				cs = 0
   182  			}
   183  		case '(':
   184  			if cs == 0 {
   185  				cp++
   186  			}
   187  		case ')':
   188  			if cs == 0 {
   189  				cp--
   190  			}
   191  		case '\\':
   192  			i++
   193  		case '/':
   194  			if cs == 0 && cp == 0 {
   195  				a = append(a, s[:i])
   196  				s = s[i+1:]
   197  				i = 0
   198  				continue
   199  			}
   200  		case '|':
   201  			if cs == 0 && cp == 0 {
   202  				a = append(a, s[:i])
   203  				s = s[i+1:]
   204  				i = 0
   205  				b = append(b, a)
   206  				a = make(simpleMatch, 0, len(a))
   207  				continue
   208  			}
   209  		}
   210  		i++
   211  	}
   212  
   213  	a = append(a, s)
   214  	if len(b) == 0 {
   215  		return a
   216  	}
   217  	return append(b, a)
   218  }
   219  
   220  // unique creates a unique name for the given parent and subname by affixing it
   221  // with one or more counts, if necessary.
   222  func (m *matcher) unique(parent, subname string) string {
   223  	base := parent + "/" + subname
   224  
   225  	for {
   226  		n := m.subNames[base]
   227  		if n < 0 {
   228  			panic("subtest count overflow")
   229  		}
   230  		m.subNames[base] = n + 1
   231  
   232  		if n == 0 && subname != "" {
   233  			prefix, nn := parseSubtestNumber(base)
   234  			if len(prefix) < len(base) && nn < m.subNames[prefix] {
   235  				// This test is explicitly named like "parent/subname#NN",
   236  				// and #NN was already used for the NNth occurrence of "parent/subname".
   237  				// Loop to add a disambiguating suffix.
   238  				continue
   239  			}
   240  			return base
   241  		}
   242  
   243  		name := fmt.Sprintf("%s#%02d", base, n)
   244  		if m.subNames[name] != 0 {
   245  			// This is the nth occurrence of base, but the name "parent/subname#NN"
   246  			// collides with the first occurrence of a subtest *explicitly* named
   247  			// "parent/subname#NN". Try the next number.
   248  			continue
   249  		}
   250  
   251  		return name
   252  	}
   253  }
   254  
   255  // parseSubtestNumber splits a subtest name into a "#%02d"-formatted int32
   256  // suffix (if present), and a prefix preceding that suffix (always).
   257  func parseSubtestNumber(s string) (prefix string, nn int32) {
   258  	i := strings.LastIndex(s, "#")
   259  	if i < 0 {
   260  		return s, 0
   261  	}
   262  
   263  	prefix, suffix := s[:i], s[i+1:]
   264  	if len(suffix) < 2 || (len(suffix) > 2 && suffix[0] == '0') {
   265  		// Even if suffix is numeric, it is not a possible output of a "%02" format
   266  		// string: it has either too few digits or too many leading zeroes.
   267  		return s, 0
   268  	}
   269  	if suffix == "00" {
   270  		if !strings.HasSuffix(prefix, "/") {
   271  			// We only use "#00" as a suffix for subtests named with the empty
   272  			// string — it isn't a valid suffix if the subtest name is non-empty.
   273  			return s, 0
   274  		}
   275  	}
   276  
   277  	n, err := strconv.ParseInt(suffix, 10, 32)
   278  	if err != nil || n < 0 {
   279  		return s, 0
   280  	}
   281  	return prefix, int32(n)
   282  }
   283  
   284  // rewrite rewrites a subname to having only printable characters and no white
   285  // space.
   286  func rewrite(s string) string {
   287  	b := []byte{}
   288  	for _, r := range s {
   289  		switch {
   290  		case isSpace(r):
   291  			b = append(b, '_')
   292  		case !strconv.IsPrint(r):
   293  			s := strconv.QuoteRune(r)
   294  			b = append(b, s[1:len(s)-1]...)
   295  		default:
   296  			b = append(b, string(r)...)
   297  		}
   298  	}
   299  	return string(b)
   300  }
   301  
   302  func isSpace(r rune) bool {
   303  	if r < 0x2000 {
   304  		switch r {
   305  		// Note: not the same as Unicode Z class.
   306  		case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680:
   307  			return true
   308  		}
   309  	} else {
   310  		if r <= 0x200a {
   311  			return true
   312  		}
   313  		switch r {
   314  		case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000:
   315  			return true
   316  		}
   317  	}
   318  	return false
   319  }