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