Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aos
GitHub Repository: aos/grafana-agent
Path: blob/main/component/module/string/string_test.go
4096 views
1
//go:build linux
2
3
package string
4
5
import (
6
"bytes"
7
"context"
8
"os"
9
"path/filepath"
10
"strings"
11
"testing"
12
"time"
13
14
_ "github.com/grafana/agent/component/local/file"
15
"github.com/grafana/agent/pkg/cluster"
16
"github.com/grafana/agent/pkg/flow"
17
"github.com/grafana/agent/pkg/flow/logging"
18
"github.com/stretchr/testify/require"
19
)
20
21
const loggingConfig = `
22
logging {}`
23
24
const tracingConfig = `
25
tracing {}`
26
27
const argumentConfig = `
28
argument "username" {}
29
argument "defaulted" {
30
optional = true
31
default = "default_value"
32
}`
33
34
const argumentModuleLoaderConfig = `
35
local.file "args" { filename = "%arg" }
36
module.string "importer" {
37
content = local.file.args.content
38
arguments {
39
username = module.string.exporter.exports.username
40
}
41
}`
42
43
const exportStringConfig = `
44
export "username" {
45
value = "bob"
46
}`
47
48
const exportComponentConfig = `
49
testcomponents.tick "t1" {
50
frequency = "1s"
51
}
52
53
export "dummy" {
54
value = testcomponents.tick.t1.tick_time
55
}`
56
57
const exportModuleLoaderConfig = `
58
local.file "exporter" { filename = "%exp" }
59
60
module.string "exporter" {
61
content = local.file.exporter.content
62
}`
63
64
func TestModule(t *testing.T) {
65
tt := []struct {
66
name string
67
riverContent string
68
argumentModuleContent string
69
exportModuleContent string
70
expectedComponentId string
71
expectedExports []string
72
expectedErrorContains string
73
}{
74
{
75
name: "Export String",
76
riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,
77
argumentModuleContent: argumentConfig,
78
exportModuleContent: exportStringConfig,
79
expectedComponentId: "module.string.importer",
80
expectedExports: []string{"bob"},
81
},
82
{
83
name: "Export Component",
84
riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,
85
argumentModuleContent: argumentConfig,
86
exportModuleContent: exportStringConfig + exportComponentConfig,
87
expectedComponentId: "module.string.exporter",
88
expectedExports: []string{"username", "dummy"},
89
},
90
{
91
name: "Empty Content Allowed",
92
riverContent: `module.string "empty" { content = "" }`,
93
},
94
{
95
name: "Argument blocks not allowed in parent config",
96
riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig + argumentConfig,
97
argumentModuleContent: argumentConfig,
98
exportModuleContent: exportStringConfig,
99
expectedErrorContains: "argument blocks only allowed inside a module",
100
},
101
{
102
name: "Export blocks not allowed in parent config",
103
riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig + exportStringConfig,
104
argumentModuleContent: argumentConfig,
105
exportModuleContent: exportStringConfig,
106
expectedErrorContains: "export blocks only allowed inside a module",
107
},
108
{
109
name: "Logging blocks not allowed in module config",
110
riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,
111
argumentModuleContent: argumentConfig + loggingConfig,
112
exportModuleContent: exportStringConfig,
113
expectedErrorContains: "logging block not allowed inside a module",
114
},
115
{
116
name: "Tracing blocks not allowed in module config",
117
riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,
118
argumentModuleContent: argumentConfig + tracingConfig,
119
exportModuleContent: exportStringConfig,
120
expectedErrorContains: "tracing block not allowed inside a module",
121
},
122
{
123
name: "Argument not defined in module source",
124
riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,
125
argumentModuleContent: `argument "different_argument" {}`,
126
exportModuleContent: exportStringConfig,
127
expectedErrorContains: "Provided argument \"username\" is not defined in the module",
128
},
129
{
130
name: "Missing required argument",
131
riverContent: exportModuleLoaderConfig + `
132
local.file "args" { filename = "%arg" }
133
module.string "importer" {
134
content = local.file.args.content
135
}`,
136
argumentModuleContent: argumentConfig,
137
exportModuleContent: exportStringConfig,
138
expectedErrorContains: "Failed to evaluate node for config block: missing required argument \"username\" to module",
139
},
140
{
141
name: "Duplicate logging config",
142
riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig + loggingConfig + loggingConfig,
143
argumentModuleContent: argumentConfig,
144
exportModuleContent: exportStringConfig,
145
expectedErrorContains: "\"logging\" block already declared",
146
},
147
{
148
name: "Duplicate tracing config",
149
riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig + tracingConfig + tracingConfig,
150
argumentModuleContent: argumentConfig,
151
exportModuleContent: exportStringConfig,
152
expectedErrorContains: "\"tracing\" block already declared",
153
},
154
{
155
name: "Duplicate argument config",
156
riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,
157
argumentModuleContent: argumentConfig + argumentConfig,
158
exportModuleContent: exportStringConfig,
159
expectedErrorContains: "\"argument.username\" block already declared",
160
},
161
{
162
name: "Duplicate export config",
163
riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,
164
argumentModuleContent: argumentConfig,
165
exportModuleContent: exportStringConfig + exportStringConfig,
166
expectedErrorContains: "\"export.username\" block already declared",
167
},
168
}
169
170
for _, tc := range tt {
171
t.Run(tc.name, func(t *testing.T) {
172
tmpDir := t.TempDir()
173
riverFile := tc.riverContent
174
175
// Prep the argument module file
176
if tc.argumentModuleContent != "" {
177
argPath := filepath.Join(tmpDir, "args.river")
178
writeFile(t, argPath, tc.argumentModuleContent)
179
riverFile = strings.Replace(riverFile, "%arg", argPath, 1)
180
}
181
182
// Prep the export module file
183
if tc.exportModuleContent != "" {
184
exportPath := filepath.Join(tmpDir, "export.river")
185
writeFile(t, exportPath, tc.exportModuleContent)
186
riverFile = strings.Replace(riverFile, "%exp", exportPath, 1)
187
}
188
189
testFile(t, riverFile, tc.expectedComponentId, tc.expectedExports, tc.expectedErrorContains)
190
})
191
}
192
}
193
194
func testFile(t *testing.T, fmtFile string, componentToFind string, searchable []string, expectedErrorContains string) {
195
f := flow.New(testOptions(t))
196
ff, err := flow.ReadFile("test", []byte(fmtFile))
197
require.NoError(t, err)
198
err = f.LoadFile(ff, nil)
199
if expectedErrorContains == "" {
200
require.NoError(t, err)
201
} else {
202
require.ErrorContains(t, err, expectedErrorContains)
203
return
204
}
205
206
ctx := context.Background()
207
ctx, cncl := context.WithTimeout(ctx, 20*time.Second)
208
defer cncl()
209
go f.Run(ctx)
210
time.Sleep(3 * time.Second)
211
infos := f.ComponentInfos()
212
for _, i := range infos {
213
if i.ID != componentToFind {
214
continue
215
}
216
buf := bytes.NewBuffer(nil)
217
err = f.ComponentJSON(buf, i)
218
require.NoError(t, err)
219
// This ensures that although dummy is not used it still exists.
220
// And that multiple exports are displayed when only one of them is updating.
221
for _, s := range searchable {
222
require.True(t, strings.Contains(buf.String(), s))
223
}
224
}
225
}
226
227
func writeFile(t *testing.T, path, content string) {
228
err := os.WriteFile(path, []byte(content), 0666)
229
require.NoError(t, err)
230
}
231
232
func testOptions(t *testing.T) flow.Options {
233
t.Helper()
234
235
s, err := logging.WriterSink(os.Stderr, logging.DefaultSinkOptions)
236
require.NoError(t, err)
237
238
c := &cluster.Clusterer{Node: cluster.NewLocalNode("")}
239
240
return flow.Options{
241
LogSink: s,
242
DataPath: t.TempDir(),
243
Reg: nil,
244
Clusterer: c,
245
}
246
}
247
248