diff --git a/.gitignore b/.gitignore index 849ddff..9fd6756 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +coverage.out dist/ diff --git a/Makefile b/Makefile index 488be91..db3b171 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,9 @@ build: go build -o smug *.go + +test: + go test . + +coverage: + go test -coverprofile=coverage.out + go tool cover -html=coverage.out diff --git a/commander_test.go b/commander_test.go new file mode 100644 index 0000000..ac20f68 --- /dev/null +++ b/commander_test.go @@ -0,0 +1,88 @@ +package main + +import ( + "bytes" + "fmt" + "log" + "os" + "os/exec" + "strings" + "testing" +) + +func TestMain(m *testing.M) { + switch os.Getenv("TEST_MAIN") { + case "": + os.Exit(m.Run()) + case "echo": + fmt.Println(strings.Join(os.Args[1:], " ")) + case "exit": + os.Exit(42) + } +} + +func TestExec(t *testing.T) { + logger := log.New(bytes.NewBuffer([]byte{}), "", 0) + commander := DefaultCommander{logger} + + cmd := exec.Command(os.Args[0], "42") + cmd.Env = append(os.Environ(), "TEST_MAIN=echo") + + output, err := commander.Exec(cmd) + if err != nil { + t.Fatalf("unexpected error %v", err) + } + + if output != "42" { + t.Errorf("expected 42, got %q", output) + } +} + +func TestExecError(t *testing.T) { + logger := log.New(bytes.NewBuffer([]byte{}), "", 0) + commander := DefaultCommander{logger} + + cmd := exec.Command(os.Args[0], "42") + cmd.Env = append(os.Environ(), "TEST_MAIN=exit") + + _, err := commander.Exec(cmd) + if err == nil { + t.Errorf("expected error") + } + + got := cmd.ProcessState.ExitCode() + if got != 42 { + t.Errorf("expected %d, got %d", 42, got) + } +} + +func TestExecSilently(t *testing.T) { + logger := log.New(bytes.NewBuffer([]byte{}), "", 0) + commander := DefaultCommander{logger} + + cmd := exec.Command(os.Args[0], "42") + cmd.Env = append(os.Environ(), "TEST_MAIN=echo") + + err := commander.ExecSilently(cmd) + if err != nil { + t.Fatalf("unexpected error %v", err) + } +} + +func TestExecSilentlyError(t *testing.T) { + logger := log.New(bytes.NewBuffer([]byte{}), "", 0) + commander := DefaultCommander{logger} + + cmd := exec.Command(os.Args[0], "42") + cmd.Env = append(os.Environ(), "TEST_MAIN=exit") + + err := commander.ExecSilently(cmd) + if err == nil { + t.Errorf("expected error") + } + + got := cmd.ProcessState.ExitCode() + if got != 42 { + t.Errorf("expected %d, got %d", 42, got) + } +} diff --git a/context.go b/context.go new file mode 100644 index 0000000..93fe176 --- /dev/null +++ b/context.go @@ -0,0 +1,12 @@ +package main + +import "os" + +type Context struct { + InsideTmuxSession bool +} + +func CreateContext() *Context { + insideTmuxSession := os.Getenv("TERM") == "screen" + return &Context{insideTmuxSession} +} diff --git a/main.go b/main.go index 77813e1..1ca520b 100644 --- a/main.go +++ b/main.go @@ -48,6 +48,8 @@ func main() { tmux := Tmux{commander} smug := Smug{tmux, commander} + context := CreateContext() + switch options.Command { case "start": if len(options.Windows) == 0 { @@ -55,10 +57,10 @@ func main() { } else { fmt.Println("Starting new windows...") } - err = smug.Start(*config, options.Windows, options.Attach) + err = smug.Start(*config, options, *context) if err != nil { fmt.Println("Oops, an error occurred! Rolling back...") - smug.Stop(*config, options.Windows) + smug.Stop(*config, options, *context) } case "stop": if len(options.Windows) == 0 { @@ -66,7 +68,7 @@ func main() { } else { fmt.Println("Killing windows...") } - err = smug.Stop(*config, options.Windows) + err = smug.Stop(*config, options, *context) default: err = fmt.Errorf("Unknown command %q", options.Command) } diff --git a/smug.go b/smug.go index 4eebb6d..2bcb988 100644 --- a/smug.go +++ b/smug.go @@ -37,7 +37,6 @@ type Smug struct { func (smug Smug) execShellCommands(commands []string, path string) error { for _, c := range commands { - cmd := exec.Command("/bin/sh", "-c", c) cmd.Dir = path @@ -49,19 +48,18 @@ func (smug Smug) execShellCommands(commands []string, path string) error { return nil } -func (smug Smug) switchOrAttach(ses string, windows []string, attach bool) error { - insideTmuxSession := os.Getenv("TERM") == "screen" +func (smug Smug) switchOrAttach(sessionName string, attach bool, insideTmuxSession bool) error { if insideTmuxSession && attach { - return smug.tmux.SwitchClient(ses) + return smug.tmux.SwitchClient(sessionName) } else if !insideTmuxSession { - return smug.tmux.Attach(ses, os.Stdin, os.Stdout, os.Stderr) + return smug.tmux.Attach(sessionName, os.Stdin, os.Stdout, os.Stderr) } return nil } -func (smug Smug) Stop(config Config, windows []string) error { +func (smug Smug) Stop(config Config, options Options, context Context) error { + windows := options.Windows if len(windows) == 0 { - sessionRoot := ExpandPath(config.Root) err := smug.execShellCommands(config.Stop, sessionRoot) @@ -82,15 +80,16 @@ func (smug Smug) Stop(config Config, windows []string) error { return nil } -func (smug Smug) Start(config Config, windows []string, attach bool) error { - var ses string - var err error - +func (smug Smug) Start(config Config, options Options, context Context) error { + sessionName := config.Session + ":" + sessionExists := smug.tmux.SessionExists(sessionName) sessionRoot := ExpandPath(config.Root) - sessionExists := smug.tmux.SessionExists(config.Session) + windows := options.Windows + attach := options.Attach + if !sessionExists { - err = smug.execShellCommands(config.BeforeStart, sessionRoot) + err := smug.execShellCommands(config.BeforeStart, sessionRoot) if err != nil { return err } @@ -102,16 +101,12 @@ func (smug Smug) Start(config Config, windows []string, attach bool) error { defaultWindowName = config.Windows[0].Name } - ses, err = smug.tmux.NewSession(config.Session, sessionRoot, defaultWindowName) + _, err = smug.tmux.NewSession(strings.Replace(sessionName, ":", "", 1), sessionRoot, defaultWindowName) if err != nil { return err } - } else { - ses = config.Session + ":" - if len(windows) == 0 { - smug.switchOrAttach(ses, windows, attach) - return nil - } + } else if len(windows) == 0 { + return smug.switchOrAttach(sessionName, attach, context.InsideTmuxSession) } for wIndex, w := range config.Windows { @@ -124,16 +119,16 @@ func (smug Smug) Start(config Config, windows []string, attach bool) error { windowRoot = filepath.Join(sessionRoot, w.Root) } - window := ses + w.Name + window := sessionName + w.Name if (!sessionExists && wIndex > 0 && len(windows) == 0) || (sessionExists && len(windows) > 0) { - _, err = smug.tmux.NewWindow(ses, w.Name, windowRoot) + _, err := smug.tmux.NewWindow(sessionName, w.Name, windowRoot) if err != nil { return err } } for _, c := range w.Commands { - err = smug.tmux.SendKeys(window, c) + err := smug.tmux.SendKeys(window, c) if err != nil { return err } @@ -145,7 +140,7 @@ func (smug Smug) Start(config Config, windows []string, attach bool) error { paneRoot = filepath.Join(windowRoot, p.Root) } - _, err = smug.tmux.SplitWindow(window, p.Type, paneRoot, p.Commands) + _, err := smug.tmux.SplitWindow(window, p.Type, paneRoot, p.Commands) if err != nil { return err } @@ -156,14 +151,14 @@ func (smug Smug) Start(config Config, windows []string, attach bool) error { layout = EvenHorizontal } - _, err = smug.tmux.SelectLayout(ses+w.Name, layout) + _, err := smug.tmux.SelectLayout(sessionName+w.Name, layout) if err != nil { return err } } if len(windows) == 0 { - smug.switchOrAttach(ses, windows, attach) + return smug.switchOrAttach(sessionName, attach, context.InsideTmuxSession) } return nil diff --git a/smug_test.go b/smug_test.go index 839360b..43e6055 100644 --- a/smug_test.go +++ b/smug_test.go @@ -8,10 +8,12 @@ import ( ) var testTable = []struct { - config Config - startCommands []string - stopCommands []string - windows []string + config Config + options Options + context Context + startCommands []string + stopCommands []string + commanderOutput string }{ { Config{ @@ -19,8 +21,12 @@ var testTable = []struct { Root: "root", BeforeStart: []string{"command1", "command2"}, }, + Options{ + Windows: []string{}, + }, + Context{}, []string{ - "tmux has-session -t ses", + "tmux has-session -t ses:", "/bin/sh -c command1", "/bin/sh -c command2", "tmux new -Pd -s ses -n -c root", @@ -29,7 +35,7 @@ var testTable = []struct { []string{ "tmux kill-session -t ses", }, - []string{}, + "xyz", }, { Config{ @@ -57,8 +63,10 @@ var testTable = []struct { "stop2 -d --foo=bar", }, }, + Options{}, + Context{}, []string{ - "tmux has-session -t ses", + "tmux has-session -t ses:", "tmux new -Pd -s ses -n win1 -c root", "tmux split-window -Pd -t ses:win1 -c root -h", "tmux select-layout -t ses:win1 main-horizontal", @@ -69,7 +77,7 @@ var testTable = []struct { "/bin/sh -c stop2 -d --foo=bar", "tmux kill-session -t ses", }, - []string{}, + "xyz", }, { Config{ @@ -86,17 +94,19 @@ var testTable = []struct { }, }, }, + Options{ + Windows: []string{"win2"}, + }, + Context{}, []string{ - "tmux has-session -t ses", + "tmux has-session -t ses:", "tmux new -Pd -s ses -n win2 -c root", "tmux select-layout -t ses:win2 even-horizontal", }, []string{ "tmux kill-window -t ses:win2", }, - []string{ - "win2", - }, + "xyz", }, { Config{ @@ -115,8 +125,12 @@ var testTable = []struct { }, }, }, + Options{ + Windows: []string{}, + }, + Context{}, []string{ - "tmux has-session -t ses", + "tmux has-session -t ses:", "tmux new -Pd -s ses -n win1 -c root", "tmux send-keys -t ses:win1 command1 Enter", "tmux send-keys -t ses:win1 command2 Enter", @@ -130,18 +144,86 @@ var testTable = []struct { []string{ "tmux kill-session -t ses", }, - []string{}, + "xyz", + }, + + { + Config{ + Session: "ses", + Root: "root", + BeforeStart: []string{"command1", "command2"}, + }, + Options{}, + Context{}, + []string{ + "tmux has-session -t ses:", + "tmux attach -d -t ses:", + }, + []string{ + "tmux kill-session -t ses", + }, + "", + }, + { + Config{ + Session: "ses", + Root: "root", + }, + Options{Attach: true}, + Context{InsideTmuxSession: true}, + []string{ + "tmux has-session -t ses:", + "tmux new -Pd -s ses -n -c root", + "tmux switch-client -t ses:", + }, + []string{ + "tmux kill-session -t ses", + }, + "xyz", + }, + { + Config{ + Session: "ses", + Root: "root", + }, + Options{Attach: false}, + Context{InsideTmuxSession: true}, + []string{ + "tmux has-session -t ses:", + "tmux new -Pd -s ses -n -c root", + }, + []string{ + "tmux kill-session -t ses", + }, + "xyz", + }, + { + Config{ + Session: "ses", + Root: "root", + }, + Options{Attach: true}, + Context{InsideTmuxSession: true}, + []string{ + "tmux has-session -t ses:", + "tmux switch-client -t ses:", + }, + []string{ + "tmux kill-session -t ses", + }, + "", }, } type MockCommander struct { - Commands []string + Commands []string + DefaultOutput string } func (c *MockCommander) Exec(cmd *exec.Cmd) (string, error) { c.Commands = append(c.Commands, strings.Join(cmd.Args, " ")) - return "ses:", nil + return c.DefaultOutput, nil } func (c *MockCommander) ExecSilently(cmd *exec.Cmd) error { @@ -153,11 +235,11 @@ func TestStartSession(t *testing.T) { for _, params := range testTable { t.Run("test start session", func(t *testing.T) { - commander := &MockCommander{} + commander := &MockCommander{[]string{}, params.commanderOutput} tmux := Tmux{commander} smug := Smug{tmux, commander} - err := smug.Start(params.config, params.windows, false) + err := smug.Start(params.config, params.options, params.context) if err != nil { t.Fatalf("error %v", err) } @@ -168,11 +250,11 @@ func TestStartSession(t *testing.T) { }) t.Run("test stop session", func(t *testing.T) { - commander := &MockCommander{} + commander := &MockCommander{[]string{}, params.commanderOutput} tmux := Tmux{commander} smug := Smug{tmux, commander} - err := smug.Stop(params.config, params.windows) + err := smug.Stop(params.config, params.options, params.context) if err != nil { t.Fatalf("error %v", err) } diff --git a/tmux.go b/tmux.go index cb23050..830e37e 100644 --- a/tmux.go +++ b/tmux.go @@ -3,7 +3,6 @@ package main import ( "os" "os/exec" - "strings" ) const ( @@ -105,17 +104,6 @@ func (tmux Tmux) StopSession(target string) (string, error) { return tmux.commander.Exec(cmd) } -func (tmux Tmux) ListWindows(target string) ([]string, error) { - cmd := exec.Command("tmux", "list-windows", "-t", target, "-F", "#{window_index}") - - output, err := tmux.commander.Exec(cmd) - if err != nil { - return []string{}, err - } - - return strings.Split(output, "\n"), nil -} - func (tmux Tmux) SwitchClient(target string) error { cmd := exec.Command("tmux", "switch-client", "-t", target) return tmux.commander.ExecSilently(cmd)