Skip to content

Commit

Permalink
Add tests for types other than integers
Browse files Browse the repository at this point in the history
  • Loading branch information
ptodev committed Jan 17, 2025
1 parent 30766dd commit aaaff3e
Show file tree
Hide file tree
Showing 13 changed files with 417 additions and 3 deletions.
55 changes: 55 additions & 0 deletions internal/runtime/foreach_stringer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package runtime_test

import (
"context"
"os"
"path/filepath"
"sync"
"testing"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestForeachStringer(t *testing.T) {
directory := "./testdata/foreach_stringer"
for _, file := range getTestFiles(directory, t) {
tc := buildTestForEach(t, filepath.Join(directory, file.Name()))
t.Run(file.Name(), func(t *testing.T) {
if tc.module != "" {
defer os.Remove("module.alloy")
require.NoError(t, os.WriteFile("module.alloy", []byte(tc.module), 0664))
}
testConfigForEachStringer(t, tc.main, *tc.expectedDebugInfo)
})
}
}

func testConfigForEachStringer(t *testing.T, config string, expectedDebugInfo string) {
defer verifyNoGoroutineLeaks(t)
reg := prometheus.NewRegistry()
ctrl, f := setup(t, config, reg)

err := ctrl.LoadSource(f, nil, "")
require.NoError(t, err)

ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
defer func() {
cancel()
wg.Wait()
}()

wg.Add(1)
go func() {
defer wg.Done()
ctrl.Run(ctx)
}()

require.EventuallyWithT(t, func(c *assert.CollectT) {
debugInfo := getDebugInfo[string](t, ctrl, "", "testcomponents.string_receiver.log")
require.Equal(t, expectedDebugInfo, debugInfo)
}, 3*time.Second, 10*time.Millisecond)
}
6 changes: 5 additions & 1 deletion internal/runtime/foreach_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestForeach(t *testing.T) {
directory := "./testdata/foreach"
for _, file := range getTestFiles(directory, t) {
tc := buildTestForEach(t, filepath.Join(directory, file.Name()))
t.Run(tc.description, func(t *testing.T) {
t.Run(file.Name(), func(t *testing.T) {
if tc.module != "" {
defer os.Remove("module.alloy")
require.NoError(t, os.WriteFile("module.alloy", []byte(tc.module), 0664))
Expand Down Expand Up @@ -67,6 +67,7 @@ type testForEachFile struct {
update *updateFile // update can be used to update the content of a file at runtime
expectedMetrics *string // expected prometheus metrics
expectedDurationMetrics *int // expected prometheus duration metrics - check those separately as they vary with each test run
expectedDebugInfo *string // expected debug info after running the config
}

func buildTestForEach(t *testing.T, filename string) testForEachFile {
Expand Down Expand Up @@ -95,6 +96,9 @@ func buildTestForEach(t *testing.T, filename string) testForEachFile {
expectedDurationMetrics, err := strconv.Atoi(strings.TrimSpace(string((alloyConfig.Data))))
require.NoError(t, err)
tc.expectedDurationMetrics = &expectedDurationMetrics
case "expected_debug_info.txt":
expectedDebugInfo := string(alloyConfig.Data)
tc.expectedDebugInfo = &expectedDebugInfo
}
}
return tc
Expand Down
16 changes: 15 additions & 1 deletion internal/runtime/import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -393,5 +393,19 @@ func getTestFiles(directory string, t *testing.T) []fs.FileInfo {
files, err := dir.Readdir(-1)
require.NoError(t, err)

return files
// Don't use files which start with a dot (".").
// This is to prevent the test suite from using files such as ".DS_Store",
// which Visual Studio Code may add.
return filterFiles(files, ".")
}

// Only take into account files which don't have a certain prefix.
func filterFiles(files []fs.FileInfo, denylistedPrefix string) []fs.FileInfo {
res := make([]fs.FileInfo, 0, len(files))
for _, file := range files {
if !strings.HasPrefix(file.Name(), denylistedPrefix) {
res = append(res, file)
}
}
return res
}
8 changes: 7 additions & 1 deletion internal/runtime/internal/controller/node_config_foreach.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"hash/fnv"
"path"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -381,9 +382,14 @@ func computeHash(s string) string {
}

func hashObject(obj any) string {
//TODO: Test what happens if there is a "true" string and a true bool in the collection.
switch v := obj.(type) {
case int, string, float64, bool:
case int, string, bool:
return fmt.Sprintf("%v", v)
case float64:
// Dots are not valid characters in Alloy syntax identifiers.
// For example, "foreach_3.14_1" should become "foreach_3_14_1".
return strings.Replace(fmt.Sprintf("%f", v), ".", "_", -1)
default:
return computeHash(fmt.Sprintf("%#v", v))
}
Expand Down
91 changes: 91 additions & 0 deletions internal/runtime/internal/testcomponents/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package testcomponents

import (
"context"
"sync"

"github.com/go-kit/log"
"github.com/grafana/alloy/internal/component"
"github.com/grafana/alloy/internal/featuregate"
)

func init() {
component.Register(component.Registration{
Name: "testcomponents.string_receiver",
Stability: featuregate.StabilityPublicPreview,
Args: StringReceiverConfig{},
Exports: StringReceiverExports{},

Build: func(opts component.Options, args component.Arguments) (component.Component, error) {
return NewStringReceiverComp(opts, args.(StringReceiverConfig))
},
})
}

type StringReceiverConfig struct {
}

type StringReceiver interface {
Receive(string)
}

type StringReceiverImpl struct {
log func(string)
}

func (r StringReceiverImpl) Receive(s string) {
r.log(s)
}

type StringReceiverExports struct {
Receiver StringReceiver `alloy:"receiver,attr"`
}

type StringReceiverComponent struct {
opts component.Options
log log.Logger

mut sync.Mutex
recvStr string
receiver StringReceiver
}

// NewStringReceiver creates a new summation component.
func NewStringReceiverComp(o component.Options, cfg StringReceiverConfig) (*StringReceiverComponent, error) {
s := &StringReceiverComponent{opts: o, log: o.Logger}
s.receiver = StringReceiverImpl{
log: func(str string) {
s.mut.Lock()
defer s.mut.Unlock()
s.recvStr += str + "\n"
},
}

o.OnStateChange(StringReceiverExports{
Receiver: s.receiver,
})

return s, nil
}

var (
_ component.Component = (*StringReceiverComponent)(nil)
)

// Run implements Component.
func (s *StringReceiverComponent) Run(ctx context.Context) error {
<-ctx.Done()
return nil
}

// Return the sum as debug info instead of export to avoid evaluation loop.
func (s *StringReceiverComponent) DebugInfo() interface{} {
s.mut.Lock()
defer s.mut.Unlock()
return s.recvStr
}

// Update implements Component.
func (s *StringReceiverComponent) Update(args component.Arguments) error {
return nil
}
95 changes: 95 additions & 0 deletions internal/runtime/internal/testcomponents/stringer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package testcomponents

import (
"context"
"fmt"

"github.com/go-kit/log"
"github.com/grafana/alloy/internal/component"
"github.com/grafana/alloy/internal/featuregate"
)

// testcomponents.stringer takes in an Alloy value, converts it to a string, and forwards it to the defined receivers.
func init() {
component.Register(component.Registration{
Name: "testcomponents.stringer",
Stability: featuregate.StabilityPublicPreview,
Args: StringerConfig{},

Build: func(opts component.Options, args component.Arguments) (component.Component, error) {
return NewStringer(opts, args.(StringerConfig))
},
})
}

type StringerConfig struct {
InputString *string `alloy:"input_string,attr,optional"`
InputInt *int `alloy:"input_int,attr,optional"`
InputFloat *float64 `alloy:"input_float,attr,optional"`
InputBool *bool `alloy:"input_bool,attr,optional"`
InputMap *map[string]any `alloy:"input_map,attr,optional"`
InputArray *[]any `alloy:"input_array,attr,optional"`
ForwardTo []StringReceiver `alloy:"forward_to,attr"`
}

type Stringer struct {
opts component.Options
log log.Logger
cfgUpdate chan StringerConfig
}

func NewStringer(o component.Options, cfg StringerConfig) (*Stringer, error) {
t := &Stringer{
opts: o,
log: o.Logger,
cfgUpdate: make(chan StringerConfig, 10),
}
return t, nil
}

var (
_ component.Component = (*Stringer)(nil)
)

func forward(val any, to []StringReceiver) {
for _, r := range to {
str := fmt.Sprintf("%#v", val)
r.Receive(str)
}
}

func (s *Stringer) Run(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return nil
case cfg := <-s.cfgUpdate:
// Send the new values to the receivers
if cfg.InputString != nil {
forward(*cfg.InputString, cfg.ForwardTo)
}
if cfg.InputInt != nil {
forward(*cfg.InputInt, cfg.ForwardTo)
}
if cfg.InputFloat != nil {
forward(*cfg.InputFloat, cfg.ForwardTo)
}
if cfg.InputBool != nil {
forward(*cfg.InputBool, cfg.ForwardTo)
}
if cfg.InputArray != nil {
forward(*cfg.InputArray, cfg.ForwardTo)
}
if cfg.InputMap != nil {
forward(*cfg.InputMap, cfg.ForwardTo)
}
}
}
}

// Update implements Component.
func (s *Stringer) Update(args component.Arguments) error {
cfg := args.(StringerConfig)
s.cfgUpdate <- cfg
return nil
}
20 changes: 20 additions & 0 deletions internal/runtime/testdata/foreach/foreach_10.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
A collection containing arrays.

-- main.alloy --
foreach "testForeach" {
collection = [[10, 4, 100], [20, 6, 200]]
var = "num"

template {
testcomponents.pulse "pt" {
// Only ingest the 4 and the 6.
max = num[1]
frequency = "10ms"
forward_to = [testcomponents.summation_receiver.sum.receiver]
}
}
}

// Similar to testcomponents.summation, but with a "receiver" export
testcomponents.summation_receiver "sum" {
}
19 changes: 19 additions & 0 deletions internal/runtime/testdata/foreach/foreach_9.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
A collection containing maps.

-- main.alloy --
foreach "testForeach" {
collection = [{"a" = 4}, {"a" = 6}]
var = "num"

template {
testcomponents.pulse "pt" {
max = num["a"]
frequency = "10ms"
forward_to = [testcomponents.summation_receiver.sum.receiver]
}
}
}

// Similar to testcomponents.summation, but with a "receiver" export
testcomponents.summation_receiver "sum" {
}
22 changes: 22 additions & 0 deletions internal/runtime/testdata/foreach_stringer/foreach_1.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
A collection containing an int.

-- main.alloy --
foreach "testForeach" {
collection = [1]
var = "item"

template {
testcomponents.stringer "st" {
input_int = item
forward_to = [testcomponents.string_receiver.log.receiver]
}
}
}

// Receive strings and append them to a log,
// separated by a new line.
testcomponents.string_receiver "log" {
}

-- expected_debug_info.txt --
1
Loading

0 comments on commit aaaff3e

Please sign in to comment.