package errlog
import (
"errors"
"testing"
)
// The ring's whole value is what it does under repetition: one
// signature is one row with a count (a hot loop must not flood), the
// first sighting fires the alert hook exactly once, and eviction
// removes what STOPPED happening, never what is still happening.
func TestDedupFirstSightingAndEviction(t *testing.T) {
r := New(2)
var fired []string
r.OnFirst = func(e Entry) { fired = append(fired, e.Message) }
boom := errors.New("boom")
for i := 0; i < 5; i++ {
r.Report("web", "/x", boom)
}
if got := r.Recent(0); len(got) != 1 || got[0].Count != 5 {
t.Fatalf("recent = %+v, want one row counted 5", got)
}
if len(fired) != 1 {
t.Fatalf("OnFirst fired %d times, want once", len(fired))
}
r.Report("api", "/y", errors.New("crash"))
r.Report("web", "/x", boom) // boom is STILL happening
r.Report("panic", "/z", errors.New("bang")) // full: evict the stalest
got := r.Recent(0)
if len(got) != 2 {
t.Fatalf("ring exceeded its cap: %+v", got)
}
for _, e := range got {
if e.Message == "crash" {
t.Fatalf("evicted the still-happening error instead of the stopped one: %+v", got)
}
}
if got[0].Message != "bang" {
t.Fatalf("recent not newest-first: %+v", got)
}
total, distinct, lastAt := r.Stats()
if total != 7 || distinct != 2 || lastAt.IsZero() {
t.Fatalf("stats = %d %d %v", total, distinct, lastAt)
}
r.Report("web", "/x", nil) // a nil error is not an error
if tot, _, _ := r.Stats(); tot != 7 {
t.Fatal("nil error was counted")
}
}
// The append file is per-OCCURRENCE (the ring dedupes for the UI; the
// file is forensics and wants every event), and it must survive the
// server the ring cannot.
func TestOutputReceivesEveryOccurrence(t *testing.T) {
var buf syncBuf
r := New(10)
r.SetOutput(&buf)
e := errors.New("boom")
r.Report("web", "/x", e)
r.Report("web", "/x", e)
if got := buf.String(); len(got) == 0 || countLines(got) != 2 {
t.Fatalf("file lines = %q, want two occurrences", got)
}
}
type syncBuf struct{ b []byte }
func (s *syncBuf) Write(p []byte) (int, error) { s.b = append(s.b, p...); return len(p), nil }
func (s *syncBuf) String() string { return string(s.b) }
func countLines(s string) int {
n := 0
for _, c := range s {
if c == '\n' {
n++
}
}
return n
}