initial commit
This commit is contained in:
464
libraries/FastLED/ci/test_integration/test_xcache.py
Normal file
464
libraries/FastLED/ci/test_integration/test_xcache.py
Normal file
@@ -0,0 +1,464 @@
|
||||
#!/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 <stdio.h>
|
||||
|
||||
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())
|
||||
Reference in New Issue
Block a user