aa137e3558
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 38s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Failing after 3s
New scrabble/loadtest module (the pre-release stress harness): seeds 1000 guest + 10000 durable accounts with pre-created sessions directly in Postgres (token hash matches backend/internal/session), drives virtual players through the edge protocol (real 2-4p games assembled via invitations, mid-ranked legal moves generated locally by the embedded scrabble-solver — the edge carries no board, so the client replays history), plus nudge/chat/check-word/draft/profile/stats and a gateway-hammer that verifies the rate limiter. Prints a trip-report summary (per-op latency percentiles, result codes, live-event tally). Go unit tests cover the pure pieces; the DAWG-backed move test runs under BACKEND_DICT_DIR. Contour: add cAdvisor + postgres_exporter + a 'Scrabble - Resources' Grafana dashboard and the two Prometheus scrape jobs, for the R2/R7 stress-run resource baseline. CI: gate ./loadtest/... (path filter + vet/build/test). Docs: TESTING, ARCHITECTURE, project CLAUDE repo layout.
54 lines
1.4 KiB
Go
54 lines
1.4 KiB
Go
package report
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestRecorderTotalsAndSummary checks call/error tallying and that the rendered
|
|
// summary surfaces operations, codes, events and stream errors.
|
|
func TestRecorderTotalsAndSummary(t *testing.T) {
|
|
r := New()
|
|
r.Record("game.state", "ok", 5*time.Millisecond)
|
|
r.Record("game.state", "ok", 7*time.Millisecond)
|
|
r.Record("game.submit_play", "not_your_turn", 3*time.Millisecond)
|
|
r.Record("hammer:games.list", "rate_limited", time.Millisecond)
|
|
r.Event("your_turn")
|
|
r.Event("your_turn")
|
|
r.StreamErr()
|
|
|
|
calls, nonOK := r.Totals()
|
|
if calls != 4 {
|
|
t.Errorf("calls = %d, want 4", calls)
|
|
}
|
|
if nonOK != 2 {
|
|
t.Errorf("nonOK = %d, want 2", nonOK)
|
|
}
|
|
|
|
s := r.Summary()
|
|
for _, want := range []string{"game.state", "not_your_turn", "rate_limited", "your_turn", "stream errors: 1"} {
|
|
if !strings.Contains(s, want) {
|
|
t.Errorf("summary missing %q\n---\n%s", want, s)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestOpStatQuantile checks the bucketed percentile estimate lands on the right bucket
|
|
// bound.
|
|
func TestOpStatQuantile(t *testing.T) {
|
|
s := newOpStat()
|
|
for i := 0; i < 90; i++ {
|
|
s.record("ok", 10*time.Millisecond)
|
|
}
|
|
for i := 0; i < 10; i++ {
|
|
s.record("ok", 1000*time.Millisecond)
|
|
}
|
|
if got := s.quantile(0.50); got != "10" {
|
|
t.Errorf("p50 = %s, want 10", got)
|
|
}
|
|
if got := s.quantile(0.99); got != "1000" {
|
|
t.Errorf("p99 = %s, want 1000", got)
|
|
}
|
|
}
|