go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/exec/execmock/filter.go (about) 1 // Copyright 2023 The LUCI 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 execmock 16 17 import ( 18 "go.chromium.org/luci/common/data/text/sequence" 19 "go.chromium.org/luci/common/exec/internal/execmockctx" 20 ) 21 22 type unsetMatcher struct{} 23 24 var _ sequence.Matcher = unsetMatcher{} 25 26 func (unsetMatcher) Matches(txt string) bool { panic("do not call unsetMatcher.Matches") } 27 28 type filter struct { 29 argFilters []sequence.Pattern 30 envFilters map[string][]sequence.Matcher 31 limit uint64 32 33 // literalArgMatchers is a count of the number of LiteralMatcher objects in 34 // `argFilters`. 35 literalArgMatchers int 36 37 // argMatchers is a count of all the Matcher objects in `argFilters`. 38 argMatchers int 39 } 40 41 func (f filter) less(other filter) (less, ok bool) { 42 switch { 43 case f.literalArgMatchers != other.literalArgMatchers: 44 // this is inverted; we want more specific (more literal matchers) to come 45 // earlier. 46 return f.literalArgMatchers > other.literalArgMatchers, true 47 48 case f.argMatchers != other.argMatchers: 49 // this is inverted; we want more specific (more literal matchers) to come 50 // earlier. 51 return f.argMatchers > other.argMatchers, true 52 53 case f.limit != other.limit: 54 switch { 55 case f.limit == 0: 56 return false, true 57 58 case other.limit == 0: 59 return true, true 60 } 61 62 return f.limit < other.limit, true 63 64 case len(f.envFilters) != len(other.argFilters): 65 return len(f.envFilters) < len(other.envFilters), true 66 } 67 68 return false, false 69 } 70 71 func (f filter) matches(mc *execmockctx.MockCriteria) (match bool) { 72 for _, pat := range f.argFilters { 73 if !pat.In(mc.Args...) { 74 return false 75 } 76 } 77 for key, matchers := range f.envFilters { 78 for _, mch := range matchers { 79 if _, mustBeUnset := mch.(unsetMatcher); mustBeUnset { 80 if _, ok := mc.Env.Lookup(key); ok { 81 return false 82 } 83 } else { 84 val, ok := mc.Env.Lookup(key) 85 if !ok { 86 return false 87 } 88 if !mch.Matches(val) { 89 return false 90 } 91 } 92 } 93 } 94 return true 95 } 96 97 func (f filter) withArgs(argPattern []string) filter { 98 seq, err := sequence.NewPattern(argPattern...) 99 if err != nil { 100 panic(err) 101 } 102 103 ret := f 104 105 for _, m := range seq { 106 if _, ok := m.(sequence.LiteralMatcher); ok { 107 ret.literalArgMatchers++ 108 } 109 } 110 ret.argMatchers += len(seq) 111 112 ret.argFilters = make([]sequence.Pattern, len(f.argFilters), len(f.argFilters)+1) 113 copy(ret.argFilters, f.argFilters) 114 ret.argFilters = append(ret.argFilters, seq) 115 116 return ret 117 } 118 119 func (f filter) withEnv(varName, valuePattern string) filter { 120 var pat sequence.Matcher 121 if valuePattern == "!" { 122 pat = unsetMatcher{} 123 } else { 124 var err error 125 pat, err = sequence.ParseRegLiteral(valuePattern) 126 if err != nil { 127 panic(err) 128 } 129 } 130 131 ret := f 132 133 ret.envFilters = make(map[string][]sequence.Matcher, len(f.envFilters)+1) 134 ret.envFilters[varName] = append(f.envFilters[varName], pat) 135 136 return ret 137 }