github.com/containerd/Containerd@v1.4.13/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  }