master
go 362 lines 12.4 KB
Raw
1 package pluginconfig
2
3 import (
4 "os"
5 "path/filepath"
6 "testing"
7
8 "github.com/stretchr/testify/assert"
9 "github.com/stretchr/testify/require"
10 )
11
12 // helpers
13
14 func mkdir(t *testing.T, p string) {
15 t.Helper()
16 require.NoError(t, os.MkdirAll(p, 0o755))
17 }
18
19 const testPluginName = "test.plugin"
20
21 func TestDirectoriesBuild(t *testing.T) {
22 tmp := t.TempDir()
23 execDir := filepath.Join(tmp, "root", "opt", "netdata", "bin")
24 mkdir(t, execDir)
25
26 // Probe locations (used ONLY when build() falls back to cygwinBase discovery).
27 // These dirs must exist for probe-based selection to succeed; otherwise,
28 // build() will skip them and fall back to build-relative defaults.
29 probeUser := filepath.Join(tmp, "etc", "netdata")
30 probeStock := filepath.Join(tmp, "usr", "lib", "netdata", "conf.d")
31 mkdir(t, probeUser)
32 mkdir(t, probeStock)
33
34 tests := map[string]struct {
35 input InitInput
36 env envData
37 want directories
38 wantErr bool
39 }{
40 // ─────────────────────────────────── core (no cygwinBase) ───────────────────────────────────
41 "cli_dirs_only": {
42 input: InitInput{
43 ConfDir: []string{"/tmp/user1", "/tmp/user2"},
44 },
45 env: envData{
46 stockDir: probeStock, // avoid probe/mapping; keep paths clean in expectations
47 },
48 want: directories{
49 userConfigDirs: []string{"/tmp/user1", "/tmp/user2"},
50 stockConfigDir: probeStock,
51 collectorsUserDirs: []string{"/tmp/user1/" + testPluginName, "/tmp/user2/" + testPluginName},
52 collectorsStockDir: filepath.Join(probeStock, testPluginName),
53 sdUserDirs: []string{"/tmp/user1/" + testPluginName + "/sd", "/tmp/user2/" + testPluginName + "/sd"},
54 sdStockDir: filepath.Join(probeStock, testPluginName, "sd"),
55 collectorsWatch: []string{},
56 varLibDir: "",
57 },
58 },
59 "env_dirs_only": {
60 input: InitInput{},
61 env: envData{
62 userDir: "/tmp/env/user",
63 stockDir: "/tmp/env/stock",
64 },
65 want: directories{
66 userConfigDirs: []string{"/tmp/env/user"},
67 stockConfigDir: "/tmp/env/stock",
68 collectorsUserDirs: []string{"/tmp/env/user/" + testPluginName},
69 collectorsStockDir: "/tmp/env/stock/" + testPluginName,
70 sdUserDirs: []string{"/tmp/env/user/" + testPluginName + "/sd"},
71 sdStockDir: "/tmp/env/stock/" + testPluginName + "/sd",
72 collectorsWatch: []string{},
73 varLibDir: "",
74 },
75 },
76 "cli_overrides_env": {
77 input: InitInput{
78 ConfDir: []string{"/tmp/cli/dir"},
79 },
80 env: envData{
81 userDir: "/tmp/env/user",
82 stockDir: "/tmp/env/stock",
83 },
84 want: directories{
85 userConfigDirs: []string{"/tmp/cli/dir", "/tmp/env/user"},
86 stockConfigDir: "/tmp/env/stock",
87 collectorsUserDirs: []string{"/tmp/cli/dir/" + testPluginName, "/tmp/env/user/" + testPluginName},
88 collectorsStockDir: "/tmp/env/stock/" + testPluginName,
89 sdUserDirs: []string{"/tmp/cli/dir/" + testPluginName + "/sd", "/tmp/env/user/" + testPluginName + "/sd"},
90 sdStockDir: "/tmp/env/stock/" + testPluginName + "/sd",
91 collectorsWatch: []string{},
92 varLibDir: "",
93 },
94 },
95 "fallback_dirs_when_no_config": {
96 input: InitInput{},
97 env: envData{},
98 // build-relative fallback from execDir
99 want: directories{
100 userConfigDirs: []string{filepath.Join(execDir, "..", "..", "..", "..", "etc", "netdata")},
101 stockConfigDir: filepath.Join(execDir, "..", "..", "..", "..", "usr", "lib", "netdata", "conf.d"),
102 collectorsUserDirs: []string{filepath.Join(execDir, "..", "..", "..", "..", "etc", "netdata", testPluginName)},
103 collectorsStockDir: filepath.Join(execDir, "..", "..", "..", "..", "usr", "lib", "netdata", "conf.d", testPluginName),
104 sdUserDirs: []string{filepath.Join(execDir, "..", "..", "..", "..", "etc", "netdata", testPluginName, "sd")},
105 sdStockDir: filepath.Join(execDir, "..", "..", "..", "..", "usr", "lib", "netdata", "conf.d", testPluginName, "sd"),
106 collectorsWatch: []string{},
107 varLibDir: "",
108 },
109 },
110 "multiple_cli_dirs": {
111 input: InitInput{
112 ConfDir: []string{"/tmp/dir1", "/tmp/dir2", "/tmp/dir3"},
113 },
114 env: envData{
115 stockDir: probeStock,
116 },
117 want: directories{
118 userConfigDirs: []string{"/tmp/dir1", "/tmp/dir2", "/tmp/dir3"},
119 stockConfigDir: probeStock,
120 collectorsUserDirs: []string{
121 "/tmp/dir1/" + testPluginName,
122 "/tmp/dir2/" + testPluginName,
123 "/tmp/dir3/" + testPluginName,
124 },
125 collectorsStockDir: filepath.Join(probeStock, testPluginName),
126 sdUserDirs: []string{
127 "/tmp/dir1/" + testPluginName + "/sd",
128 "/tmp/dir2/" + testPluginName + "/sd",
129 "/tmp/dir3/" + testPluginName + "/sd",
130 },
131 sdStockDir: filepath.Join(probeStock, testPluginName, "sd"),
132 collectorsWatch: []string{},
133 varLibDir: "",
134 },
135 },
136
137 // ─────────────────────────────── explicit cygwinBase cases ───────────────────────────────
138 "watch_paths_from_cli_and_env (cygwinBase)": {
139 input: InitInput{
140 WatchPath: []string{"/tmp/watch1", "/tmp/watch2"},
141 },
142 env: envData{
143 cygwinBase: tmp, // exercise probe discovery
144 watchPath: "/tmp/watch/env",
145 },
146 want: directories{
147 userConfigDirs: []string{probeUser}, // from probe discovery
148 stockConfigDir: probeStock,
149 collectorsUserDirs: []string{filepath.Join(probeUser, testPluginName)},
150 collectorsStockDir: filepath.Join(probeStock, testPluginName),
151 sdUserDirs: []string{filepath.Join(probeUser, testPluginName, "sd")},
152 sdStockDir: filepath.Join(probeStock, testPluginName, "sd"),
153 collectorsWatch: []string{"/tmp/watch1", "/tmp/watch2", "/tmp/watch/env"}, // dedup preserved
154 varLibDir: "",
155 },
156 },
157 "varlib_from_env (cygwinBase + probes)": {
158 input: InitInput{},
159 env: envData{
160 cygwinBase: tmp,
161 varLibDir: "/var/lib/netdata",
162 },
163 want: directories{
164 userConfigDirs: []string{probeUser},
165 stockConfigDir: probeStock,
166 collectorsUserDirs: []string{filepath.Join(probeUser, testPluginName)},
167 collectorsStockDir: filepath.Join(probeStock, testPluginName),
168 sdUserDirs: []string{filepath.Join(probeUser, testPluginName, "sd")},
169 sdStockDir: filepath.Join(probeStock, testPluginName, "sd"),
170 collectorsWatch: []string{},
171 varLibDir: "/var/lib/netdata",
172 },
173 },
174 "empty_stock_dir_from_env (cygwinBase + probes)": {
175 input: InitInput{},
176 env: envData{
177 userDir: "/tmp/user", // explicit user
178 stockDir: "", // missing -> probe for stock
179 cygwinBase: tmp,
180 },
181 want: directories{
182 userConfigDirs: []string{"/tmp/user"},
183 stockConfigDir: probeStock, // discovered
184 collectorsUserDirs: []string{"/tmp/user/" + testPluginName},
185 collectorsStockDir: filepath.Join(probeStock, testPluginName),
186 sdUserDirs: []string{"/tmp/user/" + testPluginName + "/sd"},
187 sdStockDir: filepath.Join(probeStock, testPluginName, "sd"),
188 collectorsWatch: []string{},
189 varLibDir: "",
190 },
191 },
192 "env_user_pre_normalized (cygwinBase set)": {
193 input: InitInput{},
194 env: envData{
195 cygwinBase: tmp, // present but build() expects env already normalized
196 userDir: filepath.Join(tmp, "etc", "netdata"), // pre-normalized
197 stockDir: filepath.Join(tmp, "custom", "stock"), // pre-normalized
198 },
199 want: directories{
200 userConfigDirs: []string{filepath.Join(tmp, "etc", "netdata")},
201 stockConfigDir: filepath.Join(tmp, "custom", "stock"),
202 collectorsUserDirs: []string{filepath.Join(tmp, "etc", "netdata", testPluginName)},
203 collectorsStockDir: filepath.Join(tmp, "custom", "stock", testPluginName),
204 sdUserDirs: []string{filepath.Join(tmp, "etc", "netdata", testPluginName, "sd")},
205 sdStockDir: filepath.Join(tmp, "custom", "stock", testPluginName, "sd"),
206 collectorsWatch: []string{},
207 varLibDir: "",
208 },
209 },
210 "cli_dirs_remapped (cygwinBase)": {
211 input: InitInput{
212 ConfDir: []string{"/etc/netdata", "/opt/netdata/etc/netdata"},
213 },
214 env: envData{
215 cygwinBase: tmp, // remap CLI POSIX to <tmp> paths
216 stockDir: probeStock,
217 },
218 want: directories{
219 userConfigDirs: []string{
220 filepath.Join(tmp, "etc", "netdata"),
221 filepath.Join(tmp, "opt", "netdata", "etc", "netdata"),
222 },
223 stockConfigDir: probeStock,
224 collectorsUserDirs: []string{
225 filepath.Join(tmp, "etc", "netdata", testPluginName),
226 filepath.Join(tmp, "opt", "netdata", "etc", "netdata", testPluginName),
227 },
228 collectorsStockDir: filepath.Join(probeStock, testPluginName),
229 sdUserDirs: []string{
230 filepath.Join(tmp, "etc", "netdata", testPluginName, "sd"),
231 filepath.Join(tmp, "opt", "netdata", "etc", "netdata", testPluginName, "sd"),
232 },
233 sdStockDir: filepath.Join(probeStock, testPluginName, "sd"),
234 collectorsWatch: []string{},
235 varLibDir: "",
236 },
237 },
238 }
239
240 for name, tc := range tests {
241 t.Run(name, func(t *testing.T) {
242 t.Parallel() // safe: build() uses only inputs, no globals
243 var got directories
244 err := got.build(tc.input, tc.env, testPluginName, execDir)
245
246 if tc.wantErr {
247 require.Error(t, err)
248 return
249 }
250 require.NoError(t, err)
251 assert.Equal(t, tc.want, got)
252 })
253 }
254 }
255
256 func TestDirectoriesBuildValidation(t *testing.T) {
257 tests := map[string]struct {
258 dirs directories
259 wantErr bool
260 }{
261 "empty_user_config_dirs": {
262 wantErr: true,
263 dirs: directories{
264 userConfigDirs: nil,
265 stockConfigDir: "/stock",
266 }},
267 "empty_stock_config_dir": {
268 wantErr: true,
269 dirs: directories{
270 userConfigDirs: []string{"/user"},
271 stockConfigDir: "",
272 collectorsUserDirs: []string{"/user/plugin"},
273 collectorsStockDir: "/stock/plugin",
274 sdUserDirs: []string{"/user/plugin/sd"},
275 sdStockDir: "/stock/plugin/sd",
276 }},
277 "empty_collectors_user_dirs": {
278 wantErr: true,
279 dirs: directories{
280 userConfigDirs: []string{"/user"},
281 stockConfigDir: "/stock",
282 collectorsUserDirs: nil,
283 collectorsStockDir: "/stock/plugin",
284 sdUserDirs: []string{"/user/plugin/sd"},
285 sdStockDir: "/stock/plugin/sd",
286 },
287 },
288 "empty_collectors_stock_dir": {
289 wantErr: true,
290 dirs: directories{
291 userConfigDirs: []string{"/user"},
292 stockConfigDir: "/stock",
293 collectorsUserDirs: []string{"/user/plugin"},
294 collectorsStockDir: "",
295 sdUserDirs: []string{"/user/plugin/sd"},
296 sdStockDir: "/stock/plugin/sd",
297 },
298 },
299 "empty_sd_user_dirs": {
300 wantErr: true,
301 dirs: directories{
302 userConfigDirs: []string{"/user"},
303 stockConfigDir: "/stock",
304 collectorsUserDirs: []string{"/user/plugin"},
305 collectorsStockDir: "/stock/plugin",
306 sdUserDirs: nil,
307 sdStockDir: "/stock/plugin/sd",
308 },
309 },
310 "empty_sd_stock_dir": {
311 wantErr: true,
312 dirs: directories{
313 userConfigDirs: []string{"/user"},
314 stockConfigDir: "/stock",
315 collectorsUserDirs: []string{"/user/plugin"},
316 collectorsStockDir: "/stock/plugin",
317 sdUserDirs: []string{"/user/plugin/sd"},
318 sdStockDir: "",
319 },
320 },
321 "valid_directories": {
322 wantErr: false,
323 dirs: directories{
324 userConfigDirs: []string{"/user"},
325 stockConfigDir: "/stock",
326 collectorsUserDirs: []string{"/user/plugin"},
327 collectorsStockDir: "/stock/plugin",
328 sdUserDirs: []string{"/user/plugin/sd"},
329 sdStockDir: "/stock/plugin/sd",
330 },
331 },
332 }
333
334 for name, tc := range tests {
335 t.Run(name, func(t *testing.T) {
336 err := tc.dirs.validate()
337 if tc.wantErr {
338 require.Error(t, err)
339 } else {
340 require.NoError(t, err)
341 }
342 })
343 }
344 }
345
346 func TestIsStock(t *testing.T) {
347 orig := dirs
348 t.Cleanup(func() { dirs = orig })
349
350 t.Run("fallback heuristic when stock dir is not initialized", func(t *testing.T) {
351 dirs = directories{}
352 assert.True(t, IsStock("/usr/lib/netdata/conf.d/go.d/module.conf"))
353 assert.False(t, IsStock("/etc/netdata/go.d/module.conf"))
354 })
355
356 t.Run("uses configured stock root when available", func(t *testing.T) {
357 dirs = directories{stockConfigDir: "/custom/stock"}
358 assert.True(t, IsStock("/custom/stock/go.d/module.conf"))
359 assert.False(t, IsStock("/usr/lib/netdata/conf.d/go.d/module.conf"))
360 assert.False(t, IsStock("/etc/netdata/go.d/module.conf"))
361 })
362 }