Queue-full test: read the drop from the worker's log, not a counter it may have reset
The worker swaps the drop counter to zero when it logs it, so reading the counter raced the worker taking its first link. The test now releases the stalled log sink, closes the queue and requires the "links dropped" line.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 file changed,
+25
−6
internal/sheets/mailqueue_test.go+25 −6
CI passed — run details
CI log
amadan: .amadan/timeout: this job's budget is 15m0s (this runner allows up to 30m0s) === vet === CGO_ENABLED=0 go vet ./... CGO_ENABLED=0 go vet -tags browser ./internal/sheets/ === fmt === === build === CGO_ENABLED=0 go build ./... === migrations === go tool rastrillo migration check rastrillo migration check: models and migrations agree === go-test === CGO_ENABLED=0 go test -count=1 ./... ? amadan.net/oficina/sheets/cmd/sheets [no test files] ok amadan.net/oficina/sheets/internal/guardstest 0.098s ok amadan.net/oficina/sheets/internal/sheets 4.394s ok amadan.net/oficina/sheets/internal/sheets/calc 0.012s ok amadan.net/oficina/sheets/internal/sheets/format 0.003s ? amadan.net/oficina/sheets/internal/sheets/keymailtest [no test files] ok amadan.net/oficina/sheets/internal/sheets/live 0.054s ok amadan.net/oficina/sheets/internal/sheets/seal 0.002s ok amadan.net/oficina/sheets/internal/sheets/sealtest 0.003s ok amadan.net/oficina/sheets/internal/sheets/store 3.274s ok amadan.net/oficina/sheets/internal/sheets/xlsx 0.355s ? amadan.net/oficina/sheets/tools/gencorpus [no test files] === race === go test -race -count=1 ./internal/sheets/store/... ./internal/sheets/live/... ok amadan.net/oficina/sheets/internal/sheets/store 55.363s ok amadan.net/oficina/sheets/internal/sheets/live 1.067s
diff --git a/internal/sheets/mailqueue_test.go b/internal/sheets/mailqueue_test.go| index 0a8ab71..eb4c471 100644 |
| --- a/internal/sheets/mailqueue_test.go |
| +++ b/internal/sheets/mailqueue_test.go |
| @@ -5,18 +5,27 @@ import ( |
| "fmt" |
| "log/slog" |
| "runtime" |
| + "strings" |
| "sync" |
| "testing" |
| "time" |
| ) |
| // stuckHandler is a slog handler whose every write blocks until release |
| -// is closed: a log sink that has stalled, as a full pipe does. |
| -type stuckHandler struct{ release chan struct{} } |
| +// is closed: a log sink that has stalled, as a full pipe does. It |
| +// remembers what it was finally allowed to write. |
| +type stuckHandler struct { |
| + release chan struct{} |
| + mu *sync.Mutex |
| + msgs *[]string |
| +} |
| func (h stuckHandler) Enabled(context.Context, slog.Level) bool { return true } |
| -func (h stuckHandler) Handle(context.Context, slog.Record) error { |
| +func (h stuckHandler) Handle(_ context.Context, r slog.Record) error { |
| <-h.release |
| + h.mu.Lock() |
| + *h.msgs = append(*h.msgs, r.Message) |
| + h.mu.Unlock() |
| return nil |
| } |
| func (h stuckHandler) WithAttrs([]slog.Attr) slog.Handler { return h } |
| @@ -47,7 +56,9 @@ func TestMailQueueSendNeverBlocksWhenFull(t *testing.T) { |
| } |
| return nil |
| }) |
| - q := newMailQueue(stuck, everyone, nil, slog.New(stuckHandler{release})) |
| + var mu sync.Mutex |
| + var msgs []string |
| + q := newMailQueue(stuck, everyone, nil, slog.New(stuckHandler{release, &mu, &msgs})) |
| t.Cleanup(func() { free(); q.close() }) |
| done := make(chan struct{}) |
| @@ -64,9 +75,17 @@ func TestMailQueueSendNeverBlocksWhenFull(t *testing.T) { |
| <-done |
| t.Fatal("Send blocked with the queue full: the request path waited on the queue or the log") |
| } |
| - if n := q.dropped.Load(); n == 0 { |
| - t.Fatal("no drop was counted with twice the queue's depth sent") |
| + // The drops are the worker's to log, once it is free to. |
| + free() |
| + q.close() |
| + mu.Lock() |
| + defer mu.Unlock() |
| + for _, m := range msgs { |
| + if strings.Contains(m, "links dropped") { |
| + return |
| + } |
| } |
| + t.Fatalf("no drop was logged with twice the queue's depth sent; the log has %q", msgs) |
| } |
| // TestMailQueueQueuesEveryAddress: the queue's fill level must not |