initial commit
This commit is contained in:
171
libraries/FastLED/ci/tests/test_unity_generation.py
Normal file
171
libraries/FastLED/ci/tests/test_unity_generation.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
Unit tests for unity file generation behavior in the Python compiler.
|
||||
|
||||
Validates that:
|
||||
- Unity content matches expected includes order
|
||||
- Unity file writes are idempotent (no rewrite when content unchanged)
|
||||
- Unity file is rewritten when source list changes
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from unittest import TestCase
|
||||
|
||||
from ci.compiler.clang_compiler import BuildFlags, Compiler, CompilerOptions
|
||||
|
||||
|
||||
class TestUnityGeneration(TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.temp_dir = Path(tempfile.mkdtemp(prefix="unity_test_")).resolve()
|
||||
self.unity_dir = self.temp_dir / "unity"
|
||||
self.unity_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.src_dir = self.temp_dir / "src"
|
||||
self.src_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Create minimal compilable .cpp files
|
||||
self.cpp1 = self.src_dir / "a.cpp"
|
||||
self.cpp2 = self.src_dir / "b.cpp"
|
||||
|
||||
with open(self.cpp1, "w", encoding="utf-8") as f1:
|
||||
f1.write(
|
||||
"""
|
||||
static int func_a() { return 1; }
|
||||
int var_a = func_a();
|
||||
""".lstrip()
|
||||
)
|
||||
|
||||
with open(self.cpp2, "w", encoding="utf-8") as f2:
|
||||
f2.write(
|
||||
"""
|
||||
static int func_b() { return 2; }
|
||||
int var_b = func_b();
|
||||
""".lstrip()
|
||||
)
|
||||
|
||||
# Load build flags and create a compiler instance
|
||||
project_root = Path.cwd()
|
||||
toml_path = project_root / "ci" / "build_unit.toml"
|
||||
build_flags: BuildFlags = BuildFlags.parse(
|
||||
toml_path, quick_build=True, strict_mode=False
|
||||
)
|
||||
|
||||
compiler_args: List[str] = []
|
||||
for item in build_flags.tools.cpp_compiler:
|
||||
compiler_args.append(item)
|
||||
for item in build_flags.compiler_flags:
|
||||
compiler_args.append(item)
|
||||
for item in build_flags.include_flags:
|
||||
compiler_args.append(item)
|
||||
|
||||
settings = CompilerOptions(
|
||||
include_path=str(project_root / "src"),
|
||||
defines=[],
|
||||
compiler_args=compiler_args,
|
||||
)
|
||||
|
||||
self.compiler = Compiler(settings, build_flags)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
# Best effort cleanup of temp directory
|
||||
try:
|
||||
for root, dirs, files in os.walk(self.temp_dir, topdown=False):
|
||||
for name in files:
|
||||
try:
|
||||
(Path(root) / name).unlink(missing_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
for name in dirs:
|
||||
try:
|
||||
(Path(root) / name).rmdir()
|
||||
except Exception:
|
||||
pass
|
||||
self.temp_dir.rmdir()
|
||||
except Exception:
|
||||
# Ignore cleanup issues on Windows
|
||||
pass
|
||||
|
||||
def test_unity_write_idempotent_and_updates_on_change(self) -> None:
|
||||
# First run: generate and compile one chunk unity
|
||||
options = CompilerOptions(
|
||||
include_path=self.compiler.settings.include_path,
|
||||
defines=[],
|
||||
compiler_args=self.compiler.settings.compiler_args,
|
||||
use_pch=False,
|
||||
additional_flags=["-c"],
|
||||
)
|
||||
|
||||
cpp_list: List[str] = [str(self.cpp1), str(self.cpp2)]
|
||||
|
||||
result = self.compiler._compile_unity_chunks_sync( # Internal, synchronous for testing
|
||||
options,
|
||||
cpp_list,
|
||||
chunks=1,
|
||||
unity_dir=self.unity_dir,
|
||||
no_parallel=True,
|
||||
)
|
||||
|
||||
self.assertTrue(result.success, "Initial unity compilation should succeed")
|
||||
|
||||
unity_cpp = self.unity_dir / "unity1.cpp"
|
||||
self.assertTrue(unity_cpp.exists(), "unity1.cpp should be created")
|
||||
|
||||
first_mtime = unity_cpp.stat().st_mtime_ns
|
||||
|
||||
# Second run with identical inputs should not rewrite the file
|
||||
time.sleep(0.01) # Ensure detectable time difference if rewritten
|
||||
|
||||
result2 = self.compiler._compile_unity_chunks_sync(
|
||||
options,
|
||||
cpp_list,
|
||||
chunks=1,
|
||||
unity_dir=self.unity_dir,
|
||||
no_parallel=True,
|
||||
)
|
||||
self.assertTrue(result2.success, "Second unity compilation should succeed")
|
||||
|
||||
second_mtime = unity_cpp.stat().st_mtime_ns
|
||||
self.assertEqual(
|
||||
first_mtime,
|
||||
second_mtime,
|
||||
"unity1.cpp should not be rewritten when content is unchanged",
|
||||
)
|
||||
|
||||
# Change the input set (add a new file) to force unity content change and rewrite
|
||||
cpp3 = self.src_dir / "c.cpp"
|
||||
with open(cpp3, "w", encoding="utf-8") as f3:
|
||||
f3.write(
|
||||
"""
|
||||
static int func_c() { return 3; }
|
||||
int var_c = func_c();
|
||||
""".lstrip()
|
||||
)
|
||||
|
||||
# Update list to include the new file
|
||||
cpp_list.append(str(cpp3))
|
||||
|
||||
time.sleep(0.01)
|
||||
result3 = self.compiler._compile_unity_chunks_sync(
|
||||
options,
|
||||
cpp_list,
|
||||
chunks=1,
|
||||
unity_dir=self.unity_dir,
|
||||
no_parallel=True,
|
||||
)
|
||||
self.assertTrue(
|
||||
result3.success, "Unity compilation after input set change should succeed"
|
||||
)
|
||||
|
||||
third_mtime = unity_cpp.stat().st_mtime_ns
|
||||
self.assertGreater(
|
||||
third_mtime,
|
||||
second_mtime,
|
||||
"unity1.cpp should be rewritten when the included file list changes",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user