watcher: a stop during the first scan sends no startup notification (closes #271)
check / check (push) Waiting to run

Run sent the startup notification right after the first scan without
checking the context. RunOnce returns as usual when shutdown cuts the
scan short, so a stop during the first scan sent "has started and
completed its initial scan" to every endpoint as the process shut down.
Run now sends it only when the context was not cancelled.

The new test cancels the context before Run starts, so the scan never
sends its lookup; a stop part way through a scan follows the same path.

Model: opus-5-5
This commit is contained in:
2026-10-13 17:45:12 +00:00
parent 79761c29e1
commit bb343bd44d
3 changed files with 32 additions and 1 deletions
+2
View File
@@ -19,6 +19,8 @@ trial run of the finished image: https://git.eeqj.de/sneak/dnswatcher/issues/149
# Completed Steps # Completed Steps
- 2026-10-13: a stop before the first scan completes sends no startup
notification; a scan that ran to the end still sends it (closes #271).
- 2026-10-13: a nameserver leaving a domain's delegation sends only NS Change; - 2026-10-13: a nameserver leaving a domain's delegation sends only NS Change;
one leaving a hostname's zone sends NS Change, not NS Failure (closes #262). one leaving a hostname's zone sends NS Change, not NS Failure (closes #262).
- 2026-10-13: `DNSWATCHER_METRICS_USERNAME` set without - 2026-10-13: `DNSWATCHER_METRICS_USERNAME` set without
+24
View File
@@ -83,6 +83,30 @@ func TestCancelledCheckSavesNothing(t *testing.T) {
} }
} }
// TestStopBeforeFirstScanSendsNoStartupNotification runs a watcher with
// the startup notification on and its context already cancelled, so the
// first scan is cut short before its lookup is sent. The scan did not
// complete, so no startup notification may be sent.
func TestStopBeforeFirstScanSendsNoStartupNotification(t *testing.T) {
t.Parallel()
cfg := defaultTestConfig(t)
cfg.Hostnames = []string{testHost}
cfg.SendTestNotification = true
w, deps := newTestWatcher(t, cfg)
ctx, cancel := context.WithCancel(t.Context())
cancel()
w.Run(ctx)
notifications := deps.notifier.getNotifications()
if len(notifications) != 0 {
t.Errorf("sent %v, want no notifications", notifications)
}
}
// newLoggingWatcher returns a watcher for a domain and a hostname, with // newLoggingWatcher returns a watcher for a domain and a hostname, with
// the real resolver, that writes what it logs at warning level or above // the real resolver, that writes what it logs at warning level or above
// into the returned buffer. // into the returned buffer.
+6 -1
View File
@@ -133,7 +133,12 @@ func (w *Watcher) Run(ctx context.Context) {
w.cleanupRemovedTargets() w.cleanupRemovedTargets()
w.RunOnce(ctx) w.RunOnce(ctx)
w.maybeSendTestNotification(ctx)
// RunOnce returns as usual when shutdown cuts the scan short, and
// the startup notification says the scan completed.
if ctx.Err() == nil {
w.maybeSendTestNotification(ctx)
}
dnsTicker := time.NewTicker(w.config.DNSInterval) dnsTicker := time.NewTicker(w.config.DNSInterval)
tlsTicker := time.NewTicker(w.config.TLSInterval) tlsTicker := time.NewTicker(w.config.TLSInterval)