1
0
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:
Robert Pająk
2025-05-13 21:13:59 +02:00
committed by GitHub
parent 5cd1611cda
commit bf7ffa3900
6 changed files with 229 additions and 113 deletions

View File

@@ -18,13 +18,15 @@ 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
- Drop support for [Go 1.22]. (#6381, #6418)
- 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 `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
View 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
View 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")
}
})
}
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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