package obs
import (
"context"
"reflect"
"testing"
"time"
)
// The strip's lines are an hour of each signal, a bin a minute, kept as
// events happen rather than read back off a ring whose span depends on
// traffic. Quiet minutes are zero, not absent, and the hour is the
// hour: the sixty-first minute pushes the first one out.
func TestSignalsKeepAnHourOfMinutes(t *testing.T) {
var s signalRing
s.render(t0, false)
s.render(t0.Add(10*time.Second), true)
s.running(t0.Add(20*time.Second), 3)
s.running(t0.Add(30*time.Second), 1)
// Two quiet minutes pass; the third has one render.
s.render(t0.Add(3*time.Minute), false)
got := s.snapshot(t0.Add(3*time.Minute + 5*time.Second))
if got.Minutes != 4 {
t.Fatalf("FAIL: %d minutes, want 4: %+v", got.Minutes, got)
}
if want := []int{2, 0, 0, 1}; !reflect.DeepEqual(got.Renders, want) {
t.Errorf("FAIL: renders %v, want %v", got.Renders, want)
}
if want := []int{1, 0, 0, 0}; !reflect.DeepEqual(got.Slow, want) {
t.Errorf("FAIL: slow %v, want %v", got.Slow, want)
}
// Busy is the most running at once in the minute, not a count.
if want := []int{3, 0, 0, 0}; !reflect.DeepEqual(got.Busy, want) {
t.Errorf("FAIL: busy %v, want %v", got.Busy, want)
}
// A snapshot taken later fills the quiet minutes to now.
later := s.snapshot(t0.Add(70 * time.Minute))
if later.Minutes != SignalMinutes {
t.Errorf("FAIL: after 70 minutes the ring holds %d, want %d", later.Minutes, SignalMinutes)
}
if later.Renders[len(later.Renders)-1] != 0 || sumInts(later.Renders) != 0 {
t.Errorf("FAIL: the hour after the events should be empty: %v", later.Renders)
}
}
// The default recorder feeds the ring; a private recorder in a test
// does not, so one test's renders cannot leak into another's hour.
func TestTheDefaultRecorderFeedsTheSignals(t *testing.T) {
resetSignals()
defer resetSignals()
now := time.Now()
Default.RecordRender(Render{At: now, Path: "GET /x", Elapsed: Budget + time.Millisecond})
NewRecorder(10, nil).RecordRender(Render{At: now, Path: "GET /y", Elapsed: time.Millisecond})
got := SignalsNow()
if got.Minutes == 0 || got.Renders[len(got.Renders)-1] != 1 || got.Slow[len(got.Slow)-1] != 1 {
t.Errorf("FAIL: signals after one slow render on Default: %+v", got)
}
}
func sumInts(xs []int) int {
n := 0
for _, x := range xs {
n += x
}
return n
}
// A subprocess started under a request names the address it serves;
// one started under a background activity names that; one with neither
// names nothing rather than guessing.
func TestCauseIsTheAddressElseTheReason(t *testing.T) {
req := WithAddress(WithReason(context.Background(), ReasonWarm), "GET", "/r/fossil/timeline")
if got := Cause(req); got != "GET /r/fossil/timeline" {
t.Errorf("FAIL: a request's cause reads %q", got)
}
if got := Cause(WithReason(context.Background(), ReasonIngest)); got != ReasonIngest {
t.Errorf("FAIL: a background cause reads %q", got)
}
if got := Cause(context.Background()); got != "" {
t.Errorf("FAIL: no cause reads %q", got)
}
}