This commit is contained in:
313
vendor/gotest.tools/v3/assert/assert.go
vendored
Normal file
313
vendor/gotest.tools/v3/assert/assert.go
vendored
Normal file
@ -0,0 +1,313 @@
|
||||
/*
|
||||
Package assert provides assertions for comparing expected values to actual
|
||||
values in tests. When an assertion fails a helpful error message is printed.
|
||||
|
||||
# Example usage
|
||||
|
||||
All the assertions in this package use [testing.T.Helper] to mark themselves as
|
||||
test helpers. This allows the testing package to print the filename and line
|
||||
number of the file function that failed.
|
||||
|
||||
assert.NilError(t, err)
|
||||
// filename_test.go:212: assertion failed: error is not nil: file not found
|
||||
|
||||
If any assertion is called from a helper function, make sure to call t.Helper
|
||||
from the helper function so that the filename and line number remain correct.
|
||||
|
||||
The examples below show assert used with some common types and the failure
|
||||
messages it produces. The filename and line number portion of the failure
|
||||
message is omitted from these examples for brevity.
|
||||
|
||||
// booleans
|
||||
|
||||
assert.Assert(t, ok)
|
||||
// assertion failed: ok is false
|
||||
assert.Assert(t, !missing)
|
||||
// assertion failed: missing is true
|
||||
|
||||
// primitives
|
||||
|
||||
assert.Equal(t, count, 1)
|
||||
// assertion failed: 0 (count int) != 1 (int)
|
||||
assert.Equal(t, msg, "the message")
|
||||
// assertion failed: my message (msg string) != the message (string)
|
||||
assert.Assert(t, total != 10) // use Assert for NotEqual
|
||||
// assertion failed: total is 10
|
||||
assert.Assert(t, count > 20, "count=%v", count)
|
||||
// assertion failed: count is <= 20: count=1
|
||||
|
||||
// errors
|
||||
|
||||
assert.NilError(t, closer.Close())
|
||||
// assertion failed: error is not nil: close /file: errno 11
|
||||
assert.Error(t, err, "the exact error message")
|
||||
// assertion failed: expected error "the exact error message", got "oops"
|
||||
assert.ErrorContains(t, err, "includes this")
|
||||
// assertion failed: expected error to contain "includes this", got "oops"
|
||||
assert.ErrorIs(t, err, os.ErrNotExist)
|
||||
// assertion failed: error is "oops", not "file does not exist" (os.ErrNotExist)
|
||||
|
||||
// complex types
|
||||
|
||||
assert.DeepEqual(t, result, myStruct{Name: "title"})
|
||||
// assertion failed: ... (diff of the two structs)
|
||||
assert.Assert(t, is.Len(items, 3))
|
||||
// assertion failed: expected [] (length 0) to have length 3
|
||||
assert.Assert(t, len(sequence) != 0) // use Assert for NotEmpty
|
||||
// assertion failed: len(sequence) is 0
|
||||
assert.Assert(t, is.Contains(mapping, "key"))
|
||||
// assertion failed: map[other:1] does not contain key
|
||||
|
||||
// pointers and interface
|
||||
|
||||
assert.Assert(t, ref == nil)
|
||||
// assertion failed: ref is not nil
|
||||
assert.Assert(t, ref != nil) // use Assert for NotNil
|
||||
// assertion failed: ref is nil
|
||||
|
||||
# Assert and Check
|
||||
|
||||
[Assert] and [Check] are very similar, they both accept a [cmp.Comparison], and fail
|
||||
the test when the comparison fails. The one difference is that Assert uses
|
||||
[testing.T.FailNow] to fail the test, which will end the test execution immediately.
|
||||
Check uses [testing.T.Fail] to fail the test, which allows it to return the
|
||||
result of the comparison, then proceed with the rest of the test case.
|
||||
|
||||
Like [testing.T.FailNow], [Assert] must be called from the goroutine running the test,
|
||||
not from other goroutines created during the test. [Check] is safe to use from any
|
||||
goroutine.
|
||||
|
||||
# Comparisons
|
||||
|
||||
Package [gotest.tools/v3/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 command which translates Go source code from
|
||||
testify assertions to the assertions provided by this package.
|
||||
|
||||
See http://pkg.go.dev/gotest.tools/v3/assert/cmd/gty-migrate-from-testify.
|
||||
*/
|
||||
package assert // import "gotest.tools/v3/assert"
|
||||
|
||||
import (
|
||||
gocmp "github.com/google/go-cmp/cmp"
|
||||
"gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/internal/assert"
|
||||
)
|
||||
|
||||
// BoolOrComparison can be a bool, [cmp.Comparison], or error. See [Assert] for
|
||||
// details about how this type is used.
|
||||
type BoolOrComparison interface{}
|
||||
|
||||
// TestingT is the subset of [testing.T] (see also [testing.TB]) used by the assert package.
|
||||
type TestingT interface {
|
||||
FailNow()
|
||||
Fail()
|
||||
Log(args ...interface{})
|
||||
}
|
||||
|
||||
type helperT interface {
|
||||
Helper()
|
||||
}
|
||||
|
||||
// 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
|
||||
// True is success. False is a failure. The failure message will contain
|
||||
// the literal source code of the expression.
|
||||
//
|
||||
// cmp.Comparison
|
||||
// Uses cmp.Result.Success() to check for success or failure.
|
||||
// The comparison is responsible for producing a helpful failure message.
|
||||
// http://pkg.go.dev/gotest.tools/v3/assert/cmp provides many common comparisons.
|
||||
//
|
||||
// error
|
||||
// A nil value is considered success, and a non-nil error is a failure.
|
||||
// The return value of error.Error is used as the failure message.
|
||||
//
|
||||
// Extra details can be added to the failure message using msgAndArgs. msgAndArgs
|
||||
// may be either a single string, or a format string and args that will be
|
||||
// passed to [fmt.Sprintf].
|
||||
//
|
||||
// Assert uses [testing.TB.FailNow] to fail the test. Like t.FailNow, Assert must be called
|
||||
// from the goroutine running the test function, not from other
|
||||
// goroutines created during the test. Use [Check] from other goroutines.
|
||||
func Assert(t TestingT, comparison BoolOrComparison, msgAndArgs ...interface{}) {
|
||||
if ht, ok := t.(helperT); ok {
|
||||
ht.Helper()
|
||||
}
|
||||
if !assert.Eval(t, assert.ArgsFromComparisonCall, comparison, msgAndArgs...) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// Check performs a comparison. If the comparison fails the test is marked as
|
||||
// failed, a failure message is printed, and Check returns false. If the comparison
|
||||
// is successful Check returns true. Check may be called from any goroutine.
|
||||
//
|
||||
// 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()
|
||||
}
|
||||
if !assert.Eval(t, assert.ArgsFromComparisonCall, comparison, msgAndArgs...) {
|
||||
t.Fail()
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// NilError fails the test immediately if err is not nil, and includes err.Error
|
||||
// in the failure message.
|
||||
//
|
||||
// NilError uses [testing.TB.FailNow] to fail the test. Like t.FailNow, NilError must be
|
||||
// called from the goroutine running the test function, not from other
|
||||
// goroutines created during the test. Use [Check] from other goroutines.
|
||||
func NilError(t TestingT, err error, msgAndArgs ...interface{}) {
|
||||
if ht, ok := t.(helperT); ok {
|
||||
ht.Helper()
|
||||
}
|
||||
if !assert.Eval(t, assert.ArgsAfterT, err, msgAndArgs...) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// 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 and types of
|
||||
// x and y as part of the failure message to identify the actual and expected
|
||||
// values.
|
||||
//
|
||||
// assert.Equal(t, actual, expected)
|
||||
// // main_test.go:41: assertion failed: 1 (actual int) != 21 (expected int32)
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Equal uses [testing.T.FailNow] to fail the test. Like t.FailNow, Equal must be
|
||||
// called from the goroutine running the test function, not from other
|
||||
// goroutines created during the test. Use [Check] with [cmp.Equal] from other
|
||||
// goroutines.
|
||||
func Equal(t TestingT, x, y interface{}, msgAndArgs ...interface{}) {
|
||||
if ht, ok := t.(helperT); ok {
|
||||
ht.Helper()
|
||||
}
|
||||
if !assert.Eval(t, assert.ArgsAfterT, cmp.Equal(x, y), msgAndArgs...) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// DeepEqual uses [github.com/google/go-cmp/cmp]
|
||||
// to assert two values are equal and fails the test if they are not equal.
|
||||
//
|
||||
// Package [gotest.tools/v3/assert/opt] provides some additional
|
||||
// commonly used Options.
|
||||
//
|
||||
// DeepEqual uses [testing.T.FailNow] to fail the test. Like t.FailNow, DeepEqual must be
|
||||
// called from the goroutine running the test function, not from other
|
||||
// goroutines created during the test. Use [Check] with [cmp.DeepEqual] from other
|
||||
// goroutines.
|
||||
func DeepEqual(t TestingT, x, y interface{}, opts ...gocmp.Option) {
|
||||
if ht, ok := t.(helperT); ok {
|
||||
ht.Helper()
|
||||
}
|
||||
if !assert.Eval(t, assert.ArgsAfterT, cmp.DeepEqual(x, y, opts...)) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// Error fails the test if err is nil, or if err.Error is not equal to expected.
|
||||
// Both err.Error and expected will be included in the failure message.
|
||||
// Error performs an exact match of the error text. Use [ErrorContains] if only
|
||||
// part of the error message is relevant. Use [ErrorType] or [ErrorIs] to compare
|
||||
// errors by type.
|
||||
//
|
||||
// Error uses [testing.T.FailNow] to fail the test. Like t.FailNow, Error must be
|
||||
// called from the goroutine running the test function, not from other
|
||||
// goroutines created during the test. Use [Check] with [cmp.Error] from other
|
||||
// goroutines.
|
||||
func Error(t TestingT, err error, expected string, msgAndArgs ...interface{}) {
|
||||
if ht, ok := t.(helperT); ok {
|
||||
ht.Helper()
|
||||
}
|
||||
if !assert.Eval(t, assert.ArgsAfterT, cmp.Error(err, expected), msgAndArgs...) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorContains fails the test if err is nil, or if err.Error does not
|
||||
// contain the expected substring. Both err.Error and the expected substring
|
||||
// will be included in the failure message.
|
||||
//
|
||||
// ErrorContains uses [testing.T.FailNow] to fail the test. Like t.FailNow, ErrorContains
|
||||
// must be called from the goroutine running the test function, not from other
|
||||
// goroutines created during the test. Use [Check] with [cmp.ErrorContains] from other
|
||||
// goroutines.
|
||||
func ErrorContains(t TestingT, err error, substring string, msgAndArgs ...interface{}) {
|
||||
if ht, ok := t.(helperT); ok {
|
||||
ht.Helper()
|
||||
}
|
||||
if !assert.Eval(t, assert.ArgsAfterT, cmp.ErrorContains(err, substring), msgAndArgs...) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorType fails the test if err is nil, or err is not the expected type.
|
||||
// New code should use ErrorIs instead.
|
||||
//
|
||||
// Expected can be one of:
|
||||
//
|
||||
// func(error) bool
|
||||
// The function should return true if the error is the expected type.
|
||||
//
|
||||
// struct{} or *struct{}
|
||||
// A struct or a pointer to a struct. The assertion fails if the error is
|
||||
// not of the same type.
|
||||
//
|
||||
// *interface{}
|
||||
// A pointer to an interface type. The assertion fails if err does not
|
||||
// implement the interface.
|
||||
//
|
||||
// reflect.Type
|
||||
// The assertion fails if err does not implement the reflect.Type.
|
||||
//
|
||||
// ErrorType uses [testing.T.FailNow] to fail the test. Like t.FailNow, ErrorType
|
||||
// must be called from the goroutine running the test function, not from other
|
||||
// goroutines created during the test. Use [Check] with [cmp.ErrorType] from other
|
||||
// goroutines.
|
||||
//
|
||||
// Deprecated: Use [ErrorIs]
|
||||
func ErrorType(t TestingT, err error, expected interface{}, msgAndArgs ...interface{}) {
|
||||
if ht, ok := t.(helperT); ok {
|
||||
ht.Helper()
|
||||
}
|
||||
if !assert.Eval(t, assert.ArgsAfterT, cmp.ErrorType(err, expected), msgAndArgs...) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorIs fails the test if err is nil, or the error does not match expected
|
||||
// when compared using errors.Is. See [errors.Is] for
|
||||
// accepted arguments.
|
||||
//
|
||||
// ErrorIs uses [testing.T.FailNow] to fail the test. Like t.FailNow, ErrorIs
|
||||
// must be called from the goroutine running the test function, not from other
|
||||
// goroutines created during the test. Use [Check] with [cmp.ErrorIs] from other
|
||||
// goroutines.
|
||||
func ErrorIs(t TestingT, err error, expected error, msgAndArgs ...interface{}) {
|
||||
if ht, ok := t.(helperT); ok {
|
||||
ht.Helper()
|
||||
}
|
||||
if !assert.Eval(t, assert.ArgsAfterT, cmp.ErrorIs(err, expected), msgAndArgs...) {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
404
vendor/gotest.tools/v3/assert/cmp/compare.go
vendored
Normal file
404
vendor/gotest.tools/v3/assert/cmp/compare.go
vendored
Normal file
@ -0,0 +1,404 @@
|
||||
/*Package cmp provides Comparisons for Assert and Check*/
|
||||
package cmp // import "gotest.tools/v3/assert/cmp"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gotest.tools/v3/internal/format"
|
||||
)
|
||||
|
||||
// Comparison is a function which compares values and returns [ResultSuccess] if
|
||||
// the actual value matches the expected value. If the values do not match the
|
||||
// [Result] will contain a message about why it failed.
|
||||
type Comparison func() Result
|
||||
|
||||
// DeepEqual compares two values using [github.com/google/go-cmp/cmp]
|
||||
// and succeeds if the values are equal.
|
||||
//
|
||||
// The comparison can be customized using comparison Options.
|
||||
// Package [gotest.tools/v3/assert/opt] provides some additional
|
||||
// commonly used Options.
|
||||
func DeepEqual(x, y interface{}, opts ...cmp.Option) Comparison {
|
||||
return func() (result Result) {
|
||||
defer func() {
|
||||
if panicmsg, handled := handleCmpPanic(recover()); handled {
|
||||
result = ResultFailure(panicmsg)
|
||||
}
|
||||
}()
|
||||
diff := cmp.Diff(x, y, opts...)
|
||||
if diff == "" {
|
||||
return ResultSuccess
|
||||
}
|
||||
return multiLineDiffResult(diff, x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func handleCmpPanic(r interface{}) (string, bool) {
|
||||
if r == nil {
|
||||
return "", false
|
||||
}
|
||||
panicmsg, ok := r.(string)
|
||||
if !ok {
|
||||
panic(r)
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(panicmsg, "cannot handle unexported field"):
|
||||
return panicmsg, true
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
|
||||
func toResult(success bool, msg string) Result {
|
||||
if success {
|
||||
return ResultSuccess
|
||||
}
|
||||
return ResultFailure(msg)
|
||||
}
|
||||
|
||||
// RegexOrPattern may be either a [*regexp.Regexp] or a string that is a valid
|
||||
// regexp pattern.
|
||||
type RegexOrPattern interface{}
|
||||
|
||||
// Regexp succeeds if value v matches regular expression re.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// assert.Assert(t, cmp.Regexp("^[0-9a-f]{32}$", str))
|
||||
// r := regexp.MustCompile("^[0-9a-f]{32}$")
|
||||
// assert.Assert(t, cmp.Regexp(r, str))
|
||||
func Regexp(re RegexOrPattern, v string) Comparison {
|
||||
match := func(re *regexp.Regexp) Result {
|
||||
return toResult(
|
||||
re.MatchString(v),
|
||||
fmt.Sprintf("value %q does not match regexp %q", v, re.String()))
|
||||
}
|
||||
|
||||
return func() Result {
|
||||
switch regex := re.(type) {
|
||||
case *regexp.Regexp:
|
||||
return match(regex)
|
||||
case string:
|
||||
re, err := regexp.Compile(regex)
|
||||
if err != nil {
|
||||
return ResultFailure(err.Error())
|
||||
}
|
||||
return match(re)
|
||||
default:
|
||||
return ResultFailure(fmt.Sprintf("invalid type %T for regex pattern", regex))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Equal succeeds if x == y. See [gotest.tools/v3/assert.Equal] for full documentation.
|
||||
func Equal(x, y interface{}) Comparison {
|
||||
return func() Result {
|
||||
switch {
|
||||
case x == y:
|
||||
return ResultSuccess
|
||||
case isMultiLineStringCompare(x, y):
|
||||
diff := format.UnifiedDiff(format.DiffConfig{A: x.(string), B: y.(string)})
|
||||
return multiLineDiffResult(diff, x, y)
|
||||
}
|
||||
return ResultFailureTemplate(`
|
||||
{{- printf "%v" .Data.x}} (
|
||||
{{- with callArg 0 }}{{ formatNode . }} {{end -}}
|
||||
{{- printf "%T" .Data.x -}}
|
||||
) != {{ printf "%v" .Data.y}} (
|
||||
{{- with callArg 1 }}{{ formatNode . }} {{end -}}
|
||||
{{- printf "%T" .Data.y -}}
|
||||
)`,
|
||||
map[string]interface{}{"x": x, "y": y})
|
||||
}
|
||||
}
|
||||
|
||||
func isMultiLineStringCompare(x, y interface{}) bool {
|
||||
strX, ok := x.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
strY, ok := y.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(strX, "\n") || strings.Contains(strY, "\n")
|
||||
}
|
||||
|
||||
func multiLineDiffResult(diff string, x, y interface{}) Result {
|
||||
return ResultFailureTemplate(`
|
||||
--- {{ with callArg 0 }}{{ formatNode . }}{{else}}←{{end}}
|
||||
+++ {{ with callArg 1 }}{{ formatNode . }}{{else}}→{{end}}
|
||||
{{ .Data.diff }}`,
|
||||
map[string]interface{}{"diff": diff, "x": x, "y": y})
|
||||
}
|
||||
|
||||
// Len succeeds if the sequence has the expected length.
|
||||
func Len(seq interface{}, expected int) Comparison {
|
||||
return func() (result Result) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
result = ResultFailure(fmt.Sprintf("type %T does not have a length", seq))
|
||||
}
|
||||
}()
|
||||
value := reflect.ValueOf(seq)
|
||||
length := value.Len()
|
||||
if length == expected {
|
||||
return ResultSuccess
|
||||
}
|
||||
msg := fmt.Sprintf("expected %s (length %d) to have length %d", seq, length, expected)
|
||||
return ResultFailure(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Contains succeeds if item is in collection. Collection may be a string, map,
|
||||
// slice, or array.
|
||||
//
|
||||
// If collection is a string, item must also be a string, and is compared using
|
||||
// [strings.Contains].
|
||||
// If collection is a Map, contains will succeed if item is a key in the map.
|
||||
// If collection is a slice or array, item is compared to each item in the
|
||||
// sequence using [reflect.DeepEqual].
|
||||
func Contains(collection interface{}, item interface{}) Comparison {
|
||||
return func() Result {
|
||||
colValue := reflect.ValueOf(collection)
|
||||
if !colValue.IsValid() {
|
||||
return ResultFailure("nil does not contain items")
|
||||
}
|
||||
msg := fmt.Sprintf("%v does not contain %v", collection, item)
|
||||
|
||||
itemValue := reflect.ValueOf(item)
|
||||
switch colValue.Type().Kind() {
|
||||
case reflect.String:
|
||||
if itemValue.Type().Kind() != reflect.String {
|
||||
return ResultFailure("string may only contain strings")
|
||||
}
|
||||
return toResult(
|
||||
strings.Contains(colValue.String(), itemValue.String()),
|
||||
fmt.Sprintf("string %q does not contain %q", collection, item))
|
||||
|
||||
case reflect.Map:
|
||||
if itemValue.Type() != colValue.Type().Key() {
|
||||
return ResultFailure(fmt.Sprintf(
|
||||
"%v can not contain a %v key", colValue.Type(), itemValue.Type()))
|
||||
}
|
||||
return toResult(colValue.MapIndex(itemValue).IsValid(), msg)
|
||||
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < colValue.Len(); i++ {
|
||||
if reflect.DeepEqual(colValue.Index(i).Interface(), item) {
|
||||
return ResultSuccess
|
||||
}
|
||||
}
|
||||
return ResultFailure(msg)
|
||||
default:
|
||||
return ResultFailure(fmt.Sprintf("type %T does not contain items", collection))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Panics succeeds if f() panics.
|
||||
func Panics(f func()) Comparison {
|
||||
return func() (result Result) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
result = ResultSuccess
|
||||
}
|
||||
}()
|
||||
f()
|
||||
return ResultFailure("did not panic")
|
||||
}
|
||||
}
|
||||
|
||||
// Error succeeds if err is a non-nil error, and the error message equals the
|
||||
// expected message.
|
||||
func Error(err error, message string) Comparison {
|
||||
return func() Result {
|
||||
switch {
|
||||
case err == nil:
|
||||
return ResultFailure("expected an error, got nil")
|
||||
case err.Error() != message:
|
||||
return ResultFailure(fmt.Sprintf(
|
||||
"expected error %q, got %s", message, formatErrorMessage(err)))
|
||||
}
|
||||
return ResultSuccess
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorContains succeeds if err is a non-nil error, and the error message contains
|
||||
// the expected substring.
|
||||
func ErrorContains(err error, substring string) Comparison {
|
||||
return func() Result {
|
||||
switch {
|
||||
case err == nil:
|
||||
return ResultFailure("expected an error, got nil")
|
||||
case !strings.Contains(err.Error(), substring):
|
||||
return ResultFailure(fmt.Sprintf(
|
||||
"expected error to contain %q, got %s", substring, formatErrorMessage(err)))
|
||||
}
|
||||
return ResultSuccess
|
||||
}
|
||||
}
|
||||
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
func formatErrorMessage(err error) string {
|
||||
//nolint:errorlint // unwrapping is not appropriate here
|
||||
if _, ok := err.(causer); ok {
|
||||
return fmt.Sprintf("%q\n%+v", err, err)
|
||||
}
|
||||
// This error was not wrapped with github.com/pkg/errors
|
||||
return fmt.Sprintf("%q", err)
|
||||
}
|
||||
|
||||
// Nil succeeds if obj is a nil interface, pointer, or function.
|
||||
//
|
||||
// Use [gotest.tools/v3/assert.NilError] for comparing errors. Use Len(obj, 0) for comparing slices,
|
||||
// maps, and channels.
|
||||
func Nil(obj interface{}) Comparison {
|
||||
msgFunc := func(value reflect.Value) string {
|
||||
return fmt.Sprintf("%v (type %s) is not nil", reflect.Indirect(value), value.Type())
|
||||
}
|
||||
return isNil(obj, msgFunc)
|
||||
}
|
||||
|
||||
func isNil(obj interface{}, msgFunc func(reflect.Value) string) Comparison {
|
||||
return func() Result {
|
||||
if obj == nil {
|
||||
return ResultSuccess
|
||||
}
|
||||
value := reflect.ValueOf(obj)
|
||||
kind := value.Type().Kind()
|
||||
if kind >= reflect.Chan && kind <= reflect.Slice {
|
||||
if value.IsNil() {
|
||||
return ResultSuccess
|
||||
}
|
||||
return ResultFailure(msgFunc(value))
|
||||
}
|
||||
|
||||
return ResultFailure(fmt.Sprintf("%v (type %s) can not be nil", value, value.Type()))
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorType succeeds if err is not nil and is of the expected type.
|
||||
//
|
||||
// Expected can be one of:
|
||||
//
|
||||
// func(error) bool
|
||||
//
|
||||
// Function should return true if the error is the expected type.
|
||||
//
|
||||
// type struct{}, type &struct{}
|
||||
//
|
||||
// A struct or a pointer to a struct.
|
||||
// Fails if the error is not of the same type as expected.
|
||||
//
|
||||
// type &interface{}
|
||||
//
|
||||
// A pointer to an interface type.
|
||||
// Fails if err does not implement the interface.
|
||||
//
|
||||
// reflect.Type
|
||||
//
|
||||
// Fails if err does not implement the [reflect.Type].
|
||||
//
|
||||
// Deprecated: Use [ErrorIs]
|
||||
func ErrorType(err error, expected interface{}) Comparison {
|
||||
return func() Result {
|
||||
switch expectedType := expected.(type) {
|
||||
case func(error) bool:
|
||||
return cmpErrorTypeFunc(err, expectedType)
|
||||
case reflect.Type:
|
||||
if expectedType.Kind() == reflect.Interface {
|
||||
return cmpErrorTypeImplementsType(err, expectedType)
|
||||
}
|
||||
return cmpErrorTypeEqualType(err, expectedType)
|
||||
case nil:
|
||||
return ResultFailure("invalid type for expected: nil")
|
||||
}
|
||||
|
||||
expectedType := reflect.TypeOf(expected)
|
||||
switch {
|
||||
case expectedType.Kind() == reflect.Struct, isPtrToStruct(expectedType):
|
||||
return cmpErrorTypeEqualType(err, expectedType)
|
||||
case isPtrToInterface(expectedType):
|
||||
return cmpErrorTypeImplementsType(err, expectedType.Elem())
|
||||
}
|
||||
return ResultFailure(fmt.Sprintf("invalid type for expected: %T", expected))
|
||||
}
|
||||
}
|
||||
|
||||
func cmpErrorTypeFunc(err error, f func(error) bool) Result {
|
||||
if f(err) {
|
||||
return ResultSuccess
|
||||
}
|
||||
actual := "nil"
|
||||
if err != nil {
|
||||
actual = fmt.Sprintf("%s (%T)", err, err)
|
||||
}
|
||||
return ResultFailureTemplate(`error is {{ .Data.actual }}
|
||||
{{- with callArg 1 }}, not {{ formatNode . }}{{end -}}`,
|
||||
map[string]interface{}{"actual": actual})
|
||||
}
|
||||
|
||||
func cmpErrorTypeEqualType(err error, expectedType reflect.Type) Result {
|
||||
if err == nil {
|
||||
return ResultFailure(fmt.Sprintf("error is nil, not %s", expectedType))
|
||||
}
|
||||
errValue := reflect.ValueOf(err)
|
||||
if errValue.Type() == expectedType {
|
||||
return ResultSuccess
|
||||
}
|
||||
return ResultFailure(fmt.Sprintf("error is %s (%T), not %s", err, err, expectedType))
|
||||
}
|
||||
|
||||
func cmpErrorTypeImplementsType(err error, expectedType reflect.Type) Result {
|
||||
if err == nil {
|
||||
return ResultFailure(fmt.Sprintf("error is nil, not %s", expectedType))
|
||||
}
|
||||
errValue := reflect.ValueOf(err)
|
||||
if errValue.Type().Implements(expectedType) {
|
||||
return ResultSuccess
|
||||
}
|
||||
return ResultFailure(fmt.Sprintf("error is %s (%T), not %s", err, err, expectedType))
|
||||
}
|
||||
|
||||
func isPtrToInterface(typ reflect.Type) bool {
|
||||
return typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Interface
|
||||
}
|
||||
|
||||
func isPtrToStruct(typ reflect.Type) bool {
|
||||
return typ.Kind() == reflect.Ptr && typ.Elem().Kind() == reflect.Struct
|
||||
}
|
||||
|
||||
var (
|
||||
stdlibErrorNewType = reflect.TypeOf(errors.New(""))
|
||||
stdlibFmtErrorType = reflect.TypeOf(fmt.Errorf("%w", fmt.Errorf("")))
|
||||
)
|
||||
|
||||
// ErrorIs succeeds if errors.Is(actual, expected) returns true. See
|
||||
// [errors.Is] for accepted argument values.
|
||||
func ErrorIs(actual error, expected error) Comparison {
|
||||
return func() Result {
|
||||
if errors.Is(actual, expected) {
|
||||
return ResultSuccess
|
||||
}
|
||||
|
||||
// The type of stdlib errors is excluded because the type is not relevant
|
||||
// in those cases. The type is only important when it is a user defined
|
||||
// custom error type.
|
||||
return ResultFailureTemplate(`error is
|
||||
{{- if not .Data.a }} nil,{{ else }}
|
||||
{{- printf " \"%v\"" .Data.a }}
|
||||
{{- if notStdlibErrorType .Data.a }} ({{ printf "%T" .Data.a }}){{ end }},
|
||||
{{- end }} not {{ printf "\"%v\"" .Data.x }} (
|
||||
{{- with callArg 1 }}{{ formatNode . }}{{ end }}
|
||||
{{- if notStdlibErrorType .Data.x }}{{ printf " %T" .Data.x }}{{ end }})`,
|
||||
map[string]interface{}{"a": actual, "x": expected})
|
||||
}
|
||||
}
|
110
vendor/gotest.tools/v3/assert/cmp/result.go
vendored
Normal file
110
vendor/gotest.tools/v3/assert/cmp/result.go
vendored
Normal file
@ -0,0 +1,110 @@
|
||||
package cmp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"text/template"
|
||||
|
||||
"gotest.tools/v3/internal/source"
|
||||
)
|
||||
|
||||
// A Result of a [Comparison].
|
||||
type Result interface {
|
||||
Success() bool
|
||||
}
|
||||
|
||||
// StringResult is an implementation of [Result] that reports the error message
|
||||
// string verbatim and does not provide any templating or formatting of the
|
||||
// message.
|
||||
type StringResult struct {
|
||||
success bool
|
||||
message string
|
||||
}
|
||||
|
||||
// Success returns true if the comparison was successful.
|
||||
func (r StringResult) Success() bool {
|
||||
return r.success
|
||||
}
|
||||
|
||||
// FailureMessage returns the message used to provide additional information
|
||||
// about the failure.
|
||||
func (r StringResult) FailureMessage() string {
|
||||
return r.message
|
||||
}
|
||||
|
||||
// ResultSuccess is a constant which is returned by a [Comparison] to
|
||||
// indicate success.
|
||||
var ResultSuccess = StringResult{success: true}
|
||||
|
||||
// ResultFailure returns a failed [Result] with a failure message.
|
||||
func ResultFailure(message string) StringResult {
|
||||
return StringResult{message: message}
|
||||
}
|
||||
|
||||
// ResultFromError returns [ResultSuccess] if err is nil. Otherwise [ResultFailure]
|
||||
// is returned with the error message as the failure message.
|
||||
func ResultFromError(err error) Result {
|
||||
if err == nil {
|
||||
return ResultSuccess
|
||||
}
|
||||
return ResultFailure(err.Error())
|
||||
}
|
||||
|
||||
type templatedResult struct {
|
||||
template string
|
||||
data map[string]interface{}
|
||||
}
|
||||
|
||||
func (r templatedResult) Success() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r templatedResult) FailureMessage(args []ast.Expr) string {
|
||||
msg, err := renderMessage(r, args)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("failed to render failure message: %s", err)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (r templatedResult) UpdatedExpected(stackIndex int) error {
|
||||
// TODO: would be nice to have structured data instead of a map
|
||||
return source.UpdateExpectedValue(stackIndex+1, r.data["x"], r.data["y"])
|
||||
}
|
||||
|
||||
// ResultFailureTemplate returns a [Result] with a template string and data which
|
||||
// can be used to format a failure message. The template may access data from .Data,
|
||||
// the comparison args with the callArg function, and the formatNode function may
|
||||
// be used to format the call args.
|
||||
func ResultFailureTemplate(template string, data map[string]interface{}) Result {
|
||||
return templatedResult{template: template, data: data}
|
||||
}
|
||||
|
||||
func renderMessage(result templatedResult, args []ast.Expr) (string, error) {
|
||||
tmpl := template.New("failure").Funcs(template.FuncMap{
|
||||
"formatNode": source.FormatNode,
|
||||
"callArg": func(index int) ast.Expr {
|
||||
if index >= len(args) {
|
||||
return nil
|
||||
}
|
||||
return args[index]
|
||||
},
|
||||
// TODO: any way to include this from ErrorIS instead of here?
|
||||
"notStdlibErrorType": func(typ interface{}) bool {
|
||||
r := reflect.TypeOf(typ)
|
||||
return r != stdlibFmtErrorType && r != stdlibErrorNewType
|
||||
},
|
||||
})
|
||||
var err error
|
||||
tmpl, err = tmpl.Parse(result.template)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
err = tmpl.Execute(buf, map[string]interface{}{
|
||||
"Data": result.data,
|
||||
})
|
||||
return buf.String(), err
|
||||
}
|
Reference in New Issue
Block a user