- public API streamline
- Failf[T]("msg") - originate a failure from a message; embed a cause with %w
- Err[T](err) - sets .err verbatim (the return zero, err / sentinel case)
83 lines
2.6 KiB
Go
83 lines
2.6 KiB
Go
package result_test
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.djmil.dev/go/template/pkg/result"
|
|
)
|
|
|
|
var errRoot = errors.New("root cause")
|
|
|
|
// Intended usage of Wrap is demonstrated in example_test.go (parsePort). These
|
|
// tests pin the behavioral guarantees that an example's Output cannot assert:
|
|
// chain preservation, file:line stamping, stack survival, and the panic
|
|
// contract on misuse.
|
|
|
|
// TestWrapRetypesVerbatim verifies that Wrap with no message carries a failure
|
|
// from one value type to another, preserving the error chain unchanged.
|
|
func TestWrapRetypesVerbatim(t *testing.T) {
|
|
src := result.Err[int](errRoot) // Expect[int]
|
|
|
|
dst := result.Wrap[string](src) // Expect[string]
|
|
|
|
if !errors.Is(dst.Err(), errRoot) {
|
|
t.Fatalf("errors.Is chain not preserved: %v", dst.Err())
|
|
}
|
|
if dst.Err().Error() != errRoot.Error() {
|
|
t.Fatalf("verbatim wrap changed the message: got %q want %q", dst.Err(), errRoot)
|
|
}
|
|
}
|
|
|
|
// TestWrapAnnotates verifies that a context message is prepended with the
|
|
// caller's file:line while the underlying chain stays intact and leads the
|
|
// trace (outermost context first, root cause last).
|
|
func TestWrapAnnotates(t *testing.T) {
|
|
src := result.Failf[int]("parse %d", 7)
|
|
|
|
dst := result.Wrap[string](src, "load config id=%d", 42)
|
|
|
|
msg := dst.Err().Error()
|
|
if !strings.Contains(msg, "load config id=42") {
|
|
t.Fatalf("formatted context missing: %q", msg)
|
|
}
|
|
if !strings.Contains(msg, "wrap_test.go:") {
|
|
t.Fatalf("caller file:line not prepended: %q", msg)
|
|
}
|
|
if !strings.Contains(msg, "parse 7") {
|
|
t.Fatalf("underlying cause lost: %q", msg)
|
|
}
|
|
if first, _, _ := strings.Cut(msg, "\n"); !strings.Contains(first, "load config id=42") {
|
|
t.Fatalf("context should head the trace, got first line %q", first)
|
|
}
|
|
}
|
|
|
|
// TestWrapPreservesStackError verifies that a failure captured by Expect (a
|
|
// *stackError) survives the retype, so StackTrace still resolves it.
|
|
func TestWrapPreservesStackError(t *testing.T) {
|
|
captured := result.Run(func() {
|
|
result.Err[int](errRoot).Expect("inner")
|
|
})
|
|
if captured == nil {
|
|
t.Fatal("setup: expected Run to collect an error")
|
|
}
|
|
|
|
dst := result.Wrap[string](result.Err[int](captured), "outer")
|
|
|
|
if result.StackTrace(dst.Err()) == "" {
|
|
t.Fatal("stack trace lost through Wrap")
|
|
}
|
|
}
|
|
|
|
// TestWrapOnSuccessPanics locks in the documented contract: Wrap is only valid
|
|
// on a failed result; calling it on a success is a programmer error.
|
|
func TestWrapOnSuccessPanics(t *testing.T) {
|
|
defer func() {
|
|
if recover() == nil {
|
|
t.Fatal("expected Wrap on a successful Expect to panic")
|
|
}
|
|
}()
|
|
_ = result.Wrap[string](result.Ok(1))
|
|
}
|