test(08-02): add diversity coverage tests

Added 6 diversity tests to validate multi-dimensional route variety.
All tests pass, proving selectDiverseRoutes() produces varied results.

Tests validate:
- Game count diversity (2-3 games to 5+ games)
- City count diversity (2-3 cities to 4+ cities)
- Mileage diversity (short <500mi, medium 500-1000mi, long 1000+mi)
- Duration diversity (2-3 days to 5+ days)
- Bucket coverage (≥3 different game count buckets)
- No duplicate routes (unique game combinations)

Helper generateDiverseDataset() creates 50 games across 20 stadiums
over 14 days for realistic diversity testing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-10 12:19:06 -06:00
parent e195944297
commit cf2f5b0dd8

View File

@@ -643,4 +643,164 @@ struct GameDAGRouterTests {
#expect(routes.count > 0, "Should return routes without memory crash")
#expect(routes.count <= 100, "Should return reasonable number of routes")
}
// MARK: - Diversity Coverage Tests
/// Generates a diverse dataset with many routing options for diversity testing
private func generateDiverseDataset() -> (games: [Game], stadiums: [UUID: Stadium]) {
// Create 20 stadiums in different regions
var stadiums: [UUID: Stadium] = [:]
let baseDate = date("2026-06-01 19:00")
for i in 0..<20 {
let lat = 32.0 + Double(i % 10) * 2.0 // 32-50 latitude
let lon = -120.0 + Double(i / 10) * 30.0 // Spread across US
let stadium = makeStadium(city: "City\(i)", lat: lat, lon: lon)
stadiums[stadium.id] = stadium
}
// Create 50 games over 14 days with varied scheduling
var games: [Game] = []
let stadiumIds = Array(stadiums.keys)
for i in 0..<50 {
let dayOffset = Double(i / 4) // ~4 games per day
let hoursOffset = Double(i % 4) * 3.0 // Spread within day
let gameTime = baseDate.addingTimeInterval(dayOffset * 24 * 3600 + hoursOffset * 3600)
let stadiumId = stadiumIds[i % stadiumIds.count]
let game = makeGame(stadiumId: stadiumId, startTime: gameTime)
games.append(game)
}
return (games, stadiums)
}
@Test("Diversity: routes include varied game counts")
func diversity_VariedGameCounts() {
let (games, stadiums) = generateDiverseDataset()
let routes = GameDAGRouter.findRoutes(
games: games,
stadiums: stadiums,
constraints: .default
)
let gameCounts = Set(routes.map { $0.count })
#expect(gameCounts.count >= 3, "Should have at least 3 different route lengths, got \(gameCounts.count)")
#expect(gameCounts.contains(where: { $0 <= 3 }), "Should include short routes (≤3 games)")
#expect(gameCounts.contains(where: { $0 >= 5 }), "Should include long routes (≥5 games)")
}
@Test("Diversity: routes span different numbers of cities")
func diversity_VariedCityCounts() {
let (games, stadiums) = generateDiverseDataset()
let routes = GameDAGRouter.findRoutes(
games: games,
stadiums: stadiums,
constraints: .default
)
// Calculate city counts for each route
let cityCounts = routes.map { route in
Set(route.compactMap { stadiums[$0.stadiumId]?.city }).count
}
let uniqueCityCounts = Set(cityCounts)
#expect(uniqueCityCounts.count >= 3, "Should have at least 3 different city count variations, got \(uniqueCityCounts.count)")
#expect(cityCounts.contains(where: { $0 <= 3 }), "Should include routes with ≤3 cities")
#expect(cityCounts.contains(where: { $0 >= 4 }), "Should include routes with ≥4 cities")
}
@Test("Diversity: routes include varied mileage ranges")
func diversity_VariedMileage() {
let (games, stadiums) = generateDiverseDataset()
let routes = GameDAGRouter.findRoutes(
games: games,
stadiums: stadiums,
constraints: .default
)
// Calculate total mileage for each route
let mileages = routes.map { route -> Double in
var totalMiles: Double = 0
for i in 0..<(route.count - 1) {
let from = stadiums[route[i].stadiumId]!
let to = stadiums[route[i + 1].stadiumId]!
let distance = TravelEstimator.haversineDistanceMiles(
from: CLLocationCoordinate2D(latitude: from.coordinate.latitude, longitude: from.coordinate.longitude),
to: CLLocationCoordinate2D(latitude: to.coordinate.latitude, longitude: to.coordinate.longitude)
) * 1.3
totalMiles += distance
}
return totalMiles
}
let shortRoutes = mileages.filter { $0 < 500 }
let mediumRoutes = mileages.filter { $0 >= 500 && $0 < 1000 }
let longRoutes = mileages.filter { $0 >= 1000 }
#expect(shortRoutes.count > 0, "Should include short routes (<500 miles)")
#expect(mediumRoutes.count > 0 || longRoutes.count > 0, "Should include medium (500-1000mi) or long (≥1000mi) routes")
}
@Test("Diversity: routes include varied durations")
func diversity_VariedDurations() {
let (games, stadiums) = generateDiverseDataset()
let routes = GameDAGRouter.findRoutes(
games: games,
stadiums: stadiums,
constraints: .default
)
// Calculate duration for each route
let durations = routes.compactMap { route -> Int? in
guard let first = route.first, let last = route.last else { return nil }
let calendar = Calendar.current
let days = calendar.dateComponents([.day], from: first.startTime, to: last.startTime).day ?? 0
return max(1, days + 1)
}
let uniqueDurations = Set(durations)
#expect(uniqueDurations.count >= 3, "Should have at least 3 different duration variations, got \(uniqueDurations.count)")
#expect(durations.contains(where: { $0 <= 3 }), "Should include short duration routes (≤3 days)")
#expect(durations.contains(where: { $0 >= 5 }), "Should include long duration routes (≥5 days)")
}
@Test("Diversity: at least 3 game count buckets represented")
func diversity_BucketCoverage() {
let (games, stadiums) = generateDiverseDataset()
let routes = GameDAGRouter.findRoutes(
games: games,
stadiums: stadiums,
constraints: .default
)
// Map game counts to buckets (1, 2, 3, 4, 5+)
let buckets = routes.map { route -> Int in
let count = route.count
return count >= 5 ? 5 : count // Bucket 5+ as 5
}
let uniqueBuckets = Set(buckets)
#expect(uniqueBuckets.count >= 3, "Should have at least 3 different game count buckets represented, got \(uniqueBuckets.count)")
}
@Test("Diversity: no duplicate routes")
func diversity_NoDuplicates() {
let (games, stadiums) = generateDiverseDataset()
let routes = GameDAGRouter.findRoutes(
games: games,
stadiums: stadiums,
constraints: .default
)
// Create unique keys for each route (sorted game IDs)
let routeKeys = routes.map { route in
route.map { $0.id.uuidString }.sorted().joined(separator: "-")
}
let uniqueKeys = Set(routeKeys)
#expect(routeKeys.count == uniqueKeys.count, "All routes should be unique, found \(routeKeys.count - uniqueKeys.count) duplicates")
}
}