github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/filters/filter_test.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package filters 22 23 import ( 24 "fmt" 25 "testing" 26 27 "github.com/stretchr/testify/require" 28 ) 29 30 func TestNewFilterFromFilterValueInvalidPattern(t *testing.T) { 31 inputs := []string{"ab]c[sdf", "abc[z-a]", "*con[tT]ains*"} 32 for _, input := range inputs { 33 _, err := NewFilterFromFilterValue(FilterValue{Pattern: input}) 34 require.Error(t, err) 35 } 36 } 37 38 func TestNewFilterFromFilterValueWithNegation(t *testing.T) { 39 inputs := []struct { 40 pattern string 41 negate bool 42 data []mockFilterData 43 }{ 44 { 45 pattern: "foo", 46 negate: false, 47 data: []mockFilterData{ 48 {val: "foo", match: true}, 49 {val: "fo", match: false}, 50 }, 51 }, 52 { 53 pattern: "foo*bar", 54 negate: false, 55 data: []mockFilterData{ 56 {val: "foobar", match: true}, 57 {val: "foozapbar", match: true}, 58 {val: "bazbar", match: false}, 59 }, 60 }, 61 { 62 pattern: "ba?[0-9][!a-z]9", 63 negate: false, 64 data: []mockFilterData{ 65 {val: "bar959", match: true}, 66 {val: "bat449", match: true}, 67 {val: "bar9", match: false}, 68 }, 69 }, 70 { 71 pattern: "{ba,fo,car}*", 72 negate: false, 73 data: []mockFilterData{ 74 {val: "ba", match: true}, 75 {val: "foo", match: true}, 76 {val: "car", match: true}, 77 {val: "ca", match: false}, 78 }, 79 }, 80 { 81 pattern: "foo", 82 negate: true, 83 data: []mockFilterData{ 84 {val: "foo", match: false}, 85 {val: "fo", match: true}, 86 }, 87 }, 88 { 89 pattern: "foo*bar", 90 negate: true, 91 data: []mockFilterData{ 92 {val: "foobar", match: false}, 93 {val: "foozapbar", match: false}, 94 {val: "bazbar", match: true}, 95 }, 96 }, 97 { 98 pattern: "ba?[0-9][!a-z]9", 99 negate: true, 100 data: []mockFilterData{ 101 {val: "bar959", match: false}, 102 {val: "bat449", match: false}, 103 {val: "bar9", match: true}, 104 }, 105 }, 106 { 107 pattern: "{ba,fo,car}*", 108 negate: true, 109 data: []mockFilterData{ 110 {val: "ba", match: false}, 111 {val: "foo", match: false}, 112 {val: "car", match: false}, 113 {val: "ca", match: true}, 114 }, 115 }, 116 } 117 118 for _, input := range inputs { 119 f, err := NewFilterFromFilterValue(FilterValue{Pattern: input.pattern, Negate: input.negate}) 120 require.NoError(t, err) 121 for _, testcase := range input.data { 122 require.Equal(t, testcase.match, f.Matches([]byte(testcase.val))) 123 } 124 } 125 } 126 127 func TestFilters(t *testing.T) { 128 filters := genAndValidateFilters(t, []testPattern{ 129 testPattern{pattern: "f[A-z]?*", expectedStr: "StartsWith(Equals(\"f\") then Range(\"A-z\") then AnyChar)"}, 130 testPattern{pattern: "*ba[a-z]", expectedStr: "EndsWith(Equals(\"ba\") then Range(\"a-z\"))"}, 131 testPattern{pattern: "fo*?ba[!0-9][0-9]{8,9}", expectedStr: "StartsWith(Equals(\"fo\")) && EndsWith(AnyChar then Equals(\"ba\") then Not(Range(\"0-9\")) then Range(\"0-9\") then Range(\"8,9\"))"}, 132 }) 133 134 inputs := []testInput{ 135 newTestInput("foo", true, false, false), 136 newTestInput("test", false, false, false), 137 newTestInput("bar", false, true, false), 138 newTestInput("foobar", true, true, false), 139 newTestInput("foobar08", true, false, true), 140 newTestInput("footybar09", true, false, true), 141 } 142 143 for _, input := range inputs { 144 for i, expectedMatch := range input.matches { 145 require.Equal(t, expectedMatch, filters[i].Matches(input.val), 146 fmt.Sprintf("input: %s, pattern: %s", input.val, filters[i].String())) 147 } 148 } 149 } 150 151 func TestEqualityFilter(t *testing.T) { 152 inputs := []mockFilterData{ 153 {val: "foo", match: true}, 154 {val: "fo", match: false}, 155 {val: "foob", match: false}, 156 } 157 f := newEqualityFilter([]byte("foo")) 158 for _, input := range inputs { 159 require.Equal(t, input.match, f.Matches([]byte(input.val))) 160 } 161 } 162 163 func TestEmptyFilter(t *testing.T) { 164 f, err := NewFilter(nil) 165 require.NoError(t, err) 166 require.True(t, f.Matches([]byte(""))) 167 require.False(t, f.Matches([]byte(" "))) 168 require.False(t, f.Matches([]byte("foo"))) 169 } 170 171 func TestWildcardFilters(t *testing.T) { 172 filters := genAndValidateFilters(t, []testPattern{ 173 testPattern{pattern: "foo", expectedStr: "Equals(\"foo\")"}, 174 testPattern{pattern: "*bar", expectedStr: "EndsWith(Equals(\"bar\"))"}, 175 testPattern{pattern: "baz*", expectedStr: "StartsWith(Equals(\"baz\"))"}, 176 testPattern{pattern: "*cat*", expectedStr: "Contains(\"cat\")"}, 177 testPattern{pattern: "foo*bar", expectedStr: "StartsWith(Equals(\"foo\")) && EndsWith(Equals(\"bar\"))"}, 178 testPattern{pattern: "*", expectedStr: "All"}, 179 }) 180 181 inputs := []testInput{ 182 newTestInput("foo", true, false, false, false, false, true), 183 newTestInput("foobar", false, true, false, false, true, true), 184 newTestInput("foozapbar", false, true, false, false, true, true), 185 newTestInput("bazbar", false, true, true, false, false, true), 186 newTestInput("bazzzbar", false, true, true, false, false, true), 187 newTestInput("cat", false, false, false, true, false, true), 188 newTestInput("catbar", false, true, false, true, false, true), 189 newTestInput("baztestcat", false, false, true, true, false, true), 190 newTestInput("foocatbar", false, true, false, true, true, true), 191 newTestInput("footestcatbar", false, true, false, true, true, true), 192 } 193 194 for _, input := range inputs { 195 for i, expectedMatch := range input.matches { 196 require.Equal(t, expectedMatch, filters[i].Matches(input.val), 197 fmt.Sprintf("input: %s, pattern: %s", input.val, filters[i].String())) 198 } 199 } 200 } 201 202 func TestRangeFilters(t *testing.T) { 203 filters := genAndValidateFilters(t, []testPattern{ 204 testPattern{pattern: "fo[a-zA-Z0-9]", expectedStr: "Equals(\"fo\") then Range(\"a-z || A-Z || 0-9\")"}, 205 testPattern{pattern: "f[o-o]?", expectedStr: "Equals(\"f\") then Range(\"o-o\") then AnyChar"}, 206 testPattern{pattern: "???", expectedStr: "AnyChar then AnyChar then AnyChar"}, 207 testPattern{pattern: "ba?", expectedStr: "Equals(\"ba\") then AnyChar"}, 208 testPattern{pattern: "[!cC]ar", expectedStr: "Not(Range(\"cC\")) then Equals(\"ar\")"}, 209 testPattern{pattern: "ba?[0-9][!a-z]9", expectedStr: "Equals(\"ba\") then AnyChar then Range(\"0-9\") then Not(Range(\"a-z\")) then Equals(\"9\")"}, 210 testPattern{pattern: "{ba,fo,car}*", expectedStr: "StartsWith(Range(\"ba,fo,car\"))"}, 211 testPattern{pattern: "ba{r,t}*[!a-zA-Z]", expectedStr: "StartsWith(Equals(\"ba\") then Range(\"r,t\")) && EndsWith(Not(Range(\"a-z || A-Z\")))"}, 212 testPattern{pattern: "*{9}", expectedStr: "EndsWith(Range(\"9\"))"}, 213 }) 214 215 inputs := []testInput{ 216 newTestInput("foo", true, true, true, false, false, false, true, false, false), 217 newTestInput("fo!", false, true, true, false, false, false, true, false, false), 218 newTestInput("boo", false, false, true, false, false, false, false, false, false), 219 newTestInput("bar", false, false, true, true, true, false, true, false, false), 220 newTestInput("Bar", false, false, true, false, true, false, false, false, false), 221 newTestInput("car", false, false, true, false, false, false, true, false, false), 222 newTestInput("bar9", false, false, false, false, false, false, true, true, true), 223 newTestInput("bar990", false, false, false, false, false, false, true, true, false), 224 newTestInput("bar959", false, false, false, false, false, true, true, true, true), 225 newTestInput("bar009", false, false, false, false, false, true, true, true, true), 226 newTestInput("bat449", false, false, false, false, false, true, true, true, true), 227 } 228 229 for _, input := range inputs { 230 for i, expectedMatch := range input.matches { 231 require.Equal(t, expectedMatch, filters[i].Matches(input.val), 232 fmt.Sprintf("input: %s, pattern: %s", input.val, filters[i].String())) 233 } 234 } 235 } 236 237 func TestMultiFilter(t *testing.T) { 238 cf, _ := newContainsFilter([]byte("bar")) 239 filters := []Filter{ 240 NewMultiFilter([]Filter{}, Conjunction), 241 NewMultiFilter([]Filter{}, Disjunction), 242 NewMultiFilter([]Filter{newEqualityFilter([]byte("foo"))}, Conjunction), 243 NewMultiFilter([]Filter{newEqualityFilter([]byte("foo"))}, Disjunction), 244 NewMultiFilter([]Filter{newEqualityFilter([]byte("foo")), cf}, Conjunction), 245 NewMultiFilter([]Filter{newEqualityFilter([]byte("foo")), cf}, Disjunction), 246 } 247 248 inputs := []testInput{ 249 newTestInput("cat", true, true, false, false, false, false), 250 newTestInput("foo", true, true, true, true, false, true), 251 newTestInput("foobar", true, true, false, false, false, true), 252 newTestInput("bar", true, true, false, false, false, true), 253 } 254 255 for _, input := range inputs { 256 for i, expectedMatch := range input.matches { 257 require.Equal(t, expectedMatch, filters[i].Matches(input.val), 258 fmt.Sprintf("input: %s, pattern: %s", input.val, filters[i].String())) 259 } 260 } 261 } 262 263 func TestNegationFilter(t *testing.T) { 264 filters := genAndValidateFilters(t, []testPattern{ 265 testPattern{pattern: "!foo", expectedStr: "Not(Equals(\"foo\"))"}, 266 testPattern{pattern: "!*bar", expectedStr: "Not(EndsWith(Equals(\"bar\")))"}, 267 testPattern{pattern: "!baz*", expectedStr: "Not(StartsWith(Equals(\"baz\")))"}, 268 testPattern{pattern: "!*cat*", expectedStr: "Not(Contains(\"cat\"))"}, 269 testPattern{pattern: "!foo*bar", expectedStr: "Not(StartsWith(Equals(\"foo\")) && EndsWith(Equals(\"bar\")))"}, 270 testPattern{pattern: "foo!", expectedStr: "Equals(\"foo!\")"}, 271 }) 272 273 inputs := []testInput{ 274 newTestInput("foo", false, true, true, true, true, false), 275 newTestInput("foo!", true, true, true, true, true, true), 276 newTestInput("foobar", true, false, true, true, false, false), 277 newTestInput("bazbar", true, false, false, true, true, false), 278 newTestInput("cat", true, true, true, false, true, false), 279 newTestInput("catbar", true, false, true, false, true, false), 280 newTestInput("baztestcat", true, true, false, false, true, false), 281 newTestInput("foocatbar", true, false, true, false, false, false), 282 newTestInput("footestcatbar", true, false, true, false, false, false), 283 } 284 285 for _, input := range inputs { 286 for i, expectedMatch := range input.matches { 287 require.Equal(t, expectedMatch, filters[i].Matches(input.val), 288 fmt.Sprintf("input: %s, pattern: %s", input.val, filters[i].String())) 289 } 290 } 291 } 292 293 func TestBadPatterns(t *testing.T) { 294 patterns := []string{ 295 "!", // negation of nothing is everything, so user should use *. 296 "**", 297 "***", 298 "*too*many*", 299 "*too**many", 300 "to*o*many", 301 "to*o*ma*ny", 302 "abc[sdf", 303 "ab]c[sdf", 304 "abc[z-a]", 305 "*con[tT]ains*", 306 "*con{tT}ains*", 307 "*con?ains*", 308 "abc[a-zA-Z0-]", 309 "abc[a-zA-Z0]", 310 "abc[a-zZ-A]", 311 "ab}c{sdf", 312 "ab{}sdf", 313 "ab[]sdf", 314 } 315 316 for _, pattern := range patterns { 317 _, err := NewFilter([]byte(pattern)) 318 require.Error(t, err, fmt.Sprintf("pattern: %s", pattern)) 319 } 320 } 321 322 func TestMultiCharSequenceFilter(t *testing.T) { 323 _, err := newMultiCharSequenceFilter([]byte(""), false) 324 require.Error(t, err) 325 326 f, err := newMultiCharSequenceFilter([]byte("test2,test,tent,book"), false) 327 require.NoError(t, err) 328 validateLookup(t, f, "", false, "") 329 validateLookup(t, f, "t", false, "") 330 validateLookup(t, f, "tes", false, "") 331 validateLookup(t, f, "tset", false, "") 332 333 validateLookup(t, f, "test", true, "") 334 validateLookup(t, f, "tent", true, "") 335 validateLookup(t, f, "test3", true, "3") 336 validateLookup(t, f, "test2", true, "") 337 validateLookup(t, f, "book123", true, "123") 338 339 f, err = newMultiCharSequenceFilter([]byte("test2,test,tent,book"), true) 340 require.NoError(t, err) 341 validateLookup(t, f, "", false, "") 342 validateLookup(t, f, "t", false, "") 343 validateLookup(t, f, "tes", false, "") 344 validateLookup(t, f, "test3", false, "") 345 346 validateLookup(t, f, "test", true, "") 347 validateLookup(t, f, "tent", true, "") 348 validateLookup(t, f, "test2", true, "") 349 validateLookup(t, f, "12test", true, "12") 350 validateLookup(t, f, "12test2", true, "12") 351 validateLookup(t, f, "123book", true, "123") 352 353 f, err = newMultiCharSequenceFilter([]byte("test,test_post,extra_unused_filter"), false) 354 require.NoError(t, err) 355 validateLookup(t, f, "test", true, "") 356 validateLookup(t, f, "test_post", true, "") 357 358 f, err = newMultiCharSequenceFilter([]byte("test,pre_test,extra_unused_filter"), true) 359 require.NoError(t, err) 360 validateLookup(t, f, "test", true, "") 361 validateLookup(t, f, "pre_test", true, "") 362 } 363 364 func validateLookup(t *testing.T, f chainFilter, val string, expectedMatch bool, expectedRemainder string) { 365 remainder, match := f.matches([]byte(val)) 366 require.Equal(t, expectedMatch, match) 367 require.Equal(t, expectedRemainder, string(remainder)) 368 } 369 370 type mockFilterData struct { 371 val string 372 match bool 373 err error 374 } 375 376 type testPattern struct { 377 pattern string 378 expectedStr string 379 } 380 381 type testInput struct { 382 val []byte 383 matches []bool 384 } 385 386 func newTestInput(val string, matches ...bool) testInput { 387 return testInput{val: []byte(val), matches: matches} 388 } 389 390 func genAndValidateFilters(t *testing.T, patterns []testPattern) []Filter { 391 var err error 392 filters := make([]Filter, len(patterns)) 393 for i, pattern := range patterns { 394 filters[i], err = NewFilter([]byte(pattern.pattern)) 395 require.NoError(t, err, fmt.Sprintf("No error expected, but got: %v for pattern: %s", err, pattern.pattern)) 396 require.Equal(t, pattern.expectedStr, filters[i].String()) 397 } 398 399 return filters 400 }