Mining/pkg/database/database_race_test.go
Claude 3673757d2a
ax(batch): replace prose comments with usage examples across all packages
Converts comments that restate function signatures into concrete usage
examples per AX Principle 2. Affected files: database_race_test.go,
interface_test.go, errors_test.go, xmrig_test.go, service_test.go,
manager_test.go.

Co-Authored-By: Charon <charon@lethean.io>
2026-04-02 18:16:56 +01:00

273 lines
5.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 TestConcurrentHashrateInserts(t *testing.T) {
cleanup := setupRaceTestDB(t)
defer cleanup()
var wg sync.WaitGroup
// 10 goroutines inserting points concurrently
for i := 0; i < 10; i++ {
wg.Add(1)
go func(minerIndex int) {
defer wg.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)
}
wg.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 TestConcurrentInsertAndQuery(t *testing.T) {
cleanup := setupRaceTestDB(t)
defer cleanup()
var wg sync.WaitGroup
stop := make(chan struct{})
// Writer goroutine
wg.Add(1)
go func() {
defer wg.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++ {
wg.Add(1)
go func() {
defer wg.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)
wg.Wait()
// Test passes if no race detector warnings
}
// inserts (old + new data) + periodic Cleanup(7) → no race detector warnings
func TestConcurrentInsertAndCleanup(t *testing.T) {
cleanup := setupRaceTestDB(t)
defer cleanup()
var wg sync.WaitGroup
stop := make(chan struct{})
// Continuous inserts
wg.Add(1)
go func() {
defer wg.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
wg.Add(1)
go func() {
defer wg.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)
wg.Wait()
// Test passes if no race detector warnings
}
// 20 goroutines × 50 GetHashrateStats calls → no race detector warnings
func TestConcurrentStats(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 wg sync.WaitGroup
// Multiple goroutines querying stats
for i := 0; i < 20; i++ {
wg.Add(1)
go func() {
defer wg.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
}
}
}()
}
wg.Wait()
// Test passes if no race detector warnings
}
// 10 readers + 1 writer concurrently on GetAllMinerStats → no race detector warnings
func TestConcurrentGetAllStats(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 wg sync.WaitGroup
// Multiple goroutines querying all stats
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 30; j++ {
_, err := GetAllMinerStats()
if err != nil {
t.Errorf("GetAllMinerStats error: %v", err)
}
}
}()
}
// Concurrent inserts
wg.Add(1)
go func() {
defer wg.Done()
for i := 0; i < 50; i++ {
point := HashratePoint{
Timestamp: time.Now(),
Hashrate: 2000 + i,
}
InsertHashratePoint(nil, "all-stats-new", "xmrig", point, ResolutionHigh)
}
}()
wg.Wait()
// Test passes if no race detector warnings
}