github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/filters/scanner_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  	"fmt"
    21  	"testing"
    22  )
    23  
    24  type tokenResult struct {
    25  	pos   int
    26  	token token
    27  	text  string
    28  	err   string
    29  }
    30  
    31  func (tr tokenResult) String() string {
    32  	if tr.err != "" {
    33  		return fmt.Sprintf("{pos: %v, token: %v, text: %q, err: %q}", tr.pos, tr.token, tr.text, tr.err)
    34  	}
    35  	return fmt.Sprintf("{pos: %v, token: %v, text: %q}", tr.pos, tr.token, tr.text)
    36  }
    37  
    38  func TestScanner(t *testing.T) {
    39  
    40  	for _, testcase := range []struct {
    41  		name     string
    42  		input    string
    43  		expected []tokenResult
    44  	}{
    45  		{
    46  			name:  "Field",
    47  			input: "name",
    48  			expected: []tokenResult{
    49  				{pos: 0, token: tokenField, text: "name"},
    50  				{pos: 4, token: tokenEOF},
    51  			},
    52  		},
    53  		{
    54  			name:  "SelectorsWithOperators",
    55  			input: "name==value,foo!=bar",
    56  			expected: []tokenResult{
    57  				{pos: 0, token: tokenField, text: "name"},
    58  				{pos: 4, token: tokenOperator, text: "=="},
    59  				{pos: 6, token: tokenValue, text: "value"},
    60  				{pos: 11, token: tokenSeparator, text: ","},
    61  				{pos: 12, token: tokenField, text: "foo"},
    62  				{pos: 15, token: tokenOperator, text: "!="},
    63  				{pos: 17, token: tokenValue, text: "bar"},
    64  				{pos: 20, token: tokenEOF},
    65  			},
    66  		},
    67  		{
    68  			name:  "SelectorsWithFieldPaths",
    69  			input: "name==value,labels.foo=value,other.bar~=match",
    70  			expected: []tokenResult{
    71  				{pos: 0, token: tokenField, text: "name"},
    72  				{pos: 4, token: tokenOperator, text: "=="},
    73  				{pos: 6, token: tokenValue, text: "value"},
    74  				{pos: 11, token: tokenSeparator, text: ","},
    75  				{pos: 12, token: tokenField, text: "labels"},
    76  				{pos: 18, token: tokenSeparator, text: "."},
    77  				{pos: 19, token: tokenField, text: "foo"},
    78  				{pos: 22, token: tokenOperator, text: "="},
    79  				{pos: 23, token: tokenValue, text: "value"},
    80  				{pos: 28, token: tokenSeparator, text: ","},
    81  				{pos: 29, token: tokenField, text: "other"},
    82  				{pos: 34, token: tokenSeparator, text: "."},
    83  				{pos: 35, token: tokenField, text: "bar"},
    84  				{pos: 38, token: tokenOperator, text: "~="},
    85  				{pos: 40, token: tokenValue, text: "match"},
    86  				{pos: 45, token: tokenEOF},
    87  			},
    88  		},
    89  		{
    90  			name:  "RegexpValue",
    91  			input: "name~=[abc]+,foo=test",
    92  			expected: []tokenResult{
    93  				{pos: 0, token: tokenField, text: "name"},
    94  				{pos: 4, token: tokenOperator, text: "~="},
    95  				{pos: 6, token: tokenValue, text: "[abc]+"},
    96  				{pos: 12, token: tokenSeparator, text: ","},
    97  				{pos: 13, token: tokenField, text: "foo"},
    98  				{pos: 16, token: tokenOperator, text: "="},
    99  				{pos: 17, token: tokenValue, text: "test"},
   100  				{pos: 21, token: tokenEOF},
   101  			},
   102  		},
   103  		{
   104  			name:  "RegexpEscapedValue",
   105  			input: `name~=[abc]\+,foo=test`,
   106  			expected: []tokenResult{
   107  				{pos: 0, token: tokenField, text: "name"},
   108  				{pos: 4, token: tokenOperator, text: "~="},
   109  				{pos: 6, token: tokenValue, text: "[abc]\\+"},
   110  				{pos: 13, token: tokenSeparator, text: ","},
   111  				{pos: 14, token: tokenField, text: "foo"},
   112  				{pos: 17, token: tokenOperator, text: "="},
   113  				{pos: 18, token: tokenValue, text: "test"},
   114  				{pos: 22, token: tokenEOF},
   115  			},
   116  		},
   117  		{
   118  			name:  "RegexpQuotedValue",
   119  			input: `name~=/[abc]{0,2}/,foo=test`,
   120  			expected: []tokenResult{
   121  				{pos: 0, token: tokenField, text: "name"},
   122  				{pos: 4, token: tokenOperator, text: "~="},
   123  				{pos: 6, token: tokenQuoted, text: "/[abc]{0,2}/"},
   124  				{pos: 18, token: tokenSeparator, text: ","},
   125  				{pos: 19, token: tokenField, text: "foo"},
   126  				{pos: 22, token: tokenOperator, text: "="},
   127  				{pos: 23, token: tokenValue, text: "test"},
   128  				{pos: 27, token: tokenEOF},
   129  			},
   130  		},
   131  		{
   132  			name:  "Cowsay",
   133  			input: "name~=牛,labels.moo=true",
   134  			expected: []tokenResult{
   135  				{pos: 0, token: tokenField, text: "name"},
   136  				{pos: 4, token: tokenOperator, text: "~="},
   137  				{pos: 6, token: tokenValue, text: "牛"},
   138  				{pos: 9, token: tokenSeparator, text: ","},
   139  				{pos: 10, token: tokenField, text: "labels"},
   140  				{pos: 16, token: tokenSeparator, text: "."},
   141  				{pos: 17, token: tokenField, text: "moo"},
   142  				{pos: 20, token: tokenOperator, text: "="},
   143  				{pos: 21, token: tokenValue, text: "true"},
   144  				{pos: 25, token: tokenEOF},
   145  			},
   146  		},
   147  		{
   148  			name:  "CowsayRegexpQuoted",
   149  			input: "name~=|牛|,labels.moo=true",
   150  			expected: []tokenResult{
   151  				{pos: 0, token: tokenField, text: "name"},
   152  				{pos: 4, token: tokenOperator, text: "~="},
   153  				{pos: 6, token: tokenQuoted, text: "|牛|"},
   154  				{pos: 11, token: tokenSeparator, text: ","},
   155  				{pos: 12, token: tokenField, text: "labels"},
   156  				{pos: 18, token: tokenSeparator, text: "."},
   157  				{pos: 19, token: tokenField, text: "moo"},
   158  				{pos: 22, token: tokenOperator, text: "="},
   159  				{pos: 23, token: tokenValue, text: "true"},
   160  				{pos: 27, token: tokenEOF},
   161  			},
   162  		},
   163  		{
   164  			name:  "Escapes",
   165  			input: `name~="asdf\n\tfooo"`,
   166  			expected: []tokenResult{
   167  				{pos: 0, token: tokenField, text: "name"},
   168  				{pos: 4, token: tokenOperator, text: "~="},
   169  				{pos: 6, token: tokenQuoted, text: "\"asdf\\n\\tfooo\""},
   170  				{pos: 20, token: tokenEOF},
   171  			},
   172  		},
   173  		{
   174  			name:  "NullInput",
   175  			input: "foo\x00bar",
   176  			expected: []tokenResult{
   177  				{pos: 0, token: tokenField, text: "foo"},
   178  				{pos: 3, token: tokenIllegal, err: "unexpected null"},
   179  				{pos: 4, token: tokenField, text: "bar"},
   180  				{pos: 7, token: tokenEOF},
   181  			},
   182  		},
   183  		{
   184  			name:  "SpacesChomped",
   185  			input: "foo = bar    ",
   186  			expected: []tokenResult{
   187  				{pos: 0, token: tokenField, text: "foo"},
   188  				{pos: 4, token: tokenOperator, text: "="},
   189  				{pos: 6, token: tokenValue, text: "bar"},
   190  				{pos: 13, token: tokenEOF},
   191  			},
   192  		},
   193  		{
   194  			name:  "ValuesPunctauted",
   195  			input: "compound.labels==punctuated_value.foo-bar",
   196  			expected: []tokenResult{
   197  				{pos: 0, token: tokenField, text: "compound"},
   198  				{pos: 8, token: tokenSeparator, text: "."},
   199  				{pos: 9, token: tokenField, text: "labels"},
   200  				{pos: 15, token: tokenOperator, text: "=="},
   201  				{pos: 17, token: tokenValue, text: "punctuated_value.foo-bar"},
   202  				{pos: 41, token: tokenEOF},
   203  			},
   204  		},
   205  		{
   206  			name:  "PartialInput",
   207  			input: "interrupted=",
   208  			expected: []tokenResult{
   209  				{pos: 0, token: tokenField, text: "interrupted"},
   210  				{pos: 11, token: tokenOperator, text: "="},
   211  				{pos: 12, token: tokenEOF},
   212  			},
   213  		},
   214  		{
   215  			name:  "DoubleValue",
   216  			input: "doublevalue=value value",
   217  			expected: []tokenResult{
   218  				{pos: 0, token: tokenField, text: "doublevalue"},
   219  				{pos: 11, token: tokenOperator, text: "="},
   220  				{pos: 12, token: tokenValue, text: "value"},
   221  				{pos: 18, token: tokenField, text: "value"},
   222  				{pos: 23, token: tokenEOF},
   223  			},
   224  		},
   225  		{
   226  			name:  "LeadingWithQuoted",
   227  			input: `"leading quote".postquote==value`,
   228  			expected: []tokenResult{
   229  				{pos: 0, token: tokenQuoted, text: "\"leading quote\""},
   230  				{pos: 15, token: tokenSeparator, text: "."},
   231  				{pos: 16, token: tokenField, text: "postquote"},
   232  				{pos: 25, token: tokenOperator, text: "=="},
   233  				{pos: 27, token: tokenValue, text: "value"},
   234  				{pos: 32, token: tokenEOF},
   235  			},
   236  		},
   237  		{
   238  			name:  "MissingValue",
   239  			input: "input==,id!=ff",
   240  			expected: []tokenResult{
   241  				{pos: 0, token: tokenField, text: "input"},
   242  				{pos: 5, token: tokenOperator, text: "=="},
   243  				{pos: 7, token: tokenSeparator, text: ","},
   244  				{pos: 8, token: tokenField, text: "id"},
   245  				{pos: 10, token: tokenOperator, text: "!="},
   246  				{pos: 12, token: tokenValue, text: "ff"},
   247  				{pos: 14, token: tokenEOF},
   248  			},
   249  		},
   250  		{
   251  			name:  "QuotedRegexp",
   252  			input: "input~=/foo\\/bar/,id!=ff",
   253  			expected: []tokenResult{
   254  				{pos: 0, token: tokenField, text: "input"},
   255  				{pos: 5, token: tokenOperator, text: "~="},
   256  				{pos: 7, token: tokenQuoted, text: "/foo\\/bar/"},
   257  				{pos: 17, token: tokenSeparator, text: ","},
   258  				{pos: 18, token: tokenField, text: "id"},
   259  				{pos: 20, token: tokenOperator, text: "!="},
   260  				{pos: 22, token: tokenValue, text: "ff"},
   261  				{pos: 24, token: tokenEOF},
   262  			},
   263  		},
   264  		{
   265  			name:  "QuotedRegexpAlt",
   266  			input: "input~=|foo/bar|,id!=ff",
   267  			expected: []tokenResult{
   268  				{pos: 0, token: tokenField, text: "input"},
   269  				{pos: 5, token: tokenOperator, text: "~="},
   270  				{pos: 7, token: tokenQuoted, text: "|foo/bar|"},
   271  				{pos: 16, token: tokenSeparator, text: ","},
   272  				{pos: 17, token: tokenField, text: "id"},
   273  				{pos: 19, token: tokenOperator, text: "!="},
   274  				{pos: 21, token: tokenValue, text: "ff"},
   275  				{pos: 23, token: tokenEOF},
   276  			},
   277  		},
   278  		{
   279  			name:  "IllegalQuoted",
   280  			input: "labels.containerd.io/key==value",
   281  			expected: []tokenResult{
   282  				{pos: 0, token: tokenField, text: "labels"},
   283  				{pos: 6, token: tokenSeparator, text: "."},
   284  				{pos: 7, token: tokenField, text: "containerd"},
   285  				{pos: 17, token: tokenSeparator, text: "."},
   286  				{pos: 18, token: tokenField, text: "io"},
   287  				{pos: 20, token: tokenIllegal, text: "/key==value", err: "quoted literal not terminated"},
   288  				{pos: 31, token: tokenEOF},
   289  			},
   290  		},
   291  		{
   292  			name:  "IllegalQuotedWithNewLine",
   293  			input: "labels.\"containerd.io\nkey\"==value",
   294  			expected: []tokenResult{
   295  				{pos: 0, token: tokenField, text: "labels"},
   296  				{pos: 6, token: tokenSeparator, text: "."},
   297  				{pos: 7, token: tokenIllegal, text: "\"containerd.io\n", err: "quoted literal not terminated"},
   298  				{pos: 22, token: tokenField, text: "key"},
   299  				{pos: 25, token: tokenIllegal, text: "\"==value", err: "quoted literal not terminated"},
   300  				{pos: 33, token: tokenEOF},
   301  			},
   302  		},
   303  		{
   304  			name:  "IllegalEscapeSequence",
   305  			input: `labels."\g"`,
   306  			expected: []tokenResult{
   307  				{pos: 0, token: tokenField, text: "labels"},
   308  				{pos: 6, token: tokenSeparator, text: "."},
   309  				{pos: 7, token: tokenIllegal, text: `"\g"`, err: "illegal escape sequence"},
   310  				{pos: 11, token: tokenEOF},
   311  			},
   312  		},
   313  		{
   314  			name:  "IllegalNumericEscapeSequence",
   315  			input: `labels."\xaz"`,
   316  			expected: []tokenResult{
   317  				{pos: 0, token: tokenField, text: "labels"},
   318  				{pos: 6, token: tokenSeparator, text: "."},
   319  				{pos: 7, token: tokenIllegal, text: `"\xaz"`, err: "illegal numeric escape sequence"},
   320  				{pos: 13, token: tokenEOF},
   321  			},
   322  		},
   323  	} {
   324  		t.Run(testcase.name, func(t *testing.T) {
   325  			var sc scanner
   326  			sc.init(testcase.input)
   327  
   328  			// If you leave the expected empty, the test case will just print
   329  			// out the token stream, which you can paste into the testcase when
   330  			// adding new cases.
   331  			if len(testcase.expected) == 0 {
   332  				fmt.Println("Name", testcase.name)
   333  			}
   334  
   335  			for i := 0; ; i++ {
   336  				pos, tok, s := sc.scan()
   337  				if len(testcase.expected) == 0 {
   338  					if len(s) > 0 {
   339  						fmt.Printf("{pos: %v, token: %#v, text: %q},\n", pos, tok, s)
   340  					} else {
   341  						fmt.Printf("{pos: %v, token: %#v},\n", pos, tok)
   342  					}
   343  				} else {
   344  					tokv := tokenResult{pos: pos, token: tok, text: s}
   345  					if i >= len(testcase.expected) {
   346  						t.Fatalf("too many tokens parsed")
   347  					}
   348  					if tok == tokenIllegal {
   349  						tokv.err = sc.err
   350  					}
   351  
   352  					if tokv != testcase.expected[i] {
   353  						t.Fatalf("token unexpected: %v != %v", tokv, testcase.expected[i])
   354  					}
   355  				}
   356  
   357  				if tok == tokenEOF {
   358  					break
   359  				}
   360  
   361  			}
   362  
   363  			// make sure we've eof'd
   364  			_, tok, _ := sc.scan()
   365  			if tok != tokenEOF {
   366  				t.Fatal("must consume all input")
   367  			}
   368  
   369  			if len(testcase.expected) == 0 {
   370  				t.Fatal("must define expected tokens")
   371  			}
   372  		})
   373  	}
   374  }