initial commit
This commit is contained in:
437
libraries/FastLED/ci/test_integration/test_xcache_standalone.py
Normal file
437
libraries/FastLED/ci/test_integration/test_xcache_standalone.py
Normal file
@@ -0,0 +1,437 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Standalone tests for xcache.py using clang compiler without requiring sccache server.
|
||||
|
||||
This test suite verifies that xcache works correctly by testing the wrapper
|
||||
functionality and ensuring proper command construction and execution.
|
||||
"""
|
||||
|
||||
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 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 (space-separated)
|
||||
# Properly quote arguments that contain spaces (Windows paths)
|
||||
# Use forward slashes for Windows paths to avoid backslash escape issues
|
||||
quoted_args: list[str] = []
|
||||
for arg in args:
|
||||
# Convert Windows backslashes to forward slashes for response files
|
||||
normalized_arg = arg.replace("\\", "/")
|
||||
if " " in normalized_arg and not (
|
||||
normalized_arg.startswith('"') and normalized_arg.endswith('"')
|
||||
):
|
||||
quoted_args.append(f'"{normalized_arg}"')
|
||||
else:
|
||||
quoted_args.append(normalized_arg)
|
||||
|
||||
content = " ".join(quoted_args)
|
||||
response_file.write_text(content)
|
||||
|
||||
return response_file
|
||||
|
||||
|
||||
def test_clang_direct_compilation(results: TestResults) -> None:
|
||||
"""Test that clang compilation works directly (baseline test)."""
|
||||
print("🧪 Testing direct clang compilation (baseline)...")
|
||||
|
||||
clang_path = find_clang()
|
||||
if not clang_path:
|
||||
results.errors.append("clang compiler not found - skipping baseline 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_baseline.o"
|
||||
|
||||
# Test direct clang compilation
|
||||
cmd = [clang_path, "-c", str(source_file), "-o", str(output_file)]
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
|
||||
if result.returncode == 0 and output_file.exists():
|
||||
print(" ✅ Direct clang compilation works")
|
||||
print(f" Output file created: {output_file.stat().st_size} bytes")
|
||||
results.passed += 1
|
||||
else:
|
||||
print(
|
||||
f" ❌ Direct clang compilation failed (return code: {result.returncode})"
|
||||
)
|
||||
print(f" Stderr: {result.stderr}")
|
||||
results.failed += 1
|
||||
results.errors.append(
|
||||
f"Direct clang compilation failed: {result.stderr}"
|
||||
)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print(" ❌ Direct clang compilation timed out")
|
||||
results.failed += 1
|
||||
results.errors.append("Direct clang compilation timed out")
|
||||
except Exception as e:
|
||||
print(f" ❌ Direct clang compilation error: {e}")
|
||||
results.failed += 1
|
||||
results.errors.append(f"Direct clang compilation error: {e}")
|
||||
|
||||
|
||||
def test_clang_response_file_compilation(results: TestResults) -> None:
|
||||
"""Test that clang can handle response files directly."""
|
||||
print("\n🧪 Testing clang response file compilation...")
|
||||
|
||||
clang_path = find_clang()
|
||||
if not clang_path:
|
||||
results.errors.append("clang compiler 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)]
|
||||
response_file = create_response_file(temp_path, compile_args)
|
||||
|
||||
# Test clang with response file
|
||||
cmd = [clang_path, f"@{response_file}"]
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
|
||||
if result.returncode == 0 and output_file.exists():
|
||||
print(" ✅ Clang response file compilation works")
|
||||
print(f" Output file created: {output_file.stat().st_size} bytes")
|
||||
print(f" Response file: @{response_file}")
|
||||
results.passed += 1
|
||||
else:
|
||||
print(
|
||||
f" ❌ Clang response file compilation failed (return code: {result.returncode})"
|
||||
)
|
||||
print(f" Stderr: {result.stderr}")
|
||||
results.failed += 1
|
||||
results.errors.append(
|
||||
f"Clang response file compilation failed: {result.stderr}"
|
||||
)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print(" ❌ Clang response file compilation timed out")
|
||||
results.failed += 1
|
||||
results.errors.append("Clang response file compilation timed out")
|
||||
except Exception as e:
|
||||
print(f" ❌ Clang response file compilation error: {e}")
|
||||
results.failed += 1
|
||||
results.errors.append(f"Clang response file compilation error: {e}")
|
||||
|
||||
|
||||
def test_xcache_wrapper_script_creation(results: TestResults) -> None:
|
||||
"""Test that xcache creates wrapper scripts correctly."""
|
||||
print("\n🧪 Testing xcache wrapper script creation...")
|
||||
|
||||
clang_path = find_clang()
|
||||
if not clang_path:
|
||||
results.errors.append("clang compiler not found - skipping wrapper script test")
|
||||
return
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Create test source file and response file
|
||||
source_file = create_test_source_file(temp_path)
|
||||
compile_args = ["-c", str(source_file), "-o", "test.o"]
|
||||
response_file = create_response_file(temp_path, compile_args)
|
||||
|
||||
# Create a mock sccache script that just echoes its arguments
|
||||
mock_sccache = (
|
||||
temp_path / "sccache.bat" if os.name == "nt" else temp_path / "sccache"
|
||||
)
|
||||
|
||||
if os.name == "nt":
|
||||
mock_content = """@echo off
|
||||
echo MOCK_SCCACHE_CALLED: %*
|
||||
"""
|
||||
else:
|
||||
mock_content = """#!/bin/bash
|
||||
echo "MOCK_SCCACHE_CALLED: $@"
|
||||
"""
|
||||
|
||||
mock_sccache.write_text(mock_content)
|
||||
if os.name != "nt":
|
||||
mock_sccache.chmod(0o755)
|
||||
|
||||
# Set up environment to use mock sccache
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = f"{temp_path}{os.pathsep}{env.get('PATH', '')}"
|
||||
env["XCACHE_DEBUG"] = "1"
|
||||
|
||||
# Test xcache with response file (should create wrapper script)
|
||||
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
|
||||
)
|
||||
|
||||
# Check if wrapper script was created and executed
|
||||
if "Created compiler wrapper:" in result.stderr:
|
||||
print(" ✅ Wrapper script creation detected")
|
||||
|
||||
# Check if wrapper was executed
|
||||
if "Executing with wrapper:" in result.stderr:
|
||||
print(" ✅ Wrapper script execution detected")
|
||||
|
||||
# Check if mock sccache was called
|
||||
if "MOCK_SCCACHE_CALLED:" in result.stdout:
|
||||
print(" ✅ Mock sccache was called through wrapper")
|
||||
print(f" Sccache called with: {result.stdout.strip()}")
|
||||
results.passed += 1
|
||||
else:
|
||||
print(" ❌ Mock sccache was not called")
|
||||
results.failed += 1
|
||||
results.errors.append(
|
||||
"Mock sccache was not called through wrapper"
|
||||
)
|
||||
else:
|
||||
print(" ❌ Wrapper script was not executed")
|
||||
results.failed += 1
|
||||
results.errors.append("Wrapper script was not executed")
|
||||
else:
|
||||
print(" ❌ Wrapper script was not created")
|
||||
print(f" Stderr: {result.stderr}")
|
||||
results.failed += 1
|
||||
results.errors.append(
|
||||
f"Wrapper script creation failed: {result.stderr}"
|
||||
)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print(" ❌ Wrapper script test timed out")
|
||||
results.failed += 1
|
||||
results.errors.append("Wrapper script test timed out")
|
||||
except Exception as e:
|
||||
print(f" ❌ Wrapper script test error: {e}")
|
||||
results.failed += 1
|
||||
results.errors.append(f"Wrapper script test error: {e}")
|
||||
|
||||
|
||||
def test_xcache_direct_mode(results: TestResults) -> None:
|
||||
"""Test xcache direct mode (no response files)."""
|
||||
print("\n🧪 Testing xcache direct mode...")
|
||||
|
||||
clang_path = find_clang()
|
||||
if not clang_path:
|
||||
results.errors.append("clang compiler not found - skipping direct mode 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)
|
||||
|
||||
# Create a mock sccache script that just passes through to real compiler
|
||||
mock_sccache = (
|
||||
temp_path / "sccache.bat" if os.name == "nt" else temp_path / "sccache"
|
||||
)
|
||||
|
||||
if os.name == "nt":
|
||||
mock_content = f'''@echo off
|
||||
echo MOCK_SCCACHE_DIRECT: %*
|
||||
"{clang_path}" %*
|
||||
'''
|
||||
else:
|
||||
mock_content = f'''#!/bin/bash
|
||||
echo "MOCK_SCCACHE_DIRECT: $@"
|
||||
"{clang_path}" "$@"
|
||||
'''
|
||||
|
||||
mock_sccache.write_text(mock_content)
|
||||
if os.name != "nt":
|
||||
mock_sccache.chmod(0o755)
|
||||
|
||||
# Set up environment to use mock sccache
|
||||
env = os.environ.copy()
|
||||
env["PATH"] = f"{temp_path}{os.pathsep}{env.get('PATH', '')}"
|
||||
env["XCACHE_DEBUG"] = "1"
|
||||
|
||||
output_file = temp_path / "test_direct.o"
|
||||
|
||||
# Test xcache without response files (direct mode)
|
||||
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 mode compilation succeeded")
|
||||
print(f" Output file created: {output_file.stat().st_size} bytes")
|
||||
|
||||
# Check if direct execution was used (no wrapper)
|
||||
if "Direct execution:" in result.stderr:
|
||||
print(" ✅ Direct execution mode confirmed")
|
||||
results.passed += 1
|
||||
else:
|
||||
print(" ⚠️ Direct execution mode not clearly indicated")
|
||||
results.passed += 1 # Still pass since compilation worked
|
||||
|
||||
else:
|
||||
print(
|
||||
f" ❌ Direct mode compilation failed (return code: {result.returncode})"
|
||||
)
|
||||
print(f" Stderr: {result.stderr}")
|
||||
results.failed += 1
|
||||
results.errors.append(
|
||||
f"Direct mode compilation failed: {result.stderr}"
|
||||
)
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print(" ❌ Direct mode test timed out")
|
||||
results.failed += 1
|
||||
results.errors.append("Direct mode test timed out")
|
||||
except Exception as e:
|
||||
print(f" ❌ Direct mode test error: {e}")
|
||||
results.failed += 1
|
||||
results.errors.append(f"Direct mode test error: {e}")
|
||||
|
||||
|
||||
def run_all_tests() -> TestResults:
|
||||
"""Run all xcache standalone tests."""
|
||||
print("🚀 XCACHE STANDALONE TESTS WITH CLANG")
|
||||
print("=" * 60)
|
||||
|
||||
results = TestResults()
|
||||
|
||||
# Check prerequisites
|
||||
clang_path = find_clang()
|
||||
|
||||
print(f"📋 Test Environment:")
|
||||
print(f" Clang: {clang_path or 'NOT FOUND'}")
|
||||
print(f" xcache: ci/util/xcache.py")
|
||||
print(f" Note: Using mock sccache for testing wrapper functionality")
|
||||
print()
|
||||
|
||||
if not clang_path:
|
||||
print("❌ Clang compiler not found - install clang to run tests")
|
||||
results.errors.append("Clang compiler not available")
|
||||
return results
|
||||
|
||||
# Run tests
|
||||
test_clang_direct_compilation(results)
|
||||
test_clang_response_file_compilation(results)
|
||||
test_xcache_wrapper_script_creation(results)
|
||||
test_xcache_direct_mode(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 standalone tests passed! Wrapper functionality verified."
|
||||
)
|
||||
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