From 4e544bd2e38ff8e1eee4719020216a22a00b65b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20Soul=C3=A9?= Date: Sat, 1 Jan 2022 21:50:19 +0100 Subject: [PATCH] feat(Between): handles value implementing Compare & Less methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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é --- internal/types/order.go | 64 ++++++++++ internal/types/order_test.go | 92 +++++++++++++++ td/td_between.go | 189 ++++++++++++++++++++--------- td/td_between_test.go | 223 +++++++++++++++++++++++++++++------ td/td_contains_test.go | 4 +- td/td_json_test.go | 20 ++-- tools/gen_funcs.pl | 34 +++++- 7 files changed, 519 insertions(+), 107 deletions(-) create mode 100644 internal/types/order.go create mode 100644 internal/types/order_test.go diff --git a/internal/types/order.go b/internal/types/order.go new file mode 100644 index 00000000..47e497ab --- /dev/null +++ b/internal/types/order.go @@ -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 +} diff --git a/internal/types/order_test.go b/internal/types/order_test.go new file mode 100644 index 00000000..670ba2a7 --- /dev/null +++ b/internal/types/order_test.go @@ -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") + } +} diff --git a/td/td_between.go b/td/td_between.go index bacef1d3..d06978fb 100644 --- a/td/td_between.go +++ b/td/td_between.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018, Maxime Soulé +// Copyright (c) 2018-2022, Maxime Soulé // All rights reserved. // // This source code is licensed under the BSD-style license found in the @@ -58,17 +58,27 @@ type tdBetweenTime struct { var _ TestDeep = &tdBetweenTime{} +type tdBetweenCmp struct { + tdBetween + expectedType reflect.Type + cmp func(a, b reflect.Value) int +} + // summary(Between): checks that a number, string or time.Time is // between two bounds // input(Between): str,int,float,cplx(todo),struct(time.Time) // Between operator checks that data is between "from" and -// "to". "from" and "to" can be any numeric, string or time.Time (or -// assignable) value. "from" and "to" must be the same kind as the -// compared value if numeric, and the same type if string or time.Time -// (or assignable). time.Duration type is accepted as "to" when "from" -// is time.Time or convertible. "bounds" allows to specify whether -// bounds are included or not: +// "to". "from" and "to" can be any numeric, string, time.Time (or +// assignable) value or implement at least one of the two following +// methods: +// 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 +// +// "from" and "to" must be the same type as the compared value, except +// if BeLax config flag is true. time.Duration type is accepted as +// "to" when "from" is time.Time or convertible. "bounds" allows to +// specify whether bounds are included or not: // - BoundsInIn (default): between "from" and "to" both included // - BoundsInOut: between "from" included and "to" excluded // - BoundsOutIn: between "from" excluded and "to" included @@ -80,6 +90,9 @@ var _ TestDeep = &tdBetweenTime{} // tc.Cmp(t, 17, td.Between(10, 17, BoundsInOut)) // fails // tc.Cmp(t, 17, td.Between(10, 17, BoundsOutIn)) // succeeds // tc.Cmp(t, 17, td.Between(17, 20, BoundsOutOut)) // fails +// tc.Cmp(t, // succeeds +// netip.MustParse("127.0.0.1"), +// td.Between(netip.MustParse("127.0.0.0"), netip.MustParse("127.255.255.255"))) // // TypeBehind method returns the reflect.Type of "from" (same as the "to" one.) func Between(from, to interface{}, bounds ...BoundsKind) TestDeep { @@ -154,6 +167,22 @@ func (b *tdBetween) initBetween(usage string) TestDeep { b.expectedMax = b.expectedMin } + // Is any of: + // (T) Compare(T) int + // or + // (T) Less(T) bool + // available? + if cmp := types.NewOrder(b.expectedMin.Type()); cmp != nil { + if order := cmp(b.expectedMin, b.expectedMax); order > 0 { + b.expectedMin, b.expectedMax = b.expectedMax, b.expectedMin + } + return &tdBetweenCmp{ + tdBetween: *b, + expectedType: b.expectedMin.Type(), + cmp: cmp, + } + } + switch b.expectedMin.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if b.expectedMin.Int() > b.expectedMax.Int() { @@ -275,7 +304,8 @@ func (b *tdBetween) nFloat(tolerance reflect.Value) { // N operator compares a numeric data against "num" ± "tolerance". If // "tolerance" is missing, it defaults to 0. "num" and "tolerance" -// must be the same kind as the compared value. +// must be the same type as the compared value, except if BeLax config +// flag is true. // // td.Cmp(t, 12.2, td.N(12., 0.3)) // succeeds // td.Cmp(t, 12.2, td.N(12., 0.1)) // fails @@ -337,10 +367,14 @@ func N(num interface{}, tolerance ...interface{}) TestDeep { // input(Gt): str,int,float,cplx(todo),struct(time.Time) // Gt operator checks that data is greater than -// "minExpectedValue". "minExpectedValue" can be any numeric or -// time.Time (or assignable) value. "minExpectedValue" must be the -// same kind as the compared value if numeric, and the same type if -// time.Time (or assignable). +// "minExpectedValue". "minExpectedValue" can be any numeric, string, +// time.Time (or assignable) value or implements at least one of the +// two following methods: +// 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 +// +// "minExpectedValue" must be the same type as the compared value, +// except if BeLax config flag is true. // // td.Cmp(t, 17, td.Gt(15)) // before := time.Now() @@ -361,10 +395,14 @@ func Gt(minExpectedValue interface{}) TestDeep { // input(Gte): str,int,float,cplx(todo),struct(time.Time) // Gte operator checks that data is greater or equal than -// "minExpectedValue". "minExpectedValue" can be any numeric or -// time.Time (or assignable) value. "minExpectedValue" must be the -// same kind as the compared value if numeric, and the same type if -// time.Time (or assignable). +// "minExpectedValue". "minExpectedValue" can be any numeric, string, +// time.Time (or assignable) value or implements at least one of the +// two following methods: +// 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 +// +// "minExpectedValue" must be the same type as the compared value, +// except if BeLax config flag is true. // // td.Cmp(t, 17, td.Gte(17)) // before := time.Now() @@ -385,10 +423,14 @@ func Gte(minExpectedValue interface{}) TestDeep { // input(Lt): str,int,float,cplx(todo),struct(time.Time) // Lt operator checks that data is lesser than -// "maxExpectedValue". "maxExpectedValue" can be any numeric or -// time.Time (or assignable) value. "maxExpectedValue" must be the -// same kind as the compared value if numeric, and the same type if -// time.Time (or assignable). +// "maxExpectedValue". "maxExpectedValue" can be any numeric, string, +// time.Time (or assignable) value or implements at least one of the +// two following methods: +// 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 +// +// "maxExpectedValue" must be the same type as the compared value, +// except if BeLax config flag is true. // // td.Cmp(t, 17, td.Lt(19)) // before := time.Now() @@ -409,10 +451,14 @@ func Lt(maxExpectedValue interface{}) TestDeep { // input(Lte): str,int,float,cplx(todo),struct(time.Time) // Lte operator checks that data is lesser or equal than -// "maxExpectedValue". "maxExpectedValue" can be any numeric or -// time.Time (or assignable) value. "maxExpectedValue" must be the -// same kind as the compared value if numeric, and the same type if -// time.Time (or assignable). +// "maxExpectedValue". "maxExpectedValue" can be any numeric, string, +// time.Time (or assignable) value or implements at least one of the +// two following methods: +// 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 +// +// "maxExpectedValue" must be the same type as the compared value, +// except if BeLax config flag is true. // // td.Cmp(t, 17, td.Lte(17)) // before := time.Now() @@ -559,16 +605,9 @@ func (b *tdBetween) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error { return ctxerr.BooleanError } - var gotStr types.RawString - if got.Kind() == reflect.String { - gotStr = types.RawString(util.ToString(got)) - } else { - gotStr = types.RawString(fmt.Sprintf("%v", got)) - } - return ctx.CollectError(&ctxerr.Error{ Message: "values differ", - Got: gotStr, + Got: got, Expected: types.RawString(b.String()), }) } @@ -585,43 +624,27 @@ func (b *tdBetween) String() string { if b.minBound != boundNone { min = b.expectedMin.Interface() - - // We want strings be double-quoted - if b.expectedMin.Kind() == reflect.String { - minStr = util.ToString(min) - } else { - minStr = fmt.Sprintf("%v", min) - } + minStr = util.ToString(min) } if b.maxBound != boundNone { max = b.expectedMax.Interface() - - // We want strings be double-quoted - if b.expectedMax.Kind() == reflect.String { - maxStr = util.ToString(max) - } else { - maxStr = fmt.Sprintf("%v", max) - } - } - - if min == max { - return minStr + maxStr = util.ToString(max) } if min != nil { if max != nil { - return fmt.Sprintf("%v %c got %c %v", + return fmt.Sprintf("%s %c got %c %s", minStr, util.TernRune(b.minBound == boundIn, '≤', '<'), util.TernRune(b.maxBound == boundIn, '≤', '<'), maxStr) } - return fmt.Sprintf("%c %v", + return fmt.Sprintf("%c %s", util.TernRune(b.minBound == boundIn, '≥', '>'), minStr) } - return fmt.Sprintf("%c %v", + return fmt.Sprintf("%c %s", util.TernRune(b.maxBound == boundIn, '≤', '<'), maxStr) } @@ -686,7 +709,7 @@ func (b *tdBetweenTime) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Err } return ctx.CollectError(&ctxerr.Error{ Message: "values differ", - Got: types.RawString(fmt.Sprintf("%v", got.Interface())), + Got: got, Expected: types.RawString(b.String()), }) } @@ -696,3 +719,61 @@ func (b *tdBetweenTime) TypeBehind() reflect.Type { // built, there is never an error return b.expectedType } + +var _ TestDeep = &tdBetweenCmp{} + +func (b *tdBetweenCmp) Match(ctx ctxerr.Context, got reflect.Value) *ctxerr.Error { + // b.err != nil is not possible here, as when a *tdBetweenCmp is + // built, there is never an error + + if got.Type() != b.expectedType { + if ctx.BeLax && types.IsConvertible(got, b.expectedType) { + got = got.Convert(b.expectedType) + } else { + if ctx.BooleanError { + return ctxerr.BooleanError + } + return ctx.CollectError(ctxerr.TypeMismatch(got.Type(), b.expectedType)) + } + } + + var ok bool + if b.minBound != boundNone { + order := b.cmp(got, b.expectedMin) + if b.minBound == boundIn { + ok = order >= 0 + } else { + ok = order > 0 + } + } else { + ok = true + } + + if ok && b.maxBound != boundNone { + order := b.cmp(got, b.expectedMax) + if b.maxBound == boundIn { + ok = order <= 0 + } else { + ok = order < 0 + } + } + + if ok { + return nil + } + + if ctx.BooleanError { + return ctxerr.BooleanError + } + return ctx.CollectError(&ctxerr.Error{ + Message: "values differ", + Got: got, + Expected: types.RawString(b.String()), + }) +} + +func (b *tdBetweenCmp) TypeBehind() reflect.Type { + // b.err != nil is not possible here, as when a *tdBetweenCmp is + // built, there is never an error + return b.expectedType +} diff --git a/td/td_between_test.go b/td/td_between_test.go index 60180006..60068e04 100644 --- a/td/td_between_test.go +++ b/td/td_between_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018, Maxime Soulé +// Copyright (c) 2018-2022, Maxime Soulé // All rights reserved. // // This source code is licensed under the BSD-style license found in the @@ -189,8 +189,8 @@ func TestN(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe("DATA"), - Got: mustBe("0"), - Expected: mustBe(fmt.Sprintf("%v ≤ got ≤ %v", + Got: mustBe("(uint64) 0"), + Expected: mustBe(fmt.Sprintf("(uint64) %v ≤ got ≤ (uint64) %v", uint64(math.MaxUint64)-2, uint64(math.MaxUint64))), }) @@ -199,8 +199,8 @@ func TestN(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe("DATA"), - Got: mustBe(fmt.Sprintf("%v", uint64(math.MaxUint64))), - Expected: mustBe("0 ≤ got ≤ 2"), + Got: mustBe(fmt.Sprintf("(uint64) %v", uint64(math.MaxUint64))), + Expected: mustBe("(uint64) 0 ≤ got ≤ (uint64) 2"), }) // @@ -221,7 +221,7 @@ func TestN(t *testing.T) { Message: mustBe("values differ"), Path: mustBe("DATA"), Got: mustBe("10"), - Expected: mustBe("12"), + Expected: mustBe("12 ≤ got ≤ 12"), }) checkOK(t, int8(12), td.N(int8(12))) @@ -273,8 +273,8 @@ func TestN(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe("DATA"), - Got: mustBe("0"), - Expected: mustBe(fmt.Sprintf("%v ≤ got ≤ %v", + Got: mustBe("(int64) 0"), + Expected: mustBe(fmt.Sprintf("(int64) %v ≤ got ≤ (int64) %v", int64(math.MaxInt64)-2, int64(math.MaxInt64))), }) @@ -283,8 +283,8 @@ func TestN(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe("DATA"), - Got: mustBe("0"), - Expected: mustBe(fmt.Sprintf("%v ≤ got ≤ %v", + Got: mustBe("(int64) 0"), + Expected: mustBe(fmt.Sprintf("(int64) %v ≤ got ≤ (int64) %v", int64(math.MinInt64), int64(math.MinInt64)+2)), }) @@ -319,7 +319,7 @@ func TestN(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe("DATA"), - Got: mustBe("0"), + Got: mustBe("0.0"), Expected: mustBe(fmt.Sprintf("%v ≤ got ≤ +Inf", float64(math.MaxFloat64)-floatTol)), }) @@ -330,7 +330,7 @@ func TestN(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe("DATA"), - Got: mustBe("0"), + Got: mustBe("0.0"), Expected: mustBe(fmt.Sprintf("-Inf ≤ got ≤ %v", -float64(math.MaxFloat64)+floatTol)), }) @@ -453,15 +453,15 @@ func TestLGt(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe("DATA"), - Got: mustBe("2018-03-04 01:02:03 +0000 UTC"), - Expected: mustBe("> 2018-03-04 01:02:03 +0000 UTC"), + Got: mustBe("(time.Time) 2018-03-04 01:02:03 +0000 UTC"), + Expected: mustBe("> (time.Time) 2018-03-04 01:02:03 +0000 UTC"), }) checkError(t, gotDate, td.Lt(expectedDate), expectedError{ Message: mustBe("values differ"), Path: mustBe("DATA"), - Got: mustBe("2018-03-04 01:02:03 +0000 UTC"), - Expected: mustBe("< 2018-03-04 01:02:03 +0000 UTC"), + Got: mustBe("(time.Time) 2018-03-04 01:02:03 +0000 UTC"), + Expected: mustBe("< (time.Time) 2018-03-04 01:02:03 +0000 UTC"), }) // @@ -557,10 +557,10 @@ func TestBetweenTime(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe("DATA"), - Got: mustBe("2018-03-04 00:00:00 +0000 UTC"), - Expected: mustBe("2018-03-03 23:59:58 +0000 UTC" + + Got: mustBe("(time.Time) 2018-03-04 00:00:00 +0000 UTC"), + Expected: mustBe("(time.Time) 2018-03-03 23:59:58 +0000 UTC" + " ≤ got ≤ " + - "2018-03-03 23:59:59 +0000 UTC"), + "(time.Time) 2018-03-03 23:59:59 +0000 UTC"), }) checkError(t, date, @@ -568,10 +568,10 @@ func TestBetweenTime(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe("DATA"), - Got: mustBe("2018-03-04 00:00:00 +0000 UTC"), - Expected: mustBe("2018-03-03 23:59:58 +0000 UTC" + + Got: mustBe("(time.Time) 2018-03-04 00:00:00 +0000 UTC"), + Expected: mustBe("(time.Time) 2018-03-03 23:59:58 +0000 UTC" + " ≤ got < " + - "2018-03-04 00:00:00 +0000 UTC"), + "(time.Time) 2018-03-04 00:00:00 +0000 UTC"), }) checkError(t, date, @@ -579,10 +579,10 @@ func TestBetweenTime(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe("DATA"), - Got: mustBe("2018-03-04 00:00:00 +0000 UTC"), - Expected: mustBe("2018-03-04 00:00:00 +0000 UTC" + + Got: mustBe("(time.Time) 2018-03-04 00:00:00 +0000 UTC"), + Expected: mustBe("(time.Time) 2018-03-04 00:00:00 +0000 UTC" + " < got ≤ " + - "2018-03-04 00:00:02 +0000 UTC"), + "(time.Time) 2018-03-04 00:00:02 +0000 UTC"), }) checkError(t, "string", @@ -623,20 +623,173 @@ func TestBetweenTime(t *testing.T) { checkOK(t, now, td.Lt(now.Add(time.Second))) } -func TestBetweenTypeBehind(t *testing.T) { - equalTypes(t, td.Between(0, 10), 23) - equalTypes(t, td.Between(int64(0), int64(10)), int64(23)) +type compareType int - type MyTime time.Time +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 +} + +func TestBetweenCmp(t *testing.T) { + t.Run("compareType", func(t *testing.T) { + checkOK(t, compareType(5), td.Between(compareType(4), compareType(6))) + checkOK(t, compareType(5), td.Between(compareType(6), compareType(4))) + checkOK(t, compareType(5), td.Between(compareType(5), compareType(6))) + checkOK(t, compareType(5), td.Between(compareType(4), compareType(5))) + + checkOK(t, compareType(5), + td.Between(compareType(4), compareType(6), td.BoundsOutOut)) + checkError(t, compareType(5), + td.Between(compareType(5), compareType(6), td.BoundsOutIn), + expectedError{ + Message: mustBe("values differ"), + Path: mustBe("DATA"), + Got: mustBe("(td_test.compareType) 5"), + Expected: mustBe("(td_test.compareType) 5 < got ≤ (td_test.compareType) 6"), + }) + checkError(t, compareType(5), + td.Between(compareType(4), compareType(5), td.BoundsInOut), + expectedError{ + Message: mustBe("values differ"), + Path: mustBe("DATA"), + Got: mustBe("(td_test.compareType) 5"), + Expected: mustBe("(td_test.compareType) 4 ≤ got < (td_test.compareType) 5"), + }) + + // Other between forms + checkOK(t, compareType(5), td.Gt(compareType(4))) + checkOK(t, compareType(5), td.Gte(compareType(5))) + checkOK(t, compareType(5), td.Lt(compareType(6))) + checkOK(t, compareType(5), td.Lte(compareType(5))) + + // BeLax or not BeLax + for i, op := range []td.TestDeep{ + td.Between(compareType(4), compareType(6)), + td.Gt(compareType(4)), + td.Gte(compareType(5)), + td.Lt(compareType(6)), + td.Lte(compareType(5)), + } { + // Type mismatch if BeLax not enabled + checkError(t, 5, op, + expectedError{ + Message: mustBe("type mismatch"), + Path: mustBe("DATA"), + Got: mustBe("int"), + Expected: mustBe("td_test.compareType"), + }, + "Op #%d", i) + + // BeLax enabled is OK + checkOK(t, 5, td.Lax(op), "Op #%d", i) + } + + // In a private field + type private struct { + num compareType + } + checkOK(t, private{num: 5}, + td.Struct(private{}, + td.StructFields{ + "num": td.Between(compareType(4), compareType(6)), + })) + }) + + t.Run("lessType", func(t *testing.T) { + checkOK(t, lessType(5), td.Between(lessType(4), lessType(6))) + checkOK(t, lessType(5), td.Between(lessType(6), lessType(4))) + checkOK(t, lessType(5), td.Between(lessType(5), lessType(6))) + checkOK(t, lessType(5), td.Between(lessType(4), lessType(5))) + + checkOK(t, lessType(5), + td.Between(lessType(4), lessType(6), td.BoundsOutOut)) + checkError(t, lessType(5), + td.Between(lessType(5), lessType(6), td.BoundsOutIn), + expectedError{ + Message: mustBe("values differ"), + Path: mustBe("DATA"), + Got: mustBe("(td_test.lessType) 5"), + Expected: mustBe("(td_test.lessType) 5 < got ≤ (td_test.lessType) 6"), + }) + checkError(t, lessType(5), + td.Between(lessType(4), lessType(5), td.BoundsInOut), + expectedError{ + Message: mustBe("values differ"), + Path: mustBe("DATA"), + Got: mustBe("(td_test.lessType) 5"), + Expected: mustBe("(td_test.lessType) 4 ≤ got < (td_test.lessType) 5"), + }) + + // Other between forms + checkOK(t, lessType(5), td.Gt(lessType(4))) + checkOK(t, lessType(5), td.Gte(lessType(5))) + checkOK(t, lessType(5), td.Lt(lessType(6))) + checkOK(t, lessType(5), td.Lte(lessType(5))) + + // BeLax or not BeLax + for i, op := range []td.TestDeep{ + td.Between(lessType(4), lessType(6)), + td.Gt(lessType(4)), + td.Gte(lessType(5)), + td.Lt(lessType(6)), + td.Lte(lessType(5)), + } { + // Type mismatch if BeLax not enabled + checkError(t, 5, op, + expectedError{ + Message: mustBe("type mismatch"), + Path: mustBe("DATA"), + Got: mustBe("int"), + Expected: mustBe("td_test.lessType"), + }, + "Op #%d", i) + + // BeLax enabled is OK + checkOK(t, 5, td.Lax(op), "Op #%d", i) + } + + // In a private field + type private struct { + num lessType + } + checkOK(t, private{num: 5}, + td.Struct(private{}, + td.StructFields{ + "num": td.Between(lessType(4), lessType(6)), + })) + }) +} - equalTypes(t, td.Between(time.Time{}, time.Time{}), time.Time{}) - equalTypes(t, td.Between(MyTime{}, MyTime{}), MyTime{}) +func TestBetweenTypeBehind(t *testing.T) { + type MyTime time.Time + for _, typ := range []interface{}{ + 10, + int64(23), + int32(23), + time.Time{}, + MyTime{}, + compareType(0), + lessType(0), + } { + equalTypes(t, td.Between(typ, typ), typ) + equalTypes(t, td.Gt(typ), typ) + equalTypes(t, td.Gte(typ), typ) + equalTypes(t, td.Lt(typ), typ) + equalTypes(t, td.Lte(typ), typ) + } equalTypes(t, td.N(int64(23), int64(5)), int64(0)) - equalTypes(t, td.Gt(int32(23)), int32(0)) - equalTypes(t, td.Gte(int32(23)), int32(0)) - equalTypes(t, td.Lt(int32(23)), int32(0)) - equalTypes(t, td.Lte(int32(23)), int32(0)) // Erroneous op equalTypes(t, td.Between("test", 12), nil) diff --git a/td/td_contains_test.go b/td/td_contains_test.go index 253bdbe9..7004ae30 100644 --- a/td/td_contains_test.go +++ b/td/td_contains_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2018, Maxime Soulé +// Copyright (c) 2018-2022, Maxime Soulé // All rights reserved. // // This source code is licensed under the BSD-style license found in the @@ -61,7 +61,7 @@ func TestContains(t *testing.T) { Message: mustBe("does not contain"), Path: mustBe("DATA"), Got: mustContain(`"foobar"`), // as well as other items in fact... - Expected: mustBe(fmt.Sprintf("Contains(%d ≤ got ≤ %d)", 'y', 'z')), + Expected: mustBe(fmt.Sprintf("Contains((int32) %d ≤ got ≤ (int32) %d)", 'y', 'z')), }, testName) } } diff --git a/td/td_json_test.go b/td/td_json_test.go index abf9c15a..48908a2d 100644 --- a/td/td_json_test.go +++ b/td/td_json_test.go @@ -1,4 +1,4 @@ -// Copyright (c) 2019-2021, Maxime Soulé +// Copyright (c) 2019-2022, Maxime Soulé // All rights reserved. // // This source code is licensed under the BSD-style license found in the @@ -436,7 +436,7 @@ JSON({ ` JSON({ "label": { - "age": 12 ≤ got ≤ 24, + "age": 12.0 ≤ got ≤ 24.0, "name": HasPrefix("Bob") }, "zip": NotZero() @@ -461,8 +461,8 @@ func TestJSONInside(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe(`DATA["val2"]`), - Got: mustBe("2"), - Expected: mustBe("1 ≤ got < 2"), + Got: mustBe("2.0"), + Expected: mustBe("1.0 ≤ got < 2.0"), }) } @@ -481,8 +481,8 @@ func TestJSONInside(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe(`DATA["val2"]`), - Got: mustBe("2"), - Expected: mustBe("2 < got ≤ 3"), + Got: mustBe("2.0"), + Expected: mustBe("2.0 < got ≤ 3.0"), }) } for _, bounds := range []string{"][", "BoundsOutOut"} { @@ -491,8 +491,8 @@ func TestJSONInside(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe(`DATA["val2"]`), - Got: mustBe("2"), - Expected: mustBe("2 < got < 3"), + Got: mustBe("2.0"), + Expected: mustBe("2.0 < got < 3.0"), }, "using bounds %q", bounds) checkError(t, got, @@ -500,8 +500,8 @@ func TestJSONInside(t *testing.T) { expectedError{ Message: mustBe("values differ"), Path: mustBe(`DATA["val2"]`), - Got: mustBe("2"), - Expected: mustBe("1 < got < 2"), + Got: mustBe("2.0"), + Expected: mustBe("1.0 < got < 2.0"), }, "using bounds %q", bounds) } diff --git a/tools/gen_funcs.pl b/tools/gen_funcs.pl index 3451d62b..8f2ec8e8 100755 --- a/tools/gen_funcs.pl +++ b/tools/gen_funcs.pl @@ -1,6 +1,6 @@ #!/usr/bin/env perl -# Copyright (c) 2018, Maxime Soulé +# Copyright (c) 2018-2022, Maxime Soulé # All rights reserved. # # This source code is licensed under the BSD-style license found in the @@ -511,7 +511,9 @@ sub extract_params { my($comment, $func, $params) = ($1, $2, $3); - next if $func eq '(t *T) CmpDeeply' or $func eq 'CmpDeeply'; + next if ($func eq '(t *T) CmpDeeply' + or $func eq 'CmpDeeply' + or $func =~ /^\(t \*T\) (?:Log|Error|Fatal)Trace\z/); if ($params =~ /\Qargs ...interface{})\E\z/) { @@ -1055,7 +1057,20 @@ sub go_format my $pid = open2(my $fmt_out, my $fmt_in, 'gofmt', '-s'); - print $fmt_in <{name}.go:1 +$code +EOM + } + else + { + print $fmt_in <{name}.go:1 @@ -1063,11 +1078,18 @@ package x $code } EOM + } close $fmt_in; - (my $new_code = do { local $/; <$fmt_out> }) =~ s/[^\t]+//; - $new_code =~ s/\n\}\n\z//; - $new_code =~ s/^\t//gm; + my $new_code = do { <$fmt_out>; <$fmt_out>; <$fmt_out>; # skip 1st 3 lines + local $/; <$fmt_out> }; + chomp($new_code); + unless ($root) + { + $new_code =~ s/[^\t]+//; + $new_code =~ s/\n\}\z//; + $new_code =~ s/^\t//gm; + } waitpid $pid, 0; if ($? != 0)