diff options
Diffstat (limited to 'internal/exec')
| -rw-r--r-- | internal/exec/exec.go | 38 | ||||
| -rw-r--r-- | internal/exec/exec_test.go | 69 |
2 files changed, 107 insertions, 0 deletions
diff --git a/internal/exec/exec.go b/internal/exec/exec.go new file mode 100644 index 0000000..87e861b --- /dev/null +++ b/internal/exec/exec.go @@ -0,0 +1,38 @@ +package exec + +import ( + "bytes" + "os/exec" +) + +// Run executes a shell command with the given arguments and returns stdout, stderr, exit code, and any error encountered. +func Run(name string, args ...string) (stdout, stderr string, exitCode int, err error) { + cmd := exec.Command(name, args...) + + var stdoutBuf, stderrBuf bytes.Buffer + cmd.Stdout = &stdoutBuf + cmd.Stderr = &stderrBuf + + err = cmd.Run() + + stdout = stdoutBuf.String() + stderr = stderrBuf.String() + + if err != nil { + if exitError, ok := err.(*exec.ExitError); ok { + exitCode = exitError.ExitCode() + // In this case, the error is just the non-zero exit code, + // which we've already captured. We return nil for err to indicate + // that the command actually ran and exited (even if non-zero). + err = nil + } else { + // This is a "real" error, e.g., binary not found + exitCode = -1 + return stdout, stderr, exitCode, err + } + } else { + exitCode = 0 + } + + return stdout, stderr, exitCode, nil +} diff --git a/internal/exec/exec_test.go b/internal/exec/exec_test.go new file mode 100644 index 0000000..4dc6505 --- /dev/null +++ b/internal/exec/exec_test.go @@ -0,0 +1,69 @@ +package exec + +import ( + "testing" +) + +func TestRun(t *testing.T) { + tests := []struct { + name string + cmd string + args []string + wantStdout string + wantStderr string + wantExitCode int + wantErr bool + }{ + { + name: "success", + cmd: "echo", + args: []string{"hello world"}, + wantStdout: "hello world\n", + wantStderr: "", + wantExitCode: 0, + wantErr: false, + }, + { + name: "fail-exit-code", + cmd: "ls", + args: []string{"/non-existent-directory-12345"}, + wantStdout: "", + wantStderr: "", // ls stderr varies by OS, but should not be empty usually. + wantExitCode: 2, // Typical for ls non-existent + wantErr: false, + }, + { + name: "fail-binary-not-found", + cmd: "non-existent-command-12345", + args: []string{}, + wantStdout: "", + wantStderr: "", + wantExitCode: -1, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stdout, stderr, exitCode, err := Run(tt.cmd, tt.args...) + + if (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if exitCode != tt.wantExitCode { + t.Errorf("Run() exitCode = %v, want %v", exitCode, tt.wantExitCode) + } + + if tt.name == "success" && stdout != tt.wantStdout { + t.Errorf("Run() stdout = %q, want %q", stdout, tt.wantStdout) + } + + // For ls error, we just check that stderr is not empty since exact text varies + if tt.name == "fail-exit-code" && stderr == "" { + t.Errorf("Run() stderr = %q, want non-empty", stderr) + } + }) + } +} |
