istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/framework/resource/matcher.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package resource 16 17 import ( 18 "regexp" 19 "strconv" 20 "strings" 21 ) 22 23 // testFilter is a regex matcher on a test. It is split by / following go subtest format 24 type testFilter []*regexp.Regexp 25 26 type Matcher struct { 27 filters []testFilter 28 } 29 30 // NewMatcher reimplements the logic of Go's -test.run. The code is mostly directly copied from Go's source. 31 func NewMatcher(regexs []string) (*Matcher, error) { 32 filters := []testFilter{} 33 for _, regex := range regexs { 34 filter := splitRegexp(regex) 35 for i, s := range filter { 36 filter[i] = rewrite(s) 37 } 38 rxs := []*regexp.Regexp{} 39 for _, f := range filter { 40 r, err := regexp.Compile(f) 41 if err != nil { 42 return nil, err 43 } 44 rxs = append(rxs, r) 45 } 46 filters = append(filters, rxs) 47 } 48 return &Matcher{filters: filters}, nil 49 } 50 51 func (m *Matcher) MatchTest(testName string) bool { 52 for _, f := range m.filters { 53 if matchSingle(f, testName) { 54 return true 55 } 56 } 57 return false 58 } 59 60 func matchSingle(filter testFilter, testName string) bool { 61 if len(filter) == 0 { 62 // No regex defined, we default to NOT matching. This ensures our default skips nothing 63 return false 64 } 65 elem := strings.Split(testName, "/") 66 if len(filter) > len(elem) { 67 return false 68 } 69 for i, s := range elem { 70 if i >= len(filter) { 71 break 72 } 73 if !filter[i].MatchString(s) { 74 return false 75 } 76 } 77 return true 78 } 79 80 // From go/src/testing/match.go 81 // nolint 82 func splitRegexp(s string) []string { 83 a := make([]string, 0, strings.Count(s, "/")) 84 cs := 0 85 cp := 0 86 for i := 0; i < len(s); { 87 switch s[i] { 88 case '[': 89 cs++ 90 case ']': 91 if cs--; cs < 0 { // An unmatched ']' is legal. 92 cs = 0 93 } 94 case '(': 95 if cs == 0 { 96 cp++ 97 } 98 case ')': 99 if cs == 0 { 100 cp-- 101 } 102 case '\\': 103 i++ 104 case '/': 105 if cs == 0 && cp == 0 { 106 a = append(a, s[:i]) 107 s = s[i+1:] 108 i = 0 109 continue 110 } 111 } 112 i++ 113 } 114 return append(a, s) 115 } 116 117 // rewrite rewrites a subname to having only printable characters and no white 118 // space. 119 // From go/src/testing/match.go 120 func rewrite(s string) string { 121 b := []byte{} 122 for _, r := range s { 123 switch { 124 case isSpace(r): 125 b = append(b, '_') 126 case !strconv.IsPrint(r): 127 s := strconv.QuoteRune(r) 128 b = append(b, s[1:len(s)-1]...) 129 default: 130 b = append(b, string(r)...) 131 } 132 } 133 return string(b) 134 } 135 136 // From go/src/testing/match.go 137 func isSpace(r rune) bool { 138 if r < 0x2000 { 139 switch r { 140 // Note: not the same as Unicode Z class. 141 case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680: 142 return true 143 } 144 } else { 145 if r <= 0x200a { 146 return true 147 } 148 switch r { 149 case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000: 150 return true 151 } 152 } 153 return false 154 }