// Copyright 2015 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package testingimport ()// matcher sanitizes, uniques, and filters names of subtests and subbenchmarks.type matcher struct { filter filterMatch skip filterMatch matchFunc func(pat, str string) (bool, error) mu sync.Mutex// subNames is used to deduplicate subtest names. // Each key is the subtest name joined to the deduplicated name of the parent test. // Each value is the count of the number of occurrences of the given subtest name // already seen. subNames map[string]int32}type filterMatch interface {// matches checks the name against the receiver's pattern strings using the // given match function. matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool)// verify checks that the receiver's pattern strings are valid filters by // calling the given match function. verify(name string, matchString func(pat, str string) (bool, error)) error}// simpleMatch matches a test name if all of the pattern strings match in// sequence.type simpleMatch []string// alternationMatch matches a test name if one of the alternations match.type alternationMatch []filterMatch// TODO: fix test_main to avoid race and improve caching, also allowing to// eliminate this Mutex.var matchMutex sync.Mutexfunc allMatcher() *matcher {returnnewMatcher(nil, "", "", "")}func newMatcher( func(, string) (bool, error), , , string) *matcher {var , filterMatchif == "" { = simpleMatch{} // always partial true } else { = splitRegexp()if := .verify(, ); != nil {fmt.Fprintf(os.Stderr, "testing: invalid regexp for %s\n", )os.Exit(1) } }if == "" { = alternationMatch{} // always false } else { = splitRegexp()if := .verify("-test.skip", ); != nil {fmt.Fprintf(os.Stderr, "testing: invalid regexp for %v\n", )os.Exit(1) } }return &matcher{filter: ,skip: ,matchFunc: ,subNames: map[string]int32{}, }}func ( *matcher) ( *common, string) ( string, , bool) { = .mu.Lock()defer .mu.Unlock()if != nil && .level > 0 { = .unique(.name, rewrite()) }matchMutex.Lock()defermatchMutex.Unlock()// We check the full array of paths each time to allow for the case that a pattern contains a '/'. := strings.Split(, "/")// filter must match. // accept partial match that may produce full match later. , = .filter.matches(, .matchFunc)if ! {return , false, false }// skip must not match. // ignore partial match so we can get to more precise match later. , := .skip.matches(, .matchFunc)if && ! {return , false, false }return , , }// clearSubNames clears the matcher's internal state, potentially freeing// memory. After this is called, T.Name may return the same strings as it did// for earlier subtests.func ( *matcher) () { .mu.Lock()defer .mu.Unlock()clear(.subNames)}func ( simpleMatch) ( []string, func(, string) (bool, error)) (, bool) {for , := range {if >= len() {break }if , := ([], ); ! {returnfalse, false } }returntrue, len() < len()}func ( simpleMatch) ( string, func(, string) (bool, error)) error {for , := range { [] = rewrite() }// Verify filters before doing any processing.for , := range {if , := (, "non-empty"); != nil {returnfmt.Errorf("element %d of %s (%q): %s", , , , ) } }returnnil}func ( alternationMatch) ( []string, func(, string) (bool, error)) (, bool) {for , := range {if , = .matches(, ); {return , } }returnfalse, false}func ( alternationMatch) ( string, func(, string) (bool, error)) error {for , := range {if := .verify(, ); != nil {returnfmt.Errorf("alternation %d of %s", , ) } }returnnil}func splitRegexp( string) filterMatch { := make(simpleMatch, 0, strings.Count(, "/")) := make(alternationMatch, 0, strings.Count(, "|")) := 0 := 0for := 0; < len(); {switch [] {case'[': ++case']':if --; < 0 { // An unmatched ']' is legal. = 0 }case'(':if == 0 { ++ }case')':if == 0 { -- }case'\\': ++case'/':if == 0 && == 0 { = append(, [:]) = [+1:] = 0continue }case'|':if == 0 && == 0 { = append(, [:]) = [+1:] = 0 = append(, ) = make(simpleMatch, 0, len())continue } } ++ } = append(, )iflen() == 0 {return }returnappend(, )}// unique creates a unique name for the given parent and subname by affixing it// with one or more counts, if necessary.func ( *matcher) (, string) string { := + "/" + for { := .subNames[]if < 0 {panic("subtest count overflow") } .subNames[] = + 1if == 0 && != "" { , := parseSubtestNumber()iflen() < len() && < .subNames[] {// This test is explicitly named like "parent/subname#NN", // and #NN was already used for the NNth occurrence of "parent/subname". // Loop to add a disambiguating suffix.continue }return } := fmt.Sprintf("%s#%02d", , )if .subNames[] != 0 {// This is the nth occurrence of base, but the name "parent/subname#NN" // collides with the first occurrence of a subtest *explicitly* named // "parent/subname#NN". Try the next number.continue }return }}// parseSubtestNumber splits a subtest name into a "#%02d"-formatted int32// suffix (if present), and a prefix preceding that suffix (always).func parseSubtestNumber( string) ( string, int32) { := strings.LastIndex(, "#")if < 0 {return , 0 } , := [:], [+1:]iflen() < 2 || (len() > 2 && [0] == '0') {// Even if suffix is numeric, it is not a possible output of a "%02" format // string: it has either too few digits or too many leading zeroes.return , 0 }if == "00" {if !strings.HasSuffix(, "/") {// We only use "#00" as a suffix for subtests named with the empty // string — it isn't a valid suffix if the subtest name is non-empty.return , 0 } } , := strconv.ParseInt(, 10, 32)if != nil || < 0 {return , 0 }return , int32()}// rewrite rewrites a subname to having only printable characters and no white// space.func rewrite( string) string { := []byte{}for , := range {switch {caseisSpace(): = append(, '_')case !strconv.IsPrint(): := strconv.QuoteRune() = append(, [1:len()-1]...)default: = append(, string()...) } }returnstring()}func isSpace( rune) bool {if < 0x2000 {switch {// Note: not the same as Unicode Z class.case'\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680:returntrue } } else {if <= 0x200a {returntrue }switch {case0x2028, 0x2029, 0x202f, 0x205f, 0x3000:returntrue } }returnfalse}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.