Skip to content

Commit 960bd75

Browse files
refactor: improve tmux command (#59)
Restructuring the tmux package to use a central struct for command execution to help with testability. It includes the use of a package level instance of the new tmux.Command struct and an init function to initialize it. Once all of the functions using the tmux cli are updated to use the Command struct directly that code can be removed in favor of a single tmux.Command instance configured in the cli.
1 parent cc46b8d commit 960bd75

File tree

5 files changed

+126
-26
lines changed

5 files changed

+126
-26
lines changed

.github/workflows/ci-cd.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ jobs:
2525
go-version: "1.21"
2626
- name: Install deps
2727
run: go install github.com/jstemmer/go-junit-report/v2@latest
28+
- if: startsWith(matrix.os, 'macOS')
29+
run: |
30+
brew update
31+
brew install tmux
2832
- name: Run tests
2933
run: go test -cover -bench=. -benchmem -race -v 2>&1 ./... | go-junit-report -set-exit-code > report.xml
3034
- name: Test Summary

tmux/list.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,12 @@ func sortSessions(sessions []*TmuxSession) []*TmuxSession {
156156

157157
func List(o Options) ([]*TmuxSession, error) {
158158
format := format()
159-
output, err := tmuxCmd([]string{"list-sessions", "-F", format})
160-
cleanOutput := strings.TrimSpace(output)
161-
if err != nil || strings.HasPrefix(cleanOutput, "no server running on") {
162-
return nil, nil
159+
output, err := command.Run([]string{"list-sessions", "-F", format})
160+
if err != nil {
161+
return nil, err
163162
}
164-
sessionList := strings.TrimSpace(string(output))
163+
164+
sessionList := output
165165
lines := strings.Split(sessionList, "\n")
166166
sessions := processSessions(o, lines)
167167

tmux/list_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tmux
22

33
import (
4+
_ "embed"
45
"testing"
56

67
"github.com/stretchr/testify/require"
@@ -86,6 +87,47 @@ func TestProcessSessions(t *testing.T) {
8687
}
8788
}
8889

90+
//go:embed testdata/session_list.txt
91+
var sessionList string
92+
93+
func TestList(t *testing.T) {
94+
testCase := map[string]struct {
95+
MockResponse string
96+
MockError error
97+
Options Options
98+
ExpectedLength int
99+
Error error
100+
}{
101+
"happy path": {
102+
MockResponse: sessionList,
103+
ExpectedLength: 3,
104+
},
105+
"happy path show hidden": {
106+
MockResponse: sessionList,
107+
Options: Options{HideAttached: true},
108+
ExpectedLength: 2,
109+
},
110+
}
111+
112+
for name, tc := range testCase {
113+
t.Run(name, func(t *testing.T) {
114+
command = &Command{
115+
execFunc: func(string, []string) (string, error) {
116+
return tc.MockResponse, tc.MockError
117+
},
118+
}
119+
res, err := List(tc.Options)
120+
require.ErrorIs(t, tc.Error, err)
121+
if err != nil {
122+
return
123+
}
124+
125+
require.Len(t, res, tc.ExpectedLength)
126+
t.Log(res)
127+
})
128+
}
129+
}
130+
89131
func BenchmarkProcessSessions(b *testing.B) {
90132
for n := 0; n < b.N; n++ {
91133
processSessions(Options{}, []string{

tmux/testdata/session_list.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
1706646951 1 /dev/ttys000 1706475706 1 0 $2 1706643877 0 0 sesh /Users/test_user/dev/sesh 2,1 2
2+
1706632190 0 1706485534 1 0 $8 1706632189 0 0 dotfiles /Users/test_user/dotfiles 1 1
3+
1706485830 0 1706485825 1 0 $10 1706485825 0 0 window-name /Users/test_user/test_user/tmux-nerd-font-window-name 1 1

tmux/tmux.go

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,82 @@ import (
77
"os"
88
"os/exec"
99
"strings"
10+
"sync"
1011

1112
"github.com/joshmedeski/sesh/config"
1213
"github.com/joshmedeski/sesh/dir"
1314
)
1415

16+
var (
17+
command *Command
18+
once sync.Once
19+
)
20+
21+
func init() {
22+
once.Do(func() {
23+
var err error
24+
command, err = NewCommand()
25+
if err != nil {
26+
log.Fatal(err)
27+
}
28+
})
29+
}
30+
31+
type Error struct{ msg string }
32+
33+
func (e Error) Error() string { return e.msg }
34+
35+
var ErrNotRunning = Error{"no server running"}
36+
37+
func executeCommand(command string, args []string) (string, error) {
38+
var stdout, stderr bytes.Buffer
39+
cmd := exec.Command(command, args...)
40+
cmd.Stdin = os.Stdin
41+
cmd.Stdout = &stdout
42+
cmd.Stderr = &stderr
43+
44+
if err := cmd.Start(); err != nil {
45+
return "", err
46+
}
47+
48+
if err := cmd.Wait(); err != nil {
49+
if strings.Contains(stderr.String(), "no server running on") {
50+
return "", ErrNotRunning
51+
}
52+
53+
return "", err
54+
}
55+
56+
out := strings.TrimSpace(stdout.String())
57+
if strings.Contains(out, "no server running on") {
58+
return "", ErrNotRunning
59+
}
60+
61+
return out, nil
62+
}
63+
64+
type Command struct {
65+
cliPath string
66+
execFunc func(string, []string) (string, error)
67+
}
68+
69+
func NewCommand() (c *Command, err error) {
70+
c = new(Command)
71+
72+
c.cliPath, err = exec.LookPath("tmux")
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
c.execFunc = executeCommand
78+
79+
return c, nil
80+
}
81+
82+
func (c *Command) Run(args []string) (string, error) {
83+
return c.execFunc(c.cliPath, args)
84+
}
85+
1586
func GetSession(s string) (TmuxSession, error) {
1687
sessionList, err := List(Options{})
1788
if err != nil {
@@ -41,27 +112,7 @@ func GetSession(s string) (TmuxSession, error) {
41112
}
42113

43114
func tmuxCmd(args []string) (string, error) {
44-
tmux, err := exec.LookPath("tmux")
45-
if err != nil {
46-
return "", err
47-
}
48-
var stdout, stderr bytes.Buffer
49-
cmd := exec.Command(tmux, args...)
50-
cmd.Stdin = os.Stdin
51-
cmd.Stdout = &stdout
52-
cmd.Stderr = os.Stderr
53-
cmd.Stderr = &stderr
54-
if err := cmd.Start(); err != nil {
55-
return "", err
56-
}
57-
if err := cmd.Wait(); err != nil {
58-
errString := strings.TrimSpace(stderr.String())
59-
if strings.HasPrefix(errString, "no server running on") {
60-
return "", nil
61-
}
62-
return "", err
63-
}
64-
return stdout.String(), nil
115+
return command.Run(args)
65116
}
66117

67118
func isAttached() bool {

0 commit comments

Comments
 (0)