306 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			306 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package filters
 | 
						|
 | 
						|
import (
 | 
						|
	"fmt"
 | 
						|
	"testing"
 | 
						|
)
 | 
						|
 | 
						|
type tokenResult struct {
 | 
						|
	pos   int
 | 
						|
	token token
 | 
						|
	text  string
 | 
						|
}
 | 
						|
 | 
						|
func (tr tokenResult) String() string {
 | 
						|
	return fmt.Sprintf("{pos: %v, token: %v, text: %q}", tr.pos, tr.token, tr.text)
 | 
						|
}
 | 
						|
 | 
						|
func TestScanner(t *testing.T) {
 | 
						|
 | 
						|
	for _, testcase := range []struct {
 | 
						|
		name     string
 | 
						|
		input    string
 | 
						|
		expected []tokenResult
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			name:  "Field",
 | 
						|
			input: "name",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "name"},
 | 
						|
				{pos: 4, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "SelectorsWithOperators",
 | 
						|
			input: "name==value,foo!=bar",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "name"},
 | 
						|
				{pos: 4, token: tokenOperator, text: "=="},
 | 
						|
				{pos: 6, token: tokenValue, text: "value"},
 | 
						|
				{pos: 11, token: tokenSeparator, text: ","},
 | 
						|
				{pos: 12, token: tokenField, text: "foo"},
 | 
						|
				{pos: 15, token: tokenOperator, text: "!="},
 | 
						|
				{pos: 17, token: tokenValue, text: "bar"},
 | 
						|
				{pos: 20, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "SelectorsWithFieldPaths",
 | 
						|
			input: "name==value,labels.foo=value,other.bar~=match",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "name"},
 | 
						|
				{pos: 4, token: tokenOperator, text: "=="},
 | 
						|
				{pos: 6, token: tokenValue, text: "value"},
 | 
						|
				{pos: 11, token: tokenSeparator, text: ","},
 | 
						|
				{pos: 12, token: tokenField, text: "labels"},
 | 
						|
				{pos: 18, token: tokenSeparator, text: "."},
 | 
						|
				{pos: 19, token: tokenField, text: "foo"},
 | 
						|
				{pos: 22, token: tokenOperator, text: "="},
 | 
						|
				{pos: 23, token: tokenValue, text: "value"},
 | 
						|
				{pos: 28, token: tokenSeparator, text: ","},
 | 
						|
				{pos: 29, token: tokenField, text: "other"},
 | 
						|
				{pos: 34, token: tokenSeparator, text: "."},
 | 
						|
				{pos: 35, token: tokenField, text: "bar"},
 | 
						|
				{pos: 38, token: tokenOperator, text: "~="},
 | 
						|
				{pos: 40, token: tokenValue, text: "match"},
 | 
						|
				{pos: 45, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "RegexpValue",
 | 
						|
			input: "name~=[abc]+,foo=test",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "name"},
 | 
						|
				{pos: 4, token: tokenOperator, text: "~="},
 | 
						|
				{pos: 6, token: tokenValue, text: "[abc]+"},
 | 
						|
				{pos: 12, token: tokenSeparator, text: ","},
 | 
						|
				{pos: 13, token: tokenField, text: "foo"},
 | 
						|
				{pos: 16, token: tokenOperator, text: "="},
 | 
						|
				{pos: 17, token: tokenValue, text: "test"},
 | 
						|
				{pos: 21, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "RegexpEscapedValue",
 | 
						|
			input: `name~=[abc]\+,foo=test`,
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "name"},
 | 
						|
				{pos: 4, token: tokenOperator, text: "~="},
 | 
						|
				{pos: 6, token: tokenValue, text: "[abc]\\+"},
 | 
						|
				{pos: 13, token: tokenSeparator, text: ","},
 | 
						|
				{pos: 14, token: tokenField, text: "foo"},
 | 
						|
				{pos: 17, token: tokenOperator, text: "="},
 | 
						|
				{pos: 18, token: tokenValue, text: "test"},
 | 
						|
				{pos: 22, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "RegexpQuotedValue",
 | 
						|
			input: `name~=/[abc]{0,2}/,foo=test`,
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "name"},
 | 
						|
				{pos: 4, token: tokenOperator, text: "~="},
 | 
						|
				{pos: 6, token: tokenQuoted, text: "/[abc]{0,2}/"},
 | 
						|
				{pos: 18, token: tokenSeparator, text: ","},
 | 
						|
				{pos: 19, token: tokenField, text: "foo"},
 | 
						|
				{pos: 22, token: tokenOperator, text: "="},
 | 
						|
				{pos: 23, token: tokenValue, text: "test"},
 | 
						|
				{pos: 27, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "Cowsay",
 | 
						|
			input: "name~=牛,labels.moo=true",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "name"},
 | 
						|
				{pos: 4, token: tokenOperator, text: "~="},
 | 
						|
				{pos: 6, token: tokenValue, text: "牛"},
 | 
						|
				{pos: 9, token: tokenSeparator, text: ","},
 | 
						|
				{pos: 10, token: tokenField, text: "labels"},
 | 
						|
				{pos: 16, token: tokenSeparator, text: "."},
 | 
						|
				{pos: 17, token: tokenField, text: "moo"},
 | 
						|
				{pos: 20, token: tokenOperator, text: "="},
 | 
						|
				{pos: 21, token: tokenValue, text: "true"},
 | 
						|
				{pos: 25, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "CowsayRegexpQuoted",
 | 
						|
			input: "name~=|牛|,labels.moo=true",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "name"},
 | 
						|
				{pos: 4, token: tokenOperator, text: "~="},
 | 
						|
				{pos: 6, token: tokenQuoted, text: "|牛|"},
 | 
						|
				{pos: 11, token: tokenSeparator, text: ","},
 | 
						|
				{pos: 12, token: tokenField, text: "labels"},
 | 
						|
				{pos: 18, token: tokenSeparator, text: "."},
 | 
						|
				{pos: 19, token: tokenField, text: "moo"},
 | 
						|
				{pos: 22, token: tokenOperator, text: "="},
 | 
						|
				{pos: 23, token: tokenValue, text: "true"},
 | 
						|
				{pos: 27, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "Escapes",
 | 
						|
			input: `name~="asdf\n\tfooo"`,
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "name"},
 | 
						|
				{pos: 4, token: tokenOperator, text: "~="},
 | 
						|
				{pos: 6, token: tokenQuoted, text: "\"asdf\\n\\tfooo\""},
 | 
						|
				{pos: 20, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "NullInput",
 | 
						|
			input: "foo\x00bar",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "foo"},
 | 
						|
				{pos: 3, token: tokenIllegal},
 | 
						|
				{pos: 4, token: tokenField, text: "bar"},
 | 
						|
				{pos: 7, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "SpacesChomped",
 | 
						|
			input: "foo = bar    ",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "foo"},
 | 
						|
				{pos: 4, token: tokenOperator, text: "="},
 | 
						|
				{pos: 6, token: tokenValue, text: "bar"},
 | 
						|
				{pos: 13, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "ValuesPunctauted",
 | 
						|
			input: "compound.labels==punctuated_value.foo-bar",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "compound"},
 | 
						|
				{pos: 8, token: tokenSeparator, text: "."},
 | 
						|
				{pos: 9, token: tokenField, text: "labels"},
 | 
						|
				{pos: 15, token: tokenOperator, text: "=="},
 | 
						|
				{pos: 17, token: tokenValue, text: "punctuated_value.foo-bar"},
 | 
						|
				{pos: 41, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "PartialInput",
 | 
						|
			input: "interrupted=",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "interrupted"},
 | 
						|
				{pos: 11, token: tokenOperator, text: "="},
 | 
						|
				{pos: 12, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "DoubleValue",
 | 
						|
			input: "doublevalue=value value",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "doublevalue"},
 | 
						|
				{pos: 11, token: tokenOperator, text: "="},
 | 
						|
				{pos: 12, token: tokenValue, text: "value"},
 | 
						|
				{pos: 18, token: tokenField, text: "value"},
 | 
						|
				{pos: 23, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "LeadingWithQuoted",
 | 
						|
			input: `"leading quote".postquote==value`,
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenQuoted, text: "\"leading quote\""},
 | 
						|
				{pos: 15, token: tokenSeparator, text: "."},
 | 
						|
				{pos: 16, token: tokenField, text: "postquote"},
 | 
						|
				{pos: 25, token: tokenOperator, text: "=="},
 | 
						|
				{pos: 27, token: tokenValue, text: "value"},
 | 
						|
				{pos: 32, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "MissingValue",
 | 
						|
			input: "input==,id!=ff",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "input"},
 | 
						|
				{pos: 5, token: tokenOperator, text: "=="},
 | 
						|
				{pos: 7, token: tokenSeparator, text: ","},
 | 
						|
				{pos: 8, token: tokenField, text: "id"},
 | 
						|
				{pos: 10, token: tokenOperator, text: "!="},
 | 
						|
				{pos: 12, token: tokenValue, text: "ff"},
 | 
						|
				{pos: 14, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "QuotedRegexp",
 | 
						|
			input: "input~=/foo\\/bar/,id!=ff",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "input"},
 | 
						|
				{pos: 5, token: tokenOperator, text: "~="},
 | 
						|
				{pos: 7, token: tokenQuoted, text: "/foo\\/bar/"},
 | 
						|
				{pos: 17, token: tokenSeparator, text: ","},
 | 
						|
				{pos: 18, token: tokenField, text: "id"},
 | 
						|
				{pos: 20, token: tokenOperator, text: "!="},
 | 
						|
				{pos: 22, token: tokenValue, text: "ff"},
 | 
						|
				{pos: 24, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
		{
 | 
						|
			name:  "QuotedRegexpAlt",
 | 
						|
			input: "input~=|foo/bar|,id!=ff",
 | 
						|
			expected: []tokenResult{
 | 
						|
				{pos: 0, token: tokenField, text: "input"},
 | 
						|
				{pos: 5, token: tokenOperator, text: "~="},
 | 
						|
				{pos: 7, token: tokenQuoted, text: "|foo/bar|"},
 | 
						|
				{pos: 16, token: tokenSeparator, text: ","},
 | 
						|
				{pos: 17, token: tokenField, text: "id"},
 | 
						|
				{pos: 19, token: tokenOperator, text: "!="},
 | 
						|
				{pos: 21, token: tokenValue, text: "ff"},
 | 
						|
				{pos: 23, token: tokenEOF},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	} {
 | 
						|
		t.Run(testcase.name, func(t *testing.T) {
 | 
						|
			var sc scanner
 | 
						|
			sc.init(testcase.input)
 | 
						|
 | 
						|
			// If you leave the expected empty, the test case will just print
 | 
						|
			// out the token stream, which you can paste into the testcase when
 | 
						|
			// adding new cases.
 | 
						|
			if len(testcase.expected) == 0 {
 | 
						|
				fmt.Println("Name", testcase.name)
 | 
						|
			}
 | 
						|
 | 
						|
			for i := 0; ; i++ {
 | 
						|
				pos, tok, s := sc.scan()
 | 
						|
				if len(testcase.expected) == 0 {
 | 
						|
					if len(s) > 0 {
 | 
						|
						fmt.Printf("{pos: %v, token: %#v, text: %q},\n", pos, tok, s)
 | 
						|
					} else {
 | 
						|
						fmt.Printf("{pos: %v, token: %#v},\n", pos, tok)
 | 
						|
					}
 | 
						|
				} else {
 | 
						|
					tokv := tokenResult{pos: pos, token: tok, text: s}
 | 
						|
					if i >= len(testcase.expected) {
 | 
						|
						t.Fatalf("too many tokens parsed")
 | 
						|
					}
 | 
						|
 | 
						|
					if tokv != testcase.expected[i] {
 | 
						|
						t.Fatalf("token unexpected: %v != %v", tokv, testcase.expected[i])
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if tok == tokenEOF {
 | 
						|
					break
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			// make sure we've eof'd
 | 
						|
			_, tok, _ := sc.scan()
 | 
						|
			if tok != tokenEOF {
 | 
						|
				t.Fatal("must consume all input")
 | 
						|
			}
 | 
						|
 | 
						|
			if len(testcase.expected) == 0 {
 | 
						|
				t.Fatal("must define expected tokens")
 | 
						|
			}
 | 
						|
		})
 | 
						|
	}
 | 
						|
}
 |