66 "os"
77 "path/filepath"
88 "strings"
9+ "sync"
910 "testing"
1011
1112 tuikitIO "github.com/flowexec/tuikit/io"
@@ -39,26 +40,96 @@ type Context struct {
3940 cacheDir string
4041 configDir string
4142 wsDir string
43+ exit * ExitRecorder
4244}
4345
4446func (c * Context ) WorkspaceDir () string {
4547 return c .wsDir
4648}
4749
50+ // ExpectFailure flips the test context into capture mode: subsequent fatal
51+ // logger calls will be recorded on the context and cause the currently
52+ // executing command to return an error instead of failing the test. Reset
53+ // on every ResetTestContext call.
54+ func (c * Context ) ExpectFailure () {
55+ c .exit .setExpect (true )
56+ }
57+
58+ // ExitCalls returns the fatal messages captured while the context was in
59+ // failure-capture mode.
60+ func (c * Context ) ExitCalls () []string {
61+ return c .exit .snapshot ()
62+ }
63+
64+ // ExitRecorder routes logger fatal calls so they can be asserted on in tests
65+ // rather than aborting the process or the test goroutine.
66+ type ExitRecorder struct {
67+ mu sync.Mutex
68+ expect bool
69+ calls []string
70+ tb testing.TB
71+ }
72+
73+ // fatalExit is the panic value emitted by the recorder when capture mode is on.
74+ // CommandRunner.Run recovers from it and surfaces the message as an error.
75+ type fatalExit struct { msg string }
76+
77+ func (f fatalExit ) String () string { return f .msg }
78+
79+ func newExitRecorder (tb testing.TB ) * ExitRecorder {
80+ return & ExitRecorder {tb : tb }
81+ }
82+
83+ func (r * ExitRecorder ) setTB (tb testing.TB ) {
84+ r .mu .Lock ()
85+ defer r .mu .Unlock ()
86+ r .tb = tb
87+ r .expect = false
88+ r .calls = nil
89+ }
90+
91+ func (r * ExitRecorder ) setExpect (v bool ) {
92+ r .mu .Lock ()
93+ defer r .mu .Unlock ()
94+ r .expect = v
95+ if v {
96+ r .calls = nil
97+ }
98+ }
99+
100+ func (r * ExitRecorder ) snapshot () []string {
101+ r .mu .Lock ()
102+ defer r .mu .Unlock ()
103+ return append ([]string (nil ), r .calls ... )
104+ }
105+
106+ func (r * ExitRecorder ) exit (msg string , args ... any ) {
107+ formatted := fmt .Sprintf (msg , args ... )
108+ r .mu .Lock ()
109+ expect := r .expect
110+ if expect {
111+ r .calls = append (r .calls , formatted )
112+ }
113+ tb := r .tb
114+ r .mu .Unlock ()
115+ if expect {
116+ panic (fatalExit {msg : formatted })
117+ }
118+ tb .Fatalf ("logger exit called - %s" , formatted )
119+ }
120+
48121// NewContext creates a new context for testing runners. It initializes the context with
49122// a real logger that writes it's output to a temporary file.
50123// It also creates a temporary testing directory for the test workspace, user configs, and caches.
51124// Test environment variables are set the config and cache directories override paths.
52125func NewContext (ctx stdCtx.Context , tb testing.TB ) * Context {
53126 stdOut , stdIn := createTempIOFiles (tb )
127+ recorder := newExitRecorder (tb )
54128 tempLogger := tuikitIO .NewLogger (
55129 tuikitIO .WithOutput (stdOut ),
56130 tuikitIO .WithTheme (logger .Theme ("" )),
57131 tuikitIO .WithMode (tuikitIO .Text ),
58- tuikitIO .WithExitFunc (func (msg string , args ... any ) {
59- msg = fmt .Sprintf (msg , args ... )
60- tb .Fatalf ("logger exit called - %s" , msg )
61- }),
132+ tuikitIO .WithExitFunc (recorder .exit ),
62133 )
63134 logger .Init (logger.InitOptions {Logger : tempLogger , TestingTB : tb })
64135 ctxx , configDir , cacheDir , wsDir := newTestContext (ctx , tb , stdIn , stdOut )
@@ -67,6 +138,7 @@ func NewContext(ctx stdCtx.Context, tb testing.TB) *Context {
67138 configDir : configDir ,
68139 cacheDir : cacheDir ,
69140 wsDir : wsDir ,
141+ exit : recorder ,
70142 }
71143}
72144
@@ -126,14 +198,12 @@ func ResetTestContext(ctx *Context, tb testing.TB) {
126198 stdIn , stdOut := createTempIOFiles (tb )
127199 ctx .SetIO (stdIn , stdOut )
128200 setTestEnv (tb , ctx .configDir , ctx .cacheDir )
201+ ctx .exit .setTB (tb )
129202 newLogger := tuikitIO .NewLogger (
130203 tuikitIO .WithOutput (stdOut ),
131204 tuikitIO .WithTheme (logger .Theme ("" )),
132205 tuikitIO .WithMode (tuikitIO .Text ),
133- tuikitIO .WithExitFunc (func (msg string , args ... any ) {
134- msg = fmt .Sprintf (msg , args ... )
135- tb .Fatalf ("logger exit called - %s" , msg )
136- }),
206+ tuikitIO .WithExitFunc (ctx .exit .exit ),
137207 )
138208 logger .Init (logger.InitOptions {Logger : newLogger , TestingTB : tb })
139209}
0 commit comments