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 }