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() }