go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/mqlc/parser/parser_test.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package parser 5 6 import ( 7 "testing" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 "go.mondoo.com/cnquery/logger" 12 ) 13 14 func init() { 15 logger.InitTestEnv() 16 } 17 18 func TestParser_Lex(t *testing.T) { 19 tests := []struct { 20 typ rune 21 str string 22 }{ 23 {Ident, "name"}, 24 {Float, "1.23"}, 25 {Int, "123"}, 26 {String, "'hi'"}, 27 {String, "\"hi\""}, 28 {Regex, "/regex/"}, 29 {Op, "+"}, 30 } 31 for i := range tests { 32 res, err := Lex(tests[i].str) 33 assert.Nil(t, err) 34 assert.Equal(t, tests[i].typ, res[0].Type) 35 } 36 } 37 38 func sPtr(s string) *string { 39 return &s 40 } 41 42 func vBool(b bool) *Value { 43 return &Value{Bool: &b} 44 } 45 46 func vIdent(v string) *Value { 47 return &Value{Ident: &v} 48 } 49 50 func vFloat(v float64) *Value { 51 return &Value{Float: &v} 52 } 53 54 func vInt(v int64) *Value { 55 return &Value{Int: &v} 56 } 57 58 func vString(v string) *Value { 59 return &Value{String: &v} 60 } 61 62 func vRegex(v string) *Value { 63 return &Value{Regex: &v} 64 } 65 66 func vMap(v map[string]*Expression) *Value { 67 return &Value{Map: v} 68 } 69 70 func callIdent(ident string) *Call { 71 return &Call{Ident: &ident} 72 } 73 74 type parserTest struct { 75 code string 76 res *Expression 77 } 78 79 func runParserTests(t *testing.T, tests []parserTest) { 80 for i := range tests { 81 test := tests[i] 82 83 t.Run(test.code, func(t *testing.T) { 84 res, err := Parse(test.code) 85 require.NoError(t, err, "parsing should not generate an error") 86 require.NotNil(t, res, "parsing must generate a result value") 87 require.NotEmpty(t, res.Expressions, "parsing must generate one expression") 88 89 assert.Equal(t, test.res, res.Expressions[0]) 90 }) 91 } 92 } 93 94 type multiTest struct { 95 code string 96 res []*Expression 97 } 98 99 func runMultiTest(t *testing.T, tests []multiTest) { 100 for i := range tests { 101 test := tests[i] 102 103 t.Run(test.code, func(t *testing.T) { 104 res, err := Parse(test.code) 105 require.NoError(t, err, "parsing should not generate an error") 106 require.NotNil(t, res, "parsing must generate a result value") 107 assert.Equal(t, test.res, res.Expressions, "resulting expressions must match") 108 }) 109 } 110 } 111 112 func TestParser_ParseValues(t *testing.T) { 113 runParserTests(t, []parserTest{ 114 {"null", &Expression{Operand: &Operand{Value: &nilValue}}}, 115 {"NaN", &Expression{Operand: &Operand{Value: &nanValue}}}, 116 {"Infinity", &Expression{Operand: &Operand{Value: &infinityValue}}}, 117 {"Never", &Expression{Operand: &Operand{Value: &neverValue}}}, 118 {"true", &Expression{Operand: &Operand{Value: vBool(true)}}}, 119 {"false", &Expression{Operand: &Operand{Value: vBool(false)}}}, 120 {"name", &Expression{Operand: &Operand{Value: vIdent("name")}}}, 121 {"1.23", &Expression{Operand: &Operand{Value: vFloat(1.23)}}}, 122 {"123", &Expression{Operand: &Operand{Value: vInt(123)}}}, 123 {"'hi'", &Expression{Operand: &Operand{Value: vString("hi")}}}, 124 {"'h\\ni'", &Expression{Operand: &Operand{Value: vString("h\\ni")}}}, 125 {"'h\\i'", &Expression{Operand: &Operand{Value: vString("h\\i")}}}, 126 {"\"hi\"", &Expression{Operand: &Operand{Value: vString("hi")}}}, 127 {"\"h\\ni\"", &Expression{Operand: &Operand{Value: vString("h\ni")}}}, 128 {"\"h\\i\"", &Expression{Operand: &Operand{Value: vString("hi")}}}, 129 {"/hi/", &Expression{Operand: &Operand{Value: vRegex("hi")}}}, 130 {"[]", &Expression{Operand: &Operand{Value: &Value{Array: []*Expression{}}}}}, 131 {"[1]", &Expression{Operand: &Operand{Value: &Value{Array: []*Expression{ 132 {Operand: &Operand{Value: vInt(1)}}, 133 }}}}}, 134 {"[1,2.3]", &Expression{Operand: &Operand{Value: &Value{Array: []*Expression{ 135 {Operand: &Operand{Value: vInt(1)}}, 136 {Operand: &Operand{Value: vFloat(2.3)}}, 137 }}}}}, 138 {"[1,2,]", &Expression{Operand: &Operand{Value: &Value{Array: []*Expression{ 139 {Operand: &Operand{Value: vInt(1)}}, 140 {Operand: &Operand{Value: vInt(2)}}, 141 }}}}}, 142 {"{}", &Expression{Operand: &Operand{Value: vMap(map[string]*Expression{})}}}, 143 {"{'a': 'word'}", &Expression{Operand: &Operand{Value: vMap(map[string]*Expression{ 144 "a": {Operand: &Operand{Value: vString("word")}}, 145 })}}}, 146 {"{\"b\": \"there\"}", &Expression{Operand: &Operand{Value: vMap(map[string]*Expression{ 147 "b": {Operand: &Operand{Value: vString("there")}}, 148 })}}}, 149 {"{c: 123}", &Expression{Operand: &Operand{Value: vMap(map[string]*Expression{ 150 "c": {Operand: &Operand{Value: vInt(123)}}, 151 })}}}, 152 {"{a: 1, b: 2,}", &Expression{Operand: &Operand{Value: vMap(map[string]*Expression{ 153 "a": {Operand: &Operand{Value: vInt(1)}}, 154 "b": {Operand: &Operand{Value: vInt(2)}}, 155 })}}}, 156 {"name.last", &Expression{Operand: &Operand{ 157 Value: vIdent("name"), 158 Calls: []*Call{callIdent("last")}, 159 }}}, 160 {"name[1]", &Expression{Operand: &Operand{ 161 Value: vIdent("name"), 162 Calls: []*Call{{Accessor: &Expression{Operand: &Operand{Value: vInt(1)}}}}, 163 }}}, 164 {"name()", &Expression{Operand: &Operand{ 165 Value: vIdent("name"), 166 Calls: []*Call{{Function: []*Arg{}}}, 167 }}}, 168 {"name(1)", &Expression{Operand: &Operand{ 169 Value: vIdent("name"), 170 Calls: []*Call{{Function: []*Arg{ 171 {Value: &Expression{Operand: &Operand{Value: vInt(1)}}}, 172 }}}, 173 }}}, 174 {"name(arg)", &Expression{Operand: &Operand{ 175 Value: vIdent("name"), 176 Calls: []*Call{{Function: []*Arg{ 177 {Value: &Expression{Operand: &Operand{Value: vIdent("arg")}}}, 178 }}}, 179 }}}, 180 {"name(uid: 1)", &Expression{Operand: &Operand{ 181 Value: vIdent("name"), 182 Calls: []*Call{{Function: []*Arg{ 183 {Name: "uid", Value: &Expression{Operand: &Operand{Value: vInt(1)}}}, 184 }}}, 185 }}}, 186 {"a(b(c,d))", &Expression{Operand: &Operand{ 187 Value: vIdent("a"), 188 Calls: []*Call{{Function: []*Arg{ 189 {Value: &Expression{Operand: &Operand{ 190 Value: vIdent("b"), 191 Calls: []*Call{{Function: []*Arg{ 192 {Value: &Expression{Operand: &Operand{Value: vIdent("c")}}}, 193 {Value: &Expression{Operand: &Operand{Value: vIdent("d")}}}, 194 }}}, 195 }}}, 196 }}}, 197 }}}, 198 {"a(\nb(\nc,\nd\n)\n)", &Expression{Operand: &Operand{ 199 Value: vIdent("a"), 200 Calls: []*Call{{Function: []*Arg{ 201 {Value: &Expression{Operand: &Operand{ 202 Value: vIdent("b"), 203 Calls: []*Call{{Function: []*Arg{ 204 {Value: &Expression{Operand: &Operand{Value: vIdent("c")}}}, 205 {Value: &Expression{Operand: &Operand{Value: vIdent("d")}}}, 206 }}}, 207 }}}, 208 }}}, 209 }}}, 210 {"user { name uid }", &Expression{Operand: &Operand{ 211 Value: vIdent("user"), 212 Block: []*Expression{ 213 {Operand: &Operand{Value: vIdent("name")}}, 214 {Operand: &Operand{Value: vIdent("uid")}}, 215 }, 216 }}}, 217 {"user {\n name\n uid\n}", &Expression{Operand: &Operand{ 218 Value: vIdent("user"), 219 Block: []*Expression{ 220 {Operand: &Operand{Value: vIdent("name")}}, 221 {Operand: &Operand{Value: vIdent("uid")}}, 222 }, 223 }}}, 224 {"users.list { uid }", &Expression{Operand: &Operand{ 225 Value: vIdent("users"), 226 Calls: []*Call{callIdent("list")}, 227 Block: []*Expression{ 228 {Operand: &Operand{Value: vIdent("uid")}}, 229 }, 230 }}}, 231 {"users.where()", &Expression{Operand: &Operand{ 232 Value: vIdent("users"), 233 Calls: []*Call{ 234 callIdent("where"), 235 {Function: []*Arg{}}, 236 }, 237 }}}, 238 {"users.where(uid > 2).list { uid }", &Expression{Operand: &Operand{ 239 Value: vIdent("users"), 240 Calls: []*Call{ 241 callIdent("where"), 242 {Function: []*Arg{{Value: &Expression{ 243 Operand: &Operand{Value: vIdent("uid")}, 244 Operations: []*Operation{{ 245 Operator: OpGreater, 246 Operand: &Operand{Value: vInt(2)}, 247 }}, 248 }}}}, 249 callIdent("list"), 250 }, 251 Block: []*Expression{ 252 {Operand: &Operand{Value: vIdent("uid")}}, 253 }, 254 }}}, 255 {"1 + 2 == 3", &Expression{ 256 Operand: &Operand{Value: vInt(1)}, 257 Operations: []*Operation{ 258 {Operator: OpAdd, Operand: &Operand{Value: vInt(2)}}, 259 {Operator: OpEqual, Operand: &Operand{Value: vInt(3)}}, 260 }, 261 }}, 262 {"1 && 2 || 3", &Expression{ 263 Operand: &Operand{Value: vInt(1)}, 264 Operations: []*Operation{ 265 {Operator: OpAnd, Operand: &Operand{Value: vInt(2)}}, 266 {Operator: OpOr, Operand: &Operand{Value: vInt(3)}}, 267 }, 268 }}, 269 {"true + 'some'.length()", &Expression{ 270 Operand: &Operand{Value: vBool(true)}, 271 Operations: []*Operation{ 272 {Operator: OpAdd, Operand: &Operand{ 273 Value: vString("some"), 274 Calls: []*Call{callIdent("length"), {Function: []*Arg{}}}, 275 }}, 276 }, 277 }}, 278 {"// this // is a comment\n'hi'", &Expression{Operand: &Operand{ 279 Value: vString("hi"), 280 Comments: "this // is a comment\n", 281 }}}, 282 {"# this # is a comment\n'hi'", &Expression{Operand: &Operand{ 283 Value: vString("hi"), 284 Comments: "this # is a comment\n", 285 }}}, 286 }) 287 } 288 289 func TestParser_Comments(t *testing.T) { 290 runMultiTest(t, []multiTest{ 291 // empty comments 292 {"#", []*Expression(nil)}, 293 {"//", []*Expression(nil)}, 294 // call chain with many newlines 295 {"// 1\nsshd\n// 2\n\t.\n// 3\nconfig // 4", []*Expression{ 296 {Operand: &Operand{ 297 Comments: "1\n", 298 Value: vIdent("sshd"), 299 Calls: []*Call{ 300 { 301 Comments: "2\n3\n", 302 Ident: sPtr("config"), 303 }, 304 }, 305 }}, 306 {Operand: &Operand{Comments: "4"}}, 307 }}, 308 // blocks and newlines 309 {"file\n// 1\n{\n// 2\npath\n// 3\n==\n// 4\n'abc'\n// 5\n}\n// 6", []*Expression{ 310 {Operand: &Operand{ 311 Value: vIdent("file"), 312 Block: []*Expression{ 313 { 314 Operand: &Operand{ 315 Comments: "1\n2\n", 316 Value: vIdent("path"), 317 }, 318 Operations: []*Operation{{ 319 Operator: 103, 320 Operand: &Operand{ 321 Comments: "3\n4\n", 322 Value: vString("abc"), 323 }, 324 }}, 325 }, 326 { 327 Operand: &Operand{Comments: "5\n"}, 328 }, 329 }, 330 }}, 331 {Operand: &Operand{Comments: "6"}}, 332 }}, 333 }) 334 } 335 336 func TestParser_Multiline(t *testing.T) { 337 runMultiTest(t, []multiTest{ 338 {"true\n1\n2\n", []*Expression{ 339 {Operand: &Operand{Value: vBool(true)}}, 340 {Operand: &Operand{Value: vInt(1)}}, 341 {Operand: &Operand{Value: vInt(2)}}, 342 }}, 343 }) 344 }