Path: blob/main/pkg/river/token/builder/builder_test.go
4096 views
package builder_test12import (3"bytes"4"fmt"5"testing"6"time"78"github.com/grafana/agent/pkg/river/parser"9"github.com/grafana/agent/pkg/river/printer"10"github.com/grafana/agent/pkg/river/token"11"github.com/grafana/agent/pkg/river/token/builder"12"github.com/stretchr/testify/require"13)1415func TestBuilder_File(t *testing.T) {16f := builder.NewFile()1718f.Body().SetAttributeTokens("attr_1", []builder.Token{{Tok: token.NUMBER, Lit: "15"}})19f.Body().SetAttributeTokens("attr_2", []builder.Token{{Tok: token.BOOL, Lit: "true"}})2021b1 := builder.NewBlock([]string{"test", "block"}, "")22b1.Body().SetAttributeTokens("inner_attr", []builder.Token{{Tok: token.STRING, Lit: `"block 1"`}})23f.Body().AppendBlock(b1)2425b2 := builder.NewBlock([]string{"test", "block"}, "labeled")26b2.Body().SetAttributeTokens("inner_attr", []builder.Token{{Tok: token.STRING, Lit: `"block 2"`}})27f.Body().AppendBlock(b2)2829expect := format(t, `30attr_1 = 1531attr_2 = true3233test.block {34inner_attr = "block 1"35}3637test.block "labeled" {38inner_attr = "block 2"39}40`)4142require.Equal(t, expect, string(f.Bytes()))43}4445func TestBuilder_GoEncode(t *testing.T) {46f := builder.NewFile()4748f.Body().AppendTokens([]builder.Token{{token.COMMENT, "// Hello, world!"}})49f.Body().SetAttributeValue("null_value", nil)50f.Body().AppendTokens([]builder.Token{{token.LITERAL, "\n"}})5152f.Body().SetAttributeValue("num", 15)53f.Body().SetAttributeValue("string", "Hello, world!")54f.Body().SetAttributeValue("bool", true)55f.Body().SetAttributeValue("list", []int{0, 1, 2})56f.Body().SetAttributeValue("func", func(int, int) int { return 0 })57f.Body().SetAttributeValue("capsule", make(chan int))58f.Body().AppendTokens([]builder.Token{{token.LITERAL, "\n"}})5960f.Body().SetAttributeValue("map", map[string]interface{}{"foo": "bar"})61f.Body().SetAttributeValue("map_2", map[string]interface{}{"non ident": "bar"})62f.Body().AppendTokens([]builder.Token{{token.LITERAL, "\n"}})6364f.Body().SetAttributeValue("mixed_list", []interface{}{650,66true,67map[string]interface{}{"key": true},68"Hello!",69})7071expect := format(t, `72// Hello, world!73null_value = null7475num = 1576string = "Hello, world!"77bool = true78list = [0, 1, 2]79func = function80capsule = capsule("chan int")8182map = {83foo = "bar",84}85map_2 = {86"non ident" = "bar",87}8889mixed_list = [0, true, {90key = true,91}, "Hello!"]92`)9394require.Equal(t, expect, string(f.Bytes()))95}9697// TestBuilder_GoEncode_SortMapKeys ensures that object literals from unordered98// values (i.e., Go maps) are printed in a deterministic order by sorting the99// keys lexicographically. Other object literals should be printed in the order100// the keys are reported in (i.e., in the order presented by the Go structs).101func TestBuilder_GoEncode_SortMapKeys(t *testing.T) {102f := builder.NewFile()103104type Ordered struct {105SomeKey string `river:"some_key,attr"`106OtherKey string `river:"other_key,attr"`107}108109// Maps are unordered because you can't iterate over their keys in a110// consistent order.111var unordered = map[string]interface{}{112"key_a": 1,113"key_c": 3,114"key_b": 2,115}116117f.Body().SetAttributeValue("ordered", Ordered{SomeKey: "foo", OtherKey: "bar"})118f.Body().SetAttributeValue("unordered", unordered)119120expect := format(t, `121ordered = {122some_key = "foo",123other_key = "bar",124}125unordered = {126key_a = 1,127key_b = 2,128key_c = 3,129}130`)131132require.Equal(t, expect, string(f.Bytes()))133}134135func TestBuilder_AppendFrom(t *testing.T) {136type InnerBlock struct {137Number int `river:"number,attr"`138}139140type Structure struct {141Field string `river:"field,attr"`142143Block InnerBlock `river:"block,block"`144OtherBlocks []InnerBlock `river:"other_block,block"`145}146147f := builder.NewFile()148f.Body().AppendFrom(Structure{149Field: "some_value",150151Block: InnerBlock{Number: 1},152OtherBlocks: []InnerBlock{153{Number: 2},154{Number: 3},155},156})157158expect := format(t, `159field = "some_value"160161block {162number = 1163}164165other_block {166number = 2167}168169other_block {170number = 3171}172`)173174require.Equal(t, expect, string(f.Bytes()))175}176177func TestBuilder_AppendFrom_EnumSlice(t *testing.T) {178type InnerBlock struct {179Number int `river:"number,attr"`180}181182type EnumBlock struct {183BlockA InnerBlock `river:"a,block,optional"`184BlockB InnerBlock `river:"b,block,optional"`185BlockC InnerBlock `river:"c,block,optional"`186}187188type Structure struct {189Field string `river:"field,attr"`190191OtherBlocks []EnumBlock `river:"block,enum"`192}193194f := builder.NewFile()195f.Body().AppendFrom(Structure{196Field: "some_value",197OtherBlocks: []EnumBlock{198{BlockC: InnerBlock{Number: 1}},199{BlockB: InnerBlock{Number: 2}},200{BlockC: InnerBlock{Number: 3}},201},202})203204expect := format(t, `205field = "some_value"206207block.c {208number = 1209}210211block.b {212number = 2213}214215block.c {216number = 3217}218`)219220require.Equal(t, expect, string(f.Bytes()))221}222223func TestBuilder_AppendFrom_EnumSlice_Pointer(t *testing.T) {224type InnerBlock struct {225Number int `river:"number,attr"`226}227228type EnumBlock struct {229BlockA *InnerBlock `river:"a,block,optional"`230BlockB *InnerBlock `river:"b,block,optional"`231BlockC *InnerBlock `river:"c,block,optional"`232}233234type Structure struct {235Field string `river:"field,attr"`236237OtherBlocks []EnumBlock `river:"block,enum"`238}239240f := builder.NewFile()241f.Body().AppendFrom(Structure{242Field: "some_value",243OtherBlocks: []EnumBlock{244{BlockC: &InnerBlock{Number: 1}},245{BlockB: &InnerBlock{Number: 2}},246{BlockC: &InnerBlock{Number: 3}},247},248})249250expect := format(t, `251field = "some_value"252253block.c {254number = 1255}256257block.b {258number = 2259}260261block.c {262number = 3263}264`)265266require.Equal(t, expect, string(f.Bytes()))267}268269func TestBuilder_SkipOptional(t *testing.T) {270type Structure struct {271OptFieldA string `river:"opt_field_a,attr,optional"`272OptFieldB string `river:"opt_field_b,attr,optional"`273ReqFieldA string `river:"req_field_a,attr"`274ReqFieldB string `river:"req_field_b,attr"`275}276277f := builder.NewFile()278f.Body().AppendFrom(Structure{279OptFieldA: "some_value",280OptFieldB: "", // Zero value281ReqFieldA: "some_value",282ReqFieldB: "", // Zero value283})284285expect := format(t, `286opt_field_a = "some_value"287req_field_a = "some_value"288req_field_b = ""289`)290291require.Equal(t, expect, string(f.Bytes()))292}293294func format(t *testing.T, in string) string {295t.Helper()296297f, err := parser.ParseFile(t.Name(), []byte(in))298require.NoError(t, err)299300var buf bytes.Buffer301require.NoError(t, printer.Fprint(&buf, f))302303return buf.String()304}305306type CustomTokenizer bool307308var _ builder.Tokenizer = (CustomTokenizer)(false)309310func (ct CustomTokenizer) RiverTokenize() []builder.Token {311return []builder.Token{{Tok: token.LITERAL, Lit: "CUSTOM_TOKENS"}}312}313314func TestBuilder_GoEncode_Tokenizer(t *testing.T) {315t.Run("Tokenizer", func(t *testing.T) {316f := builder.NewFile()317f.Body().SetAttributeValue("value", CustomTokenizer(true))318319expect := format(t, `value = CUSTOM_TOKENS`)320require.Equal(t, expect, string(f.Bytes()))321})322323t.Run("TextMarshaler", func(t *testing.T) {324now := time.Now()325expectBytes, err := now.MarshalText()326require.NoError(t, err)327328f := builder.NewFile()329f.Body().SetAttributeValue("value", now)330331expect := format(t, fmt.Sprintf(`value = %q`, string(expectBytes)))332require.Equal(t, expect, string(f.Bytes()))333})334335t.Run("Duration", func(t *testing.T) {336dur := 15 * time.Second337338f := builder.NewFile()339f.Body().SetAttributeValue("value", dur)340341expect := format(t, fmt.Sprintf(`value = %q`, dur.String()))342require.Equal(t, expect, string(f.Bytes()))343})344}345346347