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>
273 lines
5.8 KiB
Go
273 lines
5.8 KiB
Go
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
|
||
}
|