Repository: stretchr/testify
Pull request #844 (2019-11-13 17:33:15)

Print more details in ElementsMatch
It is not very helpful to print that the lengths differ when an
assertion fails, since that does not reveal what the cause of the issue
might be.

Let's print which elements are extra in each list, that should convey
the relevant information to the user. Also use spew to format the
objects, similar to what Equal does, to make the output more readable.


Patch:
diff --git a/.travis.yml b/.travis.yml
index 2d4a445..46fdecf 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,6 +20,9 @@ matrix:
     - go: "1.13.x"
       env: GO111MODULE=on
     - go: master
+      env: GO111MODULE=on
+    - go: master
+      env: GO111MODULE=off
 script:
   - ./.travis.gogenerate.sh
   - ./.travis.gofmt.sh
diff --git a/assert/assertion_order.go b/assert/assertion_compare.go
similarity index 62%
rename from assert/assertion_order.go
rename to assert/assertion_compare.go
index 15a486c..66961ba 100644
--- a/assert/assertion_order.go
+++ b/assert/assertion_compare.go
@@ -5,20 +5,28 @@ import (
 	"reflect"
 )
 
-func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
+type CompareType int
+
+const (
+	compareLess CompareType = iota - 1
+	compareEqual
+	compareGreater
+)
+
+func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
 	switch kind {
 	case reflect.Int:
 		{
 			intobj1 := obj1.(int)
 			intobj2 := obj2.(int)
 			if intobj1 > intobj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if intobj1 == intobj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if intobj1 < intobj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.Int8:
@@ -26,13 +34,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			int8obj1 := obj1.(int8)
 			int8obj2 := obj2.(int8)
 			if int8obj1 > int8obj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if int8obj1 == int8obj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if int8obj1 < int8obj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.Int16:
@@ -40,13 +48,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			int16obj1 := obj1.(int16)
 			int16obj2 := obj2.(int16)
 			if int16obj1 > int16obj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if int16obj1 == int16obj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if int16obj1 < int16obj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.Int32:
@@ -54,13 +62,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			int32obj1 := obj1.(int32)
 			int32obj2 := obj2.(int32)
 			if int32obj1 > int32obj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if int32obj1 == int32obj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if int32obj1 < int32obj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.Int64:
@@ -68,13 +76,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			int64obj1 := obj1.(int64)
 			int64obj2 := obj2.(int64)
 			if int64obj1 > int64obj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if int64obj1 == int64obj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if int64obj1 < int64obj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.Uint:
@@ -82,13 +90,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			uintobj1 := obj1.(uint)
 			uintobj2 := obj2.(uint)
 			if uintobj1 > uintobj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if uintobj1 == uintobj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if uintobj1 < uintobj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.Uint8:
@@ -96,13 +104,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			uint8obj1 := obj1.(uint8)
 			uint8obj2 := obj2.(uint8)
 			if uint8obj1 > uint8obj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if uint8obj1 == uint8obj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if uint8obj1 < uint8obj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.Uint16:
@@ -110,13 +118,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			uint16obj1 := obj1.(uint16)
 			uint16obj2 := obj2.(uint16)
 			if uint16obj1 > uint16obj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if uint16obj1 == uint16obj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if uint16obj1 < uint16obj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.Uint32:
@@ -124,13 +132,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			uint32obj1 := obj1.(uint32)
 			uint32obj2 := obj2.(uint32)
 			if uint32obj1 > uint32obj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if uint32obj1 == uint32obj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if uint32obj1 < uint32obj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.Uint64:
@@ -138,13 +146,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			uint64obj1 := obj1.(uint64)
 			uint64obj2 := obj2.(uint64)
 			if uint64obj1 > uint64obj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if uint64obj1 == uint64obj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if uint64obj1 < uint64obj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.Float32:
@@ -152,13 +160,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			float32obj1 := obj1.(float32)
 			float32obj2 := obj2.(float32)
 			if float32obj1 > float32obj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if float32obj1 == float32obj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if float32obj1 < float32obj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.Float64:
@@ -166,13 +174,13 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			float64obj1 := obj1.(float64)
 			float64obj2 := obj2.(float64)
 			if float64obj1 > float64obj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if float64obj1 == float64obj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if float64obj1 < float64obj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	case reflect.String:
@@ -180,18 +188,18 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 			stringobj1 := obj1.(string)
 			stringobj2 := obj2.(string)
 			if stringobj1 > stringobj2 {
-				return -1, true
+				return compareGreater, true
 			}
 			if stringobj1 == stringobj2 {
-				return 0, true
+				return compareEqual, true
 			}
 			if stringobj1 < stringobj2 {
-				return 1, true
+				return compareLess, true
 			}
 		}
 	}
 
-	return 0, false
+	return compareEqual, false
 }
 
 // Greater asserts that the first element is greater than the second
@@ -200,26 +208,7 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) {
 //    assert.Greater(t, float64(2), float64(1))
 //    assert.Greater(t, "b", "a")
 func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
-	if h, ok := t.(tHelper); ok {
-		h.Helper()
-	}
-
-	e1Kind := reflect.ValueOf(e1).Kind()
-	e2Kind := reflect.ValueOf(e2).Kind()
-	if e1Kind != e2Kind {
-		return Fail(t, "Elements should be the same type", msgAndArgs...)
-	}
-
-	res, isComparable := compare(e1, e2, e1Kind)
-	if !isComparable {
-		return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
-	}
-
-	if res != -1 {
-		return Fail(t, fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2), msgAndArgs...)
-	}
-
-	return true
+	return compareTwoValues(t, e1, e2, []CompareType{compareGreater}, "\"%s\" is not greater than \"%s\"", msgAndArgs)
 }
 
 // GreaterOrEqual asserts that the first element is greater than or equal to the second
@@ -229,26 +218,7 @@ func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface
 //    assert.GreaterOrEqual(t, "b", "a")
 //    assert.GreaterOrEqual(t, "b", "b")
 func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
-	if h, ok := t.(tHelper); ok {
-		h.Helper()
-	}
-
-	e1Kind := reflect.ValueOf(e1).Kind()
-	e2Kind := reflect.ValueOf(e2).Kind()
-	if e1Kind != e2Kind {
-		return Fail(t, "Elements should be the same type", msgAndArgs...)
-	}
-
-	res, isComparable := compare(e1, e2, e1Kind)
-	if !isComparable {
-		return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
-	}
-
-	if res != -1 && res != 0 {
-		return Fail(t, fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2), msgAndArgs...)
-	}
-
-	return true
+	return compareTwoValues(t, e1, e2, []CompareType{compareGreater, compareEqual}, "\"%s\" is not greater than or equal to \"%s\"", msgAndArgs)
 }
 
 // Less asserts that the first element is less than the second
@@ -257,26 +227,7 @@ func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...in
 //    assert.Less(t, float64(1), float64(2))
 //    assert.Less(t, "a", "b")
 func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
-	if h, ok := t.(tHelper); ok {
-		h.Helper()
-	}
-
-	e1Kind := reflect.ValueOf(e1).Kind()
-	e2Kind := reflect.ValueOf(e2).Kind()
-	if e1Kind != e2Kind {
-		return Fail(t, "Elements should be the same type", msgAndArgs...)
-	}
-
-	res, isComparable := compare(e1, e2, e1Kind)
-	if !isComparable {
-		return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
-	}
-
-	if res != 1 {
-		return Fail(t, fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2), msgAndArgs...)
-	}
-
-	return true
+	return compareTwoValues(t, e1, e2, []CompareType{compareLess}, "\"%s\" is not less than \"%s\"", msgAndArgs)
 }
 
 // LessOrEqual asserts that the first element is less than or equal to the second
@@ -286,6 +237,10 @@ func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{})
 //    assert.LessOrEqual(t, "a", "b")
 //    assert.LessOrEqual(t, "b", "b")
 func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool {
+	return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%s\" is not less than or equal to \"%s\"", msgAndArgs)
+}
+
+func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
 	if h, ok := t.(tHelper); ok {
 		h.Helper()
 	}
@@ -296,14 +251,24 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter
 		return Fail(t, "Elements should be the same type", msgAndArgs...)
 	}
 
-	res, isComparable := compare(e1, e2, e1Kind)
+	compareResult, isComparable := compare(e1, e2, e1Kind)
 	if !isComparable {
 		return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...)
 	}
 
-	if res != 1 && res != 0 {
-		return Fail(t, fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2), msgAndArgs...)
+	if !containsValue(allowedComparesResults, compareResult) {
+		return Fail(t, fmt.Sprintf(failMessage, e1, e2), msgAndArgs...)
 	}
 
 	return true
 }
+
+func containsValue(values []CompareType, value CompareType) bool {
+	for _, v := range values {
+		if v == value {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/assert/assertions.go b/assert/assertions.go
index bdd8138..eefc89c 100644
--- a/assert/assertions.go
+++ b/assert/assertions.go
@@ -104,11 +104,11 @@ the problem actually occurred in calling code.*/
 // failed.
 func CallerInfo() []string {
 
-	pc := uintptr(0)
-	file := ""
-	line := 0
-	ok := false
-	name := ""
+	var pc uintptr
+	var ok bool
+	var file string
+	var line int
+	var name string
 
 	callers := []string{}
 	for i := 0; ; i++ {
@@ -429,14 +429,27 @@ func samePointers(first, second interface{}) bool {
 // to a type conversion in the Go grammar.
 func formatUnequalValues(expected, actual interface{}) (e string, a string) {
 	if reflect.TypeOf(expected) != reflect.TypeOf(actual) {
-		return fmt.Sprintf("%T(%#v)", expected, expected),
-			fmt.Sprintf("%T(%#v)", actual, actual)
+		return fmt.Sprintf("%T(%s)", expected, truncatingFormat(expected)),
+			fmt.Sprintf("%T(%s)", actual, truncatingFormat(actual))
 	}
 	switch expected.(type) {
 	case time.Duration:
 		return fmt.Sprintf("%v", expected), fmt.Sprintf("%v", actual)
 	}
-	return fmt.Sprintf("%#v", expected), fmt.Sprintf("%#v", actual)
+	return truncatingFormat(expected), truncatingFormat(actual)
+}
+
+// truncatingFormat formats the data and truncates it if it's too long.
+//
+// This helps keep formatted error messages lines from exceeding the
+// bufio.MaxScanTokenSize max line length that the go testing framework imposes.
+func truncatingFormat(data interface{}) string {
+	value := fmt.Sprintf("%#v", data)
+	max := bufio.MaxScanTokenSize - 100 // Give us some space the type info too if needed.
+	if len(value) > max {
+		value = value[0:max] + "<... truncated>"
+	}
+	return value
 }
 
 // EqualValues asserts that two objects are equal or convertable to the same types
@@ -648,7 +661,7 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
 		h.Helper()
 	}
 
-	if value != true {
+	if !value {
 		return Fail(t, "Should be true", msgAndArgs...)
 	}
 
@@ -664,7 +677,7 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool {
 		h.Helper()
 	}
 
-	if value != false {
+	if value {
 		return Fail(t, "Should be false", msgAndArgs...)
 	}
 
@@ -747,10 +760,10 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo
 
 	ok, found := includeElement(s, contains)
 	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 {
-		return Fail(t, fmt.Sprintf("\"%s\" does not contain \"%s\"", s, contains), msgAndArgs...)
+		return Fail(t, fmt.Sprintf("%#v does not contain %#v", s, contains), msgAndArgs...)
 	}
 
 	return true
@@ -881,27 +894,39 @@ func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface
 		return true
 	}
 
-	aKind := reflect.TypeOf(listA).Kind()
-	bKind := reflect.TypeOf(listB).Kind()
+	if !isList(t, listA, msgAndArgs...) || !isList(t, listB, msgAndArgs...) {
+		return false
+	}
 
-	if aKind != reflect.Array && aKind != reflect.Slice {
-		return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listA, aKind), msgAndArgs...)
+	extraA, extraB := diffLists(listA, listB)
+
+	if len(extraA) == 0 && len(extraB) == 0 {
+		return true
 	}
 
-	if bKind != reflect.Array && bKind != reflect.Slice {
-		return Fail(t, fmt.Sprintf("%q has an unsupported type %s", listB, bKind), msgAndArgs...)
+	return Fail(t, formatListDiff(listA, listB, extraA, extraB), msgAndArgs...)
+}
+
+// isList checks that the provided value is array or slice.
+func isList(t TestingT, list interface{}, msgAndArgs ...interface{}) (ok bool) {
+	kind := reflect.TypeOf(list).Kind()
+	if kind != reflect.Array && kind != reflect.Slice {
+		return Fail(t, fmt.Sprintf("%q has an unsupported type %s, expecting array or slice", list, kind),
+			msgAndArgs...)
 	}
+	return true
+}
 
+// diffLists diffs two arrays/slices and returns slices of elements that are only in A and only in B.
+// If some element is present multiple times, each instance is counted separately (e.g. if something is 2x in A and
+// 5x in B, it will be 0x in extraA and 3x in extraB). The order of items in both lists is ignored.
+func diffLists(listA, listB interface{}) (extraA, extraB []interface{}) {
 	aValue := reflect.ValueOf(listA)
 	bValue := reflect.ValueOf(listB)
 
 	aLen := aValue.Len()
 	bLen := bValue.Len()
 
-	if aLen != bLen {
-		return Fail(t, fmt.Sprintf("lengths don't match: %d != %d", aLen, bLen), msgAndArgs...)
-	}
-
 	// Mark indexes in bValue that we already used
 	visited := make([]bool, bLen)
 	for i := 0; i < aLen; i++ {
@@ -918,11 +943,38 @@ func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface
 			}
 		}
 		if !found {
-			return Fail(t, fmt.Sprintf("element %s appears more times in %s than in %s", element, aValue, bValue), msgAndArgs...)
+			extraA = append(extraA, element)
 		}
 	}
 
-	return true
+	for j := 0; j < bLen; j++ {
+		if visited[j] {
+			continue
+		}
+		extraB = append(extraB, bValue.Index(j).Interface())
+	}
+
+	return
+}
+
+func formatListDiff(listA, listB interface{}, extraA, extraB []interface{}) string {
+	var msg bytes.Buffer
+
+	msg.WriteString("elements differ")
+	if len(extraA) > 0 {
+		msg.WriteString("\n\nextra elements in list A:\n")
+		msg.WriteString(spewConfig.Sdump(extraA))
+	}
+	if len(extraB) > 0 {
+		msg.WriteString("\n\nextra elements in list B:\n")
+		msg.WriteString(spewConfig.Sdump(extraB))
+	}
+	msg.WriteString("\n\nlistA:\n")
+	msg.WriteString(spewConfig.Sdump(listA))
+	msg.WriteString("\n\nlistB:\n")
+	msg.WriteString(spewConfig.Sdump(listB))
+
+	return msg.String()
 }
 
 // Condition uses a Comparison to assert a complex condition.
@@ -1079,7 +1131,7 @@ func toFloat(x interface{}) (float64, bool) {
 	case float32:
 		xf = float64(xn)
 	case float64:
-		xf = float64(xn)
+		xf = xn
 	case time.Duration:
 		xf = float64(xn)
 	default:
@@ -1553,6 +1605,7 @@ var spewConfig = spew.ConfigState{
 	DisablePointerAddresses: true,
 	DisableCapacities:       true,
 	SortKeys:                true,
+	DisableMethods:          true,
 }
 
 type tHelper interface {
diff --git a/mock/mock.go b/mock/mock.go
index 58e0798..3c0dc94 100644
--- a/mock/mock.go
+++ b/mock/mock.go
@@ -527,6 +527,45 @@ func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...inter
 	return true
 }
 
+// IsMethodCallable checking that the method can be called
+// If the method was called more than `Repeatability` return false
+func (m *Mock) IsMethodCallable(t TestingT, methodName string, arguments ...interface{}) bool {
+	if h, ok := t.(tHelper); ok {
+		h.Helper()
+	}
+	m.mutex.Lock()
+	defer m.mutex.Unlock()
+
+	for _, v := range m.ExpectedCalls {
+		if v.Method != methodName {
+			continue
+		}
+		if len(arguments) != len(v.Arguments) {
+			continue
+		}
+		if v.Repeatability < v.totalCalls {
+			continue
+		}
+		if isArgsEqual(v.Arguments, arguments) {
+			return true
+		}
+	}
+	return false
+}
+
+// isArgsEqual compares arguments
+func isArgsEqual(expected Arguments, args []interface{}) bool {
+	if len(expected) != len(args) {
+		return false
+	}
+	for i, v := range args {
+		if !reflect.DeepEqual(expected[i], v) {
+			return false
+		}
+	}
+	return true
+}
+
 func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool {
 	for _, call := range m.calls() {
 		if call.Method == methodName {
@@ -791,7 +830,7 @@ func (args Arguments) String(indexOrNil ...int) string {
 		// normal String() method - return a string representation of the args
 		var argsStr []string
 		for _, arg := range args {
-			argsStr = append(argsStr, fmt.Sprintf("%s", reflect.TypeOf(arg)))
+			argsStr = append(argsStr, reflect.TypeOf(arg).String())
 		}
 		return strings.Join(argsStr, ",")
 	} else if len(indexOrNil) == 1 {
diff --git a/suite/interfaces.go b/suite/interfaces.go
index b37cb04..8b98a8a 100644
--- a/suite/interfaces.go
+++ b/suite/interfaces.go
@@ -44,3 +44,10 @@ type BeforeTest interface {
 type AfterTest interface {
 	AfterTest(suiteName, testName string)
 }
+
+// WithStats implements HandleStats, a function that will be executed
+// when a test suite is finished. The stats contain information about
+// the execution of that suite and its tests.
+type WithStats interface {
+	HandleStats(suiteName string, stats *SuiteInformation)
+}
diff --git a/suite/stats.go b/suite/stats.go
new file mode 100644
index 0000000..261da37
--- /dev/null
+++ b/suite/stats.go
@@ -0,0 +1,46 @@
+package suite
+
+import "time"
+
+// SuiteInformation stats stores stats for the whole suite execution.
+type SuiteInformation struct {
+	Start, End time.Time
+	TestStats  map[string]*TestInformation
+}
+
+// TestInformation stores information about the execution of each test.
+type TestInformation struct {
+	TestName   string
+	Start, End time.Time
+	Passed     bool
+}
+
+func newSuiteInformation() *SuiteInformation {
+	testStats := make(map[string]*TestInformation)
+
+	return &SuiteInformation{
+		TestStats: testStats,
+	}
+}
+
+func (s SuiteInformation) start(testName string) {
+	s.TestStats[testName] = &TestInformation{
+		TestName: testName,
+		Start:    time.Now(),
+	}
+}
+
+func (s SuiteInformation) end(testName string, passed bool) {
+	s.TestStats[testName].End = time.Now()
+	s.TestStats[testName].Passed = passed
+}
+
+func (s SuiteInformation) Passed() bool {
+	for _, stats := range s.TestStats {
+		if !stats.Passed {
+			return false
+		}
+	}
+
+	return true
+}
diff --git a/suite/suite.go b/suite/suite.go
index 6195301..cdaf9a9 100644
--- a/suite/suite.go
+++ b/suite/suite.go
@@ -9,6 +9,7 @@ import (
 	"runtime/debug"
 	"sync"
 	"testing"
+	"time"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -87,6 +88,12 @@ func Run(t *testing.T, suite TestingSuite) {
 
 	suiteSetupDone := false
 
+	var stats *SuiteInformation
+
+	if _, ok := suite.(WithStats); ok {
+		stats = newSuiteInformation()
+	}
+
 	methodFinder := reflect.TypeOf(suite)
 	tests := []testing.InternalTest{}
 	for index := 0; index < methodFinder.NumMethod(); index++ {
@@ -96,21 +103,36 @@ func Run(t *testing.T, suite TestingSuite) {
 			fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
 			os.Exit(1)
 		}
+
 		if !ok {
 			continue
 		}
+
+		suiteName := methodFinder.Elem().Name()
+
 		if !suiteSetupDone {
+			if stats != nil {
+				stats.Start = time.Now()
+			}
+
 			if setupAllSuite, ok := suite.(SetupAllSuite); ok {
 				setupAllSuite.SetupSuite()
 			}
+
 			defer func() {
 				if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
 					testsSync.Wait()
 					tearDownAllSuite.TearDownSuite()
 				}
+
+				if suiteWithStats, measureStats := suite.(WithStats); measureStats {
+					stats.End = time.Now()
+					suiteWithStats.HandleStats(suiteName, stats)
+				}
 			}()
 			suiteSetupDone = true
 		}
+
 		test := testing.InternalTest{
 			Name: method.Name,
 			F: func(t *testing.T) {
@@ -122,16 +144,29 @@ func Run(t *testing.T, suite TestingSuite) {
 				if setupTestSuite, ok := suite.(SetupTestSuite); ok {
 					setupTestSuite.SetupTest()
 				}
+
 				if beforeTestSuite, ok := suite.(BeforeTest); ok {
 					beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
 				}
+
+				if stats != nil {
+					stats.start(method.Name)
+				}
+
 				defer func() {
+					if stats != nil {
+						passed := !t.Failed()
+						stats.end(method.Name, passed)
+					}
+
 					if afterTestSuite, ok := suite.(AfterTest); ok {
-						afterTestSuite.AfterTest(methodFinder.Elem().Name(), method.Name)
+						afterTestSuite.AfterTest(suiteName, method.Name)
 					}
+
 					if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
 						tearDownTestSuite.TearDownTest()
 					}
+
 					suite.SetT(parentT)
 				}()
 				method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})


Test patch:
diff --git a/assert/assertion_order_test.go b/assert/assertion_compare_test.go
similarity index 53%
rename from assert/assertion_order_test.go
rename to assert/assertion_compare_test.go
index 4dd897a..daf9bea 100644
--- a/assert/assertion_order_test.go
+++ b/assert/assertion_compare_test.go
@@ -29,7 +29,7 @@ func TestCompare(t *testing.T) {
 			t.Error("object should be comparable for type " + currCase.cType)
 		}
 
-		if resLess != 1 {
+		if resLess != compareLess {
 			t.Errorf("object less should be less than greater for type " + currCase.cType)
 		}
 
@@ -38,7 +38,7 @@ func TestCompare(t *testing.T) {
 			t.Error("object are comparable for type " + currCase.cType)
 		}
 
-		if resGreater != -1 {
+		if resGreater != compareGreater {
 			t.Errorf("object greater should be greater than less for type " + currCase.cType)
 		}
 
@@ -116,3 +116,76 @@ func TestLessOrEqual(t *testing.T) {
 		t.Error("Greater should return false")
 	}
 }
+
+func Test_compareTwoValuesDifferentValuesTypes(t *testing.T) {
+	mockT := new(testing.T)
+
+	for _, currCase := range []struct {
+		v1            interface{}
+		v2            interface{}
+		compareResult bool
+	}{
+		{v1: 123, v2: "abc"},
+		{v1: "abc", v2: 123456},
+		{v1: float64(12), v2: "123"},
+		{v1: "float(12)", v2: float64(1)},
+	} {
+		compareResult := compareTwoValues(mockT, currCase.v1, currCase.v2, []CompareType{compareLess, compareEqual, compareGreater}, "testFailMessage")
+		False(t, compareResult)
+	}
+}
+
+func Test_compareTwoValuesNotComparableValues(t *testing.T) {
+	mockT := new(testing.T)
+
+	type CompareStruct struct {
+	}
+
+	for _, currCase := range []struct {
+		v1 interface{}
+		v2 interface{}
+	}{
+		{v1: CompareStruct{}, v2: CompareStruct{}},
+		{v1: map[string]int{}, v2: map[string]int{}},
+		{v1: make([]int, 5, 5), v2: make([]int, 5, 5)},
+	} {
+		compareResult := compareTwoValues(mockT, currCase.v1, currCase.v2, []CompareType{compareLess, compareEqual, compareGreater}, "testFailMessage")
+		False(t, compareResult)
+	}
+}
+
+func Test_compareTwoValuesCorrectCompareResult(t *testing.T) {
+	mockT := new(testing.T)
+
+	for _, currCase := range []struct {
+		v1           interface{}
+		v2           interface{}
+		compareTypes []CompareType
+	}{
+		{v1: 1, v2: 2, compareTypes: []CompareType{compareLess}},
+		{v1: 1, v2: 2, compareTypes: []CompareType{compareLess, compareEqual}},
+		{v1: 2, v2: 2, compareTypes: []CompareType{compareGreater, compareEqual}},
+		{v1: 2, v2: 2, compareTypes: []CompareType{compareEqual}},
+		{v1: 2, v2: 1, compareTypes: []CompareType{compareEqual, compareGreater}},
+		{v1: 2, v2: 1, compareTypes: []CompareType{compareGreater}},
+	} {
+		compareResult := compareTwoValues(mockT, currCase.v1, currCase.v2, currCase.compareTypes, "testFailMessage")
+		True(t, compareResult)
+	}
+}
+
+func Test_containsValue(t *testing.T) {
+	for _, currCase := range []struct {
+		values []CompareType
+		value  CompareType
+		result bool
+	}{
+		{values: []CompareType{compareGreater}, value: compareGreater, result: true},
+		{values: []CompareType{compareGreater, compareLess}, value: compareGreater, result: true},
+		{values: []CompareType{compareGreater, compareLess}, value: compareLess, result: true},
+		{values: []CompareType{compareGreater, compareLess}, value: compareEqual, result: false},
+	} {
+		compareResult := containsValue(currCase.values, currCase.value)
+		Equal(t, currCase.result, compareResult)
+	}
+}
diff --git a/assert/assertions_test.go b/assert/assertions_test.go
index 35a643e..744a191 100644
--- a/assert/assertions_test.go
+++ b/assert/assertions_test.go
@@ -1,6 +1,7 @@
 package assert
 
 import (
+	"bufio"
 	"bytes"
 	"encoding/json"
 	"errors"
@@ -73,11 +74,11 @@ var (
 		[1]interface{}{1},
 		[]interface{}{},
 		struct{ x int }{1},
-		(*interface{})(&i),
-		(func())(func() {}),
+		(&i),
+		(func() {}),
 		interface{}(1),
 		map[interface{}]interface{}{},
-		(chan interface{})(make(chan interface{})),
+		(make(chan interface{})),
 		(<-chan interface{})(make(chan interface{})),
 		(chan<- interface{})(make(chan interface{})),
 	}
@@ -591,6 +592,18 @@ func TestContains(t *testing.T) {
 	}
 }
 
+func TestContainsFailMessage(t *testing.T) {
+
+	mockT := new(mockTestingT)
+
+	Contains(mockT, "Hello World", errors.New("Hello"))
+	expectedFail := "\"Hello World\" does not contain &errors.errorString{s:\"Hello\"}"
+	actualFail := mockT.errorString()
+	if !strings.Contains(actualFail, expectedFail) {
+		t.Errorf("Contains failure should include %q but was %q", expectedFail, actualFail)
+	}
+}
+
 func TestNotContains(t *testing.T) {
 
 	mockT := new(testing.T)
@@ -788,6 +801,90 @@ func TestElementsMatch(t *testing.T) {
 	}
 }
 
+func TestDiffLists(t *testing.T) {
+	tests := []struct {
+		name   string
+		listA  interface{}
+		listB  interface{}
+		extraA []interface{}
+		extraB []interface{}
+	}{
+		{
+			name:   "equal empty",
+			listA:  []string{},
+			listB:  []string{},
+			extraA: nil,
+			extraB: nil,
+		},
+		{
+			name:   "equal same order",
+			listA:  []string{"hello", "world"},
+			listB:  []string{"hello", "world"},
+			extraA: nil,
+			extraB: nil,
+		},
+		{
+			name:   "equal different order",
+			listA:  []string{"hello", "world"},
+			listB:  []string{"world", "hello"},
+			extraA: nil,
+			extraB: nil,
+		},
+		{
+			name:   "extra A",
+			listA:  []string{"hello", "hello", "world"},
+			listB:  []string{"hello", "world"},
+			extraA: []interface{}{"hello"},
+			extraB: nil,
+		},
+		{
+			name:   "extra A twice",
+			listA:  []string{"hello", "hello", "hello", "world"},
+			listB:  []string{"hello", "world"},
+			extraA: []interface{}{"hello", "hello"},
+			extraB: nil,
+		},
+		{
+			name:   "extra B",
+			listA:  []string{"hello", "world"},
+			listB:  []string{"hello", "hello", "world"},
+			extraA: nil,
+			extraB: []interface{}{"hello"},
+		},
+		{
+			name:   "extra B twice",
+			listA:  []string{"hello", "world"},
+			listB:  []string{"hello", "hello", "world", "hello"},
+			extraA: nil,
+			extraB: []interface{}{"hello", "hello"},
+		},
+		{
+			name:   "integers 1",
+			listA:  []int{1, 2, 3, 4, 5},
+			listB:  []int{5, 4, 3, 2, 1},
+			extraA: nil,
+			extraB: nil,
+		},
+		{
+			name:   "integers 2",
+			listA:  []int{1, 2, 1, 2, 1},
+			listB:  []int{2, 1, 2, 1, 2},
+			extraA: []interface{}{1},
+			extraB: []interface{}{2},
+		},
+	}
+	for _, test := range tests {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			actualExtraA, actualExtraB := diffLists(test.listA, test.listB)
+			Equal(t, test.extraA, actualExtraA, "extra A does not match for listA=%v listB=%v",
+				test.listA, test.listB)
+			Equal(t, test.extraB, actualExtraB, "extra B does not match for listA=%v listB=%v",
+				test.listA, test.listB)
+		})
+	}
+}
+
 func TestCondition(t *testing.T) {
 	mockT := new(testing.T)
 
@@ -917,9 +1014,6 @@ func TestNoError(t *testing.T) {
 	// returning an empty error interface
 	err = func() error {
 		var err *customError
-		if err != nil {
-			t.Fatal("err should be nil here")
-		}
 		return err
 	}()
 
@@ -954,9 +1048,6 @@ func TestError(t *testing.T) {
 	// returning an empty error interface
 	err = func() error {
 		var err *customError
-		if err != nil {
-			t.Fatal("err should be nil here")
-		}
 		return err
 	}()
 
@@ -1025,7 +1116,6 @@ func TestEmpty(t *testing.T) {
 	type TString string
 	type TStruct struct {
 		x int
-		s []int
 	}
 
 	True(t, Empty(mockT, ""), "Empty string is empty")
@@ -1750,6 +1840,15 @@ func TestYAMLEq_ArraysOfDifferentOrder(t *testing.T) {
 	False(t, YAMLEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`))
 }
 
+type diffTestingStruct struct {
+	A string
+	B int
+}
+
+func (d *diffTestingStruct) String() string {
+	return d.A
+}
+
 func TestDiff(t *testing.T) {
 	expected := `
 
@@ -1829,6 +1928,42 @@ Diff:
 		map[string]int{"one": 1, "three": 3, "five": 5, "seven": 7},
 	)
 	Equal(t, expected, actual)
+
+	expected = `
+
+Diff:
+--- Expected
++++ Actual
+@@ -1,3 +1,3 @@
+ (*errors.errorString)({
+- s: (string) (len=19) "some expected error"
++ s: (string) (len=12) "actual error"
+ })
+`
+
+	actual = diff(
+		errors.New("some expected error"),
+		errors.New("actual error"),
+	)
+	Equal(t, expected, actual)
+
+	expected = `
+
+Diff:
+--- Expected
++++ Actual
+@@ -2,3 +2,3 @@
+  A: (string) (len=11) "some string",
+- B: (int) 10
++ B: (int) 15
+ }
+`
+
+	actual = diff(
+		diffTestingStruct{A: "some string", B: 10},
+		diffTestingStruct{A: "some string", B: 15},
+	)
+	Equal(t, expected, actual)
 }
 
 func TestTimeEqualityErrorFormatting(t *testing.T) {
@@ -2159,7 +2294,7 @@ func TestEventuallyTrue(t *testing.T) {
 	state := 0
 	condition := func() bool {
 		defer func() {
-			state = state + 1
+			state += 1
 		}()
 		return state == 2
 	}
@@ -2210,3 +2345,18 @@ func Test_validateEqualArgs(t *testing.T) {
 		t.Error("nil functions are equal")
 	}
 }
+
+func Test_truncatingFormat(t *testing.T) {
+
+	original := strings.Repeat("a", bufio.MaxScanTokenSize-102)
+	result := truncatingFormat(original)
+	Equal(t, fmt.Sprintf("%#v", original), result, "string should not be truncated")
+
+	original = original + "x"
+	result = truncatingFormat(original)
+	NotEqual(t, fmt.Sprintf("%#v", original), result, "string should have been truncated.")
+
+	if !strings.HasSuffix(result, "<... truncated>") {
+		t.Error("truncated string should have <... truncated> suffix")
+	}
+}
diff --git a/http/test_response_writer.go b/http/test_response_writer.go
index 5c3f813..300a63b 100644
--- a/http/test_response_writer.go
+++ b/http/test_response_writer.go
@@ -36,7 +36,7 @@ func (rw *TestResponseWriter) Write(bytes []byte) (int, error) {
 	}
 
 	// add these bytes to the output string
-	rw.Output = rw.Output + string(bytes)
+	rw.Output += string(bytes)
 
 	// return normal values
 	return 0, nil
diff --git a/http/test_round_tripper.go b/http/test_round_tripper.go
index b1e32f1..11a024e 100644
--- a/http/test_round_tripper.go
+++ b/http/test_round_tripper.go
@@ -1,8 +1,9 @@
 package http
 
 import (
-	"github.com/stretchr/testify/mock"
 	"net/http"
+
+	"github.com/stretchr/testify/mock"
 )
 
 // TestRoundTripper DEPRECATED USE net/http/httptest
diff --git a/mock/mock_test.go b/mock/mock_test.go
index 7da34b7..fcbc4f6 100644
--- a/mock/mock_test.go
+++ b/mock/mock_test.go
@@ -1143,6 +1143,41 @@ func Test_Mock_AssertNotCalled(t *testing.T) {
 
 }
 
+func Test_Mock_IsMethodCallable(t *testing.T) {
+	var mockedService = new(TestExampleImplementation)
+
+	arg := []Call{{Repeatability: 1}, {Repeatability: 2}}
+	arg2 := []Call{{Repeatability: 1}, {Repeatability: 1}}
+	arg3 := []Call{{Repeatability: 1}, {Repeatability: 1}}
+
+	mockedService.On("Test_Mock_IsMethodCallable", arg2).Return(true).Twice()
+
+	assert.False(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg))
+	assert.True(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg2))
+	assert.True(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg3))
+
+	mockedService.MethodCalled("Test_Mock_IsMethodCallable", arg2)
+	mockedService.MethodCalled("Test_Mock_IsMethodCallable", arg2)
+
+	assert.False(t, mockedService.IsMethodCallable(t, "Test_Mock_IsMethodCallable", arg2))
+}
+
+func TestIsArgsEqual(t *testing.T) {
+	var expected = Arguments{5, 3, 4, 6, 7, 2}
+	var args = make([]interface{}, 5)
+	for i := 1; i < len(expected); i++ {
+		args[i-1] = expected[i]
+	}
+	args[2] = expected[1]
+	assert.False(t, isArgsEqual(expected, args))
+
+	var arr = make([]interface{}, 6)
+	for i := 0; i < len(expected); i++ {
+		arr[i] = expected[i]
+	}
+	assert.True(t, isArgsEqual(expected, arr))
+}
+
 func Test_Mock_AssertOptional(t *testing.T) {
 	// Optional called
 	var ms1 = new(TestExampleImplementation)
@@ -1290,6 +1325,7 @@ func Test_Arguments_Diff_WithArgMatcher(t *testing.T) {
 	assert.Contains(t, diff, `(bool=false) not matched by func(int) bool`)
 
 	diff, count = args.Diff([]interface{}{"string", 123, false})
+	assert.Equal(t, 1, count)
 	assert.Contains(t, diff, `(int=123) matched by func(int) bool`)
 
 	diff, count = args.Diff([]interface{}{"string", 123, true})
diff --git a/suite/stats_test.go b/suite/stats_test.go
new file mode 100644
index 0000000..4446a6d
--- /dev/null
+++ b/suite/stats_test.go
@@ -0,0 +1,29 @@
+package suite
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestPassedReturnsTrueWhenAllTestsPass(t *testing.T) {
+	sinfo := newSuiteInformation()
+	sinfo.TestStats = map[string]*TestInformation{
+		"Test1": {TestName: "Test1", Passed: true},
+		"Test2": {TestName: "Test2", Passed: true},
+		"Test3": {TestName: "Test3", Passed: true},
+	}
+
+	assert.True(t, sinfo.Passed())
+}
+
+func TestPassedReturnsFalseWhenSomeTestFails(t *testing.T) {
+	sinfo := newSuiteInformation()
+	sinfo.TestStats = map[string]*TestInformation{
+		"Test1": {TestName: "Test1", Passed: true},
+		"Test2": {TestName: "Test2", Passed: false},
+		"Test3": {TestName: "Test3", Passed: true},
+	}
+
+	assert.False(t, sinfo.Passed())
+}
diff --git a/suite/suite_test.go b/suite/suite_test.go
index 5d757dd..8b698a0 100644
--- a/suite/suite_test.go
+++ b/suite/suite_test.go
@@ -482,3 +482,33 @@ func (s *CallOrderSuite) Test_A() {
 func (s *CallOrderSuite) Test_B() {
 	s.call("Test B")
 }
+
+type suiteWithStats struct {
+	Suite
+	wasCalled bool
+	stats     *SuiteInformation
+}
+
+func (s *suiteWithStats) HandleStats(suiteName string, stats *SuiteInformation) {
+	s.wasCalled = true
+	s.stats = stats
+}
+
+func (s *suiteWithStats) TestSomething() {
+	s.Equal(1, 1)
+}
+
+func TestSuiteWithStats(t *testing.T) {
+	suiteWithStats := new(suiteWithStats)
+	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())
+
+	testStats := suiteWithStats.stats.TestStats["TestSomething"]
+	assert.NotZero(t, testStats.Start)
+	assert.NotZero(t, testStats.End)
+	assert.True(t, testStats.Passed)
+}