Merge branch 'master' into patch-1

pull/1248/head
Olivier Mengué 2023-10-16 19:23:14 +02:00 committed by GitHub
commit f19cdfc9fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2306 additions and 1213 deletions

View File

@ -6,14 +6,32 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
go_version: ["1.18.1", "1.17.6", "1.16.5"] go_version:
- "1.19"
- "1.20"
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v3.2.0 uses: actions/setup-go@v4.1.0
with: with:
go-version: ${{ matrix.go_version }} go-version: ${{ matrix.go_version }}
- run: ./.ci.gogenerate.sh - run: ./.ci.gogenerate.sh
- run: ./.ci.gofmt.sh - run: ./.ci.gofmt.sh
- run: ./.ci.govet.sh - run: ./.ci.govet.sh
- run: go test -v -race ./... - run: go test -v -race ./...
test:
runs-on: ubuntu-latest
strategy:
matrix:
go_version:
- "1.17"
- "1.18"
- "1.19"
- "1.20"
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v4.1.0
with:
go-version: ${{ matrix.go_version }}
- run: go test -v -race ./...

19
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,19 @@
name: Create release from new tag
# this flow will be run only when new tags are pushed that match our pattern
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Create GitHub release from tag
uses: softprops/action-gh-release@v1

View File

@ -5,5 +5,4 @@ pull requests.
* @glesica * @glesica
* @boyan-soubachov * @boyan-soubachov
* @mvdkleijn

View File

@ -16,13 +16,13 @@ Features include:
Get started: Get started:
* Install testify with [one line of code](#installation), or [update it with another](#staying-up-to-date) * Install testify with [one line of code](#installation), or [update it with another](#staying-up-to-date)
* For an introduction to writing test code in Go, see http://golang.org/doc/code.html#Testing * For an introduction to writing test code in Go, see https://go.dev/doc/code#Testing
* Check out the API Documentation http://godoc.org/github.com/stretchr/testify * Check out the API Documentation https://pkg.go.dev/github.com/stretchr/testify
* A little about [Test-Driven Development (TDD)](http://en.wikipedia.org/wiki/Test-driven_development) * A little about [Test-Driven Development (TDD)](https://en.wikipedia.org/wiki/Test-driven_development)
[`assert`](http://godoc.org/github.com/stretchr/testify/assert "API documentation") package [`assert`](https://pkg.go.dev/github.com/stretchr/testify/assert "API documentation") package
------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------
The `assert` package provides some helpful methods that allow you to write better test code in Go. The `assert` package provides some helpful methods that allow you to write better test code in Go.
@ -99,14 +99,16 @@ func TestSomething(t *testing.T) {
} }
``` ```
[`require`](http://godoc.org/github.com/stretchr/testify/require "API documentation") package [`require`](https://pkg.go.dev/github.com/stretchr/testify/require "API documentation") package
--------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------
The `require` package provides same global functions as the `assert` package, but instead of returning a boolean result they terminate current test. The `require` package provides same global functions as the `assert` package, but instead of returning a boolean result they terminate current test.
These functions must be called from the goroutine running the test or benchmark function, not from other goroutines created during the test.
Otherwise race conditions may occur.
See [t.FailNow](http://golang.org/pkg/testing/#T.FailNow) for details. See [t.FailNow](https://pkg.go.dev/testing#T.FailNow) for details.
[`mock`](http://godoc.org/github.com/stretchr/testify/mock "API documentation") package [`mock`](https://pkg.go.dev/github.com/stretchr/testify/mock "API documentation") package
---------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------
The `mock` package provides a mechanism for easily writing mock objects that can be used in place of real objects when writing test code. The `mock` package provides a mechanism for easily writing mock objects that can be used in place of real objects when writing test code.
@ -216,14 +218,14 @@ func TestSomethingElse2(t *testing.T) {
} }
``` ```
For more information on how to write mock code, check out the [API documentation for the `mock` package](http://godoc.org/github.com/stretchr/testify/mock). For more information on how to write mock code, check out the [API documentation for the `mock` package](https://pkg.go.dev/github.com/stretchr/testify/mock).
You can use the [mockery tool](http://github.com/vektra/mockery) to autogenerate the mock code against an interface as well, making using mocks much quicker. You can use the [mockery tool](https://vektra.github.io/mockery/latest/) to autogenerate the mock code against an interface as well, making using mocks much quicker.
[`suite`](http://godoc.org/github.com/stretchr/testify/suite "API documentation") package [`suite`](https://pkg.go.dev/github.com/stretchr/testify/suite "API documentation") package
----------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------
The `suite` package provides functionality that you might be used to from more common object oriented languages. With it, you can build a testing suite as a struct, build setup/teardown methods and testing methods on your struct, and run them with 'go test' as per normal. The `suite` package provides functionality that you might be used to from more common object-oriented languages. With it, you can build a testing suite as a struct, build setup/teardown methods and testing methods on your struct, and run them with 'go test' as per normal.
An example suite is shown below: An example suite is shown below:
@ -264,7 +266,7 @@ func TestExampleTestSuite(t *testing.T) {
For a more complete example, using all of the functionality provided by the suite package, look at our [example testing suite](https://github.com/stretchr/testify/blob/master/suite/suite_test.go) For a more complete example, using all of the functionality provided by the suite package, look at our [example testing suite](https://github.com/stretchr/testify/blob/master/suite/suite_test.go)
For more information on writing suites, check out the [API documentation for the `suite` package](http://godoc.org/github.com/stretchr/testify/suite). For more information on writing suites, check out the [API documentation for the `suite` package](https://pkg.go.dev/github.com/stretchr/testify/suite).
`Suite` object has assertion methods: `Suite` object has assertion methods:
@ -347,7 +349,7 @@ To update Testify to the latest version, use `go get -u github.com/stretchr/test
Supported go versions Supported go versions
================== ==================
We currently support the most recent major Go versions from 1.13 onward. We currently support the most recent major Go versions from 1.19 onward.
------ ------
@ -358,7 +360,7 @@ Please feel free to submit issues, fork the repository and send pull requests!
When submitting an issue, we ask that you please include a complete test function that demonstrates the issue. Extra credit for those using Testify to write the test code that demonstrates it. When submitting an issue, we ask that you please include a complete test function that demonstrates the issue. Extra credit for those using Testify to write the test code that demonstrates it.
Code generation is used. Look for `CODE GENERATED AUTOMATICALLY` at the top of some files. Run `go generate ./...` to update generated files. Code generation is used. [Look for `Code generated with`](https://github.com/search?q=repo%3Astretchr%2Ftestify%20%22Code%20generated%20with%22&type=code) at the top of some files. Run `go generate ./...` to update generated files.
We also chat on the [Gophers Slack](https://gophers.slack.com) group in the `#testify` and `#testify-dev` channels. We also chat on the [Gophers Slack](https://gophers.slack.com) group in the `#testify` and `#testify-dev` channels.

View File

@ -297,10 +297,8 @@ func (f *testFunc) CommentWithoutT(receiver string) string {
return strings.Replace(f.Comment(), search, replace, -1) return strings.Replace(f.Comment(), search, replace, -1)
} }
var headerTemplate = `/* // Standard header https://go.dev/s/generatedcode.
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen var headerTemplate = `// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
* THIS FILE MUST NOT BE EDITED BY HAND
*/
package {{.Name}} package {{.Name}}

View File

@ -308,11 +308,11 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
case reflect.Struct: case reflect.Struct:
{ {
// All structs enter here. We're not interested in most types. // All structs enter here. We're not interested in most types.
if !canConvert(obj1Value, timeType) { if !obj1Value.CanConvert(timeType) {
break break
} }
// time.Time can compared! // time.Time can be compared!
timeObj1, ok := obj1.(time.Time) timeObj1, ok := obj1.(time.Time)
if !ok { if !ok {
timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time) timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)
@ -328,7 +328,7 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
case reflect.Slice: case reflect.Slice:
{ {
// We only care about the []byte type. // We only care about the []byte type.
if !canConvert(obj1Value, bytesType) { if !obj1Value.CanConvert(bytesType) {
break break
} }

View File

@ -1,16 +0,0 @@
//go:build go1.17
// +build go1.17
// TODO: once support for Go 1.16 is dropped, this file can be
// merged/removed with assertion_compare_go1.17_test.go and
// assertion_compare_legacy.go
package assert
import "reflect"
// Wrapper around reflect.Value.CanConvert, for compatibility
// reasons.
func canConvert(value reflect.Value, to reflect.Type) bool {
return value.CanConvert(to)
}

View File

@ -1,182 +0,0 @@
//go:build go1.17
// +build go1.17
// TODO: once support for Go 1.16 is dropped, this file can be
// merged/removed with assertion_compare_can_convert.go and
// assertion_compare_legacy.go
package assert
import (
"bytes"
"reflect"
"testing"
"time"
)
func TestCompare17(t *testing.T) {
type customTime time.Time
type customBytes []byte
for _, currCase := range []struct {
less interface{}
greater interface{}
cType string
}{
{less: time.Now(), greater: time.Now().Add(time.Hour), cType: "time.Time"},
{less: customTime(time.Now()), greater: customTime(time.Now().Add(time.Hour)), cType: "time.Time"},
{less: []byte{1, 1}, greater: []byte{1, 2}, cType: "[]byte"},
{less: customBytes([]byte{1, 1}), greater: customBytes([]byte{1, 2}), cType: "[]byte"},
} {
resLess, isComparable := compare(currCase.less, currCase.greater, reflect.ValueOf(currCase.less).Kind())
if !isComparable {
t.Error("object should be comparable for type " + currCase.cType)
}
if resLess != compareLess {
t.Errorf("object less (%v) should be less than greater (%v) for type "+currCase.cType,
currCase.less, currCase.greater)
}
resGreater, isComparable := compare(currCase.greater, currCase.less, reflect.ValueOf(currCase.less).Kind())
if !isComparable {
t.Error("object are comparable for type " + currCase.cType)
}
if resGreater != compareGreater {
t.Errorf("object greater should be greater than less for type " + currCase.cType)
}
resEqual, isComparable := compare(currCase.less, currCase.less, reflect.ValueOf(currCase.less).Kind())
if !isComparable {
t.Error("object are comparable for type " + currCase.cType)
}
if resEqual != 0 {
t.Errorf("objects should be equal for type " + currCase.cType)
}
}
}
func TestGreater17(t *testing.T) {
mockT := new(testing.T)
if !Greater(mockT, 2, 1) {
t.Error("Greater should return true")
}
if Greater(mockT, 1, 1) {
t.Error("Greater should return false")
}
if Greater(mockT, 1, 2) {
t.Error("Greater should return false")
}
// Check error report
for _, currCase := range []struct {
less interface{}
greater interface{}
msg string
}{
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 1]" is not greater than "[1 2]"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 00:00:00 +0000 UTC" is not greater than "0001-01-01 01:00:00 +0000 UTC"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, Greater(out, currCase.less, currCase.greater))
Contains(t, out.buf.String(), currCase.msg)
Contains(t, out.helpers, "github.com/stretchr/testify/assert.Greater")
}
}
func TestGreaterOrEqual17(t *testing.T) {
mockT := new(testing.T)
if !GreaterOrEqual(mockT, 2, 1) {
t.Error("GreaterOrEqual should return true")
}
if !GreaterOrEqual(mockT, 1, 1) {
t.Error("GreaterOrEqual should return true")
}
if GreaterOrEqual(mockT, 1, 2) {
t.Error("GreaterOrEqual should return false")
}
// Check error report
for _, currCase := range []struct {
less interface{}
greater interface{}
msg string
}{
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 1]" is not greater than or equal to "[1 2]"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 00:00:00 +0000 UTC" is not greater than or equal to "0001-01-01 01:00:00 +0000 UTC"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, GreaterOrEqual(out, currCase.less, currCase.greater))
Contains(t, out.buf.String(), currCase.msg)
Contains(t, out.helpers, "github.com/stretchr/testify/assert.GreaterOrEqual")
}
}
func TestLess17(t *testing.T) {
mockT := new(testing.T)
if !Less(mockT, 1, 2) {
t.Error("Less should return true")
}
if Less(mockT, 1, 1) {
t.Error("Less should return false")
}
if Less(mockT, 2, 1) {
t.Error("Less should return false")
}
// Check error report
for _, currCase := range []struct {
less interface{}
greater interface{}
msg string
}{
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 2]" is not less than "[1 1]"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 01:00:00 +0000 UTC" is not less than "0001-01-01 00:00:00 +0000 UTC"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, Less(out, currCase.greater, currCase.less))
Contains(t, out.buf.String(), currCase.msg)
Contains(t, out.helpers, "github.com/stretchr/testify/assert.Less")
}
}
func TestLessOrEqual17(t *testing.T) {
mockT := new(testing.T)
if !LessOrEqual(mockT, 1, 2) {
t.Error("LessOrEqual should return true")
}
if !LessOrEqual(mockT, 1, 1) {
t.Error("LessOrEqual should return true")
}
if LessOrEqual(mockT, 2, 1) {
t.Error("LessOrEqual should return false")
}
// Check error report
for _, currCase := range []struct {
less interface{}
greater interface{}
msg string
}{
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 2]" is not less than or equal to "[1 1]"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 01:00:00 +0000 UTC" is not less than or equal to "0001-01-01 00:00:00 +0000 UTC"`},
} {
out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, LessOrEqual(out, currCase.greater, currCase.less))
Contains(t, out.buf.String(), currCase.msg)
Contains(t, out.helpers, "github.com/stretchr/testify/assert.LessOrEqual")
}
}

View File

@ -1,16 +0,0 @@
//go:build !go1.17
// +build !go1.17
// TODO: once support for Go 1.16 is dropped, this file can be
// merged/removed with assertion_compare_go1.17_test.go and
// assertion_compare_can_convert.go
package assert
import "reflect"
// Older versions of Go does not have the reflect.Value.CanConvert
// method.
func canConvert(value reflect.Value, to reflect.Type) bool {
return false
}

View File

@ -6,6 +6,7 @@ import (
"reflect" "reflect"
"runtime" "runtime"
"testing" "testing"
"time"
) )
func TestCompare(t *testing.T) { func TestCompare(t *testing.T) {
@ -22,6 +23,8 @@ func TestCompare(t *testing.T) {
type customFloat32 float32 type customFloat32 float32
type customFloat64 float64 type customFloat64 float64
type customString string type customString string
type customTime time.Time
type customBytes []byte
for _, currCase := range []struct { for _, currCase := range []struct {
less interface{} less interface{}
greater interface{} greater interface{}
@ -52,6 +55,10 @@ func TestCompare(t *testing.T) {
{less: customFloat32(1.23), greater: customFloat32(2.23), cType: "float32"}, {less: customFloat32(1.23), greater: customFloat32(2.23), cType: "float32"},
{less: float64(1.23), greater: float64(2.34), cType: "float64"}, {less: float64(1.23), greater: float64(2.34), cType: "float64"},
{less: customFloat64(1.23), greater: customFloat64(2.34), cType: "float64"}, {less: customFloat64(1.23), greater: customFloat64(2.34), cType: "float64"},
{less: time.Now(), greater: time.Now().Add(time.Hour), cType: "time.Time"},
{less: customTime(time.Now()), greater: customTime(time.Now().Add(time.Hour)), cType: "time.Time"},
{less: []byte{1, 1}, greater: []byte{1, 2}, cType: "[]byte"},
{less: customBytes([]byte{1, 1}), greater: customBytes([]byte{1, 2}), cType: "[]byte"},
} { } {
resLess, isComparable := compare(currCase.less, currCase.greater, reflect.ValueOf(currCase.less).Kind()) resLess, isComparable := compare(currCase.less, currCase.greater, reflect.ValueOf(currCase.less).Kind())
if !isComparable { if !isComparable {
@ -148,6 +155,8 @@ func TestGreater(t *testing.T) {
{less: uint64(1), greater: uint64(2), msg: `"1" is not greater than "2"`}, {less: uint64(1), greater: uint64(2), msg: `"1" is not greater than "2"`},
{less: float32(1.23), greater: float32(2.34), msg: `"1.23" is not greater than "2.34"`}, {less: float32(1.23), greater: float32(2.34), msg: `"1.23" is not greater than "2.34"`},
{less: float64(1.23), greater: float64(2.34), msg: `"1.23" is not greater than "2.34"`}, {less: float64(1.23), greater: float64(2.34), msg: `"1.23" is not greater than "2.34"`},
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 1]" is not greater than "[1 2]"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 00:00:00 +0000 UTC" is not greater than "0001-01-01 01:00:00 +0000 UTC"`},
} { } {
out := &outputT{buf: bytes.NewBuffer(nil)} out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, Greater(out, currCase.less, currCase.greater)) False(t, Greater(out, currCase.less, currCase.greater))
@ -189,6 +198,8 @@ func TestGreaterOrEqual(t *testing.T) {
{less: uint64(1), greater: uint64(2), msg: `"1" is not greater than or equal to "2"`}, {less: uint64(1), greater: uint64(2), msg: `"1" is not greater than or equal to "2"`},
{less: float32(1.23), greater: float32(2.34), msg: `"1.23" is not greater than or equal to "2.34"`}, {less: float32(1.23), greater: float32(2.34), msg: `"1.23" is not greater than or equal to "2.34"`},
{less: float64(1.23), greater: float64(2.34), msg: `"1.23" is not greater than or equal to "2.34"`}, {less: float64(1.23), greater: float64(2.34), msg: `"1.23" is not greater than or equal to "2.34"`},
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 1]" is not greater than or equal to "[1 2]"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 00:00:00 +0000 UTC" is not greater than or equal to "0001-01-01 01:00:00 +0000 UTC"`},
} { } {
out := &outputT{buf: bytes.NewBuffer(nil)} out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, GreaterOrEqual(out, currCase.less, currCase.greater)) False(t, GreaterOrEqual(out, currCase.less, currCase.greater))
@ -230,6 +241,8 @@ func TestLess(t *testing.T) {
{less: uint64(1), greater: uint64(2), msg: `"2" is not less than "1"`}, {less: uint64(1), greater: uint64(2), msg: `"2" is not less than "1"`},
{less: float32(1.23), greater: float32(2.34), msg: `"2.34" is not less than "1.23"`}, {less: float32(1.23), greater: float32(2.34), msg: `"2.34" is not less than "1.23"`},
{less: float64(1.23), greater: float64(2.34), msg: `"2.34" is not less than "1.23"`}, {less: float64(1.23), greater: float64(2.34), msg: `"2.34" is not less than "1.23"`},
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 2]" is not less than "[1 1]"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 01:00:00 +0000 UTC" is not less than "0001-01-01 00:00:00 +0000 UTC"`},
} { } {
out := &outputT{buf: bytes.NewBuffer(nil)} out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, Less(out, currCase.greater, currCase.less)) False(t, Less(out, currCase.greater, currCase.less))
@ -271,6 +284,8 @@ func TestLessOrEqual(t *testing.T) {
{less: uint64(1), greater: uint64(2), msg: `"2" is not less than or equal to "1"`}, {less: uint64(1), greater: uint64(2), msg: `"2" is not less than or equal to "1"`},
{less: float32(1.23), greater: float32(2.34), msg: `"2.34" is not less than or equal to "1.23"`}, {less: float32(1.23), greater: float32(2.34), msg: `"2.34" is not less than or equal to "1.23"`},
{less: float64(1.23), greater: float64(2.34), msg: `"2.34" is not less than or equal to "1.23"`}, {less: float64(1.23), greater: float64(2.34), msg: `"2.34" is not less than or equal to "1.23"`},
{less: []byte{1, 1}, greater: []byte{1, 2}, msg: `"[1 2]" is not less than or equal to "[1 1]"`},
{less: time.Time{}, greater: time.Time{}.Add(time.Hour), msg: `"0001-01-01 01:00:00 +0000 UTC" is not less than or equal to "0001-01-01 00:00:00 +0000 UTC"`},
} { } {
out := &outputT{buf: bytes.NewBuffer(nil)} out := &outputT{buf: bytes.NewBuffer(nil)}
False(t, LessOrEqual(out, currCase.greater, currCase.less)) False(t, LessOrEqual(out, currCase.greater, currCase.less))

View File

@ -1,7 +1,4 @@
/* // Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
package assert package assert
@ -90,7 +87,24 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args
return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...) return EqualError(t, theError, errString, append([]interface{}{msg}, args...)...)
} }
// EqualValuesf asserts that two objects are equal or convertable to the same types // EqualExportedValuesf asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EqualExportedValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// EqualValuesf asserts that two objects are equal or convertible to the same types
// and equal. // and equal.
// //
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") // assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
@ -155,6 +169,31 @@ func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick
return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...) return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
} }
// EventuallyWithTf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func EventuallyWithTf(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return EventuallyWithT(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...)
}
// Exactlyf asserts that two objects are equal in value and type. // Exactlyf asserts that two objects are equal in value and type.
// //
// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") // assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted")

View File

@ -1,7 +1,4 @@
/* // Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
package assert package assert
@ -155,7 +152,41 @@ func (a *Assertions) EqualErrorf(theError error, errString string, msg string, a
return EqualErrorf(a.t, theError, errString, msg, args...) return EqualErrorf(a.t, theError, errString, msg, args...)
} }
// EqualValues asserts that two objects are equal or convertable to the same types // EqualExportedValues asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// a.EqualExportedValues(S{1, 2}, S{1, 3}) => true
// a.EqualExportedValues(S{1, 2}, S{2, 3}) => false
func (a *Assertions) EqualExportedValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EqualExportedValues(a.t, expected, actual, msgAndArgs...)
}
// EqualExportedValuesf asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// a.EqualExportedValuesf(S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
// a.EqualExportedValuesf(S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EqualExportedValuesf(a.t, expected, actual, msg, args...)
}
// EqualValues asserts that two objects are equal or convertible to the same types
// and equal. // and equal.
// //
// a.EqualValues(uint32(123), int32(123)) // a.EqualValues(uint32(123), int32(123))
@ -166,7 +197,7 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn
return EqualValues(a.t, expected, actual, msgAndArgs...) return EqualValues(a.t, expected, actual, msgAndArgs...)
} }
// EqualValuesf asserts that two objects are equal or convertable to the same types // EqualValuesf asserts that two objects are equal or convertible to the same types
// and equal. // and equal.
// //
// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") // a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted")
@ -288,6 +319,56 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti
return Eventually(a.t, condition, waitFor, tick, msgAndArgs...) return Eventually(a.t, condition, waitFor, tick, msgAndArgs...)
} }
// EventuallyWithT asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// a.EventuallyWithT(func(c *assert.CollectT) {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func (a *Assertions) EventuallyWithT(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EventuallyWithT(a.t, condition, waitFor, tick, msgAndArgs...)
}
// EventuallyWithTf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func (a *Assertions) EventuallyWithTf(condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return EventuallyWithTf(a.t, condition, waitFor, tick, msg, args...)
}
// Eventuallyf asserts that given condition will be met in waitFor time, // Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. // periodically checking target function each tick.
// //

View File

@ -8,7 +8,6 @@ import (
"fmt" "fmt"
"math" "math"
"os" "os"
"path/filepath"
"reflect" "reflect"
"regexp" "regexp"
"runtime" "runtime"
@ -20,7 +19,7 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/pmezard/go-difflib/difflib" "github.com/pmezard/go-difflib/difflib"
yaml "gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl" //go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl"
@ -60,12 +59,8 @@ func ObjectsAreEqual(expected, actual interface{}) bool {
if expected == nil || actual == nil { if expected == nil || actual == nil {
return expected == actual return expected == actual
} }
switch exp := expected.(type) {
exp, ok := expected.([]byte) case []byte:
if !ok {
return reflect.DeepEqual(expected, actual)
}
act, ok := actual.([]byte) act, ok := actual.([]byte)
if !ok { if !ok {
return false return false
@ -74,6 +69,91 @@ func ObjectsAreEqual(expected, actual interface{}) bool {
return exp == nil && act == nil return exp == nil && act == nil
} }
return bytes.Equal(exp, act) return bytes.Equal(exp, act)
case time.Time:
act, ok := actual.(time.Time)
if !ok {
return false
}
return exp.Equal(act)
default:
return reflect.DeepEqual(expected, actual)
}
}
// copyExportedFields iterates downward through nested data structures and creates a copy
// that only contains the exported struct fields.
func copyExportedFields(expected interface{}) interface{} {
if isNil(expected) {
return expected
}
expectedType := reflect.TypeOf(expected)
expectedKind := expectedType.Kind()
expectedValue := reflect.ValueOf(expected)
switch expectedKind {
case reflect.Struct:
result := reflect.New(expectedType).Elem()
for i := 0; i < expectedType.NumField(); i++ {
field := expectedType.Field(i)
isExported := field.IsExported()
if isExported {
fieldValue := expectedValue.Field(i)
if isNil(fieldValue) || isNil(fieldValue.Interface()) {
continue
}
newValue := copyExportedFields(fieldValue.Interface())
result.Field(i).Set(reflect.ValueOf(newValue))
}
}
return result.Interface()
case reflect.Ptr:
result := reflect.New(expectedType.Elem())
unexportedRemoved := copyExportedFields(expectedValue.Elem().Interface())
result.Elem().Set(reflect.ValueOf(unexportedRemoved))
return result.Interface()
case reflect.Array, reflect.Slice:
var result reflect.Value
if expectedKind == reflect.Array {
result = reflect.New(reflect.ArrayOf(expectedValue.Len(), expectedType.Elem())).Elem()
} else {
result = reflect.MakeSlice(expectedType, expectedValue.Len(), expectedValue.Len())
}
for i := 0; i < expectedValue.Len(); i++ {
index := expectedValue.Index(i)
if isNil(index) {
continue
}
unexportedRemoved := copyExportedFields(index.Interface())
result.Index(i).Set(reflect.ValueOf(unexportedRemoved))
}
return result.Interface()
case reflect.Map:
result := reflect.MakeMap(expectedType)
for _, k := range expectedValue.MapKeys() {
index := expectedValue.MapIndex(k)
unexportedRemoved := copyExportedFields(index.Interface())
result.SetMapIndex(k, reflect.ValueOf(unexportedRemoved))
}
return result.Interface()
default:
return expected
}
}
// ObjectsExportedFieldsAreEqual determines if the exported (public) fields of two objects are
// considered equal. This comparison of only exported fields is applied recursively to nested data
// structures.
//
// This function does no assertion of any kind.
func ObjectsExportedFieldsAreEqual(expected, actual interface{}) bool {
expectedCleaned := copyExportedFields(expected)
actualCleaned := copyExportedFields(actual)
return ObjectsAreEqualValues(expectedCleaned, actualCleaned)
} }
// ObjectsAreEqualValues gets whether two objects are equal, or if their // ObjectsAreEqualValues gets whether two objects are equal, or if their
@ -141,12 +221,11 @@ func CallerInfo() []string {
} }
parts := strings.Split(file, "/") parts := strings.Split(file, "/")
file = parts[len(parts)-1]
if len(parts) > 1 { if len(parts) > 1 {
filename := parts[len(parts)-1]
dir := parts[len(parts)-2] dir := parts[len(parts)-2]
if (dir != "assert" && dir != "mock" && dir != "require") || file == "mock_test.go" { if (dir != "assert" && dir != "mock" && dir != "require") || filename == "mock_test.go" {
path, _ := filepath.Abs(file) callers = append(callers, fmt.Sprintf("%s:%d", file, line))
callers = append(callers, fmt.Sprintf("%s:%d", path, line))
} }
} }
@ -197,7 +276,7 @@ func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
// Aligns the provided message so that all lines after the first line start at the same location as the first line. // Aligns the provided message so that all lines after the first line start at the same location as the first line.
// Assumes that the first line starts at the correct location (after carriage return, tab, label, spacer and tab). // Assumes that the first line starts at the correct location (after carriage return, tab, label, spacer and tab).
// The longestLabelLen parameter specifies the length of the longest label in the output (required becaues this is the // The longestLabelLen parameter specifies the length of the longest label in the output (required because this is the
// basis on which the alignment occurs). // basis on which the alignment occurs).
func indentMessageLines(message string, longestLabelLen int) string { func indentMessageLines(message string, longestLabelLen int) string {
outBuf := new(bytes.Buffer) outBuf := new(bytes.Buffer)
@ -427,7 +506,7 @@ func samePointers(first, second interface{}) bool {
// representations appropriate to be presented to the user. // representations appropriate to be presented to the user.
// //
// If the values are not of like type, the returned strings will be prefixed // If the values are not of like type, the returned strings will be prefixed
// with the type name, and the value will be enclosed in parenthesis similar // with the type name, and the value will be enclosed in parentheses similar
// to a type conversion in the Go grammar. // to a type conversion in the Go grammar.
func formatUnequalValues(expected, actual interface{}) (e string, a string) { func formatUnequalValues(expected, actual interface{}) (e string, a string) {
if reflect.TypeOf(expected) != reflect.TypeOf(actual) { if reflect.TypeOf(expected) != reflect.TypeOf(actual) {
@ -454,7 +533,7 @@ func truncatingFormat(data interface{}) string {
return value return value
} }
// EqualValues asserts that two objects are equal or convertable to the same types // EqualValues asserts that two objects are equal or convertible to the same types
// and equal. // and equal.
// //
// assert.EqualValues(t, uint32(123), int32(123)) // assert.EqualValues(t, uint32(123), int32(123))
@ -475,6 +554,50 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa
} }
// EqualExportedValues asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// assert.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true
// assert.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false
func EqualExportedValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
aType := reflect.TypeOf(expected)
bType := reflect.TypeOf(actual)
if aType != bType {
return Fail(t, fmt.Sprintf("Types expected to match exactly\n\t%v != %v", aType, bType), msgAndArgs...)
}
if aType.Kind() != reflect.Struct {
return Fail(t, fmt.Sprintf("Types expected to both be struct \n\t%v != %v", aType.Kind(), reflect.Struct), msgAndArgs...)
}
if bType.Kind() != reflect.Struct {
return Fail(t, fmt.Sprintf("Types expected to both be struct \n\t%v != %v", bType.Kind(), reflect.Struct), msgAndArgs...)
}
expected = copyExportedFields(expected)
actual = copyExportedFields(actual)
if !ObjectsAreEqualValues(expected, actual) {
diff := diff(expected, actual)
expected, actual = formatUnequalValues(expected, actual)
return Fail(t, fmt.Sprintf("Not equal (comparing only exported fields): \n"+
"expected: %s\n"+
"actual : %s%s", expected, actual, diff), msgAndArgs...)
}
return true
}
// Exactly asserts that two objects are equal in value and type. // Exactly asserts that two objects are equal in value and type.
// //
// assert.Exactly(t, int32(123), int64(123)) // assert.Exactly(t, int32(123), int64(123))
@ -530,7 +653,7 @@ func isNil(object interface{}) bool {
[]reflect.Kind{ []reflect.Kind{
reflect.Chan, reflect.Func, reflect.Chan, reflect.Func,
reflect.Interface, reflect.Map, reflect.Interface, reflect.Map,
reflect.Ptr, reflect.Slice}, reflect.Ptr, reflect.Slice, reflect.UnsafePointer},
kind) kind)
if isNilableKind && value.IsNil() { if isNilableKind && value.IsNil() {
@ -618,16 +741,14 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
} }
// getLen try to get length of object. // getLen tries to get the length of an object.
// return (false, 0) if impossible. // It returns (0, false) if impossible.
func getLen(x interface{}) (ok bool, length int) { func getLen(x interface{}) (length int, ok bool) {
v := reflect.ValueOf(x) v := reflect.ValueOf(x)
defer func() { defer func() {
if e := recover(); e != nil { ok = recover() == nil
ok = false
}
}() }()
return true, v.Len() return v.Len(), true
} }
// Len asserts that the specified object has specific length. // Len asserts that the specified object has specific length.
@ -638,7 +759,7 @@ func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{})
if h, ok := t.(tHelper); ok { if h, ok := t.(tHelper); ok {
h.Helper() h.Helper()
} }
ok, l := getLen(object) l, ok := getLen(object)
if !ok { if !ok {
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", object), msgAndArgs...) return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", object), msgAndArgs...)
} }
@ -796,10 +917,10 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{})
ok, found := containsElement(s, contains) ok, found := containsElement(s, contains)
if !ok { if !ok {
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", s), msgAndArgs...) return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...)
} }
if found { if found {
return Fail(t, fmt.Sprintf("\"%s\" should not contain \"%s\"", s, contains), msgAndArgs...) return Fail(t, fmt.Sprintf("%#v should not contain %#v", s, contains), msgAndArgs...)
} }
return true return true
@ -818,49 +939,44 @@ func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok
return true // we consider nil to be equal to the nil set return true // we consider nil to be equal to the nil set
} }
defer func() {
if e := recover(); e != nil {
ok = false
}
}()
listKind := reflect.TypeOf(list).Kind() listKind := reflect.TypeOf(list).Kind()
subsetKind := reflect.TypeOf(subset).Kind()
if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map { if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
} }
subsetKind := reflect.TypeOf(subset).Kind()
if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map { if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
} }
subsetValue := reflect.ValueOf(subset)
if subsetKind == reflect.Map && listKind == reflect.Map { if subsetKind == reflect.Map && listKind == reflect.Map {
listValue := reflect.ValueOf(list) subsetMap := reflect.ValueOf(subset)
subsetKeys := subsetValue.MapKeys() actualMap := reflect.ValueOf(list)
for i := 0; i < len(subsetKeys); i++ { for _, k := range subsetMap.MapKeys() {
subsetKey := subsetKeys[i] ev := subsetMap.MapIndex(k)
subsetElement := subsetValue.MapIndex(subsetKey).Interface() av := actualMap.MapIndex(k)
listElement := listValue.MapIndex(subsetKey).Interface()
if !ObjectsAreEqual(subsetElement, listElement) { if !av.IsValid() {
return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, subsetElement), msgAndArgs...) return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, subset), msgAndArgs...)
}
if !ObjectsAreEqual(ev.Interface(), av.Interface()) {
return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, subset), msgAndArgs...)
} }
} }
return true return true
} }
for i := 0; i < subsetValue.Len(); i++ { subsetList := reflect.ValueOf(subset)
element := subsetValue.Index(i).Interface() for i := 0; i < subsetList.Len(); i++ {
element := subsetList.Index(i).Interface()
ok, found := containsElement(list, element) ok, found := containsElement(list, element)
if !ok { if !ok {
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...) return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", list), msgAndArgs...)
} }
if !found { if !found {
return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", list, element), msgAndArgs...) return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, element), msgAndArgs...)
} }
} }
@ -879,34 +995,28 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
return Fail(t, "nil is the empty set which is a subset of every set", msgAndArgs...) return Fail(t, "nil is the empty set which is a subset of every set", msgAndArgs...)
} }
defer func() {
if e := recover(); e != nil {
ok = false
}
}()
listKind := reflect.TypeOf(list).Kind() listKind := reflect.TypeOf(list).Kind()
subsetKind := reflect.TypeOf(subset).Kind()
if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map { if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...) return Fail(t, fmt.Sprintf("%q has an unsupported type %s", list, listKind), msgAndArgs...)
} }
subsetKind := reflect.TypeOf(subset).Kind()
if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map { if subsetKind != reflect.Array && subsetKind != reflect.Slice && listKind != reflect.Map {
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...) return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
} }
subsetValue := reflect.ValueOf(subset)
if subsetKind == reflect.Map && listKind == reflect.Map { if subsetKind == reflect.Map && listKind == reflect.Map {
listValue := reflect.ValueOf(list) subsetMap := reflect.ValueOf(subset)
subsetKeys := subsetValue.MapKeys() actualMap := reflect.ValueOf(list)
for i := 0; i < len(subsetKeys); i++ { for _, k := range subsetMap.MapKeys() {
subsetKey := subsetKeys[i] ev := subsetMap.MapIndex(k)
subsetElement := subsetValue.MapIndex(subsetKey).Interface() av := actualMap.MapIndex(k)
listElement := listValue.MapIndex(subsetKey).Interface()
if !ObjectsAreEqual(subsetElement, listElement) { if !av.IsValid() {
return true
}
if !ObjectsAreEqual(ev.Interface(), av.Interface()) {
return true return true
} }
} }
@ -914,8 +1024,9 @@ func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{})
return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...) return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...)
} }
for i := 0; i < subsetValue.Len(); i++ { subsetList := reflect.ValueOf(subset)
element := subsetValue.Index(i).Interface() for i := 0; i < subsetList.Len(); i++ {
element := subsetList.Index(i).Interface()
ok, found := containsElement(list, element) ok, found := containsElement(list, element)
if !ok { if !ok {
return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...) return Fail(t, fmt.Sprintf("\"%s\" could not be applied builtin len()", list), msgAndArgs...)
@ -1756,6 +1867,90 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t
} }
} }
// CollectT implements the TestingT interface and collects all errors.
type CollectT struct {
errors []error
}
// Errorf collects the error.
func (c *CollectT) Errorf(format string, args ...interface{}) {
c.errors = append(c.errors, fmt.Errorf(format, args...))
}
// FailNow panics.
func (*CollectT) FailNow() {
panic("Assertion failed")
}
// Deprecated: That was a method for internal usage that should not have been published. Now just panics.
func (*CollectT) Reset() {
panic("Reset() is deprecated")
}
// Deprecated: That was a method for internal usage that should not have been published. Now just panics.
func (*CollectT) Copy(TestingT) {
panic("Copy() is deprecated")
}
// EventuallyWithT asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// assert.EventuallyWithT(t, func(c *assert.CollectT) {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func EventuallyWithT(t TestingT, condition func(collect *CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
var lastFinishedTickErrs []error
ch := make(chan []error, 1)
timer := time.NewTimer(waitFor)
defer timer.Stop()
ticker := time.NewTicker(tick)
defer ticker.Stop()
for tick := ticker.C; ; {
select {
case <-timer.C:
for _, err := range lastFinishedTickErrs {
t.Errorf("%v", err)
}
return Fail(t, "Condition never satisfied", msgAndArgs...)
case <-tick:
tick = nil
go func() {
collect := new(CollectT)
defer func() {
ch <- collect.errors
}()
condition(collect)
}()
case errs := <-ch:
if len(errs) == 0 {
return true
}
// Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached.
lastFinishedTickErrs = errs
tick = ticker.C
}
}
}
// Never asserts that the given condition doesn't satisfy in waitFor time, // Never asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick. // periodically checking the target function each tick.
// //

View File

@ -9,12 +9,14 @@ import (
"io" "io"
"math" "math"
"os" "os"
"path/filepath"
"reflect" "reflect"
"regexp" "regexp"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
"time" "time"
"unsafe"
) )
var ( var (
@ -146,6 +148,289 @@ func TestObjectsAreEqual(t *testing.T) {
t.Fail() t.Fail()
} }
tm := time.Now()
tz := tm.In(time.Local)
if !ObjectsAreEqualValues(tm, tz) {
t.Error("ObjectsAreEqualValues should return true for time.Time objects with different time zones")
}
}
type Nested struct {
Exported interface{}
notExported interface{}
}
type S struct {
Exported1 interface{}
Exported2 Nested
notExported1 interface{}
notExported2 Nested
}
type S2 struct {
foo interface{}
}
type S3 struct {
Exported1 *Nested
Exported2 *Nested
}
type S4 struct {
Exported1 []*Nested
}
type S5 struct {
Exported Nested
}
type S6 struct {
Exported string
unexported string
}
func TestObjectsExportedFieldsAreEqual(t *testing.T) {
intValue := 1
cases := []struct {
expected interface{}
actual interface{}
result bool
}{
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{2, 3}, 4, Nested{5, 6}}, true},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{2, 3}, "a", Nested{5, 6}}, true},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{2, 3}, 4, Nested{5, "a"}}, true},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{2, 3}, 4, Nested{"a", "a"}}, true},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{2, "a"}, 4, Nested{5, 6}}, true},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{"a", Nested{2, 3}, 4, Nested{5, 6}}, false},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S{1, Nested{"a", 3}, 4, Nested{5, 6}}, false},
{S{1, Nested{2, 3}, 4, Nested{5, 6}}, S2{1}, false},
{1, S{1, Nested{2, 3}, 4, Nested{5, 6}}, false},
{S3{&Nested{1, 2}, &Nested{3, 4}}, S3{&Nested{1, 2}, &Nested{3, 4}}, true},
{S3{nil, &Nested{3, 4}}, S3{nil, &Nested{3, 4}}, true},
{S3{&Nested{1, 2}, &Nested{3, 4}}, S3{&Nested{1, 2}, &Nested{3, "b"}}, true},
{S3{&Nested{1, 2}, &Nested{3, 4}}, S3{&Nested{1, "a"}, &Nested{3, "b"}}, true},
{S3{&Nested{1, 2}, &Nested{3, 4}}, S3{&Nested{"a", 2}, &Nested{3, 4}}, false},
{S3{&Nested{1, 2}, &Nested{3, 4}}, S3{}, false},
{S3{}, S3{}, true},
{S4{[]*Nested{{1, 2}}}, S4{[]*Nested{{1, 2}}}, true},
{S4{[]*Nested{{1, 2}}}, S4{[]*Nested{{1, 3}}}, true},
{S4{[]*Nested{{1, 2}, {3, 4}}}, S4{[]*Nested{{1, "a"}, {3, "b"}}}, true},
{S4{[]*Nested{{1, 2}, {3, 4}}}, S4{[]*Nested{{1, "a"}, {2, "b"}}}, false},
{Nested{&intValue, 2}, Nested{&intValue, 2}, true},
{Nested{&Nested{1, 2}, 3}, Nested{&Nested{1, "b"}, 3}, true},
{Nested{&Nested{1, 2}, 3}, Nested{nil, 3}, false},
{
Nested{map[interface{}]*Nested{nil: nil}, 2},
Nested{map[interface{}]*Nested{nil: nil}, 2},
true,
},
{
Nested{map[interface{}]*Nested{"a": nil}, 2},
Nested{map[interface{}]*Nested{"a": nil}, 2},
true,
},
{
Nested{map[interface{}]*Nested{"a": nil}, 2},
Nested{map[interface{}]*Nested{"a": {1, 2}}, 2},
false,
},
{
Nested{map[interface{}]Nested{"a": {1, 2}, "b": {3, 4}}, 2},
Nested{map[interface{}]Nested{"a": {1, 5}, "b": {3, 7}}, 2},
true,
},
{
Nested{map[interface{}]Nested{"a": {1, 2}, "b": {3, 4}}, 2},
Nested{map[interface{}]Nested{"a": {2, 2}, "b": {3, 4}}, 2},
false,
},
}
for _, c := range cases {
t.Run(fmt.Sprintf("ObjectsExportedFieldsAreEqual(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
res := ObjectsExportedFieldsAreEqual(c.expected, c.actual)
if res != c.result {
t.Errorf("ObjectsExportedFieldsAreEqual(%#v, %#v) should return %#v", c.expected, c.actual, c.result)
}
})
}
}
func TestCopyExportedFields(t *testing.T) {
intValue := 1
cases := []struct {
input interface{}
expected interface{}
}{
{
input: Nested{"a", "b"},
expected: Nested{"a", nil},
},
{
input: Nested{&intValue, 2},
expected: Nested{&intValue, nil},
},
{
input: Nested{nil, 3},
expected: Nested{nil, nil},
},
{
input: S{1, Nested{2, 3}, 4, Nested{5, 6}},
expected: S{1, Nested{2, nil}, nil, Nested{}},
},
{
input: S3{},
expected: S3{},
},
{
input: S3{&Nested{1, 2}, &Nested{3, 4}},
expected: S3{&Nested{1, nil}, &Nested{3, nil}},
},
{
input: S3{Exported1: &Nested{"a", "b"}},
expected: S3{Exported1: &Nested{"a", nil}},
},
{
input: S4{[]*Nested{
nil,
{1, 2},
}},
expected: S4{[]*Nested{
nil,
{1, nil},
}},
},
{
input: S4{[]*Nested{
{1, 2}},
},
expected: S4{[]*Nested{
{1, nil}},
},
},
{
input: S4{[]*Nested{
{1, 2},
{3, 4},
}},
expected: S4{[]*Nested{
{1, nil},
{3, nil},
}},
},
{
input: S5{Exported: Nested{"a", "b"}},
expected: S5{Exported: Nested{"a", nil}},
},
{
input: S6{"a", "b"},
expected: S6{"a", ""},
},
}
for _, c := range cases {
t.Run("", func(t *testing.T) {
output := copyExportedFields(c.input)
if !ObjectsAreEqualValues(c.expected, output) {
t.Errorf("%#v, %#v should be equal", c.expected, output)
}
})
}
}
func TestEqualExportedValues(t *testing.T) {
cases := []struct {
value1 interface{}
value2 interface{}
expectedEqual bool
expectedFail string
}{
{
value1: S{1, Nested{2, 3}, 4, Nested{5, 6}},
value2: S{1, Nested{2, nil}, nil, Nested{}},
expectedEqual: true,
},
{
value1: S{1, Nested{2, 3}, 4, Nested{5, 6}},
value2: S{1, Nested{1, nil}, nil, Nested{}},
expectedEqual: false,
expectedFail: `
Diff:
--- Expected
+++ Actual
@@ -3,3 +3,3 @@
Exported2: (assert.Nested) {
- Exported: (int) 2,
+ Exported: (int) 1,
notExported: (interface {}) <nil>`,
},
{
value1: S3{&Nested{1, 2}, &Nested{3, 4}},
value2: S3{&Nested{"a", 2}, &Nested{3, 4}},
expectedEqual: false,
expectedFail: `
Diff:
--- Expected
+++ Actual
@@ -2,3 +2,3 @@
Exported1: (*assert.Nested)({
- Exported: (int) 1,
+ Exported: (string) (len=1) "a",
notExported: (interface {}) <nil>`,
},
{
value1: S4{[]*Nested{
{1, 2},
{3, 4},
}},
value2: S4{[]*Nested{
{1, "a"},
{2, "b"},
}},
expectedEqual: false,
expectedFail: `
Diff:
--- Expected
+++ Actual
@@ -7,3 +7,3 @@
(*assert.Nested)({
- Exported: (int) 3,
+ Exported: (int) 2,
notExported: (interface {}) <nil>`,
},
{
value1: S{[2]int{1, 2}, Nested{2, 3}, 4, Nested{5, 6}},
value2: S{[2]int{1, 2}, Nested{2, nil}, nil, Nested{}},
expectedEqual: true,
},
}
for _, c := range cases {
t.Run("", func(t *testing.T) {
mockT := new(mockTestingT)
actual := EqualExportedValues(mockT, c.value1, c.value2)
if actual != c.expectedEqual {
t.Errorf("Expected EqualExportedValues to be %t, but was %t", c.expectedEqual, actual)
}
actualFail := mockT.errorString()
if !strings.Contains(actualFail, c.expectedFail) {
t.Errorf("Contains failure should include %q but was %q", c.expectedFail, actualFail)
}
})
}
} }
func TestImplements(t *testing.T) { func TestImplements(t *testing.T) {
@ -642,15 +927,59 @@ func TestContainsNotContains(t *testing.T) {
} }
} }
func TestContainsFailMessage(t *testing.T) { func TestContainsNotContainsFailMessage(t *testing.T) {
mockT := new(mockTestingT) mockT := new(mockTestingT)
Contains(mockT, "Hello World", errors.New("Hello")) type nonContainer struct {
expectedFail := "\"Hello World\" does not contain &errors.errorString{s:\"Hello\"}" Value string
}
cases := []struct {
assertion func(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool
container interface{}
instance interface{}
expected string
}{
{
assertion: Contains,
container: "Hello World",
instance: errors.New("Hello"),
expected: "\"Hello World\" does not contain &errors.errorString{s:\"Hello\"}",
},
{
assertion: Contains,
container: map[string]int{"one": 1},
instance: "two",
expected: "map[string]int{\"one\":1} does not contain \"two\"\n",
},
{
assertion: NotContains,
container: map[string]int{"one": 1},
instance: "one",
expected: "map[string]int{\"one\":1} should not contain \"one\"",
},
{
assertion: Contains,
container: nonContainer{Value: "Hello"},
instance: "Hello",
expected: "assert.nonContainer{Value:\"Hello\"} could not be applied builtin len()\n",
},
{
assertion: NotContains,
container: nonContainer{Value: "Hello"},
instance: "Hello",
expected: "assert.nonContainer{Value:\"Hello\"} could not be applied builtin len()\n",
},
}
for _, c := range cases {
name := filepath.Base(runtime.FuncForPC(reflect.ValueOf(c.assertion).Pointer()).Name())
t.Run(fmt.Sprintf("%v(%T, %T)", name, c.container, c.instance), func(t *testing.T) {
c.assertion(mockT, c.container, c.instance)
actualFail := mockT.errorString() actualFail := mockT.errorString()
if !strings.Contains(actualFail, expectedFail) { if !strings.Contains(actualFail, c.expected) {
t.Errorf("Contains failure should include %q but was %q", expectedFail, actualFail) t.Errorf("Contains failure should include %q but was %q", c.expected, actualFail)
}
})
} }
} }
@ -671,20 +1000,18 @@ func TestContainsNotContainsOnNilValue(t *testing.T) {
} }
func TestSubsetNotSubset(t *testing.T) { func TestSubsetNotSubset(t *testing.T) {
// MTestCase adds a custom message to the case
cases := []struct { cases := []struct {
expected interface{} list interface{}
actual interface{} subset interface{}
result bool result bool
message string message string
}{ }{
// cases that are expected to contain // cases that are expected to contain
{[]int{1, 2, 3}, nil, true, "given subset is nil"}, {[]int{1, 2, 3}, nil, true, `nil is the empty set which is a subset of every set`},
{[]int{1, 2, 3}, []int{}, true, "any set contains the nil set"}, {[]int{1, 2, 3}, []int{}, true, `[] is a subset of ['\x01' '\x02' '\x03']`},
{[]int{1, 2, 3}, []int{1, 2}, true, "[1, 2, 3] contains [1, 2]"}, {[]int{1, 2, 3}, []int{1, 2}, true, `['\x01' '\x02'] is a subset of ['\x01' '\x02' '\x03']`},
{[]int{1, 2, 3}, []int{1, 2, 3}, true, "[1, 2, 3] contains [1, 2, 3"}, {[]int{1, 2, 3}, []int{1, 2, 3}, true, `['\x01' '\x02' '\x03'] is a subset of ['\x01' '\x02' '\x03']`},
{[]string{"hello", "world"}, []string{"hello"}, true, "[\"hello\", \"world\"] contains [\"hello\"]"}, {[]string{"hello", "world"}, []string{"hello"}, true, `["hello"] is a subset of ["hello" "world"]`},
{map[string]string{ {map[string]string{
"a": "x", "a": "x",
"c": "z", "c": "z",
@ -692,12 +1019,12 @@ func TestSubsetNotSubset(t *testing.T) {
}, map[string]string{ }, map[string]string{
"a": "x", "a": "x",
"b": "y", "b": "y",
}, true, `{ "a": "x", "b": "y", "c": "z"} contains { "a": "x", "b": "y"}`}, }, true, `map["a":"x" "b":"y"] is a subset of map["a":"x" "b":"y" "c":"z"]`},
// cases that are expected not to contain // cases that are expected not to contain
{[]string{"hello", "world"}, []string{"hello", "testify"}, false, "[\"hello\", \"world\"] does not contain [\"hello\", \"testify\"]"}, {[]string{"hello", "world"}, []string{"hello", "testify"}, false, `[]string{"hello", "world"} does not contain "testify"`},
{[]int{1, 2, 3}, []int{4, 5}, false, "[1, 2, 3] does not contain [4, 5"}, {[]int{1, 2, 3}, []int{4, 5}, false, `[]int{1, 2, 3} does not contain 4`},
{[]int{1, 2, 3}, []int{1, 5}, false, "[1, 2, 3] does not contain [1, 5]"}, {[]int{1, 2, 3}, []int{1, 5}, false, `[]int{1, 2, 3} does not contain 5`},
{map[string]string{ {map[string]string{
"a": "x", "a": "x",
"c": "z", "c": "z",
@ -705,35 +1032,51 @@ func TestSubsetNotSubset(t *testing.T) {
}, map[string]string{ }, map[string]string{
"a": "x", "a": "x",
"b": "z", "b": "z",
}, false, `{ "a": "x", "b": "y", "c": "z"} does not contain { "a": "x", "b": "z"}`}, }, false, `map[string]string{"a":"x", "b":"y", "c":"z"} does not contain map[string]string{"a":"x", "b":"z"}`},
{map[string]string{
"a": "x",
"b": "y",
}, map[string]string{
"a": "x",
"b": "y",
"c": "z",
}, false, `map[string]string{"a":"x", "b":"y"} does not contain map[string]string{"a":"x", "b":"y", "c":"z"}`},
} }
for _, c := range cases { for _, c := range cases {
t.Run("SubSet: "+c.message, func(t *testing.T) { t.Run("SubSet: "+c.message, func(t *testing.T) {
mockT := new(testing.T) mockT := new(mockTestingT)
res := Subset(mockT, c.expected, c.actual) res := Subset(mockT, c.list, c.subset)
if res != c.result { if res != c.result {
if res { t.Errorf("Subset should return %t: %s", c.result, c.message)
t.Errorf("Subset should return true: %s", c.message) }
} else { if !c.result {
t.Errorf("Subset should return false: %s", c.message) expectedFail := c.message
actualFail := mockT.errorString()
if !strings.Contains(actualFail, expectedFail) {
t.Log(actualFail)
t.Errorf("Subset failure should contain %q but was %q", expectedFail, actualFail)
} }
} }
}) })
} }
for _, c := range cases { for _, c := range cases {
t.Run("NotSubSet: "+c.message, func(t *testing.T) { t.Run("NotSubSet: "+c.message, func(t *testing.T) {
mockT := new(testing.T) mockT := new(mockTestingT)
res := NotSubset(mockT, c.expected, c.actual) res := NotSubset(mockT, c.list, c.subset)
// NotSubset should match the inverse of Subset. If it doesn't, something is wrong // NotSubset should match the inverse of Subset. If it doesn't, something is wrong
if res == Subset(mockT, c.expected, c.actual) { if res == Subset(mockT, c.list, c.subset) {
if res { t.Errorf("NotSubset should return %t: %s", !c.result, c.message)
t.Errorf("NotSubset should return true: %s", c.message) }
} else { if c.result {
t.Errorf("NotSubset should return false: %s", c.message) expectedFail := c.message
actualFail := mockT.errorString()
if !strings.Contains(actualFail, expectedFail) {
t.Log(actualFail)
t.Errorf("NotSubset failure should contain %q but was %q", expectedFail, actualFail)
} }
} }
}) })
@ -1096,7 +1439,7 @@ func TestError(t *testing.T) {
True(t, Error(mockT, err), "Error with error should return True") True(t, Error(mockT, err), "Error with error should return True")
// go vet check // go vet check
True(t, Errorf(mockT, err, "example with %s", "formatted message"), "Errorf with error should rturn True") True(t, Errorf(mockT, err, "example with %s", "formatted message"), "Errorf with error should return True")
// returning an empty error interface // returning an empty error interface
err = func() error { err = func() error {
@ -1250,7 +1593,7 @@ func Test_getLen(t *testing.T) {
struct{}{}, struct{}{},
} }
for _, v := range falseCases { for _, v := range falseCases {
ok, l := getLen(v) l, ok := getLen(v)
False(t, ok, "Expected getLen fail to get length of %#v", v) False(t, ok, "Expected getLen fail to get length of %#v", v)
Equal(t, 0, l, "getLen should return 0 for %#v", v) Equal(t, 0, l, "getLen should return 0 for %#v", v)
} }
@ -1279,7 +1622,7 @@ func Test_getLen(t *testing.T) {
} }
for _, c := range trueCases { for _, c := range trueCases {
ok, l := getLen(c.v) l, ok := getLen(c.v)
True(t, ok, "Expected getLen success to get length of %#v", c.v) True(t, ok, "Expected getLen success to get length of %#v", c.v)
Equal(t, c.l, l) Equal(t, c.l, l)
} }
@ -1575,12 +1918,12 @@ func TestInEpsilonSlice(t *testing.T) {
True(t, InEpsilonSlice(mockT, True(t, InEpsilonSlice(mockT,
[]float64{2.2, math.NaN(), 2.0}, []float64{2.2, math.NaN(), 2.0},
[]float64{2.1, math.NaN(), 2.1}, []float64{2.1, math.NaN(), 2.1},
0.06), "{2.2, NaN, 2.0} is element-wise close to {2.1, NaN, 2.1} in espilon=0.06") 0.06), "{2.2, NaN, 2.0} is element-wise close to {2.1, NaN, 2.1} in epsilon=0.06")
False(t, InEpsilonSlice(mockT, False(t, InEpsilonSlice(mockT,
[]float64{2.2, 2.0}, []float64{2.2, 2.0},
[]float64{2.1, 2.1}, []float64{2.1, 2.1},
0.04), "{2.2, 2.0} is not element-wise close to {2.1, 2.1} in espilon=0.04") 0.04), "{2.2, 2.0} is not element-wise close to {2.1, 2.1} in epsilon=0.04")
False(t, InEpsilonSlice(mockT, "", nil, 1), "Expected non numeral slices to fail") False(t, InEpsilonSlice(mockT, "", nil, 1), "Expected non numeral slices to fail")
} }
@ -2428,6 +2771,78 @@ func TestEventuallyTrue(t *testing.T) {
True(t, Eventually(t, condition, 100*time.Millisecond, 20*time.Millisecond)) True(t, Eventually(t, condition, 100*time.Millisecond, 20*time.Millisecond))
} }
// errorsCapturingT is a mock implementation of TestingT that captures errors reported with Errorf.
type errorsCapturingT struct {
errors []error
}
func (t *errorsCapturingT) Errorf(format string, args ...interface{}) {
t.errors = append(t.errors, fmt.Errorf(format, args...))
}
func (t *errorsCapturingT) Helper() {}
func TestEventuallyWithTFalse(t *testing.T) {
mockT := new(errorsCapturingT)
condition := func(collect *CollectT) {
Fail(collect, "condition fixed failure")
}
False(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond))
Len(t, mockT.errors, 2)
}
func TestEventuallyWithTTrue(t *testing.T) {
mockT := new(errorsCapturingT)
state := 0
condition := func(collect *CollectT) {
defer func() {
state += 1
}()
True(collect, state == 2)
}
True(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond))
Len(t, mockT.errors, 0)
}
func TestEventuallyWithT_ConcurrencySafe(t *testing.T) {
mockT := new(errorsCapturingT)
condition := func(collect *CollectT) {
Fail(collect, "condition fixed failure")
}
// To trigger race conditions, we run EventuallyWithT with a nanosecond tick.
False(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, time.Nanosecond))
Len(t, mockT.errors, 2)
}
func TestEventuallyWithT_ReturnsTheLatestFinishedConditionErrors(t *testing.T) {
// We'll use a channel to control whether a condition should sleep or not.
mustSleep := make(chan bool, 2)
mustSleep <- false
mustSleep <- true
close(mustSleep)
condition := func(collect *CollectT) {
if <-mustSleep {
// Sleep to ensure that the second condition runs longer than timeout.
time.Sleep(time.Second)
return
}
// The first condition will fail. We expect to get this error as a result.
Fail(collect, "condition fixed failure")
}
mockT := new(errorsCapturingT)
False(t, EventuallyWithT(mockT, condition, 100*time.Millisecond, 20*time.Millisecond))
Len(t, mockT.errors, 2)
}
func TestNeverFalse(t *testing.T) { func TestNeverFalse(t *testing.T) {
condition := func() bool { condition := func() bool {
return false return false
@ -2436,14 +2851,20 @@ func TestNeverFalse(t *testing.T) {
True(t, Never(t, condition, 100*time.Millisecond, 20*time.Millisecond)) True(t, Never(t, condition, 100*time.Millisecond, 20*time.Millisecond))
} }
// TestNeverTrue checks Never with a condition that returns true on second call.
func TestNeverTrue(t *testing.T) { func TestNeverTrue(t *testing.T) {
mockT := new(testing.T) mockT := new(testing.T)
state := 0
// A list of values returned by condition.
// Channel protects against concurrent access.
returns := make(chan bool, 2)
returns <- false
returns <- true
defer close(returns)
// Will return true on second call.
condition := func() bool { condition := func() bool {
defer func() { return <-returns
state = state + 1
}()
return state == 2
} }
False(t, Never(mockT, condition, 100*time.Millisecond, 20*time.Millisecond)) False(t, Never(mockT, condition, 100*time.Millisecond, 20*time.Millisecond))
@ -2558,3 +2979,10 @@ func TestErrorAs(t *testing.T) {
}) })
} }
} }
func TestIsNil(t *testing.T) {
var n unsafe.Pointer = nil
if !isNil(n) {
t.Fatal("fail")
}
}

View File

@ -1,8 +1,9 @@
// Package assert provides a set of comprehensive testing tools for use with the normal Go testing system. // Package assert provides a set of comprehensive testing tools for use with the normal Go testing system.
// //
// Example Usage // # Example Usage
// //
// The following is a complete example using assert in a standard test function: // The following is a complete example using assert in a standard test function:
//
// import ( // import (
// "testing" // "testing"
// "github.com/stretchr/testify/assert" // "github.com/stretchr/testify/assert"
@ -33,7 +34,7 @@
// assert.Equal(a, b, "The two words should be the same.") // assert.Equal(a, b, "The two words should be the same.")
// } // }
// //
// Assertions // # Assertions
// //
// Assertions allow you to easily write test code, and are global funcs in the `assert` package. // Assertions allow you to easily write test code, and are global funcs in the `assert` package.
// All assertion functions take, as the first argument, the `*testing.T` object provided by the // All assertion functions take, as the first argument, the `*testing.T` object provided by the

View File

@ -12,7 +12,7 @@ import (
// an error if building a new request fails. // an error if building a new request fails.
func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) { func httpCode(handler http.HandlerFunc, method, url string, values url.Values) (int, error) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
req, err := http.NewRequest(method, url, nil) req, err := http.NewRequest(method, url, http.NoBody)
if err != nil { if err != nil {
return -1, err return -1, err
} }
@ -113,7 +113,10 @@ func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method, url string, va
// empty string if building a new request fails. // empty string if building a new request fails.
func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string { func HTTPBody(handler http.HandlerFunc, method, url string, values url.Values) string {
w := httptest.NewRecorder() w := httptest.NewRecorder()
req, err := http.NewRequest(method, url+"?"+values.Encode(), nil) if len(values) > 0 {
url += "?" + values.Encode()
}
req, err := http.NewRequest(method, url, http.NoBody)
if err != nil { if err != nil {
return "" return ""
} }

View File

@ -2,6 +2,7 @@ package assert
import ( import (
"fmt" "fmt"
"io"
"net/http" "net/http"
"net/url" "net/url"
"testing" "testing"
@ -11,6 +12,12 @@ func httpOK(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
func httpReadBody(w http.ResponseWriter, r *http.Request) {
_, _ = io.Copy(io.Discard, r.Body)
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("hello"))
}
func httpRedirect(w http.ResponseWriter, r *http.Request) { func httpRedirect(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusTemporaryRedirect) w.WriteHeader(http.StatusTemporaryRedirect)
} }
@ -41,6 +48,10 @@ func TestHTTPSuccess(t *testing.T) {
mockT4 := new(testing.T) mockT4 := new(testing.T)
assert.Equal(HTTPSuccess(mockT4, httpStatusCode, "GET", "/", nil), false) assert.Equal(HTTPSuccess(mockT4, httpStatusCode, "GET", "/", nil), false)
assert.True(mockT4.Failed()) assert.True(mockT4.Failed())
mockT5 := new(testing.T)
assert.Equal(HTTPSuccess(mockT5, httpReadBody, "POST", "/", nil), true)
assert.False(mockT5.Failed())
} }
func TestHTTPRedirect(t *testing.T) { func TestHTTPRedirect(t *testing.T) {
@ -122,7 +133,7 @@ func TestHTTPStatusesWrapper(t *testing.T) {
func httpHelloName(w http.ResponseWriter, r *http.Request) { func httpHelloName(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("name") name := r.FormValue("name")
_, _ = w.Write([]byte(fmt.Sprintf("Hello, %s!", name))) _, _ = fmt.Fprintf(w, "Hello, %s!", name)
} }
func TestHTTPRequestWithNoParams(t *testing.T) { func TestHTTPRequestWithNoParams(t *testing.T) {
@ -165,6 +176,8 @@ func TestHttpBody(t *testing.T) {
assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!")) assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World")) assert.False(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
assert.True(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world")) assert.True(HTTPBodyNotContains(mockT, httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
assert.True(HTTPBodyContains(mockT, httpReadBody, "GET", "/", nil, "hello"))
} }
func TestHttpBodyWrappers(t *testing.T) { func TestHttpBodyWrappers(t *testing.T) {
@ -178,5 +191,4 @@ func TestHttpBodyWrappers(t *testing.T) {
assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!")) assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "Hello, World!"))
assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World")) assert.False(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "World"))
assert.True(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world")) assert.True(mockAssert.HTTPBodyNotContains(httpHelloName, "GET", "/", url.Values{"name": []string{"World"}}, "world"))
} }

6
go.mod
View File

@ -1,10 +1,12 @@
module github.com/stretchr/testify module github.com/stretchr/testify
go 1.13 // This should match the minimum supported version that is tested in
// .github/workflows/main.yml
go 1.17
require ( require (
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib v1.0.0
github.com/stretchr/objx v0.4.0 github.com/stretchr/objx v0.5.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )

4
go.sum
View File

@ -4,9 +4,11 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -1,7 +1,7 @@
// Package mock provides a system by which it is possible to mock your objects // Package mock provides a system by which it is possible to mock your objects
// and verify calls are happening as expected. // and verify calls are happening as expected.
// //
// Example Usage // # Example Usage
// //
// The mock package provides an object, Mock, that tracks activity on another object. It is usually // The mock package provides an object, Mock, that tracks activity on another object. It is usually
// embedded into a test object as shown below: // embedded into a test object as shown below:

View File

@ -3,6 +3,7 @@ package mock
import ( import (
"errors" "errors"
"fmt" "fmt"
"path"
"reflect" "reflect"
"regexp" "regexp"
"runtime" "runtime"
@ -13,9 +14,13 @@ import (
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/pmezard/go-difflib/difflib" "github.com/pmezard/go-difflib/difflib"
"github.com/stretchr/objx" "github.com/stretchr/objx"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// regex for GCCGO functions
var gccgoRE = regexp.MustCompile(`\.pN\d+_`)
// TestingT is an interface wrapper around *testing.T // TestingT is an interface wrapper around *testing.T
type TestingT interface { type TestingT interface {
Logf(format string, args ...interface{}) Logf(format string, args ...interface{})
@ -109,7 +114,7 @@ func (c *Call) Return(returnArguments ...interface{}) *Call {
return c return c
} }
// Panic specifies if the functon call should fail and the panic message // Panic specifies if the function call should fail and the panic message
// //
// Mock.On("DoSomething").Panic("test panic") // Mock.On("DoSomething").Panic("test panic")
func (c *Call) Panic(msg string) *Call { func (c *Call) Panic(msg string) *Call {
@ -121,21 +126,21 @@ func (c *Call) Panic(msg string) *Call {
return c return c
} }
// Once indicates that that the mock should only return the value once. // Once indicates that the mock should only return the value once.
// //
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once() // Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once()
func (c *Call) Once() *Call { func (c *Call) Once() *Call {
return c.Times(1) return c.Times(1)
} }
// Twice indicates that that the mock should only return the value twice. // Twice indicates that the mock should only return the value twice.
// //
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice() // Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice()
func (c *Call) Twice() *Call { func (c *Call) Twice() *Call {
return c.Times(2) return c.Times(2)
} }
// Times indicates that that the mock should only return the indicated number // Times indicates that the mock should only return the indicated number
// of times. // of times.
// //
// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5) // Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5)
@ -197,12 +202,14 @@ func (c *Call) Maybe() *Call {
// Mock. // Mock.
// On("MyMethod", 1).Return(nil). // On("MyMethod", 1).Return(nil).
// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error")) // On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error"))
//
//go:noinline //go:noinline
func (c *Call) On(methodName string, arguments ...interface{}) *Call { func (c *Call) On(methodName string, arguments ...interface{}) *Call {
return c.Parent.On(methodName, arguments...) return c.Parent.On(methodName, arguments...)
} }
// Unset removes a mock handler from being called. // Unset removes a mock handler from being called.
//
// test.On("func", mock.Anything).Unset() // test.On("func", mock.Anything).Unset()
func (c *Call) Unset() *Call { func (c *Call) Unset() *Call {
var unlockOnce sync.Once var unlockOnce sync.Once
@ -218,16 +225,22 @@ func (c *Call) Unset() *Call {
foundMatchingCall := false foundMatchingCall := false
for i, call := range c.Parent.ExpectedCalls { // in-place filter slice for calls to be removed - iterate from 0'th to last skipping unnecessary ones
var index int // write index
for _, call := range c.Parent.ExpectedCalls {
if call.Method == c.Method { if call.Method == c.Method {
_, diffCount := call.Arguments.Diff(c.Arguments) _, diffCount := call.Arguments.Diff(c.Arguments)
if diffCount == 0 { if diffCount == 0 {
foundMatchingCall = true foundMatchingCall = true
// Remove from ExpectedCalls // Remove from ExpectedCalls - just skip it
c.Parent.ExpectedCalls = append(c.Parent.ExpectedCalls[:i], c.Parent.ExpectedCalls[i+1:]...) continue
} }
} }
c.Parent.ExpectedCalls[index] = call
index++
} }
// trim slice up to last copied index
c.Parent.ExpectedCalls = c.Parent.ExpectedCalls[:index]
if !foundMatchingCall { if !foundMatchingCall {
unlockOnce.Do(c.unlock) unlockOnce.Do(c.unlock)
@ -418,6 +431,10 @@ func callString(method string, arguments Arguments, includeArgumentValues bool)
if includeArgumentValues { if includeArgumentValues {
var argVals []string var argVals []string
for argIndex, arg := range arguments { for argIndex, arg := range arguments {
if _, ok := arg.(*FunctionalOptionsArgument); ok {
argVals = append(argVals, fmt.Sprintf("%d: %s", argIndex, arg))
continue
}
argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg)) argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg))
} }
argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t")) argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t"))
@ -441,9 +458,8 @@ func (m *Mock) Called(arguments ...interface{}) Arguments {
// For Ex: github_com_docker_libkv_store_mock.WatchTree.pN39_github_com_docker_libkv_store_mock.Mock // For Ex: github_com_docker_libkv_store_mock.WatchTree.pN39_github_com_docker_libkv_store_mock.Mock
// uses interface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree // uses interface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree
// With GCCGO we need to remove interface information starting from pN<dd>. // With GCCGO we need to remove interface information starting from pN<dd>.
re := regexp.MustCompile("\\.pN\\d+_") if gccgoRE.MatchString(functionPath) {
if re.MatchString(functionPath) { functionPath = gccgoRE.Split(functionPath, -1)[0]
functionPath = re.Split(functionPath, -1)[0]
} }
parts := strings.Split(functionPath, ".") parts := strings.Split(functionPath, ".")
functionName := parts[len(parts)-1] functionName := parts[len(parts)-1]
@ -460,7 +476,7 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen
found, call := m.findExpectedCall(methodName, arguments...) found, call := m.findExpectedCall(methodName, arguments...)
if found < 0 { if found < 0 {
// expected call found but it has already been called with repeatable times // expected call found, but it has already been called with repeatable times
if call != nil { if call != nil {
m.mutex.Unlock() m.mutex.Unlock()
m.fail("\nassert: mock: The method has been called over %d times.\n\tEither do one more Mock.On(\"%s\").Return(...), or remove extra call.\n\tThis call was unexpected:\n\t\t%s\n\tat: %s", call.totalCalls, methodName, callString(methodName, arguments, true), assert.CallerInfo()) m.fail("\nassert: mock: The method has been called over %d times.\n\tEither do one more Mock.On(\"%s\").Return(...), or remove extra call.\n\tThis call was unexpected:\n\t\t%s\n\tat: %s", call.totalCalls, methodName, callString(methodName, arguments, true), assert.CallerInfo())
@ -549,7 +565,7 @@ func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Argumen
Assertions Assertions
*/ */
type assertExpectationser interface { type assertExpectationiser interface {
AssertExpectations(TestingT) bool AssertExpectations(TestingT) bool
} }
@ -566,7 +582,7 @@ func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool {
t.Logf("Deprecated mock.AssertExpectationsForObjects(myMock.Mock) use mock.AssertExpectationsForObjects(myMock)") t.Logf("Deprecated mock.AssertExpectationsForObjects(myMock.Mock) use mock.AssertExpectationsForObjects(myMock)")
obj = m obj = m
} }
m := obj.(assertExpectationser) m := obj.(assertExpectationiser)
if !m.AssertExpectations(t) { if !m.AssertExpectations(t) {
t.Logf("Expectations didn't match for Mock: %+v", reflect.TypeOf(m)) t.Logf("Expectations didn't match for Mock: %+v", reflect.TypeOf(m))
return false return false
@ -592,9 +608,9 @@ func (m *Mock) AssertExpectations(t TestingT) bool {
satisfied, reason := m.checkExpectation(expectedCall) satisfied, reason := m.checkExpectation(expectedCall)
if !satisfied { if !satisfied {
failedExpectations++ failedExpectations++
}
t.Logf(reason) t.Logf(reason)
} }
}
if failedExpectations != 0 { if failedExpectations != 0 {
t.Errorf("FAIL: %d out of %d expectation(s) were met.\n\tThe code you are testing needs to make %d more call(s).\n\tat: %s", len(expectedCalls)-failedExpectations, len(expectedCalls), failedExpectations, assert.CallerInfo()) t.Errorf("FAIL: %d out of %d expectation(s) were met.\n\tThe code you are testing needs to make %d more call(s).\n\tat: %s", len(expectedCalls)-failedExpectations, len(expectedCalls), failedExpectations, assert.CallerInfo())
@ -744,17 +760,26 @@ const (
Anything = "mock.Anything" Anything = "mock.Anything"
) )
// AnythingOfTypeArgument is a string that contains the type of an argument // AnythingOfTypeArgument contains the type of an argument
// for use when type checking. Used in Diff and Assert. // for use when type checking. Used in Diff and Assert.
type AnythingOfTypeArgument string //
// Deprecated: this is an implementation detail that must not be used. Use [AnythingOfType] instead.
type AnythingOfTypeArgument = anythingOfTypeArgument
// AnythingOfType returns an AnythingOfTypeArgument object containing the // anythingOfTypeArgument is a string that contains the type of an argument
// name of the type to check for. Used in Diff and Assert. // for use when type checking. Used in Diff and Assert.
type anythingOfTypeArgument string
// AnythingOfType returns a special value containing the
// name of the type to check for. The type name will be matched against the type name returned by [reflect.Type.String].
//
// Used in Diff and Assert.
// //
// For example: // For example:
//
// Assert(t, AnythingOfType("string"), AnythingOfType("int")) // Assert(t, AnythingOfType("string"), AnythingOfType("int"))
func AnythingOfType(t string) AnythingOfTypeArgument { func AnythingOfType(t string) AnythingOfTypeArgument {
return AnythingOfTypeArgument(t) return anythingOfTypeArgument(t)
} }
// IsTypeArgument is a struct that contains the type of an argument // IsTypeArgument is a struct that contains the type of an argument
@ -774,6 +799,34 @@ func IsType(t interface{}) *IsTypeArgument {
return &IsTypeArgument{t: t} return &IsTypeArgument{t: t}
} }
// FunctionalOptionsArgument is a struct that contains the type and value of an functional option argument
// for use when type checking.
type FunctionalOptionsArgument struct {
value interface{}
}
// String returns the string representation of FunctionalOptionsArgument
func (f *FunctionalOptionsArgument) String() string {
var name string
tValue := reflect.ValueOf(f.value)
if tValue.Len() > 0 {
name = "[]" + reflect.TypeOf(tValue.Index(0).Interface()).String()
}
return strings.Replace(fmt.Sprintf("%#v", f.value), "[]interface {}", name, 1)
}
// FunctionalOptions returns an FunctionalOptionsArgument object containing the functional option type
// and the values to check of
//
// For example:
// Assert(t, FunctionalOptions("[]foo.FunctionalOption", foo.Opt1(), foo.Opt2()))
func FunctionalOptions(value ...interface{}) *FunctionalOptionsArgument {
return &FunctionalOptionsArgument{
value: value,
}
}
// argumentMatcher performs custom argument matching, returning whether or // argumentMatcher performs custom argument matching, returning whether or
// not the argument is matched by the expectation fixture function. // not the argument is matched by the expectation fixture function.
type argumentMatcher struct { type argumentMatcher struct {
@ -907,9 +960,9 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
differences++ differences++
output = fmt.Sprintf("%s\t%d: FAIL: %s not matched by %s\n", output, i, actualFmt, matcher) output = fmt.Sprintf("%s\t%d: FAIL: %s not matched by %s\n", output, i, actualFmt, matcher)
} }
} else if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() { } else if reflect.TypeOf(expected) == reflect.TypeOf((*anythingOfTypeArgument)(nil)).Elem() {
// type checking // type checking
if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) { if reflect.TypeOf(actual).Name() != string(expected.(anythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(anythingOfTypeArgument)) {
// not match // not match
differences++ differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actualFmt) output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actualFmt)
@ -920,6 +973,29 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
differences++ differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, reflect.TypeOf(t).Name(), reflect.TypeOf(actual).Name(), actualFmt) output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, reflect.TypeOf(t).Name(), reflect.TypeOf(actual).Name(), actualFmt)
} }
} else if reflect.TypeOf(expected) == reflect.TypeOf((*FunctionalOptionsArgument)(nil)) {
t := expected.(*FunctionalOptionsArgument).value
var name string
tValue := reflect.ValueOf(t)
if tValue.Len() > 0 {
name = "[]" + reflect.TypeOf(tValue.Index(0).Interface()).String()
}
tName := reflect.TypeOf(t).Name()
if name != reflect.TypeOf(actual).String() && tValue.Len() != 0 {
differences++
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, tName, reflect.TypeOf(actual).Name(), actualFmt)
} else {
if ef, af := assertOpts(t, actual); ef == "" && af == "" {
// match
output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, tName, tName)
} else {
// not match
differences++
output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, af, ef)
}
}
} else { } else {
// normal checking // normal checking
@ -1096,3 +1172,65 @@ var spewConfig = spew.ConfigState{
type tHelper interface { type tHelper interface {
Helper() Helper()
} }
func assertOpts(expected, actual interface{}) (expectedFmt, actualFmt string) {
expectedOpts := reflect.ValueOf(expected)
actualOpts := reflect.ValueOf(actual)
var expectedNames []string
for i := 0; i < expectedOpts.Len(); i++ {
expectedNames = append(expectedNames, funcName(expectedOpts.Index(i).Interface()))
}
var actualNames []string
for i := 0; i < actualOpts.Len(); i++ {
actualNames = append(actualNames, funcName(actualOpts.Index(i).Interface()))
}
if !assert.ObjectsAreEqual(expectedNames, actualNames) {
expectedFmt = fmt.Sprintf("%v", expectedNames)
actualFmt = fmt.Sprintf("%v", actualNames)
return
}
for i := 0; i < expectedOpts.Len(); i++ {
expectedOpt := expectedOpts.Index(i).Interface()
actualOpt := actualOpts.Index(i).Interface()
expectedFunc := expectedNames[i]
actualFunc := actualNames[i]
if expectedFunc != actualFunc {
expectedFmt = expectedFunc
actualFmt = actualFunc
return
}
ot := reflect.TypeOf(expectedOpt)
var expectedValues []reflect.Value
var actualValues []reflect.Value
if ot.NumIn() == 0 {
return
}
for i := 0; i < ot.NumIn(); i++ {
vt := ot.In(i).Elem()
expectedValues = append(expectedValues, reflect.New(vt))
actualValues = append(actualValues, reflect.New(vt))
}
reflect.ValueOf(expectedOpt).Call(expectedValues)
reflect.ValueOf(actualOpt).Call(actualValues)
for i := 0; i < ot.NumIn(); i++ {
if !assert.ObjectsAreEqual(expectedValues[i].Interface(), actualValues[i].Interface()) {
expectedFmt = fmt.Sprintf("%s %+v", expectedNames[i], expectedValues[i].Interface())
actualFmt = fmt.Sprintf("%s %+v", expectedNames[i], actualValues[i].Interface())
return
}
}
}
return "", ""
}
func funcName(opt interface{}) string {
n := runtime.FuncForPC(reflect.ValueOf(opt).Pointer()).Name()
return strings.TrimSuffix(path.Base(n), path.Ext(n))
}

View File

@ -32,6 +32,29 @@ func (i *TestExampleImplementation) TheExampleMethod(a, b, c int) (int, error) {
return args.Int(0), errors.New("Whoops") return args.Int(0), errors.New("Whoops")
} }
type options struct {
num int
str string
}
type OptionFn func(*options)
func OpNum(n int) OptionFn {
return func(o *options) {
o.num = n
}
}
func OpStr(s string) OptionFn {
return func(o *options) {
o.str = s
}
}
func (i *TestExampleImplementation) TheExampleMethodFunctionalOptions(x string, opts ...OptionFn) error {
args := i.Called(x, opts)
return args.Error(0)
}
//go:noinline //go:noinline
func (i *TestExampleImplementation) TheExampleMethod2(yesorno bool) { func (i *TestExampleImplementation) TheExampleMethod2(yesorno bool) {
i.Called(yesorno) i.Called(yesorno)
@ -569,6 +592,80 @@ func Test_Mock_UnsetIfAlreadyUnsetFails(t *testing.T) {
assert.Equal(t, 0, len(mockedService.ExpectedCalls)) assert.Equal(t, 0, len(mockedService.ExpectedCalls))
} }
func Test_Mock_UnsetByOnMethodSpec(t *testing.T) {
// make a test impl object
var mockedService = new(TestExampleImplementation)
mock1 := mockedService.
On("TheExampleMethod", 1, 2, 3).
Return(0, nil)
assert.Equal(t, 1, len(mockedService.ExpectedCalls))
mock1.On("TheExampleMethod", 1, 2, 3).
Return(0, nil).Unset()
assert.Equal(t, 0, len(mockedService.ExpectedCalls))
assert.Panics(t, func() {
mock1.Unset()
})
assert.Equal(t, 0, len(mockedService.ExpectedCalls))
}
func Test_Mock_UnsetByOnMethodSpecAmongOthers(t *testing.T) {
// make a test impl object
var mockedService = new(TestExampleImplementation)
_, filename, line, _ := runtime.Caller(0)
mock1 := mockedService.
On("TheExampleMethod", 1, 2, 3).
Return(0, nil).
On("TheExampleMethodVariadic", 1, 2, 3, 4, 5).Once().
Return(nil)
mock1.
On("TheExampleMethodFuncType", Anything).
Return(nil)
assert.Equal(t, 3, len(mockedService.ExpectedCalls))
mock1.On("TheExampleMethod", 1, 2, 3).
Return(0, nil).Unset()
assert.Equal(t, 2, len(mockedService.ExpectedCalls))
expectedCalls := []*Call{
{
Parent: &mockedService.Mock,
Method: "TheExampleMethodVariadic",
Repeatability: 1,
Arguments: []interface{}{1, 2, 3, 4, 5},
ReturnArguments: []interface{}{nil},
callerInfo: []string{fmt.Sprintf("%s:%d", filename, line+4)},
},
{
Parent: &mockedService.Mock,
Method: "TheExampleMethodFuncType",
Arguments: []interface{}{Anything},
ReturnArguments: []interface{}{nil},
callerInfo: []string{fmt.Sprintf("%s:%d", filename, line+7)},
},
}
assert.Equal(t, 2, len(mockedService.ExpectedCalls))
assert.Equal(t, expectedCalls, mockedService.ExpectedCalls)
}
func Test_Mock_Unset_WithFuncPanics(t *testing.T) {
// make a test impl object
var mockedService = new(TestExampleImplementation)
mock1 := mockedService.On("TheExampleMethod", 1)
mock1.Arguments = append(mock1.Arguments, func(string) error { return nil })
assert.Panics(t, func() {
mock1.Unset()
})
}
func Test_Mock_Return(t *testing.T) { func Test_Mock_Return(t *testing.T) {
// make a test impl object // make a test impl object
@ -1341,6 +1438,40 @@ func Test_Mock_AssertExpectationsCustomType(t *testing.T) {
} }
func Test_Mock_AssertExpectationsFunctionalOptionsType(t *testing.T) {
var mockedService = new(TestExampleImplementation)
mockedService.On("TheExampleMethodFunctionalOptions", "test", FunctionalOptions(OpNum(1), OpStr("foo"))).Return(nil).Once()
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
// make the call now
mockedService.TheExampleMethodFunctionalOptions("test", OpNum(1), OpStr("foo"))
// now assert expectations
assert.True(t, mockedService.AssertExpectations(tt))
}
func Test_Mock_AssertExpectationsFunctionalOptionsType_Empty(t *testing.T) {
var mockedService = new(TestExampleImplementation)
mockedService.On("TheExampleMethodFunctionalOptions", "test", FunctionalOptions()).Return(nil).Once()
tt := new(testing.T)
assert.False(t, mockedService.AssertExpectations(tt))
// make the call now
mockedService.TheExampleMethodFunctionalOptions("test")
// now assert expectations
assert.True(t, mockedService.AssertExpectations(tt))
}
func Test_Mock_AssertExpectations_With_Repeatability(t *testing.T) { func Test_Mock_AssertExpectations_With_Repeatability(t *testing.T) {
var mockedService = new(TestExampleImplementation) var mockedService = new(TestExampleImplementation)

View File

@ -1,9 +1,10 @@
// Package require implements the same assertions as the `assert` package but // Package require implements the same assertions as the `assert` package but
// stops test execution when a test fails. // stops test execution when a test fails.
// //
// Example Usage // # Example Usage
// //
// The following is a complete example using require in a standard test function: // The following is a complete example using require in a standard test function:
//
// import ( // import (
// "testing" // "testing"
// "github.com/stretchr/testify/require" // "github.com/stretchr/testify/require"
@ -18,7 +19,7 @@
// //
// } // }
// //
// Assertions // # Assertions
// //
// The `require` package have same global functions as in the `assert` package, // The `require` package have same global functions as in the `assert` package,
// but instead of returning a boolean result they call `t.FailNow()`. // but instead of returning a boolean result they call `t.FailNow()`.

View File

@ -1,7 +1,4 @@
/* // Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
package require package require
@ -195,7 +192,47 @@ func EqualErrorf(t TestingT, theError error, errString string, msg string, args
t.FailNow() t.FailNow()
} }
// EqualValues asserts that two objects are equal or convertable to the same types // EqualExportedValues asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// assert.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true
// assert.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false
func EqualExportedValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.EqualExportedValues(t, expected, actual, msgAndArgs...) {
return
}
t.FailNow()
}
// EqualExportedValuesf asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.EqualExportedValuesf(t, expected, actual, msg, args...) {
return
}
t.FailNow()
}
// EqualValues asserts that two objects are equal or convertible to the same types
// and equal. // and equal.
// //
// assert.EqualValues(t, uint32(123), int32(123)) // assert.EqualValues(t, uint32(123), int32(123))
@ -209,7 +246,7 @@ func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArg
t.FailNow() t.FailNow()
} }
// EqualValuesf asserts that two objects are equal or convertable to the same types // EqualValuesf asserts that two objects are equal or convertible to the same types
// and equal. // and equal.
// //
// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") // assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted")
@ -364,6 +401,62 @@ func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick t
t.FailNow() t.FailNow()
} }
// EventuallyWithT asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// assert.EventuallyWithT(t, func(c *assert.CollectT) {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.EventuallyWithT(t, condition, waitFor, tick, msgAndArgs...) {
return
}
t.FailNow()
}
// EventuallyWithTf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func EventuallyWithTf(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.EventuallyWithTf(t, condition, waitFor, tick, msg, args...) {
return
}
t.FailNow()
}
// Eventuallyf asserts that given condition will be met in waitFor time, // Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. // periodically checking target function each tick.
// //

View File

@ -1,7 +1,4 @@
/* // Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT.
* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen
* THIS FILE MUST NOT BE EDITED BY HAND
*/
package require package require
@ -156,7 +153,41 @@ func (a *Assertions) EqualErrorf(theError error, errString string, msg string, a
EqualErrorf(a.t, theError, errString, msg, args...) EqualErrorf(a.t, theError, errString, msg, args...)
} }
// EqualValues asserts that two objects are equal or convertable to the same types // EqualExportedValues asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// a.EqualExportedValues(S{1, 2}, S{1, 3}) => true
// a.EqualExportedValues(S{1, 2}, S{2, 3}) => false
func (a *Assertions) EqualExportedValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
EqualExportedValues(a.t, expected, actual, msgAndArgs...)
}
// EqualExportedValuesf asserts that the types of two objects are equal and their public
// fields are also equal. This is useful for comparing structs that have private fields
// that could potentially differ.
//
// type S struct {
// Exported int
// notExported int
// }
// a.EqualExportedValuesf(S{1, 2}, S{1, 3}, "error message %s", "formatted") => true
// a.EqualExportedValuesf(S{1, 2}, S{2, 3}, "error message %s", "formatted") => false
func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
EqualExportedValuesf(a.t, expected, actual, msg, args...)
}
// EqualValues asserts that two objects are equal or convertible to the same types
// and equal. // and equal.
// //
// a.EqualValues(uint32(123), int32(123)) // a.EqualValues(uint32(123), int32(123))
@ -167,7 +198,7 @@ func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAn
EqualValues(a.t, expected, actual, msgAndArgs...) EqualValues(a.t, expected, actual, msgAndArgs...)
} }
// EqualValuesf asserts that two objects are equal or convertable to the same types // EqualValuesf asserts that two objects are equal or convertible to the same types
// and equal. // and equal.
// //
// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") // a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted")
@ -289,6 +320,56 @@ func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, ti
Eventually(a.t, condition, waitFor, tick, msgAndArgs...) Eventually(a.t, condition, waitFor, tick, msgAndArgs...)
} }
// EventuallyWithT asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// a.EventuallyWithT(func(c *assert.CollectT) {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
EventuallyWithT(a.t, condition, waitFor, tick, msgAndArgs...)
}
// EventuallyWithTf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. In contrast to Eventually,
// it supplies a CollectT to the condition function, so that the condition
// function can use the CollectT to call other assertions.
// The condition is considered "met" if no errors are raised in a tick.
// The supplied CollectT collects all errors from one tick (if there are any).
// If the condition is not met before waitFor, the collected errors of
// the last tick are copied to t.
//
// externalValue := false
// go func() {
// time.Sleep(8*time.Second)
// externalValue = true
// }()
// a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") {
// // add assertions as needed; any assertion failure will fail the current tick
// assert.True(c, externalValue, "expected 'externalValue' to be true")
// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false")
func (a *Assertions) EventuallyWithTf(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
EventuallyWithTf(a.t, condition, waitFor, tick, msg, args...)
}
// Eventuallyf asserts that given condition will be met in waitFor time, // Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick. // periodically checking target function each tick.
// //

View File

@ -29,6 +29,7 @@
// Suite object has assertion methods. // Suite object has assertion methods.
// //
// A crude example: // A crude example:
//
// // Basic imports // // Basic imports
// import ( // import (
// "testing" // "testing"

View File

@ -7,6 +7,7 @@ import "testing"
type TestingSuite interface { type TestingSuite interface {
T() *testing.T T() *testing.T
SetT(*testing.T) SetT(*testing.T)
SetS(suite TestingSuite)
} }
// SetupAllSuite has a SetupSuite method, which will run before the // SetupAllSuite has a SetupSuite method, which will run before the
@ -51,3 +52,15 @@ type AfterTest interface {
type WithStats interface { type WithStats interface {
HandleStats(suiteName string, stats *SuiteInformation) HandleStats(suiteName string, stats *SuiteInformation)
} }
// SetupSubTest has a SetupSubTest method, which will run before each
// subtest in the suite.
type SetupSubTest interface {
SetupSubTest()
}
// TearDownSubTest has a TearDownSubTest method, which will run after
// each subtest in the suite have been run.
type TearDownSubTest interface {
TearDownSubTest()
}

View File

@ -22,9 +22,13 @@ var matchMethod = flag.String("testify.m", "", "regular expression to select tes
// retrieving the current *testing.T context. // retrieving the current *testing.T context.
type Suite struct { type Suite struct {
*assert.Assertions *assert.Assertions
mu sync.RWMutex mu sync.RWMutex
require *require.Assertions require *require.Assertions
t *testing.T t *testing.T
// Parent suite to have access to the implemented methods of parent struct
s TestingSuite
} }
// T retrieves the current *testing.T context. // T retrieves the current *testing.T context.
@ -43,6 +47,12 @@ func (suite *Suite) SetT(t *testing.T) {
suite.require = require.New(t) suite.require = require.New(t)
} }
// SetS needs to set the current test suite as parent
// to get access to the parent methods
func (suite *Suite) SetS(s TestingSuite) {
suite.s = s
}
// Require returns a require context for suite. // Require returns a require context for suite.
func (suite *Suite) Require() *require.Assertions { func (suite *Suite) Require() *require.Assertions {
suite.mu.Lock() suite.mu.Lock()
@ -85,7 +95,18 @@ func failOnPanic(t *testing.T, r interface{}) {
// Provides compatibility with go test pkg -run TestSuite/TestName/SubTestName. // Provides compatibility with go test pkg -run TestSuite/TestName/SubTestName.
func (suite *Suite) Run(name string, subtest func()) bool { func (suite *Suite) Run(name string, subtest func()) bool {
oldT := suite.T() oldT := suite.T()
defer suite.SetT(oldT)
if setupSubTest, ok := suite.s.(SetupSubTest); ok {
setupSubTest.SetupSubTest()
}
defer func() {
suite.SetT(oldT)
if tearDownSubTest, ok := suite.s.(TearDownSubTest); ok {
tearDownSubTest.TearDownSubTest()
}
}()
return oldT.Run(name, func(t *testing.T) { return oldT.Run(name, func(t *testing.T) {
suite.SetT(t) suite.SetT(t)
subtest() subtest()
@ -98,6 +119,7 @@ func Run(t *testing.T, suite TestingSuite) {
defer recoverAndFailOnPanic(t) defer recoverAndFailOnPanic(t)
suite.SetT(t) suite.SetT(t)
suite.SetS(suite)
var suiteSetupDone bool var suiteSetupDone bool

View File

@ -159,6 +159,8 @@ type SuiteTester struct {
TestTwoRunCount int TestTwoRunCount int
TestSubtestRunCount int TestSubtestRunCount int
NonTestMethodRunCount int NonTestMethodRunCount int
SetupSubTestRunCount int
TearDownSubTestRunCount int
SuiteNameBefore []string SuiteNameBefore []string
TestNameBefore []string TestNameBefore []string
@ -255,6 +257,14 @@ func (suite *SuiteTester) TestSubtest() {
} }
} }
func (suite *SuiteTester) TearDownSubTest() {
suite.TearDownSubTestRunCount++
}
func (suite *SuiteTester) SetupSubTest() {
suite.SetupSubTestRunCount++
}
type SuiteSkipTester struct { type SuiteSkipTester struct {
// Include our basic suite logic. // Include our basic suite logic.
Suite Suite
@ -336,6 +346,9 @@ func TestRunSuite(t *testing.T) {
assert.Equal(t, suiteTester.TestTwoRunCount, 1) assert.Equal(t, suiteTester.TestTwoRunCount, 1)
assert.Equal(t, suiteTester.TestSubtestRunCount, 1) assert.Equal(t, suiteTester.TestSubtestRunCount, 1)
assert.Equal(t, suiteTester.TearDownSubTestRunCount, 2)
assert.Equal(t, suiteTester.SetupSubTestRunCount, 2)
// Methods that don't match the test method identifier shouldn't // Methods that don't match the test method identifier shouldn't
// have been run at all. // have been run at all.
assert.Equal(t, suiteTester.NonTestMethodRunCount, 0) assert.Equal(t, suiteTester.NonTestMethodRunCount, 0)