package phb import ( "errors" "fmt" "strconv" "sync" "testing" "time" ) func TestRaceness(t *testing.T) { reg, counters := New(1) n, v, s := "bob", "GET", "users" reg.SetDefaultPolicy(&CounterPolicy{ DetailedEnforceError: true, Buckets: []BucketPolicy{{ Window: 36000, Splits: 1, Max: 20, }}}) var wg sync.WaitGroup wg.Add(2) go func() { for range 10 { err := Increment(reg, counters, n, v, s, 1) if err != nil { _, isBackoff := errors.AsType[*ErrBackoffOverLimitTyp](err) if !isBackoff { t.Logf("err incrementing: %s", err.Error()) t.Fail() } } } wg.Done() }() go func() { for range 10 { err := Increment(reg, counters, n, v, s, 1) if err != nil { _, isBackoff := errors.AsType[*ErrBackoffOverLimitTyp](err) if !isBackoff { t.Logf("err incrementing: %s", err.Error()) t.Fail() } } } wg.Done() }() wg.Wait() counts, ok := GetCounts(reg, counters, "bob", "GET", "users", time.Now()) if !ok { t.Log("Could not find registry item bob, GET, users") t.FailNow() } if count := counts[36000]; count != 20 { fmt.Println("photoengravings", counters) t.Logf("Expected count of %d, got %d", 20, count) t.FailNow() } if err := Enforce(reg, counters, n, v, s, time.Now()); err != nil { t.Logf("expected to be at or below limit, but got error: %s", err.Error()) } err := Increment(reg, counters, n, v, s, 1) _, incrementIsBackoff := errors.AsType[*ErrBackoffOverLimitTyp](err) if err == nil || !incrementIsBackoff { errString := "nil err" if err != nil { errString = err.Error() } t.Logf("expected backoff error, but got %s", errString) } } func TestRegisterRespectsCap(t *testing.T) { reg, counters := New(2) reg.SetDefaultPolicy(&CounterPolicy{ DetailedEnforceError: true, Buckets: []BucketPolicy{{ Window: 36000, Splits: 1, Max: 20, }}}) n, v, s := "bob", "GET", "users" err := Register(reg, counters, n, v, s, nil) if err != nil { t.Logf("expected no error but got: %s", err.Error()) t.FailNow() } err = Register(reg, counters, n, "POST", s, nil) if err != nil { t.Logf("expected no error but got: %s", err.Error()) t.FailNow() } err = Register(reg, counters, n, "DELETE", s, nil) if err == nil { t.Logf("expected register error, but got none") t.FailNow() } Unregister(reg, counters, n, "POST", s) err = Register(reg, counters, n, "DELETE", s, nil) if err != nil { t.Logf("expected no error but got: %s", err.Error()) t.FailNow() } } func asT(timeStr string) time.Time { t, err := time.Parse(time.RFC3339, timeStr) if err != nil { panic(err.Error()) } return t } func asTC(h, m, s uint) time.Time { if h >= 24 { panic("bad hour") } if m >= 60 { panic("bad minute") } if s >= 60 { panic("bad second") } hH := strconv.Itoa(int(h)) if len(hH) == 1 { hH = "0" + hH } hM := strconv.Itoa(int(m)) if len(hM) == 1 { hM = "0" + hM } hS := strconv.Itoa(int(s)) if len(hS) == 1 { hS = "0" + hS } return asT(fmt.Sprintf("2006-01-02T%s:%s:%sZ", hH, hM, hS)) } type writeIndexCase struct { start time.Time now time.Time window uint split uint index uint } func TestToWriteIndex(t *testing.T) { tests := []writeIndexCase{ { start: asTC(0, 0, 0), now: asTC(0, 1, 0), window: 60, split: 60, index: 60, }, { start: asT("2006-01-02T00:00:00Z"), now: asT("2006-01-02T00:00:00.9999999Z"), window: 60, split: 60, index: 0, }, { start: asTC(0, 0, 0), now: asTC(0, 0, 1), window: 60, split: 60, index: 1, }, { start: asTC(0, 0, 0), now: asTC(0, 3, 0), window: 60, split: 10, index: 30, }, } for _, tc := range tests { t.Run(fmt.Sprintf("%s -> %s (%d/%d) -> %d", tc.start, tc.now, tc.window, tc.split, tc.index), func(t *testing.T) { index := toWriteIndex(tc.start, tc.now, tc.window, tc.split) if tc.index != index { t.Logf("expected index %d, got %d", tc.index, index) t.FailNow() } }) } }