eq2go/internal/appearances/benchmark_test.go
2025-08-08 10:44:31 -05:00

549 lines
13 KiB
Go

package appearances
import (
"fmt"
"math/rand"
"sync"
"testing"
"eq2emu/internal/database"
)
// Global shared master list for benchmarks to avoid repeated setup
var (
sharedAppearanceMasterList *MasterList
sharedAppearances []*Appearance
appearanceSetupOnce sync.Once
)
// setupSharedAppearanceMasterList creates the shared master list once
func setupSharedAppearanceMasterList(b *testing.B) {
appearanceSetupOnce.Do(func() {
// Create test database
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
if err != nil {
b.Fatalf("Failed to create test database: %v", err)
}
sharedAppearanceMasterList = NewMasterList()
// Pre-populate with appearances for realistic testing
const numAppearances = 1000
sharedAppearances = make([]*Appearance, numAppearances)
clientVersions := []int16{1096, 1200, 1300, 1400, 1500}
nameTemplates := []string{
"Human %s",
"Elf %s",
"Dwarf %s",
"Halfling %s",
"Barbarian %s",
"Dark Elf %s",
"Wood Elf %s",
"High Elf %s",
"Gnome %s",
"Troll %s",
}
genders := []string{"Male", "Female"}
for i := range numAppearances {
sharedAppearances[i] = NewWithData(
int32(i+1),
fmt.Sprintf(nameTemplates[i%len(nameTemplates)], genders[i%len(genders)]),
clientVersions[i%len(clientVersions)],
db,
)
sharedAppearanceMasterList.AddAppearance(sharedAppearances[i])
}
})
}
// createTestAppearance creates an appearance for benchmarking
func createTestAppearance(b *testing.B, id int32) *Appearance {
b.Helper()
// Use nil database for benchmarking in-memory operations
clientVersions := []int16{1096, 1200, 1300, 1400, 1500}
nameTemplates := []string{"Human", "Elf", "Dwarf", "Halfling"}
genders := []string{"Male", "Female"}
app := NewWithData(
id,
fmt.Sprintf("Benchmark %s %s", nameTemplates[id%int32(len(nameTemplates))], genders[id%2]),
clientVersions[id%int32(len(clientVersions))],
nil,
)
return app
}
// BenchmarkAppearanceCreation measures appearance creation performance
func BenchmarkAppearanceCreation(b *testing.B) {
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
if err != nil {
b.Fatalf("Failed to create test database: %v", err)
}
defer db.Close()
b.ResetTimer()
b.Run("Sequential", func(b *testing.B) {
for i := 0; i < b.N; i++ {
app := New(db)
app.ID = int32(i)
app.Name = fmt.Sprintf("Appearance %d", i)
app.MinClient = 1096
_ = app
}
})
b.Run("Parallel", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
id := int32(0)
for pb.Next() {
app := New(db)
app.ID = id
app.Name = fmt.Sprintf("Appearance %d", id)
app.MinClient = 1096
id++
_ = app
}
})
})
b.Run("NewWithData", func(b *testing.B) {
for i := 0; i < b.N; i++ {
app := NewWithData(int32(i), fmt.Sprintf("Appearance %d", i), 1096, db)
_ = app
}
})
}
// BenchmarkAppearanceOperations measures individual appearance operations
func BenchmarkAppearanceOperations(b *testing.B) {
app := createTestAppearance(b, 1001)
b.Run("GetID", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = app.GetID()
}
})
})
b.Run("GetName", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = app.GetName()
}
})
})
b.Run("GetMinClientVersion", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = app.GetMinClientVersion()
}
})
})
b.Run("IsCompatibleWithClient", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = app.IsCompatibleWithClient(1200)
}
})
})
b.Run("Clone", func(b *testing.B) {
for b.Loop() {
_ = app.Clone()
}
})
b.Run("IsNew", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = app.IsNew()
}
})
})
}
// BenchmarkMasterListOperations measures master list performance
func BenchmarkMasterListOperations(b *testing.B) {
setupSharedAppearanceMasterList(b)
ml := sharedAppearanceMasterList
b.Run("GetAppearance", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
id := int32(rand.Intn(1000) + 1)
_ = ml.GetAppearance(id)
}
})
})
b.Run("AddAppearance", func(b *testing.B) {
// Create a separate master list for add operations
addML := NewMasterList()
startID := int32(10000)
// Pre-create appearances to measure just the Add operation
appsToAdd := make([]*Appearance, b.N)
for i := 0; i < b.N; i++ {
appsToAdd[i] = createTestAppearance(b, startID+int32(i))
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
addML.AddAppearance(appsToAdd[i])
}
})
b.Run("GetAppearanceSafe", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
id := int32(rand.Intn(1000) + 1)
_, _ = ml.GetAppearanceSafe(id)
}
})
})
b.Run("HasAppearance", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
id := int32(rand.Intn(1000) + 1)
_ = ml.HasAppearance(id)
}
})
})
b.Run("FindAppearancesByMinClient", func(b *testing.B) {
clientVersions := []int16{1096, 1200, 1300, 1400, 1500}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
version := clientVersions[rand.Intn(len(clientVersions))]
_ = ml.FindAppearancesByMinClient(version)
}
})
})
b.Run("GetCompatibleAppearances", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
version := int16(1200 + rand.Intn(300))
_ = ml.GetCompatibleAppearances(version)
}
})
})
b.Run("FindAppearancesByName", func(b *testing.B) {
searchTerms := []string{"human", "male", "elf", "female", "dwarf"}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
term := searchTerms[rand.Intn(len(searchTerms))]
_ = ml.FindAppearancesByName(term)
}
})
})
b.Run("GetAppearancesByIDRange", func(b *testing.B) {
for b.Loop() {
start := int32(rand.Intn(900) + 1)
end := start + int32(rand.Intn(100)+10)
_ = ml.GetAppearancesByIDRange(start, end)
}
})
b.Run("GetAppearancesByClientRange", func(b *testing.B) {
for b.Loop() {
minVersion := int16(1096 + rand.Intn(200))
maxVersion := minVersion + int16(rand.Intn(200))
_ = ml.GetAppearancesByClientRange(minVersion, maxVersion)
}
})
b.Run("GetClientVersions", func(b *testing.B) {
for b.Loop() {
_ = ml.GetClientVersions()
}
})
b.Run("Size", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = ml.Size()
}
})
})
b.Run("GetAppearanceCount", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = ml.GetAppearanceCount()
}
})
})
}
// BenchmarkConcurrentOperations tests mixed workload performance
func BenchmarkConcurrentOperations(b *testing.B) {
setupSharedAppearanceMasterList(b)
ml := sharedAppearanceMasterList
b.Run("MixedOperations", func(b *testing.B) {
clientVersions := []int16{1096, 1200, 1300, 1400, 1500}
searchTerms := []string{"human", "male", "elf", "female", "dwarf"}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
switch rand.Intn(10) {
case 0:
id := int32(rand.Intn(1000) + 1)
_ = ml.GetAppearance(id)
case 1:
id := int32(rand.Intn(1000) + 1)
_, _ = ml.GetAppearanceSafe(id)
case 2:
id := int32(rand.Intn(1000) + 1)
_ = ml.HasAppearance(id)
case 3:
version := clientVersions[rand.Intn(len(clientVersions))]
_ = ml.FindAppearancesByMinClient(version)
case 4:
version := int16(1200 + rand.Intn(300))
_ = ml.GetCompatibleAppearances(version)
case 5:
term := searchTerms[rand.Intn(len(searchTerms))]
_ = ml.FindAppearancesByName(term)
case 6:
start := int32(rand.Intn(900) + 1)
end := start + int32(rand.Intn(100)+10)
_ = ml.GetAppearancesByIDRange(start, end)
case 7:
minVersion := int16(1096 + rand.Intn(200))
maxVersion := minVersion + int16(rand.Intn(200))
_ = ml.GetAppearancesByClientRange(minVersion, maxVersion)
case 8:
_ = ml.GetClientVersions()
case 9:
_ = ml.Size()
}
}
})
})
}
// BenchmarkMemoryAllocation measures memory allocation patterns
func BenchmarkMemoryAllocation(b *testing.B) {
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
if err != nil {
b.Fatalf("Failed to create test database: %v", err)
}
defer db.Close()
b.Run("AppearanceAllocation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
app := New(db)
app.ID = int32(i)
app.Name = fmt.Sprintf("Appearance %d", i)
app.MinClient = 1096
_ = app
}
})
b.Run("NewWithDataAllocation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
app := NewWithData(int32(i), fmt.Sprintf("Appearance %d", i), 1096, db)
_ = app
}
})
b.Run("MasterListAllocation", func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
ml := NewMasterList()
_ = ml
}
})
b.Run("AddAppearance_Allocations", func(b *testing.B) {
b.ReportAllocs()
ml := NewMasterList()
for i := 0; i < b.N; i++ {
app := createTestAppearance(b, int32(i+1))
ml.AddAppearance(app)
}
})
b.Run("FindAppearancesByMinClient_Allocations", func(b *testing.B) {
setupSharedAppearanceMasterList(b)
ml := sharedAppearanceMasterList
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
_ = ml.FindAppearancesByMinClient(1096)
}
})
b.Run("FindAppearancesByName_Allocations", func(b *testing.B) {
setupSharedAppearanceMasterList(b)
ml := sharedAppearanceMasterList
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
_ = ml.FindAppearancesByName("human")
}
})
b.Run("GetClientVersions_Allocations", func(b *testing.B) {
setupSharedAppearanceMasterList(b)
ml := sharedAppearanceMasterList
b.ReportAllocs()
b.ResetTimer()
for b.Loop() {
_ = ml.GetClientVersions()
}
})
}
// BenchmarkUpdateOperations measures update performance
func BenchmarkUpdateOperations(b *testing.B) {
setupSharedAppearanceMasterList(b)
ml := sharedAppearanceMasterList
b.Run("UpdateAppearance", func(b *testing.B) {
// Create appearances to update
updateApps := make([]*Appearance, b.N)
for i := 0; i < b.N; i++ {
updateApps[i] = createTestAppearance(b, int32((i%1000)+1))
updateApps[i].Name = "Updated Name"
updateApps[i].MinClient = 1600
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ml.UpdateAppearance(updateApps[i])
}
})
b.Run("RemoveAppearance", func(b *testing.B) {
// Create a separate master list for removal testing
removeML := NewMasterList()
// Add appearances to remove
for i := 0; i < b.N; i++ {
app := createTestAppearance(b, int32(i+1))
removeML.AddAppearance(app)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
removeML.RemoveAppearance(int32(i + 1))
}
})
}
// BenchmarkValidation measures validation performance
func BenchmarkValidation(b *testing.B) {
setupSharedAppearanceMasterList(b)
ml := sharedAppearanceMasterList
b.Run("ValidateAppearances", func(b *testing.B) {
for b.Loop() {
_ = ml.ValidateAppearances()
}
})
b.Run("IsValid", func(b *testing.B) {
for b.Loop() {
_ = ml.IsValid()
}
})
b.Run("IndividualValidation", func(b *testing.B) {
app := createTestAppearance(b, 1001)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = app.IsCompatibleWithClient(1200)
}
})
})
}
// BenchmarkCloneOperations measures cloning performance
func BenchmarkCloneOperations(b *testing.B) {
setupSharedAppearanceMasterList(b)
ml := sharedAppearanceMasterList
b.Run("GetAppearanceClone", func(b *testing.B) {
for b.Loop() {
id := int32(rand.Intn(1000) + 1)
_ = ml.GetAppearanceClone(id)
}
})
b.Run("DirectClone", func(b *testing.B) {
app := createTestAppearance(b, 1001)
for b.Loop() {
_ = app.Clone()
}
})
}
// BenchmarkStatistics measures statistics performance
func BenchmarkStatistics(b *testing.B) {
setupSharedAppearanceMasterList(b)
ml := sharedAppearanceMasterList
b.Run("GetStatistics", func(b *testing.B) {
for b.Loop() {
_ = ml.GetStatistics()
}
})
b.Run("GetAllAppearances", func(b *testing.B) {
for b.Loop() {
_ = ml.GetAllAppearances()
}
})
b.Run("GetAllAppearancesList", func(b *testing.B) {
for b.Loop() {
_ = ml.GetAllAppearancesList()
}
})
}
// BenchmarkStringOperations measures string operations performance
func BenchmarkStringOperations(b *testing.B) {
b.Run("GetAppearanceType", func(b *testing.B) {
typeNames := []string{"hair_color1", "skin_color", "eye_color", "unknown_type"}
for b.Loop() {
typeName := typeNames[rand.Intn(len(typeNames))]
_ = GetAppearanceType(typeName)
}
})
b.Run("GetAppearanceTypeName", func(b *testing.B) {
typeConstants := []int8{AppearanceHairColor1, AppearanceSkinColor, AppearanceEyeColor, -1}
for b.Loop() {
typeConst := typeConstants[rand.Intn(len(typeConstants))]
_ = GetAppearanceTypeName(typeConst)
}
})
b.Run("ContainsSubstring", func(b *testing.B) {
testStrings := []string{"Human Male Fighter", "Elf Female Mage", "Dwarf Male Warrior"}
searchTerms := []string{"human", "male", "elf", "notfound"}
for b.Loop() {
str := testStrings[rand.Intn(len(testStrings))]
term := searchTerms[rand.Intn(len(searchTerms))]
_ = contains(str, term)
}
})
}