package miner import ( "strings" "sync/atomic" "testing" "time" "crypto-miner-agent/config" "crypto-miner-agent/job" "crypto-miner-agent/stats" ) func testPoolCfg() config.RuntimeConfig { return config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{ MiningMode: "always", MaxCPUUsage: 0, MaxMemoryPct: 0, MinFreeRAM: 0, }} } func TestNewPoolDefaultsThreads(t *testing.T) { p := NewPool(0, testPoolCfg(), stats.NewReporter(), nil) if p == nil || len(p.engines) != 1 { t.Fatalf("threads<=0 should default to 1 engine, got %d", len(p.engines)) } } func TestNewPoolMultipleEngines(t *testing.T) { p := NewPool(3, testPoolCfg(), stats.NewReporter(), nil) if len(p.engines) != 3 { t.Fatalf("expected 3 engines, got %d", len(p.engines)) } } func TestPoolSetJobNil(t *testing.T) { p := NewPool(1, testPoolCfg(), stats.NewReporter(), nil) p.SetJob(nil) p.mu.RLock() defer p.mu.RUnlock() if p.currentJob != nil { t.Fatal("SetJob(nil) should leave current job nil") } } func TestPoolSetJobDerivesSeedFromBlob(t *testing.T) { p := NewPool(1, testPoolCfg(), stats.NewReporter(), nil) blobPrefix := strings.Repeat("ee", 32) blob := blobPrefix + strings.Repeat("11", 22) j := &job.Job{ID: "j1", Blob: blob} p.SetJob(j) if p.engines[0].seedHex != blobPrefix { t.Fatalf("seed from blob prefix = %q, want %q", p.engines[0].seedHex, blobPrefix) } } func TestPoolHashesPerSecondAndReset(t *testing.T) { p := NewPool(1, testPoolCfg(), stats.NewReporter(), nil) p.hashesTotal.Store(1000) p.hashesLastReset = time.Now().Add(-2 * time.Second) rate := p.HashesPerSecond() if rate <= 0 { t.Fatalf("expected positive hashrate, got %f", rate) } p.ResetHashCounter() if p.hashesTotal.Load() != 0 { t.Fatal("ResetHashCounter should zero hashesTotal") } if p.HashesPerSecond() != 0 { t.Fatalf("hashrate after reset with no hashes should be 0, got %f", p.HashesPerSecond()) } } func TestPoolRemotePause(t *testing.T) { p := NewPool(1, testPoolCfg(), stats.NewReporter(), nil) if p.IsRemotePaused() { t.Fatal("new pool should not be remote-paused") } p.PauseRemote() if !p.IsRemotePaused() { t.Fatal("PauseRemote should set flag") } p.ResumeRemote() if p.IsRemotePaused() { t.Fatal("ResumeRemote should clear flag") } } func TestPoolSetShareHandler(t *testing.T) { p := NewPool(1, testPoolCfg(), stats.NewReporter(), nil) var called atomic.Bool p.SetShareHandler(func(jobID, nonce, hash string) { called.Store(true) }) p.handlerMu.RLock() h := p.handler p.handlerMu.RUnlock() if h == nil { t.Fatal("handler should be set") } h("j", "n", "h") if !called.Load() { t.Fatal("swapped handler should run") } } func TestPoolMiningAllowedAlways(t *testing.T) { p := NewPool(1, testPoolCfg(), stats.NewReporter(), nil) if !p.miningAllowed() { t.Fatal("always mode with resource limits disabled should allow mining") } } func TestPoolResourcesOKDisabledLimits(t *testing.T) { p := NewPool(1, testPoolCfg(), stats.NewReporter(), nil) if !p.resourcesOK() { t.Fatal("zero resource limits should allow mining") } }