diff --git a/README.md b/README.md index 0ad8b03d..4eabeec7 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ go-testdeep - [Latest news](#latest-news) - [Synopsis](#synopsis) +- [Godoc table of contents](doc/toc.md#godoc-table-of-contents) - [Installation](#installation) - [Presentation](#presentation) - [Available operators](#available-operators) @@ -28,6 +29,15 @@ go-testdeep ## Latest news +- 2019/07/07: multiple changes occurred: + - `*T` type now implements `TestingFT`, + - add [`UseEqual` feature](https://godoc.org/github.com/maxatome/go-testdeep#T.UseEqual) + aka. delegates comparison to `Equal()` method of object, + - [`tdhttp.NewRequest()`](https://godoc.org/github.com/maxatome/go-testdeep/helpers/tdhttp#NewRequest), + [`tdhttp.NewJSONRequest()`](https://godoc.org/github.com/maxatome/go-testdeep/helpers/tdhttp#NewJSONRequest) + and + [`tdhttp.NewXMLRequest()`](https://godoc.org/github.com/maxatome/go-testdeep/helpers/tdhttp#NewXMLRequest) + now accept headers definition, - 2019/05/01: new [`Keys`] & [`Values`] operators (and their friends [`CmpKeys`](https://godoc.org/github.com/maxatome/go-testdeep#CmpKeys), [`CmpValues`](https://godoc.org/github.com/maxatome/go-testdeep#CmpValues), @@ -41,8 +51,6 @@ go-testdeep and [`T.CmpDeeply`](https://godoc.org/github.com/maxatome/go-testdeep#T.CmpDeeply); - 2019/01/13: test failures output is now colored by default. See [Environment variables](#environment-variables) to configure it; -- 2019/01/07: introducing TestDeep helpers. First one is - [`tdhttp` or HTTP API testing helper](#tdhttp-or-http-api-testing-helper); - see [commits history](https://github.com/maxatome/go-testdeep/commits/master) for other/older changes. @@ -201,6 +209,8 @@ func TestCreateRecord(tt *testing.T) { } ``` +See [godoc table of contents](doc/toc.md#godoc-table-of-contents) for details. + ## Installation @@ -369,6 +379,8 @@ func TestCreateRecord(tt *testing.T) { } ``` +See [godoc table of contents](doc/toc.md#godoc-table-of-contents) for details. + ## Available operators diff --git a/config.go b/config.go index 8ebbf102..56d39153 100644 --- a/config.go +++ b/config.go @@ -36,10 +36,19 @@ type ContextConfig struct { // will be dumped. MaxErrors int // FailureIsFatal allows to Fatal() (instead of Error()) when a test - // fails. Using *testing.T instance as - // t.TestingFT value, FailNow() is called behind the scenes when - // Fatal() is called. See testing documentation for details. + // fails. Using *testing.T instance as t.TestingFT value, FailNow() + // is called behind the scenes when Fatal() is called. See testing + // documentation for details. FailureIsFatal bool + // UseEqual allows to use the Equal method on got (if it exists) or + // on any of its component to compare got and expected values. + // + // The signature of the Equal method should be: + // (A) Equal(B) bool + // with B assignable to A. + // + // See time.Time as an example of accepted Equal() method. + UseEqual bool } const ( @@ -66,6 +75,7 @@ var DefaultContextConfig = ContextConfig{ RootName: contextDefaultRootName, MaxErrors: getMaxErrorsFromEnv(), FailureIsFatal: false, + UseEqual: false, } func (c *ContextConfig) sanitize() { @@ -93,6 +103,7 @@ func newContextWithConfig(config ContextConfig) (ctx ctxerr.Context) { Visited: visited.NewVisited(), MaxErrors: config.MaxErrors, FailureIsFatal: config.FailureIsFatal, + UseEqual: config.UseEqual, } ctx.InitErrors() @@ -104,5 +115,6 @@ func newBooleanContext() ctxerr.Context { return ctxerr.Context{ Visited: visited.NewVisited(), BooleanError: true, + UseEqual: DefaultContextConfig.UseEqual, } } diff --git a/doc/FAQ.md b/doc/FAQ.md index 23b3a322..1c31bc47 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -1,5 +1,6 @@ # FAQ +- [Table of contents for all these functions/methods?](#table-of-contents-for-all-these-functionsmethods) - [How to mix strict requirements and simple assertions?](#how-to-mix-strict-requirements-and-simple-assertions) - [How to test `io.Reader` contents, like `http.Response.Body` for example?](#how-to-test-ioreader-contents-like-httpresponsebody-for-example) - [OK, but I prefer comparing `string`s instead of `byte`s](#ok-but-I-prefer-comparing-strings-instead-of-bytes) @@ -10,6 +11,11 @@ - [How to add a new operator?](#how-to-add-a-new-operator) +## Table of contents for all these functions/methods? + +Of course! See the [Godoc table of contents](toc.md#godoc-table-of-contents). + + ## How to mix strict requirements and simple assertions? ```golang @@ -323,6 +329,13 @@ You want to add a new `FooBar` operator. small description, respecting the alphabetical order; - in the [Operators vs go types](../README.md#operators-vs-go-types) matrix, still respecting the alphabetical order. +- [ ] in [`toc.md#godoc-table-of-contents`](toc.md#godoc-table-of-contents), + add this new new `FooBar` operator: + - in [Main shortcut functions](toc.md#main-shortcut-functions); + - in [Shortcut methods of `*testdeep.T`](toc.md#shortcut-methods-of-testdeep-t); + - in [`Testdeep` operators](toc.md#testdeep-operators), a simple copy + of the line inserted in [Available operators](../README.md#available-operators) + and its corresponding link of course. Each time you change `example_test.go`, re-run `./tools/gen_funcs.pl .` to update corresponding `CmpFooBar` & `T.FooBar` examples. diff --git a/doc/image.png b/doc/image.png index 5774c642..5eacf649 100644 Binary files a/doc/image.png and b/doc/image.png differ diff --git a/doc/toc.md b/doc/toc.md new file mode 100644 index 00000000..f1686cba --- /dev/null +++ b/doc/toc.md @@ -0,0 +1,429 @@ +# Godoc table of contents + +`testdeep` package provides a lot of functions and methods. +As [godoc format](https://godoc.org/github.com/maxatome/go-testdeep) +does not provide a way to tidy up all of these using sections, this +document tries to overcome. + +- [Main functions](#main-functions) +- [Main shortcut functions](#main-shortcut-functions) +- [`testdeep.T`](#testdeept) + - [Constructing `*testdeep.T`](#constructing-testdeept) + - [Configuring `*testdeep.T`](#configuring-testdeept) + - [Main methods of `*testdeep.T`](#main-methods-of-testdeept) + - [Shortcut methods of `*testdeep.T`](#shortcut-methods-of-testdeept) +- [`Testdeep` operators](#testdeep-operators) + + +## Main functions + +```go +import ( + "testing" + "github.com/maxatome/go-testdeep" +) + +func TestMyFunc(t *testing.T) { + // Compares MyFunc() result against a fixed value + testdeep.Cmp(t, MyFunc(), 128, "MyFunc() result is 128") + + // Compares MyFunc() result using the Between Testdeep operator + testdeep.Cmp(t, MyFunc(), testdeep.Between(100, 199), + "MyFunc() result is between 100 and 199") +} +``` + +- [`func Cmp(t TestingT, got, expected interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#Cmp) +- [`func CmpError(t TestingT, got error, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpError) +- [`func CmpFalse(t TestingT, got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpFalse) +- [`func CmpNoError(t TestingT, got error, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNoError) +- [`func CmpNotPanic(t TestingT, fn func(), args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNotPanic) +- [`func CmpPanic(t TestingT, fn func(), expectedPanic interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpPanic) +- [`func CmpTrue(t TestingT, got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpTrue) +- [`func EqDeeply(got, expected interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#EqDeeply) +- [`func EqDeeplyError(got, expected interface{}) error`](https://godoc.org/github.com/maxatome/go-testdeep#EqDeeplyError) + +Note that the convenient [`CmpNot()`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNot) function is listed +in the shortcut section below. + +[`CmpDeeply()`](https://godoc.org/github.com/maxatome/go-testdeep#Cmp) +is now replaced by +[`Cmp()`](https://godoc.org/github.com/maxatome/go-testdeep#Cmp), but it +is still available for backward compatibility purpose. + + +## Main shortcut functions + +```go +import ( + "testing" + "github.com/maxatome/go-testdeep" +) + +func TestMyFunc(t *testing.T) { + testdeep.CmpBetween(t, MyFunc(), 100, 199, testdeep.BoundsInIn, + "MyFunc() result is between 100 and 199") +} +``` + +For each of these functions, it is always a shortcut on +[`Cmp()`](https://godoc.org/github.com/maxatome/go-testdeep#Cmp) and +the correponding [Testdeep operator](#testdeep-operators): + +``` +CmpNot(t, got, expected, args...) ⇒ Cmp(t, got, Not(expected), args...) + ^-^ ^-^ + +--------------------------------------------+ +``` + +- [`func CmpAll(t TestingT, got interface{}, expectedValues []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpAll) +- [`func CmpAny(t TestingT, got interface{}, expectedValues []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpAny) +- [`func CmpArray(t TestingT, got interface{}, model interface{}, expectedEntries ArrayEntries, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpArray) +- [`func CmpArrayEach(t TestingT, got interface{}, expectedValue interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpArrayEach) +- [`func CmpBag(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpBag) +- [`func CmpBetween(t TestingT, got interface{}, from interface{}, to interface{}, bounds BoundsKind, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpBetween) +- [`func CmpCap(t TestingT, got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpCap) +- [`func CmpCode(t TestingT, got interface{}, fn interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpCode) +- [`func CmpContains(t TestingT, got interface{}, expectedValue interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpContains) +- [`func CmpContainsKey(t TestingT, got interface{}, expectedValue interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpContainsKey) +- [`func CmpEmpty(t TestingT, got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpEmpty) +- [`func CmpGt(t TestingT, got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpGt) +- [`func CmpGte(t TestingT, got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpGte) +- [`func CmpHasPrefix(t TestingT, got interface{}, expected string, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpHasPrefix) +- [`func CmpHasSuffix(t TestingT, got interface{}, expected string, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpHasSuffix) +- [`func CmpIsa(t TestingT, got interface{}, model interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpIsa) +- [`func CmpKeys(t TestingT, got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpKeys) +- [`func CmpLen(t TestingT, got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpLen) +- [`func CmpLt(t TestingT, got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpLt) +- [`func CmpLte(t TestingT, got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpLte) +- [`func CmpMap(t TestingT, got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpMap) +- [`func CmpMapEach(t TestingT, got interface{}, expectedValue interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpMapEach) +- [`func CmpN(t TestingT, got interface{}, num interface{}, tolerance interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpN) +- [`func CmpNaN(t TestingT, got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNaN) +- [`func CmpNil(t TestingT, got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNil) +- [`func CmpNone(t TestingT, got interface{}, expectedValues []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNone) +- [`func CmpNot(t TestingT, got interface{}, expected interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNot) +- [`func CmpNotAny(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNotAny) +- [`func CmpNotEmpty(t TestingT, got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNotEmpty) +- [`func CmpNotNaN(t TestingT, got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNotNaN) +- [`func CmpNotNil(t TestingT, got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNotNil) +- [`func CmpNotZero(t TestingT, got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpNotZero) +- [`func CmpPPtr(t TestingT, got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpPPtr) +- [`func CmpPtr(t TestingT, got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpPtr) +- [`func CmpRe(t TestingT, got interface{}, reg interface{}, capture interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpRe) +- [`func CmpReAll(t TestingT, got interface{}, reg interface{}, capture interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpReAll) +- [`func CmpSet(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpSet) +- [`func CmpShallow(t TestingT, got interface{}, expectedPtr interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpShallow) +- [`func CmpSlice(t TestingT, got interface{}, model interface{}, expectedEntries ArrayEntries, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpSlice) +- [`func CmpSmuggle(t TestingT, got interface{}, fn interface{}, expectedValue interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpSmuggle) +- [`func CmpString(t TestingT, got interface{}, expected string, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpString) +- [`func CmpStruct(t TestingT, got interface{}, model interface{}, expectedFields StructFields, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpStruct) +- [`func CmpSubBagOf(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpSubBagOf) +- [`func CmpSubMapOf(t TestingT, got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpSubMapOf) +- [`func CmpSubSetOf(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpSubSetOf) +- [`func CmpSuperBagOf(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpSuperBagOf) +- [`func CmpSuperMapOf(t TestingT, got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpSuperMapOf) +- [`func CmpSuperSetOf(t TestingT, got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpSuperSetOf) +- [`func CmpTruncTime(t TestingT, got interface{}, expectedTime interface{}, trunc time.Duration, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpTruncTime) +- [`func CmpValues(t TestingT, got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpValues) +- [`func CmpZero(t TestingT, got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#CmpZero) + +## [`testdeep.T`] + +### Constructing [`*testdeep.T`] + +```go +import ( + "testing" + "github.com/maxatome/go-testdeep" +) + +func TestMyFunc(tt *testing.T) { + t := testdeep.NewT(tt) + t.Cmp(MyFunc(), 12) +} +``` + +- [`func NewT(t TestingFT, config ...ContextConfig) *T`](https://godoc.org/github.com/maxatome/go-testdeep#NewT) + + +### Configuring [`*testdeep.T`] + +```go +func TestMyFunc(tt *testing.T) { + t := testdeep.NewT(tt).UseEqual().RootName("RECORD") + ... +} +``` + +- [`func (t *T) FailureIsFatal(enable ...bool) *T`](https://godoc.org/github.com/maxatome/go-testdeep#T.FailureIsFatal) +- [`func (t *T) RootName(rootName string) *T`](https://godoc.org/github.com/maxatome/go-testdeep#T.RootName) +- [`func (t *T) UseEqual(enable ...bool) *T`](https://godoc.org/github.com/maxatome/go-testdeep#T.UseEqual) + + +### Main methods of [`*testdeep.T`] + +```go +import ( + "testing" + "github.com/maxatome/go-testdeep" +) + +func TestMyFunc(tt *testing.T) { + t := testdeep.NewT(tt).UseEqual() + + // Compares MyFunc() result against a fixed value + t.Cmp(MyFunc(), 128, "MyFunc() result is 128") + + // Compares MyFunc() result using the Between Testdeep operator + t.Cmp(MyFunc(), testdeep.Between(100, 199), + "MyFunc() result is between 100 and 199") +} +``` + +- [`func (t *T) Cmp(got, expected interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Cmp) +- [`func (t *T) CmpError(got error, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.CmpError) +- [`func (t *T) CmpNoError(got error, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.CmpNoError) +- [`func (t *T) CmpNotPanic(fn func(), args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.CmpNotPanic) +- [`func (t *T) CmpPanic(fn func(), expected interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.CmpPanic) +- [`func (t *T) False(got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.False) +- [`func (t *T) Run(name string, f func(t *T)) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Run) +- [`func (t *T) True(got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.True) + +Note that the convenient +[`Not()`](https://godoc.org/github.com/maxatome/go-testdeep#T.Not) +method is listed in the shortcut section below. + +[`CmpDeeply()`](https://godoc.org/github.com/maxatome/go-testdeep#T.CmpDeeply) +method is now replaced by +[`Cmp()`](https://godoc.org/github.com/maxatome/go-testdeep#T.Cmp), +but it is still available for backward compatibility purpose. + + +### Shortcut methods of [`*testdeep.T`] + +```go +import ( + "testing" + "github.com/maxatome/go-testdeep" +) + +func TestMyFunc(tt *testing.T) { + t := testdeep.NewT(tt).UseEqual() + t.CmpBetween(MyFunc(), 100, 199, testdeep.BoundsInIn, + "MyFunc() result is between 100 and 199") +} +``` + +For each of these methods, it is always a shortcut on +[`T.Cmp()`](https://godoc.org/github.com/maxatome/go-testdeep#T.Cmp) and +the correponding [Testdeep operator](#testdeep-operators): + +``` +T.CmpNot(got, expected, args...) ⇒ T.Cmp(t, got, Not(expected), args...) + ^-^ ^-^ + +-------------------------------------------+ +``` + +- [`func (t *T) All(got interface{}, expectedValues []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.All) +- [`func (t *T) Any(got interface{}, expectedValues []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Any) +- [`func (t *T) Array(got interface{}, model interface{}, expectedEntries ArrayEntries, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Array) +- [`func (t *T) ArrayEach(got interface{}, expectedValue interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.ArrayEach) +- [`func (t *T) Bag(got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Bag) +- [`func (t *T) Between(got interface{}, from interface{}, to interface{}, bounds BoundsKind, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Between) +- [`func (t *T) Cap(got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Cap) +- [`func (t *T) Code(got interface{}, fn interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Code) +- [`func (t *T) Contains(got interface{}, expectedValue interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Contains) +- [`func (t *T) ContainsKey(got interface{}, expectedValue interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.ContainsKey) +- [`func (t *T) Empty(got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Empty) +- [`func (t *T) Gt(got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Gt) +- [`func (t *T) Gte(got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Gte) +- [`func (t *T) HasPrefix(got interface{}, expected string, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.HasPrefix) +- [`func (t *T) HasSuffix(got interface{}, expected string, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.HasSuffix) +- [`func (t *T) Isa(got interface{}, model interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Isa) +- [`func (t *T) Keys(got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Keys) +- [`func (t *T) Len(got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Len) +- [`func (t *T) Lt(got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Lt) +- [`func (t *T) Lte(got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Lte) +- [`func (t *T) Map(got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Map) +- [`func (t *T) MapEach(got interface{}, expectedValue interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.MapEach) +- [`func (t *T) N(got interface{}, num interface{}, tolerance interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.N) +- [`func (t *T) NaN(got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.NaN) +- [`func (t *T) Nil(got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Nil) +- [`func (t *T) None(got interface{}, expectedValues []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.None) +- [`func (t *T) Not(got interface{}, expected interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Not) +- [`func (t *T) NotAny(got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.NotAny) +- [`func (t *T) NotEmpty(got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.NotEmpty) +- [`func (t *T) NotNaN(got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.NotNaN) +- [`func (t *T) NotNil(got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.NotNil) +- [`func (t *T) NotZero(got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.NotZero) +- [`func (t *T) PPtr(got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.PPtr) +- [`func (t *T) Ptr(got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Ptr) +- [`func (t *T) Re(got interface{}, reg interface{}, capture interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Re) +- [`func (t *T) ReAll(got interface{}, reg interface{}, capture interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.ReAll) +- [`func (t *T) Set(got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Set) +- [`func (t *T) Shallow(got interface{}, expectedPtr interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Shallow) +- [`func (t *T) Slice(got interface{}, model interface{}, expectedEntries ArrayEntries, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Slice) +- [`func (t *T) Smuggle(got interface{}, fn interface{}, expectedValue interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Smuggle) +- [`func (t *T) String(got interface{}, expected string, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.String) +- [`func (t *T) Struct(got interface{}, model interface{}, expectedFields StructFields, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Struct) +- [`func (t *T) SubBagOf(got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.SubBagOf) +- [`func (t *T) SubMapOf(got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.SubMapOf) +- [`func (t *T) SubSetOf(got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.SubSetOf) +- [`func (t *T) SuperBagOf(got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.SuperBagOf) +- [`func (t *T) SuperMapOf(got interface{}, model interface{}, expectedEntries MapEntries, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.SuperMapOf) +- [`func (t *T) SuperSetOf(got interface{}, expectedItems []interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.SuperSetOf) +- [`func (t *T) TruncTime(got interface{}, expectedTime interface{}, trunc time.Duration, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.TruncTime) +- [`func (t *T) Values(got interface{}, val interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Values) +- [`func (t *T) Zero(got interface{}, args ...interface{}) bool`](https://godoc.org/github.com/maxatome/go-testdeep#T.Zero) + + +## `Testdeep` operators + +- [`All`] all expected values have to match; +- [`Any`] at least one expected value have to match; +- [`Array`] compares the contents of an array or a pointer on an + array; +- [`ArrayEach`] compares each array or slice item; +- [`Bag`] compares the contents of an array or a slice without taking + care of the order of items; +- [`Between`] checks that a number, string or [`time.Time`] is between two + bounds; +- [`Cap`] checks an array, slice or channel capacity; +- [`Code`] allows to use a custom function; +- [`Contains`] checks that a string, [`error`] or [`fmt.Stringer`] + interfaces contain a sub-string; or an array, slice or map contain a + value; +- [`ContainsKey`] checks that a map contains a key; +- [`Empty`] checks that an array, a channel, a map, a slice or a + string is empty; +- [`Gt`] checks that a number, string or [`time.Time`] is greater than a + value; +- [`Gte`] checks that a number, string or [`time.Time`] is greater or equal + than a value; +- [`HasPrefix`] checks the prefix of a string, [`error`] or + [`fmt.Stringer`] interfaces; +- [`HasSuffix`] checks the suffix of a string, [`error`] or + [`fmt.Stringer`] interfaces; +- [`Ignore`] allows to ignore a comparison; +- [`Isa`] checks the data type or whether data implements an interface + or not; +- [`Keys`] checks keys of a map; +- [`Len`] checks an array, slice, map, string or channel length; +- [`Lt`] checks that a number, string or [`time.Time`] is lesser than a value; +- [`Lte`] checks that a number, string or [`time.Time`] is lesser or equal + than a value; +- [`Map`] compares the contents of a map; +- [`MapEach`] compares each map entry; +- [`N`] compares a number with a tolerance value; +- [`NaN`] checks a floating number is [`math.NaN`]; +- [`Nil`] compares to `nil`; +- [`None`] no values have to match; +- [`NotAny`] compares the contents of an array or a slice, no values + have to match; +- [`Not`] value must not match; +- [`NotEmpty`] checks that an array, a channel, a map, a slice or a + string is not empty; +- [`NotNaN`] checks a floating number is not [`math.NaN`]; +- [`NotNil`] checks that data is not `nil`; +- [`NotZero`] checks that data is not zero regarding its type; +- [`PPtr`] allows to easily test a pointer of pointer value, +- [`Ptr`] allows to easily test a pointer value, +- [`Re`] allows to apply a regexp on a string (or convertible), + `[]byte`, [`error`] or [`fmt.Stringer`] interfaces, and even test + the captured groups; +- [`ReAll`] allows to successively apply a regexp on a string (or + convertible), `[]byte`, [`error`] or [`fmt.Stringer`] interfaces, + and even test the captured groups; +- [`Set`] compares the contents of an array or a slice ignoring + duplicates and without taking care of the order of items; +- [`Shallow`] compares pointers only, not their contents; +- [`Slice`] compares the contents of a slice or a pointer on a slice; +- [`Smuggle`] changes data contents or mutates it into another type + via a custom function or a struct fields-path before stepping down + in favor of generic comparison process; +- [`String`] checks a string, [`error`] or [`fmt.Stringer`] interfaces + string contents; +- [`Struct`] compares the contents of a struct or a pointer on a + struct; +- [`SubBagOf`] compares the contents of an array or a slice without + taking care of the order of items but with potentially some + exclusions; +- [`SubMapOf`] compares the contents of a map but with potentially + some exclusions; +- [`SubSetOf`] compares the contents of an array or a slice ignoring + duplicates and without taking care of the order of items but with + potentially some exclusions; +- [`SuperBagOf`] compares the contents of an array or a slice without + taking care of the order of items but with potentially some extra + items; +- [`SuperMapOf`] compares the contents of a map but with potentially + some extra entries; +- [`SuperSetOf`] compares the contents of an array or a slice ignoring + duplicates and without taking care of the order of items but with + potentially some extra items; +- [`TruncTime`] compares time.Time (or assignable) values after + truncating them; +- [`Values`] checks values of a map; +- [`Zero`] checks data against its zero'ed conterpart. + +[`All`]: https://godoc.org/github.com/maxatome/go-testdeep#All +[`Any`]: https://godoc.org/github.com/maxatome/go-testdeep#Any +[`Array`]: https://godoc.org/github.com/maxatome/go-testdeep#Array +[`ArrayEach`]: https://godoc.org/github.com/maxatome/go-testdeep#ArrayEach +[`Bag`]: https://godoc.org/github.com/maxatome/go-testdeep#Bag +[`Between`]: https://godoc.org/github.com/maxatome/go-testdeep#Between +[`Cap`]: https://godoc.org/github.com/maxatome/go-testdeep#Cap +[`Code`]: https://godoc.org/github.com/maxatome/go-testdeep#Code +[`Contains`]: https://godoc.org/github.com/maxatome/go-testdeep#Contains +[`ContainsKey`]: https://godoc.org/github.com/maxatome/go-testdeep#ContainsKey +[`Empty`]: https://godoc.org/github.com/maxatome/go-testdeep#Empty +[`Gt`]: https://godoc.org/github.com/maxatome/go-testdeep#Gt +[`Gte`]: https://godoc.org/github.com/maxatome/go-testdeep#Gte +[`HasPrefix`]: https://godoc.org/github.com/maxatome/go-testdeep#HasPrefix +[`HasSuffix`]: https://godoc.org/github.com/maxatome/go-testdeep#HasSuffix +[`Ignore`]: https://godoc.org/github.com/maxatome/go-testdeep#Isa +[`Isa`]: https://godoc.org/github.com/maxatome/go-testdeep#Isa +[`Keys`]: https://godoc.org/github.com/maxatome/go-testdeep#Keys +[`Len`]: https://godoc.org/github.com/maxatome/go-testdeep#Len +[`Lt`]: https://godoc.org/github.com/maxatome/go-testdeep#Lt +[`Lte`]: https://godoc.org/github.com/maxatome/go-testdeep#Lte +[`Map`]: https://godoc.org/github.com/maxatome/go-testdeep#Map +[`MapEach`]: https://godoc.org/github.com/maxatome/go-testdeep#MapEach +[`N`]: https://godoc.org/github.com/maxatome/go-testdeep#N +[`NaN`]: https://godoc.org/github.com/maxatome/go-testdeep#NaN +[`Nil`]: https://godoc.org/github.com/maxatome/go-testdeep#Nil +[`None`]: https://godoc.org/github.com/maxatome/go-testdeep#None +[`NotAny`]: https://godoc.org/github.com/maxatome/go-testdeep#NotAny +[`Not`]: https://godoc.org/github.com/maxatome/go-testdeep#Not +[`NotEmpty`]: https://godoc.org/github.com/maxatome/go-testdeep#NotEmpty +[`NotNaN`]: https://godoc.org/github.com/maxatome/go-testdeep#NotNaN +[`NotNil`]: https://godoc.org/github.com/maxatome/go-testdeep#NotNil +[`NotZero`]: https://godoc.org/github.com/maxatome/go-testdeep#NotZero +[`PPtr`]: https://godoc.org/github.com/maxatome/go-testdeep#PPtr +[`Ptr`]: https://godoc.org/github.com/maxatome/go-testdeep#Ptr +[`Re`]: https://godoc.org/github.com/maxatome/go-testdeep#Re +[`ReAll`]: https://godoc.org/github.com/maxatome/go-testdeep#ReAll +[`Set`]: https://godoc.org/github.com/maxatome/go-testdeep#Set +[`Shallow`]: https://godoc.org/github.com/maxatome/go-testdeep#Shallow +[`Slice`]: https://godoc.org/github.com/maxatome/go-testdeep#Slice +[`Smuggle`]: https://godoc.org/github.com/maxatome/go-testdeep#Smuggle +[`String`]: https://godoc.org/github.com/maxatome/go-testdeep#String +[`Struct`]: https://godoc.org/github.com/maxatome/go-testdeep#Struct +[`SubBagOf`]: https://godoc.org/github.com/maxatome/go-testdeep#SubBagOf +[`SubMapOf`]: https://godoc.org/github.com/maxatome/go-testdeep#SubMapOf +[`SubSetOf`]: https://godoc.org/github.com/maxatome/go-testdeep#SubSetOf +[`SuperBagOf`]: https://godoc.org/github.com/maxatome/go-testdeep#SuperBagOf +[`SuperMapOf`]: https://godoc.org/github.com/maxatome/go-testdeep#SuperMapOf +[`SuperSetOf`]: https://godoc.org/github.com/maxatome/go-testdeep#SuperSetOf +[`TruncTime`]: https://godoc.org/github.com/maxatome/go-testdeep#TruncTime +[`Values`]: https://godoc.org/github.com/maxatome/go-testdeep#Values +[`Zero`]: https://godoc.org/github.com/maxatome/go-testdeep#Zero + +[`testdeep.T`]: https://godoc.org/github.com/maxatome/go-testdeep#T +[`*testdeep.T`]: https://godoc.org/github.com/maxatome/go-testdeep#T + +[`error`]: https://golang.org/ref/spec#Errors +[`fmt.Stringer`]: https://golang.org/pkg/fmt/#Stringer +[`time.Time`]: https://golang.org/pkg/time/ +[`math.NaN`]: https://golang.org/pkg/math/#NaN diff --git a/equal.go b/equal.go index 4dd1653c..5f4b31d1 100644 --- a/equal.go +++ b/equal.go @@ -81,6 +81,24 @@ func nilHandler(ctx ctxerr.Context, got, expected reflect.Value) *ctxerr.Error { return ctx.CollectError(&err) } +func isCustomEqual(a, b reflect.Value) (bool, bool) { + aType, bType := a.Type(), b.Type() + + equal, ok := aType.MethodByName("Equal") + if ok { + ft := equal.Type + if !ft.IsVariadic() && + ft.NumIn() == 2 && + ft.NumOut() == 1 && + ft.In(0).AssignableTo(ft.In(1)) && + ft.Out(0) == boolType && + bType.AssignableTo(ft.In(1)) { + return true, equal.Func.Call([]reflect.Value{a, b})[0].Bool() + } + } + return false, false +} + func deepValueEqual(ctx ctxerr.Context, got, expected reflect.Value) (err *ctxerr.Error) { if !got.IsValid() || !expected.IsValid() { if got.IsValid() == expected.IsValid() { @@ -89,12 +107,29 @@ func deepValueEqual(ctx ctxerr.Context, got, expected reflect.Value) (err *ctxer return nilHandler(ctx, got, expected) } - // Special case, "got" implements testDeeper: only if allowed + // "got" must not implement testDeeper if got.Type().Implements(testDeeper) { panic("Found a TestDeep operator in got param, " + "can only use it in expected one!") } + if ctx.UseEqual { + hasEqual, isEqual := isCustomEqual(got, expected) + if hasEqual { + if isEqual { + return + } + if ctx.BooleanError { + return ctxerr.BooleanError + } + return ctx.CollectError(&ctxerr.Error{ + Message: "got.Equal(expected) failed", + Got: got, + Expected: expected, + }) + } + } + if got.Type() != expected.Type() { if expected.Type().Implements(testDeeper) { curOperator := expected.Interface().(TestDeep) diff --git a/equal_test.go b/equal_test.go index 342c2398..3599f3e8 100644 --- a/equal_test.go +++ b/equal_test.go @@ -8,6 +8,7 @@ package testdeep_test import ( "testing" + "time" "github.com/maxatome/go-testdeep" "github.com/maxatome/go-testdeep/internal/ctxerr" @@ -626,3 +627,146 @@ func TestEqualPanic(t *testing.T) { }, "Found a TestDeep operator in got param, can only use it in expected one!") } + +type AssignableType1 struct{ x, Ignore int } + +func (a AssignableType1) Equal(b AssignableType1) bool { + return a.x == b.x +} + +type AssignableType2 struct{ x, Ignore int } + +func (a AssignableType2) Equal(b struct{ x, Ignore int }) bool { + return a.x == b.x +} + +type AssignablePtrType3 struct{ x, Ignore int } + +func (a *AssignablePtrType3) Equal(b *AssignablePtrType3) bool { + if a == nil { + return b == nil + } + return b != nil && a.x == b.x +} + +type BadEqual1 int + +func (b BadEqual1) Equal(o ...BadEqual1) bool { return true } // IsVariadic + +type BadEqual2 int + +func (b BadEqual2) Equal() bool { return true } // NumIn() ≠ 2 + +type BadEqual3 int + +func (b BadEqual3) Equal(o BadEqual3) (int, int) { return 1, 2 } // NumOut() ≠ 1 + +type BadEqual4 int + +func (b BadEqual4) Equal(o string) int { return 1 } // !AssignableTo + +type BadEqual5 int + +func (b BadEqual5) Equal(o BadEqual5) int { return 1 } // Out=bool + +func TestUseEqualGlobalt(t *testing.T) { + defer func() { testdeep.DefaultContextConfig.UseEqual = false }() + testdeep.DefaultContextConfig.UseEqual = true + + // Real case with time.Time + time1 := time.Now() + time2 := time1.Truncate(0) + if !time1.Equal(time2) || !time2.Equal(time1) { + t.Fatal("time.Equal() does not work as expected") + } + + checkOK(t, time1, time2) + checkOK(t, time2, time1) + + // AssignableType1 + a1 := AssignableType1{x: 13, Ignore: 666} + b1 := AssignableType1{x: 13, Ignore: 789} + checkOK(t, a1, b1) + checkOK(t, b1, a1) + checkError(t, a1, AssignableType1{x: 14, Ignore: 666}, + expectedError{ + Message: mustBe("got.Equal(expected) failed"), + Path: mustBe("DATA"), + Got: mustContain("x: (int) 13,"), + Expected: mustContain("x: (int) 14,"), + }) + + bs := struct{ x, Ignore int }{x: 13, Ignore: 789} + checkOK(t, a1, bs) // bs type is assignable to AssignableType1 + checkError(t, bs, a1, + expectedError{ + Message: mustBe("type mismatch"), + Path: mustBe("DATA"), + Got: mustBe("struct { x int; Ignore int }"), + Expected: mustBe("testdeep_test.AssignableType1"), + }) + + // AssignableType2 + a2 := AssignableType2{x: 13, Ignore: 666} + b2 := AssignableType2{x: 13, Ignore: 789} + checkOK(t, a2, b2) + checkOK(t, b2, a2) + checkOK(t, a2, bs) // bs type is assignable to AssignableType2 + checkError(t, bs, a2, + expectedError{ + Message: mustBe("type mismatch"), + Path: mustBe("DATA"), + Got: mustBe("struct { x int; Ignore int }"), + Expected: mustBe("testdeep_test.AssignableType2"), + }) + + // AssignablePtrType3 + a3 := &AssignablePtrType3{x: 13, Ignore: 666} + b3 := &AssignablePtrType3{x: 13, Ignore: 789} + checkOK(t, a3, b3) + checkOK(t, b3, a3) + checkError(t, a3, &bs, // &bs type not assignable to AssignablePtrType3 + expectedError{ + Message: mustBe("type mismatch"), + Path: mustBe("DATA"), + Got: mustBe("*testdeep_test.AssignablePtrType3"), + Expected: mustBe("*struct { x int; Ignore int }"), + }) + checkOK(t, (*AssignablePtrType3)(nil), (*AssignablePtrType3)(nil)) + checkError(t, (*AssignablePtrType3)(nil), b3, + expectedError{ + Message: mustBe("got.Equal(expected) failed"), + Path: mustBe("DATA"), + Got: mustBe("(*testdeep_test.AssignablePtrType3)()"), + Expected: mustContain("x: (int) 13,"), + }) + checkError(t, b3, (*AssignablePtrType3)(nil), + expectedError{ + Message: mustBe("got.Equal(expected) failed"), + Path: mustBe("DATA"), + Got: mustContain("x: (int) 13,"), + Expected: mustBe("(*testdeep_test.AssignablePtrType3)()"), + }) + + // (A) Equal(A) method not found + checkError(t, BadEqual1(1), BadEqual1(2), + expectedError{ + Message: mustBe("values differ"), + }) + checkError(t, BadEqual2(1), BadEqual2(2), + expectedError{ + Message: mustBe("values differ"), + }) + checkError(t, BadEqual3(1), BadEqual3(2), + expectedError{ + Message: mustBe("values differ"), + }) + checkError(t, BadEqual4(1), BadEqual4(2), + expectedError{ + Message: mustBe("values differ"), + }) + checkError(t, BadEqual5(1), BadEqual5(2), + expectedError{ + Message: mustBe("values differ"), + }) +} diff --git a/helpers/tdhttp/http.go b/helpers/tdhttp/http.go index 16d73f8e..ba924a0f 100644 --- a/helpers/tdhttp/http.go +++ b/helpers/tdhttp/http.go @@ -52,7 +52,7 @@ func CmpMarshaledResponse(tt td.TestingFT, var statusMismatch, headerMismatch bool - t := td.NewT(tt) // nolint: vetshadow + t := td.NewT(tt) w := httptest.NewRecorder() diff --git a/helpers/tdhttp/http_test.go b/helpers/tdhttp/http_test.go index 30755569..06f81e38 100644 --- a/helpers/tdhttp/http_test.go +++ b/helpers/tdhttp/http_test.go @@ -177,7 +177,7 @@ func TestCmpResponse(tt *testing.T) { }, }, } { - t.Run(curTest.Name, + t.RunT(curTest.Name, func(t *td.T) { testCmpResponse(t, tdhttp.CmpResponse, "CmpResponse", curTest) }) @@ -235,7 +235,7 @@ func TestCmpJSONResponse(tt *testing.T) { }, }, } { - t.Run(curTest.Name, + t.RunT(curTest.Name, func(t *td.T) { testCmpResponse(t, tdhttp.CmpJSONResponse, "CmpJSONResponse", curTest) }) @@ -295,7 +295,7 @@ func TestCmpXMLResponse(tt *testing.T) { }, }, } { - t.Run(curTest.Name, + t.RunT(curTest.Name, func(t *td.T) { testCmpResponse(t, tdhttp.CmpXMLResponse, "CmpXMLResponse", curTest) }) diff --git a/helpers/tdhttp/request.go b/helpers/tdhttp/request.go index a80e78c0..7f92108b 100644 --- a/helpers/tdhttp/request.go +++ b/helpers/tdhttp/request.go @@ -10,36 +10,67 @@ import ( "bytes" "encoding/json" "encoding/xml" + "io" "net/http" "net/http/httptest" ) -// NewRequest is an alias on net/http/httptest.NewRequest for -// convenience purpose. -var NewRequest = httptest.NewRequest +func addHeaders(req *http.Request, headers []string) *http.Request { + i := 0 + for ; i < len(headers)-1; i += 2 { + req.Header.Add(headers[i], headers[i+1]) + } + if i < len(headers) { + req.Header.Add(headers[len(headers)-1], "") + } + return req +} + +// NewRequest creates a new HTTP request as +// net/http/httptest.NewRequest does, with the ability to immediately +// add some headers. +// +// req := NewRequest("POST", "/pdf", body, +// "Content-type", "application/pdf", +// ) +func NewRequest(method, target string, body io.Reader, headers ...string) *http.Request { + return addHeaders(httptest.NewRequest(method, target, body), headers) +} -// NewJSONRequest creates a new HTTP request with body marshaled to JSON. -func NewJSONRequest(method, target string, body interface{}) *http.Request { +// NewJSONRequest creates a new HTTP request with body marshaled to +// JSON. "Content-Type" header is automatically set to +// "application/json". Other headers can be added via headers, as in: +// +// req := NewJSONRequest("POST", "/data", body, +// "X-Foo", "Foo-value", +// "X-Zip", "Zip-value", +// ) +func NewJSONRequest(method, target string, body interface{}, headers ...string) *http.Request { b, err := json.Marshal(body) if err != nil { panic("JSON encoding failed: " + err.Error()) } - req := NewRequest(method, target, bytes.NewBuffer(b)) - req.Header.Add("Content-Type", "application/json") - - return req + return addHeaders(NewRequest(method, target, bytes.NewBuffer(b)), + append(headers[:len(headers):len(headers)], + "Content-Type", "application/json")) } -// NewXMLRequest creates a new HTTP request with body marshaled to XML. -func NewXMLRequest(method, target string, body interface{}) *http.Request { +// NewXMLRequest creates a new HTTP request with body marshaled to +// XML. "Content-Type" header is automatically set to +// "application/xml". Other headers can be added via headers, as in: +// +// req := NewXMLRequest("POST", "/data", body, +// "X-Foo", "Foo-value", +// "X-Zip", "Zip-value", +// ) +func NewXMLRequest(method, target string, body interface{}, headers ...string) *http.Request { b, err := xml.Marshal(body) if err != nil { panic("XML encoding failed: " + err.Error()) } - req := NewRequest(method, target, bytes.NewBuffer(b)) - req.Header.Add("Content-Type", "application/xml") - - return req + return addHeaders(NewRequest(method, target, bytes.NewBuffer(b)), + append(headers[:len(headers):len(headers)], + "Content-Type", "application/xml")) } diff --git a/helpers/tdhttp/request_test.go b/helpers/tdhttp/request_test.go index bd438a20..6cd74704 100644 --- a/helpers/tdhttp/request_test.go +++ b/helpers/tdhttp/request_test.go @@ -14,6 +14,28 @@ import ( "github.com/maxatome/go-testdeep/helpers/tdhttp" ) +func TestNewRequest(tt *testing.T) { + t := td.NewT(tt) + + t.RunT("NewRequest", func(t *td.T) { + req := tdhttp.NewRequest("GET", "/path", nil, + "Foo", "Bar", + "Zip", "Test") + + t.String(req.Header.Get("Foo"), "Bar") + t.String(req.Header.Get("Zip"), "Test") + }) + + t.RunT("NewRequest last header value less", func(t *td.T) { + req := tdhttp.NewRequest("GET", "/path", nil, + "Foo", "Bar", + "Zip") + + t.String(req.Header.Get("Foo"), "Bar") + t.String(req.Header.Get("Zip"), "") + }) +} + type TestStruct struct { Name string `json:"name" xml:"name"` } @@ -21,13 +43,17 @@ type TestStruct struct { func TestNewJSONRequest(tt *testing.T) { t := td.NewT(tt) - t.Run("NewJSONRequest", func(t *td.T) { + t.RunT("NewJSONRequest", func(t *td.T) { req := tdhttp.NewJSONRequest("GET", "/path", TestStruct{ Name: "Bob", - }) + }, + "Foo", "Bar", + "Zip", "Test") t.String(req.Header.Get("Content-Type"), "application/json") + t.String(req.Header.Get("Foo"), "Bar") + t.String(req.Header.Get("Zip"), "Test") body, err := ioutil.ReadAll(req.Body) if t.CmpNoError(err, "read request body") { @@ -35,7 +61,7 @@ func TestNewJSONRequest(tt *testing.T) { } }) - t.Run("NewJSONRequest panic", func(t *td.T) { + t.RunT("NewJSONRequest panic", func(t *td.T) { t.CmpPanic( func() { tdhttp.NewJSONRequest("GET", "/path", func() {}) }, td.NotEmpty(), @@ -46,13 +72,17 @@ func TestNewJSONRequest(tt *testing.T) { func TestNewXMLRequest(tt *testing.T) { t := td.NewT(tt) - t.Run("NewXMLRequest", func(t *td.T) { + t.RunT("NewXMLRequest", func(t *td.T) { req := tdhttp.NewXMLRequest("GET", "/path", TestStruct{ Name: "Bob", - }) + }, + "Foo", "Bar", + "Zip", "Test") t.String(req.Header.Get("Content-Type"), "application/xml") + t.String(req.Header.Get("Foo"), "Bar") + t.String(req.Header.Get("Zip"), "Test") body, err := ioutil.ReadAll(req.Body) if t.CmpNoError(err, "read request body") { @@ -60,7 +90,7 @@ func TestNewXMLRequest(tt *testing.T) { } }) - t.Run("NewXMLRequest panic", func(t *td.T) { + t.RunT("NewXMLRequest panic", func(t *td.T) { t.CmpPanic( func() { tdhttp.NewXMLRequest("GET", "/path", func() {}) }, td.NotEmpty(), diff --git a/internal/ctxerr/context.go b/internal/ctxerr/context.go index 12293e5d..4cb7046a 100644 --- a/internal/ctxerr/context.go +++ b/internal/ctxerr/context.go @@ -29,8 +29,10 @@ type Context struct { // checked. Can be used to avoid filling Error{} with expensive // computations. BooleanError bool - // See ContexConfig.FailureIsFatal for details + // See ContexConfig.FailureIsFatal for details. FailureIsFatal bool + // See ContexConfig.UseEqual for details. + UseEqual bool } // InitErrors initializes Context *Errors slice, if MaxErrors < 0 or diff --git a/private_test.go b/private_test.go index 75d53ab1..1f32c1f4 100644 --- a/private_test.go +++ b/private_test.go @@ -15,7 +15,7 @@ import ( // Edge cases not tested elsewhere... func TestBase(t *testing.T) { - td := Base{} + td := base{} td.setLocation(200) if td.location.File != "???" && td.location.Line != 0 { diff --git a/t_struct.go b/t_struct.go index fe2ca32b..6ebf072f 100644 --- a/t_struct.go +++ b/t_struct.go @@ -16,6 +16,8 @@ type T struct { Config ContextConfig // defaults to DefaultContextConfig } +var _ TestingFT = T{} + // NewT returns a new T instance. Typically used as: // // import ( @@ -58,8 +60,8 @@ type T struct { // } // // "config" is an optional argument and, if passed, must be unique. It -// allows to configure how failures will be rendered during the life -// time of the returned instance. +// allows to configure how failures will be rendered during the +// lifetime of the returned instance. // // t := NewT(tt) // t.Cmp( @@ -115,24 +117,36 @@ type T struct { // DefaultContextConfig.MaxErrors which is potentially dependent from // the TESTDEEP_MAX_ERRORS environment variable (else defaults to 10.) // See ContextConfig documentation for details. +// +// Of course "t" can already be a *T, in this special case if "config" +// is omitted, the Config of the new instance is a copy of the "t" +// Config. func NewT(t TestingFT, config ...ContextConfig) *T { - switch len(config) { - case 0: - return &T{ - TestingFT: t, - Config: DefaultContextConfig, - } + var newT T - case 1: - config[0].sanitize() - return &T{ - TestingFT: t, - Config: config[0], - } + if len(config) > 1 { + panic("usage: NewT(TestingFT[, ContextConfig]") + } - default: - panic("usage: NewT(*testing.T[, ContextConfig]") + // Already a *T, so steal its TestingFT and its Config if needed + if tdT, ok := t.(*T); ok { + newT.TestingFT = tdT.TestingFT + if len(config) == 0 { + newT.Config = tdT.Config + } else { + newT.Config = config[0] + } + } else { + newT.TestingFT = t + if len(config) == 0 { + newT.Config = DefaultContextConfig + } else { + newT.Config = config[0] + } } + newT.Config.sanitize() + + return &newT } // RootName changes the name of the got data. By default it is @@ -160,8 +174,13 @@ func NewT(t TestingFT, config ...ContextConfig) *T { // Which is more readable than the generic: // // DATA.Age: values differ +// +// If "" is passed the name is set to "DATA", the default value. func (t *T) RootName(rootName string) *T { new := *t + if rootName == "" { + rootName = contextDefaultRootName + } new.Config.RootName = rootName return &new } @@ -197,6 +216,20 @@ func (t *T) FailureIsFatal(enable ...bool) *T { return &new } +// UseEqual allows to use the Equal method on got (if it exists) or +// on any of its component to compare got and expected values. +// +// The signature should be: +// (A) Equal(B) bool +// with B assignable to A. +// +// See time.Time as an example of accepted Equal() method. +func (t *T) UseEqual(enable ...bool) *T { + new := *t + new.Config.UseEqual = len(enable) == 0 || enable[0] + return &new +} + // Cmp is mostly a shortcut for: // // Cmp(t.TestingFT, got, expected, args...) @@ -313,18 +346,23 @@ func (t *T) CmpNotPanic(fn func(), args ...interface{}) bool { return cmpNotPanic(newContextWithConfig(t.Config), t, fn, args...) } -// Run runs "f" as a subtest of t called "name". It runs "f" in a separate -// goroutine and blocks until "f" returns or calls t.Parallel to become -// a parallel test. Run reports whether "f" succeeded (or at least did -// not fail before calling t.Parallel). +// RunT runs "f" as a subtest of t called "name". It runs "f" in a +// separate goroutine and blocks until "f" returns or calls t.Parallel +// to become a parallel test. RunT reports whether "f" succeeded (or at +// least did not fail before calling t.Parallel). // -// Run may be called simultaneously from multiple goroutines, but all +// RunT may be called simultaneously from multiple goroutines, but all // such calls must return before the outer test function for t // returns. // -// Under the hood, Run delegates all this stuff to testing.Run. That +// Under the hood, RunT delegates all this stuff to testing.Run. That // is why this documentation is a copy/paste of testing.Run one. -func (t *T) Run(name string, f func(t *T)) bool { +// +// In versions up to v1.0.8, the name of this function was Run. As *T +// now implements TestingFT interface, the original +// (*testing.T).Run(string, func(t *testing.T)) is callable directly +// on *T. +func (t *T) RunT(name string, f func(t *T)) bool { t.Helper() - return t.TestingFT.Run(name, func(tt *testing.T) { f(NewT(tt)) }) + return t.TestingFT.Run(name, func(tt *testing.T) { f(NewT(tt, t.Config)) }) } diff --git a/t_struct_test.go b/t_struct_test.go index fc87c090..8b46efc6 100644 --- a/t_struct_test.go +++ b/t_struct_test.go @@ -8,30 +8,57 @@ package testdeep_test import ( "testing" + "time" "github.com/maxatome/go-testdeep" "github.com/maxatome/go-testdeep/internal/test" ) func TestT(tt *testing.T) { - t := testdeep.NewT(tt) - testdeep.Cmp(tt, t.Config, testdeep.DefaultContextConfig) + tt.Run("without config", func(tt *testing.T) { + t := testdeep.NewT(tt) + testdeep.Cmp(tt, t.Config, testdeep.DefaultContextConfig) - t = testdeep.NewT(tt, testdeep.ContextConfig{}) - testdeep.Cmp(tt, t.Config, testdeep.DefaultContextConfig) + tDup := testdeep.NewT(t) + testdeep.Cmp(tt, tDup.Config, testdeep.DefaultContextConfig) + }) - conf := testdeep.ContextConfig{ - RootName: "TEST", - MaxErrors: 33, - } - t = testdeep.NewT(tt, conf) - testdeep.Cmp(tt, t.Config, conf) - - t2 := t.RootName("T2") - testdeep.Cmp(tt, t.Config, conf) - testdeep.Cmp(tt, t2.Config, testdeep.ContextConfig{ - RootName: "T2", - MaxErrors: 33, + tt.Run("explicit default config", func(tt *testing.T) { + t := testdeep.NewT(tt, testdeep.ContextConfig{}) + testdeep.Cmp(tt, t.Config, testdeep.DefaultContextConfig) + + tDup := testdeep.NewT(t) + testdeep.Cmp(tt, tDup.Config, testdeep.DefaultContextConfig) + }) + + tt.Run("specific config", func(tt *testing.T) { + conf := testdeep.ContextConfig{ + RootName: "TEST", + MaxErrors: 33, + } + t := testdeep.NewT(tt, conf) + testdeep.Cmp(tt, t.Config, conf) + + tDup := testdeep.NewT(t) + testdeep.Cmp(tt, tDup.Config, conf) + + newConf := conf + newConf.MaxErrors = 34 + tDup = testdeep.NewT(t, newConf) + testdeep.Cmp(tt, tDup.Config, newConf) + + t2 := t.RootName("T2") + testdeep.Cmp(tt, t.Config, conf) + testdeep.Cmp(tt, t2.Config, testdeep.ContextConfig{ + RootName: "T2", + MaxErrors: 33, + }) + + t3 := t.RootName("") + testdeep.Cmp(tt, t3.Config, testdeep.ContextConfig{ + RootName: "DATA", + MaxErrors: 33, + }) }) // @@ -67,14 +94,14 @@ func TestTCmpDeeply(tt *testing.T) { testdeep.CmpTrue(tt, ttt.Failed()) } -func TestRun(tt *testing.T) { +func TestRunT(tt *testing.T) { t := testdeep.NewT(tt) runPassed := false - ok := t.Run("Test level1", + ok := t.RunT("Test level1", func(t *testdeep.T) { - ok := t.Run("Test level2", + ok := t.RunT("Test level2", func(t *testdeep.T) { runPassed = t.True(true) // test succeeds! }) @@ -122,3 +149,33 @@ func TestFailureIsFatal(tt *testing.T) { testdeep.CmpNotEmpty(tt, ttt.LastMessage) testdeep.CmpFalse(tt, ttt.IsFatal, "it must be not fatal") } + +func TestUseEqual(tt *testing.T) { + ttt := &test.TestingFT{} + + var time1, time2 time.Time + for { + time1 = time.Now() + time2 = time1.Truncate(0) + if !time1.Equal(time2) { + tt.Fatal("time.Equal() does not work as expected") + } + if time1 != time2 { // to avoid the bad luck case where time1.wall=0 + break + } + } + + // Using default config + t := testdeep.NewT(ttt) + test.IsFalse(tt, t.Cmp(time1, time2)) + + // UseEqual + t = testdeep.NewT(ttt).UseEqual() + test.IsTrue(tt, t.Cmp(time1, time2)) + + t = testdeep.NewT(ttt).UseEqual(true) + test.IsTrue(tt, t.Cmp(time1, time2)) + + t = testdeep.NewT(ttt).UseEqual(false) + test.IsFalse(tt, t.Cmp(time1, time2)) +} diff --git a/td_array.go b/td_array.go index f67485b9..f7595703 100644 --- a/td_array.go +++ b/td_array.go @@ -34,7 +34,7 @@ func newArray(kind reflect.Kind, model interface{}, expectedEntries ArrayEntries a := tdArray{ tdExpectedType: tdExpectedType{ - Base: NewBase(4), + base: newBase(4), }, } diff --git a/td_array_each.go b/td_array_each.go index e7cfbe4a..3c4291a1 100644 --- a/td_array_each.go +++ b/td_array_each.go @@ -16,7 +16,7 @@ import ( ) type tdArrayEach struct { - BaseOKNil + baseOKNil expected reflect.Value } @@ -28,7 +28,7 @@ var _ TestDeep = &tdArrayEach{} // succeed. func ArrayEach(expectedValue interface{}) TestDeep { return &tdArrayEach{ - BaseOKNil: NewBaseOKNil(3), + baseOKNil: newBaseOKNil(3), expected: reflect.ValueOf(expectedValue), } } diff --git a/td_between.go b/td_between.go index cd36f089..3238aec3 100644 --- a/td_between.go +++ b/td_between.go @@ -26,7 +26,7 @@ const ( ) type tdBetween struct { - Base + base expectedMin reflect.Value expectedMax reflect.Value @@ -104,7 +104,7 @@ func Between(from interface{}, to interface{}, bounds ...BoundsKind) TestDeep { } func (b *tdBetween) initBetween(usage string) TestDeep { - b.Base = NewBase(4) + b.base = newBase(4) if !b.expectedMax.IsValid() { b.expectedMax = b.expectedMin @@ -164,15 +164,15 @@ func (b *tdBetween) initBetween(usage string) TestDeep { func (b *tdBetween) nInt(tolerance reflect.Value) { if diff := tolerance.Int(); diff != 0 { - base := b.expectedMin.Int() + expectedBase := b.expectedMin.Int() - max := base + diff - if max < base { + max := expectedBase + diff + if max < expectedBase { max = math.MaxInt64 } - min := base - diff - if min > base { + min := expectedBase - diff + if min > expectedBase { min = math.MinInt64 } @@ -225,7 +225,7 @@ func (b *tdBetween) nFloat(tolerance reflect.Value) { // TypeBehind method returns the reflect.Type of "num". func N(num interface{}, tolerance ...interface{}) TestDeep { n := tdBetween{ - Base: NewBase(3), + base: newBase(3), expectedMin: reflect.ValueOf(num), minBound: boundIn, maxBound: boundIn, diff --git a/td_code.go b/td_code.go index c5c28d5c..0a747920 100644 --- a/td_code.go +++ b/td_code.go @@ -14,7 +14,7 @@ import ( ) type tdCode struct { - Base + base function reflect.Value argType reflect.Type } @@ -73,7 +73,7 @@ func Code(fn interface{}) TestDeep { } fnType := vfn.Type() - if fnType.NumIn() != 1 { + if fnType.IsVariadic() || fnType.NumIn() != 1 { panic("Code(FUNC): FUNC must take only one argument") } @@ -90,7 +90,7 @@ func Code(fn interface{}) TestDeep { // (*error*) (fnType.NumOut() == 1 && fnType.Out(0) == errorInterface) { return &tdCode{ - Base: NewBase(3), + base: newBase(3), function: vfn, argType: fnType.In(0), } diff --git a/td_code_test.go b/td_code_test.go index 3980f86b..c91438f5 100644 --- a/td_code_test.go +++ b/td_code_test.go @@ -103,6 +103,10 @@ func TestCode(t *testing.T) { testdeep.Code(func() bool { return true }) }, "FUNC must take only one argument") + test.CheckPanic(t, func() { + testdeep.Code(func(x ...int) bool { return true }) + }, "FUNC must take only one argument") + test.CheckPanic(t, func() { testdeep.Code(func(a int, b string) bool { return true }) }, "FUNC must take only one argument") diff --git a/td_empty.go b/td_empty.go index e7b29382..afc20b3b 100644 --- a/td_empty.go +++ b/td_empty.go @@ -16,7 +16,7 @@ import ( const emptyBadType types.RawString = "Array, Chan, Map, Slice, string or pointer(s) on them" type tdEmpty struct { - BaseOKNil + baseOKNil } var _ TestDeep = &tdEmpty{} @@ -29,7 +29,7 @@ var _ TestDeep = &tdEmpty{} // etc.) on an array, a channel, a map, a slice or a string. func Empty() TestDeep { return &tdEmpty{ - BaseOKNil: NewBaseOKNil(3), + baseOKNil: newBaseOKNil(3), } } @@ -96,7 +96,7 @@ func (e *tdEmpty) String() string { } type tdNotEmpty struct { - BaseOKNil + baseOKNil } var _ TestDeep = &tdNotEmpty{} @@ -109,7 +109,7 @@ var _ TestDeep = &tdNotEmpty{} // etc.) on an array, a channel, a map, a slice or a string. func NotEmpty() TestDeep { return &tdNotEmpty{ - BaseOKNil: NewBaseOKNil(3), + baseOKNil: newBaseOKNil(3), } } diff --git a/td_expected_type.go b/td_expected_type.go index 79c473fc..826401aa 100644 --- a/td_expected_type.go +++ b/td_expected_type.go @@ -14,7 +14,7 @@ import ( ) type tdExpectedType struct { - Base + base expectedType reflect.Type isPtr bool } diff --git a/td_ignore.go b/td_ignore.go index bd34d7b7..bd81def9 100644 --- a/td_ignore.go +++ b/td_ignore.go @@ -13,7 +13,7 @@ import ( ) type tdIgnore struct { - BaseOKNil + baseOKNil } var ignoreSingleton TestDeep = &tdIgnore{} @@ -21,7 +21,7 @@ var ignoreSingleton TestDeep = &tdIgnore{} // Ignore operator is always true, whatever data is. It is useful when // comparing a slice and wanting to ignore some indexes, for example. func Ignore() TestDeep { - // NewBase() useless + // newBase() useless return ignoreSingleton } diff --git a/td_isa.go b/td_isa.go index ab7679a1..c67c8525 100644 --- a/td_isa.go +++ b/td_isa.go @@ -45,7 +45,7 @@ func Isa(model interface{}) TestDeep { return &tdIsa{ tdExpectedType: tdExpectedType{ - Base: NewBase(3), + base: newBase(3), expectedType: modelType, }, checkImplement: modelType.Kind() == reflect.Ptr && diff --git a/td_list.go b/td_list.go index f7056736..c4a06f60 100644 --- a/td_list.go +++ b/td_list.go @@ -14,12 +14,12 @@ import ( ) type tdList struct { - BaseOKNil + baseOKNil items []reflect.Value } func newList(items ...interface{}) (ret tdList) { - ret.BaseOKNil = NewBaseOKNil(4) + ret.baseOKNil = newBaseOKNil(4) ret.items = make([]reflect.Value, len(items)) for idx, item := range items { diff --git a/td_map.go b/td_map.go index e1bd859c..10abeca9 100644 --- a/td_map.go +++ b/td_map.go @@ -48,7 +48,7 @@ func newMap(model interface{}, entries MapEntries, kind mapKind) *tdMap { m := tdMap{ tdExpectedType: tdExpectedType{ - Base: NewBase(4), + base: newBase(4), }, kind: kind, } diff --git a/td_map_each.go b/td_map_each.go index 025cc835..8276b590 100644 --- a/td_map_each.go +++ b/td_map_each.go @@ -17,7 +17,7 @@ import ( ) type tdMapEach struct { - BaseOKNil + baseOKNil expected reflect.Value } @@ -28,7 +28,7 @@ var _ TestDeep = &tdMapEach{} // to match to succeed. func MapEach(expectedValue interface{}) TestDeep { return &tdMapEach{ - BaseOKNil: NewBaseOKNil(3), + baseOKNil: newBaseOKNil(3), expected: reflect.ValueOf(expectedValue), } } diff --git a/td_nan.go b/td_nan.go index c83b58b5..e1fd6657 100644 --- a/td_nan.go +++ b/td_nan.go @@ -15,7 +15,7 @@ import ( ) type tdNaN struct { - Base + base } var _ TestDeep = &tdNaN{} @@ -23,7 +23,7 @@ var _ TestDeep = &tdNaN{} // NaN operator checks that data is a float and is not-a-number. func NaN() TestDeep { return &tdNaN{ - Base: NewBase(3), + base: newBase(3), } } @@ -53,7 +53,7 @@ func (n *tdNaN) String() string { } type tdNotNaN struct { - Base + base } var _ TestDeep = &tdNotNaN{} @@ -61,7 +61,7 @@ var _ TestDeep = &tdNotNaN{} // NotNaN operator checks that data is a float and is not not-a-number. func NotNaN() TestDeep { return &tdNotNaN{ - Base: NewBase(3), + base: newBase(3), } } diff --git a/td_nil.go b/td_nil.go index 16436f8f..d9c73435 100644 --- a/td_nil.go +++ b/td_nil.go @@ -13,7 +13,7 @@ import ( ) type tdNil struct { - BaseOKNil + baseOKNil } var _ TestDeep = &tdNil{} @@ -22,7 +22,7 @@ var _ TestDeep = &tdNil{} // but containing a nil pointer.) func Nil() TestDeep { return &tdNil{ - BaseOKNil: NewBaseOKNil(3), + baseOKNil: newBaseOKNil(3), } } @@ -54,7 +54,7 @@ func (n *tdNil) String() string { } type tdNotNil struct { - BaseOKNil + baseOKNil } var _ TestDeep = &tdNotNil{} @@ -63,7 +63,7 @@ var _ TestDeep = &tdNotNil{} // interface, containing a non-nil pointer.) func NotNil() TestDeep { return &tdNotNil{ - BaseOKNil: NewBaseOKNil(3), + baseOKNil: newBaseOKNil(3), } } diff --git a/td_re.go b/td_re.go index 39550ac6..a87c5640 100644 --- a/td_re.go +++ b/td_re.go @@ -17,7 +17,7 @@ import ( ) type tdRe struct { - Base + base re *regexp.Regexp captures reflect.Value numMatches int @@ -27,7 +27,7 @@ var _ TestDeep = &tdRe{} func newRe(regIf interface{}, capture ...interface{}) (r *tdRe) { r = &tdRe{ - Base: NewBase(4), + base: newBase(4), } switch len(capture) { diff --git a/td_set_base.go b/td_set_base.go index be2c5285..0e5c2a56 100644 --- a/td_set_base.go +++ b/td_set_base.go @@ -25,7 +25,7 @@ const ( ) type tdSetBase struct { - BaseOKNil + baseOKNil kind setKind ignoreDups bool @@ -34,7 +34,7 @@ type tdSetBase struct { func newSetBase(kind setKind, ignoreDups bool) tdSetBase { return tdSetBase{ - BaseOKNil: NewBaseOKNil(4), + baseOKNil: newBaseOKNil(4), kind: kind, ignoreDups: ignoreDups, } diff --git a/td_shallow.go b/td_shallow.go index 5e79bc1d..6dfe22c0 100644 --- a/td_shallow.go +++ b/td_shallow.go @@ -16,7 +16,7 @@ import ( ) type tdShallow struct { - Base + base expectedKind reflect.Kind expectedPointer uintptr expectedStr string // in reflect.String case, to avoid contents GC @@ -61,7 +61,7 @@ func Shallow(expectedPtr interface{}) TestDeep { vptr := reflect.ValueOf(expectedPtr) shallow := tdShallow{ - Base: NewBase(3), + base: newBase(3), expectedKind: vptr.Kind(), } diff --git a/td_smuggle.go b/td_smuggle.go index 5b7107a2..aa89a1dc 100644 --- a/td_smuggle.go +++ b/td_smuggle.go @@ -297,7 +297,7 @@ func Smuggle(fn interface{}, expectedValue interface{}) TestDeep { } fnType := vfn.Type() - if fnType.NumIn() != 1 { + if fnType.IsVariadic() || fnType.NumIn() != 1 { panic(usage + ": FUNC must take only one argument") } diff --git a/td_smuggle_test.go b/td_smuggle_test.go index a8db1f69..d75a94d4 100644 --- a/td_smuggle_test.go +++ b/td_smuggle_test.go @@ -298,6 +298,10 @@ func TestSmuggle(t *testing.T) { testdeep.Smuggle(func() int { return 0 }, 12) }, "FUNC must take only one argument") + test.CheckPanic(t, func() { + testdeep.Smuggle(func(x ...int) int { return 0 }, 12) + }, "FUNC must take only one argument") + test.CheckPanic(t, func() { testdeep.Smuggle(func(a int, b string) int { return 0 }, 12) }, "FUNC must take only one argument") diff --git a/td_smuggler_base.go b/td_smuggler_base.go index 65164c28..0edf19bc 100644 --- a/td_smuggler_base.go +++ b/td_smuggler_base.go @@ -12,7 +12,7 @@ import ( // tdSmugglerBase is the base class of all smuggler TestDeep operators. type tdSmugglerBase struct { - Base + base expectedValue reflect.Value isTestDeeper bool } @@ -22,7 +22,7 @@ func newSmugglerBase(val interface{}, depth ...int) (ret tdSmugglerBase) { if len(depth) > 0 { callDepth = depth[0] } - ret.Base = NewBase(callDepth) + ret.base = newBase(callDepth) // Initializes only if TestDeep operator. Other cases are specific. if _, ok := val.(TestDeep); ok { diff --git a/td_string.go b/td_string.go index a4c4ea5d..065de1a0 100644 --- a/td_string.go +++ b/td_string.go @@ -17,13 +17,13 @@ import ( ) type tdStringBase struct { - Base + base expected string } func newStringBase(expected string) tdStringBase { return tdStringBase{ - Base: NewBase(4), + base: newBase(4), expected: expected, } } diff --git a/td_struct.go b/td_struct.go index a305b253..1d816f1a 100644 --- a/td_struct.go +++ b/td_struct.go @@ -48,7 +48,7 @@ func newStruct(model interface{}) (*tdStruct, reflect.Value) { st := tdStruct{ tdExpectedType: tdExpectedType{ - Base: NewBase(4), + base: newBase(4), }, } diff --git a/td_trunc_time.go b/td_trunc_time.go index d75a605c..98b40ddb 100644 --- a/td_trunc_time.go +++ b/td_trunc_time.go @@ -41,7 +41,7 @@ func TruncTime(expectedTime interface{}, trunc ...time.Duration) TestDeep { if len(trunc) <= 1 { t := tdTruncTime{ tdExpectedType: tdExpectedType{ - Base: NewBase(3), + base: newBase(3), }, } diff --git a/td_zero.go b/td_zero.go index 1de8074e..39f995b7 100644 --- a/td_zero.go +++ b/td_zero.go @@ -13,7 +13,7 @@ import ( ) type tdZero struct { - BaseOKNil + baseOKNil } var _ TestDeep = &tdZero{} @@ -32,7 +32,7 @@ var _ TestDeep = &tdZero{} // Cmp(t, &AnyStruct{}, Ptr(Zero())) // is true func Zero() TestDeep { return &tdZero{ - BaseOKNil: NewBaseOKNil(3), + baseOKNil: newBaseOKNil(3), } } @@ -49,7 +49,7 @@ func (z *tdZero) String() string { } type tdNotZero struct { - BaseOKNil + baseOKNil } var _ TestDeep = &tdNotZero{} @@ -68,7 +68,7 @@ var _ TestDeep = &tdNotZero{} // Cmp(t, &AnyStruct{}, Ptr(NotZero())) // is false func NotZero() TestDeep { return &tdNotZero{ - BaseOKNil: NewBaseOKNil(3), + baseOKNil: newBaseOKNil(3), } } diff --git a/types.go b/types.go index 53557a10..19390b80 100644 --- a/types.go +++ b/types.go @@ -24,6 +24,7 @@ var ( errorInterface = reflect.TypeOf((*error)(nil)).Elem() timeType = reflect.TypeOf(time.Time{}) intType = reflect.TypeOf(int(0)) + boolType = reflect.TypeOf(false) smuggledGotType = reflect.TypeOf(SmuggledGot{}) smuggledGotPtrType = reflect.TypeOf((*SmuggledGot)(nil)) ) @@ -67,9 +68,9 @@ type TestDeep interface { TypeBehind() reflect.Type } -// Base is a base type providing some methods needed by the TestDeep +// base is a base type providing some methods needed by the TestDeep // interface. -type Base struct { +type base struct { types.TestDeepStamp location location.Location } @@ -87,7 +88,7 @@ func pkgFunc(full string) (string, string) { return pkg, fn } -func (t *Base) setLocation(callDepth int) { +func (t *base) setLocation(callDepth int) { var ok bool t.location, ok = location.New(callDepth) if !ok { @@ -115,45 +116,45 @@ func (t *Base) setLocation(callDepth int) { // GetLocation returns a copy of the location.Location where the TestDeep // operator has been created. -func (t *Base) GetLocation() location.Location { +func (t *base) GetLocation() location.Location { return t.location } // HandleInvalid tells testdeep internals that this operator does not // handle nil values directly. -func (t Base) HandleInvalid() bool { +func (t base) HandleInvalid() bool { return false } // TypeBehind returns the type handled by the operator. Only few operators // knows the type they are handling. If they do not know, nil is // returned. -func (t Base) TypeBehind() reflect.Type { +func (t base) TypeBehind() reflect.Type { return nil } -// NewBase returns a new Base struct with location.Location set to the +// newBase returns a new base struct with location.Location set to the // "callDepth" depth. -func NewBase(callDepth int) (b Base) { +func newBase(callDepth int) (b base) { b.setLocation(callDepth) return } -// BaseOKNil is a base type providing some methods needed by the TestDeep +// baseOKNil is a base type providing some methods needed by the TestDeep // interface, for operators handling nil values. -type BaseOKNil struct { - Base +type baseOKNil struct { + base } // HandleInvalid tells testdeep internals that this operator handles // nil values directly. -func (t BaseOKNil) HandleInvalid() bool { +func (t baseOKNil) HandleInvalid() bool { return true } -// NewBaseOKNil returns a new BaseOKNil struct with location.Location set to +// newBaseOKNil returns a new baseOKNil struct with location.Location set to // the "callDepth" depth. -func NewBaseOKNil(callDepth int) (b BaseOKNil) { +func newBaseOKNil(callDepth int) (b baseOKNil) { b.setLocation(callDepth) return }