feat(exec): add background command support
This commit is contained in:
parent
62e7bd7814
commit
87bebd7fa6
2 changed files with 87 additions and 2 deletions
42
exec/exec.go
42
exec/exec.go
|
|
@ -19,8 +19,8 @@ type Options struct {
|
|||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
// If true, command will run in background (not implemented in this wrapper yet)
|
||||
// Background bool
|
||||
// Background runs the command asynchronously and returns from Run immediately.
|
||||
Background bool
|
||||
}
|
||||
|
||||
// Command wraps os/exec.Command with logging and context
|
||||
|
|
@ -79,9 +79,39 @@ func (c *Cmd) WithLogger(l Logger) *Cmd {
|
|||
return c
|
||||
}
|
||||
|
||||
// WithBackground configures whether Run should wait for the command to finish.
|
||||
func (c *Cmd) WithBackground(background bool) *Cmd {
|
||||
c.opts.Background = background
|
||||
return c
|
||||
}
|
||||
|
||||
// Start launches the command.
|
||||
func (c *Cmd) Start() error {
|
||||
c.prepare()
|
||||
c.logDebug("executing command")
|
||||
|
||||
if err := c.cmd.Start(); err != nil {
|
||||
wrapped := wrapError("Cmd.Start", err, c.name, c.args)
|
||||
c.logError("command failed", wrapped)
|
||||
return wrapped
|
||||
}
|
||||
|
||||
if c.opts.Background {
|
||||
go func(cmd *exec.Cmd) {
|
||||
_ = cmd.Wait()
|
||||
}(c.cmd)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run executes the command and waits for it to finish.
|
||||
// It automatically logs the command execution at debug level.
|
||||
func (c *Cmd) Run() error {
|
||||
if c.opts.Background {
|
||||
return c.Start()
|
||||
}
|
||||
|
||||
c.prepare()
|
||||
c.logDebug("executing command")
|
||||
|
||||
|
|
@ -95,6 +125,10 @@ func (c *Cmd) Run() error {
|
|||
|
||||
// Output runs the command and returns its standard output.
|
||||
func (c *Cmd) Output() ([]byte, error) {
|
||||
if c.opts.Background {
|
||||
return nil, coreerr.E("Cmd.Output", "background execution is incompatible with Output", nil)
|
||||
}
|
||||
|
||||
c.prepare()
|
||||
c.logDebug("executing command")
|
||||
|
||||
|
|
@ -109,6 +143,10 @@ func (c *Cmd) Output() ([]byte, error) {
|
|||
|
||||
// CombinedOutput runs the command and returns its combined standard output and standard error.
|
||||
func (c *Cmd) CombinedOutput() ([]byte, error) {
|
||||
if c.opts.Background {
|
||||
return nil, coreerr.E("Cmd.CombinedOutput", "background execution is incompatible with CombinedOutput", nil)
|
||||
}
|
||||
|
||||
c.prepare()
|
||||
c.logDebug("executing command")
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,12 @@ package exec_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"dappco.re/go/core/process/exec"
|
||||
)
|
||||
|
|
@ -195,6 +199,49 @@ func TestCommand_WithStdinStdoutStderr(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCommand_Run_Background(t *testing.T) {
|
||||
logger := &mockLogger{}
|
||||
ctx := context.Background()
|
||||
dir := t.TempDir()
|
||||
marker := filepath.Join(dir, "marker.txt")
|
||||
|
||||
start := time.Now()
|
||||
err := exec.Command(ctx, "sh", "-c", fmt.Sprintf("sleep 0.2; printf done > %q", marker)).
|
||||
WithBackground(true).
|
||||
WithLogger(logger).
|
||||
Run()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if elapsed := time.Since(start); elapsed > 100*time.Millisecond {
|
||||
t.Fatalf("background run took too long: %s", elapsed)
|
||||
}
|
||||
|
||||
deadline := time.Now().Add(2 * time.Second)
|
||||
for {
|
||||
data, readErr := os.ReadFile(marker)
|
||||
if readErr == nil && strings.TrimSpace(string(data)) == "done" {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("background command did not create marker file")
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommand_Output_BackgroundRejected(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := exec.Command(ctx, "echo", "test").
|
||||
WithBackground(true).
|
||||
Output()
|
||||
if err == nil {
|
||||
t.Fatal("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunQuiet_Good(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
err := exec.RunQuiet(ctx, "echo", "quiet")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue