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  }