refactor(daemon): clarify runtime config and palette execution names and test process inspector options
- commit
- b5fd841b7ba0cc5c487fea9d2211ed384696208e
- author
- brice · · 2026-10-08 06:23 UTC
- committer
- brice · · 2026-10-08 08:12 UTC
- parent
- 4d933cb
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email - refs
- refactor/daemon-split
10 files · +126 -48
M internal/usecase/daemon/client.go +4 -1
| @@ -25,7 +25,10 @@ | |||
| 25 | 25 | // it, and the capture, queue, and worker code never takes d.mu. The only nested | |
| 26 | 26 | // acquisition is a session's snapshotMu inside it (enqueueFinalSnapshotCapture | |
| 27 | 27 | // finishes a replaced capture), so snapshotMu holders must release snapshotMu | |
| 28 | - | // before taking snapshotWorker.mu, as finishSnapshotCapture does. | |
| 28 | + | // before taking snapshotWorker.mu, as finishSnapshotCapture does. That nested | |
| 29 | + | // finishSnapshotCapture(replaced, false) cannot re-enter snapshotWorker.mu: | |
| 30 | + | // final-queue captures are never normalWorkerAdmitted, and succeeded=false | |
| 31 | + | // suppresses the forced-successor scheduling path. | |
| 29 | 32 | // snapshotWorker.noticeMu guards the active persistence-failure signature; it | |
| 30 | 33 | // is a leaf and is never held together with snapshotWorker.mu. Restoration | |
| 31 | 34 | // takes d.mu only outside snapshotWorker.mu. | |
| @@ -25,7 +25,10 @@ | |||||
| 25 | // it, and the capture, queue, and worker code never takes d.mu. The only nested | 25 | // it, and the capture, queue, and worker code never takes d.mu. The only nested | ||
| 26 | // acquisition is a session's snapshotMu inside it (enqueueFinalSnapshotCapture | 26 | // acquisition is a session's snapshotMu inside it (enqueueFinalSnapshotCapture | ||
| 27 | // finishes a replaced capture), so snapshotMu holders must release snapshotMu | 27 | // finishes a replaced capture), so snapshotMu holders must release snapshotMu | ||
| 28 | - | // before taking snapshotWorker.mu, as finishSnapshotCapture does. | 28 | + | // before taking snapshotWorker.mu, as finishSnapshotCapture does. That nested |
| 29 | + | // finishSnapshotCapture(replaced, false) cannot re-enter snapshotWorker.mu: | |||
| 30 | + | // final-queue captures are never normalWorkerAdmitted, and succeeded=false | |||
| 31 | + | // suppresses the forced-successor scheduling path. | |||
| 29 | // snapshotWorker.noticeMu guards the active persistence-failure signature; it | 32 | // snapshotWorker.noticeMu guards the active persistence-failure signature; it | ||
| 30 | // is a leaf and is never held together with snapshotWorker.mu. Restoration | 33 | // is a leaf and is never held together with snapshotWorker.mu. Restoration | ||
| 31 | // takes d.mu only outside snapshotWorker.mu. | 34 | // takes d.mu only outside snapshotWorker.mu. | ||
M internal/usecase/daemon/config.go +11 -11
| @@ -63,9 +63,9 @@ func defaultRuntimeConfig() *runtimeConfig { | |||
| 63 | 63 | } | |
| 64 | 64 | } | |
| 65 | 65 | ||
| 66 | - | // runtimeConfig returns the current snapshot, or the defaults when none has | |
| 66 | + | // currentRuntimeConfig returns the current snapshot, or the defaults when none has | |
| 67 | 67 | // been published (zero-value Daemon). The result is never nil. | |
| 68 | - | func (d *Daemon) runtimeConfig() *runtimeConfig { | |
| 68 | + | func (d *Daemon) currentRuntimeConfig() *runtimeConfig { | |
| 69 | 69 | if cfg := d.config.Load(); cfg != nil { | |
| 70 | 70 | return cfg | |
| 71 | 71 | } | |
| @@ -73,7 +73,7 @@ func (d *Daemon) runtimeConfig() *runtimeConfig { | |||
| 73 | 73 | } | |
| 74 | 74 | ||
| 75 | 75 | func (d *Daemon) currentThemeConfig() themeConfigSnapshot { | |
| 76 | - | return d.runtimeConfig().theme | |
| 76 | + | return d.currentRuntimeConfig().theme | |
| 77 | 77 | } | |
| 78 | 78 | ||
| 79 | 79 | // ApplyConfig validates and atomically swaps daemon runtime configuration. | |
| @@ -278,7 +278,7 @@ func buildRestoreProcessAllowlist(values []string) map[string]struct{} { | |||
| 278 | 278 | } | |
| 279 | 279 | ||
| 280 | 280 | func (d *Daemon) restoreProcessAllowlistSnapshot() map[string]struct{} { | |
| 281 | - | return d.runtimeConfig().restoreProcessAllowlist | |
| 281 | + | return d.currentRuntimeConfig().restoreProcessAllowlist | |
| 282 | 282 | } | |
| 283 | 283 | ||
| 284 | 284 | func (d *Daemon) logConfigWarning(w domain.Warning) { | |
| @@ -290,31 +290,31 @@ func (d *Daemon) logConfigWarning(w domain.Warning) { | |||
| 290 | 290 | } | |
| 291 | 291 | ||
| 292 | 292 | func (d *Daemon) currentCopyConfig() domain.CopyConfig { | |
| 293 | - | return d.runtimeConfig().copy | |
| 293 | + | return d.currentRuntimeConfig().copy | |
| 294 | 294 | } | |
| 295 | 295 | ||
| 296 | 296 | func (d *Daemon) currentPaletteConfig() domain.PaletteConfig { | |
| 297 | - | return d.runtimeConfig().palette | |
| 297 | + | return d.currentRuntimeConfig().palette | |
| 298 | 298 | } | |
| 299 | 299 | ||
| 300 | 300 | func (d *Daemon) currentNavConfig() domain.NavConfig { | |
| 301 | - | return d.runtimeConfig().nav | |
| 301 | + | return d.currentRuntimeConfig().nav | |
| 302 | 302 | } | |
| 303 | 303 | ||
| 304 | 304 | func (d *Daemon) currentTabsConfig() domain.TabsConfig { | |
| 305 | - | return d.runtimeConfig().tabs | |
| 305 | + | return d.currentRuntimeConfig().tabs | |
| 306 | 306 | } | |
| 307 | 307 | ||
| 308 | 308 | func (d *Daemon) currentEphemeralConfig() domain.EphemeralConfig { | |
| 309 | - | return d.runtimeConfig().ephemeral | |
| 309 | + | return d.currentRuntimeConfig().ephemeral | |
| 310 | 310 | } | |
| 311 | 311 | ||
| 312 | 312 | func (d *Daemon) currentFloatingConfig() domain.FloatingConfig { | |
| 313 | - | return d.runtimeConfig().floating | |
| 313 | + | return d.currentRuntimeConfig().floating | |
| 314 | 314 | } | |
| 315 | 315 | ||
| 316 | 316 | func (d *Daemon) codeOverrideSnapshot() map[string]string { | |
| 317 | - | return d.runtimeConfig().codeOverrides | |
| 317 | + | return d.currentRuntimeConfig().codeOverrides | |
| 318 | 318 | } | |
| 319 | 319 | ||
| 320 | 320 | func commandWithOverrides(cmd command.Command, overrides map[string]string) command.Command { | |
| @@ -63,9 +63,9 @@ func defaultRuntimeConfig() *runtimeConfig { | |||||
| 63 | } | 63 | } | ||
| 64 | } | 64 | } | ||
| 65 | 65 | ||||
| 66 | - | // runtimeConfig returns the current snapshot, or the defaults when none has | 66 | + | // currentRuntimeConfig returns the current snapshot, or the defaults when none has |
| 67 | // been published (zero-value Daemon). The result is never nil. | 67 | // been published (zero-value Daemon). The result is never nil. | ||
| 68 | - | func (d *Daemon) runtimeConfig() *runtimeConfig { | 68 | + | func (d *Daemon) currentRuntimeConfig() *runtimeConfig { |
| 69 | if cfg := d.config.Load(); cfg != nil { | 69 | if cfg := d.config.Load(); cfg != nil { | ||
| 70 | return cfg | 70 | return cfg | ||
| 71 | } | 71 | } | ||
| @@ -73,7 +73,7 @@ func (d *Daemon) runtimeConfig() *runtimeConfig { | |||||
| 73 | } | 73 | } | ||
| 74 | 74 | ||||
| 75 | func (d *Daemon) currentThemeConfig() themeConfigSnapshot { | 75 | func (d *Daemon) currentThemeConfig() themeConfigSnapshot { | ||
| 76 | - | return d.runtimeConfig().theme | 76 | + | return d.currentRuntimeConfig().theme |
| 77 | } | 77 | } | ||
| 78 | 78 | ||||
| 79 | // ApplyConfig validates and atomically swaps daemon runtime configuration. | 79 | // ApplyConfig validates and atomically swaps daemon runtime configuration. | ||
| @@ -278,7 +278,7 @@ func buildRestoreProcessAllowlist(values []string) map[string]struct{} { | |||||
| 278 | } | 278 | } | ||
| 279 | 279 | ||||
| 280 | func (d *Daemon) restoreProcessAllowlistSnapshot() map[string]struct{} { | 280 | func (d *Daemon) restoreProcessAllowlistSnapshot() map[string]struct{} { | ||
| 281 | - | return d.runtimeConfig().restoreProcessAllowlist | 281 | + | return d.currentRuntimeConfig().restoreProcessAllowlist |
| 282 | } | 282 | } | ||
| 283 | 283 | ||||
| 284 | func (d *Daemon) logConfigWarning(w domain.Warning) { | 284 | func (d *Daemon) logConfigWarning(w domain.Warning) { | ||
| @@ -290,31 +290,31 @@ func (d *Daemon) logConfigWarning(w domain.Warning) { | |||||
| 290 | } | 290 | } | ||
| 291 | 291 | ||||
| 292 | func (d *Daemon) currentCopyConfig() domain.CopyConfig { | 292 | func (d *Daemon) currentCopyConfig() domain.CopyConfig { | ||
| 293 | - | return d.runtimeConfig().copy | 293 | + | return d.currentRuntimeConfig().copy |
| 294 | } | 294 | } | ||
| 295 | 295 | ||||
| 296 | func (d *Daemon) currentPaletteConfig() domain.PaletteConfig { | 296 | func (d *Daemon) currentPaletteConfig() domain.PaletteConfig { | ||
| 297 | - | return d.runtimeConfig().palette | 297 | + | return d.currentRuntimeConfig().palette |
| 298 | } | 298 | } | ||
| 299 | 299 | ||||
| 300 | func (d *Daemon) currentNavConfig() domain.NavConfig { | 300 | func (d *Daemon) currentNavConfig() domain.NavConfig { | ||
| 301 | - | return d.runtimeConfig().nav | 301 | + | return d.currentRuntimeConfig().nav |
| 302 | } | 302 | } | ||
| 303 | 303 | ||||
| 304 | func (d *Daemon) currentTabsConfig() domain.TabsConfig { | 304 | func (d *Daemon) currentTabsConfig() domain.TabsConfig { | ||
| 305 | - | return d.runtimeConfig().tabs | 305 | + | return d.currentRuntimeConfig().tabs |
| 306 | } | 306 | } | ||
| 307 | 307 | ||||
| 308 | func (d *Daemon) currentEphemeralConfig() domain.EphemeralConfig { | 308 | func (d *Daemon) currentEphemeralConfig() domain.EphemeralConfig { | ||
| 309 | - | return d.runtimeConfig().ephemeral | 309 | + | return d.currentRuntimeConfig().ephemeral |
| 310 | } | 310 | } | ||
| 311 | 311 | ||||
| 312 | func (d *Daemon) currentFloatingConfig() domain.FloatingConfig { | 312 | func (d *Daemon) currentFloatingConfig() domain.FloatingConfig { | ||
| 313 | - | return d.runtimeConfig().floating | 313 | + | return d.currentRuntimeConfig().floating |
| 314 | } | 314 | } | ||
| 315 | 315 | ||||
| 316 | func (d *Daemon) codeOverrideSnapshot() map[string]string { | 316 | func (d *Daemon) codeOverrideSnapshot() map[string]string { | ||
| 317 | - | return d.runtimeConfig().codeOverrides | 317 | + | return d.currentRuntimeConfig().codeOverrides |
| 318 | } | 318 | } | ||
| 319 | 319 | ||||
| 320 | func commandWithOverrides(cmd command.Command, overrides map[string]string) command.Command { | 320 | func commandWithOverrides(cmd command.Command, overrides map[string]string) command.Command { | ||
M internal/usecase/daemon/config_test.go +2 -2
| @@ -167,7 +167,7 @@ func TestEffectiveThemePaletteGate(t *testing.T) { | |||
| 167 | 167 | // updateRuntimeConfig publishes a modified copy of the current runtime config | |
| 168 | 168 | // snapshot, the way ApplyConfig does, without touching unrelated sections. | |
| 169 | 169 | func updateRuntimeConfig(d *Daemon, mutate func(*runtimeConfig)) { | |
| 170 | - | next := *d.runtimeConfig() | |
| 170 | + | next := *d.currentRuntimeConfig() | |
| 171 | 171 | mutate(&next) | |
| 172 | 172 | d.config.Store(&next) | |
| 173 | 173 | } | |
| @@ -492,7 +492,7 @@ func TestPaletteCommandsUseOverrideSnapshotForRecentAndListing(t *testing.T) { | |||
| 492 | 492 | "split-right": "SRX", | |
| 493 | 493 | }, | |
| 494 | 494 | }) | |
| 495 | - | d.recordPaletteUse("SRX") | |
| 495 | + | d.paletteHistory.record("SRX") | |
| 496 | 496 | ||
| 497 | 497 | commands := d.paletteCommands() | |
| 498 | 498 | require.NotEmpty(t, commands) | |
| @@ -167,7 +167,7 @@ func TestEffectiveThemePaletteGate(t *testing.T) { | |||||
| 167 | // updateRuntimeConfig publishes a modified copy of the current runtime config | 167 | // updateRuntimeConfig publishes a modified copy of the current runtime config | ||
| 168 | // snapshot, the way ApplyConfig does, without touching unrelated sections. | 168 | // snapshot, the way ApplyConfig does, without touching unrelated sections. | ||
| 169 | func updateRuntimeConfig(d *Daemon, mutate func(*runtimeConfig)) { | 169 | func updateRuntimeConfig(d *Daemon, mutate func(*runtimeConfig)) { | ||
| 170 | - | next := *d.runtimeConfig() | 170 | + | next := *d.currentRuntimeConfig() |
| 171 | mutate(&next) | 171 | mutate(&next) | ||
| 172 | d.config.Store(&next) | 172 | d.config.Store(&next) | ||
| 173 | } | 173 | } | ||
| @@ -492,7 +492,7 @@ func TestPaletteCommandsUseOverrideSnapshotForRecentAndListing(t *testing.T) { | |||||
| 492 | "split-right": "SRX", | 492 | "split-right": "SRX", | ||
| 493 | }, | 493 | }, | ||
| 494 | }) | 494 | }) | ||
| 495 | - | d.recordPaletteUse("SRX") | 495 | + | d.paletteHistory.record("SRX") |
| 496 | 496 | ||||
| 497 | commands := d.paletteCommands() | 497 | commands := d.paletteCommands() | ||
| 498 | require.NotEmpty(t, commands) | 498 | require.NotEmpty(t, commands) | ||
M internal/usecase/daemon/daemon.go +3 -2
| @@ -193,8 +193,9 @@ type Daemon struct { | |||
| 193 | 193 | // bindings is read by keys.Router through its own atomic pointer, so it is | |
| 194 | 194 | // published next to (not inside) the runtime config snapshot. | |
| 195 | 195 | bindings atomic.Pointer[keys.Bindings] | |
| 196 | - | // config is the immutable runtime configuration snapshot built and | |
| 197 | - | // stored once by ApplyConfig; read it through runtimeConfig. | |
| 196 | + | // config is the immutable runtime configuration snapshot, published with | |
| 197 | + | // defaults by New and replaced by every ApplyConfig; read it through | |
| 198 | + | // currentRuntimeConfig. | |
| 198 | 199 | config atomic.Pointer[runtimeConfig] | |
| 199 | 200 | barScripts *barScriptState | |
| 200 | 201 | notices *noticeCenter | |
| @@ -193,8 +193,9 @@ type Daemon struct { | |||||
| 193 | // bindings is read by keys.Router through its own atomic pointer, so it is | 193 | // bindings is read by keys.Router through its own atomic pointer, so it is | ||
| 194 | // published next to (not inside) the runtime config snapshot. | 194 | // published next to (not inside) the runtime config snapshot. | ||
| 195 | bindings atomic.Pointer[keys.Bindings] | 195 | bindings atomic.Pointer[keys.Bindings] | ||
| 196 | - | // config is the immutable runtime configuration snapshot built and | 196 | + | // config is the immutable runtime configuration snapshot, published with |
| 197 | - | // stored once by ApplyConfig; read it through runtimeConfig. | 197 | + | // defaults by New and replaced by every ApplyConfig; read it through |
| 198 | + | // currentRuntimeConfig. | |||
| 198 | config atomic.Pointer[runtimeConfig] | 199 | config atomic.Pointer[runtimeConfig] | ||
| 199 | barScripts *barScriptState | 200 | barScripts *barScriptState | ||
| 200 | notices *noticeCenter | 201 | notices *noticeCenter | ||
M internal/usecase/daemon/history_policy.go +1 -1
| @@ -16,7 +16,7 @@ func historyConfigFor(policy domain.ScrollbackConfig) vt.HistoryConfig { | |||
| 16 | 16 | } | |
| 17 | 17 | ||
| 18 | 18 | func (d *Daemon) currentHistoryConfig() vt.HistoryConfig { | |
| 19 | - | return historyConfigFor(d.runtimeConfig().scrollback) | |
| 19 | + | return historyConfigFor(d.currentRuntimeConfig().scrollback) | |
| 20 | 20 | } | |
| 21 | 21 | ||
| 22 | 22 | // historyPanes gathers references without holding a session/tab lock while | |
| @@ -16,7 +16,7 @@ func historyConfigFor(policy domain.ScrollbackConfig) vt.HistoryConfig { | |||||
| 16 | } | 16 | } | ||
| 17 | 17 | ||||
| 18 | func (d *Daemon) currentHistoryConfig() vt.HistoryConfig { | 18 | func (d *Daemon) currentHistoryConfig() vt.HistoryConfig { | ||
| 19 | - | return historyConfigFor(d.runtimeConfig().scrollback) | 19 | + | return historyConfigFor(d.currentRuntimeConfig().scrollback) |
| 20 | } | 20 | } | ||
| 21 | 21 | ||||
| 22 | // historyPanes gathers references without holding a session/tab lock while | 22 | // historyPanes gathers references without holding a session/tab lock while | ||
M internal/usecase/daemon/palette.go +14 -23
| @@ -351,10 +351,6 @@ func (d *Daemon) paletteCommands() []command.Command { | |||
| 351 | 351 | return out | |
| 352 | 352 | } | |
| 353 | 353 | ||
| 354 | - | func (d *Daemon) recordPaletteUse(code string) { | |
| 355 | - | d.paletteHistory.record(code) | |
| 356 | - | } | |
| 357 | - | ||
| 358 | 354 | // paletteDecisionKind names what handlePaletteInput must do once the palette | |
| 359 | 355 | // lock is released. | |
| 360 | 356 | type paletteDecisionKind int | |
| @@ -389,8 +385,8 @@ type paletteDecision struct { | |||
| 389 | 385 | hasCancelInventory bool | |
| 390 | 386 | } | |
| 391 | 387 | ||
| 392 | - | // decidePaletteInput applies data to the attachment's palette while holding | |
| 393 | - | // paletteMu and returns what the caller must do after the lock is released. | |
| 388 | + | // decidePaletteInput takes paletteMu, applies data to the attachment's palette, | |
| 389 | + | // and returns the action to run after unlocking. | |
| 394 | 390 | func decidePaletteInput(ac *attachedClient, data []byte) paletteDecision { | |
| 395 | 391 | var cmd command.Command | |
| 396 | 392 | var sessionTarget palette.Result | |
| @@ -590,14 +586,13 @@ func (d *Daemon) executePaletteDecision(ac *attachedClient, entry *session, effe | |||
| 590 | 586 | case paletteDecisionSessionTarget: | |
| 591 | 587 | d.executePaletteSessionTarget(ac, entry, effect, generation, rawQuery, decision.sessionTarget) | |
| 592 | 588 | case paletteDecisionCommand: | |
| 593 | - | d.executePaletteCommand(ac, entry, effect, generation, rawQuery, decision) | |
| 589 | + | d.executePaletteCommand(ac, entry, effect, generation, rawQuery, decision.cmd, decision.args, decision.routeSnapshot) | |
| 594 | 590 | } | |
| 595 | 591 | } | |
| 596 | 592 | ||
| 597 | 593 | func (d *Daemon) executePaletteCreateDestination(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, createDestination palette.Result) { | |
| 598 | - | sess := entry | |
| 599 | 594 | name, _, _, _, _, _ := createDestination.CreateSessionDestination() | |
| 600 | - | exec := paletteExec{d: d, sess: sess, attachment: entry, ac: ac, effect: effect} | |
| 595 | + | exec := paletteExec{d: d, sess: entry, attachment: entry, ac: ac, effect: effect} | |
| 601 | 596 | if err := exec.validateCreateSessionDestination(effect, createDestination); err != nil { | |
| 602 | 597 | ac.paletteFailure(generation, rawQuery, errCreateDestinationUnavailable.Error()) | |
| 603 | 598 | d.invalidateRender(entry, ac, true, "palette.go") | |
| @@ -666,8 +661,7 @@ func (d *Daemon) executePaletteImportedSession(ac *attachedClient, entry *sessio | |||
| 666 | 661 | } | |
| 667 | 662 | ||
| 668 | 663 | func (d *Daemon) executePaletteRouteNavigation(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, routeTarget protocol.RouteNavigationAction) { | |
| 669 | - | sess := entry | |
| 670 | - | exec := paletteExec{d: d, sess: sess, attachment: entry, ac: ac, effect: effect} | |
| 664 | + | exec := paletteExec{d: d, sess: entry, attachment: entry, ac: ac, effect: effect} | |
| 671 | 665 | if err := exec.NavigateRecentRoute(routeTarget); err != nil { | |
| 672 | 666 | if errors.Is(err, errAttachmentTransition) { | |
| 673 | 667 | return | |
| @@ -682,7 +676,6 @@ func (d *Daemon) executePaletteRouteNavigation(ac *attachedClient, entry *sessio | |||
| 682 | 676 | } | |
| 683 | 677 | ||
| 684 | 678 | func (d *Daemon) executePaletteSessionTarget(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, sessionTarget palette.Result) { | |
| 685 | - | sess := entry | |
| 686 | 679 | createdAt, _ := sessionTarget.SessionCreatedAt() | |
| 687 | 680 | expectedCreatedAt := createdAt.UnixNano() | |
| 688 | 681 | name, _ := sessionTarget.SessionName() | |
| @@ -695,7 +688,7 @@ func (d *Daemon) executePaletteSessionTarget(ac *attachedClient, entry *session, | |||
| 695 | 688 | if effect != nil { | |
| 696 | 689 | err = d.switchToTargetForAttachment(effect, target, sessionHandoffGuard{allowSamePeer: true}, "palette-session") | |
| 697 | 690 | } else { | |
| 698 | - | err = d.switchToTarget(sess, ac, target) | |
| 691 | + | err = d.switchToTarget(entry, ac, target) | |
| 699 | 692 | } | |
| 700 | 693 | if errors.Is(err, errAttachmentTransition) { | |
| 701 | 694 | return | |
| @@ -712,9 +705,7 @@ func (d *Daemon) executePaletteSessionTarget(ac *attachedClient, entry *session, | |||
| 712 | 705 | } | |
| 713 | 706 | } | |
| 714 | 707 | ||
| 715 | - | func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, decision paletteDecision) { | |
| 716 | - | sess := entry | |
| 717 | - | cmd, args, routeSnapshot := decision.cmd, decision.args, decision.routeSnapshot | |
| 708 | + | func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, cmd command.Command, args []string, routeSnapshot protocol.RecentRouteSnapshot) { | |
| 718 | 709 | if cmd.Slug == "jump-recent-session" { | |
| 719 | 710 | rank, err := command.ParsePositiveDecimal(args) | |
| 720 | 711 | if err != nil { | |
| @@ -722,7 +713,7 @@ func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effec | |||
| 722 | 713 | d.invalidateRender(entry, ac, true, "palette.go") | |
| 723 | 714 | return | |
| 724 | 715 | } | |
| 725 | - | exec := paletteExec{d: d, sess: sess, attachment: entry, ac: ac, routeSnapshot: routeSnapshot, effect: effect} | |
| 716 | + | exec := paletteExec{d: d, sess: entry, attachment: entry, ac: ac, routeSnapshot: routeSnapshot, effect: effect} | |
| 726 | 717 | if err := exec.JumpRecentSession(rank); err != nil { | |
| 727 | 718 | if errors.Is(err, errAttachmentTransition) { | |
| 728 | 719 | return | |
| @@ -732,7 +723,7 @@ func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effec | |||
| 732 | 723 | return | |
| 733 | 724 | } | |
| 734 | 725 | if d.closeExecutedPalette(ac, effect, generation, rawQuery) { | |
| 735 | - | d.recordPaletteUse(cmd.Code) | |
| 726 | + | d.paletteHistory.record(cmd.Code) | |
| 736 | 727 | if current := ac.currentAttachmentSession(); current != nil { | |
| 737 | 728 | d.invalidateRender(current, ac, true, "palette.go") | |
| 738 | 729 | } | |
| @@ -743,9 +734,9 @@ func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effec | |||
| 743 | 734 | if !attachmentHandoff && !d.closeExecutedPalette(ac, effect, generation, rawQuery) { | |
| 744 | 735 | return | |
| 745 | 736 | } | |
| 746 | - | sess.dispatchMu.Lock() | |
| 747 | - | err := cmd.Run(paletteExec{d: d, sess: sess, ac: ac, effect: effect, redrawClosedPalette: true}, args) | |
| 748 | - | sess.dispatchMu.Unlock() | |
| 737 | + | entry.dispatchMu.Lock() | |
| 738 | + | err := cmd.Run(paletteExec{d: d, sess: entry, ac: ac, effect: effect, redrawClosedPalette: true}, args) | |
| 739 | + | entry.dispatchMu.Unlock() | |
| 749 | 740 | if attachmentHandoff { | |
| 750 | 741 | if current := ac.currentSession(); current != nil { | |
| 751 | 742 | currentToken := current.captureAttachmentCapability(ac, ac.transport()) | |
| @@ -765,9 +756,9 @@ func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effec | |||
| 765 | 756 | } | |
| 766 | 757 | if err != nil { | |
| 767 | 758 | d.log.Error("palette command failed", "err", err, "command", cmd.Code) | |
| 768 | - | d.reportError(sess, paletteCommandNoticeError(cmd, err)) | |
| 759 | + | d.reportError(entry, paletteCommandNoticeError(cmd, err)) | |
| 769 | 760 | } else { | |
| 770 | - | d.recordPaletteUse(cmd.Code) | |
| 761 | + | d.paletteHistory.record(cmd.Code) | |
| 771 | 762 | } | |
| 772 | 763 | } | |
| 773 | 764 | ||
| @@ -351,10 +351,6 @@ func (d *Daemon) paletteCommands() []command.Command { | |||||
| 351 | return out | 351 | return out | ||
| 352 | } | 352 | } | ||
| 353 | 353 | ||||
| 354 | - | func (d *Daemon) recordPaletteUse(code string) { | |||
| 355 | - | d.paletteHistory.record(code) | |||
| 356 | - | } | |||
| 357 | - | ||||
| 358 | // paletteDecisionKind names what handlePaletteInput must do once the palette | 354 | // paletteDecisionKind names what handlePaletteInput must do once the palette | ||
| 359 | // lock is released. | 355 | // lock is released. | ||
| 360 | type paletteDecisionKind int | 356 | type paletteDecisionKind int | ||
| @@ -389,8 +385,8 @@ type paletteDecision struct { | |||||
| 389 | hasCancelInventory bool | 385 | hasCancelInventory bool | ||
| 390 | } | 386 | } | ||
| 391 | 387 | ||||
| 392 | - | // decidePaletteInput applies data to the attachment's palette while holding | 388 | + | // decidePaletteInput takes paletteMu, applies data to the attachment's palette, |
| 393 | - | // paletteMu and returns what the caller must do after the lock is released. | 389 | + | // and returns the action to run after unlocking. |
| 394 | func decidePaletteInput(ac *attachedClient, data []byte) paletteDecision { | 390 | func decidePaletteInput(ac *attachedClient, data []byte) paletteDecision { | ||
| 395 | var cmd command.Command | 391 | var cmd command.Command | ||
| 396 | var sessionTarget palette.Result | 392 | var sessionTarget palette.Result | ||
| @@ -590,14 +586,13 @@ func (d *Daemon) executePaletteDecision(ac *attachedClient, entry *session, effe | |||||
| 590 | case paletteDecisionSessionTarget: | 586 | case paletteDecisionSessionTarget: | ||
| 591 | d.executePaletteSessionTarget(ac, entry, effect, generation, rawQuery, decision.sessionTarget) | 587 | d.executePaletteSessionTarget(ac, entry, effect, generation, rawQuery, decision.sessionTarget) | ||
| 592 | case paletteDecisionCommand: | 588 | case paletteDecisionCommand: | ||
| 593 | - | d.executePaletteCommand(ac, entry, effect, generation, rawQuery, decision) | 589 | + | d.executePaletteCommand(ac, entry, effect, generation, rawQuery, decision.cmd, decision.args, decision.routeSnapshot) |
| 594 | } | 590 | } | ||
| 595 | } | 591 | } | ||
| 596 | 592 | ||||
| 597 | func (d *Daemon) executePaletteCreateDestination(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, createDestination palette.Result) { | 593 | func (d *Daemon) executePaletteCreateDestination(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, createDestination palette.Result) { | ||
| 598 | - | sess := entry | |||
| 599 | name, _, _, _, _, _ := createDestination.CreateSessionDestination() | 594 | name, _, _, _, _, _ := createDestination.CreateSessionDestination() | ||
| 600 | - | exec := paletteExec{d: d, sess: sess, attachment: entry, ac: ac, effect: effect} | 595 | + | exec := paletteExec{d: d, sess: entry, attachment: entry, ac: ac, effect: effect} |
| 601 | if err := exec.validateCreateSessionDestination(effect, createDestination); err != nil { | 596 | if err := exec.validateCreateSessionDestination(effect, createDestination); err != nil { | ||
| 602 | ac.paletteFailure(generation, rawQuery, errCreateDestinationUnavailable.Error()) | 597 | ac.paletteFailure(generation, rawQuery, errCreateDestinationUnavailable.Error()) | ||
| 603 | d.invalidateRender(entry, ac, true, "palette.go") | 598 | d.invalidateRender(entry, ac, true, "palette.go") | ||
| @@ -666,8 +661,7 @@ func (d *Daemon) executePaletteImportedSession(ac *attachedClient, entry *sessio | |||||
| 666 | } | 661 | } | ||
| 667 | 662 | ||||
| 668 | func (d *Daemon) executePaletteRouteNavigation(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, routeTarget protocol.RouteNavigationAction) { | 663 | func (d *Daemon) executePaletteRouteNavigation(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, routeTarget protocol.RouteNavigationAction) { | ||
| 669 | - | sess := entry | 664 | + | exec := paletteExec{d: d, sess: entry, attachment: entry, ac: ac, effect: effect} |
| 670 | - | exec := paletteExec{d: d, sess: sess, attachment: entry, ac: ac, effect: effect} | |||
| 671 | if err := exec.NavigateRecentRoute(routeTarget); err != nil { | 665 | if err := exec.NavigateRecentRoute(routeTarget); err != nil { | ||
| 672 | if errors.Is(err, errAttachmentTransition) { | 666 | if errors.Is(err, errAttachmentTransition) { | ||
| 673 | return | 667 | return | ||
| @@ -682,7 +676,6 @@ func (d *Daemon) executePaletteRouteNavigation(ac *attachedClient, entry *sessio | |||||
| 682 | } | 676 | } | ||
| 683 | 677 | ||||
| 684 | func (d *Daemon) executePaletteSessionTarget(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, sessionTarget palette.Result) { | 678 | func (d *Daemon) executePaletteSessionTarget(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, sessionTarget palette.Result) { | ||
| 685 | - | sess := entry | |||
| 686 | createdAt, _ := sessionTarget.SessionCreatedAt() | 679 | createdAt, _ := sessionTarget.SessionCreatedAt() | ||
| 687 | expectedCreatedAt := createdAt.UnixNano() | 680 | expectedCreatedAt := createdAt.UnixNano() | ||
| 688 | name, _ := sessionTarget.SessionName() | 681 | name, _ := sessionTarget.SessionName() | ||
| @@ -695,7 +688,7 @@ func (d *Daemon) executePaletteSessionTarget(ac *attachedClient, entry *session, | |||||
| 695 | if effect != nil { | 688 | if effect != nil { | ||
| 696 | err = d.switchToTargetForAttachment(effect, target, sessionHandoffGuard{allowSamePeer: true}, "palette-session") | 689 | err = d.switchToTargetForAttachment(effect, target, sessionHandoffGuard{allowSamePeer: true}, "palette-session") | ||
| 697 | } else { | 690 | } else { | ||
| 698 | - | err = d.switchToTarget(sess, ac, target) | 691 | + | err = d.switchToTarget(entry, ac, target) |
| 699 | } | 692 | } | ||
| 700 | if errors.Is(err, errAttachmentTransition) { | 693 | if errors.Is(err, errAttachmentTransition) { | ||
| 701 | return | 694 | return | ||
| @@ -712,9 +705,7 @@ func (d *Daemon) executePaletteSessionTarget(ac *attachedClient, entry *session, | |||||
| 712 | } | 705 | } | ||
| 713 | } | 706 | } | ||
| 714 | 707 | ||||
| 715 | - | func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, decision paletteDecision) { | 708 | + | func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effect *attachmentEffect, generation uint64, rawQuery string, cmd command.Command, args []string, routeSnapshot protocol.RecentRouteSnapshot) { |
| 716 | - | sess := entry | |||
| 717 | - | cmd, args, routeSnapshot := decision.cmd, decision.args, decision.routeSnapshot | |||
| 718 | if cmd.Slug == "jump-recent-session" { | 709 | if cmd.Slug == "jump-recent-session" { | ||
| 719 | rank, err := command.ParsePositiveDecimal(args) | 710 | rank, err := command.ParsePositiveDecimal(args) | ||
| 720 | if err != nil { | 711 | if err != nil { | ||
| @@ -722,7 +713,7 @@ func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effec | |||||
| 722 | d.invalidateRender(entry, ac, true, "palette.go") | 713 | d.invalidateRender(entry, ac, true, "palette.go") | ||
| 723 | return | 714 | return | ||
| 724 | } | 715 | } | ||
| 725 | - | exec := paletteExec{d: d, sess: sess, attachment: entry, ac: ac, routeSnapshot: routeSnapshot, effect: effect} | 716 | + | exec := paletteExec{d: d, sess: entry, attachment: entry, ac: ac, routeSnapshot: routeSnapshot, effect: effect} |
| 726 | if err := exec.JumpRecentSession(rank); err != nil { | 717 | if err := exec.JumpRecentSession(rank); err != nil { | ||
| 727 | if errors.Is(err, errAttachmentTransition) { | 718 | if errors.Is(err, errAttachmentTransition) { | ||
| 728 | return | 719 | return | ||
| @@ -732,7 +723,7 @@ func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effec | |||||
| 732 | return | 723 | return | ||
| 733 | } | 724 | } | ||
| 734 | if d.closeExecutedPalette(ac, effect, generation, rawQuery) { | 725 | if d.closeExecutedPalette(ac, effect, generation, rawQuery) { | ||
| 735 | - | d.recordPaletteUse(cmd.Code) | 726 | + | d.paletteHistory.record(cmd.Code) |
| 736 | if current := ac.currentAttachmentSession(); current != nil { | 727 | if current := ac.currentAttachmentSession(); current != nil { | ||
| 737 | d.invalidateRender(current, ac, true, "palette.go") | 728 | d.invalidateRender(current, ac, true, "palette.go") | ||
| 738 | } | 729 | } | ||
| @@ -743,9 +734,9 @@ func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effec | |||||
| 743 | if !attachmentHandoff && !d.closeExecutedPalette(ac, effect, generation, rawQuery) { | 734 | if !attachmentHandoff && !d.closeExecutedPalette(ac, effect, generation, rawQuery) { | ||
| 744 | return | 735 | return | ||
| 745 | } | 736 | } | ||
| 746 | - | sess.dispatchMu.Lock() | 737 | + | entry.dispatchMu.Lock() |
| 747 | - | err := cmd.Run(paletteExec{d: d, sess: sess, ac: ac, effect: effect, redrawClosedPalette: true}, args) | 738 | + | err := cmd.Run(paletteExec{d: d, sess: entry, ac: ac, effect: effect, redrawClosedPalette: true}, args) |
| 748 | - | sess.dispatchMu.Unlock() | 739 | + | entry.dispatchMu.Unlock() |
| 749 | if attachmentHandoff { | 740 | if attachmentHandoff { | ||
| 750 | if current := ac.currentSession(); current != nil { | 741 | if current := ac.currentSession(); current != nil { | ||
| 751 | currentToken := current.captureAttachmentCapability(ac, ac.transport()) | 742 | currentToken := current.captureAttachmentCapability(ac, ac.transport()) | ||
| @@ -765,9 +756,9 @@ func (d *Daemon) executePaletteCommand(ac *attachedClient, entry *session, effec | |||||
| 765 | } | 756 | } | ||
| 766 | if err != nil { | 757 | if err != nil { | ||
| 767 | d.log.Error("palette command failed", "err", err, "command", cmd.Code) | 758 | d.log.Error("palette command failed", "err", err, "command", cmd.Code) | ||
| 768 | - | d.reportError(sess, paletteCommandNoticeError(cmd, err)) | 759 | + | d.reportError(entry, paletteCommandNoticeError(cmd, err)) |
| 769 | } else { | 760 | } else { | ||
| 770 | - | d.recordPaletteUse(cmd.Code) | 761 | + | d.paletteHistory.record(cmd.Code) |
| 771 | } | 762 | } | ||
| 772 | } | 763 | } | ||
| 773 | 764 | ||||
M internal/usecase/daemon/palette_test.go +6 -6
| @@ -936,11 +936,11 @@ func TestCommandByEffectiveCodeExcludesAPIOnlyCommands(t *testing.T) { | |||
| 936 | 936 | ||
| 937 | 937 | func TestPaletteRecentCommandsNewestFirstThenRegistryOrder(t *testing.T) { | |
| 938 | 938 | d := &Daemon{} | |
| 939 | - | d.recordPaletteUse("SSP") | |
| 940 | - | d.recordPaletteUse("NXT") | |
| 939 | + | d.paletteHistory.record("SSP") | |
| 940 | + | d.paletteHistory.record("NXT") | |
| 941 | 941 | // STALE is not a registered command code; it must be dropped from output. | |
| 942 | - | d.recordPaletteUse("STALE") | |
| 943 | - | d.recordPaletteUse("SSP") | |
| 942 | + | d.paletteHistory.record("STALE") | |
| 943 | + | d.paletteHistory.record("SSP") | |
| 944 | 944 | ||
| 945 | 945 | commands := d.paletteCommands() | |
| 946 | 946 | codes := make([]string, len(commands)) | |
| @@ -960,7 +960,7 @@ func TestPaletteRecencyCanBeUpdatedConcurrently(t *testing.T) { | |||
| 960 | 960 | wg.Add(1) | |
| 961 | 961 | go func(code string) { | |
| 962 | 962 | defer wg.Done() | |
| 963 | - | d.recordPaletteUse(code) | |
| 963 | + | d.paletteHistory.record(code) | |
| 964 | 964 | _ = d.paletteCommands() | |
| 965 | 965 | }(code) | |
| 966 | 966 | } | |
| @@ -1228,7 +1228,7 @@ func TestPaletteTabCompletesSelectedCommandWithoutForwardingToPTY(t *testing.T) | |||
| 1228 | 1228 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 1229 | 1229 | invs := installPaletteInvalidationObserver(sess) | |
| 1230 | 1230 | if tt.recent != "" { | |
| 1231 | - | d.recordPaletteUse(tt.recent) | |
| 1231 | + | d.paletteHistory.record(tt.recent) | |
| 1232 | 1232 | } | |
| 1233 | 1233 | if tt.config != nil { | |
| 1234 | 1234 | d.ApplyConfig(domain.Config{Codes: tt.config}) | |
| @@ -936,11 +936,11 @@ func TestCommandByEffectiveCodeExcludesAPIOnlyCommands(t *testing.T) { | |||||
| 936 | 936 | ||||
| 937 | func TestPaletteRecentCommandsNewestFirstThenRegistryOrder(t *testing.T) { | 937 | func TestPaletteRecentCommandsNewestFirstThenRegistryOrder(t *testing.T) { | ||
| 938 | d := &Daemon{} | 938 | d := &Daemon{} | ||
| 939 | - | d.recordPaletteUse("SSP") | 939 | + | d.paletteHistory.record("SSP") |
| 940 | - | d.recordPaletteUse("NXT") | 940 | + | d.paletteHistory.record("NXT") |
| 941 | // STALE is not a registered command code; it must be dropped from output. | 941 | // STALE is not a registered command code; it must be dropped from output. | ||
| 942 | - | d.recordPaletteUse("STALE") | 942 | + | d.paletteHistory.record("STALE") |
| 943 | - | d.recordPaletteUse("SSP") | 943 | + | d.paletteHistory.record("SSP") |
| 944 | 944 | ||||
| 945 | commands := d.paletteCommands() | 945 | commands := d.paletteCommands() | ||
| 946 | codes := make([]string, len(commands)) | 946 | codes := make([]string, len(commands)) | ||
| @@ -960,7 +960,7 @@ func TestPaletteRecencyCanBeUpdatedConcurrently(t *testing.T) { | |||||
| 960 | wg.Add(1) | 960 | wg.Add(1) | ||
| 961 | go func(code string) { | 961 | go func(code string) { | ||
| 962 | defer wg.Done() | 962 | defer wg.Done() | ||
| 963 | - | d.recordPaletteUse(code) | 963 | + | d.paletteHistory.record(code) |
| 964 | _ = d.paletteCommands() | 964 | _ = d.paletteCommands() | ||
| 965 | }(code) | 965 | }(code) | ||
| 966 | } | 966 | } | ||
| @@ -1228,7 +1228,7 @@ func TestPaletteTabCompletesSelectedCommandWithoutForwardingToPTY(t *testing.T) | |||||
| 1228 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | 1228 | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | ||
| 1229 | invs := installPaletteInvalidationObserver(sess) | 1229 | invs := installPaletteInvalidationObserver(sess) | ||
| 1230 | if tt.recent != "" { | 1230 | if tt.recent != "" { | ||
| 1231 | - | d.recordPaletteUse(tt.recent) | 1231 | + | d.paletteHistory.record(tt.recent) |
| 1232 | } | 1232 | } | ||
| 1233 | if tt.config != nil { | 1233 | if tt.config != nil { | ||
| 1234 | d.ApplyConfig(domain.Config{Codes: tt.config}) | 1234 | d.ApplyConfig(domain.Config{Codes: tt.config}) | ||
A internal/usecase/daemon/process_inspector_test.go +83 -0
| @@ -0,0 +1,83 @@ | |||
| 1 | + | package daemon | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "testing" | |
| 5 | + | ||
| 6 | + | "github.com/stretchr/testify/require" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | func TestProcessInspectorOptionsComposition(t *testing.T) { | |
| 10 | + | cwd := func(int) (string, error) { return "/override", nil } | |
| 11 | + | base := &processInspectorFake{ | |
| 12 | + | cwd: func(int) (string, error) { return "/base", nil }, | |
| 13 | + | comm: func(int) (string, error) { return "vim", nil }, | |
| 14 | + | } | |
| 15 | + | ||
| 16 | + | tests := []struct { | |
| 17 | + | name string | |
| 18 | + | opts []Option | |
| 19 | + | wantDetails bool | |
| 20 | + | wantCwd string | |
| 21 | + | wantComm string | |
| 22 | + | wantCommErr error | |
| 23 | + | }{ | |
| 24 | + | {name: "none", wantDetails: false}, | |
| 25 | + | { | |
| 26 | + | name: "cwd reader only", | |
| 27 | + | opts: []Option{WithCwdReader(cwd)}, | |
| 28 | + | wantDetails: false, | |
| 29 | + | wantCwd: "/override", | |
| 30 | + | wantCommErr: errProcessInspectionUnavailable, | |
| 31 | + | }, | |
| 32 | + | { | |
| 33 | + | name: "inspector only", | |
| 34 | + | opts: []Option{WithProcessInspector(base)}, | |
| 35 | + | wantDetails: true, | |
| 36 | + | wantCwd: "/base", | |
| 37 | + | wantComm: "vim", | |
| 38 | + | }, | |
| 39 | + | { | |
| 40 | + | name: "inspector then cwd reader keeps base details", | |
| 41 | + | opts: []Option{WithProcessInspector(base), WithCwdReader(cwd)}, | |
| 42 | + | wantDetails: true, | |
| 43 | + | wantCwd: "/override", | |
| 44 | + | wantComm: "vim", | |
| 45 | + | }, | |
| 46 | + | { | |
| 47 | + | name: "cwd reader then inspector replaces override", | |
| 48 | + | opts: []Option{WithCwdReader(cwd), WithProcessInspector(base)}, | |
| 49 | + | wantDetails: true, | |
| 50 | + | wantCwd: "/base", | |
| 51 | + | wantComm: "vim", | |
| 52 | + | }, | |
| 53 | + | { | |
| 54 | + | name: "repeated cwd readers do not stack", | |
| 55 | + | opts: []Option{WithProcessInspector(base), WithCwdReader(cwd), WithCwdReader(cwd)}, | |
| 56 | + | wantDetails: true, | |
| 57 | + | wantCwd: "/override", | |
| 58 | + | wantComm: "vim", | |
| 59 | + | }, | |
| 60 | + | } | |
| 61 | + | for _, tt := range tests { | |
| 62 | + | t.Run(tt.name, func(t *testing.T) { | |
| 63 | + | d := &Daemon{} | |
| 64 | + | for _, opt := range tt.opts { | |
| 65 | + | opt(d) | |
| 66 | + | } | |
| 67 | + | require.Equal(t, tt.wantDetails, d.hasProcessDetails()) | |
| 68 | + | if d.proc == nil { | |
| 69 | + | return | |
| 70 | + | } | |
| 71 | + | got, err := d.proc.Cwd(1) | |
| 72 | + | require.NoError(t, err) | |
| 73 | + | require.Equal(t, tt.wantCwd, got) | |
| 74 | + | comm, err := d.proc.Comm(1) | |
| 75 | + | require.ErrorIs(t, err, tt.wantCommErr) | |
| 76 | + | require.Equal(t, tt.wantComm, comm) | |
| 77 | + | if o, ok := d.proc.(cwdOverrideInspector); ok { | |
| 78 | + | _, nested := o.base.(cwdOverrideInspector) | |
| 79 | + | require.False(t, nested) | |
| 80 | + | } | |
| 81 | + | }) | |
| 82 | + | } | |
| 83 | + | } | |
| @@ -0,0 +1,83 @@ | |||||
| 1 | + | package daemon | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "testing" | |||
| 5 | + | ||||
| 6 | + | "github.com/stretchr/testify/require" | |||
| 7 | + | ) | |||
| 8 | + | ||||
| 9 | + | func TestProcessInspectorOptionsComposition(t *testing.T) { | |||
| 10 | + | cwd := func(int) (string, error) { return "/override", nil } | |||
| 11 | + | base := &processInspectorFake{ | |||
| 12 | + | cwd: func(int) (string, error) { return "/base", nil }, | |||
| 13 | + | comm: func(int) (string, error) { return "vim", nil }, | |||
| 14 | + | } | |||
| 15 | + | ||||
| 16 | + | tests := []struct { | |||
| 17 | + | name string | |||
| 18 | + | opts []Option | |||
| 19 | + | wantDetails bool | |||
| 20 | + | wantCwd string | |||
| 21 | + | wantComm string | |||
| 22 | + | wantCommErr error | |||
| 23 | + | }{ | |||
| 24 | + | {name: "none", wantDetails: false}, | |||
| 25 | + | { | |||
| 26 | + | name: "cwd reader only", | |||
| 27 | + | opts: []Option{WithCwdReader(cwd)}, | |||
| 28 | + | wantDetails: false, | |||
| 29 | + | wantCwd: "/override", | |||
| 30 | + | wantCommErr: errProcessInspectionUnavailable, | |||
| 31 | + | }, | |||
| 32 | + | { | |||
| 33 | + | name: "inspector only", | |||
| 34 | + | opts: []Option{WithProcessInspector(base)}, | |||
| 35 | + | wantDetails: true, | |||
| 36 | + | wantCwd: "/base", | |||
| 37 | + | wantComm: "vim", | |||
| 38 | + | }, | |||
| 39 | + | { | |||
| 40 | + | name: "inspector then cwd reader keeps base details", | |||
| 41 | + | opts: []Option{WithProcessInspector(base), WithCwdReader(cwd)}, | |||
| 42 | + | wantDetails: true, | |||
| 43 | + | wantCwd: "/override", | |||
| 44 | + | wantComm: "vim", | |||
| 45 | + | }, | |||
| 46 | + | { | |||
| 47 | + | name: "cwd reader then inspector replaces override", | |||
| 48 | + | opts: []Option{WithCwdReader(cwd), WithProcessInspector(base)}, | |||
| 49 | + | wantDetails: true, | |||
| 50 | + | wantCwd: "/base", | |||
| 51 | + | wantComm: "vim", | |||
| 52 | + | }, | |||
| 53 | + | { | |||
| 54 | + | name: "repeated cwd readers do not stack", | |||
| 55 | + | opts: []Option{WithProcessInspector(base), WithCwdReader(cwd), WithCwdReader(cwd)}, | |||
| 56 | + | wantDetails: true, | |||
| 57 | + | wantCwd: "/override", | |||
| 58 | + | wantComm: "vim", | |||
| 59 | + | }, | |||
| 60 | + | } | |||
| 61 | + | for _, tt := range tests { | |||
| 62 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 63 | + | d := &Daemon{} | |||
| 64 | + | for _, opt := range tt.opts { | |||
| 65 | + | opt(d) | |||
| 66 | + | } | |||
| 67 | + | require.Equal(t, tt.wantDetails, d.hasProcessDetails()) | |||
| 68 | + | if d.proc == nil { | |||
| 69 | + | return | |||
| 70 | + | } | |||
| 71 | + | got, err := d.proc.Cwd(1) | |||
| 72 | + | require.NoError(t, err) | |||
| 73 | + | require.Equal(t, tt.wantCwd, got) | |||
| 74 | + | comm, err := d.proc.Comm(1) | |||
| 75 | + | require.ErrorIs(t, err, tt.wantCommErr) | |||
| 76 | + | require.Equal(t, tt.wantComm, comm) | |||
| 77 | + | if o, ok := d.proc.(cwdOverrideInspector); ok { | |||
| 78 | + | _, nested := o.base.(cwdOverrideInspector) | |||
| 79 | + | require.False(t, nested) | |||
| 80 | + | } | |||
| 81 | + | }) | |||
| 82 | + | } | |||
| 83 | + | } | |||
M internal/usecase/daemon/snapshot.go +1 -1
| @@ -47,7 +47,7 @@ type snapshotCapture struct { | |||
| 47 | 47 | publicationContext context.Context | |
| 48 | 48 | sealedRefs map[*vt.HistoryChunk]snapcodec.ObjectRef // set by the single encoder worker | |
| 49 | 49 | coordinatorDiscarded bool // guarded by session.snapshotMu | |
| 50 | - | // normalWorkerAdmitted is set before snapshotJobs admission and immutable | |
| 50 | + | // normalWorkerAdmitted is set before snapshots.jobs admission and immutable | |
| 51 | 51 | // until completion; final-queue captures leave it false. | |
| 52 | 52 | normalWorkerAdmitted bool | |
| 53 | 53 | finishOnce sync.Once | |
| @@ -47,7 +47,7 @@ type snapshotCapture struct { | |||||
| 47 | publicationContext context.Context | 47 | publicationContext context.Context | ||
| 48 | sealedRefs map[*vt.HistoryChunk]snapcodec.ObjectRef // set by the single encoder worker | 48 | sealedRefs map[*vt.HistoryChunk]snapcodec.ObjectRef // set by the single encoder worker | ||
| 49 | coordinatorDiscarded bool // guarded by session.snapshotMu | 49 | coordinatorDiscarded bool // guarded by session.snapshotMu | ||
| 50 | - | // normalWorkerAdmitted is set before snapshotJobs admission and immutable | 50 | + | // normalWorkerAdmitted is set before snapshots.jobs admission and immutable |
| 51 | // until completion; final-queue captures leave it false. | 51 | // until completion; final-queue captures leave it false. | ||
| 52 | normalWorkerAdmitted bool | 52 | normalWorkerAdmitted bool | ||
| 53 | finishOnce sync.Once | 53 | finishOnce sync.Once | ||
M internal/usecase/daemon/snapshot_worker.go +1 -1
| @@ -275,7 +275,7 @@ func (d *Daemon) requestDurableWriterStop() (context.CancelFunc, <-chan struct{} | |||
| 275 | 275 | } | |
| 276 | 276 | ||
| 277 | 277 | // durableWriterFailureNames includes admitted buffered captures as well as the | |
| 278 | - | // active and final queues. snapshotAdmitted tracks normal captures from queue | |
| 278 | + | // active and final queues. snapshots.admitted tracks normal captures from queue | |
| 279 | 279 | // admission through completion, so worker dequeue cannot make one disappear. | |
| 280 | 280 | func (d *Daemon) durableWriterFailureNames() []string { | |
| 281 | 281 | d.snapshots.mu.Lock() | |
| @@ -275,7 +275,7 @@ func (d *Daemon) requestDurableWriterStop() (context.CancelFunc, <-chan struct{} | |||||
| 275 | } | 275 | } | ||
| 276 | 276 | ||||
| 277 | // durableWriterFailureNames includes admitted buffered captures as well as the | 277 | // durableWriterFailureNames includes admitted buffered captures as well as the | ||
| 278 | - | // active and final queues. snapshotAdmitted tracks normal captures from queue | 278 | + | // active and final queues. snapshots.admitted tracks normal captures from queue |
| 279 | // admission through completion, so worker dequeue cannot make one disappear. | 279 | // admission through completion, so worker dequeue cannot make one disappear. | ||
| 280 | func (d *Daemon) durableWriterFailureNames() []string { | 280 | func (d *Daemon) durableWriterFailureNames() []string { | ||
| 281 | d.snapshots.mu.Lock() | 281 | d.snapshots.mu.Lock() | ||