#!/usr/bin/env python3 """ Integration tests for xcache.py using real clang compiler. This test suite verifies that xcache works correctly with actual compilation scenarios, including response files and direct execution modes. """ import os import shutil import subprocess import sys import tempfile from dataclasses import dataclass from pathlib import Path from typing import Optional @dataclass class TestResults: """Test results container.""" passed: int = 0 failed: int = 0 errors: Optional[list[str]] = None def __post_init__(self) -> None: if self.errors is None: self.errors = [] def find_clang() -> Optional[str]: """Find clang compiler in PATH.""" clang_path = shutil.which("clang") if clang_path: return clang_path # Check common locations common_paths = [ "/usr/bin/clang", "/usr/local/bin/clang", "/opt/local/bin/clang", "C:/Program Files/LLVM/bin/clang.exe", "C:/msys64/mingw64/bin/clang.exe", ] for path in common_paths: if os.path.isfile(path) and os.access(path, os.X_OK): return path return None def find_sccache() -> Optional[str]: """Find sccache in PATH.""" return shutil.which("sccache") def create_test_source_file(temp_dir: Path) -> Path: """Create a simple C source file for testing.""" source_file = temp_dir / "test.c" source_content = """ #include int main() { printf("Hello from xcache test!\\n"); return 0; } """ source_file.write_text(source_content) return source_file def create_response_file(temp_dir: Path, args: list[str]) -> Path: """Create a response file with the given arguments.""" response_file = temp_dir / "compile_args.rsp" # Write arguments to response file (one per line or space-separated) content = " ".join(args) response_file.write_text(content) return response_file def test_xcache_direct_execution(results: TestResults) -> None: """Test xcache with direct execution (no response files).""" print("๐Ÿงช Testing xcache direct execution...") clang_path = find_clang() if not clang_path: results.errors.append( "clang compiler not found - skipping direct execution test" ) return sccache_path = find_sccache() if not sccache_path: results.errors.append("sccache not found - skipping direct execution test") return with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) # Create test source file source_file = create_test_source_file(temp_path) output_file = temp_path / "test_direct.o" # Test xcache with direct arguments (no response files) env = os.environ.copy() env["XCACHE_DEBUG"] = "1" cmd = [ sys.executable, "ci/util/xcache.py", clang_path, "-c", str(source_file), "-o", str(output_file), ] try: result = subprocess.run( cmd, capture_output=True, text=True, env=env, timeout=30 ) if result.returncode == 0 and output_file.exists(): print(" โœ… Direct execution test passed") print(f" Output file created: {output_file.stat().st_size} bytes") results.passed += 1 else: print( f" โŒ Direct execution test failed (return code: {result.returncode})" ) print(f" Stderr: {result.stderr}") results.failed += 1 results.errors.append(f"Direct execution failed: {result.stderr}") except subprocess.TimeoutExpired: print(" โŒ Direct execution test timed out") results.failed += 1 results.errors.append("Direct execution test timed out") except Exception as e: print(f" โŒ Direct execution test error: {e}") results.failed += 1 results.errors.append(f"Direct execution error: {e}") def test_xcache_response_file_execution(results: TestResults) -> None: """Test xcache with response file execution.""" print("\n๐Ÿงช Testing xcache response file execution...") clang_path = find_clang() if not clang_path: results.errors.append("clang compiler not found - skipping response file test") return sccache_path = find_sccache() if not sccache_path: results.errors.append("sccache not found - skipping response file test") return with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) # Create test source file source_file = create_test_source_file(temp_path) output_file = temp_path / "test_response.o" # Create response file with compilation arguments compile_args = ["-c", str(source_file), "-o", str(output_file), "-O2", "-Wall"] response_file = create_response_file(temp_path, compile_args) # Test xcache with response file env = os.environ.copy() env["XCACHE_DEBUG"] = "1" cmd = [sys.executable, "ci/util/xcache.py", clang_path, f"@{response_file}"] try: result = subprocess.run( cmd, capture_output=True, text=True, env=env, timeout=30 ) if result.returncode == 0 and output_file.exists(): print(" โœ… Response file execution test passed") print(f" Output file created: {output_file.stat().st_size} bytes") print(f" Response file handled: @{response_file}") # Verify that response file was passed through (not expanded) if f"@{response_file}" in result.stderr: print(" โœ… Response file passed through correctly") else: print( " โš ๏ธ Response file passthrough not clearly visible in debug output" ) results.passed += 1 else: print( f" โŒ Response file execution test failed (return code: {result.returncode})" ) print(f" Stderr: {result.stderr}") results.failed += 1 results.errors.append( f"Response file execution failed: {result.stderr}" ) except subprocess.TimeoutExpired: print(" โŒ Response file execution test timed out") results.failed += 1 results.errors.append("Response file execution test timed out") except Exception as e: print(f" โŒ Response file execution test error: {e}") results.failed += 1 results.errors.append(f"Response file execution error: {e}") def test_xcache_mixed_arguments(results: TestResults) -> None: """Test xcache with mixed regular arguments and response files.""" print("\n๐Ÿงช Testing xcache mixed arguments...") clang_path = find_clang() if not clang_path: results.errors.append( "clang compiler not found - skipping mixed arguments test" ) return sccache_path = find_sccache() if not sccache_path: results.errors.append("sccache not found - skipping mixed arguments test") return with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) # Create test source file source_file = create_test_source_file(temp_path) output_file = temp_path / "test_mixed.o" # Create response file with some arguments response_args = ["-O2", "-Wall", "-Wextra"] response_file = create_response_file(temp_path, response_args) # Test xcache with mixed arguments env = os.environ.copy() env["XCACHE_DEBUG"] = "1" cmd = [ sys.executable, "ci/util/xcache.py", clang_path, "-c", str(source_file), f"@{response_file}", "-o", str(output_file), ] try: result = subprocess.run( cmd, capture_output=True, text=True, env=env, timeout=30 ) if result.returncode == 0 and output_file.exists(): print(" โœ… Mixed arguments test passed") print(f" Output file created: {output_file.stat().st_size} bytes") print(f" Mixed args: regular + @{response_file}") results.passed += 1 else: print( f" โŒ Mixed arguments test failed (return code: {result.returncode})" ) print(f" Stderr: {result.stderr}") results.failed += 1 results.errors.append(f"Mixed arguments failed: {result.stderr}") except subprocess.TimeoutExpired: print(" โŒ Mixed arguments test timed out") results.failed += 1 results.errors.append("Mixed arguments test timed out") except Exception as e: print(f" โŒ Mixed arguments test error: {e}") results.failed += 1 results.errors.append(f"Mixed arguments error: {e}") def test_xcache_cache_effectiveness(results: TestResults) -> None: """Test that xcache actually caches compilation results.""" print("\n๐Ÿงช Testing xcache cache effectiveness...") clang_path = find_clang() if not clang_path: results.errors.append( "clang compiler not found - skipping cache effectiveness test" ) return sccache_path = find_sccache() if not sccache_path: results.errors.append("sccache not found - skipping cache effectiveness test") return with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) # Create test source file source_file = create_test_source_file(temp_path) # Set up environment for caching env = os.environ.copy() env["XCACHE_DEBUG"] = "1" cache_dir = temp_path / "sccache" cache_dir.mkdir() env["SCCACHE_DIR"] = str(cache_dir) # First compilation (should be cache miss) output_file1 = temp_path / "test_cache1.o" cmd1 = [ sys.executable, "ci/util/xcache.py", clang_path, "-c", str(source_file), "-o", str(output_file1), ] # Second compilation (should be cache hit) output_file2 = temp_path / "test_cache2.o" cmd2 = [ sys.executable, "ci/util/xcache.py", clang_path, "-c", str(source_file), "-o", str(output_file2), ] try: # First compilation result1 = subprocess.run( cmd1, capture_output=True, text=True, env=env, timeout=30 ) # Second compilation result2 = subprocess.run( cmd2, capture_output=True, text=True, env=env, timeout=30 ) if ( result1.returncode == 0 and output_file1.exists() and result2.returncode == 0 and output_file2.exists() ): print(" โœ… Cache effectiveness test passed") print(f" First compilation: {output_file1.stat().st_size} bytes") print(f" Second compilation: {output_file2.stat().st_size} bytes") # Check if sccache cache directory has content cache_files = list(cache_dir.rglob("*")) if cache_files: print(f" Cache directory has {len(cache_files)} entries") else: print(" โš ๏ธ Cache directory appears empty") results.passed += 1 else: print(f" โŒ Cache effectiveness test failed") print( f" First result: {result1.returncode}, {output_file1.exists()}" ) print( f" Second result: {result2.returncode}, {output_file2.exists()}" ) results.failed += 1 results.errors.append("Cache effectiveness test failed") except subprocess.TimeoutExpired: print(" โŒ Cache effectiveness test timed out") results.failed += 1 results.errors.append("Cache effectiveness test timed out") except Exception as e: print(f" โŒ Cache effectiveness test error: {e}") results.failed += 1 results.errors.append(f"Cache effectiveness error: {e}") def run_all_tests() -> TestResults: """Run all xcache tests.""" print("๐Ÿš€ XCACHE INTEGRATION TESTS WITH CLANG") print("=" * 60) results = TestResults() # Check prerequisites clang_path = find_clang() sccache_path = find_sccache() print(f"๐Ÿ“‹ Test Environment:") print(f" Clang: {clang_path or 'NOT FOUND'}") print(f" Sccache: {sccache_path or 'NOT FOUND'}") print(f" xcache: ci/util/xcache.py") print() if not clang_path: print("โŒ Clang compiler not found - install clang to run tests") results.errors.append("Clang compiler not available") return results if not sccache_path: print("โŒ Sccache not found - install sccache to run tests") results.errors.append("Sccache not available") return results # Run tests test_xcache_direct_execution(results) test_xcache_response_file_execution(results) test_xcache_mixed_arguments(results) test_xcache_cache_effectiveness(results) return results def main() -> int: """Main test function.""" results = run_all_tests() print("\n" + "=" * 60) print("๐Ÿ“Š TEST RESULTS SUMMARY") print("=" * 60) total_tests = results.passed + results.failed if total_tests > 0: success_rate = (results.passed / total_tests) * 100 print(f" Tests passed: {results.passed}") print(f" Tests failed: {results.failed}") print(f" Success rate: {success_rate:.1f}%") else: print(" No tests were executed") if results.errors: print(f"\n๐Ÿšจ Errors encountered:") for i, error in enumerate(results.errors, 1): print(f" {i}. {error}") if results.failed == 0 and results.passed > 0: print("\nโœ… All xcache tests passed! Ready for ESP32S3 integration.") return 0 elif results.passed > 0: print( f"\nโš ๏ธ Some tests passed but {results.failed} failed. Check errors above." ) return 1 else: print("\nโŒ No tests passed. Check prerequisites and errors above.") return 1 if __name__ == "__main__": sys.exit(main())