all repos — erl @ c8ab7c849959313a035bf9e448d110e794190a4f

Execute Reload Loop

initial commit

Alan Pearce
commit

c8ab7c849959313a035bf9e448d110e794190a4f

1 file changed, 431 insertions(+), 0 deletions(-)

changed files
A command/command_test.go
@@ -0,0 +1,431 @@
+package command + +import ( + "bytes" + "testing" + "time" +) + +func TestNew(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + cmdName string + args []string + expected string + }{ + { + name: "simple command", + cmdName: "echo", + args: []string{"hello"}, + expected: "echo", + }, + { + name: "command with multiple args", + cmdName: "ls", + args: []string{"-l", "-a"}, + expected: "ls", + }, + { + name: "command with no args", + cmdName: "pwd", + args: []string{}, + expected: "pwd", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var stdout, stderr bytes.Buffer + cmd := New(tt.cmdName, tt.args, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + if cmd == nil { + t.Fatal("New() returned nil") + } + + if cmd.name != tt.expected { + t.Errorf("expected name %s, got %s", tt.expected, cmd.name) + } + + if len(cmd.args) != len(tt.args) { + t.Errorf("expected %d args, got %d", len(tt.args), len(cmd.args)) + } + + for i, arg := range tt.args { + if i < len(cmd.args) && cmd.args[i] != arg { + t.Errorf("expected arg[%d] %s, got %s", i, arg, cmd.args[i]) + } + } + }) + } +} + +func TestStart(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + cmdName string + args []string + shouldError bool + }{ + { + name: "valid command", + cmdName: "echo", + args: []string{"test"}, + shouldError: false, + }, + { + name: "invalid command", + cmdName: "nonexistentcommand12345", + args: []string{}, + shouldError: true, + }, + { + name: "valid command with args", + cmdName: "sleep", + args: []string{"0.1"}, + shouldError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + var stdout, stderr bytes.Buffer + cmd := New(tt.cmdName, tt.args, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + err := cmd.Start() + + if tt.shouldError { + if err == nil { + t.Error("expected error but got none") + } + } else { + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if cmd.Cmd == nil { + t.Error("exec.Cmd should be initialized after Start()") + } + + if cmd.Process == nil { + t.Error("Process should be set after successful Start()") + } + } + }) + } +} + +func TestWait(t *testing.T) { + t.Parallel() + + t.Run("wait for successful command", func(t *testing.T) { + t.Parallel() + + var stdout, stderr bytes.Buffer + cmd := New("echo", []string{"test"}, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + err := cmd.Start() + if err != nil { + t.Fatalf("failed to start command: %v", err) + } + + err = cmd.Wait() + if err != nil { + t.Errorf("unexpected error waiting for command: %v", err) + } + + if !cmd.Exited() { + t.Error("command should be marked as exited after Wait()") + } + }) + + t.Run("wait for failing command", func(t *testing.T) { + t.Parallel() + + // Use a command that will fail + var stdout, stderr bytes.Buffer + cmd := New("ls", []string{"/nonexistent/directory/12345"}, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + err := cmd.Start() + if err != nil { + t.Fatalf("failed to start command: %v", err) + } + + err = cmd.Wait() + if err != nil { + t.Errorf("unexpected error from failing command: %v", err) + } + + if !cmd.Exited() { + t.Error("command should be marked as exited after Wait() even on failure") + } + }) +} + +func TestStop(t *testing.T) { + t.Parallel() + + t.Run("stop running command", func(t *testing.T) { + t.Parallel() + + var stdout, stderr bytes.Buffer + cmd := New("sleep", []string{"10"}, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + err := cmd.Start() + if err != nil { + t.Fatalf("failed to start command: %v", err) + } + + // Give the command a moment to actually start + time.Sleep(100 * time.Millisecond) + + err = cmd.Stop() + if err != nil { + t.Errorf("unexpected error stopping command: %v", err) + } + + // Wait a bit and check if process is actually killed + time.Sleep(100 * time.Millisecond) + + // Try to wait for the process - it should return quickly since it was killed + waitDone := make(chan error, 1) + go func() { + waitDone <- cmd.Wait() + }() + + select { + case <-waitDone: + // Good, Wait() returned quickly + case <-time.After(1 * time.Second): + t.Error("process was not killed properly - Wait() is still blocking") + } + }) + + t.Run("stop non-running command", func(t *testing.T) { + t.Parallel() + + var stdout, stderr bytes.Buffer + cmd := New("echo", []string{"test"}, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + // Try to stop before starting + err := cmd.Stop() + if err != nil { + t.Errorf("stopping non-running command should not error: %v", err) + } + }) + + t.Run("stop already finished command", func(t *testing.T) { + t.Parallel() + + var stdout, stderr bytes.Buffer + cmd := New("echo", []string{"test"}, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + err := cmd.Start() + if err != nil { + t.Fatalf("failed to start command: %v", err) + } + + err = cmd.Wait() + if err != nil { + t.Fatalf("failed to wait for command: %v", err) + } + + err = cmd.Stop() + if err != nil { + t.Errorf("stopping finished command should not error: %v", err) + } + }) +} + +func TestExited(t *testing.T) { + t.Parallel() + + t.Run("not started command", func(t *testing.T) { + t.Parallel() + + var stdout, stderr bytes.Buffer + cmd := New("echo", []string{"test"}, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + if cmd.Exited() { + t.Error("command that hasn't started should not be marked as exited") + } + }) + + t.Run("started but not finished command", func(t *testing.T) { + t.Parallel() + + var stdout, stderr bytes.Buffer + cmd := New("sleep", []string{"1"}, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + err := cmd.Start() + if err != nil { + t.Fatalf("failed to start command: %v", err) + } + + if cmd.Exited() { + t.Error("running command should not be marked as exited") + } + + // Clean up + cmd.Stop() + }) + + t.Run("finished command", func(t *testing.T) { + t.Parallel() + + var stdout, stderr bytes.Buffer + cmd := New("echo", []string{"test"}, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + err := cmd.Start() + if err != nil { + t.Fatalf("failed to start command: %v", err) + } + + err = cmd.Wait() + if err != nil { + t.Fatalf("failed to wait for command: %v", err) + } + + if !cmd.Exited() { + t.Error("finished command should be marked as exited") + } + }) +} + +func TestMultipleStarts(t *testing.T) { + t.Parallel() + + t.Run("multiple start calls", func(t *testing.T) { + t.Parallel() + + var stdout, stderr bytes.Buffer + cmd := New("echo", []string{"test"}, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + // First start + err := cmd.Start() + if err != nil { + t.Fatalf("first start failed: %v", err) + } + + err = cmd.Wait() + if err != nil { + t.Fatalf("wait failed: %v", err) + } + + // Second start should work (creates new exec.Cmd) + err = cmd.Start() + if err != nil { + t.Errorf("second start failed: %v", err) + } + + err = cmd.Wait() + if err != nil { + t.Errorf("second wait failed: %v", err) + } + }) +} + +func TestMakeCommand(t *testing.T) { + t.Parallel() + + t.Run("makeCommand sets up exec.Cmd correctly", func(t *testing.T) { + t.Parallel() + + var stdout, stderr bytes.Buffer + cmd := New("echo", []string{"hello", "world"}, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + // makeCommand is called internally by Start, so we test it through Start + err := cmd.Start() + if err != nil { + t.Fatalf("failed to start command: %v", err) + } + + if cmd.Cmd == nil { + t.Error("exec.Cmd should be initialized") + } + + if cmd.Stdout != &stdout { + t.Error("Stdout should be set to the provided buffer") + } + + if cmd.Stderr != &stderr { + t.Error("Stderr should be set to the provided buffer") + } + + cmd.Wait() + }) +} + +func TestCommandInterface(t *testing.T) { + t.Parallel() + + t.Run("Cmd implements Command interface", func(t *testing.T) { + t.Parallel() + + var _ Command = &Cmd{} + + var stdout, stderr bytes.Buffer + cmd := New("echo", []string{"test"}, Options{ + Stdout: &stdout, + Stderr: &stderr, + }) + + // Test that all Command interface methods work + err := cmd.Start() + if err != nil { + t.Fatalf("Start() failed: %v", err) + } + + err = cmd.Wait() + if err != nil { + t.Errorf("Wait() failed: %v", err) + } + + err = cmd.Stop() + if err != nil { + t.Errorf("Stop() failed: %v", err) + } + }) +}