fix(snapshot): address PR review findings
- commit
- e2b324b8e937d4371b86fef9c5273fdcd248fc07
- author
- brice · · 2026-07-06 10:32 UTC
- parent
- d5782db
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
10 files · +425 -21
M internal/app/run.go +1 -1
| @@ -284,7 +284,7 @@ func runDaemon() error { | |||
| 284 | 284 | } | |
| 285 | 285 | logConfigWarnings(log, warnings) | |
| 286 | 286 | daemonOpts = append(daemonOpts, daemon.WithConfig(cfg)) | |
| 287 | - | daemonOpts = append(daemonOpts, daemon.WithProcessInspector(platform.ProcessInspector{}), daemon.WithDirOrHome(platform.DirOrHome)) | |
| 287 | + | daemonOpts = append(daemonOpts, daemon.WithProcessInspector(platform.NewProcessInspector()), daemon.WithDirOrHome(platform.DirOrHome)) | |
| 288 | 288 | daemonOpts = append(daemonOpts, daemon.WithSnapshotStore(snapshotadapter.NewStore(snapshotDir()))) | |
| 289 | 289 | storePath := persist.StorePath(platform.StateDir()) | |
| 290 | 290 | if store, err := kv.Open(storePath); err != nil { | |
| @@ -284,7 +284,7 @@ func runDaemon() error { | |||||
| 284 | } | 284 | } | ||
| 285 | logConfigWarnings(log, warnings) | 285 | logConfigWarnings(log, warnings) | ||
| 286 | daemonOpts = append(daemonOpts, daemon.WithConfig(cfg)) | 286 | daemonOpts = append(daemonOpts, daemon.WithConfig(cfg)) | ||
| 287 | - | daemonOpts = append(daemonOpts, daemon.WithProcessInspector(platform.ProcessInspector{}), daemon.WithDirOrHome(platform.DirOrHome)) | 287 | + | daemonOpts = append(daemonOpts, daemon.WithProcessInspector(platform.NewProcessInspector()), daemon.WithDirOrHome(platform.DirOrHome)) |
| 288 | daemonOpts = append(daemonOpts, daemon.WithSnapshotStore(snapshotadapter.NewStore(snapshotDir()))) | 288 | daemonOpts = append(daemonOpts, daemon.WithSnapshotStore(snapshotadapter.NewStore(snapshotDir()))) | ||
| 289 | storePath := persist.StorePath(platform.StateDir()) | 289 | storePath := persist.StorePath(platform.StateDir()) | ||
| 290 | if store, err := kv.Open(storePath); err != nil { | 290 | if store, err := kv.Open(storePath); err != nil { | ||
M internal/platform/process_linux.go +45 -6
| @@ -8,16 +8,55 @@ import ( | |||
| 8 | 8 | "sort" | |
| 9 | 9 | "strconv" | |
| 10 | 10 | "strings" | |
| 11 | + | "sync" | |
| 12 | + | "time" | |
| 11 | 13 | ) | |
| 12 | 14 | ||
| 15 | + | const processRecordCacheTTL = 250 * time.Millisecond | |
| 16 | + | ||
| 13 | 17 | // ProcessInspector implements ports.ProcessInspector using Linux /proc. | |
| 14 | - | type ProcessInspector struct{} | |
| 18 | + | type ProcessInspector struct { | |
| 19 | + | root string | |
| 20 | + | ||
| 21 | + | mu sync.Mutex | |
| 22 | + | cachedRecords []processRecord | |
| 23 | + | cachedRecordsUntil time.Time | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | // NewProcessInspector returns a Linux /proc-backed process inspector. | |
| 27 | + | func NewProcessInspector() *ProcessInspector { return newProcessInspector("/proc") } | |
| 28 | + | ||
| 29 | + | func newProcessInspector(root string) *ProcessInspector { return &ProcessInspector{root: root} } | |
| 30 | + | ||
| 31 | + | func (*ProcessInspector) Cwd(pid int) (string, error) { return ProcessCwd(pid) } | |
| 32 | + | func (*ProcessInspector) Comm(pid int) (string, error) { return ProcessComm(pid) } | |
| 33 | + | func (*ProcessInspector) Argv(pid int) ([]string, error) { return ProcessArgv(pid) } | |
| 34 | + | func (p *ProcessInspector) GroupArgv(pgid int, shellPid int) ([]string, error) { | |
| 35 | + | recs, err := p.processRecords() | |
| 36 | + | if err != nil { | |
| 37 | + | return nil, err | |
| 38 | + | } | |
| 39 | + | pid, ok := selectProcessGroupPID(recs, pgid, shellPid) | |
| 40 | + | if !ok { | |
| 41 | + | return nil, nil | |
| 42 | + | } | |
| 43 | + | return ProcessArgv(pid) | |
| 44 | + | } | |
| 15 | 45 | ||
| 16 | - | func (ProcessInspector) Cwd(pid int) (string, error) { return ProcessCwd(pid) } | |
| 17 | - | func (ProcessInspector) Comm(pid int) (string, error) { return ProcessComm(pid) } | |
| 18 | - | func (ProcessInspector) Argv(pid int) ([]string, error) { return ProcessArgv(pid) } | |
| 19 | - | func (ProcessInspector) GroupArgv(pgid int, shellPid int) ([]string, error) { | |
| 20 | - | return ProcessGroupArgv(pgid, shellPid) | |
| 46 | + | func (p *ProcessInspector) processRecords() ([]processRecord, error) { | |
| 47 | + | p.mu.Lock() | |
| 48 | + | defer p.mu.Unlock() | |
| 49 | + | now := time.Now() | |
| 50 | + | if now.Before(p.cachedRecordsUntil) { | |
| 51 | + | return p.cachedRecords, nil | |
| 52 | + | } | |
| 53 | + | recs, err := readProcRecords(p.root) | |
| 54 | + | if err != nil { | |
| 55 | + | return nil, err | |
| 56 | + | } | |
| 57 | + | p.cachedRecords = recs | |
| 58 | + | p.cachedRecordsUntil = now.Add(processRecordCacheTTL) | |
| 59 | + | return recs, nil | |
| 21 | 60 | } | |
| 22 | 61 | ||
| 23 | 62 | // ProcessArgv returns argv from /proc/<pid>/cmdline. | |
| @@ -8,16 +8,55 @@ import ( | |||||
| 8 | "sort" | 8 | "sort" | ||
| 9 | "strconv" | 9 | "strconv" | ||
| 10 | "strings" | 10 | "strings" | ||
| 11 | + | "sync" | |||
| 12 | + | "time" | |||
| 11 | ) | 13 | ) | ||
| 12 | 14 | ||||
| 15 | + | const processRecordCacheTTL = 250 * time.Millisecond | |||
| 16 | + | ||||
| 13 | // ProcessInspector implements ports.ProcessInspector using Linux /proc. | 17 | // ProcessInspector implements ports.ProcessInspector using Linux /proc. | ||
| 14 | - | type ProcessInspector struct{} | 18 | + | type ProcessInspector struct { |
| 19 | + | root string | |||
| 20 | + | ||||
| 21 | + | mu sync.Mutex | |||
| 22 | + | cachedRecords []processRecord | |||
| 23 | + | cachedRecordsUntil time.Time | |||
| 24 | + | } | |||
| 25 | + | ||||
| 26 | + | // NewProcessInspector returns a Linux /proc-backed process inspector. | |||
| 27 | + | func NewProcessInspector() *ProcessInspector { return newProcessInspector("/proc") } | |||
| 28 | + | ||||
| 29 | + | func newProcessInspector(root string) *ProcessInspector { return &ProcessInspector{root: root} } | |||
| 30 | + | ||||
| 31 | + | func (*ProcessInspector) Cwd(pid int) (string, error) { return ProcessCwd(pid) } | |||
| 32 | + | func (*ProcessInspector) Comm(pid int) (string, error) { return ProcessComm(pid) } | |||
| 33 | + | func (*ProcessInspector) Argv(pid int) ([]string, error) { return ProcessArgv(pid) } | |||
| 34 | + | func (p *ProcessInspector) GroupArgv(pgid int, shellPid int) ([]string, error) { | |||
| 35 | + | recs, err := p.processRecords() | |||
| 36 | + | if err != nil { | |||
| 37 | + | return nil, err | |||
| 38 | + | } | |||
| 39 | + | pid, ok := selectProcessGroupPID(recs, pgid, shellPid) | |||
| 40 | + | if !ok { | |||
| 41 | + | return nil, nil | |||
| 42 | + | } | |||
| 43 | + | return ProcessArgv(pid) | |||
| 44 | + | } | |||
| 15 | 45 | ||||
| 16 | - | func (ProcessInspector) Cwd(pid int) (string, error) { return ProcessCwd(pid) } | 46 | + | func (p *ProcessInspector) processRecords() ([]processRecord, error) { |
| 17 | - | func (ProcessInspector) Comm(pid int) (string, error) { return ProcessComm(pid) } | 47 | + | p.mu.Lock() |
| 18 | - | func (ProcessInspector) Argv(pid int) ([]string, error) { return ProcessArgv(pid) } | 48 | + | defer p.mu.Unlock() |
| 19 | - | func (ProcessInspector) GroupArgv(pgid int, shellPid int) ([]string, error) { | 49 | + | now := time.Now() |
| 20 | - | return ProcessGroupArgv(pgid, shellPid) | 50 | + | if now.Before(p.cachedRecordsUntil) { |
| 51 | + | return p.cachedRecords, nil | |||
| 52 | + | } | |||
| 53 | + | recs, err := readProcRecords(p.root) | |||
| 54 | + | if err != nil { | |||
| 55 | + | return nil, err | |||
| 56 | + | } | |||
| 57 | + | p.cachedRecords = recs | |||
| 58 | + | p.cachedRecordsUntil = now.Add(processRecordCacheTTL) | |||
| 59 | + | return recs, nil | |||
| 21 | } | 60 | } | ||
| 22 | 61 | ||||
| 23 | // ProcessArgv returns argv from /proc/<pid>/cmdline. | 62 | // ProcessArgv returns argv from /proc/<pid>/cmdline. | ||
M internal/platform/process_linux_test.go +33 -0
| @@ -1,9 +1,11 @@ | |||
| 1 | 1 | package platform | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "fmt" | |
| 4 | 5 | "os" | |
| 5 | 6 | "path/filepath" | |
| 6 | 7 | "reflect" | |
| 8 | + | "strconv" | |
| 7 | 9 | "testing" | |
| 8 | 10 | ) | |
| 9 | 11 | ||
| @@ -64,6 +66,37 @@ func TestSelectProcessGroupPID(t *testing.T) { | |||
| 64 | 66 | } | |
| 65 | 67 | } | |
| 66 | 68 | ||
| 69 | + | func TestProcessInspectorCachesProcessRecordsBriefly(t *testing.T) { | |
| 70 | + | root := t.TempDir() | |
| 71 | + | writeProcStat(t, root, 10, 7) | |
| 72 | + | ins := newProcessInspector(root) | |
| 73 | + | ||
| 74 | + | first, err := ins.processRecords() | |
| 75 | + | if err != nil { | |
| 76 | + | t.Fatal(err) | |
| 77 | + | } | |
| 78 | + | writeProcStat(t, root, 12, 7) | |
| 79 | + | second, err := ins.processRecords() | |
| 80 | + | if err != nil { | |
| 81 | + | t.Fatal(err) | |
| 82 | + | } | |
| 83 | + | if len(first) != 1 || len(second) != 1 { | |
| 84 | + | t.Fatalf("cached processRecords lengths = %d, %d; want 1, 1", len(first), len(second)) | |
| 85 | + | } | |
| 86 | + | } | |
| 87 | + | ||
| 88 | + | func writeProcStat(t *testing.T, root string, pid int, pgrp int) { | |
| 89 | + | t.Helper() | |
| 90 | + | dir := filepath.Join(root, strconv.Itoa(pid)) | |
| 91 | + | if err := os.MkdirAll(dir, 0o700); err != nil { | |
| 92 | + | t.Fatal(err) | |
| 93 | + | } | |
| 94 | + | stat := fmt.Sprintf("%d (cmd) S 1 %d 1 0 0", pid, pgrp) | |
| 95 | + | if err := os.WriteFile(filepath.Join(dir, "stat"), []byte(stat), 0o600); err != nil { | |
| 96 | + | t.Fatal(err) | |
| 97 | + | } | |
| 98 | + | } | |
| 99 | + | ||
| 67 | 100 | func TestParseStatPgrp(t *testing.T) { | |
| 68 | 101 | got, err := parseStatPgrp("123 (cmd with spaces) S 1 456 456 0") | |
| 69 | 102 | if err != nil || got != 456 { | |
| @@ -1,9 +1,11 @@ | |||||
| 1 | package platform | 1 | package platform | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "fmt" | |||
| 4 | "os" | 5 | "os" | ||
| 5 | "path/filepath" | 6 | "path/filepath" | ||
| 6 | "reflect" | 7 | "reflect" | ||
| 8 | + | "strconv" | |||
| 7 | "testing" | 9 | "testing" | ||
| 8 | ) | 10 | ) | ||
| 9 | 11 | ||||
| @@ -64,6 +66,37 @@ func TestSelectProcessGroupPID(t *testing.T) { | |||||
| 64 | } | 66 | } | ||
| 65 | } | 67 | } | ||
| 66 | 68 | ||||
| 69 | + | func TestProcessInspectorCachesProcessRecordsBriefly(t *testing.T) { | |||
| 70 | + | root := t.TempDir() | |||
| 71 | + | writeProcStat(t, root, 10, 7) | |||
| 72 | + | ins := newProcessInspector(root) | |||
| 73 | + | ||||
| 74 | + | first, err := ins.processRecords() | |||
| 75 | + | if err != nil { | |||
| 76 | + | t.Fatal(err) | |||
| 77 | + | } | |||
| 78 | + | writeProcStat(t, root, 12, 7) | |||
| 79 | + | second, err := ins.processRecords() | |||
| 80 | + | if err != nil { | |||
| 81 | + | t.Fatal(err) | |||
| 82 | + | } | |||
| 83 | + | if len(first) != 1 || len(second) != 1 { | |||
| 84 | + | t.Fatalf("cached processRecords lengths = %d, %d; want 1, 1", len(first), len(second)) | |||
| 85 | + | } | |||
| 86 | + | } | |||
| 87 | + | ||||
| 88 | + | func writeProcStat(t *testing.T, root string, pid int, pgrp int) { | |||
| 89 | + | t.Helper() | |||
| 90 | + | dir := filepath.Join(root, strconv.Itoa(pid)) | |||
| 91 | + | if err := os.MkdirAll(dir, 0o700); err != nil { | |||
| 92 | + | t.Fatal(err) | |||
| 93 | + | } | |||
| 94 | + | stat := fmt.Sprintf("%d (cmd) S 1 %d 1 0 0", pid, pgrp) | |||
| 95 | + | if err := os.WriteFile(filepath.Join(dir, "stat"), []byte(stat), 0o600); err != nil { | |||
| 96 | + | t.Fatal(err) | |||
| 97 | + | } | |||
| 98 | + | } | |||
| 99 | + | ||||
| 67 | func TestParseStatPgrp(t *testing.T) { | 100 | func TestParseStatPgrp(t *testing.T) { | ||
| 68 | got, err := parseStatPgrp("123 (cmd with spaces) S 1 456 456 0") | 101 | got, err := parseStatPgrp("123 (cmd with spaces) S 1 456 456 0") | ||
| 69 | if err != nil || got != 456 { | 102 | if err != nil || got != 456 { | ||
M internal/ports/mocks/mocks.go +277 -0
| @@ -2068,6 +2068,283 @@ func (_c *MockTimer_Stop_Call) RunAndReturn(run func() bool) *MockTimer_Stop_Cal | |||
| 2068 | 2068 | return _c | |
| 2069 | 2069 | } | |
| 2070 | 2070 | ||
| 2071 | + | // NewMockProcessInspector creates a new instance of MockProcessInspector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 2072 | + | // The first argument is typically a *testing.T value. | |
| 2073 | + | func NewMockProcessInspector(t interface { | |
| 2074 | + | mock.TestingT | |
| 2075 | + | Cleanup(func()) | |
| 2076 | + | }) *MockProcessInspector { | |
| 2077 | + | mock := &MockProcessInspector{} | |
| 2078 | + | mock.Mock.Test(t) | |
| 2079 | + | ||
| 2080 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |
| 2081 | + | ||
| 2082 | + | return mock | |
| 2083 | + | } | |
| 2084 | + | ||
| 2085 | + | // MockProcessInspector is an autogenerated mock type for the ProcessInspector type | |
| 2086 | + | type MockProcessInspector struct { | |
| 2087 | + | mock.Mock | |
| 2088 | + | } | |
| 2089 | + | ||
| 2090 | + | type MockProcessInspector_Expecter struct { | |
| 2091 | + | mock *mock.Mock | |
| 2092 | + | } | |
| 2093 | + | ||
| 2094 | + | func (_m *MockProcessInspector) EXPECT() *MockProcessInspector_Expecter { | |
| 2095 | + | return &MockProcessInspector_Expecter{mock: &_m.Mock} | |
| 2096 | + | } | |
| 2097 | + | ||
| 2098 | + | // Argv provides a mock function for the type MockProcessInspector | |
| 2099 | + | func (_mock *MockProcessInspector) Argv(pid int) ([]string, error) { | |
| 2100 | + | ret := _mock.Called(pid) | |
| 2101 | + | ||
| 2102 | + | if len(ret) == 0 { | |
| 2103 | + | panic("no return value specified for Argv") | |
| 2104 | + | } | |
| 2105 | + | ||
| 2106 | + | var r0 []string | |
| 2107 | + | var r1 error | |
| 2108 | + | if returnFunc, ok := ret.Get(0).(func(int) ([]string, error)); ok { | |
| 2109 | + | return returnFunc(pid) | |
| 2110 | + | } | |
| 2111 | + | if returnFunc, ok := ret.Get(0).(func(int) []string); ok { | |
| 2112 | + | r0 = returnFunc(pid) | |
| 2113 | + | } else { | |
| 2114 | + | if ret.Get(0) != nil { | |
| 2115 | + | r0 = ret.Get(0).([]string) | |
| 2116 | + | } | |
| 2117 | + | } | |
| 2118 | + | if returnFunc, ok := ret.Get(1).(func(int) error); ok { | |
| 2119 | + | r1 = returnFunc(pid) | |
| 2120 | + | } else { | |
| 2121 | + | r1 = ret.Error(1) | |
| 2122 | + | } | |
| 2123 | + | return r0, r1 | |
| 2124 | + | } | |
| 2125 | + | ||
| 2126 | + | // MockProcessInspector_Argv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Argv' | |
| 2127 | + | type MockProcessInspector_Argv_Call struct { | |
| 2128 | + | *mock.Call | |
| 2129 | + | } | |
| 2130 | + | ||
| 2131 | + | // Argv is a helper method to define mock.On call | |
| 2132 | + | // - pid int | |
| 2133 | + | func (_e *MockProcessInspector_Expecter) Argv(pid any) *MockProcessInspector_Argv_Call { | |
| 2134 | + | return &MockProcessInspector_Argv_Call{Call: _e.mock.On("Argv", pid)} | |
| 2135 | + | } | |
| 2136 | + | ||
| 2137 | + | func (_c *MockProcessInspector_Argv_Call) Run(run func(pid int)) *MockProcessInspector_Argv_Call { | |
| 2138 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 2139 | + | var arg0 int | |
| 2140 | + | if args[0] != nil { | |
| 2141 | + | arg0 = args[0].(int) | |
| 2142 | + | } | |
| 2143 | + | run( | |
| 2144 | + | arg0, | |
| 2145 | + | ) | |
| 2146 | + | }) | |
| 2147 | + | return _c | |
| 2148 | + | } | |
| 2149 | + | ||
| 2150 | + | func (_c *MockProcessInspector_Argv_Call) Return(strings []string, err error) *MockProcessInspector_Argv_Call { | |
| 2151 | + | _c.Call.Return(strings, err) | |
| 2152 | + | return _c | |
| 2153 | + | } | |
| 2154 | + | ||
| 2155 | + | func (_c *MockProcessInspector_Argv_Call) RunAndReturn(run func(pid int) ([]string, error)) *MockProcessInspector_Argv_Call { | |
| 2156 | + | _c.Call.Return(run) | |
| 2157 | + | return _c | |
| 2158 | + | } | |
| 2159 | + | ||
| 2160 | + | // Comm provides a mock function for the type MockProcessInspector | |
| 2161 | + | func (_mock *MockProcessInspector) Comm(pid int) (string, error) { | |
| 2162 | + | ret := _mock.Called(pid) | |
| 2163 | + | ||
| 2164 | + | if len(ret) == 0 { | |
| 2165 | + | panic("no return value specified for Comm") | |
| 2166 | + | } | |
| 2167 | + | ||
| 2168 | + | var r0 string | |
| 2169 | + | var r1 error | |
| 2170 | + | if returnFunc, ok := ret.Get(0).(func(int) (string, error)); ok { | |
| 2171 | + | return returnFunc(pid) | |
| 2172 | + | } | |
| 2173 | + | if returnFunc, ok := ret.Get(0).(func(int) string); ok { | |
| 2174 | + | r0 = returnFunc(pid) | |
| 2175 | + | } else { | |
| 2176 | + | r0 = ret.Get(0).(string) | |
| 2177 | + | } | |
| 2178 | + | if returnFunc, ok := ret.Get(1).(func(int) error); ok { | |
| 2179 | + | r1 = returnFunc(pid) | |
| 2180 | + | } else { | |
| 2181 | + | r1 = ret.Error(1) | |
| 2182 | + | } | |
| 2183 | + | return r0, r1 | |
| 2184 | + | } | |
| 2185 | + | ||
| 2186 | + | // MockProcessInspector_Comm_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Comm' | |
| 2187 | + | type MockProcessInspector_Comm_Call struct { | |
| 2188 | + | *mock.Call | |
| 2189 | + | } | |
| 2190 | + | ||
| 2191 | + | // Comm is a helper method to define mock.On call | |
| 2192 | + | // - pid int | |
| 2193 | + | func (_e *MockProcessInspector_Expecter) Comm(pid any) *MockProcessInspector_Comm_Call { | |
| 2194 | + | return &MockProcessInspector_Comm_Call{Call: _e.mock.On("Comm", pid)} | |
| 2195 | + | } | |
| 2196 | + | ||
| 2197 | + | func (_c *MockProcessInspector_Comm_Call) Run(run func(pid int)) *MockProcessInspector_Comm_Call { | |
| 2198 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 2199 | + | var arg0 int | |
| 2200 | + | if args[0] != nil { | |
| 2201 | + | arg0 = args[0].(int) | |
| 2202 | + | } | |
| 2203 | + | run( | |
| 2204 | + | arg0, | |
| 2205 | + | ) | |
| 2206 | + | }) | |
| 2207 | + | return _c | |
| 2208 | + | } | |
| 2209 | + | ||
| 2210 | + | func (_c *MockProcessInspector_Comm_Call) Return(s string, err error) *MockProcessInspector_Comm_Call { | |
| 2211 | + | _c.Call.Return(s, err) | |
| 2212 | + | return _c | |
| 2213 | + | } | |
| 2214 | + | ||
| 2215 | + | func (_c *MockProcessInspector_Comm_Call) RunAndReturn(run func(pid int) (string, error)) *MockProcessInspector_Comm_Call { | |
| 2216 | + | _c.Call.Return(run) | |
| 2217 | + | return _c | |
| 2218 | + | } | |
| 2219 | + | ||
| 2220 | + | // Cwd provides a mock function for the type MockProcessInspector | |
| 2221 | + | func (_mock *MockProcessInspector) Cwd(pid int) (string, error) { | |
| 2222 | + | ret := _mock.Called(pid) | |
| 2223 | + | ||
| 2224 | + | if len(ret) == 0 { | |
| 2225 | + | panic("no return value specified for Cwd") | |
| 2226 | + | } | |
| 2227 | + | ||
| 2228 | + | var r0 string | |
| 2229 | + | var r1 error | |
| 2230 | + | if returnFunc, ok := ret.Get(0).(func(int) (string, error)); ok { | |
| 2231 | + | return returnFunc(pid) | |
| 2232 | + | } | |
| 2233 | + | if returnFunc, ok := ret.Get(0).(func(int) string); ok { | |
| 2234 | + | r0 = returnFunc(pid) | |
| 2235 | + | } else { | |
| 2236 | + | r0 = ret.Get(0).(string) | |
| 2237 | + | } | |
| 2238 | + | if returnFunc, ok := ret.Get(1).(func(int) error); ok { | |
| 2239 | + | r1 = returnFunc(pid) | |
| 2240 | + | } else { | |
| 2241 | + | r1 = ret.Error(1) | |
| 2242 | + | } | |
| 2243 | + | return r0, r1 | |
| 2244 | + | } | |
| 2245 | + | ||
| 2246 | + | // MockProcessInspector_Cwd_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Cwd' | |
| 2247 | + | type MockProcessInspector_Cwd_Call struct { | |
| 2248 | + | *mock.Call | |
| 2249 | + | } | |
| 2250 | + | ||
| 2251 | + | // Cwd is a helper method to define mock.On call | |
| 2252 | + | // - pid int | |
| 2253 | + | func (_e *MockProcessInspector_Expecter) Cwd(pid any) *MockProcessInspector_Cwd_Call { | |
| 2254 | + | return &MockProcessInspector_Cwd_Call{Call: _e.mock.On("Cwd", pid)} | |
| 2255 | + | } | |
| 2256 | + | ||
| 2257 | + | func (_c *MockProcessInspector_Cwd_Call) Run(run func(pid int)) *MockProcessInspector_Cwd_Call { | |
| 2258 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 2259 | + | var arg0 int | |
| 2260 | + | if args[0] != nil { | |
| 2261 | + | arg0 = args[0].(int) | |
| 2262 | + | } | |
| 2263 | + | run( | |
| 2264 | + | arg0, | |
| 2265 | + | ) | |
| 2266 | + | }) | |
| 2267 | + | return _c | |
| 2268 | + | } | |
| 2269 | + | ||
| 2270 | + | func (_c *MockProcessInspector_Cwd_Call) Return(s string, err error) *MockProcessInspector_Cwd_Call { | |
| 2271 | + | _c.Call.Return(s, err) | |
| 2272 | + | return _c | |
| 2273 | + | } | |
| 2274 | + | ||
| 2275 | + | func (_c *MockProcessInspector_Cwd_Call) RunAndReturn(run func(pid int) (string, error)) *MockProcessInspector_Cwd_Call { | |
| 2276 | + | _c.Call.Return(run) | |
| 2277 | + | return _c | |
| 2278 | + | } | |
| 2279 | + | ||
| 2280 | + | // GroupArgv provides a mock function for the type MockProcessInspector | |
| 2281 | + | func (_mock *MockProcessInspector) GroupArgv(pgid int, shellPid int) ([]string, error) { | |
| 2282 | + | ret := _mock.Called(pgid, shellPid) | |
| 2283 | + | ||
| 2284 | + | if len(ret) == 0 { | |
| 2285 | + | panic("no return value specified for GroupArgv") | |
| 2286 | + | } | |
| 2287 | + | ||
| 2288 | + | var r0 []string | |
| 2289 | + | var r1 error | |
| 2290 | + | if returnFunc, ok := ret.Get(0).(func(int, int) ([]string, error)); ok { | |
| 2291 | + | return returnFunc(pgid, shellPid) | |
| 2292 | + | } | |
| 2293 | + | if returnFunc, ok := ret.Get(0).(func(int, int) []string); ok { | |
| 2294 | + | r0 = returnFunc(pgid, shellPid) | |
| 2295 | + | } else { | |
| 2296 | + | if ret.Get(0) != nil { | |
| 2297 | + | r0 = ret.Get(0).([]string) | |
| 2298 | + | } | |
| 2299 | + | } | |
| 2300 | + | if returnFunc, ok := ret.Get(1).(func(int, int) error); ok { | |
| 2301 | + | r1 = returnFunc(pgid, shellPid) | |
| 2302 | + | } else { | |
| 2303 | + | r1 = ret.Error(1) | |
| 2304 | + | } | |
| 2305 | + | return r0, r1 | |
| 2306 | + | } | |
| 2307 | + | ||
| 2308 | + | // MockProcessInspector_GroupArgv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GroupArgv' | |
| 2309 | + | type MockProcessInspector_GroupArgv_Call struct { | |
| 2310 | + | *mock.Call | |
| 2311 | + | } | |
| 2312 | + | ||
| 2313 | + | // GroupArgv is a helper method to define mock.On call | |
| 2314 | + | // - pgid int | |
| 2315 | + | // - shellPid int | |
| 2316 | + | func (_e *MockProcessInspector_Expecter) GroupArgv(pgid any, shellPid any) *MockProcessInspector_GroupArgv_Call { | |
| 2317 | + | return &MockProcessInspector_GroupArgv_Call{Call: _e.mock.On("GroupArgv", pgid, shellPid)} | |
| 2318 | + | } | |
| 2319 | + | ||
| 2320 | + | func (_c *MockProcessInspector_GroupArgv_Call) Run(run func(pgid int, shellPid int)) *MockProcessInspector_GroupArgv_Call { | |
| 2321 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 2322 | + | var arg0 int | |
| 2323 | + | if args[0] != nil { | |
| 2324 | + | arg0 = args[0].(int) | |
| 2325 | + | } | |
| 2326 | + | var arg1 int | |
| 2327 | + | if args[1] != nil { | |
| 2328 | + | arg1 = args[1].(int) | |
| 2329 | + | } | |
| 2330 | + | run( | |
| 2331 | + | arg0, | |
| 2332 | + | arg1, | |
| 2333 | + | ) | |
| 2334 | + | }) | |
| 2335 | + | return _c | |
| 2336 | + | } | |
| 2337 | + | ||
| 2338 | + | func (_c *MockProcessInspector_GroupArgv_Call) Return(strings []string, err error) *MockProcessInspector_GroupArgv_Call { | |
| 2339 | + | _c.Call.Return(strings, err) | |
| 2340 | + | return _c | |
| 2341 | + | } | |
| 2342 | + | ||
| 2343 | + | func (_c *MockProcessInspector_GroupArgv_Call) RunAndReturn(run func(pgid int, shellPid int) ([]string, error)) *MockProcessInspector_GroupArgv_Call { | |
| 2344 | + | _c.Call.Return(run) | |
| 2345 | + | return _c | |
| 2346 | + | } | |
| 2347 | + | ||
| 2071 | 2348 | // NewMockClipboardReader creates a new instance of MockClipboardReader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |
| 2072 | 2349 | // The first argument is typically a *testing.T value. | |
| 2073 | 2350 | func NewMockClipboardReader(t interface { | |
| @@ -2068,6 +2068,283 @@ func (_c *MockTimer_Stop_Call) RunAndReturn(run func() bool) *MockTimer_Stop_Cal | |||||
| 2068 | return _c | 2068 | return _c | ||
| 2069 | } | 2069 | } | ||
| 2070 | 2070 | ||||
| 2071 | + | // NewMockProcessInspector creates a new instance of MockProcessInspector. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | |||
| 2072 | + | // The first argument is typically a *testing.T value. | |||
| 2073 | + | func NewMockProcessInspector(t interface { | |||
| 2074 | + | mock.TestingT | |||
| 2075 | + | Cleanup(func()) | |||
| 2076 | + | }) *MockProcessInspector { | |||
| 2077 | + | mock := &MockProcessInspector{} | |||
| 2078 | + | mock.Mock.Test(t) | |||
| 2079 | + | ||||
| 2080 | + | t.Cleanup(func() { mock.AssertExpectations(t) }) | |||
| 2081 | + | ||||
| 2082 | + | return mock | |||
| 2083 | + | } | |||
| 2084 | + | ||||
| 2085 | + | // MockProcessInspector is an autogenerated mock type for the ProcessInspector type | |||
| 2086 | + | type MockProcessInspector struct { | |||
| 2087 | + | mock.Mock | |||
| 2088 | + | } | |||
| 2089 | + | ||||
| 2090 | + | type MockProcessInspector_Expecter struct { | |||
| 2091 | + | mock *mock.Mock | |||
| 2092 | + | } | |||
| 2093 | + | ||||
| 2094 | + | func (_m *MockProcessInspector) EXPECT() *MockProcessInspector_Expecter { | |||
| 2095 | + | return &MockProcessInspector_Expecter{mock: &_m.Mock} | |||
| 2096 | + | } | |||
| 2097 | + | ||||
| 2098 | + | // Argv provides a mock function for the type MockProcessInspector | |||
| 2099 | + | func (_mock *MockProcessInspector) Argv(pid int) ([]string, error) { | |||
| 2100 | + | ret := _mock.Called(pid) | |||
| 2101 | + | ||||
| 2102 | + | if len(ret) == 0 { | |||
| 2103 | + | panic("no return value specified for Argv") | |||
| 2104 | + | } | |||
| 2105 | + | ||||
| 2106 | + | var r0 []string | |||
| 2107 | + | var r1 error | |||
| 2108 | + | if returnFunc, ok := ret.Get(0).(func(int) ([]string, error)); ok { | |||
| 2109 | + | return returnFunc(pid) | |||
| 2110 | + | } | |||
| 2111 | + | if returnFunc, ok := ret.Get(0).(func(int) []string); ok { | |||
| 2112 | + | r0 = returnFunc(pid) | |||
| 2113 | + | } else { | |||
| 2114 | + | if ret.Get(0) != nil { | |||
| 2115 | + | r0 = ret.Get(0).([]string) | |||
| 2116 | + | } | |||
| 2117 | + | } | |||
| 2118 | + | if returnFunc, ok := ret.Get(1).(func(int) error); ok { | |||
| 2119 | + | r1 = returnFunc(pid) | |||
| 2120 | + | } else { | |||
| 2121 | + | r1 = ret.Error(1) | |||
| 2122 | + | } | |||
| 2123 | + | return r0, r1 | |||
| 2124 | + | } | |||
| 2125 | + | ||||
| 2126 | + | // MockProcessInspector_Argv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Argv' | |||
| 2127 | + | type MockProcessInspector_Argv_Call struct { | |||
| 2128 | + | *mock.Call | |||
| 2129 | + | } | |||
| 2130 | + | ||||
| 2131 | + | // Argv is a helper method to define mock.On call | |||
| 2132 | + | // - pid int | |||
| 2133 | + | func (_e *MockProcessInspector_Expecter) Argv(pid any) *MockProcessInspector_Argv_Call { | |||
| 2134 | + | return &MockProcessInspector_Argv_Call{Call: _e.mock.On("Argv", pid)} | |||
| 2135 | + | } | |||
| 2136 | + | ||||
| 2137 | + | func (_c *MockProcessInspector_Argv_Call) Run(run func(pid int)) *MockProcessInspector_Argv_Call { | |||
| 2138 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 2139 | + | var arg0 int | |||
| 2140 | + | if args[0] != nil { | |||
| 2141 | + | arg0 = args[0].(int) | |||
| 2142 | + | } | |||
| 2143 | + | run( | |||
| 2144 | + | arg0, | |||
| 2145 | + | ) | |||
| 2146 | + | }) | |||
| 2147 | + | return _c | |||
| 2148 | + | } | |||
| 2149 | + | ||||
| 2150 | + | func (_c *MockProcessInspector_Argv_Call) Return(strings []string, err error) *MockProcessInspector_Argv_Call { | |||
| 2151 | + | _c.Call.Return(strings, err) | |||
| 2152 | + | return _c | |||
| 2153 | + | } | |||
| 2154 | + | ||||
| 2155 | + | func (_c *MockProcessInspector_Argv_Call) RunAndReturn(run func(pid int) ([]string, error)) *MockProcessInspector_Argv_Call { | |||
| 2156 | + | _c.Call.Return(run) | |||
| 2157 | + | return _c | |||
| 2158 | + | } | |||
| 2159 | + | ||||
| 2160 | + | // Comm provides a mock function for the type MockProcessInspector | |||
| 2161 | + | func (_mock *MockProcessInspector) Comm(pid int) (string, error) { | |||
| 2162 | + | ret := _mock.Called(pid) | |||
| 2163 | + | ||||
| 2164 | + | if len(ret) == 0 { | |||
| 2165 | + | panic("no return value specified for Comm") | |||
| 2166 | + | } | |||
| 2167 | + | ||||
| 2168 | + | var r0 string | |||
| 2169 | + | var r1 error | |||
| 2170 | + | if returnFunc, ok := ret.Get(0).(func(int) (string, error)); ok { | |||
| 2171 | + | return returnFunc(pid) | |||
| 2172 | + | } | |||
| 2173 | + | if returnFunc, ok := ret.Get(0).(func(int) string); ok { | |||
| 2174 | + | r0 = returnFunc(pid) | |||
| 2175 | + | } else { | |||
| 2176 | + | r0 = ret.Get(0).(string) | |||
| 2177 | + | } | |||
| 2178 | + | if returnFunc, ok := ret.Get(1).(func(int) error); ok { | |||
| 2179 | + | r1 = returnFunc(pid) | |||
| 2180 | + | } else { | |||
| 2181 | + | r1 = ret.Error(1) | |||
| 2182 | + | } | |||
| 2183 | + | return r0, r1 | |||
| 2184 | + | } | |||
| 2185 | + | ||||
| 2186 | + | // MockProcessInspector_Comm_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Comm' | |||
| 2187 | + | type MockProcessInspector_Comm_Call struct { | |||
| 2188 | + | *mock.Call | |||
| 2189 | + | } | |||
| 2190 | + | ||||
| 2191 | + | // Comm is a helper method to define mock.On call | |||
| 2192 | + | // - pid int | |||
| 2193 | + | func (_e *MockProcessInspector_Expecter) Comm(pid any) *MockProcessInspector_Comm_Call { | |||
| 2194 | + | return &MockProcessInspector_Comm_Call{Call: _e.mock.On("Comm", pid)} | |||
| 2195 | + | } | |||
| 2196 | + | ||||
| 2197 | + | func (_c *MockProcessInspector_Comm_Call) Run(run func(pid int)) *MockProcessInspector_Comm_Call { | |||
| 2198 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 2199 | + | var arg0 int | |||
| 2200 | + | if args[0] != nil { | |||
| 2201 | + | arg0 = args[0].(int) | |||
| 2202 | + | } | |||
| 2203 | + | run( | |||
| 2204 | + | arg0, | |||
| 2205 | + | ) | |||
| 2206 | + | }) | |||
| 2207 | + | return _c | |||
| 2208 | + | } | |||
| 2209 | + | ||||
| 2210 | + | func (_c *MockProcessInspector_Comm_Call) Return(s string, err error) *MockProcessInspector_Comm_Call { | |||
| 2211 | + | _c.Call.Return(s, err) | |||
| 2212 | + | return _c | |||
| 2213 | + | } | |||
| 2214 | + | ||||
| 2215 | + | func (_c *MockProcessInspector_Comm_Call) RunAndReturn(run func(pid int) (string, error)) *MockProcessInspector_Comm_Call { | |||
| 2216 | + | _c.Call.Return(run) | |||
| 2217 | + | return _c | |||
| 2218 | + | } | |||
| 2219 | + | ||||
| 2220 | + | // Cwd provides a mock function for the type MockProcessInspector | |||
| 2221 | + | func (_mock *MockProcessInspector) Cwd(pid int) (string, error) { | |||
| 2222 | + | ret := _mock.Called(pid) | |||
| 2223 | + | ||||
| 2224 | + | if len(ret) == 0 { | |||
| 2225 | + | panic("no return value specified for Cwd") | |||
| 2226 | + | } | |||
| 2227 | + | ||||
| 2228 | + | var r0 string | |||
| 2229 | + | var r1 error | |||
| 2230 | + | if returnFunc, ok := ret.Get(0).(func(int) (string, error)); ok { | |||
| 2231 | + | return returnFunc(pid) | |||
| 2232 | + | } | |||
| 2233 | + | if returnFunc, ok := ret.Get(0).(func(int) string); ok { | |||
| 2234 | + | r0 = returnFunc(pid) | |||
| 2235 | + | } else { | |||
| 2236 | + | r0 = ret.Get(0).(string) | |||
| 2237 | + | } | |||
| 2238 | + | if returnFunc, ok := ret.Get(1).(func(int) error); ok { | |||
| 2239 | + | r1 = returnFunc(pid) | |||
| 2240 | + | } else { | |||
| 2241 | + | r1 = ret.Error(1) | |||
| 2242 | + | } | |||
| 2243 | + | return r0, r1 | |||
| 2244 | + | } | |||
| 2245 | + | ||||
| 2246 | + | // MockProcessInspector_Cwd_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Cwd' | |||
| 2247 | + | type MockProcessInspector_Cwd_Call struct { | |||
| 2248 | + | *mock.Call | |||
| 2249 | + | } | |||
| 2250 | + | ||||
| 2251 | + | // Cwd is a helper method to define mock.On call | |||
| 2252 | + | // - pid int | |||
| 2253 | + | func (_e *MockProcessInspector_Expecter) Cwd(pid any) *MockProcessInspector_Cwd_Call { | |||
| 2254 | + | return &MockProcessInspector_Cwd_Call{Call: _e.mock.On("Cwd", pid)} | |||
| 2255 | + | } | |||
| 2256 | + | ||||
| 2257 | + | func (_c *MockProcessInspector_Cwd_Call) Run(run func(pid int)) *MockProcessInspector_Cwd_Call { | |||
| 2258 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 2259 | + | var arg0 int | |||
| 2260 | + | if args[0] != nil { | |||
| 2261 | + | arg0 = args[0].(int) | |||
| 2262 | + | } | |||
| 2263 | + | run( | |||
| 2264 | + | arg0, | |||
| 2265 | + | ) | |||
| 2266 | + | }) | |||
| 2267 | + | return _c | |||
| 2268 | + | } | |||
| 2269 | + | ||||
| 2270 | + | func (_c *MockProcessInspector_Cwd_Call) Return(s string, err error) *MockProcessInspector_Cwd_Call { | |||
| 2271 | + | _c.Call.Return(s, err) | |||
| 2272 | + | return _c | |||
| 2273 | + | } | |||
| 2274 | + | ||||
| 2275 | + | func (_c *MockProcessInspector_Cwd_Call) RunAndReturn(run func(pid int) (string, error)) *MockProcessInspector_Cwd_Call { | |||
| 2276 | + | _c.Call.Return(run) | |||
| 2277 | + | return _c | |||
| 2278 | + | } | |||
| 2279 | + | ||||
| 2280 | + | // GroupArgv provides a mock function for the type MockProcessInspector | |||
| 2281 | + | func (_mock *MockProcessInspector) GroupArgv(pgid int, shellPid int) ([]string, error) { | |||
| 2282 | + | ret := _mock.Called(pgid, shellPid) | |||
| 2283 | + | ||||
| 2284 | + | if len(ret) == 0 { | |||
| 2285 | + | panic("no return value specified for GroupArgv") | |||
| 2286 | + | } | |||
| 2287 | + | ||||
| 2288 | + | var r0 []string | |||
| 2289 | + | var r1 error | |||
| 2290 | + | if returnFunc, ok := ret.Get(0).(func(int, int) ([]string, error)); ok { | |||
| 2291 | + | return returnFunc(pgid, shellPid) | |||
| 2292 | + | } | |||
| 2293 | + | if returnFunc, ok := ret.Get(0).(func(int, int) []string); ok { | |||
| 2294 | + | r0 = returnFunc(pgid, shellPid) | |||
| 2295 | + | } else { | |||
| 2296 | + | if ret.Get(0) != nil { | |||
| 2297 | + | r0 = ret.Get(0).([]string) | |||
| 2298 | + | } | |||
| 2299 | + | } | |||
| 2300 | + | if returnFunc, ok := ret.Get(1).(func(int, int) error); ok { | |||
| 2301 | + | r1 = returnFunc(pgid, shellPid) | |||
| 2302 | + | } else { | |||
| 2303 | + | r1 = ret.Error(1) | |||
| 2304 | + | } | |||
| 2305 | + | return r0, r1 | |||
| 2306 | + | } | |||
| 2307 | + | ||||
| 2308 | + | // MockProcessInspector_GroupArgv_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GroupArgv' | |||
| 2309 | + | type MockProcessInspector_GroupArgv_Call struct { | |||
| 2310 | + | *mock.Call | |||
| 2311 | + | } | |||
| 2312 | + | ||||
| 2313 | + | // GroupArgv is a helper method to define mock.On call | |||
| 2314 | + | // - pgid int | |||
| 2315 | + | // - shellPid int | |||
| 2316 | + | func (_e *MockProcessInspector_Expecter) GroupArgv(pgid any, shellPid any) *MockProcessInspector_GroupArgv_Call { | |||
| 2317 | + | return &MockProcessInspector_GroupArgv_Call{Call: _e.mock.On("GroupArgv", pgid, shellPid)} | |||
| 2318 | + | } | |||
| 2319 | + | ||||
| 2320 | + | func (_c *MockProcessInspector_GroupArgv_Call) Run(run func(pgid int, shellPid int)) *MockProcessInspector_GroupArgv_Call { | |||
| 2321 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 2322 | + | var arg0 int | |||
| 2323 | + | if args[0] != nil { | |||
| 2324 | + | arg0 = args[0].(int) | |||
| 2325 | + | } | |||
| 2326 | + | var arg1 int | |||
| 2327 | + | if args[1] != nil { | |||
| 2328 | + | arg1 = args[1].(int) | |||
| 2329 | + | } | |||
| 2330 | + | run( | |||
| 2331 | + | arg0, | |||
| 2332 | + | arg1, | |||
| 2333 | + | ) | |||
| 2334 | + | }) | |||
| 2335 | + | return _c | |||
| 2336 | + | } | |||
| 2337 | + | ||||
| 2338 | + | func (_c *MockProcessInspector_GroupArgv_Call) Return(strings []string, err error) *MockProcessInspector_GroupArgv_Call { | |||
| 2339 | + | _c.Call.Return(strings, err) | |||
| 2340 | + | return _c | |||
| 2341 | + | } | |||
| 2342 | + | ||||
| 2343 | + | func (_c *MockProcessInspector_GroupArgv_Call) RunAndReturn(run func(pgid int, shellPid int) ([]string, error)) *MockProcessInspector_GroupArgv_Call { | |||
| 2344 | + | _c.Call.Return(run) | |||
| 2345 | + | return _c | |||
| 2346 | + | } | |||
| 2347 | + | ||||
| 2071 | // NewMockClipboardReader creates a new instance of MockClipboardReader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | 2348 | // NewMockClipboardReader creates a new instance of MockClipboardReader. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. | ||
| 2072 | // The first argument is typically a *testing.T value. | 2349 | // The first argument is typically a *testing.T value. | ||
| 2073 | func NewMockClipboardReader(t interface { | 2350 | func NewMockClipboardReader(t interface { | ||
M internal/usecase/daemon/identity.go +28 -0
| @@ -17,6 +17,18 @@ func newStableID(prefix string) (string, error) { | |||
| 17 | 17 | return prefix + "_" + strings.ToLower(enc), nil | |
| 18 | 18 | } | |
| 19 | 19 | ||
| 20 | + | func (d *Daemon) newTabPaneStableIDs() (string, string, error) { | |
| 21 | + | tabStableID, err := newStableID("t") | |
| 22 | + | if err != nil { | |
| 23 | + | return "", "", fmt.Errorf("daemon: generating tab identity: %w", err) | |
| 24 | + | } | |
| 25 | + | paneStableID, err := newStableID("p") | |
| 26 | + | if err != nil { | |
| 27 | + | return "", "", fmt.Errorf("daemon: generating pane identity: %w", err) | |
| 28 | + | } | |
| 29 | + | return tabStableID, paneStableID, nil | |
| 30 | + | } | |
| 31 | + | ||
| 20 | 32 | func fallbackStableID(prefix string) string { | |
| 21 | 33 | id, err := newStableID(prefix) | |
| 22 | 34 | if err != nil { | |
| @@ -24,3 +36,19 @@ func fallbackStableID(prefix string) string { | |||
| 24 | 36 | } | |
| 25 | 37 | return id | |
| 26 | 38 | } | |
| 39 | + | ||
| 40 | + | func escapeVEVComponent(s string) string { | |
| 41 | + | var b strings.Builder | |
| 42 | + | for i := 0; i < len(s); i++ { | |
| 43 | + | c := s[i] | |
| 44 | + | if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-' { | |
| 45 | + | b.WriteByte(c) | |
| 46 | + | continue | |
| 47 | + | } | |
| 48 | + | b.WriteByte('%') | |
| 49 | + | const hex = "0123456789ABCDEF" | |
| 50 | + | b.WriteByte(hex[c>>4]) | |
| 51 | + | b.WriteByte(hex[c&0x0f]) | |
| 52 | + | } | |
| 53 | + | return b.String() | |
| 54 | + | } | |
| @@ -17,6 +17,18 @@ func newStableID(prefix string) (string, error) { | |||||
| 17 | return prefix + "_" + strings.ToLower(enc), nil | 17 | return prefix + "_" + strings.ToLower(enc), nil | ||
| 18 | } | 18 | } | ||
| 19 | 19 | ||||
| 20 | + | func (d *Daemon) newTabPaneStableIDs() (string, string, error) { | |||
| 21 | + | tabStableID, err := newStableID("t") | |||
| 22 | + | if err != nil { | |||
| 23 | + | return "", "", fmt.Errorf("daemon: generating tab identity: %w", err) | |||
| 24 | + | } | |||
| 25 | + | paneStableID, err := newStableID("p") | |||
| 26 | + | if err != nil { | |||
| 27 | + | return "", "", fmt.Errorf("daemon: generating pane identity: %w", err) | |||
| 28 | + | } | |||
| 29 | + | return tabStableID, paneStableID, nil | |||
| 30 | + | } | |||
| 31 | + | ||||
| 20 | func fallbackStableID(prefix string) string { | 32 | func fallbackStableID(prefix string) string { | ||
| 21 | id, err := newStableID(prefix) | 33 | id, err := newStableID(prefix) | ||
| 22 | if err != nil { | 34 | if err != nil { | ||
| @@ -24,3 +36,19 @@ func fallbackStableID(prefix string) string { | |||||
| 24 | } | 36 | } | ||
| 25 | return id | 37 | return id | ||
| 26 | } | 38 | } | ||
| 39 | + | ||||
| 40 | + | func escapeVEVComponent(s string) string { | |||
| 41 | + | var b strings.Builder | |||
| 42 | + | for i := 0; i < len(s); i++ { | |||
| 43 | + | c := s[i] | |||
| 44 | + | if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '-' { | |||
| 45 | + | b.WriteByte(c) | |||
| 46 | + | continue | |||
| 47 | + | } | |||
| 48 | + | b.WriteByte('%') | |||
| 49 | + | const hex = "0123456789ABCDEF" | |||
| 50 | + | b.WriteByte(hex[c>>4]) | |||
| 51 | + | b.WriteByte(hex[c&0x0f]) | |||
| 52 | + | } | |||
| 53 | + | return b.String() | |||
| 54 | + | } | |||
M internal/usecase/daemon/process_restore.go +8 -1
| @@ -46,7 +46,7 @@ func extractAgentSessionID(strategy string, argv []string) string { | |||
| 46 | 46 | continue | |
| 47 | 47 | } | |
| 48 | 48 | candidate := argv[i+1] | |
| 49 | - | if candidate != "" && !strings.HasPrefix(candidate, "-") { | |
| 49 | + | if candidate != "" && !strings.HasPrefix(candidate, "-") && !containsControl(candidate) { | |
| 50 | 50 | return candidate | |
| 51 | 51 | } | |
| 52 | 52 | } | |
| @@ -78,6 +78,9 @@ func planProcessRestore(proc *snapcodec.Process, allow map[string]struct{}) proc | |||
| 78 | 78 | } | |
| 79 | 79 | ||
| 80 | 80 | id := proc.Opts.AgentSessionID | |
| 81 | + | if containsControl(id) { | |
| 82 | + | return processRestoreDecision{Reason: "invalid_agent_session_id"} | |
| 83 | + | } | |
| 81 | 84 | switch strategy { | |
| 82 | 85 | case processStrategyPi: | |
| 83 | 86 | if id != "" { | |
| @@ -128,6 +131,10 @@ func processAllowlistKey(strategy string, argv []string) string { | |||
| 128 | 131 | } | |
| 129 | 132 | } | |
| 130 | 133 | ||
| 134 | + | func containsControl(s string) bool { | |
| 135 | + | return strings.IndexFunc(s, unicode.IsControl) >= 0 | |
| 136 | + | } | |
| 137 | + | ||
| 131 | 138 | func shellQuoteArgvMust(argv []string) string { | |
| 132 | 139 | command, _ := shellQuoteArgv(argv) | |
| 133 | 140 | return command | |
| @@ -46,7 +46,7 @@ func extractAgentSessionID(strategy string, argv []string) string { | |||||
| 46 | continue | 46 | continue | ||
| 47 | } | 47 | } | ||
| 48 | candidate := argv[i+1] | 48 | candidate := argv[i+1] | ||
| 49 | - | if candidate != "" && !strings.HasPrefix(candidate, "-") { | 49 | + | if candidate != "" && !strings.HasPrefix(candidate, "-") && !containsControl(candidate) { |
| 50 | return candidate | 50 | return candidate | ||
| 51 | } | 51 | } | ||
| 52 | } | 52 | } | ||
| @@ -78,6 +78,9 @@ func planProcessRestore(proc *snapcodec.Process, allow map[string]struct{}) proc | |||||
| 78 | } | 78 | } | ||
| 79 | 79 | ||||
| 80 | id := proc.Opts.AgentSessionID | 80 | id := proc.Opts.AgentSessionID | ||
| 81 | + | if containsControl(id) { | |||
| 82 | + | return processRestoreDecision{Reason: "invalid_agent_session_id"} | |||
| 83 | + | } | |||
| 81 | switch strategy { | 84 | switch strategy { | ||
| 82 | case processStrategyPi: | 85 | case processStrategyPi: | ||
| 83 | if id != "" { | 86 | if id != "" { | ||
| @@ -128,6 +131,10 @@ func processAllowlistKey(strategy string, argv []string) string { | |||||
| 128 | } | 131 | } | ||
| 129 | } | 132 | } | ||
| 130 | 133 | ||||
| 134 | + | func containsControl(s string) bool { | |||
| 135 | + | return strings.IndexFunc(s, unicode.IsControl) >= 0 | |||
| 136 | + | } | |||
| 137 | + | ||||
| 131 | func shellQuoteArgvMust(argv []string) string { | 138 | func shellQuoteArgvMust(argv []string) string { | ||
| 132 | command, _ := shellQuoteArgv(argv) | 139 | command, _ := shellQuoteArgv(argv) | ||
| 133 | return command | 140 | return command | ||
M internal/usecase/daemon/process_restore_test.go +6 -0
| @@ -81,6 +81,11 @@ func TestProcessRestorePlan(t *testing.T) { | |||
| 81 | 81 | allow: map[string]struct{}{"less": {}}, | |
| 82 | 82 | reason: "not_allowlisted", | |
| 83 | 83 | }, | |
| 84 | + | { | |
| 85 | + | name: "agent ID with control byte rejected", | |
| 86 | + | proc: &snapcodec.Process{Argv: []string{"pi", "cli"}, Strategy: processStrategyPi, Opts: snapcodec.ProcessOpts{AgentSessionID: "session\rmalicious"}}, | |
| 87 | + | reason: "invalid_agent_session_id", | |
| 88 | + | }, | |
| 84 | 89 | { | |
| 85 | 90 | name: "empty executable rejected", | |
| 86 | 91 | proc: &snapcodec.Process{Argv: []string{""}, Strategy: processStrategyGeneric}, | |
| @@ -115,6 +120,7 @@ func TestExtractAgentSessionID(t *testing.T) { | |||
| 115 | 120 | {name: "codex resume extraction", strategy: processStrategyCodex, argv: []string{"codex", "resume", "abc"}, want: "abc"}, | |
| 116 | 121 | {name: "claude flag-looking ID rejected", strategy: processStrategyClaude, argv: []string{"claude", "--resume", "--continue"}}, | |
| 117 | 122 | {name: "pi flag-looking ID rejected", strategy: processStrategyPi, argv: []string{"pi", "-r", "--last"}}, | |
| 123 | + | {name: "control byte ID rejected", strategy: processStrategyPi, argv: []string{"pi", "--resume", "abc\nmalicious"}}, | |
| 118 | 124 | } | |
| 119 | 125 | ||
| 120 | 126 | for _, tt := range tests { | |
| @@ -81,6 +81,11 @@ func TestProcessRestorePlan(t *testing.T) { | |||||
| 81 | allow: map[string]struct{}{"less": {}}, | 81 | allow: map[string]struct{}{"less": {}}, | ||
| 82 | reason: "not_allowlisted", | 82 | reason: "not_allowlisted", | ||
| 83 | }, | 83 | }, | ||
| 84 | + | { | |||
| 85 | + | name: "agent ID with control byte rejected", | |||
| 86 | + | proc: &snapcodec.Process{Argv: []string{"pi", "cli"}, Strategy: processStrategyPi, Opts: snapcodec.ProcessOpts{AgentSessionID: "session\rmalicious"}}, | |||
| 87 | + | reason: "invalid_agent_session_id", | |||
| 88 | + | }, | |||
| 84 | { | 89 | { | ||
| 85 | name: "empty executable rejected", | 90 | name: "empty executable rejected", | ||
| 86 | proc: &snapcodec.Process{Argv: []string{""}, Strategy: processStrategyGeneric}, | 91 | proc: &snapcodec.Process{Argv: []string{""}, Strategy: processStrategyGeneric}, | ||
| @@ -115,6 +120,7 @@ func TestExtractAgentSessionID(t *testing.T) { | |||||
| 115 | {name: "codex resume extraction", strategy: processStrategyCodex, argv: []string{"codex", "resume", "abc"}, want: "abc"}, | 120 | {name: "codex resume extraction", strategy: processStrategyCodex, argv: []string{"codex", "resume", "abc"}, want: "abc"}, | ||
| 116 | {name: "claude flag-looking ID rejected", strategy: processStrategyClaude, argv: []string{"claude", "--resume", "--continue"}}, | 121 | {name: "claude flag-looking ID rejected", strategy: processStrategyClaude, argv: []string{"claude", "--resume", "--continue"}}, | ||
| 117 | {name: "pi flag-looking ID rejected", strategy: processStrategyPi, argv: []string{"pi", "-r", "--last"}}, | 122 | {name: "pi flag-looking ID rejected", strategy: processStrategyPi, argv: []string{"pi", "-r", "--last"}}, | ||
| 123 | + | {name: "control byte ID rejected", strategy: processStrategyPi, argv: []string{"pi", "--resume", "abc\nmalicious"}}, | |||
| 118 | } | 124 | } | ||
| 119 | 125 | ||||
| 120 | for _, tt := range tests { | 126 | for _, tt := range tests { | ||
M internal/usecase/daemon/session.go +5 -13
| @@ -123,13 +123,9 @@ func (d *Daemon) touchMRU(sess *session) { | |||
| 123 | 123 | ||
| 124 | 124 | func (d *Daemon) createSessionLocked(name string, ephemeral bool, cwd string, sz domain.Size) (*session, error) { | |
| 125 | 125 | tbSize := tabSize(sz) | |
| 126 | - | tabStableID, err := newStableID("t") | |
| 126 | + | tabStableID, paneStableID, err := d.newTabPaneStableIDs() | |
| 127 | 127 | if err != nil { | |
| 128 | - | return nil, fmt.Errorf("daemon: generating tab identity: %w", err) | |
| 129 | - | } | |
| 130 | - | paneStableID, err := newStableID("p") | |
| 131 | - | if err != nil { | |
| 132 | - | return nil, fmt.Errorf("daemon: generating pane identity: %w", err) | |
| 128 | + | return nil, err | |
| 133 | 129 | } | |
| 134 | 130 | pty, err := d.ptys.Open(d.shell, d.shellArgs, d.childEnv(name, tabStableID, paneStableID), cwd, tbSize) | |
| 135 | 131 | if err != nil { | |
| @@ -243,13 +239,9 @@ func (d *Daemon) createTab(sess *session, sz domain.Size) error { | |||
| 243 | 239 | cwd := sess.cwd | |
| 244 | 240 | client := sess.client | |
| 245 | 241 | sess.mu.Unlock() | |
| 246 | - | tabStableID, err := newStableID("t") | |
| 242 | + | tabStableID, paneStableID, err := d.newTabPaneStableIDs() | |
| 247 | 243 | if err != nil { | |
| 248 | - | return fmt.Errorf("daemon: generating tab identity: %w", err) | |
| 249 | - | } | |
| 250 | - | paneStableID, err := newStableID("p") | |
| 251 | - | if err != nil { | |
| 252 | - | return fmt.Errorf("daemon: generating pane identity: %w", err) | |
| 244 | + | return err | |
| 253 | 245 | } | |
| 254 | 246 | pty, err := d.ptys.Open(d.shell, d.shellArgs, d.childEnv(name, tabStableID, paneStableID), cwd, tbSize) | |
| 255 | 247 | if err != nil { | |
| @@ -737,5 +729,5 @@ func (d *Daemon) childEnv(name, tabStableID, paneStableID string) []string { | |||
| 737 | 729 | } | |
| 738 | 730 | out = append(out, e) | |
| 739 | 731 | } | |
| 740 | - | return append(out, "TERM=xterm-direct", "VEV=session="+name+",tab="+tabStableID+",pane="+paneStableID) | |
| 732 | + | return append(out, "TERM=xterm-direct", "VEV=session="+escapeVEVComponent(name)+",tab="+tabStableID+",pane="+paneStableID) | |
| 741 | 733 | } | |
| @@ -123,13 +123,9 @@ func (d *Daemon) touchMRU(sess *session) { | |||||
| 123 | 123 | ||||
| 124 | func (d *Daemon) createSessionLocked(name string, ephemeral bool, cwd string, sz domain.Size) (*session, error) { | 124 | func (d *Daemon) createSessionLocked(name string, ephemeral bool, cwd string, sz domain.Size) (*session, error) { | ||
| 125 | tbSize := tabSize(sz) | 125 | tbSize := tabSize(sz) | ||
| 126 | - | tabStableID, err := newStableID("t") | 126 | + | tabStableID, paneStableID, err := d.newTabPaneStableIDs() |
| 127 | if err != nil { | 127 | if err != nil { | ||
| 128 | - | return nil, fmt.Errorf("daemon: generating tab identity: %w", err) | 128 | + | return nil, err |
| 129 | - | } | |||
| 130 | - | paneStableID, err := newStableID("p") | |||
| 131 | - | if err != nil { | |||
| 132 | - | return nil, fmt.Errorf("daemon: generating pane identity: %w", err) | |||
| 133 | } | 129 | } | ||
| 134 | pty, err := d.ptys.Open(d.shell, d.shellArgs, d.childEnv(name, tabStableID, paneStableID), cwd, tbSize) | 130 | pty, err := d.ptys.Open(d.shell, d.shellArgs, d.childEnv(name, tabStableID, paneStableID), cwd, tbSize) | ||
| 135 | if err != nil { | 131 | if err != nil { | ||
| @@ -243,13 +239,9 @@ func (d *Daemon) createTab(sess *session, sz domain.Size) error { | |||||
| 243 | cwd := sess.cwd | 239 | cwd := sess.cwd | ||
| 244 | client := sess.client | 240 | client := sess.client | ||
| 245 | sess.mu.Unlock() | 241 | sess.mu.Unlock() | ||
| 246 | - | tabStableID, err := newStableID("t") | 242 | + | tabStableID, paneStableID, err := d.newTabPaneStableIDs() |
| 247 | if err != nil { | 243 | if err != nil { | ||
| 248 | - | return fmt.Errorf("daemon: generating tab identity: %w", err) | 244 | + | return err |
| 249 | - | } | |||
| 250 | - | paneStableID, err := newStableID("p") | |||
| 251 | - | if err != nil { | |||
| 252 | - | return fmt.Errorf("daemon: generating pane identity: %w", err) | |||
| 253 | } | 245 | } | ||
| 254 | pty, err := d.ptys.Open(d.shell, d.shellArgs, d.childEnv(name, tabStableID, paneStableID), cwd, tbSize) | 246 | pty, err := d.ptys.Open(d.shell, d.shellArgs, d.childEnv(name, tabStableID, paneStableID), cwd, tbSize) | ||
| 255 | if err != nil { | 247 | if err != nil { | ||
| @@ -737,5 +729,5 @@ func (d *Daemon) childEnv(name, tabStableID, paneStableID string) []string { | |||||
| 737 | } | 729 | } | ||
| 738 | out = append(out, e) | 730 | out = append(out, e) | ||
| 739 | } | 731 | } | ||
| 740 | - | return append(out, "TERM=xterm-direct", "VEV=session="+name+",tab="+tabStableID+",pane="+paneStableID) | 732 | + | return append(out, "TERM=xterm-direct", "VEV=session="+escapeVEVComponent(name)+",tab="+tabStableID+",pane="+paneStableID) |
| 741 | } | 733 | } | ||
M internal/usecase/daemon/session_test.go +8 -0
| @@ -893,6 +893,14 @@ func TestPickerStoppedTargetKillPurges(t *testing.T) { | |||
| 893 | 893 | require.False(t, ok) | |
| 894 | 894 | } | |
| 895 | 895 | ||
| 896 | + | func TestChildEnvEscapesLegacySessionName(t *testing.T) { | |
| 897 | + | d := newTestDaemon(t, nil, stubClock{}) | |
| 898 | + | ||
| 899 | + | got := d.childEnv("legacy,name=value", "t_alpha", "p_beta") | |
| 900 | + | ||
| 901 | + | require.Contains(t, got, "VEV=session=legacy%2Cname%3Dvalue,tab=t_alpha,pane=p_beta") | |
| 902 | + | } | |
| 903 | + | ||
| 896 | 904 | func TestNewSessionAssignsStableIDsAndChildEnv(t *testing.T) { | |
| 897 | 905 | sz := domain.Size{Cols: 80, Rows: 24} | |
| 898 | 906 | p, releasePTY := newBlockingPTY(t) | |
| @@ -893,6 +893,14 @@ func TestPickerStoppedTargetKillPurges(t *testing.T) { | |||||
| 893 | require.False(t, ok) | 893 | require.False(t, ok) | ||
| 894 | } | 894 | } | ||
| 895 | 895 | ||||
| 896 | + | func TestChildEnvEscapesLegacySessionName(t *testing.T) { | |||
| 897 | + | d := newTestDaemon(t, nil, stubClock{}) | |||
| 898 | + | ||||
| 899 | + | got := d.childEnv("legacy,name=value", "t_alpha", "p_beta") | |||
| 900 | + | ||||
| 901 | + | require.Contains(t, got, "VEV=session=legacy%2Cname%3Dvalue,tab=t_alpha,pane=p_beta") | |||
| 902 | + | } | |||
| 903 | + | ||||
| 896 | func TestNewSessionAssignsStableIDsAndChildEnv(t *testing.T) { | 904 | func TestNewSessionAssignsStableIDsAndChildEnv(t *testing.T) { | ||
| 897 | sz := domain.Size{Cols: 80, Rows: 24} | 905 | sz := domain.Size{Cols: 80, Rows: 24} | ||
| 898 | p, releasePTY := newBlockingPTY(t) | 906 | p, releasePTY := newBlockingPTY(t) | ||
M internal/usecase/daemon/snapshot_restore_test.go +14 -0
| @@ -190,6 +190,20 @@ func TestRestoreSnapshotsWritesAgentIDCommandAndWriteFailureIsNonFatal(t *testin | |||
| 190 | 190 | require.NoError(t, d.restoreSession(context.Background(), snapcodec.Session{Name: "agent2", Tabs: []snapcodec.Tab{{Cols: 80, Rows: 24, Tree: layout.NewTree("pane-1"), Panes: []snapcodec.Pane{{ID: "pane-1", Cwd: "/tmp", Process: proc}}}}})) | |
| 191 | 191 | } | |
| 192 | 192 | ||
| 193 | + | func TestRestoreSnapshotsRejectsAgentIDWithControlBytes(t *testing.T) { | |
| 194 | + | proc := &snapcodec.Process{Argv: []string{"pi"}, Strategy: processStrategyPi, Opts: snapcodec.ProcessOpts{AgentSessionID: "abc\nmalicious"}} | |
| 195 | + | store := processRestoreTestStore(t, "agent-control", proc) | |
| 196 | + | factory := &restorePTYFactory{} | |
| 197 | + | d := newTestDaemon(t, factory, stubClock{}) | |
| 198 | + | WithSnapshotStore(store)(d) | |
| 199 | + | d.ApplyConfig(domain.Config{Snapshot: domain.SnapshotConfig{RestoreProcessesSet: true, RestoreProcesses: []string{"pi"}}}) | |
| 200 | + | ||
| 201 | + | d.restoreSnapshots(context.Background()) | |
| 202 | + | ||
| 203 | + | require.Len(t, factory.opens, 1) | |
| 204 | + | require.Empty(t, factory.opens[0].pty.writes) | |
| 205 | + | } | |
| 206 | + | ||
| 193 | 207 | func TestRestoreSnapshotsOpensCollapsedStackPanesWithValidPTYSize(t *testing.T) { | |
| 194 | 208 | store := &restoreSnapshotStore{blobs: []ports.SnapshotBlob{{Name: "stacked", Data: mustSnapshotBytes(t, snapcodec.Session{ | |
| 195 | 209 | Name: "stacked", | |
| @@ -190,6 +190,20 @@ func TestRestoreSnapshotsWritesAgentIDCommandAndWriteFailureIsNonFatal(t *testin | |||||
| 190 | require.NoError(t, d.restoreSession(context.Background(), snapcodec.Session{Name: "agent2", Tabs: []snapcodec.Tab{{Cols: 80, Rows: 24, Tree: layout.NewTree("pane-1"), Panes: []snapcodec.Pane{{ID: "pane-1", Cwd: "/tmp", Process: proc}}}}})) | 190 | require.NoError(t, d.restoreSession(context.Background(), snapcodec.Session{Name: "agent2", Tabs: []snapcodec.Tab{{Cols: 80, Rows: 24, Tree: layout.NewTree("pane-1"), Panes: []snapcodec.Pane{{ID: "pane-1", Cwd: "/tmp", Process: proc}}}}})) | ||
| 191 | } | 191 | } | ||
| 192 | 192 | ||||
| 193 | + | func TestRestoreSnapshotsRejectsAgentIDWithControlBytes(t *testing.T) { | |||
| 194 | + | proc := &snapcodec.Process{Argv: []string{"pi"}, Strategy: processStrategyPi, Opts: snapcodec.ProcessOpts{AgentSessionID: "abc\nmalicious"}} | |||
| 195 | + | store := processRestoreTestStore(t, "agent-control", proc) | |||
| 196 | + | factory := &restorePTYFactory{} | |||
| 197 | + | d := newTestDaemon(t, factory, stubClock{}) | |||
| 198 | + | WithSnapshotStore(store)(d) | |||
| 199 | + | d.ApplyConfig(domain.Config{Snapshot: domain.SnapshotConfig{RestoreProcessesSet: true, RestoreProcesses: []string{"pi"}}}) | |||
| 200 | + | ||||
| 201 | + | d.restoreSnapshots(context.Background()) | |||
| 202 | + | ||||
| 203 | + | require.Len(t, factory.opens, 1) | |||
| 204 | + | require.Empty(t, factory.opens[0].pty.writes) | |||
| 205 | + | } | |||
| 206 | + | ||||
| 193 | func TestRestoreSnapshotsOpensCollapsedStackPanesWithValidPTYSize(t *testing.T) { | 207 | func TestRestoreSnapshotsOpensCollapsedStackPanesWithValidPTYSize(t *testing.T) { | ||
| 194 | store := &restoreSnapshotStore{blobs: []ports.SnapshotBlob{{Name: "stacked", Data: mustSnapshotBytes(t, snapcodec.Session{ | 208 | store := &restoreSnapshotStore{blobs: []ports.SnapshotBlob{{Name: "stacked", Data: mustSnapshotBytes(t, snapcodec.Session{ | ||
| 195 | Name: "stacked", | 209 | Name: "stacked", | ||