#!/usr/bin/env python3 """ Calejo Control Adapter - Detailed Test Runner This script runs all tests with detailed output and coverage reports. It creates a temporary SQLite database for integration tests. """ import os import sys import tempfile import sqlite3 import subprocess import shutil from pathlib import Path # Colors for output class Colors: RED = '\033[91m' GREEN = '\033[92m' YELLOW = '\033[93m' BLUE = '\033[94m' MAGENTA = '\033[95m' CYAN = '\033[96m' WHITE = '\033[97m' BOLD = '\033[1m' END = '\033[0m' def print_color(color, message): print(f"{color}{message}{Colors.END}") def print_info(message): print_color(Colors.BLUE, f"[INFO] {message}") def print_success(message): print_color(Colors.GREEN, f"[SUCCESS] {message}") def print_warning(message): print_color(Colors.YELLOW, f"[WARNING] {message}") def print_error(message): print_color(Colors.RED, f"[ERROR] {message}") def print_header(message): print_color(Colors.CYAN + Colors.BOLD, f"\n{'='*60}") print_color(Colors.CYAN + Colors.BOLD, f" {message}") print_color(Colors.CYAN + Colors.BOLD, f"{'='*60}\n") def create_sqlite_test_db(): """Create a temporary SQLite database for integration tests.""" temp_db = tempfile.NamedTemporaryFile(suffix='.db', delete=False) temp_db.close() conn = sqlite3.connect(temp_db.name) cursor = conn.cursor() # Create tables cursor.execute(""" CREATE TABLE stations ( station_id TEXT PRIMARY KEY, station_name TEXT, location TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) """) cursor.execute(""" CREATE TABLE pumps ( station_id TEXT, pump_id TEXT, pump_name TEXT, control_type TEXT, min_speed_hz REAL DEFAULT 20.0, max_speed_hz REAL DEFAULT 60.0, default_setpoint_hz REAL DEFAULT 35.0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (station_id, pump_id), FOREIGN KEY (station_id) REFERENCES stations(station_id) ) """) cursor.execute(""" CREATE TABLE pump_plans ( plan_id INTEGER PRIMARY KEY AUTOINCREMENT, station_id TEXT, pump_id TEXT, target_flow_m3h REAL, target_power_kw REAL, target_level_m REAL, suggested_speed_hz REAL, interval_start TIMESTAMP, interval_end TIMESTAMP, plan_version INTEGER, plan_status TEXT DEFAULT 'ACTIVE', plan_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, plan_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, optimization_run_id TEXT, FOREIGN KEY (station_id, pump_id) REFERENCES pumps(station_id, pump_id) ) """) cursor.execute(""" CREATE TABLE pump_feedback ( feedback_id INTEGER PRIMARY KEY AUTOINCREMENT, station_id TEXT, pump_id TEXT, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, actual_speed_hz REAL, actual_power_kw REAL, actual_flow_m3h REAL, wet_well_level_m REAL, pump_running BOOLEAN, alarm_active BOOLEAN, alarm_code TEXT, FOREIGN KEY (station_id, pump_id) REFERENCES pumps(station_id, pump_id) ) """) cursor.execute(""" CREATE TABLE emergency_stop_events ( event_id INTEGER PRIMARY KEY AUTOINCREMENT, triggered_by TEXT, reason TEXT, station_id TEXT, pump_id TEXT, event_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, cleared_by TEXT, cleared_timestamp TIMESTAMP, cleared_notes TEXT, FOREIGN KEY (station_id, pump_id) REFERENCES pumps(station_id, pump_id) ) """) # Insert test data cursor.execute(""" INSERT INTO stations (station_id, station_name, location) VALUES ('STATION_001', 'Main Pump Station', 'Downtown Area'), ('STATION_002', 'Secondary Station', 'Industrial Zone') """) cursor.execute(""" INSERT INTO pumps (station_id, pump_id, pump_name, control_type, min_speed_hz, max_speed_hz, default_setpoint_hz) VALUES ('STATION_001', 'PUMP_001', 'Main Pump 1', 'DIRECT_SPEED', 20.0, 60.0, 35.0), ('STATION_001', 'PUMP_002', 'Main Pump 2', 'LEVEL_CONTROLLED', 20.0, 60.0, 35.0), ('STATION_002', 'PUMP_001', 'Secondary Pump 1', 'POWER_CONTROLLED', 20.0, 60.0, 35.0) """) cursor.execute(""" INSERT INTO pump_plans ( station_id, pump_id, target_flow_m3h, target_power_kw, target_level_m, suggested_speed_hz, interval_start, interval_end, plan_version, optimization_run_id ) VALUES ('STATION_001', 'PUMP_001', 150.0, NULL, NULL, 42.5, datetime('now', '-1 hour'), datetime('now', '+1 hour'), 1, 'OPT_RUN_001'), ('STATION_001', 'PUMP_002', NULL, NULL, 2.5, 38.0, datetime('now', '-1 hour'), datetime('now', '+1 hour'), 1, 'OPT_RUN_001'), ('STATION_002', 'PUMP_001', NULL, 18.5, NULL, 40.0, datetime('now', '-1 hour'), datetime('now', '+1 hour'), 1, 'OPT_RUN_001') """) cursor.execute(""" INSERT INTO pump_feedback ( station_id, pump_id, actual_speed_hz, actual_power_kw, actual_flow_m3h, wet_well_level_m, pump_running, alarm_active ) VALUES ('STATION_001', 'PUMP_001', 42.5, 16.2, 148.5, 1.8, 1, 0), ('STATION_001', 'PUMP_002', 38.0, 14.8, 135.2, 2.3, 1, 0), ('STATION_002', 'PUMP_001', 40.0, 18.3, 142.1, 1.9, 1, 0) """) conn.commit() conn.close() return temp_db.name def run_tests(test_path, coverage_dir, test_type): """Run tests with detailed output and coverage.""" print_header(f"RUNNING {test_type.upper()} TESTS") cmd = [ 'python', '-m', 'pytest', test_path, '-v', '--tb=long', '--cov=src', '--cov-report=term-missing', f'--cov-report=html:{coverage_dir}', '--durations=10', # Show 10 slowest tests '--color=yes' ] result = subprocess.run(cmd, capture_output=True, text=True) # Print output print(result.stdout) if result.stderr: print_color(Colors.YELLOW, f"STDERR:\n{result.stderr}") return result.returncode, result.stdout def main(): """Main test runner function.""" print_header("CALEJO CONTROL ADAPTER - COMPREHENSIVE TEST RUNNER") # Create coverage directories coverage_dirs = { 'unit': 'htmlcov_unit', 'integration': 'htmlcov_integration', 'combined': 'htmlcov_combined' } for dir_name in coverage_dirs.values(): if os.path.exists(dir_name): shutil.rmtree(dir_name) # Create SQLite test database print_info("Creating SQLite test database...") test_db_path = create_sqlite_test_db() # Set environment variables os.environ['TEST_DATABASE_URL'] = f'sqlite:///{test_db_path}' os.environ['TEST_MODE'] = 'true' print_success(f"Test database created: {test_db_path}") # Run unit tests unit_exit_code, unit_output = run_tests( 'tests/unit/', coverage_dirs['unit'], 'UNIT' ) # Run integration tests integration_exit_code, integration_output = run_tests( 'tests/integration/', coverage_dirs['integration'], 'INTEGRATION' ) # Generate combined coverage report print_header("GENERATING COMBINED COVERAGE REPORT") cmd = [ 'python', '-m', 'pytest', '--cov=src', '--cov-report=html:htmlcov_combined', '--cov-report=term-missing', 'tests/' ] subprocess.run(cmd) # Clean up test database os.unlink(test_db_path) print_success("Test database cleaned up") # Generate test summary print_header("TEST RESULTS SUMMARY") # Count tests from output def count_tests(output): passed = output.count('PASSED') failed = output.count('FAILED') errors = output.count('ERROR') skipped = output.count('SKIPPED') return passed, failed, errors, skipped unit_passed, unit_failed, unit_errors, unit_skipped = count_tests(unit_output) integration_passed, integration_failed, integration_errors, integration_skipped = count_tests(integration_output) total_passed = unit_passed + integration_passed total_failed = unit_failed + integration_failed total_errors = unit_errors + integration_errors total_skipped = unit_skipped + integration_skipped total_tests = total_passed + total_failed + total_errors + total_skipped print_info(f"Unit Tests: {unit_passed} passed, {unit_failed} failed, {unit_errors} errors, {unit_skipped} skipped") print_info(f"Integration Tests: {integration_passed} passed, {integration_failed} failed, {integration_errors} errors, {integration_skipped} skipped") print_info(f"Total Tests: {total_passed} passed, {total_failed} failed, {total_errors} errors, {total_skipped} skipped") if unit_exit_code == 0 and integration_exit_code == 0: print_success("🎉 ALL TESTS PASSED! 🎉") print_info(f"Coverage reports generated in: {', '.join(coverage_dirs.values())}") return 0 else: print_error("❌ SOME TESTS FAILED ❌") if unit_exit_code != 0: print_error(f"Unit tests failed with exit code: {unit_exit_code}") if integration_exit_code != 0: print_error(f"Integration tests failed with exit code: {integration_exit_code}") return 1 if __name__ == "__main__": try: exit_code = main() sys.exit(exit_code) except KeyboardInterrupt: print_warning("\nTest run interrupted by user") sys.exit(1) except Exception as e: print_error(f"Unexpected error: {e}") import traceback traceback.print_exc() sys.exit(1)