diff --git a/tinyssert/tinyssert.go b/tinyssert/tinyssert.go index 1128cb7..79f1cb7 100644 --- a/tinyssert/tinyssert.go +++ b/tinyssert/tinyssert.go @@ -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() +}