package database import ( "os" "path/filepath" "sync" "testing" "time" ) // cleanup := setupRaceTestDB(t) // defer cleanup() func setupRaceTestDB(t *testing.T) func() { tmpDir := t.TempDir() dbPath := filepath.Join(tmpDir, "race_test.db") config := Config{ Enabled: true, Path: dbPath, RetentionDays: 7, } if err := Initialize(config); err != nil { t.Fatalf("Failed to initialize database: %v", err) } return func() { Close() os.Remove(dbPath) } } // 10 goroutines × 100 inserts each → no race detector warnings func TestDatabaseRace_ConcurrentHashrateInserts_Ugly(t *testing.T) { cleanup := setupRaceTestDB(t) defer cleanup() var waitGroup sync.WaitGroup // 10 goroutines inserting points concurrently for i := 0; i < 10; i++ { waitGroup.Add(1) go func(minerIndex int) { defer waitGroup.Done() minerName := "miner" + string(rune('A'+minerIndex)) minerType := "xmrig" for j := 0; j < 100; j++ { point := HashratePoint{ Timestamp: time.Now().Add(time.Duration(-j) * time.Second), Hashrate: 1000 + minerIndex*100 + j, } err := InsertHashratePoint(nil, minerName, minerType, point, ResolutionHigh) if err != nil { t.Errorf("Insert error for %s: %v", minerName, err) } } }(i) } waitGroup.Wait() // Verify data was inserted for i := 0; i < 10; i++ { minerName := "miner" + string(rune('A'+i)) history, err := GetHashrateHistory(minerName, ResolutionHigh, time.Now().Add(-2*time.Minute), time.Now()) if err != nil { t.Errorf("Failed to get history for %s: %v", minerName, err) } if len(history) == 0 { t.Errorf("Expected history for %s, got none", minerName) } } } // 1 writer + 5 readers concurrently → no race detector warnings func TestDatabaseRace_ConcurrentInsertAndQuery_Ugly(t *testing.T) { cleanup := setupRaceTestDB(t) defer cleanup() var waitGroup sync.WaitGroup stop := make(chan struct{}) // Writer goroutine waitGroup.Add(1) go func() { defer waitGroup.Done() for i := 0; ; i++ { select { case <-stop: return default: point := HashratePoint{ Timestamp: time.Now(), Hashrate: 1000 + i, } InsertHashratePoint(nil, "concurrent-test", "xmrig", point, ResolutionHigh) time.Sleep(time.Millisecond) } } }() // Multiple reader goroutines for i := 0; i < 5; i++ { waitGroup.Add(1) go func() { defer waitGroup.Done() for j := 0; j < 50; j++ { select { case <-stop: return default: GetHashrateHistory("concurrent-test", ResolutionHigh, time.Now().Add(-time.Hour), time.Now()) time.Sleep(2 * time.Millisecond) } } }() } // Let it run for a bit time.Sleep(200 * time.Millisecond) close(stop) waitGroup.Wait() // Test passes if no race detector warnings } // inserts (old + new data) + periodic Cleanup(7) → no race detector warnings func TestDatabaseRace_ConcurrentInsertAndCleanup_Ugly(t *testing.T) { cleanup := setupRaceTestDB(t) defer cleanup() var waitGroup sync.WaitGroup stop := make(chan struct{}) // Continuous inserts waitGroup.Add(1) go func() { defer waitGroup.Done() for i := 0; ; i++ { select { case <-stop: return default: // Insert some old data and some new data oldPoint := HashratePoint{ Timestamp: time.Now().AddDate(0, 0, -10), // 10 days old Hashrate: 500 + i, } InsertHashratePoint(nil, "cleanup-test", "xmrig", oldPoint, ResolutionHigh) newPoint := HashratePoint{ Timestamp: time.Now(), Hashrate: 1000 + i, } InsertHashratePoint(nil, "cleanup-test", "xmrig", newPoint, ResolutionHigh) time.Sleep(time.Millisecond) } } }() // Periodic cleanup waitGroup.Add(1) go func() { defer waitGroup.Done() for i := 0; i < 10; i++ { select { case <-stop: return default: Cleanup(7) // 7 day retention time.Sleep(20 * time.Millisecond) } } }() // Let it run time.Sleep(200 * time.Millisecond) close(stop) waitGroup.Wait() // Test passes if no race detector warnings } // 20 goroutines × 50 GetHashrateStats calls → no race detector warnings func TestDatabaseRace_ConcurrentStats_Ugly(t *testing.T) { cleanup := setupRaceTestDB(t) defer cleanup() // Insert some test data minerName := "stats-test" for i := 0; i < 100; i++ { point := HashratePoint{ Timestamp: time.Now().Add(time.Duration(-i) * time.Second), Hashrate: 1000 + i*10, } InsertHashratePoint(nil, minerName, "xmrig", point, ResolutionHigh) } var waitGroup sync.WaitGroup // Multiple goroutines querying stats for i := 0; i < 20; i++ { waitGroup.Add(1) go func() { defer waitGroup.Done() for j := 0; j < 50; j++ { stats, err := GetHashrateStats(minerName) if err != nil { t.Errorf("Stats error: %v", err) } if stats != nil && stats.TotalPoints == 0 { // This is fine, data might be in flux } } }() } waitGroup.Wait() // Test passes if no race detector warnings } // 10 readers + 1 writer concurrently on GetAllMinerStats → no race detector warnings func TestDatabaseRace_ConcurrentGetAllStats_Ugly(t *testing.T) { cleanup := setupRaceTestDB(t) defer cleanup() // Insert data for multiple miners for m := 0; m < 5; m++ { minerName := "all-stats-" + string(rune('A'+m)) for i := 0; i < 50; i++ { point := HashratePoint{ Timestamp: time.Now().Add(time.Duration(-i) * time.Second), Hashrate: 1000 + m*100 + i, } InsertHashratePoint(nil, minerName, "xmrig", point, ResolutionHigh) } } var waitGroup sync.WaitGroup // Multiple goroutines querying all stats for i := 0; i < 10; i++ { waitGroup.Add(1) go func() { defer waitGroup.Done() for j := 0; j < 30; j++ { _, err := GetAllMinerStats() if err != nil { t.Errorf("GetAllMinerStats error: %v", err) } } }() } // Concurrent inserts waitGroup.Add(1) go func() { defer waitGroup.Done() for i := 0; i < 50; i++ { point := HashratePoint{ Timestamp: time.Now(), Hashrate: 2000 + i, } InsertHashratePoint(nil, "all-stats-new", "xmrig", point, ResolutionHigh) } }() waitGroup.Wait() // Test passes if no race detector warnings }