Skip to content

Commit

Permalink
refactor: improve tmux command (#59)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
markfeinstein authored Feb 4, 2024
1 parent cc46b8d commit 960bd75
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 26 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions tmux/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
42 changes: 42 additions & 0 deletions tmux/list_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package tmux

import (
_ "embed"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -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{
Expand Down
3 changes: 3 additions & 0 deletions tmux/testdata/session_list.txt
Original file line number Diff line number Diff line change
@@ -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
93 changes: 72 additions & 21 deletions tmux/tmux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 960bd75

Please sign in to comment.