@@ -2,6 +2,7 @@ package cmd
22
33import (
44 "bufio"
5+ "context"
56 "errors"
67 "fmt"
78 "net/http"
@@ -32,7 +33,7 @@ const (
3233
3334type Cmd struct {
3435 opts []Opt
35- ready func (* exec.Cmd ) error
36+ ready func (context. Context , * exec.Cmd ) error
3637 stop func (* exec.Cmd ) error
3738 cmd * exec.Cmd
3839 readyTimeout time.Duration
@@ -43,7 +44,7 @@ type Opt func(*Cmd) error
4344func New (opts ... Opt ) * Cmd {
4445 return & Cmd {
4546 opts : opts ,
46- ready : func (* exec.Cmd ) error { return nil },
47+ ready : func (context. Context , * exec.Cmd ) error { return nil },
4748 stop : StopWithSignal (os .Interrupt ),
4849 readyTimeout : 30 * time .Second ,
4950 }
@@ -64,10 +65,13 @@ func (c *Cmd) Start() error {
6465}
6566
6667func (c * Cmd ) Ready () error {
68+ ctx , cancel := context .WithTimeout (context .Background (), c .readyTimeout )
69+ defer cancel ()
70+
6771 errCh := make (chan error , 1 )
6872 go func () {
6973 defer close (errCh )
70- errCh <- c .ready (c .cmd )
74+ errCh <- c .ready (ctx , c .cmd )
7175 }()
7276
7377 select {
@@ -93,13 +97,15 @@ func (c *Cmd) wrapErr(wErr, err error) error {
9397func WithCommand (name string , args ... string ) Opt {
9498 return func (c * Cmd ) error {
9599 c .cmd = exec .Command (name , args ... )
100+ c .cmd .Stdout = os .Stdout
101+ c .cmd .Stderr = os .Stderr
96102 return nil
97103 }
98104}
99105
100106// WithReadyFn allows user to provide custom readiness function.
101107// Given fn should block until the command is ready.
102- func WithReadyFn (fn func (* exec.Cmd ) error ) Opt {
108+ func WithReadyFn (fn func (context. Context , * exec.Cmd ) error ) Opt {
103109 return func (c * Cmd ) error {
104110 c .ready = fn
105111 return nil
@@ -109,19 +115,20 @@ func WithReadyFn(fn func(*exec.Cmd) error) Opt {
109115// WithReadyHTTP sets the ready function to wait for url to return 200 OK.
110116func WithReadyHTTP (url string ) Opt {
111117 return func (c * Cmd ) error {
112- c .ready = func (cmd * exec.Cmd ) error {
118+ c .ready = func (ctx context. Context , cmd * exec.Cmd ) error {
113119 client := & http.Client {
114- Timeout : 10 * time .Second ,
120+ Timeout : 1 * time .Second ,
115121 }
116122 for {
123+ if ctx .Err () != nil {
124+ return ctx .Err ()
125+ }
117126 resp , err := client .Get (url )
118- if err != nil {
127+ if err != nil || resp .StatusCode != http .StatusOK {
128+ time .Sleep (time .Millisecond * 100 )
119129 continue
120130 }
121- if resp .StatusCode == http .StatusOK {
122- return nil
123- }
124- time .Sleep (100 * time .Millisecond )
131+ return nil
125132 }
126133 }
127134 return nil
@@ -201,7 +208,7 @@ func WithReadyTimeout(d time.Duration) Opt {
201208// This is useful for commands that exit on their own and don't need to be stopped manually.
202209func WithWaitExit () Opt {
203210 return func (c * Cmd ) error {
204- c .ready = func (cmd * exec.Cmd ) error { return cmd .Wait () }
211+ c .ready = func (_ context. Context , cmd * exec.Cmd ) error { return cmd .Wait () }
205212 c .stop = func (* exec.Cmd ) error { return nil }
206213 return nil
207214 }
@@ -215,17 +222,6 @@ func WithExecCmd(cmd *exec.Cmd) Opt {
215222 }
216223}
217224
218- // WithPreCmd runs the given command as part of the setup.
219- // This can be used to prepare the actual main command.
220- func WithPreCmd (cmd * exec.Cmd ) Opt {
221- return func (c * Cmd ) error {
222- if err := cmd .Run (); err != nil {
223- return fmt .Errorf ("%w: %w" , ErrPreCmdFailed , err )
224- }
225- return nil
226- }
227- }
228-
229225// WithGoCode builds the given Go projects and sets the main package as the command.
230226// By default the the output binary is instrumented to collect coverage data
231227// Working directory for build command is set to modulePath which means that the mainPkg should be relative to it.
@@ -304,7 +300,7 @@ func StopWithSignal(s os.Signal) func(*exec.Cmd) error {
304300}
305301
306302// MatchLine waits for the command to output a line that matches the given regular expression.
307- func MatchingLine (exp string , cmd * exec.Cmd ) (func (* exec.Cmd ) error , error ) {
303+ func MatchingLine (exp string , cmd * exec.Cmd ) (func (context. Context , * exec.Cmd ) error , error ) {
308304 if cmd == nil {
309305 return nil , ErrNilCmdRegexp
310306 }
@@ -320,7 +316,7 @@ func MatchingLine(exp string, cmd *exec.Cmd) (func(*exec.Cmd) error, error) {
320316 return nil , fmt .Errorf ("%w: %w" , ErrOutputPipe , err )
321317 }
322318
323- return func (cmd * exec.Cmd ) error {
319+ return func (ctx context. Context , cmd * exec.Cmd ) error {
324320 scanner := bufio .NewScanner (stdout )
325321 for scanner .Scan () {
326322 if re .Match (scanner .Bytes ()) {
@@ -331,6 +327,9 @@ func MatchingLine(exp string, cmd *exec.Cmd) (func(*exec.Cmd) error, error) {
331327 }()
332328 return nil
333329 }
330+ if ctx .Err () != nil {
331+ return ctx .Err ()
332+ }
334333 }
335334 return errors .Join (ErrNoMatchingLine , scanner .Err ())
336335 }, nil
0 commit comments