@@ -2,6 +2,7 @@ package cmd
22
33import (
44 "bufio"
5+ "context"
56 "errors"
67 "fmt"
78 "net/http"
@@ -31,7 +32,7 @@ const (
3132
3233type Cmd struct {
3334 opts []Opt
34- ready func (* exec.Cmd ) error
35+ ready func (context. Context , * exec.Cmd ) error
3536 stop func (* exec.Cmd ) error
3637 cmd * exec.Cmd
3738 readyTimeout time.Duration
@@ -42,7 +43,7 @@ type Opt func(*Cmd) error
4243func New (opts ... Opt ) * Cmd {
4344 return & Cmd {
4445 opts : opts ,
45- ready : func (* exec.Cmd ) error { return nil },
46+ ready : func (context. Context , * exec.Cmd ) error { return nil },
4647 stop : StopWithSignal (os .Interrupt ),
4748 readyTimeout : 30 * time .Second ,
4849 }
@@ -63,10 +64,13 @@ func (c *Cmd) Start() error {
6364}
6465
6566func (c * Cmd ) Ready () error {
67+ ctx , cancel := context .WithTimeout (context .Background (), c .readyTimeout )
68+ defer cancel ()
69+
6670 errCh := make (chan error , 1 )
6771 go func () {
6872 defer close (errCh )
69- errCh <- c .ready (c .cmd )
73+ errCh <- c .ready (ctx , c .cmd )
7074 }()
7175
7276 select {
@@ -92,6 +96,8 @@ func (c *Cmd) wrapErr(wErr, err error) error {
9296func WithCommand (name string , args ... string ) Opt {
9397 return func (c * Cmd ) error {
9498 c .cmd = exec .Command (name , args ... )
99+ c .cmd .Stdout = os .Stdout
100+ c .cmd .Stderr = os .Stderr
95101 return nil
96102 }
97103}
@@ -111,7 +117,7 @@ func WithCommandFn(fn func() (*exec.Cmd, error)) Opt {
111117
112118// WithReadyFn allows user to provide custom readiness function.
113119// Given fn should block until the command is ready.
114- func WithReadyFn (fn func (* exec.Cmd ) error ) Opt {
120+ func WithReadyFn (fn func (context. Context , * exec.Cmd ) error ) Opt {
115121 return func (c * Cmd ) error {
116122 c .ready = fn
117123 return nil
@@ -121,19 +127,20 @@ func WithReadyFn(fn func(*exec.Cmd) error) Opt {
121127// WithReadyHTTP sets the ready function to wait for url to return 200 OK.
122128func WithReadyHTTP (url string ) Opt {
123129 return func (c * Cmd ) error {
124- c .ready = func (cmd * exec.Cmd ) error {
130+ c .ready = func (ctx context. Context , cmd * exec.Cmd ) error {
125131 client := & http.Client {
126- Timeout : 10 * time .Second ,
132+ Timeout : 1 * time .Second ,
127133 }
128134 for {
135+ if ctx .Err () != nil {
136+ return ctx .Err ()
137+ }
129138 resp , err := client .Get (url )
130- if err != nil {
139+ if err != nil || resp .StatusCode != http .StatusOK {
140+ time .Sleep (time .Millisecond * 100 )
131141 continue
132142 }
133- if resp .StatusCode == http .StatusOK {
134- return nil
135- }
136- time .Sleep (100 * time .Millisecond )
143+ return nil
137144 }
138145 }
139146 return nil
@@ -213,7 +220,7 @@ func WithReadyTimeout(d time.Duration) Opt {
213220// This is useful for commands that exit on their own and don't need to be stopped manually.
214221func WithWaitExit () Opt {
215222 return func (c * Cmd ) error {
216- c .ready = func (cmd * exec.Cmd ) error { return cmd .Wait () }
223+ c .ready = func (_ context. Context , cmd * exec.Cmd ) error { return cmd .Wait () }
217224 c .stop = func (* exec.Cmd ) error { return nil }
218225 return nil
219226 }
@@ -227,17 +234,6 @@ func WithExecCmd(cmd *exec.Cmd) Opt {
227234 }
228235}
229236
230- // WithPreCmd runs the given command as part of the setup.
231- // This can be used to prepare the actual main command.
232- func WithPreCmd (cmd * exec.Cmd ) Opt {
233- return func (c * Cmd ) error {
234- if err := cmd .Run (); err != nil {
235- return fmt .Errorf ("%w: %w" , ErrPreCmdFailed , err )
236- }
237- return nil
238- }
239- }
240-
241237// WithGoCode builds the given Go projects and sets the main package as the command.
242238// By default the the output binary is instrumented to collect coverage data
243239// Working directory for build command is set to modulePath which means that the mainPkg should be relative to it.
@@ -316,7 +312,7 @@ func StopWithSignal(s os.Signal) func(*exec.Cmd) error {
316312}
317313
318314// MatchLine waits for the command to output a line that matches the given regular expression.
319- func MatchingLine (exp string , cmd * exec.Cmd ) (func (* exec.Cmd ) error , error ) {
315+ func MatchingLine (exp string , cmd * exec.Cmd ) (func (context. Context , * exec.Cmd ) error , error ) {
320316 if cmd == nil {
321317 return nil , ErrNilCmdRegexp
322318 }
@@ -332,7 +328,7 @@ func MatchingLine(exp string, cmd *exec.Cmd) (func(*exec.Cmd) error, error) {
332328 return nil , fmt .Errorf ("%w: %w" , ErrOutputPipe , err )
333329 }
334330
335- return func (cmd * exec.Cmd ) error {
331+ return func (ctx context. Context , cmd * exec.Cmd ) error {
336332 scanner := bufio .NewScanner (stdout )
337333 for scanner .Scan () {
338334 if re .Match (scanner .Bytes ()) {
@@ -343,6 +339,9 @@ func MatchingLine(exp string, cmd *exec.Cmd) (func(*exec.Cmd) error, error) {
343339 }()
344340 return nil
345341 }
342+ if ctx .Err () != nil {
343+ return ctx .Err ()
344+ }
346345 }
347346 return errors .Join (ErrNoMatchingLine , scanner .Err ())
348347 }, nil
0 commit comments