| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- package observability
- import (
- "sync"
- "sync/atomic"
- "testing"
- )
- func TestMaxSeen_FirstObservationExports(t *testing.T) {
- m := NewMaxSeen()
- var got string
- var val float64
- var calls int
- m.RecordAndExport("src-A", 1, func(k string, v float64) {
- got = k
- val = v
- calls++
- })
- if calls != 1 || got != "src-A" || val != 1 {
- t.Fatalf("first: want 1 export src-A=1, got calls=%d key=%q val=%v", calls, got, val)
- }
- if m.Get("src-A") != 1 {
- t.Fatalf("Get: want 1, got %d", m.Get("src-A"))
- }
- }
- func TestMaxSeen_StrictlyMonotonic(t *testing.T) {
- m := NewMaxSeen()
- var calls int
- exp := func(string, float64) { calls++ }
- m.RecordAndExport("src-A", 5, exp)
- m.RecordAndExport("src-A", 3, exp) // smaller: should not export
- m.RecordAndExport("src-A", 5, exp) // equal: should not export
- m.RecordAndExport("src-A", 7, exp) // larger: should export
- m.RecordAndExport("src-A", 2, exp) // smaller: should not export
- m.RecordAndExport("src-A", 10, exp) // larger: should export
- if calls != 3 {
- t.Fatalf("want 3 exports (1, 7, 10), got %d", calls)
- }
- if m.Get("src-A") != 10 {
- t.Fatalf("Get: want 10, got %d", m.Get("src-A"))
- }
- }
- func TestMaxSeen_PerKeyIsolation(t *testing.T) {
- m := NewMaxSeen()
- exp := func(string, float64) {}
- m.RecordAndExport("src-A", 100, exp)
- m.RecordAndExport("src-B", 5, exp)
- if m.Get("src-A") != 100 {
- t.Fatalf("src-A: want 100, got %d", m.Get("src-A"))
- }
- if m.Get("src-B") != 5 {
- t.Fatalf("src-B: want 5, got %d", m.Get("src-B"))
- }
- if m.Get("src-C") != 0 {
- t.Fatalf("src-C (unseen): want 0, got %d", m.Get("src-C"))
- }
- }
- func TestMaxSeen_ConcurrentSameKey(t *testing.T) {
- m := NewMaxSeen()
- var exports atomic.Int64
- exp := func(string, float64) { exports.Add(1) }
- var wg sync.WaitGroup
- for g := 0; g < 8; g++ {
- wg.Add(1)
- go func(g int) {
- defer wg.Done()
- for v := 1; v <= 1000; v++ {
- // Each goroutine uses a different value range
- // so we know the global max is 8*1000 - 7 = 7993
- // (8 goroutines, each writes its own values
- // shifted by g*1000). Just write all of them.
- _ = g
- m.RecordAndExport("src", uint32(v), exp)
- }
- }(g)
- }
- wg.Wait()
- // We don't assert on the number of exports (it can vary
- // based on CAS contention); we assert that the final
- // observed max is the actual global max written.
- if m.Get("src") != 1000 {
- t.Fatalf("final max: want 1000, got %d", m.Get("src"))
- }
- if exports.Load() < 1 {
- t.Fatalf("expected ≥1 export, got %d", exports.Load())
- }
- }
- func TestMaxSeen_NilExportIsNoOp(t *testing.T) {
- m := NewMaxSeen()
- // Should not panic.
- m.RecordAndExport("src-A", 1, nil)
- m.RecordAndExport("src-A", 5, nil)
- if m.Get("src-A") != 5 {
- t.Fatalf("nil export should still update state; got %d", m.Get("src-A"))
- }
- }
|