Add missing Good/Bad/Ugly test triplets per RFC section 22: - stats_test.go: OnAccept/OnReject/Tick/OnLogin/OnClose tests with concurrency race test and top-10 diff slot verification - ratelimit_test.go: full Good/Bad/Ugly set including ban persistence and disabled-limiter edge case - customdiff_test.go: renamed to Apply_Good/Bad/Ugly convention per RFC - storage_test.go: full Add_Good/Bad/Ugly set including 256-slot fill, overflow rejection, and dead-slot reclamation via SetJob - job_test.go: added Good/Bad/Ugly for BlobWithFixedByte, DifficultyFromTarget, and IsValid Add Miner.Diff() public getter for the last difficulty sent to miner. Add AX-compliant usage-example comments (principle 2) to all Miner accessors, Proxy query methods, EffectiveShareDifficulty, targetFromDifficulty, MinerSnapshot, and RateLimiter.IsActive. Co-Authored-By: Virgil <virgil@lethean.io>
173 lines
4.7 KiB
Go
173 lines
4.7 KiB
Go
package proxy
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
)
|
|
|
|
// TestStats_OnAccept_Good verifies that accepted counter, hashes, and topDiff are updated.
|
|
//
|
|
// stats := proxy.NewStats()
|
|
// stats.OnAccept(proxy.Event{Diff: 100000, Latency: 82})
|
|
// summary := stats.Summary()
|
|
// _ = summary.Accepted // 1
|
|
// _ = summary.Hashes // 100000
|
|
func TestStats_OnAccept_Good(t *testing.T) {
|
|
stats := NewStats()
|
|
|
|
stats.OnAccept(Event{Diff: 100000, Latency: 82})
|
|
|
|
summary := stats.Summary()
|
|
if summary.Accepted != 1 {
|
|
t.Fatalf("expected accepted 1, got %d", summary.Accepted)
|
|
}
|
|
if summary.Hashes != 100000 {
|
|
t.Fatalf("expected hashes 100000, got %d", summary.Hashes)
|
|
}
|
|
if summary.TopDiff[0] != 100000 {
|
|
t.Fatalf("expected top diff 100000, got %d", summary.TopDiff[0])
|
|
}
|
|
}
|
|
|
|
// TestStats_OnAccept_Bad verifies concurrent OnAccept calls do not race.
|
|
//
|
|
// stats := proxy.NewStats()
|
|
// // 100 goroutines each call OnAccept — no data race under -race flag.
|
|
func TestStats_OnAccept_Bad(t *testing.T) {
|
|
stats := NewStats()
|
|
var wg sync.WaitGroup
|
|
for i := 0; i < 100; i++ {
|
|
wg.Add(1)
|
|
go func(diff uint64) {
|
|
defer wg.Done()
|
|
stats.OnAccept(Event{Diff: diff, Latency: 10})
|
|
}(uint64(i + 1))
|
|
}
|
|
wg.Wait()
|
|
|
|
summary := stats.Summary()
|
|
if summary.Accepted != 100 {
|
|
t.Fatalf("expected 100 accepted, got %d", summary.Accepted)
|
|
}
|
|
}
|
|
|
|
// TestStats_OnAccept_Ugly verifies that 15 accepts with varying diffs fill all topDiff slots.
|
|
//
|
|
// stats := proxy.NewStats()
|
|
// // 15 accepts with diffs 1..15 → topDiff[9] is 6 (10th highest), not 0
|
|
func TestStats_OnAccept_Ugly(t *testing.T) {
|
|
stats := NewStats()
|
|
|
|
for i := 1; i <= 15; i++ {
|
|
stats.OnAccept(Event{Diff: uint64(i)})
|
|
}
|
|
|
|
summary := stats.Summary()
|
|
// top 10 should be 15, 14, 13, ..., 6
|
|
if summary.TopDiff[0] != 15 {
|
|
t.Fatalf("expected top diff[0]=15, got %d", summary.TopDiff[0])
|
|
}
|
|
if summary.TopDiff[9] != 6 {
|
|
t.Fatalf("expected top diff[9]=6, got %d", summary.TopDiff[9])
|
|
}
|
|
}
|
|
|
|
// TestStats_OnReject_Good verifies that rejected and invalid counters are updated.
|
|
//
|
|
// stats := proxy.NewStats()
|
|
// stats.OnReject(proxy.Event{Error: "Low difficulty share"})
|
|
func TestStats_OnReject_Good(t *testing.T) {
|
|
stats := NewStats()
|
|
|
|
stats.OnReject(Event{Error: "Low difficulty share"})
|
|
stats.OnReject(Event{Error: "Malformed share"})
|
|
|
|
summary := stats.Summary()
|
|
if summary.Rejected != 2 {
|
|
t.Fatalf("expected two rejected shares, got %d", summary.Rejected)
|
|
}
|
|
if summary.Invalid != 2 {
|
|
t.Fatalf("expected two invalid shares, got %d", summary.Invalid)
|
|
}
|
|
}
|
|
|
|
// TestStats_OnReject_Bad verifies that a non-invalid rejection increments rejected but not invalid.
|
|
//
|
|
// stats := proxy.NewStats()
|
|
// stats.OnReject(proxy.Event{Error: "Stale share"})
|
|
func TestStats_OnReject_Bad(t *testing.T) {
|
|
stats := NewStats()
|
|
|
|
stats.OnReject(Event{Error: "Stale share"})
|
|
|
|
summary := stats.Summary()
|
|
if summary.Rejected != 1 {
|
|
t.Fatalf("expected one rejected, got %d", summary.Rejected)
|
|
}
|
|
if summary.Invalid != 0 {
|
|
t.Fatalf("expected zero invalid for non-invalid reason, got %d", summary.Invalid)
|
|
}
|
|
}
|
|
|
|
// TestStats_OnReject_Ugly verifies an expired accepted share increments both accepted and expired.
|
|
//
|
|
// stats := proxy.NewStats()
|
|
// stats.OnAccept(proxy.Event{Diff: 1000, Expired: true})
|
|
func TestStats_OnReject_Ugly(t *testing.T) {
|
|
stats := NewStats()
|
|
|
|
stats.OnAccept(Event{Diff: 1000, Expired: true})
|
|
|
|
summary := stats.Summary()
|
|
if summary.Accepted != 1 {
|
|
t.Fatalf("expected accepted 1, got %d", summary.Accepted)
|
|
}
|
|
if summary.Expired != 1 {
|
|
t.Fatalf("expected expired 1, got %d", summary.Expired)
|
|
}
|
|
}
|
|
|
|
// TestStats_Tick_Good verifies that Tick advances the rolling window position.
|
|
//
|
|
// stats := proxy.NewStats()
|
|
// stats.OnAccept(proxy.Event{Diff: 500})
|
|
// stats.Tick()
|
|
// summary := stats.Summary()
|
|
func TestStats_Tick_Good(t *testing.T) {
|
|
stats := NewStats()
|
|
|
|
stats.OnAccept(Event{Diff: 500})
|
|
stats.Tick()
|
|
|
|
summary := stats.Summary()
|
|
// After one tick, the hashrate should still include the 500 diff
|
|
if summary.Hashrate[HashrateWindow60s] == 0 {
|
|
t.Fatalf("expected non-zero 60s hashrate after accept and tick")
|
|
}
|
|
}
|
|
|
|
// TestStats_OnLogin_OnClose_Good verifies miner count tracking.
|
|
//
|
|
// stats := proxy.NewStats()
|
|
// stats.OnLogin(proxy.Event{Miner: &proxy.Miner{}})
|
|
// stats.OnClose(proxy.Event{Miner: &proxy.Miner{}})
|
|
func TestStats_OnLogin_OnClose_Good(t *testing.T) {
|
|
stats := NewStats()
|
|
m := &Miner{}
|
|
|
|
stats.OnLogin(Event{Miner: m})
|
|
if got := stats.miners.Load(); got != 1 {
|
|
t.Fatalf("expected 1 miner, got %d", got)
|
|
}
|
|
if got := stats.maxMiners.Load(); got != 1 {
|
|
t.Fatalf("expected max miners 1, got %d", got)
|
|
}
|
|
|
|
stats.OnClose(Event{Miner: m})
|
|
if got := stats.miners.Load(); got != 0 {
|
|
t.Fatalf("expected 0 miners after close, got %d", got)
|
|
}
|
|
if got := stats.maxMiners.Load(); got != 1 {
|
|
t.Fatalf("expected max miners to remain 1, got %d", got)
|
|
}
|
|
}
|