bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/bosun/expr/parse/lex_test.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package parse 6 7 import ( 8 "fmt" 9 "testing" 10 ) 11 12 // Make the types prettyprint. 13 var itemName = map[itemType]string{ 14 itemError: "error", 15 itemEOF: "EOF", 16 itemNot: "!", 17 itemAnd: "&&", 18 itemOr: "||", 19 itemGreater: ">", 20 itemLess: "<", 21 itemGreaterEq: ">=", 22 itemLessEq: "<=", 23 itemEq: "==", 24 itemNotEq: "!=", 25 itemPlus: "+", 26 itemMinus: "-", 27 itemMult: "*", 28 itemDiv: "/", 29 itemMod: "%", 30 itemNumber: "number", 31 itemComma: ",", 32 itemLeftParen: "(", 33 itemRightParen: ")", 34 itemString: "string", 35 itemFunc: "func", 36 } 37 38 func (i itemType) String() string { 39 s := itemName[i] 40 if s == "" { 41 return fmt.Sprintf("item%d", int(i)) 42 } 43 return s 44 } 45 46 type lexTest struct { 47 name string 48 input string 49 items []item 50 } 51 52 var ( 53 tEOF = item{itemEOF, 0, ""} 54 tLpar = item{itemLeftParen, 0, "("} 55 tRpar = item{itemRightParen, 0, ")"} 56 tComma = item{itemComma, 0, ","} 57 tLt = item{itemLess, 0, "<"} 58 tGt = item{itemGreater, 0, ">"} 59 tOr = item{itemOr, 0, "||"} 60 tNot = item{itemNot, 0, "!"} 61 tAnd = item{itemAnd, 0, "&&"} 62 tLtEq = item{itemLessEq, 0, "<="} 63 tGtEq = item{itemGreaterEq, 0, ">="} 64 tNotEq = item{itemNotEq, 0, "!="} 65 tEq = item{itemEq, 0, "=="} 66 tPlus = item{itemPlus, 0, "+"} 67 tMinus = item{itemMinus, 0, "-"} 68 tMult = item{itemMult, 0, "*"} 69 tDiv = item{itemDiv, 0, "/"} 70 tMod = item{itemMod, 0, "%"} 71 ) 72 73 var lexTests = []lexTest{ 74 {"empty", "", []item{tEOF}}, 75 {"spaces", " \t\n", []item{tEOF}}, 76 {"text", `"now is the time"`, []item{{itemString, 0, `"now is the time"`}, tEOF}}, 77 {"operators", "! && || < > <= >= == != + - * / %", []item{ 78 tNot, 79 tAnd, 80 tOr, 81 tLt, 82 tGt, 83 tLtEq, 84 tGtEq, 85 tEq, 86 tNotEq, 87 tPlus, 88 tMinus, 89 tMult, 90 tDiv, 91 tMod, 92 tEOF, 93 }}, 94 {"numbers", "1 02 0x14 7.2 1e3 1.2e-4", []item{ 95 {itemNumber, 0, "1"}, 96 {itemNumber, 0, "02"}, 97 {itemNumber, 0, "0x14"}, 98 {itemNumber, 0, "7.2"}, 99 {itemNumber, 0, "1e3"}, 100 {itemNumber, 0, "1.2e-4"}, 101 tEOF, 102 }}, 103 {"expression", `avg(q("sum:sys.cpu.user{host=*-web*}", "1m")) < 0.2 || avg(q("sum:sys.cpu.user{host=*-web*}", "1m")) > 0.4`, []item{ 104 {itemFunc, 0, "avg"}, 105 tLpar, 106 {itemFunc, 0, "q"}, 107 tLpar, 108 {itemString, 0, `"sum:sys.cpu.user{host=*-web*}"`}, 109 tComma, 110 {itemString, 0, `"1m"`}, 111 tRpar, 112 tRpar, 113 tLt, 114 {itemNumber, 0, "0.2"}, 115 tOr, 116 {itemFunc, 0, "avg"}, 117 tLpar, 118 {itemFunc, 0, "q"}, 119 tLpar, 120 {itemString, 0, `"sum:sys.cpu.user{host=*-web*}"`}, 121 tComma, 122 {itemString, 0, `"1m"`}, 123 tRpar, 124 tRpar, 125 tGt, 126 {itemNumber, 0, "0.4"}, 127 tEOF, 128 }}, 129 {"simple triple quote", `'''select'''`, []item{ 130 {itemTripleQuotedString, 0, `'''select'''`}, 131 tEOF, 132 }}, 133 {"expression with triple quotes", `influx("db", '''select value from "mymetric.name.with.dots" where "key" = 'single quoted value' and "other_key" = '' group by *''', "1h", "")`, []item{ 134 {itemFunc, 0, "influx"}, 135 tLpar, 136 {itemString, 0, `"db"`}, 137 tComma, 138 {itemTripleQuotedString, 0, `'''select value from "mymetric.name.with.dots" where "key" = 'single quoted value' and "other_key" = '' group by *'''`}, 139 tComma, 140 {itemString, 0, `"1h"`}, 141 tComma, 142 {itemString, 0, `""`}, 143 tRpar, 144 tEOF, 145 }}, 146 // errors 147 {"unclosed quote", "\"", []item{ 148 {itemError, 0, "unterminated string"}, 149 }}, 150 {"single quote", "'single quote is invalid'", []item{ 151 {itemError, 0, "invalid start of string, must use double qutoes or triple single quotes"}, 152 }}, 153 {"unclosed triple quote", "''' unclosed triple quote ''", []item{ 154 {itemError, 0, "unterminated string"}, 155 }}, 156 } 157 158 // collect gathers the emitted items into a slice. 159 func collect(t *lexTest) (items []item) { 160 l := lex(t.input) 161 for { 162 item := l.nextItem() 163 items = append(items, item) 164 if item.typ == itemEOF || item.typ == itemError { 165 break 166 } 167 } 168 return 169 } 170 171 func equal(i1, i2 []item, checkPos bool) bool { 172 if len(i1) != len(i2) { 173 return false 174 } 175 for k := range i1 { 176 if i1[k].typ != i2[k].typ { 177 return false 178 } 179 if i1[k].val != i2[k].val { 180 return false 181 } 182 if checkPos && i1[k].pos != i2[k].pos { 183 return false 184 } 185 } 186 return true 187 } 188 189 func TestLex(t *testing.T) { 190 for _, test := range lexTests { 191 items := collect(&test) 192 if !equal(items, test.items, false) { 193 t.Errorf("%s: got\n\t%+v\nexpected\n\t%v", test.name, items, test.items) 194 } 195 } 196 } 197 198 /* 199 var lexPosTests = []lexTest{ 200 {"empty", "", []item{tEOF}}, 201 {"punctuation", "{{,@%#}}", []item{ 202 {itemLeftDelim, 0, "{{"}, 203 {itemChar, 2, ","}, 204 {itemChar, 3, "@"}, 205 {itemChar, 4, "%"}, 206 {itemChar, 5, "#"}, 207 {itemRightDelim, 6, "}}"}, 208 {itemEOF, 8, ""}, 209 }}, 210 {"sample", "0123{{hello}}xyz", []item{ 211 {itemText, 0, "0123"}, 212 {itemLeftDelim, 4, "{{"}, 213 {itemIdentifier, 6, "hello"}, 214 {itemRightDelim, 11, "}}"}, 215 {itemText, 13, "xyz"}, 216 {itemEOF, 16, ""}, 217 }}, 218 } 219 220 // The other tests don't check position, to make the test cases easier to construct. 221 // This one does. 222 func TestPos(t *testing.T) { 223 for _, test := range lexPosTests { 224 items := collect(&test, "", "") 225 if !equal(items, test.items, true) { 226 t.Errorf("%s: got\n\t%v\nexpected\n\t%v", test.name, items, test.items) 227 if len(items) == len(test.items) { 228 // Detailed print; avoid item.String() to expose the position value. 229 for i := range items { 230 if !equal(items[i:i+1], test.items[i:i+1], true) { 231 i1 := items[i] 232 i2 := test.items[i] 233 t.Errorf("\t#%d: got {%v %d %q} expected {%v %d %q}", i, i1.typ, i1.pos, i1.val, i2.typ, i2.pos, i2.val) 234 } 235 } 236 } 237 } 238 } 239 } 240 */