github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/query/graphite/lexer/lexer_test.go (about) 1 // Copyright (c) 2019 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 lexer 22 23 import ( 24 "testing" 25 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 ) 29 30 type lexerTestData struct { 31 input string 32 expectedTokens []Token 33 reservedIdents map[string]TokenType 34 } 35 36 func TestLexer(t *testing.T) { 37 lexerTestInput := []lexerTestData{ 38 {"call09", []Token{{Identifier, "call09"}}, nil}, 39 {"sortByName(foo.bar.zed)", []Token{ 40 {Identifier, "sortByName"}, 41 {LParenthesis, "("}, 42 {Pattern, "foo.bar.zed"}, 43 {RParenthesis, ")"}}, nil}, 44 {"foo.'bar<1001>'.baz", 45 []Token{{Pattern, "foo.'bar<1001>'.baz"}}, nil}, 46 {"a.{b,c,d}.node[0-2].qux.*", 47 []Token{{Pattern, "a.{b,c,d}.node[0-2].qux.*"}}, nil}, 48 {"qaz{0[3-9],1[0-9],20}", 49 []Token{{Pattern, "qaz{0[3-9],1[0-9],20}"}}, nil}, 50 {"qaz{0[3-9],1[0-9],20}.bar", 51 []Token{{Pattern, "qaz{0[3-9],1[0-9],20}.bar"}}, nil}, 52 {"stats.foo.counts.bar.baz.status_code.?XX", 53 []Token{{Pattern, "stats.foo.counts.bar.baz.status_code.?XX"}}, nil}, 54 {"456.03", []Token{{Number, "456.03"}}, nil}, 55 {"foo:bar~baz", []Token{{Identifier, "foo:bar~baz"}}, nil}, 56 {"foo:bar", []Token{ 57 {Identifier, "foo:bar"}}, nil}, 58 {"foo:bar baz:q*x", []Token{ 59 {Identifier, "foo:bar"}, 60 {Pattern, "baz:q*x"}}, nil}, 61 {"!service:dispatch.*", []Token{ 62 {NotOperator, "!"}, 63 {Pattern, "service:dispatch.*"}}, nil}, 64 {"\"Whatever man\"", []Token{{String, "Whatever man"}}, nil}, // double quoted string 65 // no need to escape single quotes within double quoted string 66 {"\"Whatever 'man'\"", []Token{{String, "Whatever 'man'"}}, nil}, 67 // escape double quotes within double quoted string 68 {`"Whatever \"man\""`, []Token{{String, "Whatever \"man\""}}, nil}, 69 {"'Whatever man'", []Token{{String, "Whatever man"}}, nil}, // single quoted string 70 // no need to escape double quote within single quoted strings (start boundary), but may 71 // do it if so desired (end boundary) 72 {`'Whatever "man\"'`, []Token{{String, "Whatever \"man\\\""}}, nil}, 73 {" call09(a.{b,c,d}.node[0-2].qux.*, a{e,g}, 4546.abc, 45ahj, " + 74 `"Hello there \"Good \ Sir\" ", +20, 39540.459,-349845,.393) `, 75 []Token{ 76 {Identifier, "call09"}, 77 {LParenthesis, "("}, 78 {Pattern, "a.{b,c,d}.node[0-2].qux.*"}, 79 {Comma, ","}, 80 {Pattern, "a{e,g}"}, 81 {Comma, ","}, 82 {Pattern, "4546.abc"}, 83 {Comma, ","}, 84 {Pattern, "45ahj"}, 85 {Comma, ","}, 86 {String, "Hello there \"Good \\ Sir\" "}, 87 {Comma, ","}, 88 {Number, "+20"}, 89 {Comma, ","}, 90 {Number, "39540.459"}, 91 {Comma, ","}, 92 {Number, "-349845"}, 93 {Comma, ","}, 94 {Number, ".393"}, 95 {RParenthesis, ")"}}, nil}, 96 {`aliasSub(stats.foo.timers.scream.scream.views.quz.end_to_end_latency.p95, ` + 97 `'stats.(.*).timers.scream.scream.views.quz.(.*)', '\1.\2')`, 98 []Token{ 99 {Identifier, "aliasSub"}, 100 {LParenthesis, "("}, 101 {Pattern, "stats.foo.timers.scream.scream.views.quz.end_to_end_latency.p95"}, 102 {Comma, ","}, 103 {String, "stats.(.*).timers.scream.scream.views.quz.(.*)"}, 104 {Comma, ","}, 105 {String, `\1.\2`}, 106 {RParenthesis, ")"}}, nil}, 107 {"sumSeries(stats.with:colon)", 108 []Token{ 109 {Identifier, "sumSeries"}, 110 {LParenthesis, "("}, 111 {Pattern, "stats.with:colon"}, 112 {RParenthesis, ")"}, 113 }, 114 nil}, 115 {"sumSeries(stats.with:colon.extended.pattern.*)", 116 []Token{ 117 {Identifier, "sumSeries"}, 118 {LParenthesis, "("}, 119 {Pattern, "stats.with:colon.extended.pattern.*"}, 120 {RParenthesis, ")"}, 121 }, 122 nil}, 123 } 124 125 for _, test := range lexerTestInput { 126 lex, tokens := NewLexer(test.input, test.reservedIdents, Options{}) 127 go lex.Run() 128 129 i := 0 130 for token := range tokens { 131 require.True(t, i < len(test.expectedTokens), 132 "received more tokens than expected for %s", test.input) 133 assert.Equal(t, &test.expectedTokens[i], token, "incorrect token %d for %s", 134 i, test.input) 135 i++ 136 } 137 138 assert.True(t, lex.eof(), "did not reach eof for %s", test.input) 139 } 140 } 141 142 func TestLexerErrors(t *testing.T) { 143 badLines := [][]string{ 144 {"\"this is \\\"unterminated", 145 "reached end of input while processing string \"this is \\\"unterminated"}, 146 {"ajs.djd]", // group closed without open 147 "encountered unbalanced end of group ] in pattern ajs.djd]"}, 148 {"{dfklf]", // mismatch between open and close of group 149 "encountered unbalanced end of group ] in pattern {dfklf]"}, 150 {"{[ajs.djd}]", // flipped close groups 151 "encountered unbalanced end of group } in pattern {[ajs.djd}"}, 152 {"{unclosed", "end of pattern {unclosed reached while still in group {"}, // unclosed group 153 {"+a", "expected one of 0123456789, found a"}, // plus with no number 154 {"-b", "expected one of 0123456789, found b"}, // minus with no number 155 {".c", "expected one of 0123456789, found c"}, // digit with no number 156 {"^", "unexpected character ^"}, // unknown symbol 157 } 158 159 for _, badLine := range badLines { 160 l, tokens := NewLexer(badLine[0], nil, Options{}) 161 go l.Run() 162 163 expected := &Token{Error, badLine[1]} 164 actual := <-tokens 165 assert.Equal(t, expected, actual, "%s did not result in error", badLine[0]) 166 167 // Should have closed the channel after the error 168 actual = <-tokens 169 assert.Nil(t, actual) 170 } 171 }