- serve summary lines at -v and -q, records and HideOnTTY, DSN labels - shutdown report: aborted requests fail the stop, a drained stop is nil - request log: request_id cap, default on, off by config or env, never the path for matched, 404 and 405 routes, component http; route:list --json - WorkerStats through runAttempt: done, failed, in flight, handed back - queue:clear and lagoon's production question on a pseudo-terminal: no cancels with exit 1, --force, -n and other environments skip it - Docker: PendingMigrations, migrate/rollback/deferred:purge --pretend, production runs off a terminal never read stdin, migrate:status --json - key:generate --json and -q
88 lines
2.7 KiB
Go
88 lines
2.7 KiB
Go
package conga
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/diary"
|
|
"github.com/riverqueue/river/rivertype"
|
|
)
|
|
|
|
// TestWorkerStats: runAttempt counts every attempt of the running worker:
|
|
// done on nil, failed on an error or a panic, in flight while it runs, and
|
|
// handed back when Stop cancelled it; a nil worker has no counters.
|
|
func TestWorkerStats(t *testing.T) {
|
|
// The panicking attempt logs its stack; keep it out of the test output.
|
|
t.Cleanup(diary.Reset)
|
|
diary.Bootstrap(io.Discard, diary.Options{})
|
|
m := &Manager{}
|
|
counters := &workerCounters{}
|
|
m.counters.Store(counters)
|
|
w := &Worker{counters: counters}
|
|
row := &rivertype.JobRow{ID: 1, Kind: "acme_probe", Attempt: 1, MaxAttempts: 3}
|
|
ctx := context.Background()
|
|
|
|
for range 3 {
|
|
if err := m.runAttempt(ctx, row, func(context.Context) error { return nil }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
boom := errors.New("boom")
|
|
if err := m.runAttempt(ctx, row, func(context.Context) error { return boom }); !errors.Is(err, boom) {
|
|
t.Fatalf("failing attempt returned %v", err)
|
|
}
|
|
if err := m.runAttempt(ctx, row, func(context.Context) error { panic("kaboom") }); err == nil {
|
|
t.Fatal("a panic did not become an error")
|
|
}
|
|
|
|
started, release := make(chan struct{}), make(chan struct{})
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- m.runAttempt(ctx, row, func(context.Context) error {
|
|
close(started)
|
|
<-release
|
|
return nil
|
|
})
|
|
}()
|
|
<-started
|
|
if got := w.Stats(); got.InFlight != 1 || got.Done != 3 || got.Failed != 2 {
|
|
t.Fatalf("while running: %+v, want 1 in flight, 3 done, 2 failed", got)
|
|
}
|
|
close(release)
|
|
if err := <-done; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// Stop cancels the running attempts: they are handed back, not failed.
|
|
counters.stopping.Store(true)
|
|
cctx, cancel := context.WithCancel(ctx)
|
|
cancel()
|
|
if err := m.runAttempt(cctx, row, func(c context.Context) error { return c.Err() }); err == nil {
|
|
t.Fatal("cancelled attempt returned nil")
|
|
}
|
|
// An error unrelated to the cancellation while stopping still fails.
|
|
if err := m.runAttempt(ctx, row, func(context.Context) error { return boom }); err == nil {
|
|
t.Fatal("failing attempt returned nil")
|
|
}
|
|
|
|
want := WorkerStats{Done: 4, Failed: 3, InFlight: 0, HandedBack: 1}
|
|
if got := w.Stats(); got != want {
|
|
t.Fatalf("Stats() = %+v, want %+v", got, want)
|
|
}
|
|
var none *Worker
|
|
if got := none.Stats(); got != (WorkerStats{}) {
|
|
t.Fatalf("nil worker Stats() = %+v", got)
|
|
}
|
|
if got := (&Worker{}).Stats(); got != (WorkerStats{}) {
|
|
t.Fatalf("worker without counters Stats() = %+v", got)
|
|
}
|
|
|
|
// Without a running worker, attempts run and nothing is counted.
|
|
idle := &Manager{}
|
|
if err := idle.runAttempt(ctx, row, func(context.Context) error { return nil }); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|