McRogueFace/tests/unit/run_issue_tests.py

174 lines
5.6 KiB
Python
Raw Normal View History

Squashed commit of the following: [alpha_streamline_1] the low-hanging fruit of pre-existing issues and standardizing the Python interfaces Special thanks to Claude Code, ~100k output tokens for this merge 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> commit 99f301e3a0e9e81ad28c9e1d410390c32dfd933c Author: John McCardle <mccardle.john@gmail.com> Date: Sat Jul 5 16:25:32 2025 -0400 Add position tuple support and pos property to UI elements closes #83, closes #84 - Issue #83: Add position tuple support to constructors - Frame and Sprite now accept both (x, y) and ((x, y)) forms - Also accept Vector objects as position arguments - Caption and Entity already supported tuple/Vector forms - Uses PyVector::from_arg for flexible position parsing - Issue #84: Add pos property to Frame and Sprite - Added pos getter that returns a Vector - Added pos setter that accepts Vector or tuple - Provides consistency with Caption and Entity which already had pos properties - All UI elements now have a uniform way to get/set positions as Vectors Both features improve API consistency and make it easier to work with positions. commit 2f2b488fb54da12c39c0010dbd83cb9f6c429b01 Author: John McCardle <mccardle.john@gmail.com> Date: Sat Jul 5 16:18:10 2025 -0400 Standardize sprite_index property and add scale_x/scale_y to UISprite closes #81, closes #82 - Issue #81: Standardized property name to sprite_index across UISprite and UIEntity - Added sprite_index as the primary property name - Kept sprite_number as a deprecated alias for backward compatibility - Updated repr() methods to use sprite_index - Updated animation system to recognize both names - Issue #82: Added scale_x and scale_y properties to UISprite - Enables non-uniform scaling of sprites - scale property still works for uniform scaling - Both properties work with the animation system All existing code using sprite_number continues to work due to backward compatibility. commit 5a003a9aa587eb8ee4b79ac67ca8f342ab62e2d2 Author: John McCardle <mccardle.john@gmail.com> Date: Sat Jul 5 16:09:52 2025 -0400 Fix multiple low priority issues closes #12, closes #80, closes #95, closes #96, closes #99 - Issue #12: Set tp_new to NULL for GridPoint and GridPointState to prevent instantiation from Python - Issue #80: Renamed Caption.size to Caption.font_size for semantic clarity - Issue #95: Fixed UICollection repr to show actual derived types instead of generic UIDrawable - Issue #96: Added extend() method to UICollection for API consistency with UIEntityCollection - Issue #99: Exposed read-only properties for Texture (sprite_width, sprite_height, sheet_width, sheet_height, sprite_count, source) and Font (family, source) All issues have corresponding tests that verify the fixes work correctly. commit e5affaf317665395135c936bc4a6b840ae321765 Author: John McCardle <mccardle.john@gmail.com> Date: Sat Jul 5 15:50:09 2025 -0400 Fix critical issues: script loading, entity types, and color properties - Issue #37: Fix Windows scripts subdirectory not checked - Updated executeScript() to use executable_path() from platform.h - Scripts now load correctly when working directory differs from executable - Issue #76: Fix UIEntityCollection returns wrong type - Updated UIEntityCollectionIter::next() to check for stored Python object - Derived Entity classes now preserve their type when retrieved from collections - Issue #9: Recreate RenderTexture when resized (already fixed) - Confirmed RenderTexture recreation already implemented in set_size() and set_float_member() - Uses 1.5x padding and 4096 max size limit - Issue #79: Fix Color r, g, b, a properties return None - Implemented get_member() and set_member() in PyColor.cpp - Color component properties now work correctly with proper validation - Additional fix: Grid.at() method signature - Changed from METH_O to METH_VARARGS to accept two arguments All fixes include comprehensive tests to verify functionality. closes #37, closes #76, closes #9, closes #79
2025-07-05 17:30:49 -04:00
#!/usr/bin/env python3
"""
Test runner for high-priority McRogueFace issues
This script runs comprehensive tests for the highest priority bugs that can be fixed rapidly.
Each test is designed to fail initially (demonstrating the bug) and pass after the fix.
"""
import os
import sys
import subprocess
import time
# Test configurations
TESTS = [
{
"issue": "37",
"name": "Windows scripts subdirectory bug",
"script": "issue_37_windows_scripts_comprehensive_test.py",
"needs_game_loop": False,
"description": "Tests script loading from different working directories"
},
{
"issue": "76",
"name": "UIEntityCollection returns wrong type",
"script": "issue_76_uientitycollection_type_test.py",
"needs_game_loop": True,
"description": "Tests type preservation for derived Entity classes in collections"
},
{
"issue": "9",
"name": "RenderTexture resize bug",
"script": "issue_9_rendertexture_resize_test.py",
"needs_game_loop": True,
"description": "Tests UIGrid rendering with sizes beyond 1920x1080"
},
{
"issue": "26/28",
"name": "Iterator implementation for collections",
"script": "issue_26_28_iterator_comprehensive_test.py",
"needs_game_loop": True,
"description": "Tests Python sequence protocol for UI collections"
}
]
def run_test(test_config, mcrogueface_path):
"""Run a single test and return the result"""
script_path = os.path.join(os.path.dirname(__file__), test_config["script"])
if not os.path.exists(script_path):
return f"SKIP - Test script not found: {script_path}"
print(f"\n{'='*60}")
print(f"Running test for Issue #{test_config['issue']}: {test_config['name']}")
print(f"Description: {test_config['description']}")
print(f"Script: {test_config['script']}")
print(f"{'='*60}\n")
if test_config["needs_game_loop"]:
# Run with game loop using --exec
cmd = [mcrogueface_path, "--headless", "--exec", script_path]
else:
# Run directly as Python script
cmd = [sys.executable, script_path]
try:
start_time = time.time()
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30 # 30 second timeout
)
elapsed = time.time() - start_time
# Check for pass/fail in output
output = result.stdout + result.stderr
if "PASS" in output and "FAIL" not in output:
status = "PASS"
elif "FAIL" in output:
status = "FAIL"
else:
status = "UNKNOWN"
# Look for specific bug indicators
bug_found = False
if test_config["issue"] == "37" and "Script not loaded from different directory" in output:
bug_found = True
elif test_config["issue"] == "76" and "type lost!" in output:
bug_found = True
elif test_config["issue"] == "9" and "clipped at 1920x1080" in output:
bug_found = True
elif test_config["issue"] == "26/28" and "not implemented" in output:
bug_found = True
return {
"status": status,
"bug_found": bug_found,
"elapsed": elapsed,
"output": output if len(output) < 1000 else output[:1000] + "\n... (truncated)"
}
except subprocess.TimeoutExpired:
return {
"status": "TIMEOUT",
"bug_found": False,
"elapsed": 30,
"output": "Test timed out after 30 seconds"
}
except Exception as e:
return {
"status": "ERROR",
"bug_found": False,
"elapsed": 0,
"output": str(e)
}
def main():
"""Run all tests and provide summary"""
# Find mcrogueface executable
build_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "build")
mcrogueface_path = os.path.join(build_dir, "mcrogueface")
if not os.path.exists(mcrogueface_path):
print(f"ERROR: mcrogueface executable not found at {mcrogueface_path}")
print("Please build the project first with 'make'")
return 1
print("McRogueFace Issue Test Suite")
print(f"Executable: {mcrogueface_path}")
print(f"Running {len(TESTS)} tests...\n")
results = []
for test in TESTS:
result = run_test(test, mcrogueface_path)
results.append((test, result))
# Summary
print(f"\n{'='*60}")
print("TEST SUMMARY")
print(f"{'='*60}\n")
bugs_found = 0
tests_passed = 0
for test, result in results:
if isinstance(result, str):
print(f"Issue #{test['issue']}: {result}")
else:
status_str = result['status']
if result['bug_found']:
status_str += " (BUG CONFIRMED)"
bugs_found += 1
elif result['status'] == 'PASS':
tests_passed += 1
print(f"Issue #{test['issue']}: {status_str} ({result['elapsed']:.2f}s)")
if result['status'] not in ['PASS', 'UNKNOWN']:
print(f" Details: {result['output'].splitlines()[0] if result['output'] else 'No output'}")
print(f"\nBugs confirmed: {bugs_found}/{len(TESTS)}")
print(f"Tests passed: {tests_passed}/{len(TESTS)}")
if bugs_found > 0:
print("\nThese tests demonstrate bugs that need fixing.")
print("After fixing, the tests should pass instead of confirming bugs.")
return 0
if __name__ == "__main__":
sys.exit(main())