From 509ffe76c3faa3dfccd36fee7d19fcd02293f5dd Mon Sep 17 00:00:00 2001 From: Mark Feinstein Date: Tue, 30 Jan 2024 12:56:26 -0800 Subject: [PATCH 1/2] refactor: tmux command execution Restructuring the tmux package to use a central struct for command execution to help with testability. To limit the scope of the this PR 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. --- tmux/list.go | 10 ++-- tmux/list_test.go | 42 +++++++++++++++ tmux/testdata/session_list.txt | 3 ++ tmux/tmux.go | 93 ++++++++++++++++++++++++++-------- 4 files changed, 122 insertions(+), 26 deletions(-) create mode 100644 tmux/testdata/session_list.txt diff --git a/tmux/list.go b/tmux/list.go index 76c11b1..472ad60 100644 --- a/tmux/list.go +++ b/tmux/list.go @@ -156,12 +156,12 @@ func sortSessions(sessions []*TmuxSession) []*TmuxSession { func List(o Options) ([]*TmuxSession, error) { format := format() - output, err := tmuxCmd([]string{"list-sessions", "-F", format}) - cleanOutput := strings.TrimSpace(output) - if err != nil || strings.HasPrefix(cleanOutput, "no server running on") { - return nil, nil + output, err := command.Run([]string{"list-sessions", "-F", format}) + if err != nil { + return nil, err } - sessionList := strings.TrimSpace(string(output)) + + sessionList := output lines := strings.Split(sessionList, "\n") sessions := processSessions(o, lines) diff --git a/tmux/list_test.go b/tmux/list_test.go index f016459..48e3a16 100644 --- a/tmux/list_test.go +++ b/tmux/list_test.go @@ -1,6 +1,7 @@ package tmux import ( + _ "embed" "testing" "github.com/stretchr/testify/require" @@ -86,6 +87,47 @@ func TestProcessSessions(t *testing.T) { } } +//go:embed testdata/session_list.txt +var sessionList string + +func TestList(t *testing.T) { + testCase := map[string]struct { + MockResponse string + MockError error + Options Options + ExpectedLength int + Error error + }{ + "happy path": { + MockResponse: sessionList, + ExpectedLength: 3, + }, + "happy path show hidden": { + MockResponse: sessionList, + Options: Options{HideAttached: true}, + ExpectedLength: 2, + }, + } + + for name, tc := range testCase { + t.Run(name, func(t *testing.T) { + command = &Command{ + execFunc: func(string, []string) (string, error) { + return tc.MockResponse, tc.MockError + }, + } + res, err := List(tc.Options) + require.ErrorIs(t, tc.Error, err) + if err != nil { + return + } + + require.Len(t, res, tc.ExpectedLength) + t.Log(res) + }) + } +} + func BenchmarkProcessSessions(b *testing.B) { for n := 0; n < b.N; n++ { processSessions(Options{}, []string{ diff --git a/tmux/testdata/session_list.txt b/tmux/testdata/session_list.txt new file mode 100644 index 0000000..f2d5be0 --- /dev/null +++ b/tmux/testdata/session_list.txt @@ -0,0 +1,3 @@ +1706646951 1 /dev/ttys000 1706475706 1 0 $2 1706643877 0 0 sesh /Users/test_user/dev/sesh 2,1 2 +1706632190 0 1706485534 1 0 $8 1706632189 0 0 dotfiles /Users/test_user/dotfiles 1 1 +1706485830 0 1706485825 1 0 $10 1706485825 0 0 window-name /Users/test_user/test_user/tmux-nerd-font-window-name 1 1 diff --git a/tmux/tmux.go b/tmux/tmux.go index 9b4043c..ba21f4b 100644 --- a/tmux/tmux.go +++ b/tmux/tmux.go @@ -7,11 +7,82 @@ import ( "os" "os/exec" "strings" + "sync" "github.com/joshmedeski/sesh/config" "github.com/joshmedeski/sesh/dir" ) +var ( + command *Command + once sync.Once +) + +func init() { + once.Do(func() { + var err error + command, err = NewCommand() + if err != nil { + log.Fatal(err) + } + }) +} + +type Error struct{ msg string } + +func (e Error) Error() string { return e.msg } + +var ErrNotRunning = Error{"no server running"} + +func executeCommand(command string, args []string) (string, error) { + var stdout, stderr bytes.Buffer + cmd := exec.Command(command, args...) + cmd.Stdin = os.Stdin + cmd.Stdout = &stdout + cmd.Stderr = &stderr + + if err := cmd.Start(); err != nil { + return "", err + } + + if err := cmd.Wait(); err != nil { + if strings.Contains(stderr.String(), "no server running on") { + return "", ErrNotRunning + } + + return "", err + } + + out := strings.TrimSpace(stdout.String()) + if strings.Contains(out, "no server running on") { + return "", ErrNotRunning + } + + return out, nil +} + +type Command struct { + cliPath string + execFunc func(string, []string) (string, error) +} + +func NewCommand() (c *Command, err error) { + c = new(Command) + + c.cliPath, err = exec.LookPath("tmux") + if err != nil { + return nil, err + } + + c.execFunc = executeCommand + + return c, nil +} + +func (c *Command) Run(args []string) (string, error) { + return c.execFunc(c.cliPath, args) +} + func GetSession(s string) (TmuxSession, error) { sessionList, err := List(Options{}) if err != nil { @@ -41,27 +112,7 @@ func GetSession(s string) (TmuxSession, error) { } func tmuxCmd(args []string) (string, error) { - tmux, err := exec.LookPath("tmux") - if err != nil { - return "", err - } - var stdout, stderr bytes.Buffer - cmd := exec.Command(tmux, args...) - cmd.Stdin = os.Stdin - cmd.Stdout = &stdout - cmd.Stderr = os.Stderr - cmd.Stderr = &stderr - if err := cmd.Start(); err != nil { - return "", err - } - if err := cmd.Wait(); err != nil { - errString := strings.TrimSpace(stderr.String()) - if strings.HasPrefix(errString, "no server running on") { - return "", nil - } - return "", err - } - return stdout.String(), nil + return command.Run(args) } func isAttached() bool { From e8eb0d1de5c3645988c7d99f56cf2f269964f5ff Mon Sep 17 00:00:00 2001 From: Mark Feinstein Date: Tue, 30 Jan 2024 13:29:39 -0800 Subject: [PATCH 2/2] ci: install tmux on macos --- .github/workflows/ci-cd.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 021e955..ac586ea 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -25,6 +25,10 @@ jobs: go-version: "1.21" - name: Install deps run: go install github.com/jstemmer/go-junit-report/v2@latest + - if: startsWith(matrix.os, 'macOS') + run: | + brew update + brew install tmux - name: Run tests run: go test -cover -bench=. -benchmem -race -v 2>&1 ./... | go-junit-report -set-exit-code > report.xml - name: Test Summary