Skip to content

Commit

Permalink
Use an Ignore option instead of a Comparer
Browse files Browse the repository at this point in the history
Comparer options cannot be used with other Comparer options or
Transformer options. Unfortunatelly, go-cmp currently doesn't
provide a nice way to compose several such options
(see: google/go-cmp#36).
An Ignore option seems to be more suitable semantically in this
case and it does not conflict with other options.
  • Loading branch information
xosmig committed Jul 18, 2022
1 parent 49a2e02 commit 3fdf471
Show file tree
Hide file tree
Showing 3 changed files with 22 additions and 26 deletions.
7 changes: 2 additions & 5 deletions matchers/matchers.go
Expand Up @@ -7,10 +7,7 @@ import (
"github.com/xosmig/placeholders"
)

// DiffEq invokes cmpmock.DiffEq with an extra placeholders.Comparer() option.
// Note that, if another cmp.Comparer or cmp.Transformer option is provided, it
// will cause an ambiguity and gocmp will panic. Use a custom wrapper option in
// this case.
// DiffEq invokes cmpmock.DiffEq with an extra placeholders.Ignore() option.
func DiffEq(x any, opts ...cmp.Option) gomock.Matcher {
return cmpmock.DiffEq(x, placeholders.Comparer(), cmp.Options(opts))
return cmpmock.DiffEq(x, placeholders.Ignore(), cmp.Options(opts))
}
27 changes: 13 additions & 14 deletions placeholders.go
Expand Up @@ -39,18 +39,18 @@ type TestingCleanup interface {
// type TPtr that references the allocated object. TPtr is either *T or has *T
// as its underlying type (i.e, defined as "type Foo *Bar", where TPtr = Foo
// and T = Bar). The returned reference or any other reference to the allocated
// object is considered a placeholder reference: when cmp.Equal is invoked
// with the placeholders.Comparer option, a placeholder reference is considered
// to be equal to any other object, regardless of types or values.
// object is considered a placeholder reference: when cmp.Equal or cmp.Diff is
// invoked with the placeholders.Ignore option, a placeholder reference is
// considered to be equal to any other object, regardless of types or values.
//
// Only pointers to the allocated object are considered placeholders and not
// the object itself (see examples below).
//
// The second type parameter (T) can always be inferred from the first one
// (TPtr) and does not need to be explicitly specified.
//
// The only parameter (t) can of type *testing.T, *testing.B, or any other type
// with a function Cleanup with a similar semantic and it ensures that the
// The only parameter (t) can be of type *testing.T, *testing.B, or any other
// type with a function Cleanup with a similar semantic. It ensures that the
// resources allocated to keep track of the placeholders are eventually
// reclaimed (e.g., when t is of type *testing.T, they are reclaimed upon the
// completion of the test).
Expand All @@ -59,24 +59,24 @@ type TestingCleanup interface {
// helloString := "hello"
//
// // true
// cmp.Equal(placeholders.Make[*string](t), &helloString, placeholders.Comparer())
// cmp.Equal(placeholders.Make[*string](t), &helloString, placeholders.Ignore())
//
// placeholder := placeholders.Make[*string](t)
// anotherRef := &(*placeholder)
//
// // true, any reference to the allocated object is a placeholder
// cmp.Equal(anotherRef, &helloString, placeholders.Comparer())
// cmp.Equal(anotherRef, &helloString, placeholders.Ignore())
//
// // false, the allocated object itself is not a placeholder
// cmp.Equal(*placeholder, "hello", placeholders.Comparer())
// cmp.Equal(*placeholder, "hello", placeholders.Ignore())
//
// type Foo struct {SPtr *string; S string}
//
// // true, it works with struct fields and embedded types as well!
// cmp.Equal(Foo{placeholders.Make[*string](t), "world"}, Foo{&helloString, "world"}, placeholders.Comparer())
// cmp.Equal(Foo{placeholders.Make[*string](t), "world"}, Foo{&helloString, "world"}, placeholders.Ignore())
//
// // false, non-placeholder fields differ
// cmp.Equal(Foo{placeholders.Make[*string](t), "earthlings"}, Foo{&helloString, "world"}, placeholders.Comparer())
// cmp.Equal(Foo{placeholders.Make[*string](t), "earthlings"}, Foo{&helloString, "world"}, placeholders.Ignore())
func Make[TPtr ~*T, T any](t TestingCleanup) TPtr {
placeholderManagerInit.Do(func() {
// placeholderManager.placeholdersMx doesn't need initialization.
Expand Down Expand Up @@ -138,12 +138,11 @@ func IsPlaceholder(obj any) bool {

func equateAlways(_, _ interface{}) bool { return true }

// Comparer returns a cmp.Comparer option that determines a placeholder to be
// equal with any other object (regardless of types and values).
func Comparer() cmp.Option {
// Ignore returns a cmp.Ignore option that ignores all placeholders.
func Ignore() cmp.Option {
filter := func(a, b any) bool {
return IsPlaceholder(a) || IsPlaceholder(b)
}

return cmp.FilterValues(filter, cmp.Comparer(equateAlways))
return cmp.FilterValues(filter, cmp.Ignore())
}
14 changes: 7 additions & 7 deletions placeholders_test.go
Expand Up @@ -29,22 +29,22 @@ func TestMake_Example(t *testing.T) {
helloString := "hello"

// true
assert.Assert(t, cmp.Equal(Make[*string](t), &helloString, Comparer()))
assert.Assert(t, cmp.Equal(Make[*string](t), &helloString, Ignore()))

placeholder := Make[*string](t)
anotherRef := &(*placeholder) // nolint

// true, any reference to the allocated object is a placeholder
assert.Assert(t, cmp.Equal(anotherRef, &helloString, Comparer()))
assert.Assert(t, cmp.Equal(anotherRef, &helloString, Ignore()))

// false, the allocated object itself is not a placeholder
assert.Assert(t, !cmp.Equal(*placeholder, "hello", Comparer()))
assert.Assert(t, !cmp.Equal(*placeholder, "hello", Ignore()))

// true, it works with struct fields and embedded types as well!
assert.Assert(t, cmp.Equal(Foo{Make[*string](t), "world"}, Foo{&helloString, "world"}, Comparer()))
assert.Assert(t, cmp.Equal(Foo{Make[*string](t), "world"}, Foo{&helloString, "world"}, Ignore()))

// false, non-placeholder fields differ
assert.Assert(t, !cmp.Equal(Foo{Make[*string](t), "earthlings"}, Foo{&helloString, "world"}, Comparer()))
assert.Assert(t, !cmp.Equal(Foo{Make[*string](t), "earthlings"}, Foo{&helloString, "world"}, Ignore()))
}

func TestMake(tt *testing.T) {
Expand Down Expand Up @@ -159,10 +159,10 @@ func TestMake(tt *testing.T) {
for testName, tc := range testCases {
tt.Run(testName, func(t *testing.T) {
arg1, arg2, expectedEqual := tc(t)
equal := cmp.Equal(arg1, arg2, Comparer())
equal := cmp.Equal(arg1, arg2, Ignore())
if expectedEqual && !equal {
t.Errorf("structs are supposed to be considered equal, but are considered differet. diff: %v",
cmp.Diff(arg1, arg2, Comparer()))
cmp.Diff(arg1, arg2, Ignore()))
} else if !expectedEqual && equal {
t.Error("structs are supposed to be considered different, but are considered equal")
}
Expand Down

0 comments on commit 3fdf471

Please sign in to comment.