74 lines
3.1 KiB
Go
74 lines
3.1 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// windowOnABus is the merge window's clock on a real bus, and the batches its watcher heard expire.
|
|
|
|
func hears(t *testing.T, heard <-chan string, within time.Duration) (string, bool) {
|
|
t.Helper()
|
|
select {
|
|
case b := <-heard:
|
|
return b, true
|
|
case <-time.After(within):
|
|
return "", false
|
|
}
|
|
}
|
|
|
|
// A batch's quiet key expires when no merge came for the window, and the watcher names the batch (issue 973,
|
|
// novox/hq ADR 0407 decision 1).
|
|
|
|
// A merge into the batch makes the quiet key again, so the window restarts: it does not expire at the first
|
|
// deadline, and the batch's most closes it all the same.
|
|
|
|
// A batch cut is forgotten: its keys are taken away by a delete, which is no expiry and wakes nobody.
|
|
|
|
// The deadlines a batch's keys are made with: what is left of its window and of its most, at least a second;
|
|
// none for a batch whose window is closed or that is no batch.
|
|
func TestTheDeadlinesOfABatchsKeys(t *testing.T) {
|
|
now := time.Date(2026, 10, 17, 12, 0, 0, 0, time.UTC)
|
|
batch := func(closes, most time.Duration, closed bool) inventory.Plan {
|
|
return inventory.Plan{State: inventory.PlanAssembling, Delivery: &inventory.PlanDelivery{Batch: &inventory.PlanBatch{
|
|
ClosesAt: now.Add(closes), AtMost: now.Add(most), Closed: closed}}}
|
|
}
|
|
for _, c := range []struct {
|
|
what string
|
|
b inventory.Plan
|
|
quiet, most time.Duration
|
|
armed bool
|
|
}{
|
|
{"open", batch(90*time.Second, 10*time.Minute, false), 90 * time.Second, 10 * time.Minute, true},
|
|
{"almost up", batch(100*time.Millisecond, 5*time.Second, false), time.Second, 5 * time.Second, true},
|
|
{"closed by a stop", batch(90*time.Second, 10*time.Minute, true), 0, 0, false},
|
|
{"up", batch(-time.Second, 10*time.Minute, false), 0, 0, false},
|
|
{"cut at once", func() inventory.Plan {
|
|
b := batch(90*time.Second, 10*time.Minute, false)
|
|
b.Delivery.Batch.CutAtOnce = true
|
|
return b
|
|
}(), 0, 0, false},
|
|
{"no batch", inventory.Plan{}, 0, 0, false},
|
|
} {
|
|
quiet, most, armed := windowDeadlines(c.b, now)
|
|
if armed != c.armed || quiet != c.quiet || most != c.most {
|
|
t.Errorf("%s: %v, %v, %v; want %v, %v, %v", c.what, quiet, most, armed, c.quiet, c.most, c.armed)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A deadline that is not a whole number of seconds is rounded up, never down: the server rounds a key's time to live
|
|
// down to whole seconds, so 89.999999 s would expire at 89 s, before the window is closed by the record's times, and
|
|
// the look would cut nothing (review of issue 973).
|
|
func TestAFractionalDeadlineIsRoundedUpToTheNextWholeSecond(t *testing.T) {
|
|
now := time.Date(2026, 10, 17, 12, 0, 0, 0, time.UTC)
|
|
b := inventory.Plan{State: inventory.PlanAssembling, Delivery: &inventory.PlanDelivery{Batch: &inventory.PlanBatch{
|
|
ClosesAt: now.Add(89*time.Second + 999999*time.Microsecond), AtMost: now.Add(599*time.Second + time.Nanosecond)}}}
|
|
quiet, most, armed := windowDeadlines(b, now)
|
|
if !armed || quiet != 90*time.Second || most != 600*time.Second {
|
|
t.Errorf("deadlines %v, %v (armed %v); want 1m30s, 10m0s", quiet, most, armed)
|
|
}
|
|
}
|