package housing import ( "context" "fmt" "math/rand" "testing" "time" "zombiezen.com/go/sqlite/sqlitex" ) // setupBenchmarkDB creates a test database with sample data for benchmarking func setupBenchmarkDB(b *testing.B) (*DatabaseHousingManager, context.Context) { // Create truly unique database name to avoid cross-benchmark contamination dbName := fmt.Sprintf("file:bench_%s_%d.db?mode=memory&cache=shared", b.Name(), rand.Int63()) pool, err := sqlitex.NewPool(dbName, sqlitex.PoolOptions{}) if err != nil { b.Fatalf("Failed to create benchmark database pool: %v", err) } dhm := NewDatabaseHousingManager(pool) ctx := context.Background() if err := dhm.EnsureHousingTables(ctx); err != nil { b.Fatalf("Failed to create benchmark tables: %v", err) } return dhm, ctx } // insertBenchmarkData inserts a large dataset for benchmarking func insertBenchmarkData(b *testing.B, dhm *DatabaseHousingManager, ctx context.Context, houseZones, playerHouses int) { // Insert house zones for i := 1; i <= houseZones; i++ { zone := &HouseZone{ ID: int32(i), Name: fmt.Sprintf("House Type %d", i), ZoneID: int32(100 + i), CostCoin: int64(50000 + i*10000), CostStatus: int64(1000 + i*100), UpkeepCoin: int64(5000 + i*1000), UpkeepStatus: int64(100 + i*10), Alignment: int8(i % 3 - 1), // -1, 0, 1 GuildLevel: int8(i % 50), VaultSlots: int(4 + i%4), MaxItems: int(100 + i*50), MaxVisitors: int(10 + i*5), UpkeepPeriod: 604800, // 1 week Description: fmt.Sprintf("Benchmark house type %d description", i), } if err := dhm.SaveHouseZone(ctx, zone); err != nil { b.Fatalf("Failed to insert benchmark house zone: %v", err) } } // Insert player houses for i := 1; i <= playerHouses; i++ { houseData := PlayerHouseData{ CharacterID: int32(1000 + i), HouseID: int32((i % houseZones) + 1), InstanceID: int32(5000 + i), UpkeepDue: time.Now().Add(time.Duration(i%168) * time.Hour), // Random within a week EscrowCoins: int64(10000 + i*1000), EscrowStatus: int64(200 + i*20), Status: HouseStatusActive, HouseName: fmt.Sprintf("House %d", i), VisitPermission: int8(i % 3), PublicNote: fmt.Sprintf("Welcome to house %d!", i), PrivateNote: fmt.Sprintf("Private note %d", i), AllowFriends: i%2 == 0, AllowGuild: i%3 == 0, RequireApproval: i%4 == 0, ShowOnDirectory: i%5 != 0, AllowDecoration: i%6 != 0, TaxExempt: i%10 == 0, } _, err := dhm.AddPlayerHouse(ctx, houseData) if err != nil { b.Fatalf("Failed to insert benchmark player house: %v", err) } } } // insertHouseRelatedData inserts deposits, history, access, etc. for benchmarking func insertHouseRelatedData(b *testing.B, dhm *DatabaseHousingManager, ctx context.Context, houseID int64, entries int) { // Insert deposits for i := 1; i <= entries; i++ { deposit := HouseDeposit{ Timestamp: time.Now().Add(-time.Duration(i) * time.Hour), Amount: int64(1000 + i*100), LastAmount: int64(2000 + i*100), Status: int64(50 + i*5), LastStatus: int64(100 + i*5), Name: fmt.Sprintf("Player %d", i%10+1), CharacterID: int32(2000 + i%10), } if err := dhm.SaveDeposit(ctx, houseID, deposit); err != nil { b.Fatalf("Failed to insert benchmark deposit: %v", err) } } // Insert history for i := 1; i <= entries; i++ { history := HouseHistory{ Timestamp: time.Now().Add(-time.Duration(i*2) * time.Hour), Amount: int64(500 + i*50), Status: int64(25 + i*2), Reason: fmt.Sprintf("Transaction %d", i), Name: fmt.Sprintf("Player %d", i%10+1), CharacterID: int32(2000 + i%10), PosFlag: int8(i % 2), Type: int(i % 5), } if err := dhm.AddHistory(ctx, houseID, history); err != nil { b.Fatalf("Failed to insert benchmark history: %v", err) } } // Insert access entries accessList := make([]HouseAccess, 0, entries/10) // Fewer access entries for i := 1; i <= entries/10; i++ { access := HouseAccess{ CharacterID: int32(3000 + i), PlayerName: fmt.Sprintf("AccessPlayer%d", i), AccessLevel: int8(i % 3), Permissions: int32(i % 16), // 0-15 GrantedBy: int32(1001), GrantedDate: time.Now().Add(-time.Duration(i*24) * time.Hour), ExpiresDate: time.Now().Add(time.Duration(30-i) * 24 * time.Hour), Notes: fmt.Sprintf("Access notes for player %d", i), } accessList = append(accessList, access) } if len(accessList) > 0 { if err := dhm.SaveHouseAccess(ctx, houseID, accessList); err != nil { b.Fatalf("Failed to insert benchmark access: %v", err) } } // Insert amenities for i := 1; i <= entries/5; i++ { amenity := HouseAmenity{ ID: int32(i), Type: int(i % 10), Name: fmt.Sprintf("Amenity %d", i), Cost: int64(1000 + i*500), StatusCost: int64(20 + i*10), PurchaseDate: time.Now().Add(-time.Duration(i*12) * time.Hour), X: float32(100 + i*10), Y: float32(200 + i*15), Z: float32(50 + i*5), Heading: float32(i % 360), IsActive: i%2 == 0, } if err := dhm.SaveHouseAmenity(ctx, houseID, amenity); err != nil { b.Fatalf("Failed to insert benchmark amenity: %v", err) } } // Insert items for i := 1; i <= entries/3; i++ { item := HouseItem{ ID: int64(i), ItemID: int32(10000 + i), CharacterID: int32(1001), X: float32(150 + i*5), Y: float32(250 + i*7), Z: float32(75 + i*3), Heading: float32(i % 360), PitchX: float32(i % 10), PitchY: float32(i % 5), RollX: float32(i % 15), RollY: float32(i % 8), PlacedDate: time.Now().Add(-time.Duration(i*6) * time.Hour), Quantity: int32(1 + i%5), Condition: int8(100 - i%100), House: fmt.Sprintf("room_%d", i%5), } if err := dhm.SaveHouseItem(ctx, houseID, item); err != nil { b.Fatalf("Failed to insert benchmark item: %v", err) } } } // BenchmarkLoadHouseZones benchmarks loading all house zones func BenchmarkLoadHouseZones(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 100, 0) // 100 house zones, no player houses b.ResetTimer() for i := 0; i < b.N; i++ { zones, err := dhm.LoadHouseZones(ctx) if err != nil { b.Fatalf("LoadHouseZones failed: %v", err) } if len(zones) != 100 { b.Errorf("Expected 100 zones, got %d", len(zones)) } } } // BenchmarkLoadHouseZone benchmarks loading a single house zone func BenchmarkLoadHouseZone(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 100, 0) b.ResetTimer() for i := 0; i < b.N; i++ { zone, err := dhm.LoadHouseZone(ctx, int32((i%100)+1)) if err != nil { b.Fatalf("LoadHouseZone failed: %v", err) } if zone == nil { b.Error("LoadHouseZone returned nil zone") } } } // BenchmarkSaveHouseZone benchmarks saving a house zone func BenchmarkSaveHouseZone(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) b.ResetTimer() for i := 0; i < b.N; i++ { zone := &HouseZone{ ID: int32(1000 + i), Name: fmt.Sprintf("Benchmark House %d", i), ZoneID: int32(2000 + i), CostCoin: int64(50000 + i*1000), CostStatus: int64(1000 + i*10), UpkeepCoin: int64(5000 + i*100), UpkeepStatus: int64(100 + i), Alignment: int8(i % 3 - 1), GuildLevel: int8(i % 50), VaultSlots: int(4 + i%4), MaxItems: int(100 + i*10), MaxVisitors: int(10 + i), UpkeepPeriod: 604800, Description: fmt.Sprintf("Benchmark description %d", i), } if err := dhm.SaveHouseZone(ctx, zone); err != nil { b.Fatalf("SaveHouseZone failed: %v", err) } } } // BenchmarkLoadPlayerHouses benchmarks loading player houses func BenchmarkLoadPlayerHouses(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 10, 1000) // 10 house types, 1000 player houses b.ResetTimer() for i := 0; i < b.N; i++ { characterID := int32(1000 + (i%1000) + 1) houses, err := dhm.LoadPlayerHouses(ctx, characterID) if err != nil { b.Fatalf("LoadPlayerHouses failed: %v", err) } if len(houses) != 1 { b.Errorf("Expected 1 house for character %d, got %d", characterID, len(houses)) } } } // BenchmarkLoadPlayerHouse benchmarks loading a single player house func BenchmarkLoadPlayerHouse(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 10, 100) b.ResetTimer() for i := 0; i < b.N; i++ { houseID := int64((i % 100) + 1) house, err := dhm.LoadPlayerHouse(ctx, houseID) if err != nil { b.Fatalf("LoadPlayerHouse failed: %v", err) } if house == nil { b.Error("LoadPlayerHouse returned nil house") } } } // BenchmarkAddPlayerHouse benchmarks adding player houses func BenchmarkAddPlayerHouse(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 10, 0) // Just house zones b.ResetTimer() for i := 0; i < b.N; i++ { houseData := PlayerHouseData{ CharacterID: int32(5000 + i), HouseID: int32((i % 10) + 1), InstanceID: int32(10000 + i), UpkeepDue: time.Now().Add(24 * time.Hour), EscrowCoins: int64(25000 + i*100), EscrowStatus: int64(500 + i*5), Status: HouseStatusActive, HouseName: fmt.Sprintf("Benchmark House %d", i), VisitPermission: int8(i % 3), PublicNote: fmt.Sprintf("Welcome to benchmark house %d", i), PrivateNote: fmt.Sprintf("Private note %d", i), AllowFriends: i%2 == 0, AllowGuild: i%3 == 0, RequireApproval: i%4 == 0, ShowOnDirectory: i%5 != 0, AllowDecoration: i%6 != 0, TaxExempt: i%10 == 0, } _, err := dhm.AddPlayerHouse(ctx, houseData) if err != nil { b.Fatalf("AddPlayerHouse failed: %v", err) } } } // BenchmarkLoadDeposits benchmarks loading house deposits func BenchmarkLoadDeposits(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 1) // Insert deposit data for house ID 1 insertHouseRelatedData(b, dhm, ctx, 1, 500) // 500 deposits b.ResetTimer() for i := 0; i < b.N; i++ { deposits, err := dhm.LoadDeposits(ctx, 1) if err != nil { b.Fatalf("LoadDeposits failed: %v", err) } if len(deposits) == 0 { b.Error("LoadDeposits returned no deposits") } } } // BenchmarkSaveDeposit benchmarks saving deposits func BenchmarkSaveDeposit(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 1) b.ResetTimer() for i := 0; i < b.N; i++ { deposit := HouseDeposit{ Timestamp: time.Now(), Amount: int64(1000 + i*10), LastAmount: int64(2000 + i*10), Status: int64(50 + i), LastStatus: int64(100 + i), Name: fmt.Sprintf("Benchmark Player %d", i), CharacterID: int32(2000 + i), } if err := dhm.SaveDeposit(ctx, 1, deposit); err != nil { b.Fatalf("SaveDeposit failed: %v", err) } } } // BenchmarkLoadHistory benchmarks loading house history func BenchmarkLoadHistory(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 1) // Insert history data for house ID 1 insertHouseRelatedData(b, dhm, ctx, 1, 500) // 500 history entries b.ResetTimer() for i := 0; i < b.N; i++ { history, err := dhm.LoadHistory(ctx, 1) if err != nil { b.Fatalf("LoadHistory failed: %v", err) } if len(history) == 0 { b.Error("LoadHistory returned no history") } } } // BenchmarkAddHistory benchmarks adding history entries func BenchmarkAddHistory(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 1) b.ResetTimer() for i := 0; i < b.N; i++ { history := HouseHistory{ Timestamp: time.Now(), Amount: int64(500 + i*5), Status: int64(25 + i), Reason: fmt.Sprintf("Benchmark transaction %d", i), Name: fmt.Sprintf("Benchmark Player %d", i), CharacterID: int32(2000 + i), PosFlag: int8(i % 2), Type: int(i % 5), } if err := dhm.AddHistory(ctx, 1, history); err != nil { b.Fatalf("AddHistory failed: %v", err) } } } // BenchmarkLoadHouseAccess benchmarks loading house access func BenchmarkLoadHouseAccess(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 1) // Insert access data for house ID 1 insertHouseRelatedData(b, dhm, ctx, 1, 100) // Will create 10 access entries b.ResetTimer() for i := 0; i < b.N; i++ { access, err := dhm.LoadHouseAccess(ctx, 1) if err != nil { b.Fatalf("LoadHouseAccess failed: %v", err) } if len(access) == 0 { b.Error("LoadHouseAccess returned no access entries") } } } // BenchmarkSaveHouseAccess benchmarks saving house access func BenchmarkSaveHouseAccess(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 1) b.ResetTimer() for i := 0; i < b.N; i++ { accessList := []HouseAccess{ { CharacterID: int32(4000 + i), PlayerName: fmt.Sprintf("BenchPlayer%d", i), AccessLevel: int8(i % 3), Permissions: int32(i % 16), GrantedBy: 1001, GrantedDate: time.Now(), ExpiresDate: time.Now().Add(30 * 24 * time.Hour), Notes: fmt.Sprintf("Benchmark access %d", i), }, } if err := dhm.SaveHouseAccess(ctx, 1, accessList); err != nil { b.Fatalf("SaveHouseAccess failed: %v", err) } } } // BenchmarkLoadHouseAmenities benchmarks loading house amenities func BenchmarkLoadHouseAmenities(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 1) // Insert amenity data for house ID 1 insertHouseRelatedData(b, dhm, ctx, 1, 100) // Will create 20 amenities b.ResetTimer() for i := 0; i < b.N; i++ { amenities, err := dhm.LoadHouseAmenities(ctx, 1) if err != nil { b.Fatalf("LoadHouseAmenities failed: %v", err) } if len(amenities) == 0 { b.Error("LoadHouseAmenities returned no amenities") } } } // BenchmarkSaveHouseAmenity benchmarks saving house amenities func BenchmarkSaveHouseAmenity(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 1) b.ResetTimer() for i := 0; i < b.N; i++ { amenity := HouseAmenity{ ID: int32(5000 + i), Type: int(i % 10), Name: fmt.Sprintf("Benchmark Amenity %d", i), Cost: int64(1000 + i*100), StatusCost: int64(20 + i*2), PurchaseDate: time.Now(), X: float32(100 + i), Y: float32(200 + i), Z: float32(50 + i), Heading: float32(i % 360), IsActive: i%2 == 0, } if err := dhm.SaveHouseAmenity(ctx, 1, amenity); err != nil { b.Fatalf("SaveHouseAmenity failed: %v", err) } } } // BenchmarkLoadHouseItems benchmarks loading house items func BenchmarkLoadHouseItems(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 1) // Insert item data for house ID 1 insertHouseRelatedData(b, dhm, ctx, 1, 150) // Will create 50 items b.ResetTimer() for i := 0; i < b.N; i++ { items, err := dhm.LoadHouseItems(ctx, 1) if err != nil { b.Fatalf("LoadHouseItems failed: %v", err) } if len(items) == 0 { b.Error("LoadHouseItems returned no items") } } } // BenchmarkSaveHouseItem benchmarks saving house items func BenchmarkSaveHouseItem(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 1) b.ResetTimer() for i := 0; i < b.N; i++ { item := HouseItem{ ID: int64(6000 + i), ItemID: int32(20000 + i), CharacterID: 1001, X: float32(150 + i), Y: float32(250 + i), Z: float32(75 + i), Heading: float32(i % 360), PitchX: float32(i % 10), PitchY: float32(i % 5), RollX: float32(i % 15), RollY: float32(i % 8), PlacedDate: time.Now(), Quantity: int32(1 + i%5), Condition: int8(100 - i%100), House: "benchmark", } if err := dhm.SaveHouseItem(ctx, 1, item); err != nil { b.Fatalf("SaveHouseItem failed: %v", err) } } } // BenchmarkUpdateHouseUpkeepDue benchmarks updating house upkeep due date func BenchmarkUpdateHouseUpkeepDue(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 100) b.ResetTimer() for i := 0; i < b.N; i++ { houseID := int64((i % 100) + 1) newUpkeepDue := time.Now().Add(time.Duration(i) * time.Hour) if err := dhm.UpdateHouseUpkeepDue(ctx, houseID, newUpkeepDue); err != nil { b.Fatalf("UpdateHouseUpkeepDue failed: %v", err) } } } // BenchmarkUpdateHouseEscrow benchmarks updating house escrow func BenchmarkUpdateHouseEscrow(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 100) b.ResetTimer() for i := 0; i < b.N; i++ { houseID := int64((i % 100) + 1) coins := int64(50000 + i*1000) status := int64(1000 + i*10) if err := dhm.UpdateHouseEscrow(ctx, houseID, coins, status); err != nil { b.Fatalf("UpdateHouseEscrow failed: %v", err) } } } // BenchmarkGetHousesForUpkeep benchmarks getting houses for upkeep func BenchmarkGetHousesForUpkeep(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 10, 1000) b.ResetTimer() for i := 0; i < b.N; i++ { cutoffTime := time.Now().Add(time.Duration(i%200) * time.Hour) houses, err := dhm.GetHousesForUpkeep(ctx, cutoffTime) if err != nil { b.Fatalf("GetHousesForUpkeep failed: %v", err) } _ = houses // Prevent unused variable warning } } // BenchmarkGetHouseStatistics benchmarks getting house statistics func BenchmarkGetHouseStatistics(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 20, 2000) // Add some deposits and history for more realistic stats insertHouseRelatedData(b, dhm, ctx, 1, 100) insertHouseRelatedData(b, dhm, ctx, 2, 150) b.ResetTimer() for i := 0; i < b.N; i++ { stats, err := dhm.GetHouseStatistics(ctx) if err != nil { b.Fatalf("GetHouseStatistics failed: %v", err) } if stats.TotalHouses != 2000 { b.Errorf("Expected 2000 total houses, got %d", stats.TotalHouses) } } } // BenchmarkGetHouseByInstance benchmarks finding houses by instance ID func BenchmarkGetHouseByInstance(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 10, 1000) b.ResetTimer() for i := 0; i < b.N; i++ { instanceID := int32(5001 + (i % 1000)) house, err := dhm.GetHouseByInstance(ctx, instanceID) if err != nil { b.Fatalf("GetHouseByInstance failed: %v", err) } if house == nil { b.Error("GetHouseByInstance returned nil house") } } } // BenchmarkGetNextHouseID benchmarks getting the next house ID func BenchmarkGetNextHouseID(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 5, 100) b.ResetTimer() for i := 0; i < b.N; i++ { nextID, err := dhm.GetNextHouseID(ctx) if err != nil { b.Fatalf("GetNextHouseID failed: %v", err) } if nextID <= 100 { b.Errorf("Expected next ID > 100, got %d", nextID) } } } // BenchmarkDeletePlayerHouse benchmarks deleting player houses func BenchmarkDeletePlayerHouse(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) // We need to create houses to delete in each iteration b.ResetTimer() for i := 0; i < b.N; i++ { b.StopTimer() // Create a house to delete houseData := PlayerHouseData{ CharacterID: int32(7000 + i), HouseID: 1, InstanceID: int32(8000 + i), UpkeepDue: time.Now().Add(24 * time.Hour), EscrowCoins: 25000, EscrowStatus: 500, Status: HouseStatusActive, HouseName: fmt.Sprintf("DeleteMe %d", i), VisitPermission: 1, AllowFriends: true, ShowOnDirectory: true, } houseID, err := dhm.AddPlayerHouse(ctx, houseData) if err != nil { b.Fatalf("Failed to create house for deletion: %v", err) } b.StartTimer() // Delete the house if err := dhm.DeletePlayerHouse(ctx, houseID); err != nil { b.Fatalf("DeletePlayerHouse failed: %v", err) } } } // BenchmarkConcurrentReads benchmarks concurrent read operations func BenchmarkConcurrentReads(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 10, 100) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { characterID := int32(1001 + (i % 100)) houses, err := dhm.LoadPlayerHouses(ctx, characterID) if err != nil { b.Errorf("LoadPlayerHouses failed: %v", err) } if len(houses) != 1 { b.Errorf("Expected 1 house, got %d", len(houses)) } i++ } }) } // BenchmarkConcurrentWrites benchmarks concurrent write operations func BenchmarkConcurrentWrites(b *testing.B) { dhm, ctx := setupBenchmarkDB(b) insertBenchmarkData(b, dhm, ctx, 10, 0) b.ResetTimer() b.RunParallel(func(pb *testing.PB) { i := 0 for pb.Next() { houseData := PlayerHouseData{ CharacterID: int32(10000 + i), HouseID: int32((i % 10) + 1), InstanceID: int32(20000 + i), UpkeepDue: time.Now().Add(24 * time.Hour), EscrowCoins: 25000, EscrowStatus: 500, Status: HouseStatusActive, HouseName: fmt.Sprintf("Concurrent House %d", i), VisitPermission: 1, AllowFriends: true, ShowOnDirectory: true, } _, err := dhm.AddPlayerHouse(ctx, houseData) if err != nil { b.Errorf("AddPlayerHouse failed: %v", err) } i++ } }) }