Skip to content

Commit

Permalink
feat(Between): handles value implementing Compare & Less methods
Browse files Browse the repository at this point in the history
as well as Gt, Gte, Lt and Lte of course.

Compare and/or Less methods signatures follow:
  func (a T) Less(b T) bool   // returns true if a < b
  func (a T) Compare(b T) int // returns -1 if a < b, 1 if a > b, 0 if a == b

Additional Between, Gt, Gte, Lt and Lte changes:
- failure reports display numbers as elsewhere;
- docs fixed.

Signed-off-by: Maxime Soulé <[email protected]>
  • Loading branch information
maxatome committed Jan 3, 2022
1 parent 515da4a commit 4e544bd
Show file tree
Hide file tree
Showing 7 changed files with 519 additions and 107 deletions.
64 changes: 64 additions & 0 deletions internal/types/order.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) 2021-2022, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

package types

import (
"reflect"

"github.com/maxatome/go-testdeep/internal/dark"
)

// NewOrder returns a function able to compare 2 non-nil values of type "t".
// It returns nil if the type "t" is not comparable.
func NewOrder(t reflect.Type) func(a, b reflect.Value) int {
// Compare(T) int
if m, ok := cmpMethod("Compare", t, Int); ok {
return func(va, vb reflect.Value) int {
// use dark.MustGetInterface() to bypass possible private fields
ret := m.Call([]reflect.Value{
reflect.ValueOf(dark.MustGetInterface(va)),
reflect.ValueOf(dark.MustGetInterface(vb)),
})
return int(ret[0].Int())
}
}

// Less(T) bool
if m, ok := cmpMethod("Less", t, Bool); ok {
return func(va, vb reflect.Value) int {
// use dark.MustGetInterface() to bypass possible private fields
va = reflect.ValueOf(dark.MustGetInterface(va))
vb = reflect.ValueOf(dark.MustGetInterface(vb))
ret := m.Call([]reflect.Value{va, vb})
if ret[0].Bool() { // a < b
return -1
}
ret = m.Call([]reflect.Value{vb, va})
if ret[0].Bool() { // b < a
return 1
}
return 0
}
}

return nil
}

func cmpMethod(name string, in, out reflect.Type) (reflect.Value, bool) {
if equal, ok := in.MethodByName(name); ok {
ft := equal.Type
if !ft.IsVariadic() &&
ft.NumIn() == 2 &&
ft.NumOut() == 1 &&
ft.In(0) == in &&
ft.In(1) == in &&
ft.Out(0) == out {
return equal.Func, true
}
}
return reflect.Value{}, false
}
92 changes: 92 additions & 0 deletions internal/types/order_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) 2022, Maxime Soulé
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree.

package types_test

import (
"reflect"
"testing"

"github.com/maxatome/go-testdeep/internal/test"
"github.com/maxatome/go-testdeep/internal/types"
)

type compareType int

func (i compareType) Compare(j compareType) int {
if i < j {
return -1
}
if i > j {
return 1
}
return 0
}

type lessType int

func (i lessType) Less(j lessType) bool {
return i < j
}

type badType1 int

func (i badType1) Compare(j ...badType1) int { return 0 } // IsVariadic()
func (i badType1) Less(j, k badType1) bool { return false } // NumIn() == 3

type badType2 int

func (i badType2) Compare() int { return 0 } // NumIn() == 1
func (i badType2) Less(j badType2) {} // NumOut() == 0

type badType3 int

func (i badType3) Compare(j badType3) (int, int) { return 0, 0 } // NumOut() == 2
func (i badType3) Less(j int) bool { return false } // In(1) ≠ in

type badType4 int

func (i badType4) Compare(j badType4) bool { return false } // Out(0) ≠ out
func (i badType4) Less(j badType4) int { return 0 } // Out(0) ≠ out

func TestOrder(t *testing.T) {
if types.NewOrder(reflect.TypeOf(0)) != nil {
t.Error("types.NewOrder(int) returned non-nil func")
}

fn := types.NewOrder(reflect.TypeOf(compareType(0)))
if fn == nil {
t.Error("types.NewOrder(compareType) returned nil func")
} else {
a, b := reflect.ValueOf(compareType(1)), reflect.ValueOf(compareType(2))
test.EqualInt(t, fn(a, b), -1)
test.EqualInt(t, fn(b, a), 1)
test.EqualInt(t, fn(a, a), 0)
}

fn = types.NewOrder(reflect.TypeOf(lessType(0)))
if fn == nil {
t.Error("types.NewOrder(lessType) returned nil func")
} else {
a, b := reflect.ValueOf(lessType(1)), reflect.ValueOf(lessType(2))
test.EqualInt(t, fn(a, b), -1)
test.EqualInt(t, fn(b, a), 1)
test.EqualInt(t, fn(a, a), 0)
}

if types.NewOrder(reflect.TypeOf(badType1(0))) != nil {
t.Error("types.NewOrder(badType1) returned non-nil func")
}
if types.NewOrder(reflect.TypeOf(badType2(0))) != nil {
t.Error("types.NewOrder(badType2) returned non-nil func")
}
if types.NewOrder(reflect.TypeOf(badType3(0))) != nil {
t.Error("types.NewOrder(badType3) returned non-nil func")
}
if types.NewOrder(reflect.TypeOf(badType4(0))) != nil {
t.Error("types.NewOrder(badType4) returned non-nil func")
}
}
Loading

0 comments on commit 4e544bd

Please sign in to comment.