github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/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  	clear(m.subNames)
   123  }
   124  
   125  func (m simpleMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) {
   126  	for i, s := range name {
   127  		if i >= len(m) {
   128  			break
   129  		}
   130  		if ok, _ := matchString(m[i], s); !ok {
   131  			return false, false
   132  		}
   133  	}
   134  	return true, len(name) < len(m)
   135  }
   136  
   137  func (m simpleMatch) verify(name string, matchString func(pat, str string) (bool, error)) error {
   138  	for i, s := range m {
   139  		m[i] = rewrite(s)
   140  	}
   141  	// Verify filters before doing any processing.
   142  	for i, s := range m {
   143  		if _, err := matchString(s, "non-empty"); err != nil {
   144  			return fmt.Errorf("element %d of %s (%q): %s", i, name, s, err)
   145  		}
   146  	}
   147  	return nil
   148  }
   149  
   150  func (m alternationMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) {
   151  	for _, m := range m {
   152  		if ok, partial = m.matches(name, matchString); ok {
   153  			return ok, partial
   154  		}
   155  	}
   156  	return false, false
   157  }
   158  
   159  func (m alternationMatch) verify(name string, matchString func(pat, str string) (bool, error)) error {
   160  	for i, m := range m {
   161  		if err := m.verify(name, matchString); err != nil {
   162  			return fmt.Errorf("alternation %d of %s", i, err)
   163  		}
   164  	}
   165  	return nil
   166  }
   167  
   168  func splitRegexp(s string) filterMatch {
   169  	a := make(simpleMatch, 0, strings.Count(s, "/"))
   170  	b := make(alternationMatch, 0, strings.Count(s, "|"))
   171  	cs := 0
   172  	cp := 0
   173  	for i := 0; i < len(s); {
   174  		switch s[i] {
   175  		case '[':
   176  			cs++
   177  		case ']':
   178  			if cs--; cs < 0 { // An unmatched ']' is legal.
   179  				cs = 0
   180  			}
   181  		case '(':
   182  			if cs == 0 {
   183  				cp++
   184  			}
   185  		case ')':
   186  			if cs == 0 {
   187  				cp--
   188  			}
   189  		case '\\':
   190  			i++
   191  		case '/':
   192  			if cs == 0 && cp == 0 {
   193  				a = append(a, s[:i])
   194  				s = s[i+1:]
   195  				i = 0
   196  				continue
   197  			}
   198  		case '|':
   199  			if cs == 0 && cp == 0 {
   200  				a = append(a, s[:i])
   201  				s = s[i+1:]
   202  				i = 0
   203  				b = append(b, a)
   204  				a = make(simpleMatch, 0, len(a))
   205  				continue
   206  			}
   207  		}
   208  		i++
   209  	}
   210  
   211  	a = append(a, s)
   212  	if len(b) == 0 {
   213  		return a
   214  	}
   215  	return append(b, a)
   216  }
   217  
   218  // unique creates a unique name for the given parent and subname by affixing it
   219  // with one or more counts, if necessary.
   220  func (m *matcher) unique(parent, subname string) string {
   221  	base := parent + "/" + subname
   222  
   223  	for {
   224  		n := m.subNames[base]
   225  		if n < 0 {
   226  			panic("subtest count overflow")
   227  		}
   228  		m.subNames[base] = n + 1
   229  
   230  		if n == 0 && subname != "" {
   231  			prefix, nn := parseSubtestNumber(base)
   232  			if len(prefix) < len(base) && nn < m.subNames[prefix] {
   233  				// This test is explicitly named like "parent/subname#NN",
   234  				// and #NN was already used for the NNth occurrence of "parent/subname".
   235  				// Loop to add a disambiguating suffix.
   236  				continue
   237  			}
   238  			return base
   239  		}
   240  
   241  		name := fmt.Sprintf("%s#%02d", base, n)
   242  		if m.subNames[name] != 0 {
   243  			// This is the nth occurrence of base, but the name "parent/subname#NN"
   244  			// collides with the first occurrence of a subtest *explicitly* named
   245  			// "parent/subname#NN". Try the next number.
   246  			continue
   247  		}
   248  
   249  		return name
   250  	}
   251  }
   252  
   253  // parseSubtestNumber splits a subtest name into a "#%02d"-formatted int32
   254  // suffix (if present), and a prefix preceding that suffix (always).
   255  func parseSubtestNumber(s string) (prefix string, nn int32) {
   256  	i := strings.LastIndex(s, "#")
   257  	if i < 0 {
   258  		return s, 0
   259  	}
   260  
   261  	prefix, suffix := s[:i], s[i+1:]
   262  	if len(suffix) < 2 || (len(suffix) > 2 && suffix[0] == '0') {
   263  		// Even if suffix is numeric, it is not a possible output of a "%02" format
   264  		// string: it has either too few digits or too many leading zeroes.
   265  		return s, 0
   266  	}
   267  	if suffix == "00" {
   268  		if !strings.HasSuffix(prefix, "/") {
   269  			// We only use "#00" as a suffix for subtests named with the empty
   270  			// string — it isn't a valid suffix if the subtest name is non-empty.
   271  			return s, 0
   272  		}
   273  	}
   274  
   275  	n, err := strconv.ParseInt(suffix, 10, 32)
   276  	if err != nil || n < 0 {
   277  		return s, 0
   278  	}
   279  	return prefix, int32(n)
   280  }
   281  
   282  // rewrite rewrites a subname to having only printable characters and no white
   283  // space.
   284  func rewrite(s string) string {
   285  	b := []byte{}
   286  	for _, r := range s {
   287  		switch {
   288  		case isSpace(r):
   289  			b = append(b, '_')
   290  		case !strconv.IsPrint(r):
   291  			s := strconv.QuoteRune(r)
   292  			b = append(b, s[1:len(s)-1]...)
   293  		default:
   294  			b = append(b, string(r)...)
   295  		}
   296  	}
   297  	return string(b)
   298  }
   299  
   300  func isSpace(r rune) bool {
   301  	if r < 0x2000 {
   302  		switch r {
   303  		// Note: not the same as Unicode Z class.
   304  		case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680:
   305  			return true
   306  		}
   307  	} else {
   308  		if r <= 0x200a {
   309  			return true
   310  		}
   311  		switch r {
   312  		case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000:
   313  			return true
   314  		}
   315  	}
   316  	return false
   317  }