- Add pkg/mining/manager_race_test.go with concurrent miner tests - Add pkg/database/database_race_test.go with concurrent DB tests - Add TestCleanupRetention, TestGetHashrateHistoryTimeRange tests - Add TestMultipleMinerStats, TestIsInitialized tests - Fix AVG() float64 to int scan error in GetHashrateStats - Fix AVG() float64 to int scan error in GetAllMinerStats - Fix throttle tests to use NewManagerForSimulation to avoid autostart conflicts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
314 lines
6.9 KiB
Go
314 lines
6.9 KiB
Go
package mining
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestConcurrentStartMultipleMiners verifies that concurrent StartMiner calls
|
|
// with different algorithms create unique miners without race conditions
|
|
func TestConcurrentStartMultipleMiners(t *testing.T) {
|
|
m := setupTestManager(t)
|
|
defer m.Stop()
|
|
|
|
var wg sync.WaitGroup
|
|
errors := make(chan error, 10)
|
|
|
|
// Try to start 10 miners concurrently with different algos
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(1)
|
|
go func(index int) {
|
|
defer wg.Done()
|
|
config := &Config{
|
|
HTTPPort: 10000 + index,
|
|
Pool: "test:1234",
|
|
Wallet: "testwallet",
|
|
Algo: "algo" + string(rune('A'+index)), // algoA, algoB, etc.
|
|
}
|
|
_, err := m.StartMiner(context.Background(), "xmrig", config)
|
|
if err != nil {
|
|
errors <- err
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
close(errors)
|
|
|
|
// Collect errors
|
|
var errCount int
|
|
for err := range errors {
|
|
t.Logf("Concurrent start error: %v", err)
|
|
errCount++
|
|
}
|
|
|
|
// Some failures are expected due to port conflicts, but shouldn't crash
|
|
t.Logf("Started miners with %d errors out of 10 attempts", errCount)
|
|
|
|
// Verify no data races occurred (test passes if no race detector warnings)
|
|
}
|
|
|
|
// TestConcurrentStartDuplicateMiner verifies that starting the same miner
|
|
// concurrently results in only one success
|
|
func TestConcurrentStartDuplicateMiner(t *testing.T) {
|
|
m := setupTestManager(t)
|
|
defer m.Stop()
|
|
|
|
var wg sync.WaitGroup
|
|
successes := make(chan struct{}, 10)
|
|
failures := make(chan error, 10)
|
|
|
|
// Try to start the same miner 10 times concurrently
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
config := &Config{
|
|
HTTPPort: 11000,
|
|
Pool: "test:1234",
|
|
Wallet: "testwallet",
|
|
Algo: "duplicate_test", // Same algo = same instance name
|
|
}
|
|
_, err := m.StartMiner(context.Background(), "xmrig", config)
|
|
if err != nil {
|
|
failures <- err
|
|
} else {
|
|
successes <- struct{}{}
|
|
}
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
close(successes)
|
|
close(failures)
|
|
|
|
successCount := len(successes)
|
|
failureCount := len(failures)
|
|
|
|
t.Logf("Duplicate miner test: %d successes, %d failures", successCount, failureCount)
|
|
|
|
// Only one should succeed (or zero if there's a timing issue)
|
|
if successCount > 1 {
|
|
t.Errorf("Expected at most 1 success for duplicate miner, got %d", successCount)
|
|
}
|
|
}
|
|
|
|
// TestConcurrentStartStop verifies that starting and stopping miners
|
|
// concurrently doesn't cause race conditions
|
|
func TestConcurrentStartStop(t *testing.T) {
|
|
m := setupTestManager(t)
|
|
defer m.Stop()
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
// Start some miners
|
|
for i := 0; i < 5; i++ {
|
|
config := &Config{
|
|
HTTPPort: 12000 + i,
|
|
Pool: "test:1234",
|
|
Wallet: "testwallet",
|
|
Algo: "startstop" + string(rune('A'+i)),
|
|
}
|
|
_, err := m.StartMiner(context.Background(), "xmrig", config)
|
|
if err != nil {
|
|
t.Logf("Setup error (may be expected): %v", err)
|
|
}
|
|
}
|
|
|
|
// Give miners time to start
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Now concurrently start new ones and stop existing ones
|
|
for i := 0; i < 10; i++ {
|
|
wg.Add(2)
|
|
|
|
// Start a new miner
|
|
go func(index int) {
|
|
defer wg.Done()
|
|
config := &Config{
|
|
HTTPPort: 12100 + index,
|
|
Pool: "test:1234",
|
|
Wallet: "testwallet",
|
|
Algo: "new" + string(rune('A'+index)),
|
|
}
|
|
m.StartMiner(context.Background(), "xmrig", config)
|
|
}(i)
|
|
|
|
// Stop a miner
|
|
go func(index int) {
|
|
defer wg.Done()
|
|
minerName := "xmrig-startstop" + string(rune('A'+index%5))
|
|
m.StopMiner(context.Background(), minerName)
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Test passes if no race detector warnings
|
|
}
|
|
|
|
// TestConcurrentListMiners verifies that listing miners while modifying
|
|
// the miner map doesn't cause race conditions
|
|
func TestConcurrentListMiners(t *testing.T) {
|
|
m := setupTestManager(t)
|
|
defer m.Stop()
|
|
|
|
var wg sync.WaitGroup
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
// Continuously list miners
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
miners := m.ListMiners()
|
|
_ = len(miners) // Use the result
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Continuously start miners
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 0; i < 20; i++ {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
config := &Config{
|
|
HTTPPort: 13000 + i,
|
|
Pool: "test:1234",
|
|
Wallet: "testwallet",
|
|
Algo: "list" + string(rune('A'+i%26)),
|
|
}
|
|
m.StartMiner(context.Background(), "xmrig", config)
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
// Test passes if no race detector warnings
|
|
}
|
|
|
|
// TestConcurrentGetMiner verifies that getting a miner while others
|
|
// are being started/stopped doesn't cause race conditions
|
|
func TestConcurrentGetMiner(t *testing.T) {
|
|
m := setupTestManager(t)
|
|
defer m.Stop()
|
|
|
|
// Start a miner first
|
|
config := &Config{
|
|
HTTPPort: 14000,
|
|
Pool: "test:1234",
|
|
Wallet: "testwallet",
|
|
Algo: "gettest",
|
|
}
|
|
miner, err := m.StartMiner(context.Background(), "xmrig", config)
|
|
if err != nil {
|
|
t.Skipf("Could not start test miner: %v", err)
|
|
}
|
|
minerName := miner.GetName()
|
|
|
|
var wg sync.WaitGroup
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
// Continuously get the miner
|
|
for i := 0; i < 5; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
m.GetMiner(minerName)
|
|
time.Sleep(time.Millisecond)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Start more miners in parallel
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 0; i < 10; i++ {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
config := &Config{
|
|
HTTPPort: 14100 + i,
|
|
Pool: "test:1234",
|
|
Wallet: "testwallet",
|
|
Algo: "parallel" + string(rune('A'+i)),
|
|
}
|
|
m.StartMiner(context.Background(), "xmrig", config)
|
|
}
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
// Test passes if no race detector warnings
|
|
}
|
|
|
|
// TestConcurrentStatsCollection verifies that stats collection
|
|
// doesn't race with miner operations
|
|
func TestConcurrentStatsCollection(t *testing.T) {
|
|
m := setupTestManager(t)
|
|
defer m.Stop()
|
|
|
|
// Start some miners
|
|
for i := 0; i < 3; i++ {
|
|
config := &Config{
|
|
HTTPPort: 15000 + i,
|
|
Pool: "test:1234",
|
|
Wallet: "testwallet",
|
|
Algo: "stats" + string(rune('A'+i)),
|
|
}
|
|
m.StartMiner(context.Background(), "xmrig", config)
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
// Simulate stats collection (normally done by background goroutine)
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 0; i < 50; i++ {
|
|
miners := m.ListMiners()
|
|
for _, miner := range miners {
|
|
miner.GetStats(context.Background())
|
|
}
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
}()
|
|
|
|
// Concurrently stop miners
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
time.Sleep(100 * time.Millisecond) // Let stats collection start
|
|
for _, name := range []string{"xmrig-statsA", "xmrig-statsB", "xmrig-statsC"} {
|
|
m.StopMiner(context.Background(), name)
|
|
time.Sleep(50 * time.Millisecond)
|
|
}
|
|
}()
|
|
|
|
wg.Wait()
|
|
|
|
// Test passes if no race detector warnings
|
|
}
|