mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-09-22 04:50:46 +02:00
144 lines
5.1 KiB
Python
144 lines
5.1 KiB
Python
"""
|
|
Exclude Cards Performance Tests
|
|
|
|
Ensures that exclude filtering doesn't create significant performance
|
|
regressions and meets the specified benchmarks for parsing, filtering,
|
|
and validation operations.
|
|
"""
|
|
import time
|
|
import pytest
|
|
from deck_builder.include_exclude_utils import parse_card_list_input
|
|
|
|
|
|
def test_card_parsing_speed():
|
|
"""Test that exclude card parsing is fast."""
|
|
# Create a list of 15 cards (max excludes)
|
|
exclude_cards_text = "\n".join([
|
|
"Sol Ring", "Rhystic Study", "Smothering Tithe", "Lightning Bolt",
|
|
"Counterspell", "Swords to Plowshares", "Path to Exile",
|
|
"Mystical Tutor", "Demonic Tutor", "Vampiric Tutor",
|
|
"Mana Crypt", "Chrome Mox", "Mox Diamond", "Mox Opal", "Lotus Petal"
|
|
])
|
|
|
|
# Time the parsing operation
|
|
start_time = time.time()
|
|
for _ in range(100): # Run 100 times to get a meaningful measurement
|
|
result = parse_card_list_input(exclude_cards_text)
|
|
end_time = time.time()
|
|
|
|
# Should complete 100 parses in well under 1 second
|
|
total_time = end_time - start_time
|
|
avg_time_per_parse = total_time / 100
|
|
|
|
assert len(result) == 15
|
|
assert avg_time_per_parse < 0.01 # Less than 10ms per parse (very generous)
|
|
print(f"Average parse time: {avg_time_per_parse*1000:.2f}ms")
|
|
|
|
|
|
def test_large_cardpool_filtering_speed():
|
|
"""Simulate exclude filtering performance on a large card pool."""
|
|
# Create a mock dataframe-like structure to simulate filtering
|
|
mock_card_pool_size = 20000 # Typical large card pool
|
|
exclude_list = [
|
|
"Sol Ring", "Rhystic Study", "Smothering Tithe", "Lightning Bolt",
|
|
"Counterspell", "Swords to Plowshares", "Path to Exile",
|
|
"Mystical Tutor", "Demonic Tutor", "Vampiric Tutor",
|
|
"Mana Crypt", "Chrome Mox", "Mox Diamond", "Mox Opal", "Lotus Petal"
|
|
]
|
|
|
|
# Simulate the filtering operation (set-based lookup)
|
|
exclude_set = set(exclude_list)
|
|
|
|
# Create mock card names
|
|
mock_cards = [f"Card {i}" for i in range(mock_card_pool_size)]
|
|
# Add a few cards that will be excluded
|
|
mock_cards.extend(exclude_list)
|
|
|
|
# Time the filtering operation
|
|
start_time = time.time()
|
|
filtered_cards = [card for card in mock_cards if card not in exclude_set]
|
|
end_time = time.time()
|
|
|
|
filter_time = end_time - start_time
|
|
|
|
# Should complete filtering in well under 50ms (our target)
|
|
assert filter_time < 0.050 # 50ms
|
|
print(f"Filtering {len(mock_cards)} cards took {filter_time*1000:.2f}ms")
|
|
|
|
# Verify filtering worked
|
|
for excluded_card in exclude_list:
|
|
assert excluded_card not in filtered_cards
|
|
|
|
|
|
def test_validation_api_response_time():
|
|
"""Test validation endpoint response time."""
|
|
import importlib
|
|
import os
|
|
import sys
|
|
from starlette.testclient import TestClient
|
|
|
|
# Ensure project root is in sys.path for reliable imports
|
|
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
|
if project_root not in sys.path:
|
|
sys.path.insert(0, project_root)
|
|
|
|
# Enable feature flag
|
|
original_value = os.environ.get('ALLOW_MUST_HAVES')
|
|
os.environ['ALLOW_MUST_HAVES'] = '1'
|
|
|
|
try:
|
|
# Fresh import
|
|
try:
|
|
del importlib.sys.modules['code.web.app']
|
|
except KeyError:
|
|
pass
|
|
|
|
app_module = importlib.import_module('code.web.app')
|
|
client = TestClient(app_module.app)
|
|
|
|
# Test data
|
|
exclude_text = "\n".join([
|
|
"Sol Ring", "Rhystic Study", "Smothering Tithe", "Lightning Bolt",
|
|
"Counterspell", "Swords to Plowshares", "Path to Exile",
|
|
"Mystical Tutor", "Demonic Tutor", "Vampiric Tutor"
|
|
])
|
|
|
|
# Time the validation request
|
|
start_time = time.time()
|
|
response = client.post('/build/validate/exclude_cards',
|
|
data={'exclude_cards': exclude_text})
|
|
end_time = time.time()
|
|
|
|
response_time = end_time - start_time
|
|
|
|
# Should respond in under 100ms (our target)
|
|
assert response_time < 0.100 # 100ms
|
|
assert response.status_code == 200
|
|
|
|
print(f"Validation endpoint response time: {response_time*1000:.2f}ms")
|
|
|
|
finally:
|
|
# Restore environment
|
|
if original_value is not None:
|
|
os.environ['ALLOW_MUST_HAVES'] = original_value
|
|
else:
|
|
os.environ.pop('ALLOW_MUST_HAVES', None)
|
|
|
|
|
|
@pytest.mark.parametrize("exclude_count", [0, 5, 10, 15])
|
|
def test_parsing_scales_with_list_size(exclude_count):
|
|
"""Test that performance scales reasonably with number of excludes."""
|
|
exclude_cards = [f"Exclude Card {i}" for i in range(exclude_count)]
|
|
exclude_text = "\n".join(exclude_cards)
|
|
|
|
start_time = time.time()
|
|
result = parse_card_list_input(exclude_text)
|
|
end_time = time.time()
|
|
|
|
parse_time = end_time - start_time
|
|
|
|
# Even with maximum excludes, should be very fast
|
|
assert parse_time < 0.005 # 5ms
|
|
assert len(result) == exclude_count
|
|
|
|
print(f"Parse time for {exclude_count} excludes: {parse_time*1000:.2f}ms")
|