test(cli): keep the indefinite recovery wait test off the host's DNS

TestNetworkShutdownCancelsIndefiniteRecoveryWait relied on an empty
upstream map never recovering. Since 73db7adc an empty pool checks
upstream.os instead, so the test probed the host's real OS resolver.
On a host with working DNS that check succeeds, recovery completes, and
it reads the upstream monitor the test never built.

Recover against a loopback port nothing listens on, so the wait can
never end on its own, and give the prog the upstream monitor that run
always creates before network monitoring can start a recovery. The
panic is test-only: production never reaches recovery without one.
This commit is contained in:
Cuong Manh Le committed 2026-10-08 21:46:01 +07:00
1 parent fc9ad824da
commit 1f875af472
1 file changed
+13 -3
+13 -3
View File
@@ -176,13 +176,23 @@ func TestNetworkShutdownCancelsIndefiniteRecoveryWait(t *testing.T) {
oldReset, oldIntercept := recoveryResetDNSFn, dnsIntercept
dnsIntercept = false
defer func() { recoveryResetDNSFn, dnsIntercept = oldReset, oldIntercept }()
p := &prog{cfg: &ctrld.Config{}}
// The only upstream is a loopback port nothing listens on, so recovery
// can never succeed. An empty upstream map no longer does that: recovery
// then checks upstream.os, and on a host with working DNS that answers,
// completes the recovery and turns the test into a probe of the host.
dead := &ctrld.UpstreamConfig{Type: ctrld.ResolverTypeLegacy, Endpoint: deadUDPAddr(t), Timeout: 300}
dead.Init(context.Background())
cfg := &ctrld.Config{Upstream: map[string]*ctrld.UpstreamConfig{"0": dead}}
p := &prog{cfg: cfg}
p.logger.Store(mainLog.Load())
// Recovery reads the upstream monitor, which run creates before network
// monitoring can start any recovery.
p.um = newUpstreamMonitor(cfg, p.logger.Load())
entered := make(chan struct{})
recoveryResetDNSFn = func(*prog, bool, bool) { close(entered) }
recovered := make(chan struct{})
// An empty upstream map can never recover. Shutdown must cancel that wait
// without waiting on a mutex held for the recovery's entire lifetime.
// Shutdown must cancel that wait without waiting on a mutex held for the
// recovery's entire lifetime.
go func() { p.handleRecovery(RecoveryReasonRegularFailure); close(recovered) }()
waitNetworkShutdownSignal(t, entered, "recovery reset seam")
closed := make(chan struct{})