mirror of
https://github.com/stretchr/testify.git
synced 2025-05-31 11:42:44 +00:00
Shortcut in assert.YAMLEq once we have validated that 'expected' is valid YAML, and 'actual' is the exact same string.
2287 lines
65 KiB
Go
2287 lines
65 KiB
Go
package assert
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"reflect"
|
|
"regexp"
|
|
"runtime"
|
|
"runtime/debug"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/pmezard/go-difflib/difflib"
|
|
|
|
// Wrapper around gopkg.in/yaml.v3
|
|
"github.com/stretchr/testify/assert/yaml"
|
|
)
|
|
|
|
const stackFrameBufferSize = 10
|
|
|
|
//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=assert -template=assertion_format.go.tmpl"
|
|
|
|
// TestingT is an interface wrapper around *testing.T
|
|
type TestingT interface {
|
|
Errorf(format string, args ...interface{})
|
|
}
|
|
|
|
// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful
|
|
// for table driven tests.
|
|
type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) bool
|
|
|
|
// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful
|
|
// for table driven tests.
|
|
type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) bool
|
|
|
|
// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful
|
|
// for table driven tests.
|
|
type BoolAssertionFunc func(TestingT, bool, ...interface{}) bool
|
|
|
|
// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful
|
|
// for table driven tests.
|
|
type ErrorAssertionFunc func(TestingT, error, ...interface{}) bool
|
|
|
|
// PanicAssertionFunc is a common function prototype when validating a panic value. Can be useful
|
|
// for table driven tests.
|
|
type PanicAssertionFunc = func(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool
|
|
|
|
// Comparison is a custom function that returns true on success and false on failure
|
|
type Comparison func() (success bool)
|
|
|
|
/*
|
|
Helper functions
|
|
*/
|
|
|
|
// ObjectsAreEqual determines if two objects are considered equal.
|
|
//
|
|
// This function does no assertion of any kind.
|
|
func ObjectsAreEqual(expected, actual interface{}) bool {
|
|
if expected == nil || actual == nil {
|
|
return expected == actual
|
|
}
|
|
|
|
exp, ok := expected.([]byte)
|
|
if !ok {
|
|
return reflect.DeepEqual(expected, actual)
|
|
}
|
|
|
|
act, ok := actual.([]byte)
|
|
if !ok {
|
|
return false
|
|
}
|
|
if exp == nil || act == nil {
|
|
return exp == nil && act == nil
|
|
}
|
|
return bytes.Equal(exp, act)
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// Deprecated: Use [EqualExportedValues] instead.
|
|
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
|
|
// values are equal.
|
|
func ObjectsAreEqualValues(expected, actual interface{}) bool {
|
|
if ObjectsAreEqual(expected, actual) {
|
|
return true
|
|
}
|
|
|
|
expectedValue := reflect.ValueOf(expected)
|
|
actualValue := reflect.ValueOf(actual)
|
|
if !expectedValue.IsValid() || !actualValue.IsValid() {
|
|
return false
|
|
}
|
|
|
|
expectedType := expectedValue.Type()
|
|
actualType := actualValue.Type()
|
|
if !expectedType.ConvertibleTo(actualType) {
|
|
return false
|
|
}
|
|
|
|
if !isNumericType(expectedType) || !isNumericType(actualType) {
|
|
// Attempt comparison after type conversion
|
|
return reflect.DeepEqual(
|
|
expectedValue.Convert(actualType).Interface(), actual,
|
|
)
|
|
}
|
|
|
|
// If BOTH values are numeric, there are chances of false positives due
|
|
// to overflow or underflow. So, we need to make sure to always convert
|
|
// the smaller type to a larger type before comparing.
|
|
if expectedType.Size() >= actualType.Size() {
|
|
return actualValue.Convert(expectedType).Interface() == expected
|
|
}
|
|
|
|
return expectedValue.Convert(actualType).Interface() == actual
|
|
}
|
|
|
|
// isNumericType returns true if the type is one of:
|
|
// int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64,
|
|
// float32, float64, complex64, complex128
|
|
func isNumericType(t reflect.Type) bool {
|
|
return t.Kind() >= reflect.Int && t.Kind() <= reflect.Complex128
|
|
}
|
|
|
|
/* CallerInfo is necessary because the assert functions use the testing object
|
|
internally, causing it to print the file:line of the assert method, rather than where
|
|
the problem actually occurred in calling code.*/
|
|
|
|
// CallerInfo returns an array of strings containing the file and line number
|
|
// of each stack frame leading from the current test to the assert call that
|
|
// failed.
|
|
func CallerInfo() []string {
|
|
var pc uintptr
|
|
var file string
|
|
var line int
|
|
var name string
|
|
|
|
callers := []string{}
|
|
pcs := make([]uintptr, stackFrameBufferSize)
|
|
offset := 1
|
|
|
|
for {
|
|
n := runtime.Callers(offset, pcs)
|
|
|
|
if n == 0 {
|
|
break
|
|
}
|
|
|
|
frames := runtime.CallersFrames(pcs[:n])
|
|
|
|
for {
|
|
frame, more := frames.Next()
|
|
pc = frame.PC
|
|
file = frame.File
|
|
line = frame.Line
|
|
|
|
// This is a huge edge case, but it will panic if this is the case, see #180
|
|
if file == "<autogenerated>" {
|
|
break
|
|
}
|
|
|
|
f := runtime.FuncForPC(pc)
|
|
if f == nil {
|
|
break
|
|
}
|
|
name = f.Name()
|
|
|
|
// testing.tRunner is the standard library function that calls
|
|
// tests. Subtests are called directly by tRunner, without going through
|
|
// the Test/Benchmark/Example function that contains the t.Run calls, so
|
|
// with subtests we should break when we hit tRunner, without adding it
|
|
// to the list of callers.
|
|
if name == "testing.tRunner" {
|
|
break
|
|
}
|
|
|
|
parts := strings.Split(file, "/")
|
|
if len(parts) > 1 {
|
|
filename := parts[len(parts)-1]
|
|
dir := parts[len(parts)-2]
|
|
if (dir != "assert" && dir != "mock" && dir != "require") || filename == "mock_test.go" {
|
|
callers = append(callers, fmt.Sprintf("%s:%d", file, line))
|
|
}
|
|
}
|
|
|
|
// Drop the package
|
|
segments := strings.Split(name, ".")
|
|
name = segments[len(segments)-1]
|
|
if isTest(name, "Test") ||
|
|
isTest(name, "Benchmark") ||
|
|
isTest(name, "Example") {
|
|
break
|
|
}
|
|
if !more {
|
|
break
|
|
}
|
|
}
|
|
// We know we already have less than a buffer's worth of frames
|
|
offset += stackFrameBufferSize
|
|
|
|
}
|
|
|
|
return callers
|
|
}
|
|
|
|
// Stolen from the `go test` tool.
|
|
// isTest tells whether name looks like a test (or benchmark, according to prefix).
|
|
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
|
|
// We don't want TesticularCancer.
|
|
func isTest(name, prefix string) bool {
|
|
if !strings.HasPrefix(name, prefix) {
|
|
return false
|
|
}
|
|
if len(name) == len(prefix) { // "Test" is ok
|
|
return true
|
|
}
|
|
r, _ := utf8.DecodeRuneInString(name[len(prefix):])
|
|
return !unicode.IsLower(r)
|
|
}
|
|
|
|
func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
|
|
if len(msgAndArgs) == 0 || msgAndArgs == nil {
|
|
return ""
|
|
}
|
|
if len(msgAndArgs) == 1 {
|
|
msg := msgAndArgs[0]
|
|
if msgAsStr, ok := msg.(string); ok {
|
|
return msgAsStr
|
|
}
|
|
return fmt.Sprintf("%+v", msg)
|
|
}
|
|
if len(msgAndArgs) > 1 {
|
|
return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// 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).
|
|
// The longestLabelLen parameter specifies the length of the longest label in the output (required because this is the
|
|
// basis on which the alignment occurs).
|
|
func indentMessageLines(message string, longestLabelLen int) string {
|
|
outBuf := new(bytes.Buffer)
|
|
|
|
for i, scanner := 0, bufio.NewScanner(strings.NewReader(message)); scanner.Scan(); i++ {
|
|
// no need to align first line because it starts at the correct location (after the label)
|
|
if i != 0 {
|
|
// append alignLen+1 spaces to align with "{{longestLabel}}:" before adding tab
|
|
outBuf.WriteString("\n\t" + strings.Repeat(" ", longestLabelLen+1) + "\t")
|
|
}
|
|
outBuf.WriteString(scanner.Text())
|
|
}
|
|
|
|
return outBuf.String()
|
|
}
|
|
|
|
type failNower interface {
|
|
FailNow()
|
|
}
|
|
|
|
// FailNow fails test
|
|
func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
Fail(t, failureMessage, msgAndArgs...)
|
|
|
|
// We cannot extend TestingT with FailNow() and
|
|
// maintain backwards compatibility, so we fallback
|
|
// to panicking when FailNow is not available in
|
|
// TestingT.
|
|
// See issue #263
|
|
|
|
if t, ok := t.(failNower); ok {
|
|
t.FailNow()
|
|
} else {
|
|
panic("test failed and t is missing `FailNow()`")
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Fail reports a failure through
|
|
func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
content := []labeledContent{
|
|
{"Error Trace", strings.Join(CallerInfo(), "\n\t\t\t")},
|
|
{"Error", failureMessage},
|
|
}
|
|
|
|
// Add test name if the Go version supports it
|
|
if n, ok := t.(interface {
|
|
Name() string
|
|
}); ok {
|
|
content = append(content, labeledContent{"Test", n.Name()})
|
|
}
|
|
|
|
message := messageFromMsgAndArgs(msgAndArgs...)
|
|
if len(message) > 0 {
|
|
content = append(content, labeledContent{"Messages", message})
|
|
}
|
|
|
|
t.Errorf("\n%s", ""+labeledOutput(content...))
|
|
|
|
return false
|
|
}
|
|
|
|
type labeledContent struct {
|
|
label string
|
|
content string
|
|
}
|
|
|
|
// labeledOutput returns a string consisting of the provided labeledContent. Each labeled output is appended in the following manner:
|
|
//
|
|
// \t{{label}}:{{align_spaces}}\t{{content}}\n
|
|
//
|
|
// The initial carriage return is required to undo/erase any padding added by testing.T.Errorf. The "\t{{label}}:" is for the label.
|
|
// If a label is shorter than the longest label provided, padding spaces are added to make all the labels match in length. Once this
|
|
// alignment is achieved, "\t{{content}}\n" is added for the output.
|
|
//
|
|
// If the content of the labeledOutput contains line breaks, the subsequent lines are aligned so that they start at the same location as the first line.
|
|
func labeledOutput(content ...labeledContent) string {
|
|
longestLabel := 0
|
|
for _, v := range content {
|
|
if len(v.label) > longestLabel {
|
|
longestLabel = len(v.label)
|
|
}
|
|
}
|
|
var output string
|
|
for _, v := range content {
|
|
output += "\t" + v.label + ":" + strings.Repeat(" ", longestLabel-len(v.label)) + "\t" + indentMessageLines(v.content, longestLabel) + "\n"
|
|
}
|
|
return output
|
|
}
|
|
|
|
// Implements asserts that an object is implemented by the specified interface.
|
|
//
|
|
// assert.Implements(t, (*MyInterface)(nil), new(MyObject))
|
|
func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
interfaceType := reflect.TypeOf(interfaceObject).Elem()
|
|
|
|
if object == nil {
|
|
return Fail(t, fmt.Sprintf("Cannot check if nil implements %v", interfaceType), msgAndArgs...)
|
|
}
|
|
if !reflect.TypeOf(object).Implements(interfaceType) {
|
|
return Fail(t, fmt.Sprintf("%T must implement %v", object, interfaceType), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// NotImplements asserts that an object does not implement the specified interface.
|
|
//
|
|
// assert.NotImplements(t, (*MyInterface)(nil), new(MyObject))
|
|
func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
interfaceType := reflect.TypeOf(interfaceObject).Elem()
|
|
|
|
if object == nil {
|
|
return Fail(t, fmt.Sprintf("Cannot check if nil does not implement %v", interfaceType), msgAndArgs...)
|
|
}
|
|
if reflect.TypeOf(object).Implements(interfaceType) {
|
|
return Fail(t, fmt.Sprintf("%T implements %v", object, interfaceType), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func isType(expectedType, object interface{}) bool {
|
|
return ObjectsAreEqual(reflect.TypeOf(object), reflect.TypeOf(expectedType))
|
|
}
|
|
|
|
// IsType asserts that the specified objects are of the same type.
|
|
//
|
|
// assert.IsType(t, &MyStruct{}, &MyStruct{})
|
|
func IsType(t TestingT, expectedType, object interface{}, msgAndArgs ...interface{}) bool {
|
|
if isType(expectedType, object) {
|
|
return true
|
|
}
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
return Fail(t, fmt.Sprintf("Object expected to be of type %T, but was %T", expectedType, object), msgAndArgs...)
|
|
}
|
|
|
|
// IsNotType asserts that the specified objects are not of the same type.
|
|
//
|
|
// assert.IsNotType(t, &NotMyStruct{}, &MyStruct{})
|
|
func IsNotType(t TestingT, theType, object interface{}, msgAndArgs ...interface{}) bool {
|
|
if !isType(theType, object) {
|
|
return true
|
|
}
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
return Fail(t, fmt.Sprintf("Object type expected to be different than %T", theType), msgAndArgs...)
|
|
}
|
|
|
|
// Equal asserts that two objects are equal.
|
|
//
|
|
// assert.Equal(t, 123, 123)
|
|
//
|
|
// Pointer variable equality is determined based on the equality of the
|
|
// referenced values (as opposed to the memory addresses). Function equality
|
|
// cannot be determined and will always fail.
|
|
func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if err := validateEqualArgs(expected, actual); err != nil {
|
|
return Fail(t, fmt.Sprintf("Invalid operation: %#v == %#v (%s)",
|
|
expected, actual, err), msgAndArgs...)
|
|
}
|
|
|
|
if !ObjectsAreEqual(expected, actual) {
|
|
diff := diff(expected, actual)
|
|
expected, actual = formatUnequalValues(expected, actual)
|
|
return Fail(t, fmt.Sprintf("Not equal: \n"+
|
|
"expected: %s\n"+
|
|
"actual : %s%s", expected, actual, diff), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// validateEqualArgs checks whether provided arguments can be safely used in the
|
|
// Equal/NotEqual functions.
|
|
func validateEqualArgs(expected, actual interface{}) error {
|
|
if expected == nil && actual == nil {
|
|
return nil
|
|
}
|
|
|
|
if isFunction(expected) || isFunction(actual) {
|
|
return errors.New("cannot take func type as argument")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Same asserts that two pointers reference the same object.
|
|
//
|
|
// assert.Same(t, ptr1, ptr2)
|
|
//
|
|
// Both arguments must be pointer variables. Pointer variable sameness is
|
|
// determined based on the equality of both type and value.
|
|
func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
same, ok := samePointers(expected, actual)
|
|
if !ok {
|
|
return Fail(t, "Both arguments must be pointers", msgAndArgs...)
|
|
}
|
|
|
|
if !same {
|
|
// both are pointers but not the same type & pointing to the same address
|
|
return Fail(t, fmt.Sprintf("Not same: \n"+
|
|
"expected: %p %#[1]v\n"+
|
|
"actual : %p %#[2]v",
|
|
expected, actual), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// NotSame asserts that two pointers do not reference the same object.
|
|
//
|
|
// assert.NotSame(t, ptr1, ptr2)
|
|
//
|
|
// Both arguments must be pointer variables. Pointer variable sameness is
|
|
// determined based on the equality of both type and value.
|
|
func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
same, ok := samePointers(expected, actual)
|
|
if !ok {
|
|
// fails when the arguments are not pointers
|
|
return !(Fail(t, "Both arguments must be pointers", msgAndArgs...))
|
|
}
|
|
|
|
if same {
|
|
return Fail(t, fmt.Sprintf(
|
|
"Expected and actual point to the same object: %p %#[1]v",
|
|
expected), msgAndArgs...)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// samePointers checks if two generic interface objects are pointers of the same
|
|
// type pointing to the same object. It returns two values: same indicating if
|
|
// they are the same type and point to the same object, and ok indicating that
|
|
// both inputs are pointers.
|
|
func samePointers(first, second interface{}) (same bool, ok bool) {
|
|
firstPtr, secondPtr := reflect.ValueOf(first), reflect.ValueOf(second)
|
|
if firstPtr.Kind() != reflect.Ptr || secondPtr.Kind() != reflect.Ptr {
|
|
return false, false // not both are pointers
|
|
}
|
|
|
|
firstType, secondType := reflect.TypeOf(first), reflect.TypeOf(second)
|
|
if firstType != secondType {
|
|
return false, true // both are pointers, but of different types
|
|
}
|
|
|
|
// compare pointer addresses
|
|
return first == second, true
|
|
}
|
|
|
|
// formatUnequalValues takes two values of arbitrary types and returns string
|
|
// representations appropriate to be presented to the user.
|
|
//
|
|
// 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 parentheses similar
|
|
// 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(%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 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 convertible to the larger
|
|
// type and equal.
|
|
//
|
|
// assert.EqualValues(t, uint32(123), int32(123))
|
|
func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
if !ObjectsAreEqualValues(expected, actual) {
|
|
diff := diff(expected, actual)
|
|
expected, actual = formatUnequalValues(expected, actual)
|
|
return Fail(t, fmt.Sprintf("Not equal: \n"+
|
|
"expected: %s\n"+
|
|
"actual : %s%s", expected, actual, diff), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// 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...)
|
|
}
|
|
|
|
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.
|
|
//
|
|
// assert.Exactly(t, int32(123), int64(123))
|
|
func Exactly(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...)
|
|
}
|
|
|
|
return Equal(t, expected, actual, msgAndArgs...)
|
|
}
|
|
|
|
// NotNil asserts that the specified object is not nil.
|
|
//
|
|
// assert.NotNil(t, err)
|
|
func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
|
if !isNil(object) {
|
|
return true
|
|
}
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
return Fail(t, "Expected value not to be nil.", msgAndArgs...)
|
|
}
|
|
|
|
// isNil checks if a specified object is nil or not, without Failing.
|
|
func isNil(object interface{}) bool {
|
|
if object == nil {
|
|
return true
|
|
}
|
|
|
|
value := reflect.ValueOf(object)
|
|
switch value.Kind() {
|
|
case
|
|
reflect.Chan, reflect.Func,
|
|
reflect.Interface, reflect.Map,
|
|
reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
|
|
|
|
return value.IsNil()
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Nil asserts that the specified object is nil.
|
|
//
|
|
// assert.Nil(t, err)
|
|
func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
|
if isNil(object) {
|
|
return true
|
|
}
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
return Fail(t, fmt.Sprintf("Expected nil, but got: %#v", object), msgAndArgs...)
|
|
}
|
|
|
|
// isEmpty gets whether the specified object is considered empty or not.
|
|
func isEmpty(object interface{}) bool {
|
|
// get nil case out of the way
|
|
if object == nil {
|
|
return true
|
|
}
|
|
|
|
objValue := reflect.ValueOf(object)
|
|
|
|
switch objValue.Kind() {
|
|
// collection types are empty when they have no element
|
|
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
|
|
case reflect.Ptr:
|
|
if objValue.IsNil() {
|
|
return true
|
|
}
|
|
deref := objValue.Elem().Interface()
|
|
return isEmpty(deref)
|
|
// 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())
|
|
}
|
|
}
|
|
|
|
// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either
|
|
// a slice or a channel with len == 0.
|
|
//
|
|
// assert.Empty(t, obj)
|
|
func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
|
pass := isEmpty(object)
|
|
if !pass {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
Fail(t, fmt.Sprintf("Should be empty, but was %v", object), msgAndArgs...)
|
|
}
|
|
|
|
return pass
|
|
}
|
|
|
|
// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either
|
|
// a slice or a channel with len == 0.
|
|
//
|
|
// if assert.NotEmpty(t, obj) {
|
|
// assert.Equal(t, "two", obj[1])
|
|
// }
|
|
func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
|
|
pass := !isEmpty(object)
|
|
if !pass {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
Fail(t, fmt.Sprintf("Should NOT be empty, but was %v", object), msgAndArgs...)
|
|
}
|
|
|
|
return pass
|
|
}
|
|
|
|
// getLen tries to get the length of an object.
|
|
// It returns (0, false) if impossible.
|
|
func getLen(x interface{}) (length int, ok bool) {
|
|
v := reflect.ValueOf(x)
|
|
defer func() {
|
|
ok = recover() == nil
|
|
}()
|
|
return v.Len(), true
|
|
}
|
|
|
|
// Len asserts that the specified object has specific length.
|
|
// Len also fails if the object has a type that len() not accept.
|
|
//
|
|
// assert.Len(t, mySlice, 3)
|
|
func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
l, ok := getLen(object)
|
|
if !ok {
|
|
return Fail(t, fmt.Sprintf("\"%v\" could not be applied builtin len()", object), msgAndArgs...)
|
|
}
|
|
|
|
if l != length {
|
|
return Fail(t, fmt.Sprintf("\"%v\" should have %d item(s), but has %d", object, length, l), msgAndArgs...)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// True asserts that the specified value is true.
|
|
//
|
|
// assert.True(t, myBool)
|
|
func True(t TestingT, value bool, msgAndArgs ...interface{}) bool {
|
|
if !value {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
return Fail(t, "Should be true", msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// False asserts that the specified value is false.
|
|
//
|
|
// assert.False(t, myBool)
|
|
func False(t TestingT, value bool, msgAndArgs ...interface{}) bool {
|
|
if value {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
return Fail(t, "Should be false", msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// NotEqual asserts that the specified values are NOT equal.
|
|
//
|
|
// assert.NotEqual(t, obj1, obj2)
|
|
//
|
|
// Pointer variable equality is determined based on the equality of the
|
|
// referenced values (as opposed to the memory addresses).
|
|
func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if err := validateEqualArgs(expected, actual); err != nil {
|
|
return Fail(t, fmt.Sprintf("Invalid operation: %#v != %#v (%s)",
|
|
expected, actual, err), msgAndArgs...)
|
|
}
|
|
|
|
if ObjectsAreEqual(expected, actual) {
|
|
return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// NotEqualValues asserts that two objects are not equal even when converted to the same type
|
|
//
|
|
// assert.NotEqualValues(t, obj1, obj2)
|
|
func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
if ObjectsAreEqualValues(expected, actual) {
|
|
return Fail(t, fmt.Sprintf("Should not be: %#v\n", actual), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// containsElement try loop over the list check if the list includes the element.
|
|
// return (false, false) if impossible.
|
|
// return (true, false) if element was not found.
|
|
// return (true, true) if element was found.
|
|
func containsElement(list interface{}, element interface{}) (ok, found bool) {
|
|
listValue := reflect.ValueOf(list)
|
|
listType := reflect.TypeOf(list)
|
|
if listType == nil {
|
|
return false, false
|
|
}
|
|
listKind := listType.Kind()
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
ok = false
|
|
found = false
|
|
}
|
|
}()
|
|
|
|
if listKind == reflect.String {
|
|
elementValue := reflect.ValueOf(element)
|
|
return true, strings.Contains(listValue.String(), elementValue.String())
|
|
}
|
|
|
|
if listKind == reflect.Map {
|
|
mapKeys := listValue.MapKeys()
|
|
for i := 0; i < len(mapKeys); i++ {
|
|
if ObjectsAreEqual(mapKeys[i].Interface(), element) {
|
|
return true, true
|
|
}
|
|
}
|
|
return true, false
|
|
}
|
|
|
|
for i := 0; i < listValue.Len(); i++ {
|
|
if ObjectsAreEqual(listValue.Index(i).Interface(), element) {
|
|
return true, true
|
|
}
|
|
}
|
|
return true, false
|
|
}
|
|
|
|
// Contains asserts that the specified string, list(array, slice...) or map contains the
|
|
// specified substring or element.
|
|
//
|
|
// assert.Contains(t, "Hello World", "World")
|
|
// assert.Contains(t, ["Hello", "World"], "World")
|
|
// assert.Contains(t, {"Hello": "World"}, "Hello")
|
|
func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
ok, found := containsElement(s, contains)
|
|
if !ok {
|
|
return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...)
|
|
}
|
|
if !found {
|
|
return Fail(t, fmt.Sprintf("%#v does not contain %#v", s, contains), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the
|
|
// specified substring or element.
|
|
//
|
|
// assert.NotContains(t, "Hello World", "Earth")
|
|
// assert.NotContains(t, ["Hello", "World"], "Earth")
|
|
// assert.NotContains(t, {"Hello": "World"}, "Earth")
|
|
func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
ok, found := containsElement(s, contains)
|
|
if !ok {
|
|
return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", s), msgAndArgs...)
|
|
}
|
|
if found {
|
|
return Fail(t, fmt.Sprintf("%#v should not contain %#v", s, contains), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Subset asserts that the list (array, slice, or map) contains all elements
|
|
// given in the subset (array, slice, or map).
|
|
// Map elements are key-value pairs unless compared with an array or slice where
|
|
// only the map key is evaluated.
|
|
//
|
|
// assert.Subset(t, [1, 2, 3], [1, 2])
|
|
// assert.Subset(t, {"x": 1, "y": 2}, {"x": 1})
|
|
// assert.Subset(t, [1, 2, 3], {1: "one", 2: "two"})
|
|
// assert.Subset(t, {"x": 1, "y": 2}, ["x"])
|
|
func Subset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if subset == nil {
|
|
return true // we consider nil to be equal to the nil set
|
|
}
|
|
|
|
listKind := reflect.TypeOf(list).Kind()
|
|
if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
|
|
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 && subsetKind != reflect.Map {
|
|
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
|
|
}
|
|
|
|
if subsetKind == reflect.Map && listKind == reflect.Map {
|
|
subsetMap := reflect.ValueOf(subset)
|
|
actualMap := reflect.ValueOf(list)
|
|
|
|
for _, k := range subsetMap.MapKeys() {
|
|
ev := subsetMap.MapIndex(k)
|
|
av := actualMap.MapIndex(k)
|
|
|
|
if !av.IsValid() {
|
|
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
|
|
}
|
|
|
|
subsetList := reflect.ValueOf(subset)
|
|
if subsetKind == reflect.Map {
|
|
keys := make([]interface{}, subsetList.Len())
|
|
for idx, key := range subsetList.MapKeys() {
|
|
keys[idx] = key.Interface()
|
|
}
|
|
subsetList = reflect.ValueOf(keys)
|
|
}
|
|
for i := 0; i < subsetList.Len(); i++ {
|
|
element := subsetList.Index(i).Interface()
|
|
ok, found := containsElement(list, element)
|
|
if !ok {
|
|
return Fail(t, fmt.Sprintf("%#v could not be applied builtin len()", list), msgAndArgs...)
|
|
}
|
|
if !found {
|
|
return Fail(t, fmt.Sprintf("%#v does not contain %#v", list, element), msgAndArgs...)
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// NotSubset asserts that the list (array, slice, or map) does NOT contain all
|
|
// elements given in the subset (array, slice, or map).
|
|
// Map elements are key-value pairs unless compared with an array or slice where
|
|
// only the map key is evaluated.
|
|
//
|
|
// assert.NotSubset(t, [1, 3, 4], [1, 2])
|
|
// assert.NotSubset(t, {"x": 1, "y": 2}, {"z": 3})
|
|
// assert.NotSubset(t, [1, 3, 4], {1: "one", 2: "two"})
|
|
// assert.NotSubset(t, {"x": 1, "y": 2}, ["z"])
|
|
func NotSubset(t TestingT, list, subset interface{}, msgAndArgs ...interface{}) (ok bool) {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if subset == nil {
|
|
return Fail(t, "nil is the empty set which is a subset of every set", msgAndArgs...)
|
|
}
|
|
|
|
listKind := reflect.TypeOf(list).Kind()
|
|
if listKind != reflect.Array && listKind != reflect.Slice && listKind != reflect.Map {
|
|
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 && subsetKind != reflect.Map {
|
|
return Fail(t, fmt.Sprintf("%q has an unsupported type %s", subset, subsetKind), msgAndArgs...)
|
|
}
|
|
|
|
if subsetKind == reflect.Map && listKind == reflect.Map {
|
|
subsetMap := reflect.ValueOf(subset)
|
|
actualMap := reflect.ValueOf(list)
|
|
|
|
for _, k := range subsetMap.MapKeys() {
|
|
ev := subsetMap.MapIndex(k)
|
|
av := actualMap.MapIndex(k)
|
|
|
|
if !av.IsValid() {
|
|
return true
|
|
}
|
|
if !ObjectsAreEqual(ev.Interface(), av.Interface()) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...)
|
|
}
|
|
|
|
subsetList := reflect.ValueOf(subset)
|
|
if subsetKind == reflect.Map {
|
|
keys := make([]interface{}, subsetList.Len())
|
|
for idx, key := range subsetList.MapKeys() {
|
|
keys[idx] = key.Interface()
|
|
}
|
|
subsetList = reflect.ValueOf(keys)
|
|
}
|
|
for i := 0; i < subsetList.Len(); i++ {
|
|
element := subsetList.Index(i).Interface()
|
|
ok, found := containsElement(list, element)
|
|
if !ok {
|
|
return Fail(t, fmt.Sprintf("%q could not be applied builtin len()", list), msgAndArgs...)
|
|
}
|
|
if !found {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return Fail(t, fmt.Sprintf("%q is a subset of %q", subset, list), msgAndArgs...)
|
|
}
|
|
|
|
// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified
|
|
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
|
// the number of appearances of each of them in both lists should match.
|
|
//
|
|
// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2])
|
|
func ElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if isEmpty(listA) && isEmpty(listB) {
|
|
return true
|
|
}
|
|
|
|
if !isList(t, listA, msgAndArgs...) || !isList(t, listB, msgAndArgs...) {
|
|
return false
|
|
}
|
|
|
|
extraA, extraB := diffLists(listA, listB)
|
|
|
|
if len(extraA) == 0 && len(extraB) == 0 {
|
|
return true
|
|
}
|
|
|
|
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()
|
|
|
|
// Mark indexes in bValue that we already used
|
|
visited := make([]bool, bLen)
|
|
for i := 0; i < aLen; i++ {
|
|
element := aValue.Index(i).Interface()
|
|
found := false
|
|
for j := 0; j < bLen; j++ {
|
|
if visited[j] {
|
|
continue
|
|
}
|
|
if ObjectsAreEqual(bValue.Index(j).Interface(), element) {
|
|
visited[j] = true
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
extraA = append(extraA, element)
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
|
|
// NotElementsMatch asserts that the specified listA(array, slice...) is NOT equal to specified
|
|
// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements,
|
|
// the number of appearances of each of them in both lists should not match.
|
|
// This is an inverse of ElementsMatch.
|
|
//
|
|
// assert.NotElementsMatch(t, [1, 1, 2, 3], [1, 1, 2, 3]) -> false
|
|
//
|
|
// assert.NotElementsMatch(t, [1, 1, 2, 3], [1, 2, 3]) -> true
|
|
//
|
|
// assert.NotElementsMatch(t, [1, 2, 3], [1, 2, 4]) -> true
|
|
func NotElementsMatch(t TestingT, listA, listB interface{}, msgAndArgs ...interface{}) (ok bool) {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if isEmpty(listA) && isEmpty(listB) {
|
|
return Fail(t, "listA and listB contain the same elements", msgAndArgs)
|
|
}
|
|
|
|
if !isList(t, listA, msgAndArgs...) {
|
|
return Fail(t, "listA is not a list type", msgAndArgs...)
|
|
}
|
|
if !isList(t, listB, msgAndArgs...) {
|
|
return Fail(t, "listB is not a list type", msgAndArgs...)
|
|
}
|
|
|
|
extraA, extraB := diffLists(listA, listB)
|
|
if len(extraA) == 0 && len(extraB) == 0 {
|
|
return Fail(t, "listA and listB contain the same elements", msgAndArgs)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Condition uses a Comparison to assert a complex condition.
|
|
func Condition(t TestingT, comp Comparison, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
result := comp()
|
|
if !result {
|
|
Fail(t, "Condition failed!", msgAndArgs...)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// PanicTestFunc defines a func that should be passed to the assert.Panics and assert.NotPanics
|
|
// methods, and represents a simple func that takes no arguments, and returns nothing.
|
|
type PanicTestFunc func()
|
|
|
|
// didPanic returns true if the function passed to it panics. Otherwise, it returns false.
|
|
func didPanic(f PanicTestFunc) (didPanic bool, message interface{}, stack string) {
|
|
didPanic = true
|
|
|
|
defer func() {
|
|
message = recover()
|
|
if didPanic {
|
|
stack = string(debug.Stack())
|
|
}
|
|
}()
|
|
|
|
// call the target function
|
|
f()
|
|
didPanic = false
|
|
|
|
return
|
|
}
|
|
|
|
// Panics asserts that the code inside the specified PanicTestFunc panics.
|
|
//
|
|
// assert.Panics(t, func(){ GoCrazy() })
|
|
func Panics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
if funcDidPanic, panicValue, _ := didPanic(f); !funcDidPanic {
|
|
return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", f, panicValue), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that
|
|
// the recovered panic value equals the expected panic value.
|
|
//
|
|
// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() })
|
|
func PanicsWithValue(t TestingT, expected interface{}, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
funcDidPanic, panicValue, panickedStack := didPanic(f)
|
|
if !funcDidPanic {
|
|
return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", f, panicValue), msgAndArgs...)
|
|
}
|
|
if panicValue != expected {
|
|
return Fail(t, fmt.Sprintf("func %#v should panic with value:\t%#v\n\tPanic value:\t%#v\n\tPanic stack:\t%s", f, expected, panicValue, panickedStack), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// PanicsWithError asserts that the code inside the specified PanicTestFunc
|
|
// panics, and that the recovered panic value is an error that satisfies the
|
|
// EqualError comparison.
|
|
//
|
|
// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() })
|
|
func PanicsWithError(t TestingT, errString string, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
funcDidPanic, panicValue, panickedStack := didPanic(f)
|
|
if !funcDidPanic {
|
|
return Fail(t, fmt.Sprintf("func %#v should panic\n\tPanic value:\t%#v", f, panicValue), msgAndArgs...)
|
|
}
|
|
panicErr, ok := panicValue.(error)
|
|
if !ok || panicErr.Error() != errString {
|
|
return Fail(t, fmt.Sprintf("func %#v should panic with error message:\t%#v\n\tPanic value:\t%#v\n\tPanic stack:\t%s", f, errString, panicValue, panickedStack), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic.
|
|
//
|
|
// assert.NotPanics(t, func(){ RemainCalm() })
|
|
func NotPanics(t TestingT, f PanicTestFunc, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
if funcDidPanic, panicValue, panickedStack := didPanic(f); funcDidPanic {
|
|
return Fail(t, fmt.Sprintf("func %#v should not panic\n\tPanic value:\t%v\n\tPanic stack:\t%s", f, panicValue, panickedStack), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// WithinDuration asserts that the two times are within duration delta of each other.
|
|
//
|
|
// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second)
|
|
func WithinDuration(t TestingT, expected, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
dt := expected.Sub(actual)
|
|
if dt < -delta || dt > delta {
|
|
return Fail(t, fmt.Sprintf("Max difference between %v and %v allowed is %v, but difference was %v", expected, actual, delta, dt), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// WithinRange asserts that a time is within a time range (inclusive).
|
|
//
|
|
// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second))
|
|
func WithinRange(t TestingT, actual, start, end time.Time, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
if end.Before(start) {
|
|
return Fail(t, "Start should be before end", msgAndArgs...)
|
|
}
|
|
|
|
if actual.Before(start) {
|
|
return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is before the range", actual, start, end), msgAndArgs...)
|
|
} else if actual.After(end) {
|
|
return Fail(t, fmt.Sprintf("Time %v expected to be in time range %v to %v, but is after the range", actual, start, end), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func toFloat(x interface{}) (float64, bool) {
|
|
var xf float64
|
|
xok := true
|
|
|
|
switch xn := x.(type) {
|
|
case uint:
|
|
xf = float64(xn)
|
|
case uint8:
|
|
xf = float64(xn)
|
|
case uint16:
|
|
xf = float64(xn)
|
|
case uint32:
|
|
xf = float64(xn)
|
|
case uint64:
|
|
xf = float64(xn)
|
|
case int:
|
|
xf = float64(xn)
|
|
case int8:
|
|
xf = float64(xn)
|
|
case int16:
|
|
xf = float64(xn)
|
|
case int32:
|
|
xf = float64(xn)
|
|
case int64:
|
|
xf = float64(xn)
|
|
case float32:
|
|
xf = float64(xn)
|
|
case float64:
|
|
xf = xn
|
|
case time.Duration:
|
|
xf = float64(xn)
|
|
default:
|
|
xok = false
|
|
}
|
|
|
|
return xf, xok
|
|
}
|
|
|
|
// InDelta asserts that the two numerals are within delta of each other.
|
|
//
|
|
// assert.InDelta(t, math.Pi, 22/7.0, 0.01)
|
|
func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
af, aok := toFloat(expected)
|
|
bf, bok := toFloat(actual)
|
|
|
|
if !aok || !bok {
|
|
return Fail(t, "Parameters must be numerical", msgAndArgs...)
|
|
}
|
|
|
|
if math.IsNaN(af) && math.IsNaN(bf) {
|
|
return true
|
|
}
|
|
|
|
if math.IsNaN(af) {
|
|
return Fail(t, "Expected must not be NaN", msgAndArgs...)
|
|
}
|
|
|
|
if math.IsNaN(bf) {
|
|
return Fail(t, fmt.Sprintf("Expected %v with delta %v, but was NaN", expected, delta), msgAndArgs...)
|
|
}
|
|
|
|
dt := af - bf
|
|
if dt < -delta || dt > delta {
|
|
return Fail(t, fmt.Sprintf("Max difference between %v and %v allowed is %v, but difference was %v", expected, actual, delta, dt), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// InDeltaSlice is the same as InDelta, except it compares two slices.
|
|
func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if expected == nil || actual == nil ||
|
|
reflect.TypeOf(actual).Kind() != reflect.Slice ||
|
|
reflect.TypeOf(expected).Kind() != reflect.Slice {
|
|
return Fail(t, "Parameters must be slice", msgAndArgs...)
|
|
}
|
|
|
|
actualSlice := reflect.ValueOf(actual)
|
|
expectedSlice := reflect.ValueOf(expected)
|
|
|
|
for i := 0; i < actualSlice.Len(); i++ {
|
|
result := InDelta(t, actualSlice.Index(i).Interface(), expectedSlice.Index(i).Interface(), delta, msgAndArgs...)
|
|
if !result {
|
|
return result
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys.
|
|
func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if expected == nil || actual == nil ||
|
|
reflect.TypeOf(actual).Kind() != reflect.Map ||
|
|
reflect.TypeOf(expected).Kind() != reflect.Map {
|
|
return Fail(t, "Arguments must be maps", msgAndArgs...)
|
|
}
|
|
|
|
expectedMap := reflect.ValueOf(expected)
|
|
actualMap := reflect.ValueOf(actual)
|
|
|
|
if expectedMap.Len() != actualMap.Len() {
|
|
return Fail(t, "Arguments must have the same number of keys", msgAndArgs...)
|
|
}
|
|
|
|
for _, k := range expectedMap.MapKeys() {
|
|
ev := expectedMap.MapIndex(k)
|
|
av := actualMap.MapIndex(k)
|
|
|
|
if !ev.IsValid() {
|
|
return Fail(t, fmt.Sprintf("missing key %q in expected map", k), msgAndArgs...)
|
|
}
|
|
|
|
if !av.IsValid() {
|
|
return Fail(t, fmt.Sprintf("missing key %q in actual map", k), msgAndArgs...)
|
|
}
|
|
|
|
if !InDelta(
|
|
t,
|
|
ev.Interface(),
|
|
av.Interface(),
|
|
delta,
|
|
msgAndArgs...,
|
|
) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func calcRelativeError(expected, actual interface{}) (float64, error) {
|
|
af, aok := toFloat(expected)
|
|
bf, bok := toFloat(actual)
|
|
if !aok || !bok {
|
|
return 0, fmt.Errorf("Parameters must be numerical")
|
|
}
|
|
if math.IsNaN(af) && math.IsNaN(bf) {
|
|
return 0, nil
|
|
}
|
|
if math.IsNaN(af) {
|
|
return 0, errors.New("expected value must not be NaN")
|
|
}
|
|
if af == 0 {
|
|
return 0, fmt.Errorf("expected value must have a value other than zero to calculate the relative error")
|
|
}
|
|
if math.IsNaN(bf) {
|
|
return 0, errors.New("actual value must not be NaN")
|
|
}
|
|
|
|
return math.Abs(af-bf) / math.Abs(af), nil
|
|
}
|
|
|
|
// InEpsilon asserts that expected and actual have a relative error less than epsilon
|
|
func InEpsilon(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if math.IsNaN(epsilon) {
|
|
return Fail(t, "epsilon must not be NaN", msgAndArgs...)
|
|
}
|
|
actualEpsilon, err := calcRelativeError(expected, actual)
|
|
if err != nil {
|
|
return Fail(t, err.Error(), msgAndArgs...)
|
|
}
|
|
if math.IsNaN(actualEpsilon) {
|
|
return Fail(t, "relative error is NaN", msgAndArgs...)
|
|
}
|
|
if actualEpsilon > epsilon {
|
|
return Fail(t, fmt.Sprintf("Relative error is too high: %#v (expected)\n"+
|
|
" < %#v (actual)", epsilon, actualEpsilon), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices.
|
|
func InEpsilonSlice(t TestingT, expected, actual interface{}, epsilon float64, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
if expected == nil || actual == nil {
|
|
return Fail(t, "Parameters must be slice", msgAndArgs...)
|
|
}
|
|
|
|
expectedSlice := reflect.ValueOf(expected)
|
|
actualSlice := reflect.ValueOf(actual)
|
|
|
|
if expectedSlice.Type().Kind() != reflect.Slice {
|
|
return Fail(t, "Expected value must be slice", msgAndArgs...)
|
|
}
|
|
|
|
expectedLen := expectedSlice.Len()
|
|
if !IsType(t, expected, actual) || !Len(t, actual, expectedLen) {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i < expectedLen; i++ {
|
|
if !InEpsilon(t, expectedSlice.Index(i).Interface(), actualSlice.Index(i).Interface(), epsilon, "at index %d", i) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
/*
|
|
Errors
|
|
*/
|
|
|
|
// NoError asserts that a function returned no error (i.e. `nil`).
|
|
//
|
|
// actualObj, err := SomeFunction()
|
|
// if assert.NoError(t, err) {
|
|
// assert.Equal(t, expectedObj, actualObj)
|
|
// }
|
|
func NoError(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
|
if err != nil {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
return Fail(t, fmt.Sprintf("Received unexpected error:\n%+v", err), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Error asserts that a function returned an error (i.e. not `nil`).
|
|
//
|
|
// actualObj, err := SomeFunction()
|
|
// assert.Error(t, err)
|
|
func Error(t TestingT, err error, msgAndArgs ...interface{}) bool {
|
|
if err == nil {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
return Fail(t, "An error is expected but got nil.", msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// EqualError asserts that a function returned an error (i.e. not `nil`)
|
|
// and that it is equal to the provided error.
|
|
//
|
|
// actualObj, err := SomeFunction()
|
|
// assert.EqualError(t, err, expectedErrorString)
|
|
func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if !Error(t, theError, msgAndArgs...) {
|
|
return false
|
|
}
|
|
expected := errString
|
|
actual := theError.Error()
|
|
// don't need to use deep equals here, we know they are both strings
|
|
if expected != actual {
|
|
return Fail(t, fmt.Sprintf("Error message not equal:\n"+
|
|
"expected: %q\n"+
|
|
"actual : %q", expected, actual), msgAndArgs...)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ErrorContains asserts that a function returned an error (i.e. not `nil`)
|
|
// and that the error contains the specified substring.
|
|
//
|
|
// actualObj, err := SomeFunction()
|
|
// assert.ErrorContains(t, err, expectedErrorSubString)
|
|
func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if !Error(t, theError, msgAndArgs...) {
|
|
return false
|
|
}
|
|
|
|
actual := theError.Error()
|
|
if !strings.Contains(actual, contains) {
|
|
return Fail(t, fmt.Sprintf("Error %#v does not contain %#v", actual, contains), msgAndArgs...)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// matchRegexp return true if a specified regexp matches a string.
|
|
func matchRegexp(rx interface{}, str interface{}) bool {
|
|
var r *regexp.Regexp
|
|
if rr, ok := rx.(*regexp.Regexp); ok {
|
|
r = rr
|
|
} else {
|
|
r = regexp.MustCompile(fmt.Sprint(rx))
|
|
}
|
|
|
|
switch v := str.(type) {
|
|
case []byte:
|
|
return r.Match(v)
|
|
case string:
|
|
return r.MatchString(v)
|
|
default:
|
|
return r.MatchString(fmt.Sprint(v))
|
|
}
|
|
}
|
|
|
|
// Regexp asserts that a specified regexp matches a string.
|
|
//
|
|
// assert.Regexp(t, regexp.MustCompile("start"), "it's starting")
|
|
// assert.Regexp(t, "start...$", "it's not starting")
|
|
func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
match := matchRegexp(rx, str)
|
|
|
|
if !match {
|
|
Fail(t, fmt.Sprintf("Expect \"%v\" to match \"%v\"", str, rx), msgAndArgs...)
|
|
}
|
|
|
|
return match
|
|
}
|
|
|
|
// NotRegexp asserts that a specified regexp does not match a string.
|
|
//
|
|
// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting")
|
|
// assert.NotRegexp(t, "^start", "it's not starting")
|
|
func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
match := matchRegexp(rx, str)
|
|
|
|
if match {
|
|
Fail(t, fmt.Sprintf("Expect \"%v\" to NOT match \"%v\"", str, rx), msgAndArgs...)
|
|
}
|
|
|
|
return !match
|
|
}
|
|
|
|
// Zero asserts that i is the zero value for its type.
|
|
func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if i != nil && !reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) {
|
|
return Fail(t, fmt.Sprintf("Should be zero, but was %v", i), msgAndArgs...)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// NotZero asserts that i is not the zero value for its type.
|
|
func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if i == nil || reflect.DeepEqual(i, reflect.Zero(reflect.TypeOf(i)).Interface()) {
|
|
return Fail(t, fmt.Sprintf("Should not be zero, but was %v", i), msgAndArgs...)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// FileExists checks whether a file exists in the given path. It also fails if
|
|
// the path points to a directory or there is an error when trying to check the file.
|
|
func FileExists(t TestingT, path string, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
info, err := os.Lstat(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...)
|
|
}
|
|
return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...)
|
|
}
|
|
if info.IsDir() {
|
|
return Fail(t, fmt.Sprintf("%q is a directory", path), msgAndArgs...)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// NoFileExists checks whether a file does not exist in a given path. It fails
|
|
// if the path points to an existing _file_ only.
|
|
func NoFileExists(t TestingT, path string, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
info, err := os.Lstat(path)
|
|
if err != nil {
|
|
return true
|
|
}
|
|
if info.IsDir() {
|
|
return true
|
|
}
|
|
return Fail(t, fmt.Sprintf("file %q exists", path), msgAndArgs...)
|
|
}
|
|
|
|
// DirExists checks whether a directory exists in the given path. It also fails
|
|
// if the path is a file rather a directory or there is an error checking whether it exists.
|
|
func DirExists(t TestingT, path string, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
info, err := os.Lstat(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return Fail(t, fmt.Sprintf("unable to find file %q", path), msgAndArgs...)
|
|
}
|
|
return Fail(t, fmt.Sprintf("error when running os.Lstat(%q): %s", path, err), msgAndArgs...)
|
|
}
|
|
if !info.IsDir() {
|
|
return Fail(t, fmt.Sprintf("%q is a file", path), msgAndArgs...)
|
|
}
|
|
return true
|
|
}
|
|
|
|
// NoDirExists checks whether a directory does not exist in the given path.
|
|
// It fails if the path points to an existing _directory_ only.
|
|
func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
info, err := os.Lstat(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return true
|
|
}
|
|
return true
|
|
}
|
|
if !info.IsDir() {
|
|
return true
|
|
}
|
|
return Fail(t, fmt.Sprintf("directory %q exists", path), msgAndArgs...)
|
|
}
|
|
|
|
// JSONEq asserts that two JSON strings are equivalent.
|
|
//
|
|
// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)
|
|
func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
var expectedJSONAsInterface, actualJSONAsInterface interface{}
|
|
|
|
if err := json.Unmarshal([]byte(expected), &expectedJSONAsInterface); err != nil {
|
|
return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid json.\nJSON parsing error: '%s'", expected, err.Error()), msgAndArgs...)
|
|
}
|
|
|
|
// Shortcut if same bytes
|
|
if actual == expected {
|
|
return true
|
|
}
|
|
|
|
if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil {
|
|
return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...)
|
|
}
|
|
|
|
return Equal(t, expectedJSONAsInterface, actualJSONAsInterface, msgAndArgs...)
|
|
}
|
|
|
|
// YAMLEq asserts that two YAML strings are equivalent.
|
|
func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
var expectedYAMLAsInterface, actualYAMLAsInterface interface{}
|
|
|
|
if err := yaml.Unmarshal([]byte(expected), &expectedYAMLAsInterface); err != nil {
|
|
return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid yaml.\nYAML parsing error: '%s'", expected, err.Error()), msgAndArgs...)
|
|
}
|
|
|
|
// Shortcut if same bytes
|
|
if actual == expected {
|
|
return true
|
|
}
|
|
|
|
if err := yaml.Unmarshal([]byte(actual), &actualYAMLAsInterface); err != nil {
|
|
return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid yaml.\nYAML error: '%s'", actual, err.Error()), msgAndArgs...)
|
|
}
|
|
|
|
return Equal(t, expectedYAMLAsInterface, actualYAMLAsInterface, msgAndArgs...)
|
|
}
|
|
|
|
func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) {
|
|
t := reflect.TypeOf(v)
|
|
k := t.Kind()
|
|
|
|
if k == reflect.Ptr {
|
|
t = t.Elem()
|
|
k = t.Kind()
|
|
}
|
|
return t, k
|
|
}
|
|
|
|
// diff returns a diff of both values as long as both are of the same type and
|
|
// are a struct, map, slice, array or string. Otherwise it returns an empty string.
|
|
func diff(expected interface{}, actual interface{}) string {
|
|
if expected == nil || actual == nil {
|
|
return ""
|
|
}
|
|
|
|
et, ek := typeAndKind(expected)
|
|
at, _ := typeAndKind(actual)
|
|
|
|
if et != at {
|
|
return ""
|
|
}
|
|
|
|
if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array && ek != reflect.String {
|
|
return ""
|
|
}
|
|
|
|
var e, a string
|
|
|
|
switch et {
|
|
case reflect.TypeOf(""):
|
|
e = reflect.ValueOf(expected).String()
|
|
a = reflect.ValueOf(actual).String()
|
|
case reflect.TypeOf(time.Time{}):
|
|
e = spewConfigStringerEnabled.Sdump(expected)
|
|
a = spewConfigStringerEnabled.Sdump(actual)
|
|
default:
|
|
e = spewConfig.Sdump(expected)
|
|
a = spewConfig.Sdump(actual)
|
|
}
|
|
|
|
diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
|
|
A: difflib.SplitLines(e),
|
|
B: difflib.SplitLines(a),
|
|
FromFile: "Expected",
|
|
FromDate: "",
|
|
ToFile: "Actual",
|
|
ToDate: "",
|
|
Context: 1,
|
|
})
|
|
|
|
return "\n\nDiff:\n" + diff
|
|
}
|
|
|
|
func isFunction(arg interface{}) bool {
|
|
if arg == nil {
|
|
return false
|
|
}
|
|
return reflect.TypeOf(arg).Kind() == reflect.Func
|
|
}
|
|
|
|
var spewConfig = spew.ConfigState{
|
|
Indent: " ",
|
|
DisablePointerAddresses: true,
|
|
DisableCapacities: true,
|
|
SortKeys: true,
|
|
DisableMethods: true,
|
|
MaxDepth: 10,
|
|
}
|
|
|
|
var spewConfigStringerEnabled = spew.ConfigState{
|
|
Indent: " ",
|
|
DisablePointerAddresses: true,
|
|
DisableCapacities: true,
|
|
SortKeys: true,
|
|
MaxDepth: 10,
|
|
}
|
|
|
|
type tHelper = interface {
|
|
Helper()
|
|
}
|
|
|
|
// Eventually asserts that given condition will be met in waitFor time,
|
|
// periodically checking target function each tick.
|
|
//
|
|
// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond)
|
|
func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
ch := make(chan bool, 1)
|
|
checkCond := func() { ch <- condition() }
|
|
|
|
timer := time.NewTimer(waitFor)
|
|
defer timer.Stop()
|
|
|
|
ticker := time.NewTicker(tick)
|
|
defer ticker.Stop()
|
|
|
|
var tickC <-chan time.Time
|
|
|
|
// Check the condition once first on the initial call.
|
|
go checkCond()
|
|
|
|
for {
|
|
select {
|
|
case <-timer.C:
|
|
return Fail(t, "Condition never satisfied", msgAndArgs...)
|
|
case <-tickC:
|
|
tickC = nil
|
|
go checkCond()
|
|
case v := <-ch:
|
|
if v {
|
|
return true
|
|
}
|
|
tickC = ticker.C
|
|
}
|
|
}
|
|
}
|
|
|
|
// CollectT implements the TestingT interface and collects all errors.
|
|
type CollectT struct {
|
|
// A slice of errors. Non-nil slice denotes a failure.
|
|
// If it's non-nil but len(c.errors) == 0, this is also a failure
|
|
// obtained by direct c.FailNow() call.
|
|
errors []error
|
|
}
|
|
|
|
// Helper is like [testing.T.Helper] but does nothing.
|
|
func (CollectT) Helper() {}
|
|
|
|
// Errorf collects the error.
|
|
func (c *CollectT) Errorf(format string, args ...interface{}) {
|
|
c.errors = append(c.errors, fmt.Errorf(format, args...))
|
|
}
|
|
|
|
// FailNow stops execution by calling runtime.Goexit.
|
|
func (c *CollectT) FailNow() {
|
|
c.fail()
|
|
runtime.Goexit()
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
|
|
func (c *CollectT) fail() {
|
|
if !c.failed() {
|
|
c.errors = []error{} // Make it non-nil to mark a failure.
|
|
}
|
|
}
|
|
|
|
func (c *CollectT) failed() bool {
|
|
return c.errors != nil
|
|
}
|
|
|
|
// 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")
|
|
// }, 10*time.Second, 1*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 *CollectT, 1)
|
|
|
|
checkCond := func() {
|
|
collect := new(CollectT)
|
|
defer func() {
|
|
ch <- collect
|
|
}()
|
|
condition(collect)
|
|
}
|
|
|
|
timer := time.NewTimer(waitFor)
|
|
defer timer.Stop()
|
|
|
|
ticker := time.NewTicker(tick)
|
|
defer ticker.Stop()
|
|
|
|
var tickC <-chan time.Time
|
|
|
|
// Check the condition once first on the initial call.
|
|
go checkCond()
|
|
|
|
for {
|
|
select {
|
|
case <-timer.C:
|
|
for _, err := range lastFinishedTickErrs {
|
|
t.Errorf("%v", err)
|
|
}
|
|
return Fail(t, "Condition never satisfied", msgAndArgs...)
|
|
case <-tickC:
|
|
tickC = nil
|
|
go checkCond()
|
|
case collect := <-ch:
|
|
if !collect.failed() {
|
|
return true
|
|
}
|
|
// Keep the errors from the last ended condition, so that they can be copied to t if timeout is reached.
|
|
lastFinishedTickErrs = collect.errors
|
|
tickC = ticker.C
|
|
}
|
|
}
|
|
}
|
|
|
|
// Never asserts that the given condition doesn't satisfy in waitFor time,
|
|
// periodically checking the target function each tick.
|
|
//
|
|
// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond)
|
|
func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
|
|
ch := make(chan bool, 1)
|
|
checkCond := func() { ch <- condition() }
|
|
|
|
timer := time.NewTimer(waitFor)
|
|
defer timer.Stop()
|
|
|
|
ticker := time.NewTicker(tick)
|
|
defer ticker.Stop()
|
|
|
|
var tickC <-chan time.Time
|
|
|
|
// Check the condition once first on the initial call.
|
|
go checkCond()
|
|
|
|
for {
|
|
select {
|
|
case <-timer.C:
|
|
return true
|
|
case <-tickC:
|
|
tickC = nil
|
|
go checkCond()
|
|
case v := <-ch:
|
|
if v {
|
|
return Fail(t, "Condition satisfied", msgAndArgs...)
|
|
}
|
|
tickC = ticker.C
|
|
}
|
|
}
|
|
}
|
|
|
|
// ErrorIs asserts that at least one of the errors in err's chain matches target.
|
|
// This is a wrapper for errors.Is.
|
|
func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if errors.Is(err, target) {
|
|
return true
|
|
}
|
|
|
|
var expectedText string
|
|
if target != nil {
|
|
expectedText = target.Error()
|
|
if err == nil {
|
|
return Fail(t, fmt.Sprintf("Expected error with %q in chain but got nil.", expectedText), msgAndArgs...)
|
|
}
|
|
}
|
|
|
|
chain := buildErrorChainString(err, false)
|
|
|
|
return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+
|
|
"expected: %q\n"+
|
|
"in chain: %s", expectedText, chain,
|
|
), msgAndArgs...)
|
|
}
|
|
|
|
// NotErrorIs asserts that none of the errors in err's chain matches target.
|
|
// This is a wrapper for errors.Is.
|
|
func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if !errors.Is(err, target) {
|
|
return true
|
|
}
|
|
|
|
var expectedText string
|
|
if target != nil {
|
|
expectedText = target.Error()
|
|
}
|
|
|
|
chain := buildErrorChainString(err, false)
|
|
|
|
return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+
|
|
"found: %q\n"+
|
|
"in chain: %s", expectedText, chain,
|
|
), msgAndArgs...)
|
|
}
|
|
|
|
// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
|
|
// This is a wrapper for errors.As.
|
|
func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if errors.As(err, target) {
|
|
return true
|
|
}
|
|
|
|
expectedText := reflect.ValueOf(target).Elem().Type().String()
|
|
if err == nil {
|
|
return Fail(t, fmt.Sprintf("An error is expected but got nil.\n"+
|
|
"expected: %s", expectedText), msgAndArgs...)
|
|
}
|
|
|
|
chain := buildErrorChainString(err, true)
|
|
|
|
return Fail(t, fmt.Sprintf("Should be in error chain:\n"+
|
|
"expected: %s\n"+
|
|
"in chain: %s", expectedText, chain,
|
|
), msgAndArgs...)
|
|
}
|
|
|
|
// NotErrorAs asserts that none of the errors in err's chain matches target,
|
|
// but if so, sets target to that error value.
|
|
func NotErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool {
|
|
if h, ok := t.(tHelper); ok {
|
|
h.Helper()
|
|
}
|
|
if !errors.As(err, target) {
|
|
return true
|
|
}
|
|
|
|
chain := buildErrorChainString(err, true)
|
|
|
|
return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+
|
|
"found: %s\n"+
|
|
"in chain: %s", reflect.ValueOf(target).Elem().Type(), chain,
|
|
), msgAndArgs...)
|
|
}
|
|
|
|
func unwrapAll(err error) (errs []error) {
|
|
errs = append(errs, err)
|
|
switch x := err.(type) {
|
|
case interface{ Unwrap() error }:
|
|
err = x.Unwrap()
|
|
if err == nil {
|
|
return
|
|
}
|
|
errs = append(errs, unwrapAll(err)...)
|
|
case interface{ Unwrap() []error }:
|
|
for _, err := range x.Unwrap() {
|
|
errs = append(errs, unwrapAll(err)...)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func buildErrorChainString(err error, withType bool) string {
|
|
if err == nil {
|
|
return ""
|
|
}
|
|
|
|
var chain string
|
|
errs := unwrapAll(err)
|
|
for i := range errs {
|
|
if i != 0 {
|
|
chain += "\n\t"
|
|
}
|
|
chain += fmt.Sprintf("%q", errs[i].Error())
|
|
if withType {
|
|
chain += fmt.Sprintf(" (%T)", errs[i])
|
|
}
|
|
}
|
|
return chain
|
|
}
|