| // Copyright The OpenTelemetry Authors | |
| // SPDX-License-Identifier: Apache-2.0 | |
| package attribute_test | |
| import ( | |
| "math" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/stretchr/testify/assert" | |
| "go.opentelemetry.io/otel/attribute" | |
| ) | |
| func TestValue(t *testing.T) { | |
| k := attribute.Key("test") | |
| for _, testcase := range []struct { | |
| name string | |
| value attribute.Value | |
| wantType attribute.Type | |
| wantValue any | |
| }{ | |
| { | |
| name: "Key.Bool() correctly returns keys's internal bool value", | |
| value: k.Bool(true).Value, | |
| wantType: attribute.BOOL, | |
| wantValue: true, | |
| }, | |
| { | |
| name: "Key.BoolSlice() correctly returns keys's internal []bool value", | |
| value: k.BoolSlice([]bool{true, false, true}).Value, | |
| wantType: attribute.BOOLSLICE, | |
| wantValue: []bool{true, false, true}, | |
| }, | |
| { | |
| name: "Key.Int64() correctly returns keys's internal int64 value", | |
| value: k.Int64(42).Value, | |
| wantType: attribute.INT64, | |
| wantValue: int64(42), | |
| }, | |
| { | |
| name: "Key.Int64() correctly returns negative keys's internal int64 value", | |
| value: k.Int64(-42).Value, | |
| wantType: attribute.INT64, | |
| wantValue: int64(-42), | |
| }, | |
| { | |
| name: "Key.Int64Slice() correctly returns keys's internal []int64 value", | |
| value: k.Int64Slice([]int64{42, -3, 12}).Value, | |
| wantType: attribute.INT64SLICE, | |
| wantValue: []int64{42, -3, 12}, | |
| }, | |
| { | |
| name: "Key.Int() correctly returns keys's internal signed integral value", | |
| value: k.Int(42).Value, | |
| wantType: attribute.INT64, | |
| wantValue: int64(42), | |
| }, | |
| { | |
| name: "Key.IntSlice() correctly returns keys's internal []int64 value", | |
| value: k.IntSlice([]int{42, -3, 12}).Value, | |
| wantType: attribute.INT64SLICE, | |
| wantValue: []int64{42, -3, 12}, | |
| }, | |
| { | |
| name: "Key.Float64() correctly returns keys's internal float64 value", | |
| value: k.Float64(42.1).Value, | |
| wantType: attribute.FLOAT64, | |
| wantValue: 42.1, | |
| }, | |
| { | |
| name: "Key.Float64Slice() correctly returns keys's internal []float64 value", | |
| value: k.Float64Slice([]float64{42, -3, 12}).Value, | |
| wantType: attribute.FLOAT64SLICE, | |
| wantValue: []float64{42, -3, 12}, | |
| }, | |
| { | |
| name: "Key.String() correctly returns keys's internal string value", | |
| value: k.String("foo").Value, | |
| wantType: attribute.STRING, | |
| wantValue: "foo", | |
| }, | |
| { | |
| name: "Key.StringSlice() correctly returns keys's internal []string value", | |
| value: k.StringSlice([]string{"forty-two", "negative three", "twelve"}).Value, | |
| wantType: attribute.STRINGSLICE, | |
| wantValue: []string{"forty-two", "negative three", "twelve"}, | |
| }, | |
| { | |
| name: "Key.ByteSlice() correctly returns keys's internal []byte value", | |
| value: k.ByteSlice([]byte("hello world")).Value, | |
| wantType: attribute.BYTESLICE, | |
| wantValue: []byte("hello world"), | |
| }, | |
| { | |
| name: "Key.Slice() correctly returns keys's internal []Value value", | |
| value: k.Slice(attribute.BoolValue(true), attribute.IntValue(42), attribute.StringValue("foo")).Value, | |
| wantType: attribute.SLICE, | |
| wantValue: []attribute.Value{attribute.BoolValue(true), attribute.IntValue(42), attribute.StringValue("foo")}, | |
| }, | |
| { | |
| name: "Key.Map() correctly returns keys's internal []KeyValue value", | |
| value: k.Map(attribute.String("b", "two"), attribute.Int("a", 1)).Value, | |
| wantType: attribute.MAP, | |
| wantValue: []attribute.KeyValue{ | |
| attribute.Int("a", 1), | |
| attribute.String("b", "two"), | |
| }, | |
| }, | |
| { | |
| name: "empty value", | |
| value: attribute.Value{}, | |
| wantType: attribute.EMPTY, | |
| wantValue: nil, | |
| }, | |
| } { | |
| t.Run(testcase.name, func(t *testing.T) { | |
| if testcase.value.Type() != testcase.wantType { | |
| t.Errorf("wrong value type, got %#v, expected %#v", testcase.value.Type(), testcase.wantType) | |
| } | |
| got := testcase.value.AsInterface() | |
| if diff := cmp.Diff(testcase.wantValue, got, cmp.AllowUnexported(attribute.Value{})); diff != "" { | |
| t.Errorf("+got, -want: %s", diff) | |
| } | |
| }) | |
| } | |
| } | |
| func TestEquivalence(t *testing.T) { | |
| pairs := [][2]attribute.KeyValue{ | |
| { | |
| attribute.Bool("Bool", true), | |
| attribute.Bool("Bool", true), | |
| }, | |
| { | |
| attribute.BoolSlice("BoolSlice", []bool{true, false, true}), | |
| attribute.BoolSlice("BoolSlice", []bool{true, false, true}), | |
| }, | |
| { | |
| attribute.Int("Int", 34), | |
| attribute.Int("Int", 34), | |
| }, | |
| { | |
| attribute.IntSlice("IntSlice", []int{312, 1, -2}), | |
| attribute.IntSlice("IntSlice", []int{312, 1, -2}), | |
| }, | |
| { | |
| attribute.Int64("Int64", 98), | |
| attribute.Int64("Int64", 98), | |
| }, | |
| { | |
| attribute.Int64Slice("Int64Slice", []int64{12, 1298, -219, 2}), | |
| attribute.Int64Slice("Int64Slice", []int64{12, 1298, -219, 2}), | |
| }, | |
| { | |
| attribute.Float64("Float64", 19.09), | |
| attribute.Float64("Float64", 19.09), | |
| }, | |
| { | |
| attribute.Float64Slice("Float64Slice", []float64{12398.1, -37.1713873737, 3}), | |
| attribute.Float64Slice("Float64Slice", []float64{12398.1, -37.1713873737, 3}), | |
| }, | |
| { | |
| attribute.String("String", "string value"), | |
| attribute.String("String", "string value"), | |
| }, | |
| { | |
| attribute.StringSlice("StringSlice", []string{"one", "two", "three"}), | |
| attribute.StringSlice("StringSlice", []string{"one", "two", "three"}), | |
| }, | |
| { | |
| attribute.ByteSlice("ByteSlice", []byte("one")), | |
| attribute.ByteSlice("ByteSlice", []byte("one")), | |
| }, | |
| { | |
| attribute.Slice( | |
| "Slice", | |
| attribute.BoolValue(true), | |
| attribute.IntValue(42), | |
| attribute.SliceValue(attribute.StringValue("nested")), | |
| ), | |
| attribute.Slice( | |
| "Slice", | |
| attribute.BoolValue(true), | |
| attribute.IntValue(42), | |
| attribute.SliceValue(attribute.StringValue("nested")), | |
| ), | |
| }, | |
| { | |
| attribute.Map( | |
| "Map", | |
| attribute.Bool("b", true), | |
| attribute.Map("a", attribute.String("nested", "value")), | |
| ), | |
| attribute.Map( | |
| "Map", | |
| attribute.Map("a", attribute.String("nested", "value")), | |
| attribute.Bool("b", true), | |
| ), | |
| }, | |
| { | |
| // Map equivalence sorts entries stably by key; | |
| // when duplicate keys exist, the relative order of same-key entries must match. | |
| attribute.Map( | |
| "Map", | |
| attribute.Bool("a", true), | |
| attribute.Map("a", attribute.String("nested", "value")), | |
| ), | |
| attribute.Map( | |
| "Map", | |
| attribute.Bool("a", true), | |
| attribute.Map("a", attribute.String("nested", "value")), | |
| ), | |
| }, | |
| { | |
| attribute.KeyValue{Key: "Empty"}, | |
| attribute.KeyValue{Key: "Empty"}, | |
| }, | |
| } | |
| t.Run("Distinct", func(t *testing.T) { | |
| for _, p := range pairs { | |
| s0, s1 := attribute.NewSet(p[0]), attribute.NewSet(p[1]) | |
| m := map[attribute.Distinct]struct{}{s0.Equivalent(): {}} | |
| _, ok := m[s1.Equivalent()] | |
| assert.Truef( | |
| t, | |
| ok, | |
| "Distinct comparison of %s type: not equivalent: %s != %s", | |
| p[0].Value.Type(), | |
| s0.Encoded(attribute.DefaultEncoder()), | |
| s1.Encoded(attribute.DefaultEncoder()), | |
| ) | |
| } | |
| }) | |
| t.Run("Equality operator", func(t *testing.T) { | |
| // Maintain backwards compatibility. | |
| for _, p := range pairs { | |
| if p[0] != p[1] { | |
| t.Errorf("Expected %v to be equal to %v", p[0], p[1]) | |
| } | |
| } | |
| }) | |
| t.Run("Set", func(t *testing.T) { | |
| // Maintain backwards compatibility. | |
| for _, p := range pairs { | |
| s0, s1 := attribute.NewSet(p[0]), attribute.NewSet(p[1]) | |
| m := map[attribute.Set]struct{}{s0: {}} | |
| _, ok := m[s1] | |
| assert.Truef( | |
| t, | |
| ok, | |
| "Set comparison of %s type: not equivalent: %s != %s", | |
| p[0].Value.Type(), | |
| s0.Encoded(attribute.DefaultEncoder()), | |
| s1.Encoded(attribute.DefaultEncoder()), | |
| ) | |
| } | |
| }) | |
| } | |
| func TestNotEquivalence(t *testing.T) { | |
| pairs := [][2]attribute.KeyValue{ | |
| { | |
| attribute.Int("Key", 0), | |
| attribute.Bool("Key", false), | |
| }, | |
| { | |
| attribute.Bool("Bool", true), | |
| attribute.Bool("Bool", false), | |
| }, | |
| { | |
| attribute.BoolSlice("BoolSlice", []bool{true, false, true}), | |
| attribute.BoolSlice("BoolSlice", []bool{true, true, true}), | |
| }, | |
| { | |
| attribute.Int("Int", 34), | |
| attribute.Int("Int", 32), | |
| }, | |
| { | |
| attribute.IntSlice("IntSlice", []int{312, 1, -2}), | |
| attribute.IntSlice("IntSlice", []int{312, 2, -2}), | |
| }, | |
| { | |
| attribute.Int64("Int64", 98), | |
| attribute.Int64("Int64", 97), | |
| }, | |
| { | |
| attribute.Int64Slice("Int64Slice", []int64{12, 1298, -219, 2}), | |
| attribute.Int64Slice("Int64Slice", []int64{12, 1298, -219, 1}), | |
| }, | |
| { | |
| attribute.Float64("Float64", 19.09), | |
| attribute.Float64("Float64", 22.09), | |
| }, | |
| { | |
| attribute.ByteSlice("ByteSlice", []byte("bytes value")), | |
| attribute.ByteSlice("ByteSlice", []byte("another value")), | |
| }, | |
| { | |
| attribute.Float64Slice("Float64Slice", []float64{12398.1, -37.1713873737, 3}), | |
| attribute.Float64Slice("Float64Slice", []float64{12398.1, -37.1713873737, 5}), | |
| }, | |
| { | |
| attribute.String("String", "string value"), | |
| attribute.String("String", "another value"), | |
| }, | |
| { | |
| attribute.StringSlice("StringSlice", []string{"one", "two", "three"}), | |
| attribute.StringSlice("StringSlice", []string{"one", "two"}), | |
| }, | |
| { | |
| attribute.Slice("Slice", attribute.BoolValue(true), attribute.IntValue(42)), | |
| attribute.Slice("Slice", attribute.BoolValue(true), attribute.IntValue(43)), | |
| }, | |
| { | |
| attribute.Map("Map", attribute.String("key", "value")), | |
| attribute.Map("Map", attribute.String("key", "other")), | |
| }, | |
| { | |
| attribute.Map("Map", attribute.String("key", "value")), | |
| attribute.Map("Map", attribute.String("other", "value")), | |
| }, | |
| { | |
| // With duplicate keys, changing the relative order of same-key entries changes map equivalence | |
| // (stable key sort preserves duplicates' order). | |
| attribute.Map( | |
| "Map", | |
| attribute.Bool("a", true), | |
| attribute.Map("a", attribute.String("nested", "value")), | |
| ), | |
| attribute.Map( | |
| "Map", | |
| attribute.Map("a", attribute.String("nested", "value")), | |
| attribute.Bool("a", true), | |
| ), | |
| }, | |
| { | |
| attribute.KeyValue{Key: "Empty"}, | |
| attribute.String("Empty", ""), | |
| }, | |
| } | |
| t.Run("Distinct", func(t *testing.T) { | |
| for _, p := range pairs { | |
| s0, s1 := attribute.NewSet(p[0]), attribute.NewSet(p[1]) | |
| m := map[attribute.Distinct]struct{}{s0.Equivalent(): {}} | |
| _, ok := m[s1.Equivalent()] | |
| assert.Falsef( | |
| t, | |
| ok, | |
| "Distinct comparison of %s type: equivalent: %s == %s", | |
| p[0].Value.Type(), | |
| s0.Encoded(attribute.DefaultEncoder()), | |
| s1.Encoded(attribute.DefaultEncoder()), | |
| ) | |
| } | |
| }) | |
| t.Run("Equality operator", func(t *testing.T) { | |
| // Maintain backwards compatibility. | |
| for _, p := range pairs { | |
| if p[0] == p[1] { | |
| t.Errorf("Expected %v to not be equal to %v", p[0], p[1]) | |
| } | |
| } | |
| }) | |
| t.Run("Set", func(t *testing.T) { | |
| // Maintain backwards compatibility. | |
| for _, p := range pairs { | |
| s0, s1 := attribute.NewSet(p[0]), attribute.NewSet(p[1]) | |
| m := map[attribute.Set]struct{}{s0: {}} | |
| _, ok := m[s1] | |
| assert.Falsef( | |
| t, | |
| ok, | |
| "Set comparison of %s type: equivalent: %s == %s", | |
| p[0].Value.Type(), | |
| s0.Encoded(attribute.DefaultEncoder()), | |
| s1.Encoded(attribute.DefaultEncoder()), | |
| ) | |
| } | |
| }) | |
| } | |
| func TestAsSlice(t *testing.T) { | |
| bs1 := []bool{true, false, true} | |
| kv := attribute.BoolSlice("BoolSlice", bs1) | |
| bs2 := kv.Value.AsBoolSlice() | |
| assert.Equal(t, bs1, bs2) | |
| i64s1 := []int64{12, 1298, -219, 2} | |
| kv = attribute.Int64Slice("Int64Slice", i64s1) | |
| i64s2 := kv.Value.AsInt64Slice() | |
| assert.Equal(t, i64s1, i64s2) | |
| is1 := []int{12, 1298, -219, 2} | |
| kv = attribute.IntSlice("IntSlice", is1) | |
| i64s2 = kv.Value.AsInt64Slice() | |
| assert.Equal(t, i64s1, i64s2) | |
| fs1 := []float64{12398.1, -37.1713873737, 3} | |
| kv = attribute.Float64Slice("Float64Slice", fs1) | |
| fs2 := kv.Value.AsFloat64Slice() | |
| assert.Equal(t, fs1, fs2) | |
| ss1 := []string{"one", "two", "three"} | |
| kv = attribute.StringSlice("StringSlice", ss1) | |
| ss2 := kv.Value.AsStringSlice() | |
| assert.Equal(t, ss1, ss2) | |
| b1 := []byte("one") | |
| kv = attribute.ByteSlice("ByteSlice", b1) | |
| b2 := kv.Value.AsByteSlice() | |
| assert.Equal(t, b1, b2) | |
| for _, tc := range []struct { | |
| name string | |
| in []attribute.Value | |
| }{ | |
| { | |
| name: "empty", | |
| in: []attribute.Value{}, | |
| }, | |
| { | |
| name: "len1", | |
| in: []attribute.Value{attribute.BoolValue(true)}, | |
| }, | |
| { | |
| name: "len2", | |
| in: []attribute.Value{attribute.BoolValue(true), attribute.IntValue(42)}, | |
| }, | |
| { | |
| name: "len3", | |
| in: []attribute.Value{attribute.BoolValue(true), attribute.IntValue(42), attribute.StringValue("test")}, | |
| }, | |
| { | |
| name: "len4", | |
| in: []attribute.Value{ | |
| attribute.BoolValue(true), | |
| attribute.IntValue(42), | |
| attribute.StringValue("test"), | |
| attribute.Float64Value(1.25), | |
| }, | |
| }, | |
| { | |
| name: "len5", | |
| in: []attribute.Value{ | |
| attribute.BoolValue(true), | |
| attribute.IntValue(42), | |
| attribute.StringValue("test"), | |
| attribute.Float64Value(1.25), | |
| attribute.ByteSliceValue([]byte("bin")), | |
| }, | |
| }, | |
| { | |
| name: "reflect path", | |
| in: []attribute.Value{ | |
| attribute.BoolValue(true), | |
| attribute.IntValue(42), | |
| attribute.StringValue("test"), | |
| attribute.Float64Value(1.25), | |
| attribute.ByteSliceValue([]byte("bin")), | |
| attribute.SliceValue(attribute.BoolValue(false)), | |
| }, | |
| }, | |
| } { | |
| t.Run(tc.name, func(t *testing.T) { | |
| kv = attribute.Slice("Slice", tc.in...) | |
| assert.Equal(t, tc.in, kv.Value.AsSlice()) | |
| }) | |
| } | |
| } | |
| func TestAsMap(t *testing.T) { | |
| for _, tc := range []struct { | |
| name string | |
| in []attribute.KeyValue | |
| want []attribute.KeyValue | |
| }{ | |
| { | |
| name: "empty", | |
| }, | |
| { | |
| name: "len1", | |
| in: []attribute.KeyValue{attribute.Bool("a", true)}, | |
| want: []attribute.KeyValue{attribute.Bool("a", true)}, | |
| }, | |
| { | |
| name: "len2 sorted", | |
| in: []attribute.KeyValue{ | |
| attribute.Int("b", 2), | |
| attribute.Int("a", 1), | |
| }, | |
| want: []attribute.KeyValue{ | |
| attribute.Int("a", 1), | |
| attribute.Int("b", 2), | |
| }, | |
| }, | |
| { | |
| name: "len4 sorted", | |
| in: []attribute.KeyValue{ | |
| attribute.String("d", "4"), | |
| attribute.String("a", "1"), | |
| attribute.String("c", "3"), | |
| attribute.String("b", "2"), | |
| }, | |
| want: []attribute.KeyValue{ | |
| attribute.String("a", "1"), | |
| attribute.String("b", "2"), | |
| attribute.String("c", "3"), | |
| attribute.String("d", "4"), | |
| }, | |
| }, | |
| { | |
| name: "len5 sorted", | |
| in: []attribute.KeyValue{ | |
| attribute.String("e", "5"), | |
| attribute.String("a", "1"), | |
| attribute.String("d", "4"), | |
| attribute.String("b", "2"), | |
| attribute.String("c", "3"), | |
| }, | |
| want: []attribute.KeyValue{ | |
| attribute.String("a", "1"), | |
| attribute.String("b", "2"), | |
| attribute.String("c", "3"), | |
| attribute.String("d", "4"), | |
| attribute.String("e", "5"), | |
| }, | |
| }, | |
| { | |
| name: "reflect path sorted", | |
| in: []attribute.KeyValue{ | |
| attribute.String("f", "6"), | |
| attribute.String("a", "1"), | |
| attribute.String("e", "5"), | |
| attribute.String("b", "2"), | |
| attribute.String("d", "4"), | |
| attribute.String("c", "3"), | |
| }, | |
| want: []attribute.KeyValue{ | |
| attribute.String("a", "1"), | |
| attribute.String("b", "2"), | |
| attribute.String("c", "3"), | |
| attribute.String("d", "4"), | |
| attribute.String("e", "5"), | |
| attribute.String("f", "6"), | |
| }, | |
| }, | |
| { | |
| name: "duplicate keys keep stable order", | |
| in: []attribute.KeyValue{ | |
| attribute.String("dup", "first"), | |
| attribute.String("a", "before"), | |
| attribute.String("dup", "second"), | |
| }, | |
| want: []attribute.KeyValue{ | |
| attribute.String("a", "before"), | |
| attribute.String("dup", "first"), | |
| attribute.String("dup", "second"), | |
| }, | |
| }, | |
| { | |
| name: "empty keys and values", | |
| in: []attribute.KeyValue{ | |
| attribute.String("z", "last"), | |
| {Key: "empty-value"}, | |
| attribute.String("", "empty-key"), | |
| {}, | |
| }, | |
| want: []attribute.KeyValue{ | |
| attribute.String("", "empty-key"), | |
| {}, | |
| {Key: "empty-value"}, | |
| attribute.String("z", "last"), | |
| }, | |
| }, | |
| } { | |
| t.Run(tc.name, func(t *testing.T) { | |
| if tc.want == nil { | |
| tc.want = []attribute.KeyValue{} | |
| } | |
| v := attribute.MapValue(tc.in...) | |
| if len(tc.in) > 0 { | |
| tc.in[0] = attribute.String("mutated", "input") | |
| } | |
| assert.Equal(t, tc.want, v.AsMap()) | |
| got := v.AsMap() | |
| if len(got) > 0 { | |
| got[0] = attribute.String("mutated", "output") | |
| } | |
| assert.Equal(t, tc.want, v.AsMap()) | |
| }) | |
| } | |
| } | |
| func TestValueString(t *testing.T) { | |
| for _, tc := range []struct { | |
| name string | |
| v attribute.Value | |
| want string | |
| }{ | |
| { | |
| name: "bool", | |
| v: attribute.BoolValue(true), | |
| want: "true", | |
| }, | |
| { | |
| name: "bool false", | |
| v: attribute.BoolValue(false), | |
| want: "false", | |
| }, | |
| { | |
| name: "bool slice len1 fast path", | |
| v: attribute.BoolSliceValue([]bool{false}), | |
| want: `[false]`, | |
| }, | |
| { | |
| name: "bool slice len2 fast path", | |
| v: attribute.BoolSliceValue([]bool{true, false}), | |
| want: `[true,false]`, | |
| }, | |
| { | |
| name: "empty bool slice", | |
| v: attribute.BoolSliceValue(nil), | |
| want: "[]", | |
| }, | |
| { | |
| name: "empty bool slice literal", | |
| v: attribute.BoolSliceValue([]bool{}), | |
| want: "[]", | |
| }, | |
| { | |
| name: "bool slice", | |
| v: attribute.BoolSliceValue([]bool{true, false, true}), | |
| want: `[true,false,true]`, | |
| }, | |
| { | |
| name: "bool slice reflect path", | |
| v: attribute.BoolSliceValue([]bool{false, true, false, true}), | |
| want: `[false,true,false,true]`, | |
| }, | |
| { | |
| name: "int64", | |
| v: attribute.Int64Value(-42), | |
| want: "-42", | |
| }, | |
| { | |
| name: "int", | |
| v: attribute.IntValue(7), | |
| want: "7", | |
| }, | |
| { | |
| name: "int64 slice len1 fast path", | |
| v: attribute.Int64SliceValue([]int64{-1}), | |
| want: `[-1]`, | |
| }, | |
| { | |
| name: "int64 slice len2 fast path", | |
| v: attribute.Int64SliceValue([]int64{1, -2}), | |
| want: `[1,-2]`, | |
| }, | |
| { | |
| name: "empty int slice", | |
| v: attribute.IntSliceValue(nil), | |
| want: "[]", | |
| }, | |
| { | |
| name: "empty int slice literal", | |
| v: attribute.IntSliceValue([]int{}), | |
| want: "[]", | |
| }, | |
| { | |
| name: "empty int64 slice literal", | |
| v: attribute.Int64SliceValue([]int64{}), | |
| want: "[]", | |
| }, | |
| { | |
| name: "int slice", | |
| v: attribute.IntSliceValue([]int{1, -2, 3}), | |
| want: `[1,-2,3]`, | |
| }, | |
| { | |
| name: "int64 slice reflect path", | |
| v: attribute.Int64SliceValue([]int64{1, -2, 3, -4}), | |
| want: `[1,-2,3,-4]`, | |
| }, | |
| { | |
| name: "float64", | |
| v: attribute.Float64Value(1.23e10), | |
| want: "1.23e+10", | |
| }, | |
| { | |
| name: "float64 negative zero", | |
| v: attribute.Float64Value(math.Copysign(0, -1)), | |
| want: "-0", | |
| }, | |
| { | |
| name: "float64 NaN", | |
| v: attribute.Float64Value(math.NaN()), | |
| want: "NaN", | |
| }, | |
| { | |
| name: "float64 +Inf", | |
| v: attribute.Float64Value(math.Inf(1)), | |
| want: "Infinity", | |
| }, | |
| { | |
| name: "float64 -Inf", | |
| v: attribute.Float64Value(math.Inf(-1)), | |
| want: "-Infinity", | |
| }, | |
| { | |
| name: "empty float64 slice", | |
| v: attribute.Float64SliceValue(nil), | |
| want: "[]", | |
| }, | |
| { | |
| name: "empty float64 slice literal", | |
| v: attribute.Float64SliceValue([]float64{}), | |
| want: "[]", | |
| }, | |
| { | |
| name: "float64 slice len1 fast path", | |
| v: attribute.Float64SliceValue([]float64{math.Inf(-1)}), | |
| want: `["-Infinity"]`, | |
| }, | |
| { | |
| name: "float64 slice len3 fast path", | |
| v: attribute.Float64SliceValue([]float64{1.25, math.Copysign(0, -1), 2.5}), | |
| want: `[1.25,-0,2.5]`, | |
| }, | |
| { | |
| name: "float64 slice", | |
| v: attribute.Float64SliceValue([]float64{ | |
| 1, | |
| math.NaN(), | |
| math.Inf(1), | |
| math.Inf(-1), | |
| math.Copysign(0, -1), | |
| }), | |
| want: `[1,"NaN","Infinity","-Infinity",-0]`, | |
| }, | |
| { | |
| name: "float64 slice fast path", | |
| v: attribute.Float64SliceValue([]float64{ | |
| math.NaN(), | |
| math.Inf(1), | |
| }), | |
| want: `["NaN","Infinity"]`, | |
| }, | |
| { | |
| name: "string", | |
| v: attribute.StringValue(`hello "world"`), | |
| want: `hello "world"`, | |
| }, | |
| { | |
| name: "empty string", | |
| v: attribute.StringValue(""), | |
| want: "", | |
| }, | |
| { | |
| name: "empty string slice", | |
| v: attribute.StringSliceValue(nil), | |
| want: "[]", | |
| }, | |
| { | |
| name: "empty string slice literal", | |
| v: attribute.StringSliceValue([]string{}), | |
| want: "[]", | |
| }, | |
| { | |
| name: "string slice len1 fast path", | |
| v: attribute.StringSliceValue([]string{""}), | |
| want: `[""]`, | |
| }, | |
| { | |
| name: "string slice len3 fast path", | |
| v: attribute.StringSliceValue([]string{"snowman ☃", "left\u2028right", "left\u2029right"}), | |
| want: `["snowman ☃","left\u2028right","left\u2029right"]`, | |
| }, | |
| { | |
| name: "string slice", | |
| v: attribute.StringSliceValue([]string{ | |
| `hello "world"`, | |
| "line\nbreak", | |
| string([]byte{0xff, 'a'}), | |
| "\u2028", | |
| }), | |
| want: `["hello \"world\"","line\nbreak","\ufffda","\u2028"]`, | |
| }, | |
| { | |
| name: "string slice fast path escapes", | |
| v: attribute.StringSliceValue([]string{ | |
| "tab\treturn\rformfeed\fbackslash\\quote\"backspace\b", | |
| string([]byte{0x01}) + "\u2029", | |
| }), | |
| want: `["tab\treturn\rformfeed\fbackslash\\quote\"backspace\b","\u0001\u2029"]`, | |
| }, | |
| { | |
| name: "string slice leaves HTML characters unescaped", | |
| v: attribute.StringSliceValue([]string{"<tag>&"}), | |
| want: `["<tag>&"]`, | |
| }, | |
| { | |
| name: "string slice replaces invalid utf8 after copied prefix", | |
| v: attribute.StringSliceValue([]string{string([]byte{'a', 0xff, 'b'})}), | |
| want: `["a\ufffdb"]`, | |
| }, | |
| { | |
| name: "byte slice", | |
| v: attribute.ByteSliceValue([]byte("hello world")), | |
| want: "aGVsbG8gd29ybGQ=", | |
| }, | |
| { | |
| name: "empty byte slice", | |
| v: attribute.ByteSliceValue(nil), | |
| want: "", | |
| }, | |
| { | |
| name: "empty slice", | |
| v: attribute.SliceValue(), | |
| want: "[]", | |
| }, | |
| { | |
| name: "slice len5 fast path", | |
| v: attribute.SliceValue( | |
| attribute.BoolValue(true), | |
| attribute.IntValue(7), | |
| attribute.Float64Value(math.Copysign(0, -1)), | |
| attribute.StringValue(`hello "world"`), | |
| attribute.ByteSliceValue([]byte("bin")), | |
| ), | |
| want: `[true,7,-0,"hello \"world\"","Ymlu"]`, | |
| }, | |
| { | |
| name: "slice len1 fast path", | |
| v: attribute.SliceValue(attribute.BoolValue(false)), | |
| want: `[false]`, | |
| }, | |
| { | |
| name: "slice len2 fast path", | |
| v: attribute.SliceValue( | |
| attribute.IntValue(7), | |
| attribute.StringValue(`hello "world"`), | |
| ), | |
| want: `[7,"hello \"world\""]`, | |
| }, | |
| { | |
| name: "slice len3 fast path", | |
| v: attribute.SliceValue( | |
| attribute.Float64Value(1.25), | |
| attribute.Float64Value(math.Inf(1)), | |
| attribute.Float64Value(math.Inf(-1)), | |
| ), | |
| want: `[1.25,"Infinity","-Infinity"]`, | |
| }, | |
| { | |
| name: "slice", | |
| v: attribute.SliceValue( | |
| attribute.StringValue("hello \"world\""), | |
| attribute.Float64Value(math.NaN()), | |
| attribute.ByteSliceValue([]byte("bin")), | |
| attribute.SliceValue(attribute.BoolValue(true), attribute.Value{}), | |
| ), | |
| want: `["hello \"world\"","NaN","Ymlu",[true,null]]`, | |
| }, | |
| { | |
| name: "slice reflect path nested slice values", | |
| v: attribute.SliceValue( | |
| attribute.BoolSliceValue([]bool{}), | |
| attribute.BoolSliceValue([]bool{true}), | |
| attribute.BoolSliceValue([]bool{true, false}), | |
| attribute.BoolSliceValue([]bool{true, false, true}), | |
| attribute.BoolSliceValue([]bool{false, true, false, true}), | |
| attribute.Int64SliceValue([]int64{}), | |
| attribute.Int64SliceValue([]int64{-1}), | |
| attribute.Int64SliceValue([]int64{1, -2}), | |
| attribute.Int64SliceValue([]int64{1, -2, 3}), | |
| attribute.Int64SliceValue([]int64{1, -2, 3, -4}), | |
| attribute.Float64SliceValue([]float64{}), | |
| attribute.Float64SliceValue([]float64{math.Inf(-1)}), | |
| attribute.Float64SliceValue([]float64{math.NaN(), math.Inf(1)}), | |
| attribute.Float64SliceValue([]float64{1.25, math.Copysign(0, -1), 2.5}), | |
| attribute.Float64SliceValue([]float64{1, math.NaN(), math.Inf(1), math.Inf(-1)}), | |
| attribute.StringSliceValue([]string{}), | |
| attribute.StringSliceValue([]string{""}), | |
| attribute.StringSliceValue([]string{`hello "world"`, "line\nbreak"}), | |
| attribute.StringSliceValue([]string{"snowman ☃", "left\u2028right", "left\u2029right"}), | |
| attribute.StringSliceValue([]string{ | |
| "tab\treturn\rformfeed\fbackslash\\quote\"backspace\b", | |
| string([]byte{0x01}) + "\u2029", | |
| "<tag>&", | |
| string([]byte{'a', 0xff, 'b'}), | |
| }), | |
| attribute.SliceValue(), | |
| attribute.SliceValue(attribute.BoolValue(true)), | |
| attribute.SliceValue(attribute.BoolValue(true), attribute.IntValue(2)), | |
| attribute.SliceValue(attribute.BoolValue(true), attribute.IntValue(2), attribute.StringValue("x")), | |
| attribute.SliceValue( | |
| attribute.BoolValue(true), | |
| attribute.IntValue(2), | |
| attribute.StringValue("x"), | |
| attribute.Float64Value(math.Inf(1)), | |
| ), | |
| attribute.SliceValue( | |
| attribute.BoolValue(true), | |
| attribute.IntValue(2), | |
| attribute.StringValue("x"), | |
| attribute.Float64Value(math.Inf(1)), | |
| attribute.ByteSliceValue([]byte("bin")), | |
| ), | |
| attribute.SliceValue( | |
| attribute.BoolValue(true), | |
| attribute.IntValue(2), | |
| attribute.StringValue("x"), | |
| attribute.Float64Value(math.Inf(1)), | |
| attribute.ByteSliceValue([]byte("bin")), | |
| attribute.Value{}, | |
| ), | |
| ), | |
| want: `[[],[true],[true,false],[true,false,true],[false,true,false,true],[]` + | |
| `,[-1],[1,-2],[1,-2,3],[1,-2,3,-4],[]` + | |
| `,["-Infinity"],["NaN","Infinity"],[1.25,-0,2.5],[1,"NaN","Infinity","-Infinity"],[]` + | |
| `,[""],["hello \"world\"","line\nbreak"],["snowman ☃","left\u2028right","left\u2029right"]` + | |
| `,["tab\treturn\rformfeed\fbackslash\\quote\"backspace\b","\u0001\u2029","<tag>&","a\ufffdb"]` + | |
| `,[],[true],[true,2],[true,2,"x"],[true,2,"x","Infinity"],[true,2,"x","Infinity","Ymlu"],[true,2,"x","Infinity","Ymlu",null]]`, | |
| }, | |
| { | |
| name: "empty map", | |
| v: attribute.MapValue(), | |
| want: "{}", | |
| }, | |
| { | |
| name: "map with empty key", | |
| v: attribute.MapValue(attribute.String("", "value")), | |
| want: `{"":"value"}`, | |
| }, | |
| { | |
| name: "map with empty value", | |
| v: attribute.MapValue(attribute.KeyValue{Key: "empty"}), | |
| want: `{"empty":null}`, | |
| }, | |
| { | |
| name: "map with empty key and value", | |
| v: attribute.MapValue(attribute.KeyValue{}), | |
| want: `{"":null}`, | |
| }, | |
| { | |
| name: "map len2 sorted", | |
| v: attribute.MapValue( | |
| attribute.Int("b", 2), | |
| attribute.String("a", `hello "world"`), | |
| ), | |
| want: `{"a":"hello \"world\"","b":2}`, | |
| }, | |
| { | |
| name: "map len4 fast path", | |
| v: attribute.MapValue( | |
| attribute.String("d", "4"), | |
| attribute.String("a", "1"), | |
| attribute.String("c", "3"), | |
| attribute.String("b", "2"), | |
| ), | |
| want: `{"a":"1","b":"2","c":"3","d":"4"}`, | |
| }, | |
| { | |
| name: "map len5 fast path", | |
| v: attribute.MapValue( | |
| attribute.String("e", "5"), | |
| attribute.String("a", "1"), | |
| attribute.String("d", "4"), | |
| attribute.String("b", "2"), | |
| attribute.String("c", "3"), | |
| ), | |
| want: `{"a":"1","b":"2","c":"3","d":"4","e":"5"}`, | |
| }, | |
| { | |
| name: "map escapes keys", | |
| v: attribute.MapValue( | |
| attribute.String("line\nkey", "value"), | |
| attribute.Bool("<tag>&", true), | |
| ), | |
| want: `{"<tag>&":true,"line\nkey":"value"}`, | |
| }, | |
| { | |
| name: "map reflect path nested values", | |
| v: attribute.MapValue( | |
| attribute.String("z", "last"), | |
| attribute.Key("bytes").ByteSlice([]byte("bin")), | |
| attribute.Key("empty").Slice(attribute.Value{}), | |
| attribute.Key("float").Float64(math.Inf(1)), | |
| attribute.Key("map").Map(attribute.String("nested", "value")), | |
| attribute.Key("slice").Slice(attribute.IntValue(1), attribute.StringValue("two")), | |
| ), | |
| want: `{"bytes":"Ymlu","empty":[null],"float":"Infinity","map":{"nested":"value"},"slice":[1,"two"],"z":"last"}`, | |
| }, | |
| { | |
| name: "slice nested map storage paths", | |
| v: attribute.SliceValue( | |
| attribute.MapValue(), | |
| attribute.MapValue( | |
| attribute.String("c", "3"), | |
| attribute.String("a", "1"), | |
| attribute.String("b", "2"), | |
| ), | |
| attribute.MapValue( | |
| attribute.String("d", "4"), | |
| attribute.String("a", "1"), | |
| attribute.String("c", "3"), | |
| attribute.String("b", "2"), | |
| ), | |
| attribute.MapValue( | |
| attribute.String("e", "5"), | |
| attribute.String("a", "1"), | |
| attribute.String("d", "4"), | |
| attribute.String("b", "2"), | |
| attribute.String("c", "3"), | |
| ), | |
| attribute.MapValue( | |
| attribute.String("f", "6"), | |
| attribute.String("a", "1"), | |
| attribute.String("e", "5"), | |
| attribute.String("b", "2"), | |
| attribute.String("d", "4"), | |
| attribute.String("c", "3"), | |
| ), | |
| ), | |
| want: `[{},{"a":"1","b":"2","c":"3"},{"a":"1","b":"2","c":"3","d":"4"},{"a":"1","b":"2","c":"3","d":"4","e":"5"},{"a":"1","b":"2","c":"3","d":"4","e":"5","f":"6"}]`, | |
| }, | |
| { | |
| name: "empty", | |
| v: attribute.Value{}, | |
| want: "", | |
| }, | |
| } { | |
| t.Run(tc.name, func(t *testing.T) { | |
| assert.Equal(t, tc.want, tc.v.String()) | |
| }) | |
| } | |
| } |