diff --git a/plugins/all_test.go b/plugins/all_test.go new file mode 100644 index 000000000..f199380f4 --- /dev/null +++ b/plugins/all_test.go @@ -0,0 +1,115 @@ +package all + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/fs" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +// exceptionMap holds those plugins which differ in conventions when defining plugins. +// Most plugins follow the convention of pkg-name to plugin-name. +// For ex, Pivot processor pkg github.com/influxdata/telegraf/plugins/processors/pivot maps directly to +// the last element of the pkg i.e "pivot" +// But in case of "aws_ec2" processor, the pkg is defined as "github.com/influxdata/telegraf/plugins/processors/aws/ec2". +// This ensures package names are not tied with plugin names. +// it should be of the form : +var exceptionMap = map[string]string{ + "github.com/influxdata/telegraf/plugins/processors/aws/ec2": "aws_ec2", +} + +func Test_AllPlugins(t *testing.T) { + pluginDirs := []string{"aggregators", "inputs", "outputs", "parsers", "processors", "secretstores"} + for _, dir := range pluginDirs { + testPluginDirectory(t, dir) + } +} + +func testPluginDirectory(t *testing.T, directory string) { + allDir := filepath.Join(directory, "all") + err := filepath.WalkDir(allDir, func(goPluginFile string, d fs.DirEntry, walkErr error) error { + require.NoError(t, walkErr) + if d.IsDir() || strings.HasSuffix(d.Name(), "_test.go") || strings.EqualFold(d.Name(), "all.go") { + return nil + } + t.Run(goPluginFile, func(t *testing.T) { + parseSourceFile(t, goPluginFile, directory) + }) + return nil + }) + require.NoError(t, err) +} + +func parseSourceFile(t *testing.T, goPluginFile string, pluginCategory string) { + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, goPluginFile, nil, parser.ParseComments) + require.NoError(t, err) + + foundGoBuild := false + for _, cg := range node.Comments { + for _, comm := range cg.List { + if !strings.HasPrefix(comm.Text, "//go:build") { + continue + } + foundGoBuild = true + plugin := resolvePluginFromImports(t, node.Imports) + testBuildTags(t, comm.Text, pluginCategory, plugin) + } + } + require.Truef(t, foundGoBuild, "%s does not contain go:build tag", goPluginFile) +} + +func resolvePluginFromImports(t *testing.T, imports []*ast.ImportSpec) string { + // should contain one or more import statements + require.NotEmpty(t, imports) + + // trim the path surrounded by quotes + importPath := strings.Trim(imports[0].Path.Value, "\"") + + // check if present in exceptionMap + plugin, ok := exceptionMap[importPath] + if ok { + return plugin + } + return filepath.Base(importPath) +} + +func testBuildTags(t *testing.T, buildComment string, pluginCategory string, plugin string) { + tags := strings.Split(buildComment, "||") + // tags might contain spaces and hence trim + tags = stringMap(tags, strings.TrimSpace) + + require.Len(t, tags, 3) + require.Contains(t, tags, "!custom") + require.Contains(t, tags, pluginCategory) + + actual := getPluginBuildTag(tags, pluginCategory) + expected := fmt.Sprintf("%s.%s", pluginCategory, plugin) + require.Equal(t, expected, actual, "invalid build tag") +} + +// getPluginBuildTag takes a slice of tags and returns the build tag corresponding to this plugin type. +// +// For ex ["!custom", "inputs", "inputs.docker"] returns "inputs.docker" +func getPluginBuildTag(tags []string, pluginCategory string) string { + for _, tag := range tags { + if strings.HasPrefix(tag, fmt.Sprintf("%s.", pluginCategory)) { + return tag + } + } + return "" +} + +func stringMap(elems []string, transFormFunc func(string) string) []string { + result := make([]string, len(elems)) + for i, elem := range elems { + result[i] = strings.TrimPrefix(transFormFunc(elem), "//go:build ") + } + return result +}