312 lines
8.9 KiB
Go
312 lines
8.9 KiB
Go
/*Package assert provides assertions for comparing expected values to actual
|
|
values. When an assertion fails a helpful error message is printed.
|
|
|
|
Assert and Check
|
|
|
|
Assert() and Check() both accept a Comparison, and fail the test when the
|
|
comparison fails. The one difference is that Assert() will end the test execution
|
|
immediately (using t.FailNow()) whereas Check() will fail the test (using t.Fail()),
|
|
return the value of the comparison, then proceed with the rest of the test case.
|
|
|
|
Example usage
|
|
|
|
The example below shows assert used with some common types.
|
|
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"gotest.tools/assert"
|
|
is "gotest.tools/assert/cmp"
|
|
)
|
|
|
|
func TestEverything(t *testing.T) {
|
|
// booleans
|
|
assert.Assert(t, ok)
|
|
assert.Assert(t, !missing)
|
|
|
|
// primitives
|
|
assert.Equal(t, count, 1)
|
|
assert.Equal(t, msg, "the message")
|
|
assert.Assert(t, total != 10) // NotEqual
|
|
|
|
// errors
|
|
assert.NilError(t, closer.Close())
|
|
assert.Error(t, err, "the exact error message")
|
|
assert.ErrorContains(t, err, "includes this")
|
|
assert.ErrorType(t, err, os.IsNotExist)
|
|
|
|
// complex types
|
|
assert.DeepEqual(t, result, myStruct{Name: "title"})
|
|
assert.Assert(t, is.Len(items, 3))
|
|
assert.Assert(t, len(sequence) != 0) // NotEmpty
|
|
assert.Assert(t, is.Contains(mapping, "key"))
|
|
|
|
// pointers and interface
|
|
assert.Assert(t, is.Nil(ref))
|
|
assert.Assert(t, ref != nil) // NotNil
|
|
}
|
|
|
|
Comparisons
|
|
|
|
Package https://godoc.org/gotest.tools/assert/cmp provides
|
|
many common comparisons. Additional comparisons can be written to compare
|
|
values in other ways. See the example Assert (CustomComparison).
|
|
|
|
Automated migration from testify
|
|
|
|
gty-migrate-from-testify is a binary which can update source code which uses
|
|
testify assertions to use the assertions provided by this package.
|
|
|
|
See http://bit.do/cmd-gty-migrate-from-testify.
|
|
|
|
|
|
*/
|
|
package assert // import "gotest.tools/assert"
|
|
|
|
import (
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
|
|
gocmp "github.com/google/go-cmp/cmp"
|
|
"gotest.tools/assert/cmp"
|
|
"gotest.tools/internal/format"
|
|
"gotest.tools/internal/source"
|
|
)
|
|
|
|
// BoolOrComparison can be a bool, or cmp.Comparison. See Assert() for usage.
|
|
type BoolOrComparison interface{}
|
|
|
|
// TestingT is the subset of testing.T used by the assert package.
|
|
type TestingT interface {
|
|
FailNow()
|
|
Fail()
|
|
Log(args ...interface{})
|
|
}
|
|
|
|
type helperT interface {
|
|
Helper()
|
|
}
|
|
|
|
const failureMessage = "assertion failed: "
|
|
|
|
// nolint: gocyclo
|
|
func assert(
|
|
t TestingT,
|
|
failer func(),
|
|
argSelector argSelector,
|
|
comparison BoolOrComparison,
|
|
msgAndArgs ...interface{},
|
|
) bool {
|
|
if ht, ok := t.(helperT); ok {
|
|
ht.Helper()
|
|
}
|
|
var success bool
|
|
switch check := comparison.(type) {
|
|
case bool:
|
|
if check {
|
|
return true
|
|
}
|
|
logFailureFromBool(t, msgAndArgs...)
|
|
|
|
// Undocumented legacy comparison without Result type
|
|
case func() (success bool, message string):
|
|
success = runCompareFunc(t, check, msgAndArgs...)
|
|
|
|
case nil:
|
|
return true
|
|
|
|
case error:
|
|
msg := "error is not nil: "
|
|
t.Log(format.WithCustomMessage(failureMessage+msg+check.Error(), msgAndArgs...))
|
|
|
|
case cmp.Comparison:
|
|
success = runComparison(t, argSelector, check, msgAndArgs...)
|
|
|
|
case func() cmp.Result:
|
|
success = runComparison(t, argSelector, check, msgAndArgs...)
|
|
|
|
default:
|
|
t.Log(fmt.Sprintf("invalid Comparison: %v (%T)", check, check))
|
|
}
|
|
|
|
if success {
|
|
return true
|
|
}
|
|
failer()
|
|
return false
|
|
}
|
|
|
|
func runCompareFunc(
|
|
t TestingT,
|
|
f func() (success bool, message string),
|
|
msgAndArgs ...interface{},
|
|
) bool {
|
|
if ht, ok := t.(helperT); ok {
|
|
ht.Helper()
|
|
}
|
|
if success, message := f(); !success {
|
|
t.Log(format.WithCustomMessage(failureMessage+message, msgAndArgs...))
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func logFailureFromBool(t TestingT, msgAndArgs ...interface{}) {
|
|
if ht, ok := t.(helperT); ok {
|
|
ht.Helper()
|
|
}
|
|
const stackIndex = 3 // Assert()/Check(), assert(), formatFailureFromBool()
|
|
const comparisonArgPos = 1
|
|
args, err := source.CallExprArgs(stackIndex)
|
|
if err != nil {
|
|
t.Log(err.Error())
|
|
return
|
|
}
|
|
|
|
msg, err := boolFailureMessage(args[comparisonArgPos])
|
|
if err != nil {
|
|
t.Log(err.Error())
|
|
msg = "expression is false"
|
|
}
|
|
|
|
t.Log(format.WithCustomMessage(failureMessage+msg, msgAndArgs...))
|
|
}
|
|
|
|
func boolFailureMessage(expr ast.Expr) (string, error) {
|
|
if binaryExpr, ok := expr.(*ast.BinaryExpr); ok && binaryExpr.Op == token.NEQ {
|
|
x, err := source.FormatNode(binaryExpr.X)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
y, err := source.FormatNode(binaryExpr.Y)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return x + " is " + y, nil
|
|
}
|
|
|
|
if unaryExpr, ok := expr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.NOT {
|
|
x, err := source.FormatNode(unaryExpr.X)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return x + " is true", nil
|
|
}
|
|
|
|
formatted, err := source.FormatNode(expr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return "expression is false: " + formatted, nil
|
|
}
|
|
|
|
// Assert performs a comparison. If the comparison fails the test is marked as
|
|
// failed, a failure message is logged, and execution is stopped immediately.
|
|
//
|
|
// The comparison argument may be one of three types: bool, cmp.Comparison or
|
|
// error.
|
|
// When called with a bool the failure message will contain the literal source
|
|
// code of the expression.
|
|
// When called with a cmp.Comparison the comparison is responsible for producing
|
|
// a helpful failure message.
|
|
// When called with an error a nil value is considered success. A non-nil error
|
|
// is a failure, and Error() is used as the failure message.
|
|
func Assert(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) {
|
|
if ht, ok := t.(helperT); ok {
|
|
ht.Helper()
|
|
}
|
|
assert(t, t.FailNow, argsFromComparisonCall, comparison, msgAndArgs...)
|
|
}
|
|
|
|
// Check performs a comparison. If the comparison fails the test is marked as
|
|
// failed, a failure message is logged, and Check returns false. Otherwise returns
|
|
// true.
|
|
//
|
|
// See Assert for details about the comparison arg and failure messages.
|
|
func Check(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) bool {
|
|
if ht, ok := t.(helperT); ok {
|
|
ht.Helper()
|
|
}
|
|
return assert(t, t.Fail, argsFromComparisonCall, comparison, msgAndArgs...)
|
|
}
|
|
|
|
// NilError fails the test immediately if err is not nil.
|
|
// This is equivalent to Assert(t, err)
|
|
func NilError(t TestingT, err error, msgAndArgs ...interface{}) {
|
|
if ht, ok := t.(helperT); ok {
|
|
ht.Helper()
|
|
}
|
|
assert(t, t.FailNow, argsAfterT, err, msgAndArgs...)
|
|
}
|
|
|
|
// Equal uses the == operator to assert two values are equal and fails the test
|
|
// if they are not equal.
|
|
//
|
|
// If the comparison fails Equal will use the variable names for x and y as part
|
|
// of the failure message to identify the actual and expected values.
|
|
//
|
|
// If either x or y are a multi-line string the failure message will include a
|
|
// unified diff of the two values. If the values only differ by whitespace
|
|
// the unified diff will be augmented by replacing whitespace characters with
|
|
// visible characters to identify the whitespace difference.
|
|
//
|
|
// This is equivalent to Assert(t, cmp.Equal(x, y)).
|
|
func Equal(t TestingT, x, y interface{}, msgAndArgs ...interface{}) {
|
|
if ht, ok := t.(helperT); ok {
|
|
ht.Helper()
|
|
}
|
|
assert(t, t.FailNow, argsAfterT, cmp.Equal(x, y), msgAndArgs...)
|
|
}
|
|
|
|
// DeepEqual uses google/go-cmp (http://bit.do/go-cmp) to assert two values are
|
|
// equal and fails the test if they are not equal.
|
|
//
|
|
// Package https://godoc.org/gotest.tools/assert/opt provides some additional
|
|
// commonly used Options.
|
|
//
|
|
// This is equivalent to Assert(t, cmp.DeepEqual(x, y)).
|
|
func DeepEqual(t TestingT, x, y interface{}, opts ...gocmp.Option) {
|
|
if ht, ok := t.(helperT); ok {
|
|
ht.Helper()
|
|
}
|
|
assert(t, t.FailNow, argsAfterT, cmp.DeepEqual(x, y, opts...))
|
|
}
|
|
|
|
// Error fails the test if err is nil, or the error message is not the expected
|
|
// message.
|
|
// Equivalent to Assert(t, cmp.Error(err, message)).
|
|
func Error(t TestingT, err error, message string, msgAndArgs ...interface{}) {
|
|
if ht, ok := t.(helperT); ok {
|
|
ht.Helper()
|
|
}
|
|
assert(t, t.FailNow, argsAfterT, cmp.Error(err, message), msgAndArgs...)
|
|
}
|
|
|
|
// ErrorContains fails the test if err is nil, or the error message does not
|
|
// contain the expected substring.
|
|
// Equivalent to Assert(t, cmp.ErrorContains(err, substring)).
|
|
func ErrorContains(t TestingT, err error, substring string, msgAndArgs ...interface{}) {
|
|
if ht, ok := t.(helperT); ok {
|
|
ht.Helper()
|
|
}
|
|
assert(t, t.FailNow, argsAfterT, cmp.ErrorContains(err, substring), msgAndArgs...)
|
|
}
|
|
|
|
// ErrorType fails the test if err is nil, or err is not the expected type.
|
|
//
|
|
// Expected can be one of:
|
|
// a func(error) bool which returns true if the error is the expected type,
|
|
// an instance of (or a pointer to) a struct of the expected type,
|
|
// a pointer to an interface the error is expected to implement,
|
|
// a reflect.Type of the expected struct or interface.
|
|
//
|
|
// Equivalent to Assert(t, cmp.ErrorType(err, expected)).
|
|
func ErrorType(t TestingT, err error, expected interface{}, msgAndArgs ...interface{}) {
|
|
if ht, ok := t.(helperT); ok {
|
|
ht.Helper()
|
|
}
|
|
assert(t, t.FailNow, argsAfterT, cmp.ErrorType(err, expected), msgAndArgs...)
|
|
}
|