-
Notifications
You must be signed in to change notification settings - Fork 48
Open
Description
Summary
PDD crashes with IndexError: list index out of range when pytest fails to collect or execute tests, returning an empty test_results list. This affects the core fix loop functionality and occurs in 16+ different real-world scenarios.
Location
File: pdd/fix_error_loop.py
Line: 213
Function: run_pytest_on_file()
# Buggy code:
results = output_data.get("test_results", [{}])[0]
## Root Cause
The code assumes `test_results` will always have at least one element. The default value `[{}]` only applies when the key is **missing**, not when it exists with an **empty list**.
```python
# When key is missing:
{}.get("test_results", [{}]) # → [{}]
# When key exists but is empty (the bug):
{"test_results": []}.get("test_results", [{}]) # → []
# Then [0] → IndexError!When This Happens (16+ Scenarios)
Category 1: Collection Errors
- Import errors -
ImportError: No module named 'flask' - Syntax errors -
SyntaxError: invalid syntax - Circular imports -
cannot import name 'X' from partially initialized module - Missing fixtures -
fixture 'db' not found - Encoding errors -
UnicodeDecodeError: 'utf-8' codec can't decode
Category 2: File/Path Issues
- Permission denied -
PermissionError: [Errno 13] Permission denied - File not found -
file or directory not found: test_missing.py - Wrong directory -
ModuleNotFoundError: No module named 'src'
Category 3: Configuration Issues
- Config errors -
usage: unrecognized arguments: --invalid - Plugin failures -
plugin 'pytest_django' failed to load - Venv not activated -
ModuleNotFoundError: No module named 'pytest'
Category 4: Test Collection Results
- No tests found -
collected 0 items - Empty test file - File has no test functions
- All tests skipped -
collected 5 items / 5 skipped
Category 5: System Issues
- Pytest crash -
Segmentation fault (core dumped) - Collection timeout -
Timeout during collection
Example User Scenario
run pdd fix on a test with a missing import:
# tests/test_api.py
def test_login():
response = client.post('/login') # NameError - client not imported
assert response.status_code == 200Expected:
Pytest collection failed: NameError: name 'client' is not defined
Check for missing imports in your test file
Actual:
Traceback (most recent call last):
File "pdd/fix_error_loop.py", line 213, in run_pytest_on_file
results = output_data.get("test_results", [{}])[0]
IndexError: list index out of range
Users blame PDD instead of recognizing their test error.
Reproduction
Test Case
# Simulates pytest collection failure
output_data = {
"test_results": [], # Empty list
"stdout": "ERROR: ImportError: No module named 'flask'",
"exit_code": 1
}
# This crashes:
results = output_data.get("test_results", [{}])[0]
# IndexError: list index out of rangeVerified: Bug confirmed and reproducible
Additional Edge Cases Discovered
Beyond empty lists, we also need to handle:
test_results: None→ TypeErrortest_results: "error"→ Wrong behaviortest_results: {}→ KeyErrortest_results: [None]→ Invalid data
Proposed Fix
Robust Solution (with type validation)
def run_pytest_on_file(test_file: str, extra_files: list[str] | None = None) -> tuple[int, int, int, str]:
"""
Run pytest on the specified test file using the subprocess-based runner.
Returns a tuple: (failures, errors, warnings, logs)
"""
output_data = run_pytest_and_capture_output(test_file, extra_files=extra_files)
# Get test results with validation
test_results_list = output_data.get("test_results", [])
# Type check - handle malformed data
if not isinstance(test_results_list, list):
error_msg = output_data.get("stdout", "") or output_data.get("stderr", "")
logger.error(f"Invalid test results format: {type(test_results_list).__name__}")
return 0, 1, 0, f"Pytest returned invalid data: {error_msg[:200]}"
# Empty check - handle collection/execution failures
if not test_results_list:
error_msg = output_data.get("stdout", "") or output_data.get("stderr", "")
# Provide helpful error messages based on common patterns
if "ImportError" in error_msg or "ModuleNotFoundError" in error_msg:
helpful_msg = "Pytest collection failed: Missing import or dependency"
elif "SyntaxError" in error_msg:
helpful_msg = "Pytest collection failed: Syntax error in test file"
elif "no tests ran" in error_msg or "collected 0 items" in error_msg:
helpful_msg = "Pytest collection failed: No tests found"
elif "PermissionError" in error_msg:
helpful_msg = "Pytest collection failed: Permission denied"
else:
helpful_msg = "Pytest collection or execution failed"
logger.warning(f"{helpful_msg}: {error_msg[:200]}")
return 0, 1, 0, f"{helpful_msg}\n\n{error_msg}"
# Safe access
results = test_results_list[0]
# Validate result structure
if not isinstance(results, dict):
logger.error(f"Invalid result structure: {type(results).__name__}")
return 0, 1, 0, f"Pytest returned invalid result format"
# Extract metrics
failures = results.get("failures", 0)
errors = results.get("errors", 0)
warnings = results.get("warnings", 0)
# Combine stdout/stderr for the log
logs = (results.get("standard_output", "") or "") + "\n" + (results.get("standard_error", "") or "")
return failures, errors, warnings, logsBenefits
- Handles all 16+ failure scenarios gracefully
- Provides helpful error messages to users
- Includes type validation for robustness
- Logs issues for debugging
- Returns error counts (0, 1, 0) to signal collection failure
Testing
Comprehensive testing confirmed:
- All 16 scenarios cause IndexError with current code
- Proposed fix handles all scenarios gracefully
- Edge cases (None, string, dict) also covered
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels