container/stub: override goexit methods
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 1m52s
Test / Hpkg (push) Successful in 3m34s
Test / Sandbox (race detector) (push) Successful in 4m29s
Test / Hakurei (race detector) (push) Successful in 5m25s
Test / Hakurei (push) Successful in 2m25s
Test / Flake checks (push) Successful in 1m36s
All checks were successful
Test / Create distribution (push) Successful in 34s
Test / Sandbox (push) Successful in 1m52s
Test / Hpkg (push) Successful in 3m34s
Test / Sandbox (race detector) (push) Successful in 4m29s
Test / Hakurei (race detector) (push) Successful in 5m25s
Test / Hakurei (push) Successful in 2m25s
Test / Flake checks (push) Successful in 1m36s
FailNow, Fatal, Fatalf, SkipNow, Skip and Skipf must be called from the goroutine created by the test. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
parent
ddfb865e2d
commit
4051577d6b
@ -148,8 +148,8 @@ func checkSimple(t *testing.T, fname string, testCases []simpleTestCase) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
defer stub.HandleExit()
|
||||
k := &kstub{stub.New(t, func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{s} }, tc.want)}
|
||||
defer k.HandleExit()
|
||||
if err := tc.f(k); !reflect.DeepEqual(err, tc.wantErr) {
|
||||
t.Errorf("%s: error = %v, want %v", fname, err, tc.wantErr)
|
||||
}
|
||||
@ -184,12 +184,12 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
defer stub.HandleExit()
|
||||
state := &setupState{Params: tc.params}
|
||||
k := &kstub{stub.New(t,
|
||||
func(s *stub.Stub[syscallDispatcher]) syscallDispatcher { return &kstub{s} },
|
||||
stub.Expect{Calls: slices.Concat(tc.early, []stub.Call{{Name: stub.CallSeparator}}, tc.apply)},
|
||||
)}
|
||||
defer k.HandleExit()
|
||||
errEarly := tc.op.early(state, k)
|
||||
k.Expects(stub.CallSeparator)
|
||||
if !reflect.DeepEqual(errEarly, tc.wantErrEarly) {
|
||||
|
@ -1,15 +1,36 @@
|
||||
package stub
|
||||
|
||||
import "testing"
|
||||
|
||||
// PanicExit is a magic panic value treated as a simulated exit.
|
||||
const PanicExit = 0xdeadbeef
|
||||
|
||||
const (
|
||||
panicFailNow = 0xcafe0000 + iota
|
||||
panicFatal
|
||||
panicFatalf
|
||||
)
|
||||
|
||||
// HandleExit must be deferred before calling with the stub.
|
||||
func HandleExit() {
|
||||
r := recover()
|
||||
if r == PanicExit {
|
||||
return
|
||||
}
|
||||
if r != nil {
|
||||
func (s *Stub[K]) HandleExit() { handleExit(s.TB, true) }
|
||||
|
||||
func handleExit(t testing.TB, root bool) {
|
||||
switch r := recover(); r {
|
||||
case PanicExit:
|
||||
break
|
||||
|
||||
case panicFailNow:
|
||||
if root {
|
||||
t.FailNow()
|
||||
} else {
|
||||
t.Fail()
|
||||
}
|
||||
break
|
||||
|
||||
case panicFatal, panicFatalf, nil:
|
||||
break
|
||||
|
||||
default:
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
|
@ -2,18 +2,67 @@ package stub_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
_ "unsafe"
|
||||
|
||||
"hakurei.app/container/stub"
|
||||
)
|
||||
|
||||
//go:linkname handleExit hakurei.app/container/stub.handleExit
|
||||
func handleExit(_ testing.TB, _ bool)
|
||||
|
||||
// overrideTFailNow overrides the Fail and FailNow method.
|
||||
type overrideTFailNow struct {
|
||||
*testing.T
|
||||
failNow bool
|
||||
fail bool
|
||||
}
|
||||
|
||||
func (o *overrideTFailNow) FailNow() {
|
||||
if o.failNow {
|
||||
o.Errorf("attempted to FailNow twice")
|
||||
}
|
||||
o.failNow = true
|
||||
}
|
||||
|
||||
func (o *overrideTFailNow) Fail() {
|
||||
if o.fail {
|
||||
o.Errorf("attempted to Fail twice")
|
||||
}
|
||||
o.fail = true
|
||||
}
|
||||
|
||||
func TestHandleExit(t *testing.T) {
|
||||
t.Run("exit", func(t *testing.T) {
|
||||
defer stub.HandleExit()
|
||||
defer handleExit(t, true)
|
||||
panic(stub.PanicExit)
|
||||
})
|
||||
|
||||
t.Run("goexit", func(t *testing.T) {
|
||||
t.Run("FailNow", func(t *testing.T) {
|
||||
ot := &overrideTFailNow{T: t}
|
||||
defer func() {
|
||||
if !ot.failNow {
|
||||
t.Errorf("FailNow was never called")
|
||||
}
|
||||
}()
|
||||
defer handleExit(ot, true)
|
||||
panic(0xcafe0000)
|
||||
})
|
||||
|
||||
t.Run("Fail", func(t *testing.T) {
|
||||
ot := &overrideTFailNow{T: t}
|
||||
defer func() {
|
||||
if !ot.fail {
|
||||
t.Errorf("Fail was never called")
|
||||
}
|
||||
}()
|
||||
defer handleExit(ot, false)
|
||||
panic(0xcafe0000)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("nil", func(t *testing.T) {
|
||||
defer stub.HandleExit()
|
||||
defer handleExit(t, true)
|
||||
})
|
||||
|
||||
t.Run("passthrough", func(t *testing.T) {
|
||||
@ -24,7 +73,7 @@ func TestHandleExit(t *testing.T) {
|
||||
}
|
||||
|
||||
}()
|
||||
defer stub.HandleExit()
|
||||
defer handleExit(t, true)
|
||||
panic(0xcafebabe)
|
||||
})
|
||||
}
|
||||
|
@ -45,6 +45,13 @@ func New[K any](tb testing.TB, makeK func(s *Stub[K]) K, want Expect) *Stub[K] {
|
||||
return &Stub[K]{TB: tb, makeK: makeK, want: want, wg: new(sync.WaitGroup)}
|
||||
}
|
||||
|
||||
func (s *Stub[K]) FailNow() { panic(panicFailNow) }
|
||||
func (s *Stub[K]) Fatal(args ...any) { s.Error(args...); panic(panicFatal) }
|
||||
func (s *Stub[K]) Fatalf(format string, args ...any) { s.Errorf(format, args...); panic(panicFatalf) }
|
||||
func (s *Stub[K]) SkipNow() { panic("invalid call to SkipNow") }
|
||||
func (s *Stub[K]) Skip(...any) { panic("invalid call to Skip") }
|
||||
func (s *Stub[K]) Skipf(string, ...any) { panic("invalid call to Skipf") }
|
||||
|
||||
// New calls f in a new goroutine
|
||||
func (s *Stub[K]) New(f func(k K)) {
|
||||
s.Helper()
|
||||
@ -61,7 +68,7 @@ func (s *Stub[K]) New(f func(k K)) {
|
||||
s.Helper()
|
||||
|
||||
defer s.wg.Done()
|
||||
defer HandleExit()
|
||||
defer handleExit(s.TB, false)
|
||||
f(s.makeK(ds))
|
||||
}()
|
||||
}
|
||||
|
@ -13,29 +13,19 @@ type stubHolder struct{ *Stub[stubHolder] }
|
||||
type overrideT struct {
|
||||
*testing.T
|
||||
|
||||
fatal atomic.Pointer[func(args ...any)]
|
||||
fatalf atomic.Pointer[func(format string, args ...any)]
|
||||
error atomic.Pointer[func(args ...any)]
|
||||
errorf atomic.Pointer[func(format string, args ...any)]
|
||||
}
|
||||
|
||||
func (t *overrideT) Fatal(args ...any) {
|
||||
fp := t.fatal.Load()
|
||||
func (t *overrideT) Error(args ...any) {
|
||||
fp := t.error.Load()
|
||||
if fp == nil || *fp == nil {
|
||||
t.T.Fatal(args...)
|
||||
t.T.Error(args...)
|
||||
return
|
||||
}
|
||||
(*fp)(args...)
|
||||
}
|
||||
|
||||
func (t *overrideT) Fatalf(format string, args ...any) {
|
||||
fp := t.fatalf.Load()
|
||||
if fp == nil || *fp == nil {
|
||||
t.T.Fatalf(format, args...)
|
||||
return
|
||||
}
|
||||
(*fp)(format, args...)
|
||||
}
|
||||
|
||||
func (t *overrideT) Errorf(format string, args ...any) {
|
||||
fp := t.errorf.Load()
|
||||
if fp == nil || *fp == nil {
|
||||
@ -46,6 +36,47 @@ func (t *overrideT) Errorf(format string, args ...any) {
|
||||
}
|
||||
|
||||
func TestStub(t *testing.T) {
|
||||
t.Run("goexit", func(t *testing.T) {
|
||||
t.Run("FailNow", func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != panicFailNow {
|
||||
t.Errorf("recover: %v", r)
|
||||
}
|
||||
}()
|
||||
new(stubHolder).FailNow()
|
||||
})
|
||||
|
||||
t.Run("SkipNow", func(t *testing.T) {
|
||||
defer func() {
|
||||
want := "invalid call to SkipNow"
|
||||
if r := recover(); r != want {
|
||||
t.Errorf("recover: %v, want %v", r, want)
|
||||
}
|
||||
}()
|
||||
new(stubHolder).SkipNow()
|
||||
})
|
||||
|
||||
t.Run("Skip", func(t *testing.T) {
|
||||
defer func() {
|
||||
want := "invalid call to Skip"
|
||||
if r := recover(); r != want {
|
||||
t.Errorf("recover: %v, want %v", r, want)
|
||||
}
|
||||
}()
|
||||
new(stubHolder).Skip()
|
||||
})
|
||||
|
||||
t.Run("Skipf", func(t *testing.T) {
|
||||
defer func() {
|
||||
want := "invalid call to Skipf"
|
||||
if r := recover(); r != want {
|
||||
t.Errorf("recover: %v, want %v", r, want)
|
||||
}
|
||||
}()
|
||||
new(stubHolder).Skipf("")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("new", func(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
s := New(t, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||
@ -82,12 +113,12 @@ func TestStub(t *testing.T) {
|
||||
|
||||
t.Run("overrun", func(t *testing.T) {
|
||||
ot := &overrideT{T: t}
|
||||
ot.fatal.Store(checkFatal(t, "New: track overrun"))
|
||||
ot.error.Store(checkError(t, "New: track overrun"))
|
||||
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||
{"New", ExpectArgs{}, nil, nil},
|
||||
{"panic", ExpectArgs{"unreachable"}, nil, nil},
|
||||
}})
|
||||
func() { defer HandleExit(); s.New(func(k stubHolder) { panic("unreachable") }) }()
|
||||
func() { defer s.HandleExit(); s.New(func(k stubHolder) { panic("unreachable") }) }()
|
||||
|
||||
var visit int
|
||||
s.VisitIncomplete(func(s *Stub[stubHolder]) {
|
||||
@ -106,38 +137,38 @@ func TestStub(t *testing.T) {
|
||||
t.Run("expects", func(t *testing.T) {
|
||||
t.Run("overrun", func(t *testing.T) {
|
||||
ot := &overrideT{T: t}
|
||||
ot.fatal.Store(checkFatal(t, "Expects: advancing beyond expected calls"))
|
||||
ot.error.Store(checkError(t, "Expects: advancing beyond expected calls"))
|
||||
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{})
|
||||
func() { defer HandleExit(); s.Expects("unreachable") }()
|
||||
func() { defer s.HandleExit(); s.Expects("unreachable") }()
|
||||
})
|
||||
|
||||
t.Run("separator", func(t *testing.T) {
|
||||
t.Run("overrun", func(t *testing.T) {
|
||||
ot := &overrideT{T: t}
|
||||
ot.fatalf.Store(checkFatalf(t, "Expects: func = %s, separator overrun", "meow"))
|
||||
ot.errorf.Store(checkErrorf(t, "Expects: func = %s, separator overrun", "meow"))
|
||||
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||
{CallSeparator, ExpectArgs{}, nil, nil},
|
||||
}})
|
||||
func() { defer HandleExit(); s.Expects("meow") }()
|
||||
func() { defer s.HandleExit(); s.Expects("meow") }()
|
||||
})
|
||||
|
||||
t.Run("mismatch", func(t *testing.T) {
|
||||
ot := &overrideT{T: t}
|
||||
ot.fatalf.Store(checkFatalf(t, "Expects: separator, want %s", "panic"))
|
||||
ot.errorf.Store(checkErrorf(t, "Expects: separator, want %s", "panic"))
|
||||
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||
{"panic", ExpectArgs{}, nil, nil},
|
||||
}})
|
||||
func() { defer HandleExit(); s.Expects(CallSeparator) }()
|
||||
func() { defer s.HandleExit(); s.Expects(CallSeparator) }()
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("mismatch", func(t *testing.T) {
|
||||
ot := &overrideT{T: t}
|
||||
ot.fatalf.Store(checkFatalf(t, "Expects: func = %s, want %s", "meow", "nya"))
|
||||
ot.errorf.Store(checkErrorf(t, "Expects: func = %s, want %s", "meow", "nya"))
|
||||
s := New(ot, func(s *Stub[stubHolder]) stubHolder { return stubHolder{s} }, Expect{Calls: []Call{
|
||||
{"nya", ExpectArgs{}, nil, nil},
|
||||
}})
|
||||
func() { defer HandleExit(); s.Expects("meow") }()
|
||||
func() { defer s.HandleExit(); s.Expects("meow") }()
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -167,9 +198,9 @@ func TestCheckArg(t *testing.T) {
|
||||
}
|
||||
})
|
||||
t.Run("mismatch", func(t *testing.T) {
|
||||
defer HandleExit()
|
||||
defer s.HandleExit()
|
||||
s.Expects("meow")
|
||||
ot.errorf.Store(checkFatalf(t, "%s: %s = %#v, want %#v (%d)", "meow", "time", 0, -1, 1))
|
||||
ot.errorf.Store(checkErrorf(t, "%s: %s = %#v, want %#v (%d)", "meow", "time", 0, -1, 1))
|
||||
if CheckArg(s, "time", 0, 0) {
|
||||
t.Errorf("CheckArg: unexpected true")
|
||||
}
|
||||
@ -210,9 +241,9 @@ func TestCheckArgReflect(t *testing.T) {
|
||||
}
|
||||
})
|
||||
t.Run("mismatch", func(t *testing.T) {
|
||||
defer HandleExit()
|
||||
defer s.HandleExit()
|
||||
s.Expects("meow")
|
||||
ot.errorf.Store(checkFatalf(t, "%s: %s = %#v, want %#v (%d)", "meow", "time", 0, -1, 1))
|
||||
ot.errorf.Store(checkErrorf(t, "%s: %s = %#v, want %#v (%d)", "meow", "time", 0, -1, 1))
|
||||
if CheckArgReflect(s, "time", 0, 0) {
|
||||
t.Errorf("CheckArgReflect: unexpected true")
|
||||
}
|
||||
@ -229,35 +260,35 @@ func TestCheckArgReflect(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func checkFatal(t *testing.T, wantArgs ...any) *func(args ...any) {
|
||||
func checkError(t *testing.T, wantArgs ...any) *func(args ...any) {
|
||||
var called bool
|
||||
f := func(args ...any) {
|
||||
if called {
|
||||
panic("invalid call to fatal")
|
||||
panic("invalid call to error")
|
||||
}
|
||||
called = true
|
||||
|
||||
if !reflect.DeepEqual(args, wantArgs) {
|
||||
t.Errorf("Fatal: %#v, want %#v", args, wantArgs)
|
||||
t.Errorf("Error: %#v, want %#v", args, wantArgs)
|
||||
}
|
||||
panic(PanicExit)
|
||||
}
|
||||
return &f
|
||||
}
|
||||
|
||||
func checkFatalf(t *testing.T, wantFormat string, wantArgs ...any) *func(format string, args ...any) {
|
||||
func checkErrorf(t *testing.T, wantFormat string, wantArgs ...any) *func(format string, args ...any) {
|
||||
var called bool
|
||||
f := func(format string, args ...any) {
|
||||
if called {
|
||||
panic("invalid call to fatalf")
|
||||
panic("invalid call to errorf")
|
||||
}
|
||||
called = true
|
||||
|
||||
if format != wantFormat {
|
||||
t.Errorf("Fatalf: format = %q, want %q", format, wantFormat)
|
||||
t.Errorf("Errorf: format = %q, want %q", format, wantFormat)
|
||||
}
|
||||
if !reflect.DeepEqual(args, wantArgs) {
|
||||
t.Errorf("Fatalf: args = %#v, want %#v", args, wantArgs)
|
||||
t.Errorf("Errorf: args = %#v, want %#v", args, wantArgs)
|
||||
}
|
||||
panic(PanicExit)
|
||||
}
|
||||
|
@ -43,8 +43,8 @@ func checkOpBehaviour(t *testing.T, testCases []opBehaviourTestCase) {
|
||||
ec = (*Criteria)(&tc.ec)
|
||||
}
|
||||
|
||||
defer stub.HandleExit()
|
||||
sys, s := InternalNew(t, stub.Expect{Calls: slices.Concat(tc.apply, []stub.Call{{Name: stub.CallSeparator}}, tc.revert)}, tc.uid)
|
||||
defer s.HandleExit()
|
||||
errApply := tc.op.apply(sys)
|
||||
s.Expects(stub.CallSeparator)
|
||||
if !reflect.DeepEqual(errApply, tc.wantErrApply) {
|
||||
@ -90,8 +90,8 @@ func checkOpsBuilder(t *testing.T, fname string, testCases []opsBuilderTestCase)
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Helper()
|
||||
|
||||
defer stub.HandleExit()
|
||||
sys, s := InternalNew(t, tc.exp, tc.uid)
|
||||
defer s.HandleExit()
|
||||
tc.f(sys)
|
||||
s.VisitIncomplete(func(s *stub.Stub[syscallDispatcher]) {
|
||||
t.Helper()
|
||||
|
Loading…
x
Reference in New Issue
Block a user