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 }