Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/pkg/river/vm/vm_stdlib_test.go
4095 views
1
package vm_test
2
3
import (
4
"fmt"
5
"reflect"
6
"testing"
7
8
"github.com/grafana/agent/pkg/river/internal/value"
9
"github.com/grafana/agent/pkg/river/parser"
10
"github.com/grafana/agent/pkg/river/rivertypes"
11
"github.com/grafana/agent/pkg/river/vm"
12
"github.com/stretchr/testify/require"
13
)
14
15
func TestVM_Stdlib(t *testing.T) {
16
t.Setenv("TEST_VAR", "Hello!")
17
18
tt := []struct {
19
name string
20
input string
21
expect interface{}
22
}{
23
{"env", `env("TEST_VAR")`, string("Hello!")},
24
{"concat", `concat([true, "foo"], [], [false, 1])`, []interface{}{true, "foo", false, float64(1)}},
25
{"json_decode object", `json_decode("{\"foo\": \"bar\"}")`, map[string]interface{}{"foo": "bar"}},
26
{"json_decode array", `json_decode("[0, 1, 2]")`, []interface{}{float64(0), float64(1), float64(2)}},
27
{"json_decode nil field", `json_decode("{\"foo\": null}")`, map[string]interface{}{"foo": nil}},
28
{"json_decode nil array element", `json_decode("[0, null]")`, []interface{}{float64(0), nil}},
29
}
30
31
for _, tc := range tt {
32
t.Run(tc.name, func(t *testing.T) {
33
expr, err := parser.ParseExpression(tc.input)
34
require.NoError(t, err)
35
36
eval := vm.New(expr)
37
38
rv := reflect.New(reflect.TypeOf(tc.expect))
39
require.NoError(t, eval.Evaluate(nil, rv.Interface()))
40
require.Equal(t, tc.expect, rv.Elem().Interface())
41
})
42
}
43
}
44
45
func TestStdlibCoalesce(t *testing.T) {
46
t.Setenv("TEST_VAR2", "Hello!")
47
48
tt := []struct {
49
name string
50
input string
51
expect interface{}
52
}{
53
{"coalesce()", `coalesce()`, value.Null},
54
{"coalesce(string)", `coalesce("Hello!")`, string("Hello!")},
55
{"coalesce(string, string)", `coalesce(env("TEST_VAR2"), "World!")`, string("Hello!")},
56
{"(string, string) with fallback", `coalesce(env("NON_DEFINED"), "World!")`, string("World!")},
57
{"coalesce(list, list)", `coalesce([], ["fallback"])`, []string{"fallback"}},
58
{"coalesce(list, list) with fallback", `coalesce(concat(["item"]), ["fallback"])`, []string{"item"}},
59
{"coalesce(int, int, int)", `coalesce(0, 1, 2)`, 1},
60
{"coalesce(bool, int, int)", `coalesce(false, 1, 2)`, 1},
61
{"coalesce(bool, bool)", `coalesce(false, true)`, true},
62
{"coalesce(list, bool)", `coalesce(json_decode("[]"), true)`, true},
63
{"coalesce(object, true) and return true", `coalesce(json_decode("{}"), true)`, true},
64
{"coalesce(object, false) and return false", `coalesce(json_decode("{}"), false)`, false},
65
{"coalesce(list, nil)", `coalesce([],null)`, value.Null},
66
}
67
68
for _, tc := range tt {
69
t.Run(tc.name, func(t *testing.T) {
70
expr, err := parser.ParseExpression(tc.input)
71
require.NoError(t, err)
72
73
eval := vm.New(expr)
74
75
rv := reflect.New(reflect.TypeOf(tc.expect))
76
require.NoError(t, eval.Evaluate(nil, rv.Interface()))
77
require.Equal(t, tc.expect, rv.Elem().Interface())
78
})
79
}
80
}
81
82
func TestStdlib_Nonsensitive(t *testing.T) {
83
scope := &vm.Scope{
84
Variables: map[string]any{
85
"secret": rivertypes.Secret("foo"),
86
"optionalSecret": rivertypes.OptionalSecret{Value: "bar"},
87
},
88
}
89
90
tt := []struct {
91
name string
92
input string
93
expect interface{}
94
}{
95
{"secret to string", `nonsensitive(secret)`, string("foo")},
96
{"optional secret to string", `nonsensitive(optionalSecret)`, string("bar")},
97
}
98
99
for _, tc := range tt {
100
t.Run(tc.name, func(t *testing.T) {
101
expr, err := parser.ParseExpression(tc.input)
102
require.NoError(t, err)
103
104
eval := vm.New(expr)
105
106
rv := reflect.New(reflect.TypeOf(tc.expect))
107
require.NoError(t, eval.Evaluate(scope, rv.Interface()))
108
require.Equal(t, tc.expect, rv.Elem().Interface())
109
})
110
}
111
}
112
113
func BenchmarkConcat(b *testing.B) {
114
// There's a bit of setup work to do here: we want to create a scope holding
115
// a slice of the Person type, which has a fair amount of data in it.
116
//
117
// We then want to pass it through concat.
118
//
119
// If the code path is fully optimized, there will be no intermediate
120
// translations to interface{}.
121
type Person struct {
122
Name string `river:"name,attr"`
123
Attrs map[string]string `river:"attrs,attr"`
124
}
125
type Body struct {
126
Values []Person `river:"values,attr"`
127
}
128
129
in := `values = concat(values_ref)`
130
f, err := parser.ParseFile("", []byte(in))
131
require.NoError(b, err)
132
133
eval := vm.New(f)
134
135
valuesRef := make([]Person, 0, 20)
136
for i := 0; i < 20; i++ {
137
data := make(map[string]string, 20)
138
for j := 0; j < 20; j++ {
139
var (
140
key = fmt.Sprintf("key_%d", i+1)
141
value = fmt.Sprintf("value_%d", i+1)
142
)
143
data[key] = value
144
}
145
valuesRef = append(valuesRef, Person{
146
Name: "Test Person",
147
Attrs: data,
148
})
149
}
150
scope := &vm.Scope{
151
Variables: map[string]interface{}{
152
"values_ref": valuesRef,
153
},
154
}
155
156
// Reset timer before running the actual test
157
b.ResetTimer()
158
159
for i := 0; i < b.N; i++ {
160
var b Body
161
_ = eval.Evaluate(scope, &b)
162
}
163
}
164
165