533 lines
15 KiB
Go
533 lines
15 KiB
Go
package zone
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"eq2emu/internal/spawn"
|
|
_ "github.com/go-sql-driver/mysql"
|
|
)
|
|
|
|
// Mock implementations for testing
|
|
|
|
// MockSpawn implements basic spawn functionality for testing
|
|
type MockSpawn struct {
|
|
id int32
|
|
x, y, z float32
|
|
heading float32
|
|
name string
|
|
}
|
|
|
|
func (ms *MockSpawn) GetID() int32 { return ms.id }
|
|
func (ms *MockSpawn) GetPosition() (x, y, z, heading float32) { return ms.x, ms.y, ms.z, ms.heading }
|
|
func (ms *MockSpawn) SetPosition(x, y, z, heading float32) { ms.x, ms.y, ms.z, ms.heading = x, y, z, heading }
|
|
func (ms *MockSpawn) GetName() string { return ms.name }
|
|
func (ms *MockSpawn) SetName(name string) { ms.name = name }
|
|
func (ms *MockSpawn) GetX() float32 { return ms.x }
|
|
func (ms *MockSpawn) GetY() float32 { return ms.y }
|
|
func (ms *MockSpawn) GetZ() float32 { return ms.z }
|
|
func (ms *MockSpawn) GetHeading() float32 { return ms.heading }
|
|
func (ms *MockSpawn) SetX(x float32) { ms.x = x }
|
|
func (ms *MockSpawn) SetY(y float32, updateClients bool) { ms.y = y }
|
|
func (ms *MockSpawn) SetZ(z float32) { ms.z = z }
|
|
func (ms *MockSpawn) SetHeadingFromFloat(heading float32) { ms.heading = heading }
|
|
|
|
// TestDatabaseOperations tests database CRUD operations
|
|
func TestDatabaseOperations(t *testing.T) {
|
|
// Skip this test - requires MySQL database connection
|
|
t.Skip("Skipping database operations test - requires MySQL database")
|
|
|
|
// Example test for when MySQL is available:
|
|
// db, err := database.New(database.Config{
|
|
// DSN: "test_user:test_pass@tcp(localhost:3306)/test_db",
|
|
// })
|
|
// if err != nil {
|
|
// t.Fatalf("Failed to create test database: %v", err)
|
|
// }
|
|
// defer db.Close()
|
|
//
|
|
// // Create database instance
|
|
// zdb := NewZoneDatabase(db)
|
|
// if zdb == nil {
|
|
// t.Fatal("Expected non-nil zone database")
|
|
// }
|
|
//
|
|
// // Test LoadZoneData
|
|
// zoneData, err := zdb.LoadZoneData(1)
|
|
// if err != nil {
|
|
// t.Fatalf("Failed to load zone data: %v", err)
|
|
// }
|
|
//
|
|
// // Additional test assertions would go here...
|
|
}
|
|
|
|
// TestZoneServerLifecycle tests zone server creation, initialization, and shutdown
|
|
func TestZoneServerLifecycle(t *testing.T) {
|
|
// Create zone server
|
|
zoneServer := NewZoneServer("test_zone_lifecycle")
|
|
if zoneServer == nil {
|
|
t.Fatal("Expected non-nil zone server")
|
|
}
|
|
|
|
// Test initial state
|
|
if zoneServer.IsInitialized() {
|
|
t.Error("Expected zone to not be initialized")
|
|
}
|
|
|
|
if zoneServer.IsShuttingDown() {
|
|
t.Error("Expected zone to not be shutting down")
|
|
}
|
|
|
|
if zoneServer.GetZoneName() != "test_zone_lifecycle" {
|
|
t.Errorf("Expected zone name 'test_zone_lifecycle', got '%s'", zoneServer.GetZoneName())
|
|
}
|
|
|
|
// Test initialization
|
|
config := &ZoneServerConfig{
|
|
ZoneName: "test_zone_lifecycle",
|
|
ZoneID: 100,
|
|
InstanceID: 0,
|
|
MaxPlayers: 50,
|
|
MinLevel: 1,
|
|
MaxLevel: 100,
|
|
LoadMaps: false,
|
|
EnableWeather: false,
|
|
EnablePathfinding: false,
|
|
}
|
|
|
|
err := zoneServer.Initialize(config)
|
|
if err != nil {
|
|
t.Fatalf("Failed to initialize zone server: %v", err)
|
|
}
|
|
|
|
if !zoneServer.IsInitialized() {
|
|
t.Error("Expected zone to be initialized")
|
|
}
|
|
|
|
if zoneServer.GetZoneID() != 100 {
|
|
t.Errorf("Expected zone ID 100, got %d", zoneServer.GetZoneID())
|
|
}
|
|
|
|
if zoneServer.GetMaxPlayers() != 50 {
|
|
t.Errorf("Expected max players 50, got %d", zoneServer.GetMaxPlayers())
|
|
}
|
|
|
|
// Test shutdown
|
|
zoneServer.Shutdown()
|
|
|
|
// Give shutdown time to process
|
|
time.Sleep(time.Millisecond * 100)
|
|
|
|
if !zoneServer.IsShuttingDown() {
|
|
t.Error("Expected zone to be shutting down")
|
|
}
|
|
}
|
|
|
|
// TestZoneManagerOperations tests zone manager functionality
|
|
func TestZoneManagerOperations(t *testing.T) {
|
|
// Skip this test - requires MySQL database connection
|
|
t.Skip("Skipping zone manager operations test - requires MySQL database")
|
|
|
|
// Example test for when MySQL is available:
|
|
// db, err := database.New(database.Config{
|
|
// DSN: "test_user:test_pass@tcp(localhost:3306)/test_db",
|
|
// })
|
|
// if err != nil {
|
|
// t.Fatalf("Failed to create test database: %v", err)
|
|
// }
|
|
// defer db.Close()
|
|
//
|
|
// // Create zone manager
|
|
// config := &ZoneManagerConfig{
|
|
// MaxZones: 5,
|
|
// MaxInstanceZones: 10,
|
|
// ProcessInterval: time.Millisecond * 100,
|
|
// CleanupInterval: time.Second * 1,
|
|
// EnableWeather: false,
|
|
// EnablePathfinding: false,
|
|
// EnableCombat: false,
|
|
// EnableSpellProcess: false,
|
|
// }
|
|
//
|
|
// zoneManager := NewZoneManager(config, db)
|
|
// if zoneManager == nil {
|
|
// t.Fatal("Expected non-nil zone manager")
|
|
// }
|
|
//
|
|
// // Additional test assertions would go here...
|
|
}
|
|
|
|
// TestPositionCalculations tests position and distance calculations
|
|
func TestPositionCalculations(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
x1, y1, z1 float32
|
|
x2, y2, z2 float32
|
|
expected2D float32
|
|
expected3D float32
|
|
}{
|
|
{"Origin to (3,4,12)", 0, 0, 0, 3, 4, 12, 5.0, 13.0},
|
|
{"Same point", 10, 20, 30, 10, 20, 30, 0.0, 0.0},
|
|
{"Unit distance", 0, 0, 0, 1, 0, 0, 1.0, 1.0},
|
|
{"Negative coordinates", -5, -5, -5, 5, 5, 5, 14.142136, 17.320507},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// Test 2D distance
|
|
distance2D := Distance2D(tc.x1, tc.y1, tc.x2, tc.y2)
|
|
if testAbs(distance2D-tc.expected2D) > 0.001 {
|
|
t.Errorf("Expected 2D distance %.3f, got %.3f", tc.expected2D, distance2D)
|
|
}
|
|
|
|
// Test 3D distance
|
|
distance3D := Distance3D(tc.x1, tc.y1, tc.z1, tc.x2, tc.y2, tc.z2)
|
|
if testAbs(distance3D-tc.expected3D) > 0.001 {
|
|
t.Errorf("Expected 3D distance %.3f, got %.3f", tc.expected3D, distance3D)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test heading calculations
|
|
headingTests := []struct {
|
|
name string
|
|
fromX, fromY float32
|
|
toX, toY float32
|
|
expectedRange [2]float32 // min, max acceptable range
|
|
}{
|
|
{"East", 0, 0, 1, 0, [2]float32{0, 90}},
|
|
{"North", 0, 0, 0, 1, [2]float32{0, 180}},
|
|
{"Northeast", 0, 0, 1, 1, [2]float32{30, 90}},
|
|
{"Same point", 5, 5, 5, 5, [2]float32{0, 360}},
|
|
}
|
|
|
|
for _, tc := range headingTests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
heading := CalculateHeading(tc.fromX, tc.fromY, tc.toX, tc.toY)
|
|
if heading < tc.expectedRange[0] || heading > tc.expectedRange[1] {
|
|
t.Logf("Heading %.2f for %s (acceptable range: %.2f-%.2f)", heading, tc.name, tc.expectedRange[0], tc.expectedRange[1])
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test heading normalization
|
|
normalizeTests := []struct {
|
|
input float32
|
|
expected float32
|
|
}{
|
|
{0, 0},
|
|
{256, 256},
|
|
{512, 0},
|
|
{600, 88},
|
|
{-100, 412},
|
|
}
|
|
|
|
for _, tc := range normalizeTests {
|
|
normalized := NormalizeHeading(tc.input)
|
|
if normalized != tc.expected {
|
|
t.Errorf("Expected normalized heading %.2f for input %.2f, got %.2f", tc.expected, tc.input, normalized)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestPositionStructs tests Position data structure
|
|
func TestPositionStructs(t *testing.T) {
|
|
// Test NewPosition
|
|
pos1 := NewPosition(10.0, 20.0, 30.0, 128.0)
|
|
if pos1.X != 10.0 || pos1.Y != 20.0 || pos1.Z != 30.0 || pos1.Heading != 128.0 {
|
|
t.Errorf("NewPosition failed: expected (10,20,30,128), got (%.2f,%.2f,%.2f,%.2f)", pos1.X, pos1.Y, pos1.Z, pos1.Heading)
|
|
}
|
|
|
|
pos2 := NewPosition(13.0, 24.0, 30.0, 128.0)
|
|
|
|
// Test distance calculations
|
|
distance2D := pos1.DistanceTo2D(pos2)
|
|
expected2D := float32(5.0)
|
|
if testAbs(distance2D-expected2D) > 0.001 {
|
|
t.Errorf("Expected 2D distance %.3f, got %.3f", expected2D, distance2D)
|
|
}
|
|
|
|
distance3D := pos1.DistanceTo3D(pos2)
|
|
expected3D := float32(5.0)
|
|
if testAbs(distance3D-expected3D) > 0.001 {
|
|
t.Errorf("Expected 3D distance %.3f, got %.3f", expected3D, distance3D)
|
|
}
|
|
|
|
// Test position operations
|
|
pos1.Set(5.0, 10.0, 15.0, 64.0)
|
|
if pos1.X != 5.0 || pos1.Y != 10.0 || pos1.Z != 15.0 || pos1.Heading != 64.0 {
|
|
t.Errorf("Set failed: expected (5,10,15,64), got (%.2f,%.2f,%.2f,%.2f)", pos1.X, pos1.Y, pos1.Z, pos1.Heading)
|
|
}
|
|
|
|
// Test copy
|
|
posCopy := pos1.Copy()
|
|
if !pos1.Equals(posCopy) {
|
|
t.Error("Expected copied position to equal original")
|
|
}
|
|
|
|
// Test bounding box
|
|
bbox := NewBoundingBox(0, 0, 0, 10, 10, 10)
|
|
if !bbox.Contains(5, 5, 5) {
|
|
t.Error("Expected bounding box to contain point (5,5,5)")
|
|
}
|
|
if bbox.Contains(15, 5, 5) {
|
|
t.Error("Expected bounding box to not contain point (15,5,5)")
|
|
}
|
|
if !bbox.Contains(0, 0, 0) {
|
|
t.Error("Expected bounding box to contain min point")
|
|
}
|
|
if !bbox.Contains(10, 10, 10) {
|
|
t.Error("Expected bounding box to contain max point")
|
|
}
|
|
}
|
|
|
|
// TestMovementManager tests movement management system
|
|
func TestMovementManager(t *testing.T) {
|
|
// Create test zone
|
|
zoneServer := NewZoneServer("test_movement")
|
|
config := &ZoneServerConfig{
|
|
ZoneName: "test_movement",
|
|
ZoneID: 1,
|
|
LoadMaps: false,
|
|
EnableWeather: false,
|
|
EnablePathfinding: false,
|
|
}
|
|
|
|
err := zoneServer.Initialize(config)
|
|
if err != nil {
|
|
t.Fatalf("Failed to initialize zone server: %v", err)
|
|
}
|
|
|
|
// Create movement manager
|
|
movementMgr := NewMobMovementManager(zoneServer)
|
|
if movementMgr == nil {
|
|
t.Fatal("Expected non-nil movement manager")
|
|
}
|
|
|
|
// Create and add a test spawn to the zone
|
|
spawnID := int32(1001)
|
|
testSpawn := spawn.NewSpawn()
|
|
testSpawn.SetID(spawnID)
|
|
testSpawn.SetX(0.0)
|
|
testSpawn.SetY(0.0, false)
|
|
testSpawn.SetZ(0.0)
|
|
testSpawn.SetHeadingFromFloat(0.0)
|
|
testSpawn.SetName("test_spawn")
|
|
|
|
err = zoneServer.AddSpawn(testSpawn)
|
|
if err != nil {
|
|
t.Fatalf("Failed to add test spawn: %v", err)
|
|
}
|
|
|
|
// Test adding spawn
|
|
movementMgr.AddMovementSpawn(spawnID)
|
|
|
|
// Test movement state
|
|
state := movementMgr.GetMovementState(spawnID)
|
|
if state == nil {
|
|
t.Error("Expected non-nil movement state")
|
|
} else if state.SpawnID != spawnID {
|
|
t.Errorf("Expected spawn ID %d, got %d", spawnID, state.SpawnID)
|
|
}
|
|
|
|
// Test movement command (this may fail due to missing methods but we test the interface)
|
|
err = movementMgr.MoveTo(spawnID, 10.0, 20.0, 30.0, DefaultRunSpeed)
|
|
// We don't check error here as it depends on spawn implementation
|
|
|
|
// Test removing spawn
|
|
movementMgr.RemoveMovementSpawn(spawnID)
|
|
|
|
// Test that state is cleaned up
|
|
state = movementMgr.GetMovementState(spawnID)
|
|
if state != nil {
|
|
t.Error("Expected movement state to be cleaned up after removal")
|
|
}
|
|
}
|
|
|
|
// TestInstanceTypes tests instance type functionality
|
|
func TestInstanceTypes(t *testing.T) {
|
|
testCases := []struct {
|
|
instanceType InstanceType
|
|
expected string
|
|
}{
|
|
{InstanceTypeNone, "None"},
|
|
{InstanceTypeGroupLockout, "Group Lockout"},
|
|
{InstanceTypeRaidPersist, "Raid Persistent"},
|
|
{InstanceTypePersonalHouse, "Personal House"},
|
|
{InstanceTypeSoloLockout, "Solo Lockout"},
|
|
{InstanceTypePublic, "Public"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.expected, func(t *testing.T) {
|
|
result := tc.instanceType.String()
|
|
if result != tc.expected {
|
|
t.Errorf("Expected instance type string '%s', got '%s'", tc.expected, result)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestConcurrentOperations tests thread safety
|
|
func TestConcurrentOperations(t *testing.T) {
|
|
// Skip this test - requires MySQL database connection
|
|
t.Skip("Skipping concurrent operations test - requires MySQL database")
|
|
|
|
// Example test for when MySQL is available:
|
|
// db, err := database.New(database.Config{
|
|
// DSN: "test_user:test_pass@tcp(localhost:3306)/test_db",
|
|
// })
|
|
// if err != nil {
|
|
// t.Fatalf("Failed to create test database: %v", err)
|
|
// }
|
|
// defer db.Close()
|
|
//
|
|
// // Test concurrent database reads
|
|
// var wg sync.WaitGroup
|
|
// const numGoroutines = 5
|
|
//
|
|
// for i := 0; i < numGoroutines; i++ {
|
|
// wg.Add(1)
|
|
// go func(id int) {
|
|
// defer wg.Done()
|
|
// zdb := NewZoneDatabase(db)
|
|
// _, err := zdb.LoadZoneData(1)
|
|
// if err != nil {
|
|
// t.Errorf("Goroutine %d failed to load zone data: %v", id, err)
|
|
// }
|
|
// }(i)
|
|
// }
|
|
//
|
|
// wg.Wait()
|
|
//
|
|
// // Additional concurrent test assertions would go here...
|
|
}
|
|
|
|
// TestConstants verifies various constants are properly defined
|
|
func TestConstants(t *testing.T) {
|
|
// Test distance constants
|
|
if SendSpawnDistance != 250.0 {
|
|
t.Errorf("Expected SendSpawnDistance 250.0, got %.2f", SendSpawnDistance)
|
|
}
|
|
|
|
if MaxChaseDistance != 80.0 {
|
|
t.Errorf("Expected MaxChaseDistance 80.0, got %.2f", MaxChaseDistance)
|
|
}
|
|
|
|
// Test expansion constants
|
|
if ExpansionDOF != 1024 {
|
|
t.Errorf("Expected ExpansionDOF 1024, got %d", ExpansionDOF)
|
|
}
|
|
|
|
// Test EQ2 heading constant
|
|
if EQ2HeadingMax != 512.0 {
|
|
t.Errorf("Expected EQ2HeadingMax 512.0, got %.2f", EQ2HeadingMax)
|
|
}
|
|
|
|
// Test default speeds
|
|
if DefaultWalkSpeed <= 0 {
|
|
t.Error("Expected DefaultWalkSpeed to be positive")
|
|
}
|
|
|
|
if DefaultRunSpeed <= DefaultWalkSpeed {
|
|
t.Error("Expected DefaultRunSpeed to be greater than DefaultWalkSpeed")
|
|
}
|
|
}
|
|
|
|
// Benchmark tests
|
|
|
|
// BenchmarkDistanceCalculation benchmarks distance calculations
|
|
func BenchmarkDistanceCalculation(b *testing.B) {
|
|
x1, y1, z1 := float32(100.0), float32(200.0), float32(300.0)
|
|
x2, y2, z2 := float32(150.0), float32(250.0), float32(350.0)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
Distance3D(x1, y1, z1, x2, y2, z2)
|
|
}
|
|
}
|
|
|
|
// BenchmarkPositionDistance benchmarks position-based distance calculations
|
|
func BenchmarkPositionDistance(b *testing.B) {
|
|
pos1 := NewPosition(100.0, 200.0, 300.0, 128.0)
|
|
pos2 := NewPosition(150.0, 250.0, 350.0, 256.0)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
pos1.DistanceTo3D(pos2)
|
|
}
|
|
}
|
|
|
|
// BenchmarkHeadingCalculation benchmarks heading calculations
|
|
func BenchmarkHeadingCalculation(b *testing.B) {
|
|
fromX, fromY := float32(0.0), float32(0.0)
|
|
toX, toY := float32(100.0), float32(100.0)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
CalculateHeading(fromX, fromY, toX, toY)
|
|
}
|
|
}
|
|
|
|
// BenchmarkDatabaseOperations benchmarks database operations
|
|
func BenchmarkDatabaseOperations(b *testing.B) {
|
|
// Skip this benchmark - requires MySQL database connection
|
|
b.Skip("Skipping database operations benchmark - requires MySQL database")
|
|
|
|
// Example benchmark for when MySQL is available:
|
|
// db, err := database.New(database.Config{
|
|
// DSN: "test_user:test_pass@tcp(localhost:3306)/test_db",
|
|
// })
|
|
// if err != nil {
|
|
// b.Fatalf("Failed to create benchmark database: %v", err)
|
|
// }
|
|
// defer db.Close()
|
|
//
|
|
// zdb := NewZoneDatabase(db)
|
|
//
|
|
// b.ResetTimer()
|
|
// for i := 0; i < b.N; i++ {
|
|
// _, err := zdb.LoadZoneData(1)
|
|
// if err != nil {
|
|
// b.Fatalf("Failed to load zone data: %v", err)
|
|
// }
|
|
// }
|
|
}
|
|
|
|
// BenchmarkZoneManagerOperations benchmarks zone manager operations
|
|
func BenchmarkZoneManagerOperations(b *testing.B) {
|
|
// Skip this benchmark - requires MySQL database connection
|
|
b.Skip("Skipping zone manager operations benchmark - requires MySQL database")
|
|
|
|
// Example benchmark for when MySQL is available:
|
|
// db, err := database.New(database.Config{
|
|
// DSN: "test_user:test_pass@tcp(localhost:3306)/test_db",
|
|
// })
|
|
// if err != nil {
|
|
// b.Fatalf("Failed to create benchmark database: %v", err)
|
|
// }
|
|
// defer db.Close()
|
|
//
|
|
// config := &ZoneManagerConfig{
|
|
// MaxZones: 10,
|
|
// MaxInstanceZones: 20,
|
|
// ProcessInterval: time.Millisecond * 100,
|
|
// CleanupInterval: time.Second * 1,
|
|
// }
|
|
//
|
|
// zoneManager := NewZoneManager(config, db)
|
|
//
|
|
// b.ResetTimer()
|
|
// for i := 0; i < b.N; i++ {
|
|
// zoneManager.GetStatistics()
|
|
// }
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func testAbs(x float32) float32 {
|
|
if x < 0 {
|
|
return -x
|
|
}
|
|
return x
|
|
} |