Skip to content

log/logtest: Add AssertEqual and remove AssertRecordEqual #6662

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
The package contains semantic conventions from the `v1.31.0` version of the OpenTelemetry Semantic Conventions.
See the [migration documentation](./semconv/v1.31.0/MIGRATION.md) for information on how to upgrade from `go.opentelemetry.io/otel/semconv/v1.30.0`(#6479)
- Add `Recording`, `Scope`, and `Record` types in `go.opentelemetry.io/otel/log/logtest`. (#6507)
- 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

Expand Down
63 changes: 63 additions & 0 deletions log/logtest/assert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package logtest // import "go.opentelemetry.io/otel/log/logtest"

import (
"context"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"

"go.opentelemetry.io/otel/log"
)

// TestingT reports failure messages.
// [testing.T] implements this interface.
type TestingT interface {
Errorf(format string, args ...any)
}

// AssertEqual asserts that the two concrete data-types from the logtest package are equal.
func AssertEqual[T Recording | Record](t TestingT, want, got T, opts ...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.
}

cfg := newAssertConfig(opts)
if cfg.ignoreTimestamp {
cmpOpts = append(cmpOpts, cmpopts.IgnoreTypes(time.Time{})) // Ignore Timestamps.
}

if diff := cmp.Diff(want, got, cmpOpts...); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
return false
}
return true
}

type assertConfig struct {
ignoreTimestamp bool
}

func newAssertConfig(opts []AssertOption) assertConfig {
var cfg assertConfig
for _, opt := range opts {
cfg = opt.apply(cfg)
}
return cfg
}

// AssertOption allows for fine grain control over how AssertEqual operates.
type AssertOption interface {
apply(cfg assertConfig) assertConfig
}
154 changes: 154 additions & 0 deletions log/logtest/assert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package logtest

import (
"context"
"testing"
"time"

"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 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")
}
})
}
}
78 changes: 0 additions & 78 deletions log/logtest/assertions.go

This file was deleted.

34 changes: 0 additions & 34 deletions log/logtest/assertions_test.go

This file was deleted.

1 change: 1 addition & 0 deletions log/logtest/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading