Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/river/vm/vm_test.go
4096 views
1
package vm_test
2
3
import (
4
"reflect"
5
"strings"
6
"testing"
7
"unicode"
8
9
"github.com/grafana/agent/pkg/river/parser"
10
"github.com/grafana/agent/pkg/river/scanner"
11
"github.com/grafana/agent/pkg/river/token"
12
"github.com/grafana/agent/pkg/river/vm"
13
"github.com/stretchr/testify/require"
14
)
15
16
func TestVM_Evaluate_Literals(t *testing.T) {
17
tt := map[string]struct {
18
input string
19
expect interface{}
20
}{
21
"number to int": {`12`, int(12)},
22
"number to int8": {`13`, int8(13)},
23
"number to int16": {`14`, int16(14)},
24
"number to int32": {`15`, int32(15)},
25
"number to int64": {`16`, int64(16)},
26
"number to uint": {`17`, uint(17)},
27
"number to uint8": {`18`, uint8(18)},
28
"number to uint16": {`19`, uint16(19)},
29
"number to uint32": {`20`, uint32(20)},
30
"number to uint64": {`21`, uint64(21)},
31
"number to float32": {`22`, float32(22)},
32
"number to float64": {`23`, float64(23)},
33
"number to string": {`24`, string("24")},
34
35
"float to float32": {`3.2`, float32(3.2)},
36
"float to float64": {`3.5`, float64(3.5)},
37
"float to string": {`3.9`, string("3.9")},
38
39
"float with dot to float32": {`.2`, float32(0.2)},
40
"float with dot to float64": {`.5`, float64(0.5)},
41
"float with dot to string": {`.9`, string("0.9")},
42
43
"string to string": {`"Hello, world!"`, string("Hello, world!")},
44
"string to int": {`"12"`, int(12)},
45
"string to float64": {`"12"`, float64(12)},
46
}
47
48
for name, tc := range tt {
49
t.Run(name, func(t *testing.T) {
50
expr, err := parser.ParseExpression(tc.input)
51
require.NoError(t, err)
52
53
eval := vm.New(expr)
54
55
vPtr := reflect.New(reflect.TypeOf(tc.expect)).Interface()
56
require.NoError(t, eval.Evaluate(nil, vPtr))
57
58
actual := reflect.ValueOf(vPtr).Elem().Interface()
59
require.Equal(t, tc.expect, actual)
60
})
61
}
62
}
63
64
func TestVM_Evaluate(t *testing.T) {
65
// Shared scope across all tests below
66
scope := &vm.Scope{
67
Variables: map[string]interface{}{
68
"foobar": int(42),
69
},
70
}
71
72
tt := []struct {
73
input string
74
expect interface{}
75
}{
76
// Binops
77
{`true || false`, bool(true)},
78
{`false || false`, bool(false)},
79
{`true && false`, bool(false)},
80
{`true && true`, bool(true)},
81
{`3 == 5`, bool(false)},
82
{`3 == 3`, bool(true)},
83
{`3 != 5`, bool(true)},
84
{`3 < 5`, bool(true)},
85
{`3 <= 5`, bool(true)},
86
{`3 > 5`, bool(false)},
87
{`3 >= 5`, bool(false)},
88
{`3 + 5`, int(8)},
89
{`3 - 5`, int(-2)},
90
{`3 * 5`, int(15)},
91
{`3.0 / 5.0`, float64(0.6)},
92
{`5 % 3`, int(2)},
93
{`3 ^ 5`, int(243)},
94
{`3 + 5 * 2`, int(13)}, // Chain multiple binops
95
{`42.0^-2`, float64(0.0005668934240362812)},
96
97
// Identifier
98
{`foobar`, int(42)},
99
100
// Arrays
101
{`[]`, []int{}},
102
{`[0, 1, 2]`, []int{0, 1, 2}},
103
{`[true, false]`, []bool{true, false}},
104
105
// Objects
106
{`{ a = 5, b = 10 }`, map[string]int{"a": 5, "b": 10}},
107
{
108
input: `{
109
name = "John Doe",
110
age = 42,
111
}`,
112
expect: struct {
113
Name string `river:"name,attr"`
114
Age int `river:"age,attr"`
115
Country string `river:"country,attr,optional"`
116
}{
117
Name: "John Doe",
118
Age: 42,
119
},
120
},
121
122
// Access
123
{`{ a = 15 }.a`, int(15)},
124
{`{ a = { b = 12 } }.a.b`, int(12)},
125
126
// Indexing
127
{`[0, 1, 2][1]`, int(1)},
128
{`[[1,2,3]][0][2]`, int(3)},
129
{`[true, false][0]`, bool(true)},
130
131
// Paren
132
{`(15)`, int(15)},
133
134
// Unary
135
{`!true`, bool(false)},
136
{`!false`, bool(true)},
137
{`-15`, int(-15)},
138
}
139
140
for _, tc := range tt {
141
name := trimWhitespace(tc.input)
142
143
t.Run(name, func(t *testing.T) {
144
expr, err := parser.ParseExpression(tc.input)
145
require.NoError(t, err)
146
147
eval := vm.New(expr)
148
149
vPtr := reflect.New(reflect.TypeOf(tc.expect)).Interface()
150
require.NoError(t, eval.Evaluate(scope, vPtr))
151
152
actual := reflect.ValueOf(vPtr).Elem().Interface()
153
require.Equal(t, tc.expect, actual)
154
})
155
}
156
}
157
158
func TestVM_Evaluate_Null(t *testing.T) {
159
expr, err := parser.ParseExpression("null")
160
require.NoError(t, err)
161
162
eval := vm.New(expr)
163
164
var v interface{}
165
require.NoError(t, eval.Evaluate(nil, &v))
166
require.Nil(t, v)
167
}
168
169
func TestVM_Evaluate_IdentifierExpr(t *testing.T) {
170
t.Run("Valid lookup", func(t *testing.T) {
171
scope := &vm.Scope{
172
Variables: map[string]interface{}{
173
"foobar": 15,
174
},
175
}
176
177
expr, err := parser.ParseExpression(`foobar`)
178
require.NoError(t, err)
179
180
eval := vm.New(expr)
181
182
var actual int
183
require.NoError(t, eval.Evaluate(scope, &actual))
184
require.Equal(t, 15, actual)
185
})
186
187
t.Run("Invalid lookup", func(t *testing.T) {
188
expr, err := parser.ParseExpression(`foobar`)
189
require.NoError(t, err)
190
191
eval := vm.New(expr)
192
193
var v interface{}
194
err = eval.Evaluate(nil, &v)
195
require.EqualError(t, err, `1:1: identifier "foobar" does not exist`)
196
})
197
}
198
199
func TestVM_Evaluate_AccessExpr(t *testing.T) {
200
t.Run("Lookup optional field", func(t *testing.T) {
201
type Person struct {
202
Name string `river:"name,attr,optional"`
203
}
204
205
scope := &vm.Scope{
206
Variables: map[string]interface{}{
207
"person": Person{},
208
},
209
}
210
211
expr, err := parser.ParseExpression(`person.name`)
212
require.NoError(t, err)
213
214
eval := vm.New(expr)
215
216
var actual string
217
require.NoError(t, eval.Evaluate(scope, &actual))
218
require.Equal(t, "", actual)
219
})
220
221
t.Run("Invalid lookup 1", func(t *testing.T) {
222
expr, err := parser.ParseExpression(`{ a = 15 }.b`)
223
require.NoError(t, err)
224
225
eval := vm.New(expr)
226
227
var v interface{}
228
err = eval.Evaluate(nil, &v)
229
require.EqualError(t, err, `1:12: field "b" does not exist`)
230
})
231
232
t.Run("Invalid lookup 2", func(t *testing.T) {
233
_, err := parser.ParseExpression(`{ a = 15 }.7`)
234
require.EqualError(t, err, `1:11: expected TERMINATOR, got FLOAT`)
235
})
236
237
t.Run("Invalid lookup 3", func(t *testing.T) {
238
_, err := parser.ParseExpression(`{ a = { b = 12 }.7 }.a.b`)
239
require.EqualError(t, err, `1:17: missing ',' in field list`)
240
})
241
242
t.Run("Invalid lookup 4", func(t *testing.T) {
243
_, err := parser.ParseExpression(`{ a = { b = 12 } }.a.b.7`)
244
require.EqualError(t, err, `1:23: expected TERMINATOR, got FLOAT`)
245
})
246
}
247
248
func trimWhitespace(in string) string {
249
f := token.NewFile("")
250
251
s := scanner.New(f, []byte(in), nil, 0)
252
253
var out strings.Builder
254
255
for {
256
_, tok, lit := s.Scan()
257
if tok == token.EOF {
258
break
259
}
260
261
if lit != "" {
262
_, _ = out.WriteString(lit)
263
} else {
264
_, _ = out.WriteString(tok.String())
265
}
266
}
267
268
return strings.TrimFunc(out.String(), unicode.IsSpace)
269
}
270
271