From 0380851148fe5adb6126330d6ecc99fa4999f34b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Fri, 19 Jan 2024 12:05:27 +0100 Subject: [PATCH] Add tests and refactor --- log/keyvalue.go | 67 ------------------- log/value.go | 59 +++++++++++++++++ log/value_test.go | 163 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 67 deletions(-) delete mode 100644 log/keyvalue.go create mode 100644 log/value_test.go diff --git a/log/keyvalue.go b/log/keyvalue.go deleted file mode 100644 index 56b4e82e755..00000000000 --- a/log/keyvalue.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package log // import "go.opentelemetry.io/otel/log" - -import ( - "fmt" -) - -// An KeyValue is a key-value pair. -type KeyValue struct { - Key string - Value Value -} - -// String returns an KeyValue for a string value. -func String(key, value string) KeyValue { - return KeyValue{key, StringValue(value)} -} - -// Int64 returns an KeyValue for an int64. -func Int64(key string, value int64) KeyValue { - return KeyValue{key, Int64Value(value)} -} - -// Int converts an int to an int64 and returns -// an KeyValue with that value. -func Int(key string, value int) KeyValue { - return Int64(key, int64(value)) -} - -// Uint64 returns an KeyValue for a uint64. -func Uint64(key string, v uint64) KeyValue { - return KeyValue{key, Uint64Value(v)} -} - -// Float64 returns an KeyValue for a floating-point number. -func Float64(key string, v float64) KeyValue { - return KeyValue{key, Float64Value(v)} -} - -// Bool returns an KeyValue for a bool. -func Bool(key string, v bool) KeyValue { - return KeyValue{key, BoolValue(v)} -} - -// Group returns an KeyValue for a Group [Value]. -// -// Use Group to collect several key-value pairs under a single -// key. -func Group(key string, args ...KeyValue) KeyValue { - return KeyValue{key, GroupValue(args...)} -} - -// Invalid reports whether the key-value has empty key or value. -func (a KeyValue) Invalid() bool { - return a.Key == "" || a.Value.Empty() -} - -// Equal reports whether a and b have equal keys and values. -func (a KeyValue) Equal(b KeyValue) bool { - return a.Key == b.Key && a.Value.Equal(b.Value) -} - -func (a KeyValue) String() string { - return fmt.Sprintf("%s=%s", a.Key, a.Value) -} diff --git a/log/value.go b/log/value.go index a3bb86044ef..da9cc9af611 100644 --- a/log/value.go +++ b/log/value.go @@ -299,3 +299,62 @@ func (v Value) append(dst []byte) []byte { panic(fmt.Sprintf("bad kind: %s", v.Kind())) } } + +// An KeyValue is a key-value pair. +type KeyValue struct { + Key string + Value Value +} + +// String returns an KeyValue for a string value. +func String(key, value string) KeyValue { + return KeyValue{key, StringValue(value)} +} + +// Int64 returns an KeyValue for an int64. +func Int64(key string, value int64) KeyValue { + return KeyValue{key, Int64Value(value)} +} + +// Int converts an int to an int64 and returns +// an KeyValue with that value. +func Int(key string, value int) KeyValue { + return Int64(key, int64(value)) +} + +// Uint64 returns an KeyValue for a uint64. +func Uint64(key string, v uint64) KeyValue { + return KeyValue{key, Uint64Value(v)} +} + +// Float64 returns an KeyValue for a floating-point number. +func Float64(key string, v float64) KeyValue { + return KeyValue{key, Float64Value(v)} +} + +// Bool returns an KeyValue for a bool. +func Bool(key string, v bool) KeyValue { + return KeyValue{key, BoolValue(v)} +} + +// Group returns an KeyValue for a Group [Value]. +// +// Use Group to collect several key-value pairs under a single +// key. +func Group(key string, args ...KeyValue) KeyValue { + return KeyValue{key, GroupValue(args...)} +} + +// Invalid reports whether the key-value has empty key or value. +func (a KeyValue) Invalid() bool { + return a.Key == "" || a.Value.Empty() +} + +// Equal reports whether a and b have equal keys and values. +func (a KeyValue) Equal(b KeyValue) bool { + return a.Key == b.Key && a.Value.Equal(b.Value) +} + +func (a KeyValue) String() string { + return fmt.Sprintf("%s=%s", a.Key, a.Value) +} diff --git a/log/value_test.go b/log/value_test.go new file mode 100644 index 00000000000..a5bd19941e2 --- /dev/null +++ b/log/value_test.go @@ -0,0 +1,163 @@ +// Copyright The OpenTelemetry Authors +// SPDX-License-Identifier: Apache-2.0 + +package log + +import ( + "fmt" + "testing" + "time" + "unsafe" + + "github.com/stretchr/testify/assert" +) + +func TestKindString(t *testing.T) { + got := KindGroup.String() + assert.Equal(t, "Group", got) +} + +func TestValueEqual(t *testing.T) { + vals := []Value{ + {}, + Int64Value(1), + Int64Value(2), + Float64Value(3.5), + Float64Value(3.7), + BoolValue(true), + BoolValue(false), + GroupValue(Bool("b", true), Int("i", 3)), + } + for i, v1 := range vals { + for j, v2 := range vals { + got := v1.Equal(v2) + want := i == j + if got != want { + t.Errorf("%v.Equal(%v): got %t, want %t", v1, v2, got, want) + } + } + } +} + +func TestValueString(t *testing.T) { + for _, test := range []struct { + v Value + want string + }{ + {Int64Value(-3), "-3"}, + {Uint64Value(1), "1"}, + {Float64Value(.15), "0.15"}, + {BoolValue(true), "true"}, + {StringValue("foo"), "foo"}, + {GroupValue(Int("a", 1), Bool("b", true)), "[a=1 b=true]"}, + } { + got := test.v.String() + assert.Equal(t, test.want, got) + } +} + +func TestValueNoAlloc(t *testing.T) { + // Assign values just to make sure the compiler doesn't optimize away the statements. + var ( + i int64 + u uint64 + f float64 + b bool + s string + x any + tm time.Time + ) + a := int(testing.AllocsPerRun(5, func() { + i = Int64Value(1).Int64() + u = Uint64Value(1).Uint64() + f = Float64Value(1).Float64() + b = BoolValue(true).Bool() + s = StringValue("foo").String() + })) + assert.Zero(t, a) + _ = i + _ = u + _ = f + _ = b + _ = s + _ = x + _ = tm +} + +func TestKeyValueNoAlloc(t *testing.T) { + // Assign values just to make sure the compiler doesn't optimize away the statements. + var ( + i int64 + u uint64 + f float64 + b bool + s string + x any + ) + a := int(testing.AllocsPerRun(5, func() { + i = Int64("key", 1).Value.Int64() + u = Uint64("key", 1).Value.Uint64() + f = Float64("key", 1).Value.Float64() + b = Bool("key", true).Value.Bool() + s = String("key", "foo").Value.String() + })) + assert.Zero(t, a) + _ = i + _ = u + _ = f + _ = b + _ = s + _ = x +} + +func TestValueAny(t *testing.T) { + for _, test := range []struct { + want any + in Value + }{ + {"s", StringValue("s")}, + {true, BoolValue(true)}, + {int64(4), IntValue(4)}, + {uint64(2), Uint64Value(2)}, + {int64(11), Int64Value(11)}, + {1.5, Float64Value(1.5)}, + {[]KeyValue{Int("i", 3)}, GroupValue(Int("i", 3))}, + } { + got := test.in.Any() + assert.Equal(t, test.want, got) + } +} + +func TestEmptyGroup(t *testing.T) { + g := GroupValue( + Int("a", 1), + Group("g1", Group("g2")), + Group("g3", Group("g4", Int("b", 2)))) + got := g.Group() + want := []KeyValue{Int("a", 1), Group("g3", Group("g4", Int("b", 2)))} + assert.Equal(t, want, got) +} + +// A Value with "unsafe" strings is significantly faster: +// safe: 1785 ns/op, 0 allocs +// unsafe: 690 ns/op, 0 allocs + +// Run this with and without -tags unsafe_kvs to compare. +func BenchmarkUnsafeStrings(b *testing.B) { + b.ReportAllocs() + dst := make([]Value, 100) + src := make([]Value, len(dst)) + b.Logf("Value size = %d", unsafe.Sizeof(Value{})) + for i := range src { + src[i] = StringValue(fmt.Sprintf("string#%d", i)) + } + b.ResetTimer() + var d string + for i := 0; i < b.N; i++ { + copy(dst, src) + for _, a := range dst { + d = a.String() + } + } + _ = d +}