You've already forked opentelemetry-go
mirror of
https://github.com/open-telemetry/opentelemetry-go.git
synced 2025-08-10 22:31:50 +02:00
log/logtest: Add AssertEqual and remove AssertRecordEqual (#6662)
Fixes https://github.com/open-telemetry/opentelemetry-go/issues/6487 Fixes https://github.com/open-telemetry/opentelemetry-go/issues/6488 Towards https://github.com/open-telemetry/opentelemetry-go/issues/6341 Prior-art: https://github.com/open-telemetry/opentelemetry-go/pull/6464 Replace `AssertRecordEqual` with `AssertEqual` which accepts options and can work not only with `Record` but most importantly, whole `Recording`. The changes between the previous approach are inspired by the design of https://pkg.go.dev/go.opentelemetry.io/otel/sdk/metric/metricdata/metricdatatest. Thanks to it we continue to use the "assert" pattern, but are not impacted by downsides of `testify`. The main issue is that with `testify` we cannot force `[]log.KeyValue` to sort slices before comparing (unordered collection equality). This can make the tests too much "white-boxed" and can easy break during refactoring. Moreover, empty and nil slices are seen not equal which in this case is not relevant. Here is an example of how the current tests of log bridges can be improved: - https://github.com/open-telemetry/opentelemetry-go-contrib/pull/6953
This commit is contained in:
@@ -18,6 +18,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
- Add `WithHTTPClient` option to configure the `http.Client` used by `go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp`. (#6751)
|
||||
- Add `WithHTTPClient` option to configure the `http.Client` used by `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp`. (#6752)
|
||||
- Add `WithHTTPClient` option to configure the `http.Client` used by `go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp`. (#6688)
|
||||
- Add `AssertEqual` function in `go.opentelemetry.io/otel/log/logtest`. (#6662)
|
||||
|
||||
### Removed
|
||||
|
||||
@@ -25,6 +26,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
||||
- Remove `Resource` field from `EnabledParameters` in `go.opentelemetry.io/otel/sdk/log`. (#6494)
|
||||
- Remove `RecordFactory` type from `go.opentelemetry.io/otel/log/logtest`. (#6492)
|
||||
- Remove `ScopeRecords`, `EmittedRecord`, and `RecordFactory` types from `go.opentelemetry.io/otel/log/logtest`. (#6507)
|
||||
- Remove `AssertRecordEqual` function in `go.opentelemetry.io/otel/log/logtest`, use `AssertEqual` instead. (#6662)
|
||||
|
||||
### Changed
|
||||
|
||||
|
53
log/logtest/assert.go
Normal file
53
log/logtest/assert.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package logtest // import "go.opentelemetry.io/otel/log/logtest"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
)
|
||||
|
||||
// AssertEqual asserts that the two concrete data-types from the logtest package are equal.
|
||||
func AssertEqual[T Recording | Record](t *testing.T, want, got T, opts ...AssertOption) bool {
|
||||
t.Helper()
|
||||
return assertEqual(t, want, got, opts...)
|
||||
}
|
||||
|
||||
// testingT reports failure messages.
|
||||
// *testing.T implements this interface.
|
||||
type testingT interface {
|
||||
Errorf(format string, args ...any)
|
||||
}
|
||||
|
||||
func assertEqual[T Recording | Record](t testingT, want, got T, _ ...AssertOption) bool {
|
||||
if h, ok := t.(interface{ Helper() }); ok {
|
||||
h.Helper()
|
||||
}
|
||||
|
||||
cmpOpts := []cmp.Option{
|
||||
cmp.Comparer(func(x, y context.Context) bool { return x == y }), // Compare context.
|
||||
cmpopts.SortSlices(
|
||||
func(a, b log.KeyValue) bool { return a.Key < b.Key },
|
||||
), // Unordered compare of the key values.
|
||||
cmpopts.EquateEmpty(), // Empty and nil collections are equal.
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(want, got, cmpOpts...); diff != "" {
|
||||
t.Errorf("mismatch (-want +got):\n%s", diff)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type assertConfig struct{}
|
||||
|
||||
// AssertOption allows for fine grain control over how AssertEqual operates.
|
||||
type AssertOption interface {
|
||||
apply(cfg assertConfig) assertConfig
|
||||
}
|
172
log/logtest/assert_test.go
Normal file
172
log/logtest/assert_test.go
Normal file
@@ -0,0 +1,172 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package logtest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
)
|
||||
|
||||
var y2k = time.Date(2000, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
type mockTestingT struct {
|
||||
errors []string
|
||||
}
|
||||
|
||||
func (m *mockTestingT) Errorf(format string, args ...any) {
|
||||
m.errors = append(m.errors, format)
|
||||
}
|
||||
|
||||
func TestAssertEqual(t *testing.T) {
|
||||
a := Recording{
|
||||
Scope{Name: t.Name()}: []Record{
|
||||
{Body: log.StringValue("msg"), Attributes: []log.KeyValue{log.String("foo", "bar"), log.Int("n", 1)}},
|
||||
},
|
||||
}
|
||||
b := Recording{
|
||||
Scope{Name: t.Name()}: []Record{
|
||||
{Body: log.StringValue("msg"), Attributes: []log.KeyValue{log.Int("n", 1), log.String("foo", "bar")}},
|
||||
},
|
||||
}
|
||||
|
||||
got := AssertEqual(t, a, b)
|
||||
assert.True(t, got, "expected recordings to be equal")
|
||||
}
|
||||
|
||||
func TestAssertEqualRecording(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a Recording
|
||||
b Recording
|
||||
opts []AssertOption
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "equal recordings",
|
||||
a: Recording{
|
||||
Scope{Name: t.Name()}: []Record{
|
||||
{
|
||||
Timestamp: y2k,
|
||||
Context: context.Background(),
|
||||
Attributes: []log.KeyValue{log.Int("n", 1), log.String("foo", "bar")},
|
||||
},
|
||||
},
|
||||
},
|
||||
b: Recording{
|
||||
Scope{Name: t.Name()}: []Record{
|
||||
{
|
||||
Timestamp: y2k,
|
||||
Context: context.Background(),
|
||||
Attributes: []log.KeyValue{log.String("foo", "bar"), log.Int("n", 1)},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "different recordings",
|
||||
a: Recording{
|
||||
Scope{Name: t.Name()}: []Record{
|
||||
{Attributes: []log.KeyValue{log.String("foo", "bar")}},
|
||||
},
|
||||
},
|
||||
b: Recording{
|
||||
Scope{Name: t.Name()}: []Record{
|
||||
{Attributes: []log.KeyValue{log.Int("n", 1)}},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "equal empty scopes",
|
||||
a: Recording{
|
||||
Scope{Name: t.Name()}: nil,
|
||||
},
|
||||
b: Recording{
|
||||
Scope{Name: t.Name()}: []Record{},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "equal empty attributes",
|
||||
a: Recording{
|
||||
Scope{Name: t.Name()}: []Record{
|
||||
{Body: log.StringValue("msg"), Attributes: []log.KeyValue{}},
|
||||
},
|
||||
},
|
||||
b: Recording{
|
||||
Scope{Name: t.Name()}: []Record{
|
||||
{Body: log.StringValue("msg"), Attributes: nil},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mockT := &mockTestingT{}
|
||||
result := assertEqual(mockT, tc.a, tc.b, tc.opts...)
|
||||
if result != tc.want {
|
||||
t.Errorf("AssertEqual() = %v, want %v", result, tc.want)
|
||||
}
|
||||
if !tc.want && len(mockT.errors) == 0 {
|
||||
t.Errorf("expected Errorf call but got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssertEqualRecord(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
a Record
|
||||
b Record
|
||||
opts []AssertOption
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "equal records",
|
||||
a: Record{
|
||||
Timestamp: y2k,
|
||||
Context: context.Background(),
|
||||
Attributes: []log.KeyValue{log.Int("n", 1), log.String("foo", "bar")},
|
||||
},
|
||||
b: Record{
|
||||
Timestamp: y2k,
|
||||
Context: context.Background(),
|
||||
Attributes: []log.KeyValue{log.String("foo", "bar"), log.Int("n", 1)},
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "different records",
|
||||
a: Record{
|
||||
Attributes: []log.KeyValue{log.String("foo", "bar")},
|
||||
},
|
||||
b: Record{
|
||||
Attributes: []log.KeyValue{log.Int("n", 1)},
|
||||
},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
mockT := &mockTestingT{}
|
||||
result := assertEqual(mockT, tc.a, tc.b, tc.opts...)
|
||||
if result != tc.want {
|
||||
t.Errorf("AssertEqual() = %v, want %v", result, tc.want)
|
||||
}
|
||||
if !tc.want && len(mockT.errors) == 0 {
|
||||
t.Errorf("expected Errorf call but got none")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,78 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package logtest // import "go.opentelemetry.io/otel/log/logtest"
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
)
|
||||
|
||||
// AssertRecordEqual compares two log records, and fails the test if they are
|
||||
// not equal.
|
||||
func AssertRecordEqual(t testing.TB, want, got log.Record) bool {
|
||||
t.Helper()
|
||||
|
||||
if want.EventName() != got.EventName() {
|
||||
t.Errorf("EventName value is not equal:\nwant: %v\ngot: %v", want.EventName(), got.EventName())
|
||||
return false
|
||||
}
|
||||
if !want.Timestamp().Equal(got.Timestamp()) {
|
||||
t.Errorf("Timestamp value is not equal:\nwant: %v\ngot: %v", want.Timestamp(), got.Timestamp())
|
||||
return false
|
||||
}
|
||||
if !want.ObservedTimestamp().Equal(got.ObservedTimestamp()) {
|
||||
t.Errorf(
|
||||
"ObservedTimestamp value is not equal:\nwant: %v\ngot: %v",
|
||||
want.ObservedTimestamp(),
|
||||
got.ObservedTimestamp(),
|
||||
)
|
||||
return false
|
||||
}
|
||||
if want.Severity() != got.Severity() {
|
||||
t.Errorf("Severity value is not equal:\nwant: %v\ngot: %v", want.Severity(), got.Severity())
|
||||
return false
|
||||
}
|
||||
if want.SeverityText() != got.SeverityText() {
|
||||
t.Errorf("SeverityText value is not equal:\nwant: %v\ngot: %v", want.SeverityText(), got.SeverityText())
|
||||
return false
|
||||
}
|
||||
if !assertBody(t, want.Body(), got) {
|
||||
return false
|
||||
}
|
||||
|
||||
var attrs []log.KeyValue
|
||||
want.WalkAttributes(func(kv log.KeyValue) bool {
|
||||
attrs = append(attrs, kv)
|
||||
return true
|
||||
})
|
||||
return assertAttributes(t, attrs, got)
|
||||
}
|
||||
|
||||
func assertBody(t testing.TB, want log.Value, r log.Record) bool {
|
||||
t.Helper()
|
||||
got := r.Body()
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Body value is not equal:\nwant: %v\ngot: %v", want, got)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func assertAttributes(t testing.TB, want []log.KeyValue, r log.Record) bool {
|
||||
t.Helper()
|
||||
var got []log.KeyValue
|
||||
r.WalkAttributes(func(kv log.KeyValue) bool {
|
||||
got = append(got, kv)
|
||||
return true
|
||||
})
|
||||
if !slices.EqualFunc(want, got, log.KeyValue.Equal) {
|
||||
t.Errorf("Attributes are not equal:\nwant: %v\ngot: %v", want, got)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
// Copyright The OpenTelemetry Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package logtest
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.opentelemetry.io/otel/log"
|
||||
)
|
||||
|
||||
func TestAssertRecord(t *testing.T) {
|
||||
r1 := log.Record{}
|
||||
r2 := log.Record{}
|
||||
AssertRecordEqual(t, r1, r2)
|
||||
|
||||
now := time.Now()
|
||||
r1.SetEventName("my_event")
|
||||
r2.SetEventName("my_event")
|
||||
r1.SetTimestamp(now)
|
||||
r2.SetTimestamp(now)
|
||||
r1.SetObservedTimestamp(now)
|
||||
r2.SetObservedTimestamp(now)
|
||||
r1.SetSeverity(log.SeverityTrace1)
|
||||
r2.SetSeverity(log.SeverityTrace1)
|
||||
r1.SetSeverityText("trace")
|
||||
r2.SetSeverityText("trace")
|
||||
r1.SetBody(log.StringValue("log body"))
|
||||
r2.SetBody(log.StringValue("log body"))
|
||||
r1.AddAttributes(log.Bool("attr", true))
|
||||
r2.AddAttributes(log.Bool("attr", true))
|
||||
AssertRecordEqual(t, r1, r2)
|
||||
}
|
@@ -3,6 +3,7 @@ module go.opentelemetry.io/otel/log/logtest
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.opentelemetry.io/otel v1.35.0
|
||||
go.opentelemetry.io/otel/log v0.11.0
|
||||
|
Reference in New Issue
Block a user