master
go 413 lines 8.11 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package jobruntime
4
5 import (
6 "context"
7 "errors"
8 "fmt"
9 "io"
10 "testing"
11 "time"
12
13 "github.com/netdata/netdata/go/plugins/plugin/framework/collectorapi"
14 "github.com/netdata/netdata/go/plugins/plugin/framework/vnodes"
15 "github.com/stretchr/testify/assert"
16 )
17
18 const (
19 pluginName = "plugin"
20 modName = "module"
21 jobName = "job"
22 )
23
24 func newTestJob() *Job {
25 return NewJob(
26 JobConfig{
27 PluginName: pluginName,
28 Name: jobName,
29 ModuleName: modName,
30 FullName: modName + "_" + jobName,
31 Module: nil,
32 Out: io.Discard,
33 UpdateEvery: 0,
34 AutoDetectEvery: 0,
35 Priority: 0,
36 },
37 )
38 }
39
40 func TestNewJob(t *testing.T) {
41 assert.IsType(t, (*Job)(nil), newTestJob())
42 }
43
44 func TestJob_FullName(t *testing.T) {
45 job := newTestJob()
46
47 assert.Equal(t, job.FullName(), fmt.Sprintf("%s_%s", modName, jobName))
48 }
49
50 func TestJob_ModuleName(t *testing.T) {
51 job := newTestJob()
52
53 assert.Equal(t, job.ModuleName(), modName)
54 }
55
56 func TestJob_Name(t *testing.T) {
57 job := newTestJob()
58
59 assert.Equal(t, job.Name(), jobName)
60 }
61
62 func TestJob_Panicked(t *testing.T) {
63 job := newTestJob()
64
65 assert.Equal(t, job.Panicked(), job.panicked.Load())
66 job.panicked.Store(true)
67 assert.Equal(t, job.Panicked(), job.panicked.Load())
68 }
69
70 func TestJob_AutoDetectionEvery(t *testing.T) {
71 job := newTestJob()
72
73 assert.Equal(t, job.AutoDetectionEvery(), job.AutoDetectEvery)
74 }
75
76 func TestJob_RetryAutoDetection(t *testing.T) {
77 job := newTestJob()
78 m := &collectorapi.MockCollectorV1{
79 InitFunc: func(context.Context) error {
80 return nil
81 },
82 CheckFunc: func(context.Context) error { return errors.New("check error") },
83 ChartsFunc: func() *collectorapi.Charts {
84 return &collectorapi.Charts{}
85 },
86 }
87 job.module = m
88 job.AutoDetectEvery = 1
89
90 assert.True(t, job.RetryAutoDetection())
91 assert.Equal(t, infTries, job.AutoDetectTries)
92 for range 1000 {
93 _ = job.check()
94 }
95 assert.True(t, job.RetryAutoDetection())
96 assert.Equal(t, infTries, job.AutoDetectTries)
97
98 job.AutoDetectTries = 10
99 for range 10 {
100 _ = job.check()
101 }
102 assert.False(t, job.RetryAutoDetection())
103 assert.Equal(t, 0, job.AutoDetectTries)
104 }
105
106 func TestJob_AutoDetection(t *testing.T) {
107 job := newTestJob()
108 var v int
109 m := &collectorapi.MockCollectorV1{
110 InitFunc: func(context.Context) error {
111 v++
112 return nil
113 },
114 CheckFunc: func(context.Context) error {
115 v++
116 return nil
117 },
118 ChartsFunc: func() *collectorapi.Charts {
119 v++
120 return &collectorapi.Charts{}
121 },
122 }
123 job.module = m
124
125 assert.NoError(t, job.AutoDetection())
126 assert.Equal(t, 3, v)
127 }
128
129 func TestJob_AutoDetection_FailInit(t *testing.T) {
130 job := newTestJob()
131 m := &collectorapi.MockCollectorV1{
132 InitFunc: func(context.Context) error {
133 return errors.New("init error")
134 },
135 }
136 job.module = m
137
138 assert.Error(t, job.AutoDetection())
139 assert.True(t, m.CleanupDone)
140 }
141
142 func TestJob_AutoDetection_FailCheck(t *testing.T) {
143 job := newTestJob()
144 m := &collectorapi.MockCollectorV1{
145 InitFunc: func(context.Context) error {
146 return nil
147 },
148 CheckFunc: func(context.Context) error {
149 return errors.New("check error")
150 },
151 }
152 job.module = m
153
154 assert.Error(t, job.AutoDetection())
155 assert.True(t, m.CleanupDone)
156 }
157
158 func TestJob_AutoDetection_FailPostCheck(t *testing.T) {
159 job := newTestJob()
160 m := &collectorapi.MockCollectorV1{
161 InitFunc: func(context.Context) error {
162 return nil
163 },
164 CheckFunc: func(context.Context) error {
165 return nil
166 },
167 ChartsFunc: func() *collectorapi.Charts {
168 return nil
169 },
170 }
171 job.module = m
172
173 assert.Error(t, job.AutoDetection())
174 assert.True(t, m.CleanupDone)
175 }
176
177 func TestJob_AutoDetection_PanicInit(t *testing.T) {
178 job := newTestJob()
179 m := &collectorapi.MockCollectorV1{
180 InitFunc: func(context.Context) error {
181 panic("panic in Init")
182 },
183 }
184 job.module = m
185
186 assert.Error(t, job.AutoDetection())
187 assert.True(t, m.CleanupDone)
188 }
189
190 func TestJob_AutoDetection_PanicCheck(t *testing.T) {
191 job := newTestJob()
192 m := &collectorapi.MockCollectorV1{
193 InitFunc: func(context.Context) error {
194 return nil
195 },
196 CheckFunc: func(context.Context) error {
197 panic("panic in Check")
198 },
199 }
200 job.module = m
201
202 assert.Error(t, job.AutoDetection())
203 assert.True(t, m.CleanupDone)
204 }
205
206 func TestJob_AutoDetection_PanicPostCheck(t *testing.T) {
207 job := newTestJob()
208 m := &collectorapi.MockCollectorV1{
209 InitFunc: func(context.Context) error {
210 return nil
211 },
212 CheckFunc: func(context.Context) error {
213 return nil
214 },
215 ChartsFunc: func() *collectorapi.Charts {
216 panic("panic in PostCheck")
217 },
218 }
219 job.module = m
220
221 assert.Error(t, job.AutoDetection())
222 assert.True(t, m.CleanupDone)
223 }
224
225 func TestJob_Start(t *testing.T) {
226 m := &collectorapi.MockCollectorV1{
227 ChartsFunc: func() *collectorapi.Charts {
228 return &collectorapi.Charts{
229 &collectorapi.Chart{
230 ID: "id",
231 Title: "title",
232 Units: "units",
233 Dims: collectorapi.Dims{
234 {ID: "id1"},
235 {ID: "id2"},
236 },
237 },
238 }
239 },
240 CollectFunc: func(context.Context) map[string]int64 {
241 return map[string]int64{
242 "id1": 1,
243 "id2": 2,
244 }
245 },
246 }
247 job := newTestJob()
248 job.module = m
249 job.charts = job.module.Charts()
250 job.updateEvery = 1
251
252 go func() {
253 for i := 1; i < 3; i++ {
254 job.Tick(i)
255 time.Sleep(time.Second)
256 }
257 job.Stop()
258 }()
259
260 job.Start()
261
262 assert.True(t, m.CleanupDone)
263 }
264
265 func TestJob_StopBeforeStartDoesNotBlock(t *testing.T) {
266 job := newTestJob()
267
268 done := make(chan struct{})
269 go func() {
270 job.Stop()
271 close(done)
272 }()
273
274 select {
275 case <-done:
276 case <-time.After(200 * time.Millisecond):
277 t.Fatal("stop blocked before start")
278 }
279 }
280
281 func TestJob_MainLoop_Panic(t *testing.T) {
282 m := &collectorapi.MockCollectorV1{
283 CollectFunc: func(context.Context) map[string]int64 {
284 panic("panic in Collect")
285 },
286 }
287 job := newTestJob()
288 job.module = m
289 job.updateEvery = 1
290
291 go func() {
292 for i := 1; i < 3; i++ {
293 time.Sleep(time.Second)
294 job.Tick(i)
295 }
296 job.Stop()
297 }()
298
299 job.Start()
300
301 assert.True(t, job.Panicked())
302 assert.True(t, m.CleanupDone)
303 }
304
305 func TestJob_Tick(t *testing.T) {
306 job := newTestJob()
307 for i := range 3 {
308 job.Tick(i)
309 }
310 }
311
312 func TestJob_UpdateVnode_NilIgnored(t *testing.T) {
313 tests := map[string]struct {
314 update *vnodes.VirtualNode
315 }{
316 "nil vnode update is ignored": {
317 update: nil,
318 },
319 }
320
321 for name, tc := range tests {
322 t.Run(name, func(t *testing.T) {
323 job := newTestJob()
324 job.module = &collectorapi.MockCollectorV1{}
325 job.charts = &collectorapi.Charts{}
326
327 job.UpdateVnode(tc.update)
328
329 assert.NotPanics(t, func() {
330 _ = job.processMetrics(
331 collectedMetrics{
332 intMetrics: map[string]int64{},
333 floatMetrics: map[string]float64{},
334 },
335 time.Now(),
336 1,
337 )
338 })
339 })
340 }
341 }
342
343 func newTestFunctionOnlyJob() *Job {
344 return NewJob(
345 JobConfig{
346 PluginName: pluginName,
347 Name: jobName,
348 ModuleName: modName,
349 FullName: modName + "_" + jobName,
350 Module: nil,
351 Out: io.Discard,
352 UpdateEvery: 0,
353 AutoDetectEvery: 0,
354 Priority: 0,
355 FunctionOnly: true,
356 },
357 )
358 }
359
360 func TestJob_IsFunctionOnly(t *testing.T) {
361 job := newTestJob()
362 assert.False(t, job.IsFunctionOnly())
363
364 foJob := newTestFunctionOnlyJob()
365 assert.True(t, foJob.IsFunctionOnly())
366 }
367
368 func TestJob_AutoDetection_FunctionOnly_NilCharts(t *testing.T) {
369 job := newTestFunctionOnlyJob()
370 m := &collectorapi.MockCollectorV1{
371 InitFunc: func(context.Context) error {
372 return nil
373 },
374 CheckFunc: func(context.Context) error {
375 return nil
376 },
377 ChartsFunc: func() *collectorapi.Charts {
378 return nil
379 },
380 }
381 job.module = m
382
383 assert.NoError(t, job.AutoDetection())
384 }
385
386 func TestJob_Start_FunctionOnly(t *testing.T) {
387 collectCalled := false
388 m := &collectorapi.MockCollectorV1{
389 ChartsFunc: func() *collectorapi.Charts {
390 return nil
391 },
392 CollectFunc: func(context.Context) map[string]int64 {
393 collectCalled = true
394 return map[string]int64{"id1": 1}
395 },
396 }
397 job := newTestFunctionOnlyJob()
398 job.module = m
399 job.updateEvery = 1
400
401 go func() {
402 for i := 1; i < 3; i++ {
403 job.Tick(i)
404 time.Sleep(time.Second)
405 }
406 job.Stop()
407 }()
408
409 job.Start()
410
411 assert.False(t, collectCalled, "Collect should not be called for function-only jobs")
412 assert.True(t, m.CleanupDone)
413 }