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  }