1
0
Fork 0
telegraf/plugins/inputs/example/example_test.go
Daniel Baumann 4978089aab
Adding upstream version 1.34.4.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-24 07:26:29 +02:00

439 lines
11 KiB
Go

package example
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/testutil"
)
// This file should contain a set of unit-tests to cover your plugin. This will ease
// spotting bugs and mistakes when later modifying or extending the functionality.
// To do so, please write one 'TestXYZ' function per 'case' e.g. default init,
// things that should fail or expected values from a mockup.
func TestInitDefault(t *testing.T) {
// This test should succeed with the default initialization.
// Use whatever you use in the init() function plus the mandatory options.
// ATTENTION: Always initialize the "Log" as you will get SIGSEGV otherwise.
plugin := &Example{
DeviceName: "test",
Timeout: config.Duration(100 * time.Millisecond),
Log: testutil.Logger{},
}
// Test the initialization succeeds
require.NoError(t, plugin.Init())
// Also test that default values are set correctly
require.Equal(t, config.Duration(100*time.Millisecond), plugin.Timeout)
require.Equal(t, "test", plugin.DeviceName)
require.Equal(t, int64(2), plugin.NumberFields)
}
func TestInitFail(t *testing.T) {
// You should also test for your safety nets to work i.e. you get errors for
// invalid configuration-option values. So check your error paths in Init()
// and check if you reach them
// We setup a table-test here to specify "setting" - "expected error" values.
// Even though it seems overkill here for the example plugin, we reuse this structure
// later for checking the metrics
tests := []struct {
name string
plugin *Example
expected string
}{
{
name: "all empty",
plugin: &Example{},
expected: "device name cannot be empty",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Always initialize the logger to avoid SIGSEGV. This is done automatically by
// telegraf during normal operation.
tt.plugin.Log = testutil.Logger{}
err := tt.plugin.Init()
require.Error(t, err)
require.EqualError(t, err, tt.expected)
})
}
}
func TestFixedValue(t *testing.T) {
// You can organize the test e.g. by operation mode (like we do here random vs. fixed), by features or
// by different metrics gathered. Please choose the partitioning most suited for your plugin
// We again setup a table-test here to specify "setting" - "expected output metric" pairs.
tests := []struct {
name string
plugin *Example
expected []telegraf.Metric
}{
{
name: "count only",
plugin: &Example{
DeviceName: "test",
NumberFields: 1,
},
expected: []telegraf.Metric{
testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 1,
},
time.Unix(0, 0),
),
testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 2,
},
time.Unix(0, 0),
),
testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 3,
},
time.Unix(0, 0),
),
},
},
{
name: "default settings",
plugin: &Example{
DeviceName: "test",
},
expected: []telegraf.Metric{
testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 1,
"field1": float64(0),
},
time.Unix(0, 0),
),
testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 2,
"field1": float64(0),
},
time.Unix(0, 0),
),
testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 3,
"field1": float64(0),
},
time.Unix(0, 0),
),
},
},
{
name: "more fields",
plugin: &Example{
DeviceName: "test",
NumberFields: 4,
},
expected: []telegraf.Metric{
testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 1,
"field1": float64(0),
"field2": float64(0),
"field3": float64(0),
},
time.Unix(0, 0),
),
testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 2,
"field1": float64(0),
"field2": float64(0),
"field3": float64(0),
},
time.Unix(0, 0),
),
testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 3,
"field1": float64(0),
"field2": float64(0),
"field3": float64(0),
},
time.Unix(0, 0),
),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var acc testutil.Accumulator
tt.plugin.Log = testutil.Logger{}
require.NoError(t, tt.plugin.Init())
// Call gather and check no error occurs. In case you use acc.AddError() somewhere
// in your code, it is not sufficient to only check the return value of Gather().
require.NoError(t, tt.plugin.Gather(&acc))
require.Empty(t, acc.Errors, "found errors accumulated by acc.AddError()")
// Wait for the expected number of metrics to avoid flaky tests due to
// race conditions.
acc.Wait(len(tt.expected))
// Compare the metrics in a convenient way. Here we ignore
// the metric time during comparison as we cannot inject the time
// during test. For more comparison options check testutil package.
testutil.RequireMetricsEqual(t, tt.expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
})
}
}
func TestRandomValue(t *testing.T) {
// Sometimes, you cannot know the exact outcome of the gather cycle e.g. if the gathering involves random data.
// However, you should check the result nevertheless, applying as many conditions as you can.
// We again setup a table-test here to specify "setting" - "expected output metric" pairs.
tests := []struct {
name string
plugin *Example
template telegraf.Metric
}{
{
name: "count only",
plugin: &Example{
DeviceName: "test",
NumberFields: 1,
EnableRandomVariable: true,
},
template: testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 1,
},
time.Unix(0, 0),
),
},
{
name: "default settings",
plugin: &Example{
DeviceName: "test",
EnableRandomVariable: true,
},
template: testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 1,
"field1": float64(0),
},
time.Unix(0, 0),
),
},
{
name: "more fields",
plugin: &Example{
DeviceName: "test",
NumberFields: 4,
EnableRandomVariable: true,
},
template: testutil.MustMetric(
"example",
map[string]string{
"device": "test",
},
map[string]interface{}{
"count": 1,
"field1": float64(0),
"field2": float64(0),
"field3": float64(0),
},
time.Unix(0, 0),
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var acc testutil.Accumulator
tt.plugin.Log = testutil.Logger{}
require.NoError(t, tt.plugin.Init())
// Call gather and check no error occurs. In case you use acc.AddError() somewhere
// in your code, it is not sufficient to only check the return value of Gather().
require.NoError(t, tt.plugin.Gather(&acc))
require.Empty(t, acc.Errors, "found errors accumulated by acc.AddError()")
// Wait for the expected number of metrics to avoid flaky tests due to
// race conditions.
acc.Wait(3)
// Compare all aspects of the metric that are known to you
for i, m := range acc.GetTelegrafMetrics() {
require.Equal(t, m.Name(), tt.template.Name())
require.Equal(t, m.Tags(), tt.template.Tags())
// Check if all expected fields are there
fields := m.Fields()
for k := range tt.template.Fields() {
if k == "count" {
require.Equal(t, fields["count"], int64(i+1))
continue
}
_, found := fields[k]
require.Truef(t, found, "field %q not found", k)
}
}
})
}
}
func TestGatherFail(t *testing.T) {
// You should also test for error conditions in your Gather() method. Try to cover all error paths.
// We again setup a table-test here to specify "setting" - "expected error" pair.
tests := []struct {
name string
plugin *Example
expected string
}{
{
name: "too many fields",
plugin: &Example{
DeviceName: "test",
NumberFields: 11,
},
expected: "too many fields",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var acc testutil.Accumulator
tt.plugin.Log = testutil.Logger{}
require.NoError(t, tt.plugin.Init())
err := tt.plugin.Gather(&acc)
require.Error(t, err)
require.EqualError(t, err, tt.expected)
})
}
}
func TestRandomValueFailPartial(t *testing.T) {
// You should also test for error conditions in your Gather() with partial output. This is required when
// using acc.AddError() as Gather() might succeed (return nil) but there are some metrics missing.
// We again setup a table-test here to specify "setting" - "expected output metric" and "errors".
tests := []struct {
name string
plugin *Example
expected []telegraf.Metric
expectedErr string
}{
{
name: "flappy gather",
plugin: &Example{
DeviceName: "flappy",
NumberFields: 1,
EnableRandomVariable: true,
},
expected: []telegraf.Metric{
testutil.MustMetric(
"example",
map[string]string{
"device": "flappy",
},
map[string]interface{}{
"count": 1,
},
time.Unix(0, 0),
),
testutil.MustMetric(
"example",
map[string]string{
"device": "flappy",
},
map[string]interface{}{
"count": 2,
},
time.Unix(0, 0),
),
},
expectedErr: "too many runs for random values",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var acc testutil.Accumulator
tt.plugin.Log = testutil.Logger{}
require.NoError(t, tt.plugin.Init())
// Call gather and check no error occurs. However, we expect an error accumulated by acc.AddError()
require.NoError(t, tt.plugin.Gather(&acc))
// Wait for the expected number of metrics to avoid flaky tests due to
// race conditions.
acc.Wait(len(tt.expected))
// Check the accumulated errors
require.Len(t, acc.Errors, 1)
require.EqualError(t, acc.Errors[0], tt.expectedErr)
// Compare the expected partial metrics.
testutil.RequireMetricsEqual(t, tt.expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
})
}
}