diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-18 07:45:37 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-18 07:45:37 +0300 |
| commit | 4ffb22e7f69f1c9c79b095d4e60bad3d97aac55b (patch) | |
| tree | 2dc708cbb95975d34084eb5afd376871caa13e27 /internal/tmux | |
| parent | ece7dcfd232b780f5650326c8ac2379ca70387d4 (diff) | |
ik0 replace test seams with dependency injection
Diffstat (limited to 'internal/tmux')
| -rw-r--r-- | internal/tmux/status.go | 9 | ||||
| -rw-r--r-- | internal/tmux/status_coverage_test.go | 8 | ||||
| -rw-r--r-- | internal/tmux/tmux.go | 42 | ||||
| -rw-r--r-- | internal/tmux/tmux_test.go | 28 |
4 files changed, 54 insertions, 33 deletions
diff --git a/internal/tmux/status.go b/internal/tmux/status.go index 2f8cff6..2657495 100644 --- a/internal/tmux/status.go +++ b/internal/tmux/status.go @@ -3,7 +3,6 @@ package tmux import ( "fmt" "os" - "os/exec" "strconv" "strings" "time" @@ -33,7 +32,11 @@ func Enabled() bool { // SetUserOption sets a global tmux user option like @hexai_status to value. func SetUserOption(key, value string) error { - if !Enabled() || !HasBinary() || !InSession() { + return Runner{}.SetUserOption(key, value) +} + +func (r Runner) SetUserOption(key, value string) error { + if !Enabled() || !r.HasBinary() || !InSession() { return nil } k := strings.TrimPrefix(strings.TrimSpace(key), "@") @@ -41,7 +44,7 @@ func SetUserOption(key, value string) error { return nil } // Use set-option -g so it appears for all windows - return exec.Command("tmux", "set-option", "-g", "@"+k, value).Run() + return r.cmd("tmux", "set-option", "-g", "@"+k, value).Run() } // SetStatus is a convenience for setting @hexai_status. diff --git a/internal/tmux/status_coverage_test.go b/internal/tmux/status_coverage_test.go index 0c76ee6..732a89e 100644 --- a/internal/tmux/status_coverage_test.go +++ b/internal/tmux/status_coverage_test.go @@ -50,14 +50,12 @@ func TestSetUserOption_DisabledByEnv(t *testing.T) { func TestSetUserOption_EmptyKey(t *testing.T) { t.Setenv("HEXAI_TMUX_STATUS", "1") t.Setenv("TMUX", "/tmp/tmux-1,1,1") - old := lookPath - t.Cleanup(func() { lookPath = old }) - lookPath = func(string) (string, error) { return "/bin/tmux", nil } + r := Runner{lookPath: func(string) (string, error) { return "/bin/tmux", nil }} // Empty key after trimming should return nil - if err := SetUserOption(" @ ", "test"); err != nil { + if err := r.SetUserOption(" @ ", "test"); err != nil { t.Fatalf("expected nil for empty key, got %v", err) } - if err := SetUserOption(" ", "test"); err != nil { + if err := r.SetUserOption(" ", "test"); err != nil { t.Fatalf("expected nil for blank key, got %v", err) } } diff --git a/internal/tmux/tmux.go b/internal/tmux/tmux.go index 9a4f5ee..7e5c7c7 100644 --- a/internal/tmux/tmux.go +++ b/internal/tmux/tmux.go @@ -8,15 +8,33 @@ import ( ) // Available reports whether tmux is available and we appear to be in a tmux session. -func Available() bool { return HasBinary() && InSession() } +func Available() bool { return Runner{}.Available() } + +type Runner struct { + lookPath func(string) (string, error) + command func(string, ...string) *exec.Cmd +} + +func (r Runner) find(name string) (string, error) { + if r.lookPath != nil { + return r.lookPath(name) + } + return exec.LookPath(name) +} + +func (r Runner) cmd(name string, args ...string) *exec.Cmd { + if r.command != nil { + return r.command(name, args...) + } + return exec.Command(name, args...) +} // HasBinary reports whether the tmux binary is on PATH. -var ( - lookPath = exec.LookPath - command = exec.Command -) +func HasBinary() bool { return Runner{}.HasBinary() } -func HasBinary() bool { _, err := lookPath("tmux"); return err == nil } +func (r Runner) HasBinary() bool { _, err := r.find("tmux"); return err == nil } + +func (r Runner) Available() bool { return r.HasBinary() && InSession() } // InSession reports whether we seem to be running inside a tmux session. func InSession() bool { return strings.TrimSpace(os.Getenv("TMUX")) != "" } @@ -31,6 +49,10 @@ type SplitOpts struct { // SplitRun splits the current tmux window and runs argv in the new pane. // It returns once tmux has launched the child process. func SplitRun(opts SplitOpts, argv []string) error { + return Runner{}.SplitRun(opts, argv) +} + +func (r Runner) SplitRun(opts SplitOpts, argv []string) error { if len(argv) == 0 { return nil } @@ -49,7 +71,7 @@ func SplitRun(opts SplitOpts, argv []string) error { // tmux takes a single command string. Use a conservative shell join. cmdStr := shellJoin(argv) args = append(args, cmdStr) - c := command("tmux", args...) + c := r.cmd("tmux", args...) return c.Run() } @@ -64,6 +86,10 @@ type PopupOpts struct { // The -E flag makes the popup close automatically when the command exits. // It returns once the popup has closed (blocking call). func PopupRun(opts PopupOpts, argv []string) error { + return Runner{}.PopupRun(opts, argv) +} + +func (r Runner) PopupRun(opts PopupOpts, argv []string) error { if len(argv) == 0 { return nil } @@ -82,7 +108,7 @@ func PopupRun(opts PopupOpts, argv []string) error { } cmdStr := shellJoin(argv) args = append(args, cmdStr) - return command("tmux", args...).Run() + return r.cmd("tmux", args...).Run() } // shellJoin quotes argv elements for safe use in a single shell command string. diff --git a/internal/tmux/tmux_test.go b/internal/tmux/tmux_test.go index a8d1574..0946b01 100644 --- a/internal/tmux/tmux_test.go +++ b/internal/tmux/tmux_test.go @@ -19,14 +19,12 @@ func TestInSession(t *testing.T) { } func TestHasBinary_UsesLookPath(t *testing.T) { - old := lookPath - t.Cleanup(func() { lookPath = old }) - lookPath = func(file string) (string, error) { return "/bin/tmux", nil } - if !HasBinary() { + r := Runner{lookPath: func(file string) (string, error) { return "/bin/tmux", nil }} + if !r.HasBinary() { t.Fatal("expected HasBinary true when lookPath succeeds") } - lookPath = func(file string) (string, error) { return "", errors.New("nope") } - if HasBinary() { + r.lookPath = func(file string) (string, error) { return "", errors.New("nope") } + if r.HasBinary() { t.Fatal("expected HasBinary false when lookPath fails") } } @@ -237,17 +235,15 @@ func TestSplitRun_AssemblesArgs(t *testing.T) { name string args []string }{} - oldCmd := command - t.Cleanup(func() { command = oldCmd }) - command = func(name string, args ...string) *exec.Cmd { + r := Runner{command: func(name string, args ...string) *exec.Cmd { captured.name = name captured.args = append([]string(nil), args...) // Use a benign command that exits 0 return exec.Command("true") - } + }} opts := SplitOpts{Target: ":.", Vertical: true, Percent: 40} argv := []string{"/path/to/bin", "-flag", "value with spaces", "and'quote"} - if err := SplitRun(opts, argv); err != nil { + if err := r.SplitRun(opts, argv); err != nil { t.Fatalf("SplitRun error: %v", err) } if captured.name != "tmux" { @@ -270,17 +266,15 @@ func TestSplitRun_AssemblesArgs(t *testing.T) { } func TestAvailable(t *testing.T) { - oldLook := lookPath - t.Cleanup(func() { lookPath = oldLook }) // Present binary + TMUX set -> available - lookPath = func(file string) (string, error) { return "/bin/tmux", nil } + r := Runner{lookPath: func(file string) (string, error) { return "/bin/tmux", nil }} t.Setenv("TMUX", "/tmp/tmux-1,1,1") - if !Available() { + if !r.Available() { t.Fatal("expected Available true with TMUX + binary") } // No binary -> not available - lookPath = func(file string) (string, error) { return "", errors.New("nope") } - if Available() { + r.lookPath = func(file string) (string, error) { return "", errors.New("nope") } + if r.Available() { t.Fatal("expected Available false without binary") } } |
