Skip to content

Commit

Permalink
feat: add *T# assertions fuctions for use from unit tests. (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
xiegeo authored Jan 5, 2023
1 parent a242429 commit 66e121a
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 4 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

When you don't need error handling

I found myself using this snipped lots.
I found myself using this snipped in a few places.

``` go
func must[T any](out T, err error) T {
Expand All @@ -28,7 +28,7 @@ Occasionally a little more, so made a home for them, DRY.
``` go
// NoError panics on error.
//
// Use this because test coverage.
// You can use this instead of ignoring errors that never happen by contract.
func NoError(err error) {
if err != nil {
panic(err)
Expand Down Expand Up @@ -69,12 +69,22 @@ func ExampleB3() {

// output:
// a
}
}
```

Assertions have optional debug arguments, to provide additional information when
violated. Usually, just pass in the line comment as string.

### Usage in unit tests

`must.*T#` will cause the function from using panic to using t.Fatal on the provided unit test interface.
As an alternative to [testify/require](https://pkg.go.dev/github.com/stretchr/testify/require) that allows value chaining.

For the functions that accept `debug ...any`, if the first debug value supports t.Fatal and t.Helper,
then panic will be changed to calling t.Fatal instead. (This is experimental)

Use of testify or other alterative targeting unit testing should be preferred over must, expectably outside of test setup.

### Panic test helpers

`must.Panic` and `must.Recover`. Useful for writing tests for panicky cases.
Expand Down
2 changes: 1 addition & 1 deletion error.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ func NoError(err error) {

// Value panics on error, otherwise returns the first value.
//
// Use this instead of writing a Must version of your function.
// You can use this instead of ignoring errors that never happen by contract.
func Value[T any](out T, err error) T {
if err != nil {
panic(err)
Expand Down
7 changes: 7 additions & 0 deletions panic.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ func Panic(f func(), debug ...any) (r any) {

func panicWith(debug []any, last any) {
if len(debug) > 0 {
if t, ok := debug[0].(TBSubset); ok {
t.Helper()
if last == nil {
t.Fatal(debug...)
}
t.Fatal(append(debug, last))
}
if last == nil {
panic(debug)
}
Expand Down
71 changes: 71 additions & 0 deletions testing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package must

// TBSubset is a subset of testing.TB, TBSubset may add or remove public methods that are defined in testing.TB
// without updating major version. TBSubset should only used to accept testing.TB without depending on testing in this package.
// It also severs as documentation to what features of testing.TB must.*T# functions depends on.
type TBSubset interface {
// The method comments are copied from *testing.T

// Helper marks the calling function as a test helper function.
// When printing file and line information, that function will be skipped.
// Helper may be called simultaneously from multiple goroutines.
Helper()
// Fatal is equivalent to Log followed by FailNow.
//
// Log formats its arguments using default formatting, analogous to Println, and records the text in the error log. For tests, the text will be printed only if the test fails or the -test.v flag is set. For benchmarks, the text is always printed to avoid having performance depend on the value of the -test.v flag.
//
// FailNow marks the function as having failed and stops its execution by calling runtime.Goexit (which then runs all deferred calls in the current goroutine). Execution will continue at the next test or benchmark. FailNow must be called from the goroutine running the test or benchmark function, not from other goroutines created during the test. Calling FailNow does not stop those other goroutines.
Fatal(args ...any)
}

// NoErrorT and alike *T# functions provide test fail instead of panic version of must.
//
// NoErrorT's signature follow other test fail functions for consistency.
func NoErrorT(err error) func(TBSubset) {
return func(t TBSubset) {
if err != nil {
t.Helper()
t.Fatal(err)
}
}
}

// ValueT and alike *T# functions provide test fail instead of panic version of must.
//
// The API has accepts TBSubset in a separate function call to allow function chaining,
// It can not go first because of limitations in go type inference. It can not be encapsulated
// in an interface because interface methods can not introduce generic parameters.
func ValueT[T any](out T, err error) func(TBSubset) T {
return func(t TBSubset) T {
if err != nil {
t.Helper()
t.Fatal(err)
}
return out
}
}

// VT is short for ValueT.
//
// ValueT and alike *T# functions provide test fail instead of panic version of must.
func VT[T any](out T, err error) func(TBSubset) T {
return ValueT(out, err)
}

// ValueT2 and alike *T# functions provide test fail instead of panic version of must.
func ValueT2[T any, U any](out1 T, out2 U, err error) func(TBSubset) (T, U) {
return func(t TBSubset) (T, U) {
if err != nil {
t.Helper()
t.Fatal(err)
}
return out1, out2
}
}

// VT2 is short for ValueT2.
//
// ValueT2 and alike *T# functions provide test fail instead of panic version of must.
func VT2[T any, U any](out1 T, out2 U, err error) func(TBSubset) (T, U) {
return ValueT2(out1, out2, err)
}

0 comments on commit 66e121a

Please sign in to comment.