Skip to content

Commit c1e985f

Browse files
refactor: optimize tmux list with tests (#35)
* chore: update go packages * chore: add testify library * test: add test for the format() function in the tmux package * refactor: simplify the format function in the tmux package * refactor: move data processing loging out of the tmux.List into its own function * test: add test to repro issue 34 * perf: optimize session data processing * chore: drop nil check in sorting function * refactor: extract sorting logic into it's own function * doc: remove comments with benchmark results
1 parent fdec6b4 commit c1e985f

File tree

5 files changed

+196
-75
lines changed

5 files changed

+196
-75
lines changed

convert/string.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,19 @@ import (
99
)
1010

1111
func StringToTime(s string) *time.Time {
12+
t := new(time.Time)
1213
if s == "" {
13-
return nil
14+
return t
1415
}
16+
1517
i, err := strconv.ParseInt(s, 10, 64)
1618
if err != nil {
1719
fmt.Println("Error:", err)
1820
os.Exit(1)
1921
}
20-
t := time.Unix(i, 0)
21-
return &t
22+
*t = time.Unix(i, 0)
23+
24+
return t
2225
}
2326

2427
func StringToIntSlice(s string) []int {

go.mod

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ module github.com/joshmedeski/sesh
22

33
go 1.21
44

5-
require github.com/urfave/cli/v2 v2.27.0
5+
require github.com/urfave/cli/v2 v2.27.1
66

77
require (
8-
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
8+
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
9+
github.com/davecgh/go-spew v1.1.1 // indirect
10+
github.com/pmezard/go-difflib v1.0.0 // indirect
911
github.com/russross/blackfriday/v2 v2.1.0 // indirect
10-
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
12+
github.com/stretchr/objx v0.5.0 // indirect
13+
github.com/stretchr/testify v1.8.4 // indirect
14+
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e // indirect
15+
gopkg.in/yaml.v3 v3.0.1 // indirect
1116
)

go.sum

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,31 @@
11
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
22
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
3+
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
4+
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
5+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
7+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
9+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
310
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
411
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
12+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
13+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
14+
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
15+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
16+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
17+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
18+
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
19+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
520
github.com/urfave/cli/v2 v2.27.0 h1:uNs1K8JwTFL84X68j5Fjny6hfANh9nTlJ6dRtZAFAHY=
621
github.com/urfave/cli/v2 v2.27.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
22+
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
23+
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
724
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
825
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
26+
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e h1:+SOyEddqYF09QP7vr7CgJ1eti3pY9Fn3LHO1M1r/0sI=
27+
github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
28+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
29+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
30+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
31+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

tmux/list.go

Lines changed: 73 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -75,36 +75,79 @@ type TmuxSession struct {
7575

7676
func format() string {
7777
variables := []string{
78-
"session_activity",
79-
"session_alerts",
80-
"session_attached",
81-
"session_attached_list",
82-
"session_created",
83-
"session_format",
84-
"session_group",
85-
"session_group_attached",
86-
"session_group_attached_list",
87-
"session_group_list",
88-
"session_group_many_attached",
89-
"session_group_size",
90-
"session_grouped",
91-
"session_id",
92-
"session_last_attached",
93-
"session_many_attached",
94-
"session_marked",
95-
"session_name",
96-
"session_path",
97-
"session_stack",
98-
"session_windows",
78+
"#{session_activity}",
79+
"#{session_alerts}",
80+
"#{session_attached}",
81+
"#{session_attached_list}",
82+
"#{session_created}",
83+
"#{session_format}",
84+
"#{session_group}",
85+
"#{session_group_attached}",
86+
"#{session_group_attached_list}",
87+
"#{session_group_list}",
88+
"#{session_group_many_attached}",
89+
"#{session_group_size}",
90+
"#{session_grouped}",
91+
"#{session_id}",
92+
"#{session_last_attached}",
93+
"#{session_many_attached}",
94+
"#{session_marked}",
95+
"#{session_name}",
96+
"#{session_path}",
97+
"#{session_stack}",
98+
"#{session_windows}",
9999
}
100-
variablesStr := ""
101-
for i, variable := range variables {
102-
variablesStr += "#{" + variable + "}"
103-
if i != len(variables)-1 {
104-
variablesStr += " "
100+
101+
return strings.Join(variables, " ")
102+
}
103+
104+
func processSessions(sessionList []string) []*TmuxSession {
105+
sessions := make([]*TmuxSession, 0, len(sessionList))
106+
for _, line := range sessionList {
107+
fields := strings.Split(line, " ") // Strings split by single space
108+
109+
if len(fields) != 21 {
110+
continue
111+
}
112+
if fields[2] == "1" {
113+
continue
114+
}
115+
116+
session := &TmuxSession{
117+
Activity: convert.StringToTime(fields[0]),
118+
Alerts: convert.StringToIntSlice(fields[1]),
119+
Attached: convert.StringToInt(fields[2]),
120+
AttachedList: strings.Split(fields[3], ","),
121+
Created: convert.StringToTime(fields[4]),
122+
Format: convert.StringToBool(fields[5]),
123+
Group: fields[6],
124+
GroupAttached: convert.StringToInt(fields[7]),
125+
GroupAttachedList: strings.Split(fields[8], ","),
126+
GroupList: strings.Split(fields[9], ","),
127+
GroupManyAttached: convert.StringToBool(fields[10]),
128+
GroupSize: convert.StringToInt(fields[11]),
129+
Grouped: convert.StringToBool(fields[12]),
130+
ID: fields[13],
131+
LastAttached: convert.StringToTime(fields[14]),
132+
ManyAttached: convert.StringToBool(fields[15]),
133+
Marked: convert.StringToBool(fields[16]),
134+
Name: fields[17],
135+
Path: fields[18],
136+
Stack: convert.StringToIntSlice(fields[19]),
137+
Windows: convert.StringToInt(fields[20]),
105138
}
139+
sessions = append(sessions, session)
106140
}
107-
return variablesStr
141+
142+
return sessions
143+
}
144+
145+
func sortSessions(sessions []*TmuxSession) []*TmuxSession {
146+
sort.Slice(sessions, func(i, j int) bool {
147+
return sessions[j].LastAttached.Before(*sessions[i].LastAttached)
148+
})
149+
150+
return sessions
108151
}
109152

110153
func List() ([]*TmuxSession, error) {
@@ -116,46 +159,7 @@ func List() ([]*TmuxSession, error) {
116159
}
117160
sessionList := strings.TrimSpace(string(output))
118161
lines := strings.Split(sessionList, "\n")
119-
sessions := make([]*TmuxSession, 0, len(lines))
120-
for _, line := range lines {
121-
fields := strings.Split(line, " ") // Strings split by single space
122-
if len(fields) == 21 {
123-
session := &TmuxSession{
124-
Activity: convert.StringToTime(fields[0]),
125-
Alerts: convert.StringToIntSlice(fields[1]),
126-
Attached: convert.StringToInt(fields[2]),
127-
AttachedList: strings.Split(fields[3], ","),
128-
Created: convert.StringToTime(fields[4]),
129-
Format: convert.StringToBool(fields[5]),
130-
Group: fields[6],
131-
GroupAttached: convert.StringToInt(fields[7]),
132-
GroupAttachedList: strings.Split(fields[8], ","),
133-
GroupList: strings.Split(fields[9], ","),
134-
GroupManyAttached: convert.StringToBool(fields[10]),
135-
GroupSize: convert.StringToInt(fields[11]),
136-
Grouped: convert.StringToBool(fields[12]),
137-
ID: fields[13],
138-
LastAttached: convert.StringToTime(fields[14]),
139-
ManyAttached: convert.StringToBool(fields[15]),
140-
Marked: convert.StringToBool(fields[16]),
141-
Name: fields[17],
142-
Path: fields[18],
143-
Stack: convert.StringToIntSlice(fields[19]),
144-
Windows: convert.StringToInt(fields[20]),
145-
}
146-
if session.Attached != 1 {
147-
sessions = append(sessions, session)
148-
}
149-
sort.Slice(sessions, func(i, j int) bool {
150-
if sessions[i].LastAttached == nil {
151-
return false
152-
}
153-
if sessions[j].LastAttached == nil {
154-
return true
155-
}
156-
return sessions[j].LastAttached.Before(*sessions[i].LastAttached)
157-
})
158-
}
159-
}
160-
return sessions, nil
162+
sessions := processSessions(lines)
163+
164+
return sortSessions(sessions), nil
161165
}

tmux/list_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package tmux
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func TestFormat(t *testing.T) {
10+
want := "#{session_activity} #{session_alerts} #{session_attached}" +
11+
" #{session_attached_list} #{session_created} #{session_format} " +
12+
"#{session_group} #{session_group_attached} " +
13+
"#{session_group_attached_list} #{session_group_list} " +
14+
"#{session_group_many_attached} #{session_group_size} " +
15+
"#{session_grouped} #{session_id} #{session_last_attached} " +
16+
"#{session_many_attached} #{session_marked} #{session_name} " +
17+
"#{session_path} #{session_stack} #{session_windows}"
18+
got := format()
19+
require.Equal(t, want, got)
20+
}
21+
22+
func BenchmarkFormat(i *testing.B) {
23+
for n := 0; n < i.N; n++ {
24+
format()
25+
}
26+
}
27+
28+
func TestProcessSessions(t *testing.T) {
29+
testCases := map[string]struct {
30+
Input []string
31+
Expected []*TmuxSession
32+
}{
33+
"Single active session": {
34+
Input: []string{
35+
"1705879337 1 /dev/ttys000 1705878987 1 0 $2 1705879328 0 0 session-1 /some/test/path 1 1",
36+
},
37+
Expected: []*TmuxSession{},
38+
},
39+
"Single inactive session": {
40+
Input: []string{
41+
"1705879002 0 1705878987 1 0 $2 1705878987 0 0 session-1 /some/test/path 1 1",
42+
},
43+
Expected: make([]*TmuxSession, 1),
44+
},
45+
"Two inactive session": {
46+
Input: []string{
47+
"1705879002 0 1705878987 1 0 $2 1705878987 0 0 session-1 /some/test/path 1 1",
48+
"1705879063 0 1705879002 1 0 $3 1705879002 0 0 session-2 /some/other/test/path 1 1",
49+
},
50+
Expected: make([]*TmuxSession, 2),
51+
},
52+
"Two active session": {
53+
Input: []string{
54+
"1705879337 1 /dev/ttys000 1705878987 1 0 $2 1705879328 0 0 session-1 /some/test/path 1 1",
55+
"1705879337 1 /dev/ttys000 1705878987 1 0 $2 1705879328 0 0 session-1 /some/test/path 1 1",
56+
},
57+
Expected: []*TmuxSession{},
58+
},
59+
"No sessions": {
60+
Expected: []*TmuxSession{},
61+
},
62+
"Invalid LastAttached (Issue 34)": {
63+
Input: []string{
64+
"1705879002 0 1705878987 1 0 $2 1705878987 0 0 session-1 /some/test/path 1 1",
65+
"1705879063 0 1705879002 1 0 $3 0 0 session-2 /some/other/test/path 1 1",
66+
},
67+
Expected: make([]*TmuxSession, 2),
68+
},
69+
}
70+
71+
for name, tc := range testCases {
72+
t.Run(name, func(t *testing.T) {
73+
got := processSessions(tc.Input)
74+
require.Equal(t, len(tc.Expected), len(got))
75+
})
76+
}
77+
}
78+
79+
func BenchmarkProcessSessions(b *testing.B) {
80+
for n := 0; n < b.N; n++ {
81+
processSessions([]string{
82+
"1705879337 1 /dev/ttys000 1705878987 1 0 $2 1705879328 0 0 session-1 /some/test/path 1 1",
83+
"1705879337 1 /dev/ttys000 1705878987 1 0 $2 1705879328 0 0 session-1 /some/test/path 1 1",
84+
})
85+
}
86+
}

0 commit comments

Comments
 (0)