Repository: stretchr/testify (go)

Issue:
If a matcher panics and AssertExpectations has been deferred, a hang occurs
In the code below, the test case TestMatcherPanic hangs. To reproduce the hang, two conditions must be met:
1. AssertExpectations is deferred
2. An argument matcher panics

```
import (
	"testing"

	"github.com/stretchr/testify/mock"
)

type Mock struct {
	mock.Mock
}

func (_m *Mock) F() {
	_m.Called()
}

func TestMatcherPanic(t *testing.T) {
  m := &Mock{}
  defer m.AssertExpectations(t)

  matcher := mock.MatchedBy(func(_ interface{}) bool {
    panic("test")
  })
  m.On("F", matcher)

  m.F()
}
```

Pull request:
Mock can be deadlocked by a panic
## Summary
If an argumentMatcher function panics and AssertExpectations is deferred then the test would deadlock.

## Changes
* Capture panics from argumentMatcher functions and show them to the user as a difference

## Motivation
It was reported that when AssertExpectations() is deferred and an argumentMatcher function panics, that the test will deadlock.

Example from linked issue:
```go
import (
	"testing"

	"github.com/stretchr/testify/mock"
)

type Mock struct {
	mock.Mock
}

func (_m *Mock) F() {
	_m.Called()
}

func TestMatcherPanic(t *testing.T) {
  m := &Mock{}
  defer m.AssertExpectations(t)

  matcher := mock.MatchedBy(func(_ interface{}) bool {
    panic("my hovercraft is full of eels")
  })
  m.On("F", matcher)

  m.F()
}
```

After this change the output will now look like:
```
--- FAIL: TestMatcherPanic (0.00s)
    /Users/alandaws/src/github.com/brackendawson/kata/mock.go:264: 
        
        mock: Unexpected Method Call
        -----------------------------
        
        F()
        		
        
        The closest call I have is: 
        
        F(mock.argumentMatcher)
        		0: mock.argumentMatcher{fn:reflect.Value{typ:(*reflect.rtype)(0x126a520), ptr:(unsafe.Pointer)(0x12b5f88), flag:0x13}}
        
        Provided 1 arguments, mocked for 0 arguments
        Diff: 0: FAIL:  panic in argument matcher: my hovercraft is full of eels not matched by func(interface {}) bool
    /Users/alandaws/src/github.com/brackendawson/kata/panic.go:661: FAIL:	F(mock.argumentMatcher)
        		at: [kata_test.go:25]
    /Users/alandaws/src/github.com/brackendawson/kata/panic.go:661: FAIL: 0 out of 1 expectation(s) were met.
        	The code you are testing needs to make 1 more call(s).
        	at: [panic.go:661 testing.go:756 kata_test.go:14 kata_test.go:27]
```

I wonder if that is obvious enough?

## Related issues
fixes #1155 


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index cd3367d..12a23ea 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -6,11 +6,11 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        go_version: ["1.17.6", "1.16.5", "1.15.13"]
+        go_version: ["1.18.1", "1.17.6", "1.16.5"]
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
       - name: Setup Go
-        uses: actions/setup-go@v2
+        uses: actions/setup-go@v3.2.0
         with:
           go-version: ${{ matrix.go_version }}
       - run: ./.ci.gogenerate.sh    
diff --git a/README.md b/README.md
index c78250e..ce6d3de 100644
--- a/README.md
+++ b/README.md
@@ -190,6 +190,31 @@ func TestSomethingWithPlaceholder(t *testing.T) {
 
 
 }
+
+// TestSomethingElse2 is a third example that shows how you can use
+// the Unset method to cleanup handlers and then add new ones.
+func TestSomethingElse2(t *testing.T) {
+
+  // create an instance of our test object
+  testObj := new(MyMockedObject)
+
+  // setup expectations with a placeholder in the argument list
+  mockCall := testObj.On("DoSomething", mock.Anything).Return(true, nil)
+
+  // call the code we are testing
+  targetFuncThatDoesSomethingWithObj(testObj)
+
+  // assert that the expectations were met
+  testObj.AssertExpectations(t)
+
+  // remove the handler now so we can add another one that takes precedence
+  mockCall.Unset()
+
+  // return false now instead of true
+  testObj.On("DoSomething", mock.Anything).Return(false, nil)
+
+  testObj.AssertExpectations(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).
@@ -323,7 +348,7 @@ To update Testify to the latest version, use `go get -u github.com/stretchr/test
 Supported go versions
 ==================
 
-We support the three major Go versions, which are 1.13, 1.14 and 1.15 at the moment.
+We currently support the most recent major Go versions from 1.13 onward.
 
 ------
 
diff --git a/assert/assertion_compare.go b/assert/assertion_compare.go
index 718d582..95d8e59 100644
--- a/assert/assertion_compare.go
+++ b/assert/assertion_compare.go
@@ -1,8 +1,10 @@
 package assert
 
 import (
+	"bytes"
 	"fmt"
 	"reflect"
+	"time"
 )
 
 type CompareType int
@@ -30,6 +32,9 @@ var (
 	float64Type = reflect.TypeOf(float64(1))
 
 	stringType = reflect.TypeOf("")
+
+	timeType  = reflect.TypeOf(time.Time{})
+	bytesType = reflect.TypeOf([]byte{})
 )
 
 func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
@@ -299,6 +304,47 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 				return compareLess, true
 			}
 		}
+	// Check for known struct types we can check for compare results.
+	case reflect.Struct:
+		{
+			// All structs enter here. We're not interested in most types.
+			if !canConvert(obj1Value, timeType) {
+				break
+			}
+
+			// time.Time can compared!
+			timeObj1, ok := obj1.(time.Time)
+			if !ok {
+				timeObj1 = obj1Value.Convert(timeType).Interface().(time.Time)
+			}
+
+			timeObj2, ok := obj2.(time.Time)
+			if !ok {
+				timeObj2 = obj2Value.Convert(timeType).Interface().(time.Time)
+			}
+
+			return compare(timeObj1.UnixNano(), timeObj2.UnixNano(), reflect.Int64)
+		}
+	case reflect.Slice:
+		{
+			// We only care about the []byte type.
+			if !canConvert(obj1Value, bytesType) {
+				break
+			}
+
+			// []byte can be compared!
+			bytesObj1, ok := obj1.([]byte)
+			if !ok {
+				bytesObj1 = obj1Value.Convert(bytesType).Interface().([]byte)
+
+			}
+			bytesObj2, ok := obj2.([]byte)
+			if !ok {
+				bytesObj2 = obj2Value.Convert(bytesType).Interface().([]byte)
+			}
+
+			return CompareType(bytes.Compare(bytesObj1, bytesObj2)), true
+		}
 	}
 
 	return compareEqual, false
diff --git a/assert/assertion_compare_can_convert.go b/assert/assertion_compare_can_convert.go
new file mode 100644
index 0000000..da86790
--- /dev/null
+++ b/assert/assertion_compare_can_convert.go
@@ -0,0 +1,16 @@
+//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)
+}
diff --git a/assert/assertion_compare_legacy.go b/assert/assertion_compare_legacy.go
new file mode 100644
index 0000000..1701af2
--- /dev/null
+++ b/assert/assertion_compare_legacy.go
@@ -0,0 +1,16 @@
+//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
+}
diff --git a/assert/assertions.go b/assert/assertions.go
index 230118b..580fdea 100644
--- a/assert/assertions.go
+++ b/assert/assertions.go
@@ -563,16 +563,17 @@ func isEmpty(object interface{}) bool {
 
 	switch objValue.Kind() {
 	// collection types are empty when they have no element
-	case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice:
+	case reflect.Chan, reflect.Map, reflect.Slice:
 		return objValue.Len() == 0
-		// pointers are empty if nil or if the value they point to is empty
+	// pointers are empty if nil or if the value they point to is empty
 	case reflect.Ptr:
 		if objValue.IsNil() {
 			return true
 		}
 		deref := objValue.Elem().Interface()
 		return isEmpty(deref)
-		// for all other types, compare against the zero value
+	// for all other types, compare against the zero value
+	// array types are empty when they match their zero-initialized state
 	default:
 		zero := reflect.Zero(objValue.Type())
 		return reflect.DeepEqual(object, zero.Interface())
@@ -1004,27 +1005,21 @@ func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool {
 type PanicTestFunc func()
 
 // didPanic returns true if the function passed to it panics. Otherwise, it returns false.
-func didPanic(f PanicTestFunc) (bool, interface{}, string) {
-
-	didPanic := false
-	var message interface{}
-	var stack string
-	func() {
-
-		defer func() {
-			if message = recover(); message != nil {
-				didPanic = true
-				stack = string(debug.Stack())
-			}
-		}()
-
-		// call the target function
-		f()
+func didPanic(f PanicTestFunc) (didPanic bool, message interface{}, stack string) {
+	didPanic = true
 
+	defer func() {
+		message = recover()
+		if didPanic {
+			stack = string(debug.Stack())
+		}
 	}()
 
-	return didPanic, message, stack
+	// call the target function
+	f()
+	didPanic = false
 
+	return
 }
 
 // Panics asserts that the code inside the specified PanicTestFunc panics.
diff --git a/go.mod b/go.mod
index ed0b50b..c5d3e88 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,8 @@ module github.com/stretchr/testify
 go 1.13
 
 require (
-	github.com/davecgh/go-spew v1.1.0
+	github.com/davecgh/go-spew v1.1.1
 	github.com/pmezard/go-difflib v1.0.0
-	github.com/stretchr/objx v0.1.0
-	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
+	github.com/stretchr/objx v0.4.0
+	gopkg.in/yaml.v3 v3.0.1
 )
diff --git a/go.sum b/go.sum
index e23c325..3be384b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,10 +1,14 @@
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 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/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 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/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/mock/mock.go b/mock/mock.go
index 853da6c..ff8111a 100644
--- a/mock/mock.go
+++ b/mock/mock.go
@@ -199,6 +199,43 @@ func (c *Call) On(methodName string, arguments ...interface{}) *Call {
 	return c.Parent.On(methodName, arguments...)
 }
 
+// Unset removes a mock handler from being called.
+//    test.On("func", mock.Anything).Unset()
+func (c *Call) Unset() *Call {
+	var unlockOnce sync.Once
+
+	for _, arg := range c.Arguments {
+		if v := reflect.ValueOf(arg); v.Kind() == reflect.Func {
+			panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg))
+		}
+	}
+
+	c.lock()
+	defer unlockOnce.Do(c.unlock)
+
+	foundMatchingCall := false
+
+	for i, call := range c.Parent.ExpectedCalls {
+		if call.Method == c.Method {
+			_, diffCount := call.Arguments.Diff(c.Arguments)
+			if diffCount == 0 {
+				foundMatchingCall = true
+				// Remove from ExpectedCalls
+				c.Parent.ExpectedCalls = append(c.Parent.ExpectedCalls[:i], c.Parent.ExpectedCalls[i+1:]...)
+			}
+		}
+	}
+
+	if !foundMatchingCall {
+		unlockOnce.Do(c.unlock)
+		c.Parent.fail("\n\nmock: Could not find expected call\n-----------------------------\n\n%s\n\n",
+			callString(c.Method, c.Arguments, true),
+		)
+	}
+
+	return c
+}
+
 // Mock is the workhorse used to track activity on another object.
 // For an example of its usage, refer to the "Example Usage" section at the top
 // of this document.
@@ -218,7 +255,7 @@ type Mock struct {
 	// this data completely allowing you to do whatever you like with it.
 	testData objx.Map
 
-	mutex sync.Mutex
+	mutex *sync.Mutex
 }
 
 // String provides a %v format string for Mock.
@@ -232,7 +269,6 @@ func (m *Mock) String() string {
 // TestData holds any data that might be useful for testing.  Testify ignores
 // this data completely allowing you to do whatever you like with it.
 func (m *Mock) TestData() objx.Map {
-
 	if m.testData == nil {
 		m.testData = make(objx.Map)
 	}
@@ -246,6 +282,10 @@ func (m *Mock) TestData() objx.Map {
 
 // Test sets the test struct variable of the mock object
 func (m *Mock) Test(t TestingT) {
+	if m.mutex == nil {
+		m.mutex = &sync.Mutex{}
+	}
+
 	m.mutex.Lock()
 	defer m.mutex.Unlock()
 	m.test = t
@@ -276,6 +316,9 @@ func (m *Mock) On(methodName string, arguments ...interface{}) *Call {
 		}
 	}
 
+	// Since we start mocks with the .On() function, m.mutex should be reset
+	m.mutex = &sync.Mutex{}
+
 	m.mutex.Lock()
 	defer m.mutex.Unlock()
 	c := newCall(m, methodName, assert.CallerInfo(), arguments...)
@@ -354,7 +397,6 @@ func (m *Mock) findClosestCall(method string, arguments ...interface{}) (*Call,
 }
 
 func callString(method string, arguments Arguments, includeArgumentValues bool) string {
-
 	var argValsString string
 	if includeArgumentValues {
 		var argVals []string
@@ -378,10 +420,10 @@ func (m *Mock) Called(arguments ...interface{}) Arguments {
 		panic("Couldn't get the caller information")
 	}
 	functionPath := runtime.FuncForPC(pc).Name()
-	//Next four lines are required to use GCCGO function naming conventions.
-	//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
-	//With GCCGO we need to remove interface information starting from pN<dd>.
+	// Next four lines are required to use GCCGO function naming conventions.
+	// 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
+	// With GCCGO we need to remove interface information starting from pN<dd>.
 	re := regexp.MustCompile("\\.pN\\d+_")
 	if re.MatchString(functionPath) {
 		functionPath = re.Split(functionPath, -1)[0]
@@ -397,7 +439,7 @@ func (m *Mock) Called(arguments ...interface{}) Arguments {
 // If Call.WaitFor is set, blocks until the channel is closed or receives a message.
 func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Arguments {
 	m.mutex.Lock()
-	//TODO: could combine expected and closes in single loop
+	// TODO: could combine expected and closes in single loop
 	found, call := m.findExpectedCall(methodName, arguments...)
 
 	if found < 0 {
@@ -503,6 +545,10 @@ func (m *Mock) AssertExpectations(t TestingT) bool {
 	if h, ok := t.(tHelper); ok {
 		h.Helper()
 	}
+	if m.mutex == nil {
+		m.mutex = &sync.Mutex{}
+	}
+
 	m.mutex.Lock()
 	defer m.mutex.Unlock()
 	var somethingMissing bool
@@ -781,12 +827,12 @@ func (args Arguments) Is(objects ...interface{}) bool {
 //
 // Returns the diff string and number of differences found.
 func (args Arguments) Diff(objects []interface{}) (string, int) {
-	//TODO: could return string as error and nil for No difference
+	// TODO: could return string as error and nil for No difference
 
-	var output = "\n"
+	output := "\n"
 	var differences int
 
-	var maxArgCount = len(args)
+	maxArgCount := len(args)
 	if len(objects) > maxArgCount {
 		maxArgCount = len(objects)
 	}
@@ -812,21 +858,28 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
 		}
 
 		if matcher, ok := expected.(argumentMatcher); ok {
-			if matcher.Matches(actual) {
+			var matches bool
+			func() {
+				defer func() {
+					if r := recover(); r != nil {
+						actualFmt = fmt.Sprintf("panic in argument matcher: %v", r)
+					}
+				}()
+				matches = matcher.Matches(actual)
+			}()
+			if matches {
 				output = fmt.Sprintf("%s\t%d: PASS:  %s matched by %s\n", output, i, actualFmt, matcher)
 			} else {
 				differences++
 				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() {
-
 			// type checking
 			if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) {
 				// not match
 				differences++
 				output = fmt.Sprintf("%s\t%d: FAIL:  type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actualFmt)
 			}
-
 		} else if reflect.TypeOf(expected) == reflect.TypeOf((*IsTypeArgument)(nil)) {
 			t := expected.(*IsTypeArgument).t
 			if reflect.TypeOf(t) != reflect.TypeOf(actual) {
@@ -834,7 +887,6 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
 				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 {
-
 			// normal checking
 
 			if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) {
@@ -854,7 +906,6 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
 	}
 
 	return output, differences
-
 }
 
 // Assert compares the arguments with the specified objects and fails if
@@ -876,7 +927,6 @@ func (args Arguments) Assert(t TestingT, objects ...interface{}) bool {
 	t.Errorf("%sArguments do not match.", assert.CallerInfo())
 
 	return false
-
 }
 
 // String gets the argument at the specified index. Panics if there is no argument, or
@@ -885,7 +935,6 @@ func (args Arguments) Assert(t TestingT, objects ...interface{}) bool {
 // If no index is provided, String() returns a complete string representation
 // of the arguments.
 func (args Arguments) String(indexOrNil ...int) string {
-
 	if len(indexOrNil) == 0 {
 		// normal String() method - return a string representation of the args
 		var argsStr []string
@@ -895,7 +944,7 @@ func (args Arguments) String(indexOrNil ...int) string {
 		return strings.Join(argsStr, ",")
 	} else if len(indexOrNil) == 1 {
 		// Index has been specified - get the argument at that index
-		var index = indexOrNil[0]
+		index := indexOrNil[0]
 		var s string
 		var ok bool
 		if s, ok = args.Get(index).(string); !ok {
@@ -905,7 +954,6 @@ func (args Arguments) String(indexOrNil ...int) string {
 	}
 
 	panic(fmt.Sprintf("assert: arguments: Wrong number of arguments passed to String.  Must be 0 or 1, not %d", len(indexOrNil)))
-
 }
 
 // Int gets the argument at the specified index. Panics if there is no argument, or
diff --git a/suite/suite.go b/suite/suite.go
index b9b5d1c..8955918 100644
--- a/suite/suite.go
+++ b/suite/suite.go
@@ -7,6 +7,7 @@ import (
 	"reflect"
 	"regexp"
 	"runtime/debug"
+	"sync"
 	"testing"
 	"time"
 
@@ -21,17 +22,22 @@ var matchMethod = flag.String("testify.m", "", "regular expression to select tes
 // retrieving the current *testing.T context.
 type Suite struct {
 	*assert.Assertions
+	mu      sync.RWMutex
 	require *require.Assertions
 	t       *testing.T
 }
 
 // T retrieves the current *testing.T context.
 func (suite *Suite) T() *testing.T {
+	suite.mu.RLock()
+	defer suite.mu.RUnlock()
 	return suite.t
 }
 
 // SetT sets the current *testing.T context.
 func (suite *Suite) SetT(t *testing.T) {
+	suite.mu.Lock()
+	defer suite.mu.Unlock()
 	suite.t = t
 	suite.Assertions = assert.New(t)
 	suite.require = require.New(t)
@@ -39,6 +45,8 @@ func (suite *Suite) SetT(t *testing.T) {
 
 // Require returns a require context for suite.
 func (suite *Suite) Require() *require.Assertions {
+	suite.mu.Lock()
+	defer suite.mu.Unlock()
 	if suite.require == nil {
 		suite.require = require.New(suite.T())
 	}
@@ -51,14 +59,20 @@ func (suite *Suite) Require() *require.Assertions {
 // assert.Assertions with require.Assertions), this method is provided so you
 // can call `suite.Assert().NoError()`.
 func (suite *Suite) Assert() *assert.Assertions {
+	suite.mu.Lock()
+	defer suite.mu.Unlock()
 	if suite.Assertions == nil {
 		suite.Assertions = assert.New(suite.T())
 	}
 	return suite.Assertions
 }
 
-func failOnPanic(t *testing.T) {
+func recoverAndFailOnPanic(t *testing.T) {
 	r := recover()
+	failOnPanic(t, r)
+}
+
+func failOnPanic(t *testing.T, r interface{}) {
 	if r != nil {
 		t.Errorf("test panicked: %v\n%s", r, debug.Stack())
 		t.FailNow()
@@ -81,7 +95,7 @@ func (suite *Suite) Run(name string, subtest func()) bool {
 // Run takes a testing suite and runs all of the tests attached
 // to it.
 func Run(t *testing.T, suite TestingSuite) {
-	defer failOnPanic(t)
+	defer recoverAndFailOnPanic(t)
 
 	suite.SetT(t)
 
@@ -126,10 +140,12 @@ func Run(t *testing.T, suite TestingSuite) {
 			F: func(t *testing.T) {
 				parentT := suite.T()
 				suite.SetT(t)
-				defer failOnPanic(t)
+				defer recoverAndFailOnPanic(t)
 				defer func() {
+					r := recover()
+
 					if stats != nil {
-						passed := !t.Failed()
+						passed := !t.Failed() && r == nil
 						stats.end(method.Name, passed)
 					}
 
@@ -142,6 +158,7 @@ func Run(t *testing.T, suite TestingSuite) {
 					}
 
 					suite.SetT(parentT)
+					failOnPanic(t, r)
 				}()
 
 				if setupTestSuite, ok := suite.(SetupTestSuite); ok {


Test patch:
diff --git a/assert/assertion_compare_go1.17_test.go b/assert/assertion_compare_go1.17_test.go
new file mode 100644
index 0000000..53e01ed
--- /dev/null
+++ b/assert/assertion_compare_go1.17_test.go
@@ -0,0 +1,182 @@
+//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")
+	}
+}
diff --git a/assert/assertion_compare_test.go b/assert/assertion_compare_test.go
index 667dbf7..a38d880 100644
--- a/assert/assertion_compare_test.go
+++ b/assert/assertion_compare_test.go
@@ -59,7 +59,8 @@ func TestCompare(t *testing.T) {
 		}
 
 		if resLess != compareLess {
-			t.Errorf("object less should be less than greater for type " + currCase.cType)
+			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())
diff --git a/assert/assertions_test.go b/assert/assertions_test.go
index 0b5b2c1..7a35a64 100644
--- a/assert/assertions_test.go
+++ b/assert/assertions_test.go
@@ -923,10 +923,18 @@ func TestCondition(t *testing.T) {
 
 func TestDidPanic(t *testing.T) {
 
-	if funcDidPanic, _, _ := didPanic(func() {
-		panic("Panic!")
-	}); !funcDidPanic {
-		t.Error("didPanic should return true")
+	const panicMsg = "Panic!"
+
+	if funcDidPanic, msg, _ := didPanic(func() {
+		panic(panicMsg)
+	}); !funcDidPanic || msg != panicMsg {
+		t.Error("didPanic should return true, panicMsg")
+	}
+
+	if funcDidPanic, msg, _ := didPanic(func() {
+		panic(nil)
+	}); !funcDidPanic || msg != nil {
+		t.Error("didPanic should return true, nil")
 	}
 
 	if funcDidPanic, _, _ := didPanic(func() {
@@ -963,6 +971,12 @@ func TestPanicsWithValue(t *testing.T) {
 		t.Error("PanicsWithValue should return true")
 	}
 
+	if !PanicsWithValue(mockT, nil, func() {
+		panic(nil)
+	}) {
+		t.Error("PanicsWithValue should return true")
+	}
+
 	if PanicsWithValue(mockT, "Panic!", func() {
 	}) {
 		t.Error("PanicsWithValue should return false")
@@ -1131,6 +1145,7 @@ func Test_isEmpty(t *testing.T) {
 	True(t, isEmpty(new(time.Time)))
 	True(t, isEmpty(time.Time{}))
 	True(t, isEmpty(make(chan struct{})))
+	True(t, isEmpty([1]int{}))
 	False(t, isEmpty("something"))
 	False(t, isEmpty(errors.New("something")))
 	False(t, isEmpty([]string{"something"}))
@@ -1138,7 +1153,7 @@ func Test_isEmpty(t *testing.T) {
 	False(t, isEmpty(true))
 	False(t, isEmpty(map[string]string{"Hello": "World"}))
 	False(t, isEmpty(chWithValue))
-
+	False(t, isEmpty([1]int{42}))
 }
 
 func TestEmpty(t *testing.T) {
@@ -1172,6 +1187,7 @@ func TestEmpty(t *testing.T) {
 	True(t, Empty(mockT, TStruct{}), "struct with zero values is empty")
 	True(t, Empty(mockT, TString("")), "empty aliased string is empty")
 	True(t, Empty(mockT, sP), "ptr to nil value is empty")
+	True(t, Empty(mockT, [1]int{}), "array is state")
 
 	False(t, Empty(mockT, "something"), "Non Empty string is not empty")
 	False(t, Empty(mockT, errors.New("something")), "Non nil object is not empty")
@@ -1182,6 +1198,7 @@ func TestEmpty(t *testing.T) {
 	False(t, Empty(mockT, TStruct{x: 1}), "struct with initialized values is empty")
 	False(t, Empty(mockT, TString("abc")), "non-empty aliased string is empty")
 	False(t, Empty(mockT, xP), "ptr to non-nil value is not empty")
+	False(t, Empty(mockT, [1]int{42}), "array is not state")
 }
 
 func TestNotEmpty(t *testing.T) {
@@ -1196,6 +1213,7 @@ func TestNotEmpty(t *testing.T) {
 	False(t, NotEmpty(mockT, 0), "Zero int value is empty")
 	False(t, NotEmpty(mockT, false), "False value is empty")
 	False(t, NotEmpty(mockT, make(chan struct{})), "Channel without values is empty")
+	False(t, NotEmpty(mockT, [1]int{}), "array is state")
 
 	True(t, NotEmpty(mockT, "something"), "Non Empty string is not empty")
 	True(t, NotEmpty(mockT, errors.New("something")), "Non nil object is not empty")
@@ -1203,6 +1221,7 @@ func TestNotEmpty(t *testing.T) {
 	True(t, NotEmpty(mockT, 1), "Non-zero int value is not empty")
 	True(t, NotEmpty(mockT, true), "True value is not empty")
 	True(t, NotEmpty(mockT, chWithValue), "Channel with values is not empty")
+	True(t, NotEmpty(mockT, [1]int{42}), "array is not state")
 }
 
 func Test_getLen(t *testing.T) {
diff --git a/mock/mock_test.go b/mock/mock_test.go
index 91828f8..06b2501 100644
--- a/mock/mock_test.go
+++ b/mock/mock_test.go
@@ -233,6 +233,32 @@ func Test_Mock_On_WithIntArgMatcher(t *testing.T) {
 	})
 }
 
+func Test_Mock_On_WithArgMatcherThatPanics(t *testing.T) {
+	var mockedService TestExampleImplementation
+
+	mockedService.On("TheExampleMethod2", MatchedBy(func(_ interface{}) bool {
+		panic("try to lock mockedService")
+	})).Return()
+
+	defer func() {
+		assertedExpectations := make(chan struct{})
+		go func() {
+			tt := new(testing.T)
+			mockedService.AssertExpectations(tt)
+			close(assertedExpectations)
+		}()
+		select {
+		case <-assertedExpectations:
+		case <-time.After(time.Second):
+			t.Fatal("AssertExpectations() deadlocked, did the panic leave mockedService locked?")
+		}
+	}()
+
+	assert.Panics(t, func() {
+		mockedService.TheExampleMethod2(false)
+	})
+}
+
 func TestMock_WithTest(t *testing.T) {
 	var (
 		mockedService TestExampleImplementation
@@ -462,6 +488,87 @@ func Test_Mock_On_WithFuncTypeArg(t *testing.T) {
 	})
 }
 
+func Test_Mock_Unset(t *testing.T) {
+	// make a test impl object
+	var mockedService = new(TestExampleImplementation)
+
+	call := mockedService.
+		On("TheExampleMethodFuncType", "argA").
+		Return("blah")
+
+	found, foundCall := mockedService.findExpectedCall("TheExampleMethodFuncType", "argA")
+	require.NotEqual(t, -1, found)
+	require.Equal(t, foundCall, call)
+
+	call.Unset()
+
+	found, foundCall = mockedService.findExpectedCall("TheExampleMethodFuncType", "argA")
+	require.Equal(t, -1, found)
+
+	var expectedCall *Call
+	require.Equal(t, expectedCall, foundCall)
+
+	fn := func(string) error { return nil }
+	assert.Panics(t, func() {
+		mockedService.TheExampleMethodFuncType(fn)
+	})
+}
+
+// Since every time you call On it creates a new object
+// the last time you call Unset it will only unset the last call
+func Test_Mock_Chained_UnsetOnlyUnsetsLastCall(t *testing.T) {
+	// make a test impl object
+	var mockedService = new(TestExampleImplementation)
+
+	// determine our current line number so we can assert the expected calls callerInfo properly
+	_, _, line, _ := runtime.Caller(0)
+	mockedService.
+		On("TheExampleMethod1", 1, 1).
+		Return(0).
+		On("TheExampleMethod2", 2, 2).
+		On("TheExampleMethod3", 3, 3, 3).
+		Return(nil).
+		Unset()
+
+	expectedCalls := []*Call{
+		{
+			Parent:          &mockedService.Mock,
+			Method:          "TheExampleMethod1",
+			Arguments:       []interface{}{1, 1},
+			ReturnArguments: []interface{}{0},
+			callerInfo:      []string{fmt.Sprintf("mock_test.go:%d", line+2)},
+		},
+		{
+			Parent:          &mockedService.Mock,
+			Method:          "TheExampleMethod2",
+			Arguments:       []interface{}{2, 2},
+			ReturnArguments: []interface{}{},
+			callerInfo:      []string{fmt.Sprintf("mock_test.go:%d", line+4)},
+		},
+	}
+	assert.Equal(t, 2, len(expectedCalls))
+	assert.Equal(t, expectedCalls, mockedService.ExpectedCalls)
+}
+
+func Test_Mock_UnsetIfAlreadyUnsetFails(t *testing.T) {
+	// make a test impl object
+	var mockedService = new(TestExampleImplementation)
+
+	mock1 := mockedService.
+		On("TheExampleMethod1", 1, 1).
+		Return(1)
+
+	assert.Equal(t, 1, len(mockedService.ExpectedCalls))
+	mock1.Unset()
+	assert.Equal(t, 0, len(mockedService.ExpectedCalls))
+
+	assert.Panics(t, func() {
+		mock1.Unset()
+	})
+
+	assert.Equal(t, 0, len(mockedService.ExpectedCalls))
+}
+
 func Test_Mock_Return(t *testing.T) {
 
 	// make a test impl object
@@ -915,6 +1022,7 @@ func Test_AssertExpectationsForObjects_Helper(t *testing.T) {
 	var mockedService1 = new(TestExampleImplementation)
 	var mockedService2 = new(TestExampleImplementation)
 	var mockedService3 = new(TestExampleImplementation)
+	var mockedService4 = new(TestExampleImplementation) // No expectations does not cause a panic
 
 	mockedService1.On("Test_AssertExpectationsForObjects_Helper", 1).Return()
 	mockedService2.On("Test_AssertExpectationsForObjects_Helper", 2).Return()
@@ -924,8 +1032,8 @@ func Test_AssertExpectationsForObjects_Helper(t *testing.T) {
 	mockedService2.Called(2)
 	mockedService3.Called(3)
 
-	assert.True(t, AssertExpectationsForObjects(t, &mockedService1.Mock, &mockedService2.Mock, &mockedService3.Mock))
-	assert.True(t, AssertExpectationsForObjects(t, mockedService1, mockedService2, mockedService3))
+	assert.True(t, AssertExpectationsForObjects(t, &mockedService1.Mock, &mockedService2.Mock, &mockedService3.Mock, &mockedService4.Mock))
+	assert.True(t, AssertExpectationsForObjects(t, mockedService1, mockedService2, mockedService3, mockedService4))
 
 }
 
diff --git a/suite/suite_test.go b/suite/suite_test.go
index 963a252..446029a 100644
--- a/suite/suite_test.go
+++ b/suite/suite_test.go
@@ -501,19 +501,36 @@ func (s *suiteWithStats) TestSomething() {
 	s.Equal(1, 1)
 }
 
+func (s *suiteWithStats) TestPanic() {
+	panic("oops")
+}
+
 func TestSuiteWithStats(t *testing.T) {
 	suiteWithStats := new(suiteWithStats)
-	Run(t, suiteWithStats)
+
+	testing.RunTests(allTestsFilter, []testing.InternalTest{
+		{
+			Name: "WithStats",
+			F: func(t *testing.T) {
+				Run(t, suiteWithStats)
+			},
+		},
+	})
 
 	assert.True(t, suiteWithStats.wasCalled)
 	assert.NotZero(t, suiteWithStats.stats.Start)
 	assert.NotZero(t, suiteWithStats.stats.End)
-	assert.True(t, suiteWithStats.stats.Passed())
+	assert.False(t, suiteWithStats.stats.Passed())
+
+	testStats := suiteWithStats.stats.TestStats
+
+	assert.NotZero(t, testStats["TestSomething"].Start)
+	assert.NotZero(t, testStats["TestSomething"].End)
+	assert.True(t, testStats["TestSomething"].Passed)
 
-	testStats := suiteWithStats.stats.TestStats["TestSomething"]
-	assert.NotZero(t, testStats.Start)
-	assert.NotZero(t, testStats.End)
-	assert.True(t, testStats.Passed)
+	assert.NotZero(t, testStats["TestPanic"].Start)
+	assert.NotZero(t, testStats["TestPanic"].End)
+	assert.False(t, testStats["TestPanic"].Passed)
 }
 
 // FailfastSuite will test the behavior when running with the failfast flag