Files
AetherForge/agent/miner/contingency_tree_test.go
AetherForge 6dd5cbd461
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add onion contingency miner tree with orchestrator and Seer telemetry.
Single-host branch runner complements fallback chain under Fleet AI Control: persona ghost forks, onion_miner_log hops, server exhaustion splice from graft mining genome, Crucible depth badge, and hospice retire after max cycles.
2026-06-07 09:29:26 -07:00

172 lines
5.4 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")
}
}