Testing Infrastructure Improvements Guide
August 2, 2025 ยท View on GitHub
Overview
This document describes the major improvements made to the MCP Task Orchestrator testing infrastructure to resolve critical issues with output truncation, resource warnings, and test hanging.
Problems Solved
1. Output Truncation Issues
Problem: LLM systems reading test results before tests finished writing, causing truncated or incomplete output.
Solution: File-based output system with atomic writes and completion markers.
2. Resource Warning Issues
Problem: SQLite connections and SQLAlchemy engines not properly disposed, generating ResourceWarnings.
Solution: Enhanced database persistence with context managers and proper cleanup utilities.
3. Test Hanging Issues
Problem: Tests and MCP operations hanging indefinitely without timeout mechanisms.
Solution: Comprehensive hang detection and prevention system with configurable timeouts.
4. Pytest Limitations
Problem: pytest truncating output and unreliable execution in complex scenarios.
Solution: Alternative test runners that bypass pytest entirely while providing superior output handling.
New Testing Components
File-Based Output System
The core improvement is a robust file-based output system that prevents timing issues:
from mcp_task_orchestrator.testing import TestOutputWriter, TestOutputReader
# Writing test output
writer = TestOutputWriter(output_dir)
with writer.write_test_output("my_test", "text") as session:
session.write_line("Test starting...")
session.write_line("Test completed successfully")
# Reading test output safely
reader = TestOutputReader(output_dir)
output_files = list(output_dir.glob("my_test_*.txt"))
latest_file = max(output_files, key=lambda f: f.stat().st_mtime)
# Wait for completion before reading
if reader.wait_for_completion(latest_file, timeout=30.0):
content = reader.read_completed_output(latest_file)
print("Complete test output:", content)
```text
#
#
# Alternative Test Runners
Multiple specialized test runners are available:
#
#
#
# DirectFunctionRunner
```text
python
from mcp_task_orchestrator.testing import DirectFunctionRunner
runner = DirectFunctionRunner(output_dir=Path("test_outputs"))
result = runner.execute_test(my_test_function, "test_name")
```text
#
#
#
# MigrationTestRunner
```text
python
from mcp_task_orchestrator.testing import MigrationTestRunner
runner = MigrationTestRunner(output_dir=Path("migration_outputs"))
result = runner.run_migration_test()
```text
#
#
#
# ComprehensiveTestRunner
```text
python
from mcp_task_orchestrator.testing import ComprehensiveTestRunner, TestRunnerConfig
config = TestRunnerConfig(
output_dir=Path("outputs"),
runner_types=['direct', 'migration', 'integration'],
verbose=True
)
runner = ComprehensiveTestRunner(config)
results = runner.run_all_tests([test_directory])
```text
#
#
# Hang Detection System
Automatic hang detection and prevention:
```text
python
from mcp_task_orchestrator.monitoring.hang_detection import with_hang_detection
@with_hang_detection("my_operation", timeout=30.0)
async def my_async_operation():
# Your operation here
await some_database_work()
return "completed"
# Context manager for hang protection
async with hang_protected_operation("context_op", timeout=60.0):
# Protected operation
await long_running_task()
```text
#
#
# Resource Management
Enhanced database persistence with proper cleanup:
```text
python
from mcp_task_orchestrator.db.persistence import DatabasePersistenceManager
from tests.utils.db_test_utils import managed_sqlite_connection
# Context manager approach (recommended)
with DatabasePersistenceManager(db_url="sqlite:///test.db") as persistence:
tasks = persistence.get_all_active_tasks()
# For SQLite connections
with managed_sqlite_connection("test.db") as conn:
cursor = conn.cursor()
cursor.execute("SELECT * FROM subtasks")
results = cursor.fetchall()
```text
#
# Migration Guide
#
#
# From Old Testing Patterns
**Old Pattern** (prone to truncation):
```text
python
import subprocess
result = subprocess.run(["python", "-m", "pytest", "test_file.py"], capture_output=True)
print(result.stdout)
# May be truncated
```text
text
**New Pattern** (reliable):
```text
python
from mcp_task_orchestrator.testing import DirectFunctionRunner
runner = DirectFunctionRunner(output_dir=Path("outputs"))
result = runner.execute_test(test_function, "test_name")
# Complete output available in result.output_file
```text
text
#
#
# From Direct Database Connections
**Old Pattern** (resource warnings):
```python
import sqlite3
conn = sqlite3.connect("test.db")
# ... work ...
conn.close()
# May not be called on exceptions
```text
**New Pattern** (safe):
```text
python
from tests.utils.db_test_utils import managed_sqlite_connection
with managed_sqlite_connection("test.db") as conn:
# ... work ...
# Automatic cleanup guaranteed
```text
text
#
# Best Practices
#
#
# 1. Always Use File-Based Output
For any test that generates substantial output:
```text
python
def my_test_function():
writer = TestOutputWriter(output_dir)
with writer.write_test_output("my_test", "text") as session:
session.write_line("Starting test...")
# ... test logic ...
session.write_line("Test completed")
```text
#
#
# 2. Use Alternative Runners for Complex Tests
Instead of pytest for complex scenarios:
```text
python
# Direct execution with full output capture
runner = DirectFunctionRunner(output_dir=output_dir)
result = runner.execute_test(test_function, test_name)
assert result.status == "passed"
```text
#
#
# 3. Implement Hang Protection
For any potentially long-running operations:
```text
python
@with_hang_detection("database_migration", timeout=300.0)
async def run_migration():
# Migration logic with automatic timeout
pass
```text
#
#
# 4. Use Context Managers for Resources
Always use context managers for database connections:
```text
python
# DatabasePersistenceManager
with DatabasePersistenceManager(db_url=db_url) as persistence:
# Operations guaranteed to clean up
# Direct SQLite connections
with managed_sqlite_connection(db_path) as conn:
# Connection guaranteed to close
```text
#
# Troubleshooting
#
#
# Output Files Not Created
**Symptom**: No output files appear in expected directory.
**Solutions**:
1. Check directory permissions
2. Verify `TestOutputWriter` initialization
3. Ensure `write_test_output` context manager is used properly
#
#
# Tests Still Hanging
**Symptom**: Operations hang despite hang detection.
**Solutions**:
1. Verify `@with_hang_detection` decorator is applied
2. Check timeout values are appropriate
3. Use `hang_protected_operation` context manager
4. Check hang detection statistics: `get_hang_detection_statistics()`
#
#
# Resource Warnings Still Appearing
**Symptom**: ResourceWarnings still generated during tests.
**Solutions**:
1. Use `managed_sqlite_connection` instead of direct `sqlite3.connect`
2. Use `DatabasePersistenceManager` context manager
3. Call `cleanup_db_resources()` in test teardown
4. Check for missing `dispose()` calls
#
#
# Truncated Output
**Symptom**: Test output appears incomplete.
**Solutions**:
1. Use file-based output system instead of direct stdout capture
2. Wait for completion using `reader.wait_for_completion()`
3. Use alternative test runners instead of pytest
4. Check for premature file reading
#
# Testing Your Changes
To validate that improvements are working:
1. **Run Resource Cleanup Tests**:
```text
bash
python tests/test_resource_cleanup.py
```text
text
2. **Run Hang Detection Tests**:
```text
bash
python tests/test_hang_detection.py
```text
text
3. **Test File Output System**:
```text
bash
python tests/demo_file_output_system.py
```text
text
4. **Test Alternative Runners**:
```text
bash
python tests/demo_alternative_runners.py
```text
text
5. **Run Enhanced Migration Test**:
```text
bash
python tests/enhanced_migration_test.py
```text
text
#
# Configuration
#
#
# Timeout Settings
Configure hang detection timeouts:
```text
python
from mcp_task_orchestrator.monitoring.hang_detection import configure_hang_detection
configure_hang_detection(
default_timeout=60.0,
warning_timeout=30.0,
check_interval=5.0
)
```text
#
#
# Output Directory Settings
Configure test output locations:
```text
python
from mcp_task_orchestrator.testing import TestRunnerConfig
config = TestRunnerConfig(
output_dir=Path("custom_test_outputs"),
runner_types=['direct', 'migration'],
verbose=True,
timeout_per_test=120.0
)
```text
#
# Performance Considerations
#
#
# File System Performance
- Use SSD storage for output directories when possible
- Clean up old test output files periodically
- Consider output directory rotation for long-running systems
#
#
# Memory Usage
- File-based output reduces memory pressure compared to in-memory buffering
- Database connections are properly pooled and reused
- Hang detection uses minimal overhead
#
#
# Scaling
- Alternative test runners can execute tests in parallel
- File-based output supports concurrent test execution
- Database connection management scales with connection pooling
#
# Related Documentation
- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - General troubleshooting guide
- [MIGRATION_GUIDE.md](MIGRATION_GUIDE.md) - Database migration documentation
- [tests/README.md](tests/README.md) - Test-specific documentation
- [pytest_investigation_instructions.md](pytest_investigation_instructions.md) - Background on pytest issues