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