Path: blob/main/component/module/string/string_test.go
4096 views
//go:build linux12package string34import (5"bytes"6"context"7"os"8"path/filepath"9"strings"10"testing"11"time"1213_ "github.com/grafana/agent/component/local/file"14"github.com/grafana/agent/pkg/cluster"15"github.com/grafana/agent/pkg/flow"16"github.com/grafana/agent/pkg/flow/logging"17"github.com/stretchr/testify/require"18)1920const loggingConfig = `21logging {}`2223const tracingConfig = `24tracing {}`2526const argumentConfig = `27argument "username" {}28argument "defaulted" {29optional = true30default = "default_value"31}`3233const argumentModuleLoaderConfig = `34local.file "args" { filename = "%arg" }35module.string "importer" {36content = local.file.args.content37arguments {38username = module.string.exporter.exports.username39}40}`4142const exportStringConfig = `43export "username" {44value = "bob"45}`4647const exportComponentConfig = `48testcomponents.tick "t1" {49frequency = "1s"50}5152export "dummy" {53value = testcomponents.tick.t1.tick_time54}`5556const exportModuleLoaderConfig = `57local.file "exporter" { filename = "%exp" }5859module.string "exporter" {60content = local.file.exporter.content61}`6263func TestModule(t *testing.T) {64tt := []struct {65name string66riverContent string67argumentModuleContent string68exportModuleContent string69expectedComponentId string70expectedExports []string71expectedErrorContains string72}{73{74name: "Export String",75riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,76argumentModuleContent: argumentConfig,77exportModuleContent: exportStringConfig,78expectedComponentId: "module.string.importer",79expectedExports: []string{"bob"},80},81{82name: "Export Component",83riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,84argumentModuleContent: argumentConfig,85exportModuleContent: exportStringConfig + exportComponentConfig,86expectedComponentId: "module.string.exporter",87expectedExports: []string{"username", "dummy"},88},89{90name: "Empty Content Allowed",91riverContent: `module.string "empty" { content = "" }`,92},93{94name: "Argument blocks not allowed in parent config",95riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig + argumentConfig,96argumentModuleContent: argumentConfig,97exportModuleContent: exportStringConfig,98expectedErrorContains: "argument blocks only allowed inside a module",99},100{101name: "Export blocks not allowed in parent config",102riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig + exportStringConfig,103argumentModuleContent: argumentConfig,104exportModuleContent: exportStringConfig,105expectedErrorContains: "export blocks only allowed inside a module",106},107{108name: "Logging blocks not allowed in module config",109riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,110argumentModuleContent: argumentConfig + loggingConfig,111exportModuleContent: exportStringConfig,112expectedErrorContains: "logging block not allowed inside a module",113},114{115name: "Tracing blocks not allowed in module config",116riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,117argumentModuleContent: argumentConfig + tracingConfig,118exportModuleContent: exportStringConfig,119expectedErrorContains: "tracing block not allowed inside a module",120},121{122name: "Argument not defined in module source",123riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,124argumentModuleContent: `argument "different_argument" {}`,125exportModuleContent: exportStringConfig,126expectedErrorContains: "Provided argument \"username\" is not defined in the module",127},128{129name: "Missing required argument",130riverContent: exportModuleLoaderConfig + `131local.file "args" { filename = "%arg" }132module.string "importer" {133content = local.file.args.content134}`,135argumentModuleContent: argumentConfig,136exportModuleContent: exportStringConfig,137expectedErrorContains: "Failed to evaluate node for config block: missing required argument \"username\" to module",138},139{140name: "Duplicate logging config",141riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig + loggingConfig + loggingConfig,142argumentModuleContent: argumentConfig,143exportModuleContent: exportStringConfig,144expectedErrorContains: "\"logging\" block already declared",145},146{147name: "Duplicate tracing config",148riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig + tracingConfig + tracingConfig,149argumentModuleContent: argumentConfig,150exportModuleContent: exportStringConfig,151expectedErrorContains: "\"tracing\" block already declared",152},153{154name: "Duplicate argument config",155riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,156argumentModuleContent: argumentConfig + argumentConfig,157exportModuleContent: exportStringConfig,158expectedErrorContains: "\"argument.username\" block already declared",159},160{161name: "Duplicate export config",162riverContent: argumentModuleLoaderConfig + exportModuleLoaderConfig,163argumentModuleContent: argumentConfig,164exportModuleContent: exportStringConfig + exportStringConfig,165expectedErrorContains: "\"export.username\" block already declared",166},167}168169for _, tc := range tt {170t.Run(tc.name, func(t *testing.T) {171tmpDir := t.TempDir()172riverFile := tc.riverContent173174// Prep the argument module file175if tc.argumentModuleContent != "" {176argPath := filepath.Join(tmpDir, "args.river")177writeFile(t, argPath, tc.argumentModuleContent)178riverFile = strings.Replace(riverFile, "%arg", argPath, 1)179}180181// Prep the export module file182if tc.exportModuleContent != "" {183exportPath := filepath.Join(tmpDir, "export.river")184writeFile(t, exportPath, tc.exportModuleContent)185riverFile = strings.Replace(riverFile, "%exp", exportPath, 1)186}187188testFile(t, riverFile, tc.expectedComponentId, tc.expectedExports, tc.expectedErrorContains)189})190}191}192193func testFile(t *testing.T, fmtFile string, componentToFind string, searchable []string, expectedErrorContains string) {194f := flow.New(testOptions(t))195ff, err := flow.ReadFile("test", []byte(fmtFile))196require.NoError(t, err)197err = f.LoadFile(ff, nil)198if expectedErrorContains == "" {199require.NoError(t, err)200} else {201require.ErrorContains(t, err, expectedErrorContains)202return203}204205ctx := context.Background()206ctx, cncl := context.WithTimeout(ctx, 20*time.Second)207defer cncl()208go f.Run(ctx)209time.Sleep(3 * time.Second)210infos := f.ComponentInfos()211for _, i := range infos {212if i.ID != componentToFind {213continue214}215buf := bytes.NewBuffer(nil)216err = f.ComponentJSON(buf, i)217require.NoError(t, err)218// This ensures that although dummy is not used it still exists.219// And that multiple exports are displayed when only one of them is updating.220for _, s := range searchable {221require.True(t, strings.Contains(buf.String(), s))222}223}224}225226func writeFile(t *testing.T, path, content string) {227err := os.WriteFile(path, []byte(content), 0666)228require.NoError(t, err)229}230231func testOptions(t *testing.T) flow.Options {232t.Helper()233234s, err := logging.WriterSink(os.Stderr, logging.DefaultSinkOptions)235require.NoError(t, err)236237c := &cluster.Clusterer{Node: cluster.NewLocalNode("")}238239return flow.Options{240LogSink: s,241DataPath: t.TempDir(),242Reg: nil,243Clusterer: c,244}245}246247248