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  }