525 lines
12 KiB
Go
525 lines
12 KiB
Go
package factions
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Stress test MasterFactionList with concurrent operations
|
|
func TestMasterFactionListConcurrency(t *testing.T) {
|
|
mfl := NewMasterList()
|
|
|
|
// Pre-populate with some factions
|
|
for i := int32(1); i <= 10; i++ {
|
|
faction := NewFaction(i, "Test Faction", "Test", "Test faction")
|
|
mfl.AddFaction(faction)
|
|
}
|
|
|
|
const numGoroutines = 100
|
|
const operationsPerGoroutine = 100
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
t.Run("ConcurrentFactionAccess", func(t *testing.T) {
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
factionID := int32((goroutineID % 10) + 1)
|
|
|
|
// Mix of read operations
|
|
faction := mfl.GetFaction(factionID)
|
|
if faction == nil {
|
|
t.Errorf("Goroutine %d: Expected faction %d to exist", goroutineID, factionID)
|
|
}
|
|
|
|
_ = mfl.GetFactionByName("Test Faction")
|
|
_ = mfl.HasFaction(factionID)
|
|
_ = mfl.GetFactionCount()
|
|
_ = mfl.GetDefaultFactionValue(factionID)
|
|
_ = mfl.GetIncreaseAmount(factionID)
|
|
_ = mfl.GetDecreaseAmount(factionID)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("ConcurrentRelationshipManagement", func(t *testing.T) {
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
factionID := int32((goroutineID % 10) + 1)
|
|
targetID := int32(((goroutineID + 1) % 10) + 1)
|
|
|
|
// Concurrent relationship operations
|
|
if goroutineID%2 == 0 {
|
|
mfl.AddHostileFaction(factionID, targetID)
|
|
_ = mfl.GetHostileFactions(factionID)
|
|
} else {
|
|
mfl.AddFriendlyFaction(factionID, targetID)
|
|
_ = mfl.GetFriendlyFactions(factionID)
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("ConcurrentFactionModification", func(t *testing.T) {
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
newFactionID := int32(1000 + goroutineID*operationsPerGoroutine + j)
|
|
faction := NewFaction(newFactionID, "Concurrent Faction", "Test", "Concurrent test")
|
|
|
|
err := mfl.AddFaction(faction)
|
|
if err != nil {
|
|
t.Errorf("Goroutine %d: Failed to add faction %d: %v", goroutineID, newFactionID, err)
|
|
}
|
|
|
|
// Verify it was added
|
|
retrieved := mfl.GetFaction(newFactionID)
|
|
if retrieved == nil {
|
|
t.Errorf("Goroutine %d: Failed to retrieve faction %d", goroutineID, newFactionID)
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
}
|
|
|
|
// Stress test PlayerFaction with concurrent operations
|
|
func TestPlayerFactionConcurrency(t *testing.T) {
|
|
mfl := NewMasterList()
|
|
|
|
// Add test factions with proper values
|
|
for i := int32(1); i <= 10; i++ {
|
|
faction := NewFaction(i, "Test Faction", "Test", "Test faction")
|
|
faction.PositiveChange = 100
|
|
faction.NegativeChange = 50
|
|
mfl.AddFaction(faction)
|
|
}
|
|
|
|
pf := NewPlayerFaction(mfl)
|
|
|
|
const numGoroutines = 100
|
|
const operationsPerGoroutine = 100
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
t.Run("ConcurrentFactionValueOperations", func(t *testing.T) {
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
factionID := int32((goroutineID % 10) + 1)
|
|
|
|
// Mix of faction value operations
|
|
switch j % 4 {
|
|
case 0:
|
|
pf.IncreaseFaction(factionID, 10)
|
|
case 1:
|
|
pf.DecreaseFaction(factionID, 5)
|
|
case 2:
|
|
pf.SetFactionValue(factionID, int32(goroutineID*100))
|
|
case 3:
|
|
_ = pf.GetFactionValue(factionID)
|
|
_ = pf.GetCon(factionID)
|
|
_ = pf.GetPercent(factionID)
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("ConcurrentUpdateManagement", func(t *testing.T) {
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
factionID := int32((goroutineID % 10) + 1)
|
|
|
|
// Operations that trigger updates
|
|
pf.IncreaseFaction(factionID, 1)
|
|
|
|
// Check for pending updates
|
|
_ = pf.HasPendingUpdates()
|
|
_ = pf.GetPendingUpdates()
|
|
|
|
// Occasionally clear updates
|
|
if j%10 == 0 {
|
|
pf.ClearPendingUpdates()
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("ConcurrentPacketGeneration", func(t *testing.T) {
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
// Trigger faction updates
|
|
factionID := int32((goroutineID % 10) + 1)
|
|
pf.IncreaseFaction(factionID, 1)
|
|
|
|
// Generate packets concurrently
|
|
_, err := pf.FactionUpdate(int16(goroutineID % 10))
|
|
if err != nil {
|
|
t.Errorf("Goroutine %d: FactionUpdate failed: %v", goroutineID, err)
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
}
|
|
|
|
// Stress test Manager with concurrent operations
|
|
func TestManagerConcurrency(t *testing.T) {
|
|
manager := NewManager(nil, nil) // No database or logger for testing
|
|
|
|
const numGoroutines = 100
|
|
const operationsPerGoroutine = 100
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
t.Run("ConcurrentStatisticsOperations", func(t *testing.T) {
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
factionID := int32((goroutineID % 10) + 1)
|
|
|
|
// Mix of statistics operations
|
|
switch j % 4 {
|
|
case 0:
|
|
manager.RecordFactionIncrease(factionID)
|
|
case 1:
|
|
manager.RecordFactionDecrease(factionID)
|
|
case 2:
|
|
_ = manager.GetStatistics()
|
|
case 3:
|
|
_ = manager.GetFactionCount()
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Verify statistics consistency
|
|
stats := manager.GetStatistics()
|
|
totalChanges := stats["total_faction_changes"].(int64)
|
|
increases := stats["faction_increases"].(int64)
|
|
decreases := stats["faction_decreases"].(int64)
|
|
|
|
if totalChanges != increases+decreases {
|
|
t.Errorf("Statistics inconsistency: total %d != increases %d + decreases %d",
|
|
totalChanges, increases, decreases)
|
|
}
|
|
})
|
|
|
|
t.Run("ConcurrentFactionManagement", func(t *testing.T) {
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
factionID := int32(2000 + goroutineID*operationsPerGoroutine + j)
|
|
faction := NewFaction(factionID, "Manager Test", "Test", "Manager test")
|
|
|
|
// Add faction
|
|
err := manager.AddFaction(faction)
|
|
if err != nil {
|
|
t.Errorf("Goroutine %d: Failed to add faction %d: %v", goroutineID, factionID, err)
|
|
continue
|
|
}
|
|
|
|
// Read operations
|
|
_ = manager.GetFaction(factionID)
|
|
_ = manager.GetFactionByName("Manager Test")
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("ConcurrentPlayerFactionCreation", func(t *testing.T) {
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
// Create player factions concurrently
|
|
pf := manager.CreatePlayerFaction()
|
|
if pf == nil {
|
|
t.Errorf("Goroutine %d: CreatePlayerFaction returned nil", goroutineID)
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Check statistics
|
|
stats := manager.GetStatistics()
|
|
expectedPlayers := int64(numGoroutines * operationsPerGoroutine)
|
|
actualPlayers := stats["players_with_factions"].(int64)
|
|
|
|
if actualPlayers != expectedPlayers {
|
|
t.Errorf("Expected %d players with factions, got %d", expectedPlayers, actualPlayers)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Stress test EntityFactionAdapter with concurrent operations
|
|
func TestEntityFactionAdapterConcurrency(t *testing.T) {
|
|
manager := NewManager(nil, nil)
|
|
|
|
// Mock entity
|
|
entity := &mockEntity{id: 123, name: "Test Entity", dbID: 456}
|
|
adapter := NewEntityFactionAdapter(entity, manager, nil)
|
|
|
|
const numGoroutines = 100
|
|
const operationsPerGoroutine = 100
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
t.Run("ConcurrentFactionIDOperations", func(t *testing.T) {
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
factionID := int32((goroutineID % 10) + 1)
|
|
|
|
// Mix of faction ID operations
|
|
switch j % 3 {
|
|
case 0:
|
|
adapter.SetFactionID(factionID)
|
|
case 1:
|
|
_ = adapter.GetFactionID()
|
|
case 2:
|
|
_ = adapter.GetFaction()
|
|
_ = adapter.GetFactionName()
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("ConcurrentRelationshipChecks", func(t *testing.T) {
|
|
// Set up some factions first
|
|
for i := int32(1); i <= 10; i++ {
|
|
faction := NewFaction(i, "Test Faction", "Test", "Test faction")
|
|
manager.AddFaction(faction)
|
|
}
|
|
|
|
// Set up relationships
|
|
mfl := manager.GetMasterFactionList()
|
|
mfl.AddHostileFaction(1, 2)
|
|
mfl.AddFriendlyFaction(1, 3)
|
|
|
|
adapter.SetFactionID(1)
|
|
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
otherFactionID := int32((goroutineID % 10) + 1)
|
|
|
|
// Concurrent relationship checks
|
|
_ = adapter.IsHostileToFaction(otherFactionID)
|
|
_ = adapter.IsFriendlyToFaction(otherFactionID)
|
|
|
|
// Validation
|
|
_ = adapter.ValidateFaction()
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
}
|
|
|
|
// Mock entity for testing
|
|
type mockEntity struct {
|
|
id int32
|
|
name string
|
|
dbID int32
|
|
}
|
|
|
|
func (e *mockEntity) GetID() int32 {
|
|
return e.id
|
|
}
|
|
|
|
func (e *mockEntity) GetName() string {
|
|
return e.name
|
|
}
|
|
|
|
func (e *mockEntity) GetDatabaseID() int32 {
|
|
return e.dbID
|
|
}
|
|
|
|
// Test for potential deadlocks
|
|
func TestDeadlockPrevention(t *testing.T) {
|
|
mfl := NewMasterList()
|
|
manager := NewManager(nil, nil)
|
|
|
|
// Add test factions
|
|
for i := int32(1); i <= 10; i++ {
|
|
faction := NewFaction(i, "Test Faction", "Test", "Test faction")
|
|
manager.AddFaction(faction)
|
|
}
|
|
|
|
const numGoroutines = 50
|
|
var wg sync.WaitGroup
|
|
|
|
// Test potential deadlock scenarios
|
|
t.Run("MixedOperations", func(t *testing.T) {
|
|
done := make(chan bool, 1)
|
|
|
|
// Set a timeout to detect deadlocks
|
|
go func() {
|
|
time.Sleep(10 * time.Second)
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
t.Error("Potential deadlock detected - test timed out")
|
|
}
|
|
}()
|
|
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < 100; j++ {
|
|
factionID := int32((goroutineID % 10) + 1)
|
|
|
|
// Mix operations that could potentially deadlock
|
|
switch j % 6 {
|
|
case 0:
|
|
_ = mfl.GetFaction(factionID)
|
|
_ = manager.GetFaction(factionID)
|
|
case 1:
|
|
_ = mfl.GetFactionCount()
|
|
_ = manager.GetFactionCount()
|
|
case 2:
|
|
mfl.AddHostileFaction(factionID, factionID+1)
|
|
_ = manager.GetStatistics()
|
|
case 3:
|
|
_ = mfl.GetAllFactions()
|
|
manager.RecordFactionIncrease(factionID)
|
|
case 4:
|
|
_ = mfl.ValidateFactions()
|
|
_ = manager.ValidateAllFactions()
|
|
case 5:
|
|
pf := manager.CreatePlayerFaction()
|
|
pf.IncreaseFaction(factionID, 10)
|
|
_ = pf.GetFactionValue(factionID)
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
done <- true
|
|
})
|
|
}
|
|
|
|
// Race condition detection test - run with -race flag
|
|
func TestRaceConditions(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("Skipping race condition test in short mode")
|
|
}
|
|
|
|
// This test is designed to be run with: go test -race
|
|
mfl := NewMasterList()
|
|
manager := NewManager(nil, nil)
|
|
|
|
// Rapid concurrent operations to trigger race conditions
|
|
const numGoroutines = 200
|
|
const operationsPerGoroutine = 50
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(numGoroutines)
|
|
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < operationsPerGoroutine; j++ {
|
|
factionID := int32((goroutineID % 20) + 1)
|
|
|
|
// Rapid-fire operations
|
|
faction := NewFaction(factionID, "Race Test", "Test", "Race test")
|
|
mfl.AddFaction(faction)
|
|
_ = mfl.GetFaction(factionID)
|
|
mfl.AddHostileFaction(factionID, factionID+1)
|
|
_ = mfl.GetHostileFactions(factionID)
|
|
|
|
manager.AddFaction(faction)
|
|
manager.RecordFactionIncrease(factionID)
|
|
_ = manager.GetStatistics()
|
|
|
|
pf := manager.CreatePlayerFaction()
|
|
pf.IncreaseFaction(factionID, 1)
|
|
_ = pf.GetFactionValue(factionID)
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|