refactor(daemon): hold one ProcessInspector instead of four funcs
- commit
- f04184feb4855026ada30c48ee92c042f3e2b8ad
- author
- brice · · 2026-10-08 06:01 UTC
- committer
- brice · · 2026-10-08 08:12 UTC
- parent
- 7d8f24d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
14 files · +140 -46
M internal/usecase/daemon/bar_refresh.go +3 -3
| @@ -136,13 +136,13 @@ func (d *Daemon) collectBarScriptContext(sess *session, anchor string) (barScrip | |||
| 136 | 136 | pid := 0 | |
| 137 | 137 | if p := tb.focusedPane(); p != nil { | |
| 138 | 138 | ctx.Pane = p.stableID | |
| 139 | - | if d != nil && d.procCwd != nil && p.pty != nil { | |
| 139 | + | if d != nil && d.proc != nil && p.pty != nil { | |
| 140 | 140 | pid = p.pty.Pid() | |
| 141 | 141 | } | |
| 142 | 142 | } | |
| 143 | 143 | tb.mu.Unlock() | |
| 144 | - | if pid != 0 && d != nil && d.procCwd != nil { | |
| 145 | - | if cwd, err := d.procCwd(pid); err == nil && cwd != "" { | |
| 144 | + | if pid != 0 && d != nil && d.proc != nil { | |
| 145 | + | if cwd, err := d.proc.Cwd(pid); err == nil && cwd != "" { | |
| 146 | 146 | ctx.PaneCWD = cwd | |
| 147 | 147 | } | |
| 148 | 148 | } | |
| @@ -136,13 +136,13 @@ func (d *Daemon) collectBarScriptContext(sess *session, anchor string) (barScrip | |||||
| 136 | pid := 0 | 136 | pid := 0 | ||
| 137 | if p := tb.focusedPane(); p != nil { | 137 | if p := tb.focusedPane(); p != nil { | ||
| 138 | ctx.Pane = p.stableID | 138 | ctx.Pane = p.stableID | ||
| 139 | - | if d != nil && d.procCwd != nil && p.pty != nil { | 139 | + | if d != nil && d.proc != nil && p.pty != nil { |
| 140 | pid = p.pty.Pid() | 140 | pid = p.pty.Pid() | ||
| 141 | } | 141 | } | ||
| 142 | } | 142 | } | ||
| 143 | tb.mu.Unlock() | 143 | tb.mu.Unlock() | ||
| 144 | - | if pid != 0 && d != nil && d.procCwd != nil { | 144 | + | if pid != 0 && d != nil && d.proc != nil { |
| 145 | - | if cwd, err := d.procCwd(pid); err == nil && cwd != "" { | 145 | + | if cwd, err := d.proc.Cwd(pid); err == nil && cwd != "" { |
| 146 | ctx.PaneCWD = cwd | 146 | ctx.PaneCWD = cwd | ||
| 147 | } | 147 | } | ||
| 148 | } | 148 | } | ||
M internal/usecase/daemon/bar_refresh_test.go +2 -2
| @@ -96,10 +96,10 @@ func TestBarScriptContextUsesFirstOrderedPaneAndClientCols(t *testing.T) { | |||
| 96 | 96 | active := sess.tabs[0].focusedPane() | |
| 97 | 97 | active.stableID = "pane-default" | |
| 98 | 98 | active.pty = newScriptPTY(nil) | |
| 99 | - | d.procCwd = func(pid int) (string, error) { | |
| 99 | + | WithCwdReader(func(pid int) (string, error) { | |
| 100 | 100 | require.Equal(t, 4242, pid) | |
| 101 | 101 | return "/pane-repo", nil | |
| 102 | - | } | |
| 102 | + | })(d) | |
| 103 | 103 | ||
| 104 | 104 | require.True(t, d.refreshBarScriptsIfDue(sess, time.Unix(0, 0), true)) | |
| 105 | 105 | waitBarRefreshIdle(t, d) | |
| @@ -96,10 +96,10 @@ func TestBarScriptContextUsesFirstOrderedPaneAndClientCols(t *testing.T) { | |||||
| 96 | active := sess.tabs[0].focusedPane() | 96 | active := sess.tabs[0].focusedPane() | ||
| 97 | active.stableID = "pane-default" | 97 | active.stableID = "pane-default" | ||
| 98 | active.pty = newScriptPTY(nil) | 98 | active.pty = newScriptPTY(nil) | ||
| 99 | - | d.procCwd = func(pid int) (string, error) { | 99 | + | WithCwdReader(func(pid int) (string, error) { |
| 100 | require.Equal(t, 4242, pid) | 100 | require.Equal(t, 4242, pid) | ||
| 101 | return "/pane-repo", nil | 101 | return "/pane-repo", nil | ||
| 102 | - | } | 102 | + | })(d) |
| 103 | 103 | ||||
| 104 | require.True(t, d.refreshBarScriptsIfDue(sess, time.Unix(0, 0), true)) | 104 | require.True(t, d.refreshBarScriptsIfDue(sess, time.Unix(0, 0), true)) | ||
| 105 | waitBarRefreshIdle(t, d) | 105 | waitBarRefreshIdle(t, d) | ||
M internal/usecase/daemon/control.go +2 -2
| @@ -964,8 +964,8 @@ func (e controlExec) ListPanes(asJSON bool) (string, error) { | |||
| 964 | 964 | } | |
| 965 | 965 | pane.mu.Unlock() | |
| 966 | 966 | cwd := fallbackCWD | |
| 967 | - | if e.d.procCwd != nil && pid > 0 { | |
| 968 | - | if live, err := e.d.procCwd(pid); err == nil && live != "" { | |
| 967 | + | if e.d.proc != nil && pid > 0 { | |
| 968 | + | if live, err := e.d.proc.Cwd(pid); err == nil && live != "" { | |
| 969 | 969 | cwd = live | |
| 970 | 970 | } | |
| 971 | 971 | } | |
| @@ -964,8 +964,8 @@ func (e controlExec) ListPanes(asJSON bool) (string, error) { | |||||
| 964 | } | 964 | } | ||
| 965 | pane.mu.Unlock() | 965 | pane.mu.Unlock() | ||
| 966 | cwd := fallbackCWD | 966 | cwd := fallbackCWD | ||
| 967 | - | if e.d.procCwd != nil && pid > 0 { | 967 | + | if e.d.proc != nil && pid > 0 { |
| 968 | - | if live, err := e.d.procCwd(pid); err == nil && live != "" { | 968 | + | if live, err := e.d.proc.Cwd(pid); err == nil && live != "" { |
| 969 | cwd = live | 969 | cwd = live | ||
| 970 | } | 970 | } | ||
| 971 | } | 971 | } | ||
M internal/usecase/daemon/control_test.go +2 -2
| @@ -868,10 +868,10 @@ func TestRemoteCatalogRefreshesFocusedTabTitle(t *testing.T) { | |||
| 868 | 868 | pty := portsmocks.NewMockPTY(t) | |
| 869 | 869 | pty.EXPECT().ForegroundPgid().Return(1234, nil).Once() | |
| 870 | 870 | p.pty = pty | |
| 871 | - | d.procComm = func(pid int) (string, error) { | |
| 871 | + | d.proc = &processInspectorFake{comm: func(pid int) (string, error) { | |
| 872 | 872 | require.Equal(t, 1234, pid) | |
| 873 | 873 | return "fish", nil | |
| 874 | - | } | |
| 874 | + | }} | |
| 875 | 875 | ||
| 876 | 876 | result := sendCommand(t, d, protocol.CommandRequest{Slug: "remote-catalog", JSON: true}) | |
| 877 | 877 | require.True(t, result.Outcome == protocol.CommandSucceeded, result.Text) | |
| @@ -868,10 +868,10 @@ func TestRemoteCatalogRefreshesFocusedTabTitle(t *testing.T) { | |||||
| 868 | pty := portsmocks.NewMockPTY(t) | 868 | pty := portsmocks.NewMockPTY(t) | ||
| 869 | pty.EXPECT().ForegroundPgid().Return(1234, nil).Once() | 869 | pty.EXPECT().ForegroundPgid().Return(1234, nil).Once() | ||
| 870 | p.pty = pty | 870 | p.pty = pty | ||
| 871 | - | d.procComm = func(pid int) (string, error) { | 871 | + | d.proc = &processInspectorFake{comm: func(pid int) (string, error) { |
| 872 | require.Equal(t, 1234, pid) | 872 | require.Equal(t, 1234, pid) | ||
| 873 | return "fish", nil | 873 | return "fish", nil | ||
| 874 | - | } | 874 | + | }} |
| 875 | 875 | ||||
| 876 | result := sendCommand(t, d, protocol.CommandRequest{Slug: "remote-catalog", JSON: true}) | 876 | result := sendCommand(t, d, protocol.CommandRequest{Slug: "remote-catalog", JSON: true}) | ||
| 877 | require.True(t, result.Outcome == protocol.CommandSucceeded, result.Text) | 877 | require.True(t, result.Outcome == protocol.CommandSucceeded, result.Text) | ||
M internal/usecase/daemon/daemon.go +8 -10
| @@ -225,11 +225,10 @@ type Daemon struct { | |||
| 225 | 225 | shutdownNoticedSessions map[string]struct{} | |
| 226 | 226 | restoreDone chan struct{} | |
| 227 | 227 | restoreOnce sync.Once | |
| 228 | - | procCwd func(int) (string, error) | |
| 229 | - | procComm func(int) (string, error) | |
| 230 | - | procArgv func(int) ([]string, error) | |
| 231 | - | procGroupArgv func(int, int) ([]string, error) | |
| 232 | - | dirOrHome func(string) string | |
| 228 | + | // proc inspects pane processes (cwd, foreground command, argv). Nil means | |
| 229 | + | // no inspection is available; see WithProcessInspector and WithCwdReader. | |
| 230 | + | proc ports.ProcessInspector | |
| 231 | + | dirOrHome func(string) string | |
| 233 | 232 | // bindings is read by keys.Router through its own atomic pointer, so it is | |
| 234 | 233 | // published next to (not inside) the runtime config snapshot. | |
| 235 | 234 | bindings atomic.Pointer[keys.Bindings] | |
| @@ -567,10 +566,12 @@ func WithSnapshotGarbageCollection() Option { | |||
| 567 | 566 | } | |
| 568 | 567 | ||
| 569 | 568 | // WithCwdReader overrides the process cwd reader used for persistence tests. | |
| 569 | + | // Every other inspection keeps using the installed process inspector, or stays | |
| 570 | + | // unavailable when none is installed. | |
| 570 | 571 | func WithCwdReader(fn func(int) (string, error)) Option { | |
| 571 | 572 | return func(d *Daemon) { | |
| 572 | 573 | if fn != nil { | |
| 573 | - | d.procCwd = fn | |
| 574 | + | d.proc = cwdOverrideInspector{base: unwrapCwdOverride(d.proc), cwd: fn} | |
| 574 | 575 | } | |
| 575 | 576 | } | |
| 576 | 577 | } | |
| @@ -581,10 +582,7 @@ func WithProcessInspector(ins ports.ProcessInspector) Option { | |||
| 581 | 582 | if ins == nil { | |
| 582 | 583 | return | |
| 583 | 584 | } | |
| 584 | - | d.procCwd = ins.Cwd | |
| 585 | - | d.procComm = ins.Comm | |
| 586 | - | d.procArgv = ins.Argv | |
| 587 | - | d.procGroupArgv = ins.GroupArgv | |
| 585 | + | d.proc = ins | |
| 588 | 586 | } | |
| 589 | 587 | } | |
| 590 | 588 | ||
| @@ -225,11 +225,10 @@ type Daemon struct { | |||||
| 225 | shutdownNoticedSessions map[string]struct{} | 225 | shutdownNoticedSessions map[string]struct{} | ||
| 226 | restoreDone chan struct{} | 226 | restoreDone chan struct{} | ||
| 227 | restoreOnce sync.Once | 227 | restoreOnce sync.Once | ||
| 228 | - | procCwd func(int) (string, error) | 228 | + | // proc inspects pane processes (cwd, foreground command, argv). Nil means |
| 229 | - | procComm func(int) (string, error) | 229 | + | // no inspection is available; see WithProcessInspector and WithCwdReader. |
| 230 | - | procArgv func(int) ([]string, error) | 230 | + | proc ports.ProcessInspector |
| 231 | - | procGroupArgv func(int, int) ([]string, error) | 231 | + | dirOrHome func(string) string |
| 232 | - | dirOrHome func(string) string | |||
| 233 | // bindings is read by keys.Router through its own atomic pointer, so it is | 232 | // bindings is read by keys.Router through its own atomic pointer, so it is | ||
| 234 | // published next to (not inside) the runtime config snapshot. | 233 | // published next to (not inside) the runtime config snapshot. | ||
| 235 | bindings atomic.Pointer[keys.Bindings] | 234 | bindings atomic.Pointer[keys.Bindings] | ||
| @@ -567,10 +566,12 @@ func WithSnapshotGarbageCollection() Option { | |||||
| 567 | } | 566 | } | ||
| 568 | 567 | ||||
| 569 | // WithCwdReader overrides the process cwd reader used for persistence tests. | 568 | // WithCwdReader overrides the process cwd reader used for persistence tests. | ||
| 569 | + | // Every other inspection keeps using the installed process inspector, or stays | |||
| 570 | + | // unavailable when none is installed. | |||
| 570 | func WithCwdReader(fn func(int) (string, error)) Option { | 571 | func WithCwdReader(fn func(int) (string, error)) Option { | ||
| 571 | return func(d *Daemon) { | 572 | return func(d *Daemon) { | ||
| 572 | if fn != nil { | 573 | if fn != nil { | ||
| 573 | - | d.procCwd = fn | 574 | + | d.proc = cwdOverrideInspector{base: unwrapCwdOverride(d.proc), cwd: fn} |
| 574 | } | 575 | } | ||
| 575 | } | 576 | } | ||
| 576 | } | 577 | } | ||
| @@ -581,10 +582,7 @@ func WithProcessInspector(ins ports.ProcessInspector) Option { | |||||
| 581 | if ins == nil { | 582 | if ins == nil { | ||
| 582 | return | 583 | return | ||
| 583 | } | 584 | } | ||
| 584 | - | d.procCwd = ins.Cwd | 585 | + | d.proc = ins |
| 585 | - | d.procComm = ins.Comm | |||
| 586 | - | d.procArgv = ins.Argv | |||
| 587 | - | d.procGroupArgv = ins.GroupArgv | |||
| 588 | } | 586 | } | ||
| 589 | } | 587 | } | ||
| 590 | 588 | ||||
M internal/usecase/daemon/floating.go +2 -2
| @@ -361,8 +361,8 @@ func (d *Daemon) newFloatingLaunchSpec(sess *session, tb *tab, cfg domain.Floati | |||
| 361 | 361 | focused := tb.focusedPane() | |
| 362 | 362 | tabStableID, tabCtx := tb.stableID, tb.ctx | |
| 363 | 363 | tb.mu.Unlock() | |
| 364 | - | if focused != nil && d.procCwd != nil && focused.pty != nil { | |
| 365 | - | if live, err := d.procCwd(focused.pty.Pid()); err == nil && live != "" { | |
| 364 | + | if focused != nil && d.proc != nil && focused.pty != nil { | |
| 365 | + | if live, err := d.proc.Cwd(focused.pty.Pid()); err == nil && live != "" { | |
| 366 | 366 | cwd = live | |
| 367 | 367 | } | |
| 368 | 368 | } | |
| @@ -361,8 +361,8 @@ func (d *Daemon) newFloatingLaunchSpec(sess *session, tb *tab, cfg domain.Floati | |||||
| 361 | focused := tb.focusedPane() | 361 | focused := tb.focusedPane() | ||
| 362 | tabStableID, tabCtx := tb.stableID, tb.ctx | 362 | tabStableID, tabCtx := tb.stableID, tb.ctx | ||
| 363 | tb.mu.Unlock() | 363 | tb.mu.Unlock() | ||
| 364 | - | if focused != nil && d.procCwd != nil && focused.pty != nil { | 364 | + | if focused != nil && d.proc != nil && focused.pty != nil { |
| 365 | - | if live, err := d.procCwd(focused.pty.Pid()); err == nil && live != "" { | 365 | + | if live, err := d.proc.Cwd(focused.pty.Pid()); err == nil && live != "" { |
| 366 | cwd = live | 366 | cwd = live | ||
| 367 | } | 367 | } | ||
| 368 | } | 368 | } | ||
M internal/usecase/daemon/floating_spare_test.go +1 -1
| @@ -124,7 +124,7 @@ func TestFloatingSpareIsSharedPerSession(t *testing.T) { | |||
| 124 | 124 | d := newTestDaemon(t, factory, stubClock{}) | |
| 125 | 125 | d.ApplyConfig(domain.Config{Floating: cfg}) | |
| 126 | 126 | cwds := map[int]string{1: "/work", 2: tt.claimCwd} | |
| 127 | - | d.procCwd = func(pid int) (string, error) { return cwds[pid], nil } | |
| 127 | + | WithCwdReader(func(pid int) (string, error) { return cwds[pid], nil })(d) | |
| 128 | 128 | d.dirOrHome = func(cwd string) string { return cwd } | |
| 129 | 129 | ||
| 130 | 130 | first := newTab(&pidPTY{pid: 1}, domain.Size{Cols: 120, Rows: 40}) | |
| @@ -124,7 +124,7 @@ func TestFloatingSpareIsSharedPerSession(t *testing.T) { | |||||
| 124 | d := newTestDaemon(t, factory, stubClock{}) | 124 | d := newTestDaemon(t, factory, stubClock{}) | ||
| 125 | d.ApplyConfig(domain.Config{Floating: cfg}) | 125 | d.ApplyConfig(domain.Config{Floating: cfg}) | ||
| 126 | cwds := map[int]string{1: "/work", 2: tt.claimCwd} | 126 | cwds := map[int]string{1: "/work", 2: tt.claimCwd} | ||
| 127 | - | d.procCwd = func(pid int) (string, error) { return cwds[pid], nil } | 127 | + | WithCwdReader(func(pid int) (string, error) { return cwds[pid], nil })(d) |
| 128 | d.dirOrHome = func(cwd string) string { return cwd } | 128 | d.dirOrHome = func(cwd string) string { return cwd } | ||
| 129 | 129 | ||||
| 130 | first := newTab(&pidPTY{pid: 1}, domain.Size{Cols: 120, Rows: 40}) | 130 | first := newTab(&pidPTY{pid: 1}, domain.Size{Cols: 120, Rows: 40}) | ||
M internal/usecase/daemon/floating_test.go +1 -1
| @@ -627,7 +627,7 @@ func TestFloatingLaunchUsesLiveOrValidatedSessionCwd(t *testing.T) { | |||
| 627 | 627 | return cwd | |
| 628 | 628 | } | |
| 629 | 629 | if tt.liveCwd != "" { | |
| 630 | - | d.procCwd = func(int) (string, error) { return tt.liveCwd, nil } | |
| 630 | + | WithCwdReader(func(int) (string, error) { return tt.liveCwd, nil })(d) | |
| 631 | 631 | } | |
| 632 | 632 | tb := newFloatingTestTab(t) | |
| 633 | 633 | sess := &session{sessionCore: sessionCore{name: "work"}, cwd: tt.sessionCwd, tabs: []*tab{tb}, ctx: t.Context()} | |
| @@ -627,7 +627,7 @@ func TestFloatingLaunchUsesLiveOrValidatedSessionCwd(t *testing.T) { | |||||
| 627 | return cwd | 627 | return cwd | ||
| 628 | } | 628 | } | ||
| 629 | if tt.liveCwd != "" { | 629 | if tt.liveCwd != "" { | ||
| 630 | - | d.procCwd = func(int) (string, error) { return tt.liveCwd, nil } | 630 | + | WithCwdReader(func(int) (string, error) { return tt.liveCwd, nil })(d) |
| 631 | } | 631 | } | ||
| 632 | tb := newFloatingTestTab(t) | 632 | tb := newFloatingTestTab(t) | ||
| 633 | sess := &session{sessionCore: sessionCore{name: "work"}, cwd: tt.sessionCwd, tabs: []*tab{tb}, ctx: t.Context()} | 633 | sess := &session{sessionCore: sessionCore{name: "work"}, cwd: tt.sessionCwd, tabs: []*tab{tb}, ctx: t.Context()} | ||
M internal/usecase/daemon/input_test.go +6 -6
| @@ -1262,7 +1262,7 @@ func TestMouseWheelOverUnfocusedPaneDoesNotFocusAndForwardsChildMouse(t *testing | |||
| 1262 | 1262 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 5}}).Return(nil).Maybe() | |
| 1263 | 1263 | p2.EXPECT().Write([]byte("\x1b[<64;1;1M")).Return(len("\x1b[<64;1;1M"), nil).Once() | |
| 1264 | 1264 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | |
| 1265 | - | d.procComm = nil | |
| 1265 | + | d.proc = nil | |
| 1266 | 1266 | tb := testAttachmentTab(sess) | |
| 1267 | 1267 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | |
| 1268 | 1268 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | |
| @@ -1444,7 +1444,7 @@ func TestMouseDividerAndTitleBarDoNotForwardBogusCoordinates(t *testing.T) { | |||
| 1444 | 1444 | p2 := portsmocks.NewMockPTY(t) | |
| 1445 | 1445 | p2.EXPECT().Resize(mock.Anything).Return(nil).Maybe() | |
| 1446 | 1446 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | |
| 1447 | - | d.procComm = nil | |
| 1447 | + | d.proc = nil | |
| 1448 | 1448 | tb := testAttachmentTab(sess) | |
| 1449 | 1449 | tb.focusedPane().screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | |
| 1450 | 1450 | tb.mu.Lock() | |
| @@ -1467,7 +1467,7 @@ func TestCopyModeDragOutsideSplitPaneClampsToPaneContent(t *testing.T) { | |||
| 1467 | 1467 | p2 := portsmocks.NewMockPTY(t) | |
| 1468 | 1468 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 10}}).Return(nil).Maybe() | |
| 1469 | 1469 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | |
| 1470 | - | d.procComm = nil | |
| 1470 | + | d.proc = nil | |
| 1471 | 1471 | tb := testAttachmentTab(sess) | |
| 1472 | 1472 | for i := range 10 { | |
| 1473 | 1473 | writeTestRow(tb.focusedPane().screen, i, string(rune('a'+i))) | |
| @@ -1501,7 +1501,7 @@ func TestMouseHitTestFocusesPaneAndTranslatesSGRColumns(t *testing.T) { | |||
| 1501 | 1501 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 5}}).Return(nil).Maybe() | |
| 1502 | 1502 | p2.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | |
| 1503 | 1503 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | |
| 1504 | - | d.procComm = nil | |
| 1504 | + | d.proc = nil | |
| 1505 | 1505 | tb := testAttachmentTab(sess) | |
| 1506 | 1506 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | |
| 1507 | 1507 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | |
| @@ -1528,7 +1528,7 @@ func TestMouseGatedWhileNoticesOverlayActive(t *testing.T) { | |||
| 1528 | 1528 | p2 := portsmocks.NewMockPTY(t) | |
| 1529 | 1529 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 5}}).Return(nil).Maybe() | |
| 1530 | 1530 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | |
| 1531 | - | d.procComm = nil | |
| 1531 | + | d.proc = nil | |
| 1532 | 1532 | tb := testAttachmentTab(sess) | |
| 1533 | 1533 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | |
| 1534 | 1534 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | |
| @@ -1558,7 +1558,7 @@ func TestMouseCollapsedStackBarExpandsAndFocuses(t *testing.T) { | |||
| 1558 | 1558 | p2 := portsmocks.NewMockPTY(t) | |
| 1559 | 1559 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 4}}).Return(nil).Maybe() | |
| 1560 | 1560 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | |
| 1561 | - | d.procComm = nil | |
| 1561 | + | d.proc = nil | |
| 1562 | 1562 | tb := testAttachmentTab(sess) | |
| 1563 | 1563 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 3}) | |
| 1564 | 1564 | tb.mu.Lock() | |
| @@ -1262,7 +1262,7 @@ func TestMouseWheelOverUnfocusedPaneDoesNotFocusAndForwardsChildMouse(t *testing | |||||
| 1262 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 5}}).Return(nil).Maybe() | 1262 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 5}}).Return(nil).Maybe() | ||
| 1263 | p2.EXPECT().Write([]byte("\x1b[<64;1;1M")).Return(len("\x1b[<64;1;1M"), nil).Once() | 1263 | p2.EXPECT().Write([]byte("\x1b[<64;1;1M")).Return(len("\x1b[<64;1;1M"), nil).Once() | ||
| 1264 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | 1264 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | ||
| 1265 | - | d.procComm = nil | 1265 | + | d.proc = nil |
| 1266 | tb := testAttachmentTab(sess) | 1266 | tb := testAttachmentTab(sess) | ||
| 1267 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | 1267 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | ||
| 1268 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | 1268 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | ||
| @@ -1444,7 +1444,7 @@ func TestMouseDividerAndTitleBarDoNotForwardBogusCoordinates(t *testing.T) { | |||||
| 1444 | p2 := portsmocks.NewMockPTY(t) | 1444 | p2 := portsmocks.NewMockPTY(t) | ||
| 1445 | p2.EXPECT().Resize(mock.Anything).Return(nil).Maybe() | 1445 | p2.EXPECT().Resize(mock.Anything).Return(nil).Maybe() | ||
| 1446 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | 1446 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | ||
| 1447 | - | d.procComm = nil | 1447 | + | d.proc = nil |
| 1448 | tb := testAttachmentTab(sess) | 1448 | tb := testAttachmentTab(sess) | ||
| 1449 | tb.focusedPane().screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | 1449 | tb.focusedPane().screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | ||
| 1450 | tb.mu.Lock() | 1450 | tb.mu.Lock() | ||
| @@ -1467,7 +1467,7 @@ func TestCopyModeDragOutsideSplitPaneClampsToPaneContent(t *testing.T) { | |||||
| 1467 | p2 := portsmocks.NewMockPTY(t) | 1467 | p2 := portsmocks.NewMockPTY(t) | ||
| 1468 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 10}}).Return(nil).Maybe() | 1468 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 10}}).Return(nil).Maybe() | ||
| 1469 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | 1469 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | ||
| 1470 | - | d.procComm = nil | 1470 | + | d.proc = nil |
| 1471 | tb := testAttachmentTab(sess) | 1471 | tb := testAttachmentTab(sess) | ||
| 1472 | for i := range 10 { | 1472 | for i := range 10 { | ||
| 1473 | writeTestRow(tb.focusedPane().screen, i, string(rune('a'+i))) | 1473 | writeTestRow(tb.focusedPane().screen, i, string(rune('a'+i))) | ||
| @@ -1501,7 +1501,7 @@ func TestMouseHitTestFocusesPaneAndTranslatesSGRColumns(t *testing.T) { | |||||
| 1501 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 5}}).Return(nil).Maybe() | 1501 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 5}}).Return(nil).Maybe() | ||
| 1502 | p2.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | 1502 | p2.EXPECT().Write([]byte("\x1b[<0;1;1M")).Return(len("\x1b[<0;1;1M"), nil).Once() | ||
| 1503 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | 1503 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | ||
| 1504 | - | d.procComm = nil | 1504 | + | d.proc = nil |
| 1505 | tb := testAttachmentTab(sess) | 1505 | tb := testAttachmentTab(sess) | ||
| 1506 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | 1506 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | ||
| 1507 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | 1507 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | ||
| @@ -1528,7 +1528,7 @@ func TestMouseGatedWhileNoticesOverlayActive(t *testing.T) { | |||||
| 1528 | p2 := portsmocks.NewMockPTY(t) | 1528 | p2 := portsmocks.NewMockPTY(t) | ||
| 1529 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 5}}).Return(nil).Maybe() | 1529 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 5}}).Return(nil).Maybe() | ||
| 1530 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | 1530 | d, sess, ac, sends := newManualSessionWithPTYs(t, p1) | ||
| 1531 | - | d.procComm = nil | 1531 | + | d.proc = nil |
| 1532 | tb := testAttachmentTab(sess) | 1532 | tb := testAttachmentTab(sess) | ||
| 1533 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | 1533 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 5}) | ||
| 1534 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | 1534 | p2pane.screen.Write([]byte("\x1b[?1000h\x1b[?1006h")) | ||
| @@ -1558,7 +1558,7 @@ func TestMouseCollapsedStackBarExpandsAndFocuses(t *testing.T) { | |||||
| 1558 | p2 := portsmocks.NewMockPTY(t) | 1558 | p2 := portsmocks.NewMockPTY(t) | ||
| 1559 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 4}}).Return(nil).Maybe() | 1559 | p2.EXPECT().Resize(domain.Geometry{Size: domain.Size{Cols: 20, Rows: 4}}).Return(nil).Maybe() | ||
| 1560 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | 1560 | d, sess, ac, _ := newManualSessionWithPTYs(t, p1) | ||
| 1561 | - | d.procComm = nil | 1561 | + | d.proc = nil |
| 1562 | tb := testAttachmentTab(sess) | 1562 | tb := testAttachmentTab(sess) | ||
| 1563 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 3}) | 1563 | p2pane := newPane("pane-2", p2, domain.Size{Cols: 20, Rows: 3}) | ||
| 1564 | tb.mu.Lock() | 1564 | tb.mu.Lock() | ||
M internal/usecase/daemon/pane_titles.go +2 -2
| @@ -187,9 +187,9 @@ func (d *Daemon) refreshPaneDisplayTitle(_ *session, p *pane, force bool) string | |||
| 187 | 187 | // distinct from the display fallback: an OSC title must then stand alone, | |
| 188 | 188 | // and future floating panes can supply their command fallback at format time. | |
| 189 | 189 | processName := "" | |
| 190 | - | if d.procComm != nil && p.pty != nil { | |
| 190 | + | if d.hasProcessDetails() && p.pty != nil { | |
| 191 | 191 | if pgid, err := p.pty.ForegroundPgid(); err == nil && pgid > 0 { | |
| 192 | - | if comm, err := d.procComm(pgid); err == nil && strings.TrimSpace(comm) != "" { | |
| 192 | + | if comm, err := d.proc.Comm(pgid); err == nil && strings.TrimSpace(comm) != "" { | |
| 193 | 193 | processName = strings.TrimSpace(comm) | |
| 194 | 194 | } | |
| 195 | 195 | } | |
| @@ -187,9 +187,9 @@ func (d *Daemon) refreshPaneDisplayTitle(_ *session, p *pane, force bool) string | |||||
| 187 | // distinct from the display fallback: an OSC title must then stand alone, | 187 | // distinct from the display fallback: an OSC title must then stand alone, | ||
| 188 | // and future floating panes can supply their command fallback at format time. | 188 | // and future floating panes can supply their command fallback at format time. | ||
| 189 | processName := "" | 189 | processName := "" | ||
| 190 | - | if d.procComm != nil && p.pty != nil { | 190 | + | if d.hasProcessDetails() && p.pty != nil { |
| 191 | if pgid, err := p.pty.ForegroundPgid(); err == nil && pgid > 0 { | 191 | if pgid, err := p.pty.ForegroundPgid(); err == nil && pgid > 0 { | ||
| 192 | - | if comm, err := d.procComm(pgid); err == nil && strings.TrimSpace(comm) != "" { | 192 | + | if comm, err := d.proc.Comm(pgid); err == nil && strings.TrimSpace(comm) != "" { |
| 193 | processName = strings.TrimSpace(comm) | 193 | processName = strings.TrimSpace(comm) | ||
| 194 | } | 194 | } | ||
| 195 | } | 195 | } | ||
M internal/usecase/daemon/pane_titles_test.go +46 -8
| @@ -71,10 +71,10 @@ func TestRefreshPaneTitleUsesForegroundProcessComm(t *testing.T) { | |||
| 71 | 71 | clk.EXPECT().Now().Return(time.Time{}).Maybe() | |
| 72 | 72 | d := newTestDaemon(t, nil, clk) | |
| 73 | 73 | d.shell = "/bin/zsh" | |
| 74 | - | d.procComm = func(pid int) (string, error) { | |
| 74 | + | d.proc = &processInspectorFake{comm: func(pid int) (string, error) { | |
| 75 | 75 | require.Equal(t, 1234, pid) | |
| 76 | 76 | return "vim\n", nil | |
| 77 | - | } | |
| 77 | + | }} | |
| 78 | 78 | ||
| 79 | 79 | title := d.refreshPaneTitle(sess, "pane-1") | |
| 80 | 80 | require.Equal(t, "vim", title) | |
| @@ -99,10 +99,10 @@ func TestRefreshPaneTitleUsesProvidedOwningTab(t *testing.T) { | |||
| 99 | 99 | sess.tabs = append(sess.tabs, second) | |
| 100 | 100 | sess.mu.Unlock() | |
| 101 | 101 | d := newTestDaemon(t, nil, stubClock{}) | |
| 102 | - | d.procComm = func(pid int) (string, error) { | |
| 102 | + | d.proc = &processInspectorFake{comm: func(pid int) (string, error) { | |
| 103 | 103 | require.Equal(t, 222, pid) | |
| 104 | 104 | return "vim", nil | |
| 105 | - | } | |
| 105 | + | }} | |
| 106 | 106 | ||
| 107 | 107 | require.Equal(t, "vim", d.refreshPaneTitle(sess, "pane-1", second)) | |
| 108 | 108 | secondPane := second.panes["pane-1"] | |
| @@ -120,11 +120,11 @@ func TestRefreshPaneTitleCachesByTTLAndRefreshesOnFocus(t *testing.T) { | |||
| 120 | 120 | clk.EXPECT().Now().RunAndReturn(func() time.Time { return now }).Maybe() | |
| 121 | 121 | d := newTestDaemon(t, nil, clk) | |
| 122 | 122 | var calls atomic.Int32 | |
| 123 | - | d.procComm = func(pid int) (string, error) { | |
| 123 | + | d.proc = &processInspectorFake{comm: func(pid int) (string, error) { | |
| 124 | 124 | require.Equal(t, 1234, pid) | |
| 125 | 125 | calls.Add(1) | |
| 126 | 126 | return "vim\n", nil | |
| 127 | - | } | |
| 127 | + | }} | |
| 128 | 128 | ||
| 129 | 129 | require.Equal(t, "vim", d.refreshPaneTitle(sess, "pane-1")) | |
| 130 | 130 | now = now.Add(paneTitleCacheTTL / 2) | |
| @@ -141,7 +141,7 @@ func TestRefreshFloatingPaneTitlePreservesConfiguredCommandFallback(t *testing.T | |||
| 141 | 141 | clk.EXPECT().Now().Return(time.Time{}).Maybe() | |
| 142 | 142 | d := newTestDaemon(t, nil, clk) | |
| 143 | 143 | d.shell = "/usr/bin/fish" | |
| 144 | - | d.procComm = func(int) (string, error) { return "", errors.New("unused") } | |
| 144 | + | d.proc = &processInspectorFake{comm: func(int) (string, error) { return "", errors.New("unused") }} | |
| 145 | 145 | p := newPane("floating", pty, domain.Size{Cols: 20, Rows: 5}) | |
| 146 | 146 | p.title.displayFallback = floatingCommandFallback("btop --utf", d.shell) | |
| 147 | 147 | ||
| @@ -186,7 +186,7 @@ func TestRefreshPaneTitleLookupFailureKeepsProcessNameEmpty(t *testing.T) { | |||
| 186 | 186 | clk.EXPECT().Now().Return(time.Time{}).Maybe() | |
| 187 | 187 | d := newTestDaemon(t, nil, clk) | |
| 188 | 188 | d.shell = "/usr/bin/fish" | |
| 189 | - | d.procComm = func(int) (string, error) { return "", errors.New("unused") } | |
| 189 | + | d.proc = &processInspectorFake{comm: func(int) (string, error) { return "", errors.New("unused") }} | |
| 190 | 190 | p := testAttachmentTab(sess).focusedPane() | |
| 191 | 191 | ||
| 192 | 192 | p.mu.Lock() | |
| @@ -204,3 +204,41 @@ func TestRefreshPaneTitleLookupFailureKeepsProcessNameEmpty(t *testing.T) { | |||
| 204 | 204 | p.mu.Unlock() | |
| 205 | 205 | require.Equal(t, "sh", d.refreshPaneDisplayTitle(sess, p, true), "pointer refresh must retain its pane-owned fallback") | |
| 206 | 206 | } | |
| 207 | + | ||
| 208 | + | // processInspectorFake is a hand-written ports.ProcessInspector whose | |
| 209 | + | // behavior is set per test through func fields; an unset func reports an | |
| 210 | + | // error, the same as an inspector that cannot read the process. | |
| 211 | + | type processInspectorFake struct { | |
| 212 | + | cwd func(int) (string, error) | |
| 213 | + | comm func(int) (string, error) | |
| 214 | + | argv func(int) ([]string, error) | |
| 215 | + | groupArgv func(int, int) ([]string, error) | |
| 216 | + | } | |
| 217 | + | ||
| 218 | + | func (f *processInspectorFake) Cwd(pid int) (string, error) { | |
| 219 | + | if f.cwd == nil { | |
| 220 | + | return "", errProcessInspectionUnavailable | |
| 221 | + | } | |
| 222 | + | return f.cwd(pid) | |
| 223 | + | } | |
| 224 | + | ||
| 225 | + | func (f *processInspectorFake) Comm(pid int) (string, error) { | |
| 226 | + | if f.comm == nil { | |
| 227 | + | return "", errProcessInspectionUnavailable | |
| 228 | + | } | |
| 229 | + | return f.comm(pid) | |
| 230 | + | } | |
| 231 | + | ||
| 232 | + | func (f *processInspectorFake) Argv(pid int) ([]string, error) { | |
| 233 | + | if f.argv == nil { | |
| 234 | + | return nil, errProcessInspectionUnavailable | |
| 235 | + | } | |
| 236 | + | return f.argv(pid) | |
| 237 | + | } | |
| 238 | + | ||
| 239 | + | func (f *processInspectorFake) GroupArgv(pgid, shellPid int) ([]string, error) { | |
| 240 | + | if f.groupArgv == nil { | |
| 241 | + | return nil, errProcessInspectionUnavailable | |
| 242 | + | } | |
| 243 | + | return f.groupArgv(pgid, shellPid) | |
| 244 | + | } | |
| @@ -71,10 +71,10 @@ func TestRefreshPaneTitleUsesForegroundProcessComm(t *testing.T) { | |||||
| 71 | clk.EXPECT().Now().Return(time.Time{}).Maybe() | 71 | clk.EXPECT().Now().Return(time.Time{}).Maybe() | ||
| 72 | d := newTestDaemon(t, nil, clk) | 72 | d := newTestDaemon(t, nil, clk) | ||
| 73 | d.shell = "/bin/zsh" | 73 | d.shell = "/bin/zsh" | ||
| 74 | - | d.procComm = func(pid int) (string, error) { | 74 | + | d.proc = &processInspectorFake{comm: func(pid int) (string, error) { |
| 75 | require.Equal(t, 1234, pid) | 75 | require.Equal(t, 1234, pid) | ||
| 76 | return "vim\n", nil | 76 | return "vim\n", nil | ||
| 77 | - | } | 77 | + | }} |
| 78 | 78 | ||||
| 79 | title := d.refreshPaneTitle(sess, "pane-1") | 79 | title := d.refreshPaneTitle(sess, "pane-1") | ||
| 80 | require.Equal(t, "vim", title) | 80 | require.Equal(t, "vim", title) | ||
| @@ -99,10 +99,10 @@ func TestRefreshPaneTitleUsesProvidedOwningTab(t *testing.T) { | |||||
| 99 | sess.tabs = append(sess.tabs, second) | 99 | sess.tabs = append(sess.tabs, second) | ||
| 100 | sess.mu.Unlock() | 100 | sess.mu.Unlock() | ||
| 101 | d := newTestDaemon(t, nil, stubClock{}) | 101 | d := newTestDaemon(t, nil, stubClock{}) | ||
| 102 | - | d.procComm = func(pid int) (string, error) { | 102 | + | d.proc = &processInspectorFake{comm: func(pid int) (string, error) { |
| 103 | require.Equal(t, 222, pid) | 103 | require.Equal(t, 222, pid) | ||
| 104 | return "vim", nil | 104 | return "vim", nil | ||
| 105 | - | } | 105 | + | }} |
| 106 | 106 | ||||
| 107 | require.Equal(t, "vim", d.refreshPaneTitle(sess, "pane-1", second)) | 107 | require.Equal(t, "vim", d.refreshPaneTitle(sess, "pane-1", second)) | ||
| 108 | secondPane := second.panes["pane-1"] | 108 | secondPane := second.panes["pane-1"] | ||
| @@ -120,11 +120,11 @@ func TestRefreshPaneTitleCachesByTTLAndRefreshesOnFocus(t *testing.T) { | |||||
| 120 | clk.EXPECT().Now().RunAndReturn(func() time.Time { return now }).Maybe() | 120 | clk.EXPECT().Now().RunAndReturn(func() time.Time { return now }).Maybe() | ||
| 121 | d := newTestDaemon(t, nil, clk) | 121 | d := newTestDaemon(t, nil, clk) | ||
| 122 | var calls atomic.Int32 | 122 | var calls atomic.Int32 | ||
| 123 | - | d.procComm = func(pid int) (string, error) { | 123 | + | d.proc = &processInspectorFake{comm: func(pid int) (string, error) { |
| 124 | require.Equal(t, 1234, pid) | 124 | require.Equal(t, 1234, pid) | ||
| 125 | calls.Add(1) | 125 | calls.Add(1) | ||
| 126 | return "vim\n", nil | 126 | return "vim\n", nil | ||
| 127 | - | } | 127 | + | }} |
| 128 | 128 | ||||
| 129 | require.Equal(t, "vim", d.refreshPaneTitle(sess, "pane-1")) | 129 | require.Equal(t, "vim", d.refreshPaneTitle(sess, "pane-1")) | ||
| 130 | now = now.Add(paneTitleCacheTTL / 2) | 130 | now = now.Add(paneTitleCacheTTL / 2) | ||
| @@ -141,7 +141,7 @@ func TestRefreshFloatingPaneTitlePreservesConfiguredCommandFallback(t *testing.T | |||||
| 141 | clk.EXPECT().Now().Return(time.Time{}).Maybe() | 141 | clk.EXPECT().Now().Return(time.Time{}).Maybe() | ||
| 142 | d := newTestDaemon(t, nil, clk) | 142 | d := newTestDaemon(t, nil, clk) | ||
| 143 | d.shell = "/usr/bin/fish" | 143 | d.shell = "/usr/bin/fish" | ||
| 144 | - | d.procComm = func(int) (string, error) { return "", errors.New("unused") } | 144 | + | d.proc = &processInspectorFake{comm: func(int) (string, error) { return "", errors.New("unused") }} |
| 145 | p := newPane("floating", pty, domain.Size{Cols: 20, Rows: 5}) | 145 | p := newPane("floating", pty, domain.Size{Cols: 20, Rows: 5}) | ||
| 146 | p.title.displayFallback = floatingCommandFallback("btop --utf", d.shell) | 146 | p.title.displayFallback = floatingCommandFallback("btop --utf", d.shell) | ||
| 147 | 147 | ||||
| @@ -186,7 +186,7 @@ func TestRefreshPaneTitleLookupFailureKeepsProcessNameEmpty(t *testing.T) { | |||||
| 186 | clk.EXPECT().Now().Return(time.Time{}).Maybe() | 186 | clk.EXPECT().Now().Return(time.Time{}).Maybe() | ||
| 187 | d := newTestDaemon(t, nil, clk) | 187 | d := newTestDaemon(t, nil, clk) | ||
| 188 | d.shell = "/usr/bin/fish" | 188 | d.shell = "/usr/bin/fish" | ||
| 189 | - | d.procComm = func(int) (string, error) { return "", errors.New("unused") } | 189 | + | d.proc = &processInspectorFake{comm: func(int) (string, error) { return "", errors.New("unused") }} |
| 190 | p := testAttachmentTab(sess).focusedPane() | 190 | p := testAttachmentTab(sess).focusedPane() | ||
| 191 | 191 | ||||
| 192 | p.mu.Lock() | 192 | p.mu.Lock() | ||
| @@ -204,3 +204,41 @@ func TestRefreshPaneTitleLookupFailureKeepsProcessNameEmpty(t *testing.T) { | |||||
| 204 | p.mu.Unlock() | 204 | p.mu.Unlock() | ||
| 205 | require.Equal(t, "sh", d.refreshPaneDisplayTitle(sess, p, true), "pointer refresh must retain its pane-owned fallback") | 205 | require.Equal(t, "sh", d.refreshPaneDisplayTitle(sess, p, true), "pointer refresh must retain its pane-owned fallback") | ||
| 206 | } | 206 | } | ||
| 207 | + | ||||
| 208 | + | // processInspectorFake is a hand-written ports.ProcessInspector whose | |||
| 209 | + | // behavior is set per test through func fields; an unset func reports an | |||
| 210 | + | // error, the same as an inspector that cannot read the process. | |||
| 211 | + | type processInspectorFake struct { | |||
| 212 | + | cwd func(int) (string, error) | |||
| 213 | + | comm func(int) (string, error) | |||
| 214 | + | argv func(int) ([]string, error) | |||
| 215 | + | groupArgv func(int, int) ([]string, error) | |||
| 216 | + | } | |||
| 217 | + | ||||
| 218 | + | func (f *processInspectorFake) Cwd(pid int) (string, error) { | |||
| 219 | + | if f.cwd == nil { | |||
| 220 | + | return "", errProcessInspectionUnavailable | |||
| 221 | + | } | |||
| 222 | + | return f.cwd(pid) | |||
| 223 | + | } | |||
| 224 | + | ||||
| 225 | + | func (f *processInspectorFake) Comm(pid int) (string, error) { | |||
| 226 | + | if f.comm == nil { | |||
| 227 | + | return "", errProcessInspectionUnavailable | |||
| 228 | + | } | |||
| 229 | + | return f.comm(pid) | |||
| 230 | + | } | |||
| 231 | + | ||||
| 232 | + | func (f *processInspectorFake) Argv(pid int) ([]string, error) { | |||
| 233 | + | if f.argv == nil { | |||
| 234 | + | return nil, errProcessInspectionUnavailable | |||
| 235 | + | } | |||
| 236 | + | return f.argv(pid) | |||
| 237 | + | } | |||
| 238 | + | ||||
| 239 | + | func (f *processInspectorFake) GroupArgv(pgid, shellPid int) ([]string, error) { | |||
| 240 | + | if f.groupArgv == nil { | |||
| 241 | + | return nil, errProcessInspectionUnavailable | |||
| 242 | + | } | |||
| 243 | + | return f.groupArgv(pgid, shellPid) | |||
| 244 | + | } | |||
A internal/usecase/daemon/process_inspector.go +58 -0
| @@ -0,0 +1,58 @@ | |||
| 1 | + | package daemon | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "errors" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/vev/internal/ports" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | var errProcessInspectionUnavailable = errors.New("process inspection unavailable") | |
| 10 | + | ||
| 11 | + | // cwdOverrideInspector replaces only Cwd on top of an optional base inspector. | |
| 12 | + | // With a nil base, Comm, Argv, and GroupArgv report the inspection as | |
| 13 | + | // unavailable, matching a daemon that was given only a cwd reader. | |
| 14 | + | type cwdOverrideInspector struct { | |
| 15 | + | base ports.ProcessInspector | |
| 16 | + | cwd func(int) (string, error) | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | func (i cwdOverrideInspector) Cwd(pid int) (string, error) { return i.cwd(pid) } | |
| 20 | + | ||
| 21 | + | func (i cwdOverrideInspector) Comm(pid int) (string, error) { | |
| 22 | + | if i.base == nil { | |
| 23 | + | return "", errProcessInspectionUnavailable | |
| 24 | + | } | |
| 25 | + | return i.base.Comm(pid) | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | func (i cwdOverrideInspector) Argv(pid int) ([]string, error) { | |
| 29 | + | if i.base == nil { | |
| 30 | + | return nil, errProcessInspectionUnavailable | |
| 31 | + | } | |
| 32 | + | return i.base.Argv(pid) | |
| 33 | + | } | |
| 34 | + | ||
| 35 | + | func (i cwdOverrideInspector) GroupArgv(pgid, shellPid int) ([]string, error) { | |
| 36 | + | if i.base == nil { | |
| 37 | + | return nil, errProcessInspectionUnavailable | |
| 38 | + | } | |
| 39 | + | return i.base.GroupArgv(pgid, shellPid) | |
| 40 | + | } | |
| 41 | + | ||
| 42 | + | // unwrapCwdOverride returns the inspector beneath a previous cwd override so | |
| 43 | + | // repeated overrides do not stack. | |
| 44 | + | func unwrapCwdOverride(ins ports.ProcessInspector) ports.ProcessInspector { | |
| 45 | + | if o, ok := ins.(cwdOverrideInspector); ok { | |
| 46 | + | return o.base | |
| 47 | + | } | |
| 48 | + | return ins | |
| 49 | + | } | |
| 50 | + | ||
| 51 | + | // hasProcessDetails reports whether Comm, Argv, and GroupArgv are backed by a | |
| 52 | + | // real inspector rather than a cwd-only override. | |
| 53 | + | func (d *Daemon) hasProcessDetails() bool { | |
| 54 | + | if o, ok := d.proc.(cwdOverrideInspector); ok { | |
| 55 | + | return o.base != nil | |
| 56 | + | } | |
| 57 | + | return d.proc != nil | |
| 58 | + | } | |
| @@ -0,0 +1,58 @@ | |||||
| 1 | + | package daemon | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "errors" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/vev/internal/ports" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | var errProcessInspectionUnavailable = errors.New("process inspection unavailable") | |||
| 10 | + | ||||
| 11 | + | // cwdOverrideInspector replaces only Cwd on top of an optional base inspector. | |||
| 12 | + | // With a nil base, Comm, Argv, and GroupArgv report the inspection as | |||
| 13 | + | // unavailable, matching a daemon that was given only a cwd reader. | |||
| 14 | + | type cwdOverrideInspector struct { | |||
| 15 | + | base ports.ProcessInspector | |||
| 16 | + | cwd func(int) (string, error) | |||
| 17 | + | } | |||
| 18 | + | ||||
| 19 | + | func (i cwdOverrideInspector) Cwd(pid int) (string, error) { return i.cwd(pid) } | |||
| 20 | + | ||||
| 21 | + | func (i cwdOverrideInspector) Comm(pid int) (string, error) { | |||
| 22 | + | if i.base == nil { | |||
| 23 | + | return "", errProcessInspectionUnavailable | |||
| 24 | + | } | |||
| 25 | + | return i.base.Comm(pid) | |||
| 26 | + | } | |||
| 27 | + | ||||
| 28 | + | func (i cwdOverrideInspector) Argv(pid int) ([]string, error) { | |||
| 29 | + | if i.base == nil { | |||
| 30 | + | return nil, errProcessInspectionUnavailable | |||
| 31 | + | } | |||
| 32 | + | return i.base.Argv(pid) | |||
| 33 | + | } | |||
| 34 | + | ||||
| 35 | + | func (i cwdOverrideInspector) GroupArgv(pgid, shellPid int) ([]string, error) { | |||
| 36 | + | if i.base == nil { | |||
| 37 | + | return nil, errProcessInspectionUnavailable | |||
| 38 | + | } | |||
| 39 | + | return i.base.GroupArgv(pgid, shellPid) | |||
| 40 | + | } | |||
| 41 | + | ||||
| 42 | + | // unwrapCwdOverride returns the inspector beneath a previous cwd override so | |||
| 43 | + | // repeated overrides do not stack. | |||
| 44 | + | func unwrapCwdOverride(ins ports.ProcessInspector) ports.ProcessInspector { | |||
| 45 | + | if o, ok := ins.(cwdOverrideInspector); ok { | |||
| 46 | + | return o.base | |||
| 47 | + | } | |||
| 48 | + | return ins | |||
| 49 | + | } | |||
| 50 | + | ||||
| 51 | + | // hasProcessDetails reports whether Comm, Argv, and GroupArgv are backed by a | |||
| 52 | + | // real inspector rather than a cwd-only override. | |||
| 53 | + | func (d *Daemon) hasProcessDetails() bool { | |||
| 54 | + | if o, ok := d.proc.(cwdOverrideInspector); ok { | |||
| 55 | + | return o.base != nil | |||
| 56 | + | } | |||
| 57 | + | return d.proc != nil | |||
| 58 | + | } | |||
M internal/usecase/daemon/session.go +3 -3
| @@ -2111,7 +2111,7 @@ func (d *Daemon) cwdSampler(ctx context.Context) { | |||
| 2111 | 2111 | return | |
| 2112 | 2112 | case <-t.C(): | |
| 2113 | 2113 | } | |
| 2114 | - | if d.procCwd != nil { | |
| 2114 | + | if d.proc != nil { | |
| 2115 | 2115 | d.refreshNamedSessionCwds() | |
| 2116 | 2116 | } | |
| 2117 | 2117 | if err := d.flushCatalogue(); err != nil { | |
| @@ -2136,7 +2136,7 @@ func (d *Daemon) refreshNamedSessionCwds() { | |||
| 2136 | 2136 | } | |
| 2137 | 2137 | ||
| 2138 | 2138 | func (d *Daemon) refreshSessionCwd(sess *session) { | |
| 2139 | - | if d.procCwd == nil { | |
| 2139 | + | if d.proc == nil { | |
| 2140 | 2140 | return | |
| 2141 | 2141 | } | |
| 2142 | 2142 | tb := sess.firstTab() | |
| @@ -2149,7 +2149,7 @@ func (d *Daemon) refreshSessionCwd(sess *session) { | |||
| 2149 | 2149 | if p == nil { | |
| 2150 | 2150 | return | |
| 2151 | 2151 | } | |
| 2152 | - | cwd, err := d.procCwd(p.pty.Pid()) | |
| 2152 | + | cwd, err := d.proc.Cwd(p.pty.Pid()) | |
| 2153 | 2153 | if err != nil || cwd == "" { | |
| 2154 | 2154 | return | |
| 2155 | 2155 | } | |
| @@ -2111,7 +2111,7 @@ func (d *Daemon) cwdSampler(ctx context.Context) { | |||||
| 2111 | return | 2111 | return | ||
| 2112 | case <-t.C(): | 2112 | case <-t.C(): | ||
| 2113 | } | 2113 | } | ||
| 2114 | - | if d.procCwd != nil { | 2114 | + | if d.proc != nil { |
| 2115 | d.refreshNamedSessionCwds() | 2115 | d.refreshNamedSessionCwds() | ||
| 2116 | } | 2116 | } | ||
| 2117 | if err := d.flushCatalogue(); err != nil { | 2117 | if err := d.flushCatalogue(); err != nil { | ||
| @@ -2136,7 +2136,7 @@ func (d *Daemon) refreshNamedSessionCwds() { | |||||
| 2136 | } | 2136 | } | ||
| 2137 | 2137 | ||||
| 2138 | func (d *Daemon) refreshSessionCwd(sess *session) { | 2138 | func (d *Daemon) refreshSessionCwd(sess *session) { | ||
| 2139 | - | if d.procCwd == nil { | 2139 | + | if d.proc == nil { |
| 2140 | return | 2140 | return | ||
| 2141 | } | 2141 | } | ||
| 2142 | tb := sess.firstTab() | 2142 | tb := sess.firstTab() | ||
| @@ -2149,7 +2149,7 @@ func (d *Daemon) refreshSessionCwd(sess *session) { | |||||
| 2149 | if p == nil { | 2149 | if p == nil { | ||
| 2150 | return | 2150 | return | ||
| 2151 | } | 2151 | } | ||
| 2152 | - | cwd, err := d.procCwd(p.pty.Pid()) | 2152 | + | cwd, err := d.proc.Cwd(p.pty.Pid()) |
| 2153 | if err != nil || cwd == "" { | 2153 | if err != nil || cwd == "" { | ||
| 2154 | return | 2154 | return | ||
| 2155 | } | 2155 | } | ||
M internal/usecase/daemon/snapshot_capture.go +4 -4
| @@ -61,8 +61,8 @@ func (d *Daemon) captureSnapshotState(sess *session, generation uint64) (*snapsh | |||
| 61 | 61 | paneCapture.tail = paneCapture.sealed.Tail() | |
| 62 | 62 | p.mu.Unlock() | |
| 63 | 63 | paneCapture.cwd = fallbackCwd | |
| 64 | - | if d.procCwd != nil && pid > 0 { | |
| 65 | - | if cwd, err := d.procCwd(pid); err == nil && cwd != "" { | |
| 64 | + | if d.proc != nil && pid > 0 { | |
| 65 | + | if cwd, err := d.proc.Cwd(pid); err == nil && cwd != "" { | |
| 66 | 66 | paneCapture.cwd = cwd | |
| 67 | 67 | } | |
| 68 | 68 | } | |
| @@ -82,14 +82,14 @@ func (d *Daemon) captureSession(sess *session) bool { | |||
| 82 | 82 | } | |
| 83 | 83 | ||
| 84 | 84 | func (d *Daemon) capturePaneProcess(pty interface{ ForegroundPgid() (int, error) }, shellPid int) *snapcodec.Process { | |
| 85 | - | if d == nil || pty == nil || shellPid <= 0 || d.procGroupArgv == nil { | |
| 85 | + | if d == nil || pty == nil || shellPid <= 0 || !d.hasProcessDetails() { | |
| 86 | 86 | return nil | |
| 87 | 87 | } | |
| 88 | 88 | pgid, err := pty.ForegroundPgid() | |
| 89 | 89 | if err != nil || pgid <= 0 || pgid == shellPid { | |
| 90 | 90 | return nil | |
| 91 | 91 | } | |
| 92 | - | argv, err := d.procGroupArgv(pgid, shellPid) | |
| 92 | + | argv, err := d.proc.GroupArgv(pgid, shellPid) | |
| 93 | 93 | if err != nil || len(argv) == 0 || argv[0] == "" { | |
| 94 | 94 | return nil | |
| 95 | 95 | } | |
| @@ -61,8 +61,8 @@ func (d *Daemon) captureSnapshotState(sess *session, generation uint64) (*snapsh | |||||
| 61 | paneCapture.tail = paneCapture.sealed.Tail() | 61 | paneCapture.tail = paneCapture.sealed.Tail() | ||
| 62 | p.mu.Unlock() | 62 | p.mu.Unlock() | ||
| 63 | paneCapture.cwd = fallbackCwd | 63 | paneCapture.cwd = fallbackCwd | ||
| 64 | - | if d.procCwd != nil && pid > 0 { | 64 | + | if d.proc != nil && pid > 0 { |
| 65 | - | if cwd, err := d.procCwd(pid); err == nil && cwd != "" { | 65 | + | if cwd, err := d.proc.Cwd(pid); err == nil && cwd != "" { |
| 66 | paneCapture.cwd = cwd | 66 | paneCapture.cwd = cwd | ||
| 67 | } | 67 | } | ||
| 68 | } | 68 | } | ||
| @@ -82,14 +82,14 @@ func (d *Daemon) captureSession(sess *session) bool { | |||||
| 82 | } | 82 | } | ||
| 83 | 83 | ||||
| 84 | func (d *Daemon) capturePaneProcess(pty interface{ ForegroundPgid() (int, error) }, shellPid int) *snapcodec.Process { | 84 | func (d *Daemon) capturePaneProcess(pty interface{ ForegroundPgid() (int, error) }, shellPid int) *snapcodec.Process { | ||
| 85 | - | if d == nil || pty == nil || shellPid <= 0 || d.procGroupArgv == nil { | 85 | + | if d == nil || pty == nil || shellPid <= 0 || !d.hasProcessDetails() { |
| 86 | return nil | 86 | return nil | ||
| 87 | } | 87 | } | ||
| 88 | pgid, err := pty.ForegroundPgid() | 88 | pgid, err := pty.ForegroundPgid() | ||
| 89 | if err != nil || pgid <= 0 || pgid == shellPid { | 89 | if err != nil || pgid <= 0 || pgid == shellPid { | ||
| 90 | return nil | 90 | return nil | ||
| 91 | } | 91 | } | ||
| 92 | - | argv, err := d.procGroupArgv(pgid, shellPid) | 92 | + | argv, err := d.proc.GroupArgv(pgid, shellPid) |
| 93 | if err != nil || len(argv) == 0 || argv[0] == "" { | 93 | if err != nil || len(argv) == 0 || argv[0] == "" { | ||
| 94 | return nil | 94 | return nil | ||
| 95 | } | 95 | } | ||