github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/filters/filter_test.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package filters 18 19 import ( 20 "reflect" 21 "strings" 22 "testing" 23 ) 24 25 func TestFilters(t *testing.T) { 26 type cEntry struct { 27 Name string 28 Other string 29 Labels map[string]string 30 } 31 32 corpusS := []cEntry{ 33 { 34 Name: "foo", 35 Labels: map[string]string{ 36 "foo": "true", 37 }, 38 }, 39 { 40 Name: "bar", 41 }, 42 { 43 Name: "foo", 44 Labels: map[string]string{ 45 "foo": "present", 46 "more complex label": "present", 47 }, 48 }, 49 { 50 Name: "bar", 51 Labels: map[string]string{ 52 "bar": "true", 53 }, 54 }, 55 { 56 Name: "fooer", 57 Labels: map[string]string{ 58 "more complex label with \\ and \"": "present", 59 }, 60 }, 61 { 62 Name: "fooer", 63 Labels: map[string]string{ 64 "more complex label with \\ and \".post": "present", 65 }, 66 }, 67 { 68 Name: "baz", 69 Other: "too complex, yo", 70 }, 71 { 72 Name: "bazo", 73 Other: "abc", 74 }, 75 { 76 Name: "compound", 77 Labels: map[string]string{ 78 "foo": "omg_asdf.asdf-qwer", 79 }, 80 }, 81 } 82 83 var corpus []interface{} 84 for _, entry := range corpusS { 85 corpus = append(corpus, entry) 86 } 87 88 // adapt shows an example of how to build an adaptor function for a type. 89 adapt := func(o interface{}) Adaptor { 90 obj := o.(cEntry) 91 return AdapterFunc(func(fieldpath []string) (string, bool) { 92 switch fieldpath[0] { 93 case "name": 94 return obj.Name, len(obj.Name) > 0 95 case "other": 96 return obj.Other, len(obj.Other) > 0 97 case "labels": 98 value, ok := obj.Labels[strings.Join(fieldpath[1:], ".")] 99 return value, ok 100 } 101 102 return "", false 103 }) 104 } 105 106 for _, testcase := range []struct { 107 name string 108 input string 109 expected []interface{} 110 errString string 111 }{ 112 { 113 name: "Empty", 114 input: "", 115 expected: corpus, 116 }, 117 { 118 name: "Present", 119 input: "name", 120 expected: corpus, 121 }, 122 { 123 name: "LabelPresent", 124 input: "labels.foo", 125 expected: []interface{}{ 126 corpus[0], 127 corpus[2], 128 corpus[8], 129 }, 130 }, 131 { 132 name: "NameAndLabelPresent", 133 input: "labels.foo,name", 134 expected: []interface{}{ 135 corpus[0], 136 corpus[2], 137 corpus[8], 138 }, 139 }, 140 { 141 name: "LabelValue", 142 input: "labels.foo==true", 143 expected: []interface{}{ 144 corpus[0], 145 }, 146 }, 147 { 148 name: "LabelValuePunctuated", 149 input: "labels.foo==omg_asdf.asdf-qwer", 150 expected: []interface{}{ 151 corpus[8], 152 }, 153 }, 154 { 155 name: "LabelValueNoAltQuoting", 156 input: "labels.|foo|==omg_asdf.asdf-qwer", 157 errString: "filters: parse error: [labels. >|||< foo|==omg_asdf.asdf-qwer]: invalid quote encountered", 158 }, 159 { 160 name: "Name", 161 input: "name==bar", 162 expected: []interface{}{ 163 corpus[1], 164 corpus[3], 165 }, 166 }, 167 { 168 name: "NameNotEqual", 169 input: "name!=bar", 170 expected: []interface{}{ 171 corpus[0], 172 corpus[2], 173 corpus[4], 174 corpus[5], 175 corpus[6], 176 corpus[7], 177 corpus[8], 178 }, 179 }, 180 { 181 name: "NameAndLabelPresent", 182 input: "name==bar,labels.bar", 183 expected: []interface{}{ 184 corpus[3], 185 }, 186 }, 187 { 188 name: "QuotedValue", 189 input: "other==\"too complex, yo\"", 190 expected: []interface{}{ 191 corpus[6], 192 }, 193 }, 194 { 195 name: "RegexpValue", 196 input: "other~=[abc]+,name!=foo", 197 expected: []interface{}{ 198 corpus[6], 199 corpus[7], 200 }, 201 }, 202 { 203 name: "RegexpQuotedValue", 204 input: "other~=/[abc]+/,name!=foo", 205 expected: []interface{}{ 206 corpus[6], 207 corpus[7], 208 }, 209 }, 210 { 211 name: "RegexpQuotedValue", 212 input: "other~=/[abc]{1,2}/,name!=foo", 213 expected: []interface{}{ 214 corpus[6], 215 corpus[7], 216 }, 217 }, 218 { 219 name: "RegexpQuotedValueGarbage", 220 input: "other~=/[abc]{0,1}\"\\//,name!=foo", 221 // valid syntax, but doesn't match anything 222 }, 223 { 224 name: "NameAndLabelValue", 225 input: "name==bar,labels.bar==true", 226 expected: []interface{}{ 227 corpus[3], 228 }, 229 }, 230 { 231 name: "NameAndLabelValueNoMatch", 232 input: "name==bar,labels.bar==wrong", 233 }, 234 { 235 name: "LabelQuotedFieldPathPresent", 236 input: `name==foo,labels."more complex label"`, 237 expected: []interface{}{ 238 corpus[2], 239 }, 240 }, 241 { 242 name: "LabelQuotedFieldPathPresentWithQuoted", 243 input: `labels."more complex label with \\ and \""==present`, 244 expected: []interface{}{ 245 corpus[4], 246 }, 247 }, 248 { 249 name: "LabelQuotedFieldPathPresentWithQuotedEmbed", 250 input: `labels."more complex label with \\ and \"".post==present`, 251 expected: []interface{}{ 252 corpus[5], 253 }, 254 }, 255 { 256 name: "LabelQuotedFieldPathPresentWithQuotedEmbedInvalid", 257 input: `labels.?"more complex label with \\ and \"".post==present`, 258 errString: `filters: parse error: [labels. >|?|< "more complex label with \\ and \"".post==present]: expected field or quoted`, 259 }, 260 { 261 name: "TrailingComma", 262 input: "name==foo,", 263 errString: `filters: parse error: [name==foo,]: expected field or quoted`, 264 }, 265 { 266 name: "TrailingFieldSeparator", 267 input: "labels.", 268 errString: `filters: parse error: [labels.]: expected field or quoted`, 269 }, 270 { 271 name: "MissingValue", 272 input: "image~=,id?=?fbaq", 273 errString: `filters: parse error: [image~= >|,|< id?=?fbaq]: expected value or quoted`, 274 }, 275 { 276 name: "FieldQuotedLiteralNotTerminated", 277 input: "labels.ns/key==value", 278 errString: `filters: parse error: [labels.ns >|/|< key==value]: quoted literal not terminated`, 279 }, 280 { 281 name: "ValueQuotedLiteralNotTerminated", 282 input: "labels.key==/value", 283 errString: `filters: parse error: [labels.key== >|/|< value]: quoted literal not terminated`, 284 }, 285 } { 286 t.Run(testcase.name, func(t *testing.T) { 287 filter, err := Parse(testcase.input) 288 if testcase.errString != "" { 289 if err == nil { 290 t.Fatalf("expected an error, but received nil") 291 } 292 if err.Error() != testcase.errString { 293 t.Fatalf("error %v != %v", err, testcase.errString) 294 } 295 296 return 297 } 298 if err != nil { 299 t.Fatal(err) 300 } 301 302 if filter == nil { 303 t.Fatal("filter should not be nil") 304 } 305 306 var results []interface{} 307 for _, item := range corpus { 308 adaptor := adapt(item) 309 if filter.Match(adaptor) { 310 results = append(results, item) 311 } 312 } 313 314 if !reflect.DeepEqual(results, testcase.expected) { 315 t.Fatalf("%q: %#v != %#v", testcase.input, results, testcase.expected) 316 } 317 }) 318 } 319 } 320 321 func TestOperatorStrings(t *testing.T) { 322 for _, testcase := range []struct { 323 op operator 324 expected string 325 }{ 326 {operatorPresent, "?"}, 327 {operatorEqual, "=="}, 328 {operatorNotEqual, "!="}, 329 {operatorMatches, "~="}, 330 {10, "unknown"}, 331 } { 332 if !reflect.DeepEqual(testcase.op.String(), testcase.expected) { 333 t.Fatalf("return value unexpected: %v != %v", testcase.op.String(), testcase.expected) 334 } 335 } 336 }