Path: blob/main/pkg/river/internal/value/decode_test.go
4096 views
package value_test12import (3"fmt"4"reflect"5"testing"6"time"7"unsafe"89"github.com/grafana/agent/pkg/river/internal/value"10"github.com/stretchr/testify/assert"11"github.com/stretchr/testify/require"12)1314func TestDecode_Numbers(t *testing.T) {15// There's a lot of values that can represent numbers, so we construct a16// matrix dynamically of all the combinations here.17vals := []interface{}{18int(15), int8(15), int16(15), int32(15), int64(15),19uint(15), uint8(15), uint16(15), uint32(15), uint64(15),20float32(15), float64(15),21string("15"), // string holding a valid number (which can be converted to a number)22}2324for _, input := range vals {25for _, expect := range vals {26val := value.Encode(input)2728name := fmt.Sprintf(29"%s to %s",30reflect.TypeOf(input),31reflect.TypeOf(expect),32)3334t.Run(name, func(t *testing.T) {35vPtr := reflect.New(reflect.TypeOf(expect)).Interface()36require.NoError(t, value.Decode(val, vPtr))3738actual := reflect.ValueOf(vPtr).Elem().Interface()39require.Equal(t, expect, actual)40})41}42}43}4445func TestDecode(t *testing.T) {46// Declare some types to use for testing. Person2 is used as a struct47// equivalent to Person, but with a different Go type to force casting.48type Person struct {49Name string `river:"name,attr"`50}5152type Person2 struct {53Name string `river:"name,attr"`54}5556tt := []struct {57input, expect interface{}58}{59{nil, (*int)(nil)},6061// Non-number primitives.62{string("Hello!"), string("Hello!")},63{bool(true), bool(true)},6465// Arrays66{[]int{1, 2, 3}, []int{1, 2, 3}},67{[]int{1, 2, 3}, [...]int{1, 2, 3}},68{[...]int{1, 2, 3}, []int{1, 2, 3}},69{[...]int{1, 2, 3}, [...]int{1, 2, 3}},7071// Maps72{map[string]int{"year": 2022}, map[string]uint{"year": 2022}},73{map[string]string{"name": "John"}, map[string]string{"name": "John"}},74{map[string]string{"name": "John"}, Person{Name: "John"}},75{Person{Name: "John"}, map[string]string{"name": "John"}},76{Person{Name: "John"}, Person{Name: "John"}},77{Person{Name: "John"}, Person2{Name: "John"}},78{Person2{Name: "John"}, Person{Name: "John"}},7980// NOTE(rfratto): we don't test capsules or functions here because they're81// not comparable in the same way as we do the other tests.82//83// See TestDecode_Functions and TestDecode_Capsules for specific decoding84// tests of those types.85}8687for _, tc := range tt {88val := value.Encode(tc.input)8990name := fmt.Sprintf(91"%s (%s) to %s",92val.Type(),93reflect.TypeOf(tc.input),94reflect.TypeOf(tc.expect),95)9697t.Run(name, func(t *testing.T) {98vPtr := reflect.New(reflect.TypeOf(tc.expect)).Interface()99require.NoError(t, value.Decode(val, vPtr))100101actual := reflect.ValueOf(vPtr).Elem().Interface()102103require.Equal(t, tc.expect, actual)104})105}106}107108// TestDecode_PreservePointer ensures that pointer addresses can be preserved109// when decoding.110func TestDecode_PreservePointer(t *testing.T) {111num := 5112val := value.Encode(&num)113114var nump *int115require.NoError(t, value.Decode(val, &nump))116require.Equal(t, unsafe.Pointer(nump), unsafe.Pointer(&num))117}118119// TestDecode_PreserveMapReference ensures that map references can be preserved120// when decoding.121func TestDecode_PreserveMapReference(t *testing.T) {122m := make(map[string]string)123val := value.Encode(m)124125var actual map[string]string126require.NoError(t, value.Decode(val, &actual))127128// We can't check to see if the pointers of m and actual match, but we can129// modify m to see if actual is also modified.130m["foo"] = "bar"131require.Equal(t, "bar", actual["foo"])132}133134// TestDecode_PreserveSliceReference ensures that slice references can be135// preserved when decoding.136func TestDecode_PreserveSliceReference(t *testing.T) {137s := make([]string, 3)138val := value.Encode(s)139140var actual []string141require.NoError(t, value.Decode(val, &actual))142143// We can't check to see if the pointers of m and actual match, but we can144// modify s to see if actual is also modified.145s[0] = "Hello, world!"146require.Equal(t, "Hello, world!", actual[0])147}148func TestDecode_Functions(t *testing.T) {149val := value.Encode(func() int { return 15 })150151var f func() int152require.NoError(t, value.Decode(val, &f))153require.Equal(t, 15, f())154}155156func TestDecode_Capsules(t *testing.T) {157expect := make(chan int, 5)158159var actual chan int160require.NoError(t, value.Decode(value.Encode(expect), &actual))161require.Equal(t, expect, actual)162}163164type ValueInterface interface{ SomeMethod() }165166type Value1 struct{ test string }167168func (c Value1) SomeMethod() {}169170// TestDecode_CapsuleInterface tests that we are able to decode when171// the target `into` is an interface.172func TestDecode_CapsuleInterface(t *testing.T) {173tt := []struct {174name string175value ValueInterface176expected ValueInterface177}{178{179name: "Capsule to Capsule",180value: Value1{test: "true"},181expected: Value1{test: "true"},182},183{184name: "Capsule Pointer to Capsule",185value: &Value1{test: "true"},186expected: &Value1{test: "true"},187},188}189190for _, tc := range tt {191t.Run(tc.name, func(t *testing.T) {192var actual ValueInterface193require.NoError(t, value.Decode(value.Encode(tc.value), &actual))194195// require.Same validates the memory address matches after Decode.196if reflect.TypeOf(tc.value).Kind() == reflect.Pointer {197require.Same(t, tc.value, actual)198}199200// We use tc.expected to validate the properties of actual match the201// original tc.value properties (nothing has mutated them during the test).202require.Equal(t, tc.expected, actual)203})204}205}206207// TestDecode_CapsulesError tests that we are unable to decode when208// the target `into` is not an interface.209func TestDecode_CapsulesError(t *testing.T) {210type Capsule1 struct{ test string }211type Capsule2 Capsule1212213v := Capsule1{test: "true"}214actual := Capsule2{}215216require.EqualError(t, value.Decode(value.Encode(v), &actual), `expected capsule("value_test.Capsule2"), got capsule("value_test.Capsule1")`)217}218219// TestDecodeCopy_SliceCopy ensures that copies are made during decoding220// instead of setting values directly.221func TestDecodeCopy_SliceCopy(t *testing.T) {222orig := []int{1, 2, 3}223224var res []int225require.NoError(t, value.DecodeCopy(value.Encode(orig), &res))226227res[0] = 10228require.Equal(t, []int{1, 2, 3}, orig, "Original slice should not have been modified")229}230231// TestDecodeCopy_ArrayCopy ensures that copies are made during decoding232// instead of setting values directly.233func TestDecode_ArrayCopy(t *testing.T) {234orig := [...]int{1, 2, 3}235236var res [3]int237require.NoError(t, value.DecodeCopy(value.Encode(orig), &res))238239res[0] = 10240require.Equal(t, [3]int{1, 2, 3}, orig, "Original array should not have been modified")241}242243func TestDecode_CustomTypes(t *testing.T) {244t.Run("object to Unmarshaler", func(t *testing.T) {245var actual customUnmarshaler246require.NoError(t, value.Decode(value.Object(nil), &actual))247require.True(t, actual.Called, "UnmarshalRiver was not invoked")248})249250t.Run("TextMarshaler to TextUnmarshaler", func(t *testing.T) {251now := time.Now()252253var actual time.Time254require.NoError(t, value.Decode(value.Encode(now), &actual))255require.True(t, now.Equal(actual))256})257258t.Run("time.Duration to time.Duration", func(t *testing.T) {259dur := 15 * time.Second260261var actual time.Duration262require.NoError(t, value.Decode(value.Encode(dur), &actual))263require.Equal(t, dur, actual)264})265266t.Run("string to TextUnmarshaler", func(t *testing.T) {267now := time.Now()268nowBytes, _ := now.MarshalText()269270var actual time.Time271require.NoError(t, value.Decode(value.String(string(nowBytes)), &actual))272273actualBytes, _ := actual.MarshalText()274require.Equal(t, nowBytes, actualBytes)275})276277t.Run("string to time.Duration", func(t *testing.T) {278dur := 15 * time.Second279280var actual time.Duration281require.NoError(t, value.Decode(value.String(dur.String()), &actual))282require.Equal(t, dur.String(), actual.String())283})284}285286type customUnmarshaler struct {287Called bool `river:"called,attr,optional"`288}289290func (cu *customUnmarshaler) UnmarshalRiver(f func(interface{}) error) error {291cu.Called = true292293type s customUnmarshaler294return f((*s)(cu))295}296297type textEnumType bool298299func (et *textEnumType) UnmarshalText(text []byte) error {300*et = false301302switch string(text) {303case "accepted_value":304*et = true305return nil306default:307return fmt.Errorf("unrecognized value %q", string(text))308}309}310311func TestDecode_TextUnmarshaler(t *testing.T) {312t.Run("valid type and value", func(t *testing.T) {313var et textEnumType314require.NoError(t, value.Decode(value.String("accepted_value"), &et))315require.Equal(t, textEnumType(true), et)316})317318t.Run("invalid type", func(t *testing.T) {319var et textEnumType320err := value.Decode(value.Bool(true), &et)321require.EqualError(t, err, "expected string, got bool")322})323324t.Run("invalid value", func(t *testing.T) {325var et textEnumType326err := value.Decode(value.String("bad_value"), &et)327require.EqualError(t, err, `unrecognized value "bad_value"`)328})329330t.Run("unmarshaler nested in other value", func(t *testing.T) {331input := value.Array(332value.String("accepted_value"),333value.String("accepted_value"),334value.String("accepted_value"),335)336337var ett []textEnumType338require.NoError(t, value.Decode(input, &ett))339require.Equal(t, []textEnumType{true, true, true}, ett)340})341}342343func TestDecode_ErrorChain(t *testing.T) {344type Target struct {345Key struct {346Object struct {347Field1 []int `river:"field1,attr"`348} `river:"object,attr"`349} `river:"key,attr"`350}351352val := value.Object(map[string]value.Value{353"key": value.Object(map[string]value.Value{354"object": value.Object(map[string]value.Value{355"field1": value.Array(356value.Int(15),357value.Int(30),358value.String("Hello, world!"),359),360}),361}),362})363364// NOTE(rfratto): strings of errors from the value package are fairly limited365// in the amount of information they show, since the value package doesn't366// have a great way to pretty-print the chain of errors.367//368// For example, with the error below, the message doesn't explain where the369// string is coming from, even though the error values hold that context.370//371// Callers consuming errors should print the error chain with extra context372// so it's more useful to users.373err := value.Decode(val, &Target{})374expectErr := `expected number, got string`375require.EqualError(t, err, expectErr)376}377378type boolish int379380var _ value.ConvertibleFromCapsule = (*boolish)(nil)381var _ value.ConvertibleIntoCapsule = (boolish)(0)382383func (b boolish) RiverCapsule() {}384385func (b *boolish) ConvertFrom(src interface{}) error {386switch v := src.(type) {387case bool:388if v {389*b = 1390} else {391*b = 0392}393return nil394}395396return value.ErrNoConversion397}398399func (b boolish) ConvertInto(dst interface{}) error {400switch d := dst.(type) {401case *bool:402if b == 0 {403*d = false404} else {405*d = true406}407return nil408}409410return value.ErrNoConversion411}412413func TestDecode_CustomConvert(t *testing.T) {414t.Run("compatible type to custom", func(t *testing.T) {415var b boolish416err := value.Decode(value.Bool(true), &b)417require.NoError(t, err)418require.Equal(t, boolish(1), b)419})420421t.Run("custom to compatible type", func(t *testing.T) {422var b bool423err := value.Decode(value.Encapsulate(boolish(10)), &b)424require.NoError(t, err)425require.Equal(t, true, b)426})427428t.Run("incompatible type to custom", func(t *testing.T) {429var b boolish430err := value.Decode(value.String("true"), &b)431require.EqualError(t, err, "expected capsule, got string")432})433434t.Run("custom to incompatible type", func(t *testing.T) {435src := boolish(10)436437var s string438err := value.Decode(value.Encapsulate(&src), &s)439require.EqualError(t, err, "expected string, got capsule")440})441}442443func TestDecode_SquashedFields(t *testing.T) {444type InnerStruct struct {445InnerField1 string `river:"inner_field_1,attr,optional"`446InnerField2 string `river:"inner_field_2,attr,optional"`447}448449type OuterStruct struct {450OuterField1 string `river:"outer_field_1,attr,optional"`451Inner InnerStruct `river:",squash"`452OuterField2 string `river:"outer_field_2,attr,optional"`453}454455var (456in = map[string]string{457"outer_field_1": "value1",458"outer_field_2": "value2",459"inner_field_1": "value3",460"inner_field_2": "value4",461}462expect = OuterStruct{463OuterField1: "value1",464Inner: InnerStruct{465InnerField1: "value3",466InnerField2: "value4",467},468OuterField2: "value2",469}470)471472var out OuterStruct473err := value.Decode(value.Encode(in), &out)474require.NoError(t, err)475require.Equal(t, expect, out)476}477478func TestDecode_SquashedFields_Pointer(t *testing.T) {479type InnerStruct struct {480InnerField1 string `river:"inner_field_1,attr,optional"`481InnerField2 string `river:"inner_field_2,attr,optional"`482}483484type OuterStruct struct {485OuterField1 string `river:"outer_field_1,attr,optional"`486Inner *InnerStruct `river:",squash"`487OuterField2 string `river:"outer_field_2,attr,optional"`488}489490var (491in = map[string]string{492"outer_field_1": "value1",493"outer_field_2": "value2",494"inner_field_1": "value3",495"inner_field_2": "value4",496}497expect = OuterStruct{498OuterField1: "value1",499Inner: &InnerStruct{500InnerField1: "value3",501InnerField2: "value4",502},503OuterField2: "value2",504}505)506507var out OuterStruct508err := value.Decode(value.Encode(in), &out)509require.NoError(t, err)510require.Equal(t, expect, out)511}512513func TestDecode_Slice(t *testing.T) {514type Block struct {515Attr int `river:"attr,attr"`516}517518type Struct struct {519Blocks []Block `river:"block.a,block,optional"`520}521522var (523in = map[string]interface{}{524"block": map[string]interface{}{525"a": []map[string]interface{}{526{"attr": 1},527{"attr": 2},528{"attr": 3},529{"attr": 4},530},531},532}533expect = Struct{534Blocks: []Block{535{Attr: 1},536{Attr: 2},537{Attr: 3},538{Attr: 4},539},540}541)542543var out Struct544err := value.Decode(value.Encode(in), &out)545require.NoError(t, err)546require.Equal(t, expect, out)547}548549func TestDecode_SquashedSlice(t *testing.T) {550type Block struct {551Attr int `river:"attr,attr"`552}553554type InnerStruct struct {555BlockA Block `river:"a,block,optional"`556BlockB Block `river:"b,block,optional"`557BlockC Block `river:"c,block,optional"`558}559560type OuterStruct struct {561OuterField1 string `river:"outer_field_1,attr,optional"`562Inner []InnerStruct `river:"block,enum"`563OuterField2 string `river:"outer_field_2,attr,optional"`564}565566var (567in = map[string]interface{}{568"outer_field_1": "value1",569"outer_field_2": "value2",570571"block": []map[string]interface{}{572{"a": map[string]interface{}{"attr": 1}},573{"b": map[string]interface{}{"attr": 2}},574{"c": map[string]interface{}{"attr": 3}},575{"a": map[string]interface{}{"attr": 4}},576},577}578expect = OuterStruct{579OuterField1: "value1",580OuterField2: "value2",581582Inner: []InnerStruct{583{BlockA: Block{Attr: 1}},584{BlockB: Block{Attr: 2}},585{BlockC: Block{Attr: 3}},586{BlockA: Block{Attr: 4}},587},588}589)590591var out OuterStruct592err := value.Decode(value.Encode(in), &out)593require.NoError(t, err)594require.Equal(t, expect, out)595}596597func TestDecode_SquashedSlice_Pointer(t *testing.T) {598type Block struct {599Attr int `river:"attr,attr"`600}601602type InnerStruct struct {603BlockA *Block `river:"a,block,optional"`604BlockB *Block `river:"b,block,optional"`605BlockC *Block `river:"c,block,optional"`606}607608type OuterStruct struct {609OuterField1 string `river:"outer_field_1,attr,optional"`610Inner []InnerStruct `river:"block,enum"`611OuterField2 string `river:"outer_field_2,attr,optional"`612}613614var (615in = map[string]interface{}{616"outer_field_1": "value1",617"outer_field_2": "value2",618619"block": []map[string]interface{}{620{"a": map[string]interface{}{"attr": 1}},621{"b": map[string]interface{}{"attr": 2}},622{"c": map[string]interface{}{"attr": 3}},623{"a": map[string]interface{}{"attr": 4}},624},625}626expect = OuterStruct{627OuterField1: "value1",628OuterField2: "value2",629630Inner: []InnerStruct{631{BlockA: &Block{Attr: 1}},632{BlockB: &Block{Attr: 2}},633{BlockC: &Block{Attr: 3}},634{BlockA: &Block{Attr: 4}},635},636}637)638639var out OuterStruct640err := value.Decode(value.Encode(in), &out)641require.NoError(t, err)642require.Equal(t, expect, out)643}644645// TestDecode_KnownTypes_Any asserts that decoding River values into an646// any/interface{} results in known types.647func TestDecode_KnownTypes_Any(t *testing.T) {648tt := []struct {649input any650expect any651}{652// All numbers must decode to float64.653{int(15), float64(15)},654{int8(15), float64(15)},655{int16(15), float64(15)},656{int32(15), float64(15)},657{int64(15), float64(15)},658{uint(15), float64(15)},659{uint8(15), float64(15)},660{uint16(15), float64(15)},661{uint32(15), float64(15)},662{uint64(15), float64(15)},663{float32(2.5), float64(2.5)},664{float64(2.5), float64(2.5)},665666{bool(true), bool(true)},667{string("Hello"), string("Hello")},668669{670input: []int{1, 2, 3},671expect: []any{float64(1), float64(2), float64(3)},672},673674{675input: map[string]int{"number": 15},676expect: map[string]any{"number": float64(15)},677},678{679input: struct {680Name string `river:"name,attr"`681}{Name: "John"},682683expect: map[string]any{"name": "John"},684},685}686687t.Run("basic types", func(t *testing.T) {688for _, tc := range tt {689var actual any690err := value.Decode(value.Encode(tc.input), &actual)691692if assert.NoError(t, err) {693assert.Equal(t, tc.expect, actual,694"Expected %[1]v (%[1]T) to transcode to %[2]v (%[2]T)", tc.input, tc.expect)695}696}697})698699t.Run("inside maps", func(t *testing.T) {700for _, tc := range tt {701input := map[string]any{702"key": tc.input,703}704705var actual map[string]any706err := value.Decode(value.Encode(input), &actual)707708if assert.NoError(t, err) {709assert.Equal(t, tc.expect, actual["key"],710"Expected %[1]v (%[1]T) to transcode to %[2]v (%[2]T) inside a map", tc.input, tc.expect)711}712}713})714}715716func TestRetainCapsulePointer(t *testing.T) {717capsuleVal := &capsule{}718719in := map[string]any{720"foo": capsuleVal,721}722723var actual map[string]any724err := value.Decode(value.Encode(in), &actual)725require.NoError(t, err)726727expect := map[string]any{728"foo": capsuleVal,729}730require.Equal(t, expect, actual)731}732733type capsule struct{}734735func (*capsule) RiverCapsule() {}736737738