srs/internal/errors/errors_test.go
winlin f7f0676c9e Proxy: Rewrite internal/errors on stdlib with a single withStack struct.
Replaces the pkg/errors fork with a thin wrapper over the standard
library's errors package. A single withStack struct captures stack
traces via runtime.Callers, while fmt.Errorf("%w", ...) handles all
message wrapping. This enables errors.Is/As/Unwrap chain traversal
(the fork silently broke them) and deletes ~190 lines of stack/frame
formatting code. Adds table-driven tests at 100% statement coverage.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 18:36:59 -04:00

234 lines
5.8 KiB
Go

// Copyright (c) 2026 Winlin
//
// SPDX-License-Identifier: MIT
package errors
import (
stderrors "errors"
"fmt"
"strings"
"testing"
)
func TestNew_MessageAndStack(t *testing.T) {
err := New("boom")
if err == nil {
t.Fatal("New returned nil")
}
if err.Error() != "boom" {
t.Fatalf("Error() = %q, want %q", err.Error(), "boom")
}
ws, ok := err.(*withStack)
if !ok {
t.Fatalf("New did not return *withStack, got %T", err)
}
if len(ws.StackTrace()) == 0 {
t.Fatal("StackTrace is empty")
}
}
func TestErrorf_FormatsMessage(t *testing.T) {
err := Errorf("code=%d reason=%s", 42, "oops")
if err.Error() != "code=42 reason=oops" {
t.Fatalf("Error() = %q", err.Error())
}
}
func TestErrorf_SupportsWrapVerb(t *testing.T) {
root := stderrors.New("root")
err := Errorf("ctx: %w", root)
if !stderrors.Is(err, root) {
t.Fatal("errors.Is did not find root through Errorf(%w)")
}
}
func TestWithStack_NilReturnsNil(t *testing.T) {
if got := WithStack(nil); got != nil {
t.Fatalf("WithStack(nil) = %v, want nil", got)
}
}
func TestWithStack_PreservesMessage(t *testing.T) {
inner := stderrors.New("plain")
err := WithStack(inner)
if err.Error() != "plain" {
t.Fatalf("Error() = %q, want %q", err.Error(), "plain")
}
if !stderrors.Is(err, inner) {
t.Fatal("errors.Is did not find inner through WithStack")
}
}
func TestWithMessage_NilReturnsNil(t *testing.T) {
if got := WithMessage(nil, "ignored"); got != nil {
t.Fatalf("WithMessage(nil) = %v, want nil", got)
}
}
func TestWithMessage_PrependsAndWraps(t *testing.T) {
inner := stderrors.New("root")
err := WithMessage(inner, "ctx")
if err.Error() != "ctx: root" {
t.Fatalf("Error() = %q", err.Error())
}
if !stderrors.Is(err, inner) {
t.Fatal("errors.Is did not traverse WithMessage")
}
// WithMessage must not capture a stack — verify the result is not a *withStack.
if _, ok := err.(*withStack); ok {
t.Fatal("WithMessage should not attach a stack")
}
}
func TestWrap_NilReturnsNil(t *testing.T) {
if got := Wrap(nil, "ignored"); got != nil {
t.Fatalf("Wrap(nil) = %v, want nil", got)
}
}
func TestWrap_MessageAndStackAndChain(t *testing.T) {
inner := stderrors.New("root")
err := Wrap(inner, "ctx")
if err.Error() != "ctx: root" {
t.Fatalf("Error() = %q", err.Error())
}
ws, ok := err.(*withStack)
if !ok {
t.Fatalf("Wrap did not return *withStack, got %T", err)
}
if len(ws.StackTrace()) == 0 {
t.Fatal("StackTrace is empty")
}
if !stderrors.Is(err, inner) {
t.Fatal("errors.Is did not traverse Wrap")
}
}
func TestWrapf_NilReturnsNil(t *testing.T) {
if got := Wrapf(nil, "ignored %d", 1); got != nil {
t.Fatalf("Wrapf(nil) = %v, want nil", got)
}
}
func TestWrapf_FormatsAndChains(t *testing.T) {
inner := stderrors.New("root")
err := Wrapf(inner, "ctx=%d op=%s", 7, "read")
if err.Error() != "ctx=7 op=read: root" {
t.Fatalf("Error() = %q", err.Error())
}
if !stderrors.Is(err, inner) {
t.Fatal("errors.Is did not traverse Wrapf")
}
}
func TestCause_NilReturnsNil(t *testing.T) {
if got := Cause(nil); got != nil {
t.Fatalf("Cause(nil) = %v, want nil", got)
}
}
func TestCause_NoUnwrapReturnsSelf(t *testing.T) {
root := stderrors.New("root")
if got := Cause(root); got != root {
t.Fatalf("Cause(root) = %v, want root", got)
}
}
func TestCause_WalksToRoot(t *testing.T) {
root := stderrors.New("root")
err := Wrap(Wrap(WithMessage(root, "a"), "b"), "c")
if got := Cause(err); got != root {
t.Fatalf("Cause = %v, want root", got)
}
}
func TestUnwrap_ReturnsInner(t *testing.T) {
inner := stderrors.New("inner")
err := WithStack(inner)
if got := stderrors.Unwrap(err); got != inner {
t.Fatalf("Unwrap = %v, want inner", got)
}
}
func TestFormat_S(t *testing.T) {
err := New("msg")
got := fmt.Sprintf("%s", err)
if got != "msg" {
t.Fatalf("%%s = %q, want %q", got, "msg")
}
}
func TestFormat_VFallsThroughToS(t *testing.T) {
err := New("msg")
got := fmt.Sprintf("%v", err)
if got != "msg" {
t.Fatalf("%%v = %q, want %q", got, "msg")
}
}
func TestFormat_VPlusIncludesStack(t *testing.T) {
err := New("msg")
got := fmt.Sprintf("%+v", err)
if !strings.HasPrefix(got, "msg") {
t.Fatalf("%%+v output does not start with message: %q", got)
}
// Must include this test function in the captured stack.
if !strings.Contains(got, "TestFormat_VPlusIncludesStack") {
t.Fatalf("%%+v output missing caller frame:\n%s", got)
}
// Must include a file:line reference.
if !strings.Contains(got, "errors_test.go:") {
t.Fatalf("%%+v output missing file:line:\n%s", got)
}
}
func TestFormat_Q(t *testing.T) {
err := New("msg")
got := fmt.Sprintf("%q", err)
if got != `"msg"` {
t.Fatalf("%%q = %q, want %q", got, `"msg"`)
}
}
func TestIs_ThroughWrapChain(t *testing.T) {
sentinel := stderrors.New("sentinel")
err := Wrap(WithMessage(WithStack(sentinel), "mid"), "outer")
if !stderrors.Is(err, sentinel) {
t.Fatal("errors.Is failed to traverse Wrap/WithMessage/WithStack chain")
}
}
type typedErr struct{ code int }
func (t *typedErr) Error() string { return fmt.Sprintf("typed(%d)", t.code) }
func TestAs_ThroughWrapChain(t *testing.T) {
target := &typedErr{code: 7}
err := Wrap(WithStack(target), "ctx")
var got *typedErr
if !stderrors.As(err, &got) {
t.Fatal("errors.As failed to find *typedErr in chain")
}
if got.code != 7 {
t.Fatalf("As returned code=%d, want 7", got.code)
}
}
func TestReExports_AreStdlib(t *testing.T) {
// Sanity: the re-exports must actually be the stdlib functions.
a := stderrors.New("a")
b := stderrors.New("b")
joined := Join(a, b)
if !Is(joined, a) || !Is(joined, b) {
t.Fatal("Join/Is re-exports do not match stdlib behavior")
}
if Unwrap(WithStack(a)) != a {
t.Fatal("Unwrap re-export does not match stdlib behavior")
}
var target *typedErr
te := &typedErr{code: 1}
if !As(WithStack(te), &target) {
t.Fatal("As re-export does not match stdlib behavior")
}
}