From 3e209bfe4e7ba8583ec342879be19b1a5049022e Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Fri, 2 Oct 2026 16:03:01 +0200 Subject: [PATCH] Drive the archive reopen debounce test from a clock (closes #190) The archive writer's reopen debounce test made two writes that had to land inside the real 2-second window, then slept 2.1 seconds to cross it, so a slow host could turn correct code red. The archive writer now reads the time for its reopen debounce from a clock field, time.Now in production, and the test moves that clock instead of sleeping: two writes at one instant open the file once, and a write one debounce later closes and reopens it once. Removing the debounce check fails the test. This was the last test whose result depended on real elapsed time. Model: opus-5-5 --- internal/delivery/export_test.go | 6 ++++++ internal/delivery/target_database_archive.go | 9 +++++++-- internal/delivery/target_database_test.go | 15 +++++++++++---- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/internal/delivery/export_test.go b/internal/delivery/export_test.go index 145069c..40585aa 100644 --- a/internal/delivery/export_test.go +++ b/internal/delivery/export_test.go @@ -512,6 +512,12 @@ func (e *ExportArchiveWriter) Reopen( return e.w.reopen(expiry) } +// SetNow replaces the clock the writer measures its reopen +// debounce on. +func (e *ExportArchiveWriter) SetNow(now func() time.Time) { + e.w.now = now +} + // Reopens reports how many times the file has been opened. func (e *ExportArchiveWriter) Reopens() int { return e.w.reopens diff --git a/internal/delivery/target_database_archive.go b/internal/delivery/target_database_archive.go index a114276..4730602 100644 --- a/internal/delivery/target_database_archive.go +++ b/internal/delivery/target_database_archive.go @@ -194,6 +194,10 @@ type archiveWriter struct { lastReopen time.Time reopens int + // now is the clock the reopen debounce is measured on. It is + // time.Now outside tests. + now func() time.Time + // evicted marks a writer that has been removed from the // registry. Its handle is closed and it must never open the // file again: nothing holds it any more, so a reopen would @@ -228,6 +232,7 @@ func newArchiveWriter( path: path, log: log, debounce: archiveReopenDebounce, + now: time.Now, } } @@ -263,7 +268,7 @@ func (w *archiveWriter) write( ) } - if time.Since(w.lastReopen) >= w.debounce { + if w.now().Sub(w.lastReopen) >= w.debounce { return w.reopen(expiry) } @@ -323,7 +328,7 @@ func (w *archiveWriter) openMode( } w.db = gdb - w.lastReopen = time.Now() + w.lastReopen = w.now() w.reopens++ if expiry > 0 { diff --git a/internal/delivery/target_database_test.go b/internal/delivery/target_database_test.go index debc5ac..ed7fb24 100644 --- a/internal/delivery/target_database_test.go +++ b/internal/delivery/target_database_test.go @@ -184,13 +184,20 @@ func TestArchiveWriter_RecreatesAfterRemoval( func TestArchiveWriter_ReopenDebounce(t *testing.T) { t.Parallel() - // A generous debounce keeps the two rapid writes inside - // the window even on a heavily loaded test machine. + const debounce = 2 * time.Second + path := filepath.Join(t.TempDir(), "archive-wh.db") w := delivery.NewExportArchiveWriter( - path, archiveTestLogger(), 2*time.Second, + path, archiveTestLogger(), debounce, ) + // The writer measures its reopen debounce on this clock, which + // only the test moves, so how long the host takes between + // writes cannot change the result. + now := time.Now() + + w.SetNow(func() time.Time { return now }) + require.NoError(t, w.Write( delivery.ExportArchivedEvent{EventID: "a"}, 0, )) @@ -202,7 +209,7 @@ func TestArchiveWriter_ReopenDebounce(t *testing.T) { // initial open — no extra close/reopen. assert.Equal(t, 1, w.Reopens()) - time.Sleep(2100 * time.Millisecond) + now = now.Add(debounce) require.NoError(t, w.Write( delivery.ExportArchivedEvent{EventID: "c"}, 0,