cli + daemon: move test seams off package globals onto injected structs

CLI: introduce internal/cli.deps which owns every RPC/SSH/host-command
seam the tree used to reach through mutable package vars. Command
builders, orchestrators, and the completion helpers become methods on
*deps. Tests construct their own deps per case, so fakes no longer leak
across cases and tests are free to run in parallel.

Daemon: move workspaceInspectRepoFunc + workspaceImportFunc onto the
Daemon struct (workspaceInspectRepo / workspaceImport), mirroring the
existing guestWaitForSSH / guestDial pattern. Workspace-prepare tests
drop t.Parallel() guards now that they no longer mutate process-wide
state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-19 19:03:55 -03:00
parent d38f580e00
commit c42fcbe012
No known key found for this signature in database
GPG key ID: 33112E6833C34679
19 changed files with 664 additions and 733 deletions

View file

@ -18,7 +18,7 @@ import (
// ensureDaemon pings the socket; on miss it auto-starts bangerd, on
// version mismatch it restarts. Every CLI command that needs to talk
// to the daemon routes through here.
func ensureDaemon(ctx context.Context) (paths.Layout, model.DaemonConfig, error) {
func (d *deps) ensureDaemon(ctx context.Context) (paths.Layout, model.DaemonConfig, error) {
layout, err := paths.Resolve()
if err != nil {
return paths.Layout{}, model.DaemonConfig{}, err
@ -27,16 +27,16 @@ func ensureDaemon(ctx context.Context) (paths.Layout, model.DaemonConfig, error)
if err != nil {
return paths.Layout{}, model.DaemonConfig{}, err
}
if ping, err := daemonPingFunc(ctx, layout.SocketPath); err == nil {
if daemonOutdated(ping.PID) {
if err := restartDaemon(ctx, layout, ping.PID); err != nil {
if ping, err := d.daemonPing(ctx, layout.SocketPath); err == nil {
if d.daemonOutdated(ping.PID) {
if err := d.restartDaemon(ctx, layout, ping.PID); err != nil {
return paths.Layout{}, model.DaemonConfig{}, err
}
return layout, cfg, nil
}
return layout, cfg, nil
}
if err := startDaemon(ctx, layout); err != nil {
if err := d.startDaemon(ctx, layout); err != nil {
return paths.Layout{}, model.DaemonConfig{}, err
}
return layout, cfg, nil
@ -47,11 +47,11 @@ func ensureDaemon(ctx context.Context) (paths.Layout, model.DaemonConfig, error)
// session still holds a handle to an old daemon. os.SameFile compares
// inode + dev, so a fresh binary at the same path registers as
// different.
func daemonOutdated(pid int) bool {
func (d *deps) daemonOutdated(pid int) bool {
if pid <= 0 {
return false
}
daemonBin, err := bangerdPathFunc()
daemonBin, err := d.bangerdPath()
if err != nil {
return false
}
@ -59,20 +59,20 @@ func daemonOutdated(pid int) bool {
if err != nil {
return false
}
runningInfo, err := os.Stat(daemonExePath(pid))
runningInfo, err := os.Stat(d.daemonExePath(pid))
if err != nil {
return false
}
return !os.SameFile(currentInfo, runningInfo)
}
func restartDaemon(ctx context.Context, layout paths.Layout, pid int) error {
func (d *deps) restartDaemon(ctx context.Context, layout paths.Layout, pid int) error {
stopCtx, cancel := context.WithTimeout(ctx, 2*time.Second)
defer cancel()
_, _ = rpc.Call[api.ShutdownResult](stopCtx, layout.SocketPath, "shutdown", api.Empty{})
if waitForPIDExit(pid, 2*time.Second) {
return startDaemon(ctx, layout)
return d.startDaemon(ctx, layout)
}
if proc, err := os.FindProcess(pid); err == nil {
_ = proc.Signal(syscall.SIGTERM)
@ -80,7 +80,7 @@ func restartDaemon(ctx context.Context, layout paths.Layout, pid int) error {
if !waitForPIDExit(pid, 2*time.Second) {
return fmt.Errorf("timed out restarting stale daemon pid %d", pid)
}
return startDaemon(ctx, layout)
return d.startDaemon(ctx, layout)
}
func waitForPIDExit(pid int, timeout time.Duration) bool {
@ -105,7 +105,7 @@ func pidRunning(pid int) bool {
return proc.Signal(syscall.Signal(0)) == nil
}
func startDaemon(ctx context.Context, layout paths.Layout) error {
func (d *deps) startDaemon(ctx context.Context, layout paths.Layout) error {
if err := paths.Ensure(layout); err != nil {
return err
}