feat(tinyssert): AssertionsErr interface to provide error return values

This commit is contained in:
Guz
2025-07-30 19:18:28 -03:00
parent 982c2a4cb5
commit e277964414

View File

@@ -122,32 +122,32 @@ import (
// }
type Assertions interface {
// Asserts that the value is not zero-valued, is nil, or panics, aka. "is ok".
OK(v any, msg ...any) error
Ok(v any, msg ...any)
// Asserts that the actual value is equal to the expected value.
Equal(expected, actual any, msg ...any) error
Equal(expected, actual any, msg ...any)
// Asserts that the actual value is not equal to the expected value.
NotEqual(notExpected, actual any, msg ...any) error
NotEqual(notExpected, actual any, msg ...any)
// Asserts that the value is nil.
Nil(v any, msg ...any) error
Nil(v any, msg ...any)
// Asserts that the value is not nil.
NotNil(v any, msg ...any) error
NotNil(v any, msg ...any)
// Asserts that the value is a boolean true.
True(b bool, msg ...any) error
True(b bool, msg ...any)
// Asserts that the value is a boolean false.
False(b bool, msg ...any) error
False(b bool, msg ...any)
// Asserts that the value is zero-valued.
Zero(v any, msg ...any) error
Zero(v any, msg ...any)
// Asserts that the value is not zero-valued.
NotZero(v any, msg ...any) error
NotZero(v any, msg ...any)
// Asserts that the function panics.
Panic(fn func(), msg ...any) error
Panic(fn func(), msg ...any)
// Asserts that the function does not panics.
NotPanic(fn func(), msg ...any) error
NotPanic(fn func(), msg ...any)
// Logs the formatted failure message and/or marks the test as failed if possible,
// depending of what is possible to the implementation.
@@ -160,9 +160,53 @@ type Assertions interface {
CallerInfo() []string
}
// AssertionsErr is the same as [Assertions], but it returns [Failure] on it's method, useful
// for assertions that can also be returned as errors.
type AssertionsErr interface {
Assertions
// Asserts that the value is not zero-valued, is nil, or panics, aka. "is ok".
// Returns a Failure if the assertion fails, otherwise returns nil.
OkErr(v any, msg ...any) Failure
// Asserts that the actual value is equal to the expected value.
// Returns a Failure if the assertion fails, otherwise returns nil.
EqualErr(expected, actual any, msg ...any) Failure
// Asserts that the actual value is not equal to the expected value.
// Returns a Failure if the assertion fails, otherwise returns nil.
NotEqualErr(notExpected, actual any, msg ...any) Failure
// Asserts that the value is nil.
// Returns a Failure if the assertion fails, otherwise returns nil.
NilErr(v any, msg ...any) Failure
// Asserts that the value is not nil.
// Returns a Failure if the assertion fails, otherwise returns nil.
NotNilErr(v any, msg ...any) Failure
// Asserts that the value is a boolean true.
// Returns a Failure if the assertion fails, otherwise returns nil.
TrueErr(b bool, msg ...any) Failure
// Asserts that the value is a boolean false.
// Returns a Failure if the assertion fails, otherwise returns nil.
FalseErr(b bool, msg ...any) Failure
// Asserts that the value is zero-valued.
// Returns a Failure if the assertion fails, otherwise returns nil.
ZeroErr(v any, msg ...any) Failure
// Asserts that the value is not zero-valued.
NotZeroErr(v any, msg ...any) Failure
// Asserts that the function panics.
// Returns a Failure if the assertion fails, otherwise returns nil.
PanicErr(fn func(), msg ...any) Failure
// Asserts that the function does not panics.
// Returns a Failure if the assertion fails, otherwise returns nil.
NotPanicErr(fn func(), msg ...any) Failure
}
// New constructs a new implementation of [Assertions]. Use `opts` to customize the behaviour
// of the implementation.
func New(opts ...Option) Assertions {
func New(opts ...Option) AssertionsErr {
a := &assertions{
panic: false,
log: slog.New(slog.NewTextHandler(io.Discard, &slog.HandlerOptions{})),
@@ -237,22 +281,30 @@ type helperT interface {
var _ Assertions = (*assertions)(nil)
func (a *assertions) Equal(expected, actual any, msg ...any) error {
func (a *assertions) EqualErr(expected, actual any, msg ...any) Failure {
if a.equal(expected, actual) {
return nil
}
return a.fail(fmt.Sprintf("expected %v (right), got %v (left)", expected, actual), msg...)
}
func (a *assertions) NotEqual(notExpected, actual any, msg ...any) error {
func (a *assertions) Equal(expected, actual any, msg ...any) {
_ = a.EqualErr(expected, actual, msg...)
}
func (a *assertions) NotEqualErr(notExpected, actual any, msg ...any) Failure {
if !a.equal(notExpected, actual) {
return nil
}
return a.fail(fmt.Sprintf("expected to %v (right) and %v (left) to be not-equal", notExpected, actual), msg...)
}
func (a *assertions) NotEqual(notExpected, actual any, msg ...any) {
_ = a.NotEqualErr(notExpected, actual, msg...)
}
func (a *assertions) equal(ex, ac any) bool {
if nex, nac := a.OK(ex), a.OK(ac); (nex != nil) != (nac != nil) {
if nex, nac := a.OkErr(ex), a.OkErr(ac); (nex != nil) != (nac != nil) {
return false
}
@@ -277,7 +329,7 @@ func (a *assertions) equal(ex, ac any) bool {
return false
}
func (a *assertions) OK(v any, msg ...any) error {
func (a *assertions) OkErr(v any, msg ...any) Failure {
if a.nil(v) {
return a.fail("expected not-nil value", msg...)
}
@@ -294,20 +346,32 @@ func (a *assertions) OK(v any, msg ...any) error {
return nil
}
func (a *assertions) Nil(v any, msg ...any) error {
func (a *assertions) Ok(v any, msg ...any) {
_ = a.OkErr(v, msg...)
}
func (a *assertions) NilErr(v any, msg ...any) Failure {
if a.nil(v) {
return nil
}
return a.fail("expected nil value", msg...)
}
func (a *assertions) NotNil(v any, msg ...any) error {
func (a *assertions) Nil(v any, msg ...any) {
_ = a.NilErr(v, msg...)
}
func (a *assertions) NotNilErr(v any, msg ...any) Failure {
if !a.nil(v) {
return nil
}
return a.fail("expected not-nil value", msg...)
}
func (a *assertions) NotNil(v any, msg ...any) {
_ = a.NotNilErr(v, msg...)
}
func (a *assertions) nil(v any) bool {
if v == nil {
return true
@@ -322,34 +386,50 @@ func (a *assertions) nil(v any) bool {
return false
}
func (a *assertions) True(v bool, msg ...any) error {
func (a *assertions) TrueErr(v bool, msg ...any) Failure {
if v {
return nil
}
return a.fail("expected true", msg...)
}
func (a *assertions) False(v bool, msg ...any) error {
func (a *assertions) True(v bool, msg ...any) {
_ = a.TrueErr(v, msg...)
}
func (a *assertions) FalseErr(v bool, msg ...any) Failure {
if !v {
return nil
}
return a.fail("expected false", msg...)
}
func (a *assertions) Zero(v any, msg ...any) error {
func (a *assertions) False(v bool, msg ...any) {
_ = a.FalseErr(v, msg...)
}
func (a *assertions) ZeroErr(v any, msg ...any) Failure {
if a.zero(v) {
return nil
}
return a.fail("expected zero value", msg...)
}
func (a *assertions) NotZero(v any, msg ...any) error {
func (a *assertions) Zero(v any, msg ...any) {
_ = a.ZeroErr(v, msg...)
}
func (a *assertions) NotZeroErr(v any, msg ...any) Failure {
if !a.zero(v) {
return nil
}
return a.fail("expected non-zero value", msg...)
}
func (a *assertions) NotZero(v any, msg ...any) {
_ = a.NotZeroErr(v, msg...)
}
func (a *assertions) zero(v any) bool {
if v != nil && !reflect.DeepEqual(v, reflect.Zero(reflect.TypeOf(v)).Interface()) {
return false
@@ -357,20 +437,28 @@ func (a *assertions) zero(v any) bool {
return true
}
func (a *assertions) Panic(fn func(), msg ...any) error {
func (a *assertions) PanicErr(fn func(), msg ...any) Failure {
if a.panics(fn) {
return nil
}
return a.fail("expected function to panic", msg...)
}
func (a *assertions) NotPanic(fn func(), msg ...any) error {
func (a *assertions) Panic(fn func(), msg ...any) {
_ = a.PanicErr(fn, msg...)
}
func (a *assertions) NotPanicErr(fn func(), msg ...any) Failure {
if !a.panics(fn) {
return nil
}
return a.fail("expected function to not panic", msg...)
}
func (a *assertions) NotPanic(fn func(), msg ...any) {
_ = a.NotPanicErr(fn, msg...)
}
func (a *assertions) panics(fn func()) bool {
var r any
func() {
@@ -382,21 +470,21 @@ func (a *assertions) panics(fn func()) bool {
return r != nil
}
func (a *assertions) fail(reason string, msg ...any) error {
func (a *assertions) fail(reason string, msg ...any) Failure {
if a.helper != nil {
a.helper.Helper()
}
f := Failure{
Reason: reason,
Message: fmtMessage(msg),
CallerInfo: a.CallerInfo(),
f := failure{
reason: reason,
message: fmtMessage(msg),
callerInfo: a.CallerInfo(),
}
if n, ok := a.test.(interface {
Name() string
}); ok {
f.Test = n.Name()
f.test = n.Name()
}
if a.panic {
@@ -416,10 +504,10 @@ func (a *assertions) Fail(f Failure) {
ft.Fail()
} else {
a.log.Error("ASSERTION FAILED",
slog.String("reason", f.Reason),
slog.String("message", f.Message),
slog.String("test", f.Test),
slog.Any("caller", f.CallerInfo),
slog.String("reason", f.Reason()),
slog.String("message", f.Message()),
slog.String("test", f.Test()),
slog.Any("caller", f.CallerInfo()),
)
}
}
@@ -508,37 +596,42 @@ func isTest(name, prefix string) bool {
return !unicode.IsLower(r)
}
type Failure struct {
Reason string
Message string
type failure struct {
reason string
message string
Test string
CallerInfo []string
test string
callerInfo []string
}
var (
_ error = Failure{}
_ fmt.Stringer = Failure{}
)
var _ Failure = failure{}
func (e Failure) Error() string {
if e.Message != "" {
return fmt.Sprintf("assertion failed, %s: %s", e.Reason, e.Message)
func (e failure) Reason() string {
return e.reason
}
func (e failure) Message() string {
return e.message
}
func (e failure) Error() string {
if e.message != "" {
return fmt.Sprintf("assertion failed, %s: %s", e.reason, e.message)
}
return fmt.Sprintf("assertion failed, %s", e.Reason)
return fmt.Sprintf("assertion failed, %s", e.reason)
}
func (e Failure) String() string {
func (e failure) String() string {
c := map[string]string{
"Reason": e.Reason,
"Reason": e.reason,
}
if e.Message != "" {
c["Message"] = e.Message
if e.message != "" {
c["Message"] = e.message
}
if e.Test != "" {
c["Test"] = e.Test
if e.test != "" {
c["Test"] = e.test
}
c["Stack Trace"] = e.StackTrace()
@@ -555,9 +648,28 @@ func (e Failure) String() string {
return out
}
func (e failure) Test() string {
return e.test
}
func (e failure) CallerInfo() []string {
return e.callerInfo
}
// StackTrace returns the CallerInfo strings as a formatted stack trace.
func (e Failure) StackTrace() string {
return strings.Join(e.CallerInfo, "\n\t")
func (e failure) StackTrace() string {
return strings.Join(e.callerInfo, "\n\t")
}
type Failure interface {
Reason() string
Message() string
Test() string
StackTrace() string
CallerInfo() []string
error
fmt.Stringer
}
type disabledAssertions struct{}
@@ -569,25 +681,36 @@ type disabledAssertions struct{}
//
// The `opts` argument does nothing, and is just available to make the function signature
// equal to [New].
func NewDisabled(opts ...Option) Assertions {
func NewDisabled(opts ...Option) AssertionsErr {
_ = opts
return &disabledAssertions{}
}
func (*disabledAssertions) OK(any, ...any) error { return nil }
func (*disabledAssertions) Equal(_, _ any, _ ...any) error { return nil }
func (*disabledAssertions) NotEqual(_, _ any, _ ...any) error { return nil }
func (*disabledAssertions) Nil(any, ...any) error { return nil }
func (*disabledAssertions) NotNil(any, ...any) error { return nil }
func (*disabledAssertions) True(bool, ...any) error { return nil }
func (*disabledAssertions) False(bool, ...any) error { return nil }
func (*disabledAssertions) Zero(any, ...any) error { return nil }
func (*disabledAssertions) NotZero(any, ...any) error { return nil }
func (*disabledAssertions) Panic(func(), ...any) error { return nil }
func (*disabledAssertions) NotPanic(func(), ...any) error { return nil }
func (*disabledAssertions) Fail(f Failure) { Default.Fail(f) }
func (*disabledAssertions) FailNow(f Failure) { Default.FailNow(f) }
func (*disabledAssertions) CallerInfo() []string { return Default.CallerInfo() }
func (*disabledAssertions) Ok(any, ...any) { return }
func (*disabledAssertions) Equal(_, _ any, _ ...any) { return }
func (*disabledAssertions) NotEqual(_, _ any, _ ...any) { return }
func (*disabledAssertions) Nil(any, ...any) { return }
func (*disabledAssertions) NotNil(any, ...any) { return }
func (*disabledAssertions) True(bool, ...any) { return }
func (*disabledAssertions) False(bool, ...any) { return }
func (*disabledAssertions) Zero(any, ...any) { return }
func (*disabledAssertions) NotZero(any, ...any) { return }
func (*disabledAssertions) Panic(func(), ...any) { return }
func (*disabledAssertions) NotPanic(func(), ...any) { return }
func (*disabledAssertions) OkErr(any, ...any) Failure { return nil }
func (*disabledAssertions) EqualErr(_, _ any, _ ...any) Failure { return nil }
func (*disabledAssertions) NotEqualErr(_, _ any, _ ...any) Failure { return nil }
func (*disabledAssertions) NilErr(any, ...any) Failure { return nil }
func (*disabledAssertions) NotNilErr(any, ...any) Failure { return nil }
func (*disabledAssertions) TrueErr(bool, ...any) Failure { return nil }
func (*disabledAssertions) FalseErr(bool, ...any) Failure { return nil }
func (*disabledAssertions) ZeroErr(any, ...any) Failure { return nil }
func (*disabledAssertions) NotZeroErr(any, ...any) Failure { return nil }
func (*disabledAssertions) PanicErr(func(), ...any) Failure { return nil }
func (*disabledAssertions) NotPanicErr(func(), ...any) Failure { return nil }
func (*disabledAssertions) Fail(f Failure) { Default.Fail(f) }
func (*disabledAssertions) FailNow(f Failure) { Default.FailNow(f) }
func (*disabledAssertions) CallerInfo() []string { return Default.CallerInfo() }
var (
// DefaultLogger is the default [slog.Logger] used by [Default]
@@ -600,78 +723,166 @@ var (
// OK asserts that the value is not zero-valued, is nil, or panics, aka. "is ok".
//
// Logs the failure message with [DefaultLogger].
func OK(obj any, msg ...any) error {
return Default.OK(obj, msg...)
func Ok(obj any, msg ...any) {
Default.Ok(obj, msg...)
}
// OK asserts that the value is not zero-valued, is nil, or panics, aka. "is ok".
// Returns a Failure if the assertion fails, otherwise returns nil.
//
// Logs the failure message with [DefaultLogger].
func OkErr(obj any, msg ...any) Failure {
return Default.OkErr(obj, msg...)
}
// Equal asserts that the actual value is equal to the expected value.
//
// Logs the failure message with [DefaultLogger].
func Equal(expected, actual any, msg ...any) error {
return Default.Equal(expected, actual, msg...)
func Equal(expected, actual any, msg ...any) {
Default.Equal(expected, actual, msg...)
}
// Equal asserts that the actual value is equal to the expected value.
// Returns a Failure if the assertion fails, otherwise returns nil.
//
// Logs the failure message with [DefaultLogger].
func EqualErr(expected, actual any, msg ...any) Failure {
return Default.EqualErr(expected, actual, msg...)
}
// NotEqual asserts that the actual value is not equal to the expected value.
//
// Logs the failure message with [DefaultLogger].
func NotEqual(notExpected, actual any, msg ...any) error {
return Default.NotEqual(notExpected, actual, msg...)
func NotEqual(notExpected, actual any, msg ...any) {
Default.NotEqual(notExpected, actual, msg...)
}
// NotEqual asserts that the actual value is not equal to the expected value.
// Returns a Failure if the assertion fails, otherwise returns nil.
//
// Logs the failure message with [DefaultLogger].
func NotEqualErr(notExpected, actual any, msg ...any) Failure {
return Default.NotEqualErr(notExpected, actual, msg...)
}
// Nil asserts that the value is nil.
//
// Logs the failure message with [DefaultLogger].
func Nil(v any, msg ...any) error {
return Default.Nil(v, msg...)
func Nil(v any, msg ...any) {
Default.Nil(v, msg...)
}
// Nil asserts that the value is nil.
// Returns a Failure if the assertion fails, otherwise returns nil.
//
// Logs the failure message with [DefaultLogger].
func NilErr(v any, msg ...any) Failure {
return Default.NilErr(v, msg...)
}
// NotNil asserts that the value is not nil.
//
// Logs the failure message with [DefaultLogger].
func NotNil(v any, msg ...any) error {
return Default.NotNil(v, msg...)
func NotNil(v any, msg ...any) {
Default.NotNil(v, msg...)
}
// NotNil asserts that the value is not nil.
// Returns a Failure if the assertion fails, otherwise returns nil.
//
// Logs the failure message with [DefaultLogger].
func NotNilErr(v any, msg ...any) Failure {
return Default.NotNilErr(v, msg...)
}
// True asserts that the value is a boolean true.
//
// Logs the failure message with [DefaultLogger].
func True(v bool, msg ...any) error {
return Default.True(v, msg...)
func True(v bool, msg ...any) {
Default.True(v, msg...)
}
// True asserts that the value is a boolean true.
// Returns a Failure if the assertion fails, otherwise returns nil.
//
// Logs the failure message with [DefaultLogger].
func TrueErr(v bool, msg ...any) Failure {
return Default.TrueErr(v, msg...)
}
// False asserts that the value is a boolean false.
//
// Logs the failure message with [DefaultLogger].
func False(v bool, msg ...any) error {
return Default.False(v, msg...)
func False(v bool, msg ...any) {
Default.False(v, msg...)
}
// False asserts that the value is a boolean false.
// Returns a Failure if the assertion fails, otherwise returns nil.
//
// Logs the failure message with [DefaultLogger].
func FalseErr(v bool, msg ...any) Failure {
return Default.FalseErr(v, msg...)
}
// Zero asserts that the value is zero-valued.
//
// Logs the failure message with [DefaultLogger].
func Zero(v any, msg ...any) error {
return Default.Zero(v, msg...)
func Zero(v any, msg ...any) {
Default.Zero(v, msg...)
}
// Zero asserts that the value is zero-valued.
// Returns a Failure if the assertion fails, otherwise returns nil.
//
// Logs the failure message with [DefaultLogger].
func ZeroErr(v any, msg ...any) Failure {
return Default.ZeroErr(v, msg...)
}
// NotZero asserts that the value is not zero-valued.
//
// Logs the failure message with [DefaultLogger].
func NotZero(v any, msg ...any) error {
return Default.NotZero(v, msg...)
func NotZero(v any, msg ...any) {
Default.NotZero(v, msg...)
}
// NotZero asserts that the value is not zero-valued.
// Returns a Failure if the assertion fails, otherwise returns nil.
//
// Logs the failure message with [DefaultLogger].
func NotZeroErr(v any, msg ...any) Failure {
return Default.NotZeroErr(v, msg...)
}
// Panic asserts that the function panics.
//
// Logs the failure message with [DefaultLogger].
func Panic(fn func(), msg ...any) error {
return Default.Panic(fn, msg...)
func Panic(fn func(), msg ...any) {
Default.Panic(fn, msg...)
}
// Panic asserts that the function panics.
// Returns a Failure if the assertion fails, otherwise returns nil.
//
// Logs the failure message with [DefaultLogger].
func PanicErr(fn func(), msg ...any) Failure {
return Default.PanicErr(fn, msg...)
}
// NotPanic asserts that the function does not panics.
//
// Logs the failure message with [DefaultLogger].
func NotPanic(fn func(), msg ...any) error {
return Default.NotPanic(fn, msg...)
func NotPanic(fn func(), msg ...any) {
Default.NotPanic(fn, msg...)
}
// NotPanic asserts that the function does not panics.
// Returns a Failure if the assertion fails, otherwise returns nil.
//
// Logs the failure message with [DefaultLogger].
func NotPanicErr(fn func(), msg ...any) Failure {
return Default.NotPanicErr(fn, msg...)
}
// Fail logs the formatted failure message using [DefaultLogger].
@@ -683,3 +894,7 @@ func Fail(f Failure) {
func FailNow(f Failure) {
Default.FailNow(f)
}
func CallerInfo() []string {
return Default.CallerInfo()
}