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 }