check / check (push) Canceled after 0s
Five prefix handler tests gave HandleMessage two seconds to apply a full batch of 25,000 prefixes, which under the race detector on a loaded host takes longer. A test now waits up to stepTimeout, 15 seconds, for each step it expects. The slowest tests filled production limits, such as the peering handler's 500,000 paths. The peering handler now takes its path limit, and the prefix handler its limit on one write of the table's changes, as they take their timer interval. Those tests give small limits, and the tests of the maximum batch size lower it to one batch. This also fixes #215. The test helpers bound every wait, Stop included, so a test that hangs in one fails with its own message. Model: opus-5-5
121 lines
3.0 KiB
Go
121 lines
3.0 KiB
Go
package routewatch
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.eeqj.de/sneak/routewatch/internal/database"
|
|
)
|
|
|
|
// incompleteCheckpointStore is a store whose every checkpoint leaves part of
|
|
// the WAL uncopied.
|
|
type incompleteCheckpointStore struct{ *mockStore }
|
|
|
|
// Checkpoint reports the checkpoint incomplete.
|
|
func (incompleteCheckpointStore) Checkpoint(context.Context) error {
|
|
return database.ErrCheckpointIncomplete
|
|
}
|
|
|
|
// TestIncompleteCheckpointNotLoggedAsCompleted runs a checkpoint that leaves
|
|
// part of the WAL uncopied and checks that the maintainer logs it as
|
|
// incomplete, not as completed, and does not count it as a checkpoint that
|
|
// completed.
|
|
func TestIncompleteCheckpointNotLoggedAsCompleted(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var logs bytes.Buffer
|
|
|
|
logger := slog.New(slog.NewJSONHandler(&logs,
|
|
&slog.HandlerOptions{Level: slog.LevelDebug}))
|
|
m := NewDBMaintainer(incompleteCheckpointStore{newMockStore()}, logger)
|
|
|
|
m.runCheckpoint(t.Context())
|
|
|
|
var logged struct {
|
|
Msg string `json:"msg"`
|
|
}
|
|
|
|
err := json.Unmarshal(logs.Bytes(), &logged)
|
|
if err != nil {
|
|
t.Fatalf("want one log message, got %q: %v", logs.String(), err)
|
|
}
|
|
|
|
if logged.Msg != checkpointIncompleteMessage {
|
|
t.Errorf("logged %q, want %q", logged.Msg, checkpointIncompleteMessage)
|
|
}
|
|
|
|
if m.checkpointCount != 0 {
|
|
t.Errorf("checkpointCount = %d after an incomplete checkpoint, want 0",
|
|
m.checkpointCount)
|
|
}
|
|
}
|
|
|
|
// heldCheckpointStore is a store whose checkpoint closes started and then
|
|
// waits until release is closed.
|
|
type heldCheckpointStore struct {
|
|
*mockStore
|
|
|
|
started chan struct{}
|
|
release chan struct{}
|
|
}
|
|
|
|
// Checkpoint waits until release is closed.
|
|
func (s heldCheckpointStore) Checkpoint(context.Context) error {
|
|
close(s.started)
|
|
<-s.release
|
|
|
|
return nil
|
|
}
|
|
|
|
// TestDBMaintainerStopReturnsByItsDeadline stops the maintainer with a short
|
|
// deadline while its goroutine runs a checkpoint that the store holds until
|
|
// the test ends. Stop must return all the same instead of waiting for the
|
|
// checkpoint.
|
|
func TestDBMaintainerStopReturnsByItsDeadline(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const stopDeadline = 100 * time.Millisecond
|
|
|
|
store := heldCheckpointStore{
|
|
mockStore: newMockStore(),
|
|
started: make(chan struct{}),
|
|
release: make(chan struct{}),
|
|
}
|
|
m := NewDBMaintainer(store, slog.Default())
|
|
|
|
// Start would run the first checkpoint only after checkpointInterval.
|
|
// This goroutine stands for the one Start runs and checkpoints at once.
|
|
go func() {
|
|
defer close(m.done)
|
|
|
|
m.runCheckpoint(t.Context())
|
|
}()
|
|
|
|
select {
|
|
case <-store.started:
|
|
case <-time.After(stepTimeout):
|
|
t.Fatal("the maintainer did not start the checkpoint")
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(t.Context(), stopDeadline)
|
|
defer cancel()
|
|
|
|
if !returnsInTime(func() { m.Stop(ctx) }) {
|
|
t.Fatalf("Stop with a deadline of %s did not return while a "+
|
|
"checkpoint was in progress", stopDeadline)
|
|
}
|
|
|
|
close(store.release)
|
|
|
|
select {
|
|
case <-m.done:
|
|
case <-time.After(stepTimeout):
|
|
t.Fatal("the maintenance goroutine did not return after the " +
|
|
"checkpoint ended")
|
|
}
|
|
}
|