334 lines
7.5 KiB
Go
334 lines
7.5 KiB
Go
package ground_spawn
|
|
|
|
import (
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Mock implementations are in test_utils.go
|
|
|
|
// Test core GroundSpawn concurrency patterns without dependencies
|
|
func TestGroundSpawnCoreConcurrency(t *testing.T) {
|
|
config := GroundSpawnConfig{
|
|
GroundSpawnID: 1,
|
|
CollectionSkill: SkillGathering,
|
|
NumberHarvests: 10,
|
|
AttemptsPerHarvest: 2,
|
|
RandomizeHeading: true,
|
|
Location: SpawnLocation{
|
|
X: 100.0, Y: 200.0, Z: 300.0, Heading: 45.0, GridID: 1,
|
|
},
|
|
Name: "Test Node",
|
|
Description: "A test harvestable node",
|
|
}
|
|
|
|
gs := NewGroundSpawn(config)
|
|
|
|
const numGoroutines = 100
|
|
const operationsPerGoroutine = 100
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
t.Run("ConcurrentAccessors", 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++ {
|
|
switch j % 8 {
|
|
case 0:
|
|
gs.SetNumberHarvests(int8(goroutineID % 10))
|
|
case 1:
|
|
_ = gs.GetNumberHarvests()
|
|
case 2:
|
|
gs.SetAttemptsPerHarvest(int8(goroutineID % 5))
|
|
case 3:
|
|
_ = gs.GetAttemptsPerHarvest()
|
|
case 4:
|
|
gs.SetCollectionSkill(SkillMining)
|
|
case 5:
|
|
_ = gs.GetCollectionSkill()
|
|
case 6:
|
|
gs.SetRandomizeHeading(goroutineID%2 == 0)
|
|
case 7:
|
|
_ = gs.GetRandomizeHeading()
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("ConcurrentStateChecks", 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++ {
|
|
switch j % 4 {
|
|
case 0:
|
|
_ = gs.IsDepleted()
|
|
case 1:
|
|
_ = gs.IsAvailable()
|
|
case 2:
|
|
_ = gs.GetHarvestMessageName(true, false)
|
|
case 3:
|
|
_ = gs.GetHarvestSpellType()
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
}
|
|
|
|
// Test Manager core concurrency patterns
|
|
func TestManagerCoreConcurrency(t *testing.T) {
|
|
manager := NewManager(nil, &mockLogger{})
|
|
|
|
// Pre-populate with some ground spawns
|
|
for i := int32(1); i <= 10; i++ {
|
|
config := GroundSpawnConfig{
|
|
GroundSpawnID: i,
|
|
CollectionSkill: SkillGathering,
|
|
NumberHarvests: 5,
|
|
AttemptsPerHarvest: 1,
|
|
Location: SpawnLocation{
|
|
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
|
|
Heading: float32(i * 45), GridID: 1,
|
|
},
|
|
Name: "Test Node",
|
|
Description: "Test node",
|
|
}
|
|
gs := manager.CreateGroundSpawn(config)
|
|
if gs == nil {
|
|
t.Fatalf("Failed to create ground spawn %d", i)
|
|
}
|
|
}
|
|
|
|
const numGoroutines = 50
|
|
const operationsPerGoroutine = 50
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
t.Run("ConcurrentAccess", 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++ {
|
|
spawnID := int32((goroutineID % 10) + 1)
|
|
|
|
switch j % 5 {
|
|
case 0:
|
|
_ = manager.GetGroundSpawn(spawnID)
|
|
case 1:
|
|
_ = manager.GetGroundSpawnsByZone(1)
|
|
case 2:
|
|
_ = manager.GetGroundSpawnCount()
|
|
case 3:
|
|
_ = manager.GetActiveGroundSpawns()
|
|
case 4:
|
|
_ = manager.GetDepletedGroundSpawns()
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
})
|
|
|
|
t.Run("ConcurrentStatistics", 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++ {
|
|
switch j % 3 {
|
|
case 0:
|
|
_ = manager.GetStatistics()
|
|
case 1:
|
|
manager.ResetStatistics()
|
|
case 2:
|
|
// Simulate statistics updates
|
|
manager.mutex.Lock()
|
|
manager.totalHarvests++
|
|
manager.successfulHarvests++
|
|
skill := SkillGathering
|
|
manager.harvestsBySkill[skill]++
|
|
manager.mutex.Unlock()
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
// Verify statistics consistency
|
|
stats := manager.GetStatistics()
|
|
if stats.TotalHarvests < 0 || stats.SuccessfulHarvests < 0 {
|
|
t.Errorf("Invalid statistics: total=%d, successful=%d",
|
|
stats.TotalHarvests, stats.SuccessfulHarvests)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test Copy() method thread safety
|
|
func TestCopyThreadSafety(t *testing.T) {
|
|
config := GroundSpawnConfig{
|
|
GroundSpawnID: 1,
|
|
CollectionSkill: SkillGathering,
|
|
NumberHarvests: 5,
|
|
AttemptsPerHarvest: 1,
|
|
Location: SpawnLocation{
|
|
X: 100, Y: 200, Z: 300, Heading: 45, GridID: 1,
|
|
},
|
|
Name: "Copy Test Node",
|
|
Description: "Test node for copy safety",
|
|
}
|
|
|
|
original := NewGroundSpawn(config)
|
|
|
|
const numGoroutines = 50
|
|
var wg sync.WaitGroup
|
|
wg.Add(numGoroutines)
|
|
|
|
// Test copying while modifying
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(goroutineID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < 100; j++ {
|
|
if j%2 == 0 {
|
|
// Copy operations
|
|
copy := original.Copy()
|
|
if copy == nil {
|
|
t.Errorf("Goroutine %d: Copy returned nil", goroutineID)
|
|
continue
|
|
}
|
|
|
|
// Verify copy is independent by setting different values
|
|
newValue := int8(goroutineID%5 + 1) // Ensure non-zero value
|
|
copy.SetNumberHarvests(newValue)
|
|
|
|
// Copy should have the new value we just set
|
|
if copy.GetNumberHarvests() != newValue {
|
|
t.Errorf("Goroutine %d: Copy failed to set value correctly, expected %d got %d",
|
|
goroutineID, newValue, copy.GetNumberHarvests())
|
|
}
|
|
// Note: We can't reliably test that original is unchanged due to concurrent modifications
|
|
} else {
|
|
// Modify original
|
|
original.SetNumberHarvests(int8(goroutineID % 10))
|
|
original.SetCollectionSkill(SkillMining)
|
|
_ = original.GetRandomizeHeading()
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
}
|
|
|
|
// Test core deadlock prevention
|
|
func TestCoreDeadlockPrevention(t *testing.T) {
|
|
manager := NewManager(nil, &mockLogger{})
|
|
|
|
// Create test ground spawns
|
|
for i := int32(1); i <= 5; i++ {
|
|
config := GroundSpawnConfig{
|
|
GroundSpawnID: i,
|
|
CollectionSkill: SkillGathering,
|
|
NumberHarvests: 5,
|
|
AttemptsPerHarvest: 1,
|
|
Location: SpawnLocation{
|
|
X: float32(i * 10), Y: float32(i * 20), Z: float32(i * 30),
|
|
Heading: 0, GridID: 1,
|
|
},
|
|
Name: "Deadlock Test Node",
|
|
Description: "Test node",
|
|
}
|
|
gs := manager.CreateGroundSpawn(config)
|
|
if gs == nil {
|
|
t.Fatalf("Failed to create ground spawn %d", i)
|
|
}
|
|
}
|
|
|
|
const numGoroutines = 25
|
|
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(5 * 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 < 50; j++ {
|
|
spawnID := int32((goroutineID % 5) + 1)
|
|
|
|
// Mix operations that could potentially deadlock
|
|
switch j % 8 {
|
|
case 0:
|
|
gs := manager.GetGroundSpawn(spawnID)
|
|
if gs != nil {
|
|
_ = gs.GetNumberHarvests()
|
|
}
|
|
case 1:
|
|
_ = manager.GetStatistics()
|
|
case 2:
|
|
_ = manager.GetGroundSpawnsByZone(1)
|
|
case 3:
|
|
gs := manager.GetGroundSpawn(spawnID)
|
|
if gs != nil {
|
|
gs.SetNumberHarvests(int8(j % 5))
|
|
}
|
|
case 4:
|
|
manager.ProcessRespawns()
|
|
case 5:
|
|
_ = manager.GetActiveGroundSpawns()
|
|
case 6:
|
|
gs := manager.GetGroundSpawn(spawnID)
|
|
if gs != nil {
|
|
_ = gs.Copy()
|
|
}
|
|
case 7:
|
|
gs := manager.GetGroundSpawn(spawnID)
|
|
if gs != nil && gs.IsDepleted() {
|
|
manager.scheduleRespawn(gs)
|
|
}
|
|
}
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
wg.Wait()
|
|
done <- true
|
|
})
|
|
}
|