aboutsummaryrefslogtreecommitdiffhomepage
path: root/container/stub/stub.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-03-17 16:09:14 +0900
committerOphestra <cat@gensokyo.uk>2026-03-17 16:09:14 +0900
commitbac583f89edd1360150143ff51f7264c01aa69ec (patch)
tree47914efe1ae9fae256ef93725097bdb9255c03d5 /container/stub/stub.go
parent722989c68256469963aee963bed9c6ab4bf55b67 (diff)
internal/stub: move from container
This package solves a very specific stubbing use case, in a less than elegant manner. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'container/stub/stub.go')
-rw-r--r--container/stub/stub.go153
1 files changed, 0 insertions, 153 deletions
diff --git a/container/stub/stub.go b/container/stub/stub.go
deleted file mode 100644
index a945fc0a..00000000
--- a/container/stub/stub.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// Package stub provides function call level stubbing and validation
-// for library functions that are impossible to check otherwise.
-package stub
-
-import (
- "reflect"
- "sync"
- "testing"
-)
-
-// this should prevent stub from being inadvertently imported outside tests
-var _ = func() {
- if !testing.Testing() {
- panic("stub imported while not in a test")
- }
-}
-
-const (
- // A CallSeparator denotes an injected separation between two groups of calls.
- CallSeparator = "\x00"
-)
-
-// A Stub is a collection of tracks of expected calls.
-type Stub[K any] struct {
- testing.TB
-
- // makeK creates a new K for a descendant [Stub].
- // This function may be called concurrently.
- makeK func(s *Stub[K]) K
-
- // want is a hierarchy of expected calls.
- want Expect
- // pos is the current position in [Expect.Calls].
- pos int
- // goroutine counts the number of goroutines created by this [Stub].
- goroutine int
- // sub stores the addresses of descendant [Stub] created by New.
- sub []*Stub[K]
- // wg waits for all descendants to complete.
- wg *sync.WaitGroup
-}
-
-// New creates a root [Stub].
-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() { s.Helper(); panic(panicFailNow) }
-func (s *Stub[K]) Fatal(args ...any) { s.Helper(); s.Error(args...); panic(panicFatal) }
-func (s *Stub[K]) Fatalf(format string, args ...any) {
- s.Helper()
- s.Errorf(format, args...)
- panic(panicFatalf)
-}
-
-func (s *Stub[K]) SkipNow() { s.Helper(); panic("invalid call to SkipNow") }
-func (s *Stub[K]) Skip(...any) { s.Helper(); panic("invalid call to Skip") }
-func (s *Stub[K]) Skipf(string, ...any) { s.Helper(); panic("invalid call to Skipf") }
-
-// New calls f in a new goroutine
-func (s *Stub[K]) New(f func(k K)) {
- s.Helper()
-
- s.Expects("New")
- if len(s.want.Tracks) <= s.goroutine {
- s.Fatal("New: track overrun")
- }
- ds := &Stub[K]{TB: s.TB, makeK: s.makeK, want: s.want.Tracks[s.goroutine], wg: s.wg}
- s.goroutine++
- s.sub = append(s.sub, ds)
- s.wg.Add(1)
- go func() {
- s.Helper()
-
- defer s.wg.Done()
- defer handleExitNew(s.TB)
- f(s.makeK(ds))
- }()
-}
-
-// Pos returns the current position of [Stub] in its [Expect.Calls]
-func (s *Stub[K]) Pos() int { return s.pos }
-
-// Len returns the length of [Expect.Calls].
-func (s *Stub[K]) Len() int { return len(s.want.Calls) }
-
-// VisitIncomplete calls f on an incomplete s and all its descendants.
-func (s *Stub[K]) VisitIncomplete(f func(s *Stub[K])) {
- s.Helper()
- s.wg.Wait()
-
- if s.want.Calls != nil && len(s.want.Calls) != s.pos {
- f(s)
- }
- for _, ds := range s.sub {
- ds.VisitIncomplete(f)
- }
-}
-
-// Expects checks the name of and returns the current [Call] and advances pos.
-func (s *Stub[K]) Expects(name string) (expect *Call) {
- s.Helper()
-
- if len(s.want.Calls) == s.pos {
- s.Fatal("Expects: advancing beyond expected calls")
- }
- expect = &s.want.Calls[s.pos]
- if name != expect.Name {
- if expect.Name == CallSeparator {
- s.Fatalf("Expects: func = %s, separator overrun", name)
- }
- if name == CallSeparator {
- s.Fatalf("Expects: separator, want %s", expect.Name)
- }
- s.Fatalf("Expects: func = %s, want %s", name, expect.Name)
- }
- s.pos++
- return
-}
-
-// CheckArg checks an argument comparable with the == operator. Avoid using this with pointers.
-func CheckArg[T comparable, K any](s *Stub[K], arg string, got T, n int) bool {
- s.Helper()
-
- pos := s.pos - 1
- if pos < 0 || pos >= len(s.want.Calls) {
- panic("invalid call to CheckArg")
- }
- expect := s.want.Calls[pos]
- want, ok := expect.Args[n].(T)
- if !ok || got != want {
- s.Errorf("%s: %s = %#v, want %#v (%d)", expect.Name, arg, got, want, pos)
- return false
- }
- return true
-}
-
-// CheckArgReflect checks an argument of any type.
-func CheckArgReflect[K any](s *Stub[K], arg string, got any, n int) bool {
- s.Helper()
-
- pos := s.pos - 1
- if pos < 0 || pos >= len(s.want.Calls) {
- panic("invalid call to CheckArgReflect")
- }
- expect := s.want.Calls[pos]
- want := expect.Args[n]
- if !reflect.DeepEqual(got, want) {
- s.Errorf("%s: %s = %#v, want %#v (%d)", expect.Name, arg, got, want, pos)
- return false
- }
- return true
-}