Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 24 additions & 25 deletions dep/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import (
"bufio"
"context"
"errors"
"fmt"
"net/http"
Expand Down Expand Up @@ -31,7 +32,7 @@

type Cmd struct {
opts []Opt
ready func(*exec.Cmd) error
ready func(context.Context, *exec.Cmd) error
stop func(*exec.Cmd) error
cmd *exec.Cmd
readyTimeout time.Duration
Expand All @@ -42,7 +43,7 @@
func New(opts ...Opt) *Cmd {
return &Cmd{
opts: opts,
ready: func(*exec.Cmd) error { return nil },
ready: func(context.Context, *exec.Cmd) error { return nil },
stop: StopWithSignal(os.Interrupt),
readyTimeout: 30 * time.Second,
}
Expand All @@ -63,10 +64,13 @@
}

func (c *Cmd) Ready() error {
ctx, cancel := context.WithTimeout(context.Background(), c.readyTimeout)
defer cancel()

errCh := make(chan error, 1)
go func() {
defer close(errCh)
errCh <- c.ready(c.cmd)
errCh <- c.ready(ctx, c.cmd)
}()

select {
Expand All @@ -92,6 +96,8 @@
func WithCommand(name string, args ...string) Opt {
return func(c *Cmd) error {
c.cmd = exec.Command(name, args...)
c.cmd.Stdout = os.Stdout
c.cmd.Stderr = os.Stderr
return nil
}
}
Expand All @@ -111,7 +117,7 @@

// WithReadyFn allows user to provide custom readiness function.
// Given fn should block until the command is ready.
func WithReadyFn(fn func(*exec.Cmd) error) Opt {
func WithReadyFn(fn func(context.Context, *exec.Cmd) error) Opt {
return func(c *Cmd) error {
c.ready = fn
return nil
Expand All @@ -121,19 +127,20 @@
// WithReadyHTTP sets the ready function to wait for url to return 200 OK.
func WithReadyHTTP(url string) Opt {
return func(c *Cmd) error {
c.ready = func(cmd *exec.Cmd) error {
c.ready = func(ctx context.Context, cmd *exec.Cmd) error {
client := &http.Client{
Timeout: 10 * time.Second,
Timeout: 1 * time.Second,
}
for {
if ctx.Err() != nil {
return ctx.Err()
}

Check warning on line 137 in dep/cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

dep/cmd/cmd.go#L136-L137

Added lines #L136 - L137 were not covered by tests
resp, err := client.Get(url)
if err != nil {
if err != nil || resp.StatusCode != http.StatusOK {
time.Sleep(time.Millisecond * 100)

Check warning on line 140 in dep/cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

dep/cmd/cmd.go#L140

Added line #L140 was not covered by tests
continue
}
if resp.StatusCode == http.StatusOK {
return nil
}
time.Sleep(100 * time.Millisecond)
return nil
}
}
return nil
Expand Down Expand Up @@ -213,7 +220,7 @@
// This is useful for commands that exit on their own and don't need to be stopped manually.
func WithWaitExit() Opt {
return func(c *Cmd) error {
c.ready = func(cmd *exec.Cmd) error { return cmd.Wait() }
c.ready = func(_ context.Context, cmd *exec.Cmd) error { return cmd.Wait() }
c.stop = func(*exec.Cmd) error { return nil }
return nil
}
Expand All @@ -227,17 +234,6 @@
}
}

// WithPreCmd runs the given command as part of the setup.
// This can be used to prepare the actual main command.
func WithPreCmd(cmd *exec.Cmd) Opt {
return func(c *Cmd) error {
if err := cmd.Run(); err != nil {
return fmt.Errorf("%w: %w", ErrPreCmdFailed, err)
}
return nil
}
}

// WithGoCode builds the given Go projects and sets the main package as the command.
// By default the the output binary is instrumented to collect coverage data
// Working directory for build command is set to modulePath which means that the mainPkg should be relative to it.
Expand Down Expand Up @@ -316,7 +312,7 @@
}

// MatchLine waits for the command to output a line that matches the given regular expression.
func MatchingLine(exp string, cmd *exec.Cmd) (func(*exec.Cmd) error, error) {
func MatchingLine(exp string, cmd *exec.Cmd) (func(context.Context, *exec.Cmd) error, error) {
if cmd == nil {
return nil, ErrNilCmdRegexp
}
Expand All @@ -332,7 +328,7 @@
return nil, fmt.Errorf("%w: %w", ErrOutputPipe, err)
}

return func(cmd *exec.Cmd) error {
return func(ctx context.Context, cmd *exec.Cmd) error {
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
if re.Match(scanner.Bytes()) {
Expand All @@ -343,6 +339,9 @@
}()
return nil
}
if ctx.Err() != nil {
return ctx.Err()
}

Check warning on line 344 in dep/cmd/cmd.go

View check run for this annotation

Codecov / codecov/patch

dep/cmd/cmd.go#L343-L344

Added lines #L343 - L344 were not covered by tests
}
return errors.Join(ErrNoMatchingLine, scanner.Err())
}, nil
Expand Down
21 changes: 3 additions & 18 deletions dep/cmd/cmd_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cmd_test

import (
"context"
"net/http"
"net/http/httptest"
"os"
Expand Down Expand Up @@ -83,14 +84,6 @@ func TestCmd(t *testing.T) {
),
err: cmd.ErrNilCmdRegexp,
},
{
name: "WithBuildCmd_error",
cmd: cmd.New(
cmd.WithPreCmd(exec.Command("go", "build", "non/existing/path/main.go")),
cmd.WithCommand("non/existing/path/main"),
),
err: cmd.ErrPreCmdFailed,
},
{
name: "WithEnv",
cmd: cmd.New(
Expand All @@ -112,18 +105,10 @@ func TestCmd(t *testing.T) {
{
name: "WithExecCmd",
cmd: cmd.New(
cmd.WithExecCmd(exec.Command("go", "version")),
cmd.WithExecCmd(exec.Command("go", "build", "-o", waitBin, waitPkg+"/main.go")),
cmd.WithWaitExit(),
),
},
{
name: "WithBuildCmd",
cmd: cmd.New(
cmd.WithPreCmd(exec.Command("go", "build", "-o", waitBin, waitPkg+"/main.go")),
cmd.WithCommand(waitBin),
cmd.WithWaitMatchingLine("Waiting for signal"),
),
},
{
name: "DefaultReady",
cmd: cmd.New(
Expand Down Expand Up @@ -202,7 +187,7 @@ func TestCmd_WithGoCode_Coverage(t *testing.T) {
require.Len(t, files, 2)
}

func blockForever(*exec.Cmd) error {
func blockForever(context.Context, *exec.Cmd) error {
select {}
}

Expand Down