package loadlib import ( "errors" "fmt" "net" "testing" "time" ) func TestBurnDeterministic(t *testing.T) { // Same difficulty must give the same checksum: the /compute endpoint returns // it, and a flaky checksum would mean a non-deterministic loop. for _, d := range []int{1, 3, 10} { if a, b := Burn(d), Burn(d); a != b { t.Errorf("Burn(%d) not deterministic: %d != %d", d, a, b) } } } func TestBurnDifficultyChangesResult(t *testing.T) { // Different difficulties should not collide (they run different iteration // counts); a collision here would suggest the loop body ignores i. if Burn(1) == Burn(2) { t.Error("Burn(1) == Burn(2): difficulty has no effect on the checksum") } } func TestIterationsLinearAndFloored(t *testing.T) { if got := Iterations(2); got != 2*burnIters { t.Errorf("Iterations(2) = %d, want %d", got, 2*burnIters) } // difficulty < 1 is clamped to 1 so /compute?difficulty=0 still does work. for _, d := range []int{0, -5} { if got := Iterations(d); got != burnIters { t.Errorf("Iterations(%d) = %d, want %d (floor of 1)", d, got, burnIters) } } // Cost is monotonic in difficulty. if Iterations(3) <= Iterations(2) { t.Error("Iterations not increasing with difficulty") } } func TestPercentileEdges(t *testing.T) { if got := Percentile(nil, 0.5); got != 0 { t.Errorf("Percentile(empty) = %v, want 0", got) } s := []time.Duration{10, 20, 30, 40, 50} cases := []struct { p float64 want time.Duration }{ {-1, 10}, // clamped to first {0, 10}, // first {0.5, 30}, // middle {1, 50}, // last {2, 50}, // clamped to last } for _, c := range cases { if got := Percentile(s, c.p); got != c.want { t.Errorf("Percentile(%v, %v) = %v, want %v", s, c.p, got, c.want) } } if got := Percentile([]time.Duration{99}, 0.9); got != 99 { t.Errorf("Percentile(single) = %v, want 99", got) } } func TestLatenciesSnapshot(t *testing.T) { var l Latencies // Add out of order; Snapshot must sort before reporting. for _, d := range []time.Duration{50, 10, 30, 40, 20} { l.Add(d * time.Millisecond) } if l.Len() != 5 { t.Fatalf("Len = %d, want 5", l.Len()) } p50, p99, max := l.Snapshot() if p50 != 30*time.Millisecond { t.Errorf("p50 = %v, want 30ms", p50) } if max != 50*time.Millisecond { t.Errorf("max = %v, want 50ms", max) } // Nearest-rank: index int(4*0.99)=3 → 40ms for this 5-sample set. if p99 != 40*time.Millisecond { t.Errorf("p99 = %v, want 40ms", p99) } } func TestLatenciesMerge(t *testing.T) { var a, b Latencies a.Add(10 * time.Millisecond) b.Add(20 * time.Millisecond) a.Merge(&b) if a.Len() != 2 { t.Fatalf("after merge Len = %d, want 2", a.Len()) } } func TestClassify(t *testing.T) { timeoutErr := &net.OpError{ Op: "dial", Err: timeoutError{}, } headerTimeout := fmt.Errorf("net/http: timeout awaiting response headers") cases := []struct { name string err error want ErrClass }{ {"nil", nil, ErrNone}, {"client fds", errors.New("dial tcp 127.0.0.1:8098: socket: too many open files"), ErrClientFdLimit}, {"client ports", errors.New("dial tcp 127.0.0.1:8098: connect: can't assign requested address"), ErrClientPortLimit}, {"refused", errors.New("dial tcp 127.0.0.1:8098: connect: connection refused"), ErrRefused}, {"reset", errors.New("read tcp: connection reset by peer"), ErrConnReset}, {"eof", errors.New("unexpected EOF"), ErrConnReset}, {"plain net timeout", timeoutErr, ErrTimeout}, {"server cant accept", headerTimeoutNetErr{}, ErrServerCantAccept}, {"header timeout string", headerTimeout, ErrTimeout}, // not a net.Error, but has "timeout" {"deadline", errors.New("context deadline exceeded"), ErrTimeout}, {"other", errors.New("something weird"), ErrOther}, } for _, c := range cases { if got := Classify(c.err); got != c.want { t.Errorf("%s: Classify(%v) = %v, want %v", c.name, c.err, got, c.want) } } } // timeoutError is a minimal net.Error whose Timeout() reports true, used to build // a synthetic *net.OpError the way the runtime would on a real dial timeout. type timeoutError struct{} func (timeoutError) Error() string { return "i/o timeout" } func (timeoutError) Timeout() bool { return true } func (timeoutError) Temporary() bool { return true } // headerTimeoutNetErr is a net.Error whose message is the exact one net/http // produces when ResponseHeaderTimeout fires — the "server can't accept" signal. type headerTimeoutNetErr struct{} func (headerTimeoutNetErr) Error() string { return "net/http: timeout awaiting response headers" } func (headerTimeoutNetErr) Timeout() bool { return true } func (headerTimeoutNetErr) Temporary() bool { return true }