Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Cover fleet torrent DHT fetch, cloud map/venue/meta IMDS mocks, S3 shard fetch order, zero-server policy polling, self-surgery reorder, contingency skip paths, epidemiology fix guards, and ssm_document deploy lane.
191 lines
6.2 KiB
Go
191 lines
6.2 KiB
Go
package miner
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDefaultContingencyBranchOrder(t *testing.T) {
|
|
want := []ContingencyBranch{BranchInProcess, BranchContainer, BranchGPUSubprocess, BranchIdleTune, BranchSelfSurgery}
|
|
if len(DefaultContingencyBranchOrder) != len(want) {
|
|
t.Fatalf("order=%v", DefaultContingencyBranchOrder)
|
|
}
|
|
for i := range want {
|
|
if DefaultContingencyBranchOrder[i] != want[i] {
|
|
t.Fatalf("idx %d = %q want %q", i, DefaultContingencyBranchOrder[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMutateBranchOrderForPersona(t *testing.T) {
|
|
base := append([]ContingencyBranch(nil), DefaultContingencyBranchOrder...)
|
|
agg := MutateBranchOrderForPersona(base, "aggressive", false)
|
|
if agg[0] != BranchGPUSubprocess && agg[0] != BranchInProcess {
|
|
t.Fatalf("aggressive should front gpu/inprocess, got %v", agg)
|
|
}
|
|
silent := MutateBranchOrderForPersona(base, "silent", false)
|
|
if silent[0] != BranchContainer {
|
|
t.Fatalf("silent should front container, got %v", silent)
|
|
}
|
|
rotated := MutateBranchOrderForPersona(base, "balanced", true)
|
|
if len(rotated) != len(base) {
|
|
t.Fatalf("rotate length=%d", len(rotated))
|
|
}
|
|
}
|
|
|
|
func TestContingencyTreeWinsOnInProcess(t *testing.T) {
|
|
var hr atomic.Value
|
|
hr.Store(1200.0)
|
|
var hops []OnionMinerHop
|
|
gate := NewPoolGate()
|
|
r := NewContingencyTreeRunner(ContingencyPolicy{Enabled: true, JitterMs: 1}, BranchAttemptHooks{
|
|
StartInProcess: func() error { return nil },
|
|
StartContainer: func() error { return ErrMethodUnavailable },
|
|
StartGPU: func() error { return ErrMethodUnavailable },
|
|
ApplyIdleTune: func() error { return nil },
|
|
ApplySurgery: func([]string, string) error { return nil },
|
|
StopAll: func() {},
|
|
Hashrate: func() float64 { v, _ := hr.Load().(float64); return v },
|
|
PoolActive: gate.Active,
|
|
AcquirePool: func() bool { return gate.Acquire("canonical") },
|
|
ReleasePool: func() { gate.Release("canonical") },
|
|
}, func(h OnionMinerHop, depth int, exhausted bool) {
|
|
hops = append(hops, h)
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
go r.Run(ctx)
|
|
time.Sleep(200 * time.Millisecond)
|
|
cancel()
|
|
|
|
method, ok := r.Frozen()
|
|
if !ok || method != BranchInProcess {
|
|
t.Fatalf("frozen=%q ok=%v hops=%d", method, ok, len(hops))
|
|
}
|
|
won := false
|
|
for _, h := range hops {
|
|
if h.Outcome == "won" && h.Method == string(BranchInProcess) {
|
|
won = true
|
|
}
|
|
}
|
|
if !won {
|
|
t.Fatalf("expected won hop, got %v", hops)
|
|
}
|
|
}
|
|
|
|
func TestGhostSkipsWhenPoolHeld(t *testing.T) {
|
|
gate := NewPoolGate()
|
|
gate.Acquire("canonical")
|
|
var ghostSkipped int
|
|
r := NewContingencyTreeRunner(ContingencyPolicy{Enabled: true, JitterMs: 1, Personas: []string{"silent"}}, BranchAttemptHooks{
|
|
StartInProcess: func() error { return nil },
|
|
StartContainer: func() error { return nil },
|
|
Hashrate: func() float64 { return 0 },
|
|
PoolActive: gate.Active,
|
|
AcquirePool: func() bool { return gate.Acquire("canonical") },
|
|
ReleasePool: func() { gate.Release("canonical") },
|
|
}, func(h OnionMinerHop, _ int, _ bool) {
|
|
if h.Outcome == "ghost_skipped" {
|
|
ghostSkipped++
|
|
}
|
|
})
|
|
_ = r.tryBranch(context.Background(), BranchContainer, "silent", "silent", true)
|
|
if ghostSkipped != 1 {
|
|
t.Fatalf("ghost_skipped=%d", ghostSkipped)
|
|
}
|
|
}
|
|
|
|
func TestHospiceRetiresAfterExhaustCycles(t *testing.T) {
|
|
var retired bool
|
|
policy := ContingencyPolicy{Enabled: true, JitterMs: 1, HospiceAfter: 2}
|
|
r := NewContingencyTreeRunner(policy, BranchAttemptHooks{
|
|
StartInProcess: func() error { return ErrMethodUnavailable },
|
|
StartContainer: func() error { return ErrMethodUnavailable },
|
|
StartGPU: func() error { return ErrMethodUnavailable },
|
|
ApplyIdleTune: func() error { return ErrMethodUnavailable },
|
|
ApplySurgery: func([]string, string) error { return ErrMethodUnavailable },
|
|
StopAll: func() {},
|
|
Hashrate: func() float64 { return 0 },
|
|
AcquirePool: func() bool { return true },
|
|
ReleasePool: func() {},
|
|
}, func(h OnionMinerHop, _ int, exhausted bool) {
|
|
if h.Outcome == "strain_retired" {
|
|
retired = true
|
|
}
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
go r.Run(ctx)
|
|
time.Sleep(2500 * time.Millisecond)
|
|
cancel()
|
|
if !retired {
|
|
t.Fatal("expected hospice strain_retired hop")
|
|
}
|
|
}
|
|
|
|
func TestApplyBranchParamsResetsExhaustion(t *testing.T) {
|
|
r := NewContingencyTreeRunner(ContingencyPolicy{Enabled: true}, BranchAttemptHooks{}, nil)
|
|
r.exhaustCycles = 5
|
|
r.ApplyBranchParams(ContingencyBranchParams{
|
|
BranchOrder: []string{"container", "inprocess"},
|
|
Persona: "aggressive",
|
|
SkipMethods: []string{"gpu_subprocess"},
|
|
})
|
|
if r.exhaustCycles != 0 {
|
|
t.Fatalf("exhaust=%d", r.exhaustCycles)
|
|
}
|
|
if len(r.branchOrder) != 2 || r.branchOrder[0] != BranchContainer {
|
|
t.Fatalf("order=%v", r.branchOrder)
|
|
}
|
|
if !r.skipMethods["gpu_subprocess"] {
|
|
t.Fatal("expected skip gpu_subprocess")
|
|
}
|
|
}
|
|
|
|
func TestOperatorPauseStopsRun(t *testing.T) {
|
|
r := NewContingencyTreeRunner(ContingencyPolicy{Enabled: true, JitterMs: 1}, BranchAttemptHooks{
|
|
Hashrate: func() float64 { return 0 },
|
|
AcquirePool: func() bool { return true },
|
|
ReleasePool: func() {},
|
|
}, nil)
|
|
r.SetOperatorPaused(true)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
|
defer cancel()
|
|
r.Run(ctx)
|
|
if r.Depth() != 0 {
|
|
t.Fatalf("depth=%d want 0 on pause", r.Depth())
|
|
}
|
|
}
|
|
|
|
func TestBranchWonThreshold(t *testing.T) {
|
|
if !BranchWon(1.0) {
|
|
t.Fatal("1 H/s should win")
|
|
}
|
|
if BranchWon(0) {
|
|
t.Fatal("0 should not win")
|
|
}
|
|
}
|
|
|
|
func TestContingencyRunPassSkipsMethod(t *testing.T) {
|
|
r := NewContingencyTreeRunner(ContingencyPolicy{Enabled: true, JitterMs: 1}, BranchAttemptHooks{
|
|
StartInProcess: func() error { return nil },
|
|
StartContainer: func() error { return ErrMethodUnavailable },
|
|
Hashrate: func() float64 { return 100 },
|
|
AcquirePool: func() bool { return true },
|
|
ReleasePool: func() {},
|
|
}, nil)
|
|
r.ApplyBranchParams(ContingencyBranchParams{
|
|
BranchOrder: []string{"container", "inprocess"},
|
|
SkipMethods: []string{"container"},
|
|
})
|
|
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
|
defer cancel()
|
|
if !r.runPass(ctx) {
|
|
t.Fatal("expected inprocess win after container skipped")
|
|
}
|
|
}
|