Files
klubhaus-doorbell/libraries/FastLED/ci/tests/test_build_flags_toml.py
2026-02-12 00:45:31 -08:00

358 lines
12 KiB
Python

#!/usr/bin/env python3
"""
Unit tests for BuildFlags TOML parsing and serialization functionality.
Tests the BuildFlags and BuildTools classes for parsing TOML build configuration
files and serializing back to TOML format.
"""
import tempfile
import unittest
from pathlib import Path
from ci.compiler.clang_compiler import ArchiveOptions, BuildFlags, BuildTools
class TestBuildFlagsToml(unittest.TestCase):
"""Test BuildFlags TOML parsing and serialization"""
def setUp(self) -> None:
"""Set up test fixtures"""
self.temp_dir = Path(tempfile.mkdtemp())
def tearDown(self) -> None:
"""Clean up test fixtures"""
# Clean up any test files
for file in self.temp_dir.glob("*.toml"):
file.unlink()
self.temp_dir.rmdir()
def create_test_toml(self, content: str) -> Path:
"""Create a test TOML file with the given content"""
test_file = self.temp_dir / "test_build_flags.toml"
with open(test_file, "w", encoding="utf-8") as f:
f.write(content)
return test_file
def test_build_tools_defaults(self) -> None:
"""Test BuildTools default values"""
tools = BuildTools(
cpp_compiler=[],
archiver=[],
linker=[],
c_compiler=[],
objcopy=[],
nm=[],
strip=[],
ranlib=[],
)
# Test modern command-based fields (should be empty by default)
self.assertEqual(tools.cpp_compiler, [])
self.assertEqual(tools.archiver, [])
self.assertEqual(tools.linker, [])
# Test other important fields
self.assertEqual(tools.c_compiler, [])
self.assertEqual(tools.objcopy, [])
self.assertEqual(tools.nm, [])
self.assertEqual(tools.strip, [])
self.assertEqual(tools.ranlib, [])
def test_parse_minimal_toml(self) -> None:
"""Test parsing minimal TOML file with required tools section"""
toml_content = """
[all]
defines = ["-DTEST=1"]
compiler_flags = ["-Wall"]
include_flags = ["-I."]
[tools]
cpp_compiler = ["uv", "run", "python", "-m", "ziglang", "c++"]
linker = ["uv", "run", "python", "-m", "ziglang", "c++"]
c_compiler = ["clang"]
objcopy = ["uv", "run", "python", "-m", "ziglang", "objcopy"]
nm = ["uv", "run", "python", "-m", "ziglang", "nm"]
strip = ["uv", "run", "python", "-m", "ziglang", "strip"]
ranlib = ["uv", "run", "python", "-m", "ziglang", "ranlib"]
archiver = ["uv", "run", "python", "-m", "ziglang", "ar"]
[archive]
flags = "rcsD"
[linking.base]
flags = ["-pthread"]
[strict_mode]
flags = ["-Werror"]
"""
test_file = self.create_test_toml(toml_content)
flags = BuildFlags.parse(test_file, quick_build=False, strict_mode=False)
# Check basic flags
self.assertEqual(flags.defines, ["-DTEST=1"])
self.assertEqual(flags.compiler_flags, ["-Wall"])
self.assertEqual(flags.include_flags, ["-I."])
self.assertEqual(flags.link_flags, ["-pthread"])
self.assertEqual(flags.strict_mode_flags, ["-Werror"])
# Check tools (from [tools] section) - modern command-based approach
self.assertEqual(flags.tools.c_compiler, ["clang"])
self.assertEqual(
flags.tools.linker, ["uv", "run", "python", "-m", "ziglang", "c++"]
)
self.assertEqual(
flags.tools.cpp_compiler,
["uv", "run", "python", "-m", "ziglang", "c++"],
)
self.assertEqual(
flags.tools.objcopy, ["uv", "run", "python", "-m", "ziglang", "objcopy"]
)
self.assertEqual(flags.tools.nm, ["uv", "run", "python", "-m", "ziglang", "nm"])
self.assertEqual(
flags.tools.strip, ["uv", "run", "python", "-m", "ziglang", "strip"]
)
self.assertEqual(
flags.tools.ranlib, ["uv", "run", "python", "-m", "ziglang", "ranlib"]
)
self.assertEqual(
flags.tools.archiver, ["uv", "run", "python", "-m", "ziglang", "ar"]
)
def test_parse_toml_with_tools(self) -> None:
"""Test parsing TOML file with [tools] section"""
toml_content = """
[all]
defines = ["-DTEST=1"]
compiler_flags = ["-Wall"]
include_flags = ["-I."]
[tools]
cpp_compiler = ["g++"]
archiver = ["gcc-ar"]
linker = ["ld.gold"]
c_compiler = ["gcc"]
objcopy = ["arm-objcopy"]
nm = ["arm-nm"]
strip = ["arm-strip"]
ranlib = ["arm-ranlib"]
[archive]
flags = "rcsD"
[linking.base]
flags = ["-pthread"]
"""
test_file = self.create_test_toml(toml_content)
flags = BuildFlags.parse(test_file, quick_build=False, strict_mode=False)
# Check that tools were parsed correctly - modern command-based approach
self.assertEqual(flags.tools.cpp_compiler, ["g++"])
self.assertEqual(flags.tools.archiver, ["gcc-ar"])
self.assertEqual(flags.tools.linker, ["ld.gold"])
self.assertEqual(flags.tools.c_compiler, ["gcc"])
self.assertEqual(flags.tools.objcopy, ["arm-objcopy"])
self.assertEqual(flags.tools.nm, ["arm-nm"])
self.assertEqual(flags.tools.strip, ["arm-strip"])
self.assertEqual(flags.tools.ranlib, ["arm-ranlib"])
def test_parse_partial_tools_section(self) -> None:
"""Test parsing TOML with all required [tools] section fields"""
toml_content = """
[all]
defines = ["-DTEST=1"]
[tools]
cpp_compiler = ["custom-clang++"]
archiver = ["custom-ar"]
linker = ["custom-linker"]
c_compiler = ["clang"]
objcopy = ["custom-objcopy"]
nm = ["custom-nm"]
strip = ["custom-strip"]
ranlib = ["custom-ranlib"]
# All tools must be provided - no defaults allowed
[archive]
flags = "rcsD"
"""
test_file = self.create_test_toml(toml_content)
flags = BuildFlags.parse(test_file, quick_build=False, strict_mode=False)
# Check all tools are set as specified - strict validation
self.assertEqual(flags.tools.cpp_compiler, ["custom-clang++"])
self.assertEqual(flags.tools.archiver, ["custom-ar"])
self.assertEqual(flags.tools.linker, ["custom-linker"])
self.assertEqual(flags.tools.c_compiler, ["clang"])
self.assertEqual(flags.tools.objcopy, ["custom-objcopy"])
self.assertEqual(flags.tools.nm, ["custom-nm"])
self.assertEqual(flags.tools.strip, ["custom-strip"])
self.assertEqual(flags.tools.ranlib, ["custom-ranlib"])
def test_serialize_build_flags_with_tools(self) -> None:
"""Test serializing BuildFlags with tools to TOML"""
# Create BuildFlags with custom tools
custom_tools = BuildTools(
linker=["arm-none-eabi-ld"],
c_compiler=["arm-none-eabi-gcc"],
objcopy=["arm-none-eabi-objcopy"],
nm=["arm-none-eabi-nm"],
strip=["arm-none-eabi-strip"],
ranlib=["arm-none-eabi-ranlib"],
cpp_compiler=["arm-none-eabi-g++"],
archiver=["arm-none-eabi-ar"],
)
flags = BuildFlags(
defines=["-DARM_BUILD=1"],
compiler_flags=["-mcpu=cortex-m4", "-mthumb"],
include_flags=["-I.", "-Iarm"],
link_flags=["-nostdlib"],
strict_mode_flags=["-Werror"],
tools=custom_tools,
archive=ArchiveOptions(flags="rcsD"),
)
# Serialize to TOML
toml_output = flags.serialize()
# Check that tools section is present with new field names
self.assertIn("[tools]", toml_output)
self.assertIn("cpp_compiler = ['arm-none-eabi-g++']", toml_output)
self.assertIn("archiver = ['arm-none-eabi-ar']", toml_output)
self.assertIn("linker = ['arm-none-eabi-ld']", toml_output)
self.assertIn("c_compiler = ['arm-none-eabi-gcc']", toml_output)
self.assertIn("objcopy = ['arm-none-eabi-objcopy']", toml_output)
self.assertIn("nm = ['arm-none-eabi-nm']", toml_output)
self.assertIn("strip = ['arm-none-eabi-strip']", toml_output)
self.assertIn("ranlib = ['arm-none-eabi-ranlib']", toml_output)
def test_serialize_with_none_linker(self) -> None:
"""Test serializing BuildFlags when linker is None"""
flags = BuildFlags(
defines=["-DTEST=1"],
compiler_flags=[],
include_flags=[],
link_flags=[],
strict_mode_flags=[],
tools=BuildTools(
linker=[], # Empty list instead of None
cpp_compiler=["clang++"],
c_compiler=["clang"],
archiver=[],
objcopy=[],
nm=[],
strip=[],
ranlib=[],
),
archive=ArchiveOptions(flags="rcsD"),
)
toml_output = flags.serialize()
# Check that tools section is present but linker is omitted
self.assertIn("[tools]", toml_output)
self.assertIn("cpp_compiler = ['clang++']", toml_output)
self.assertNotIn("linker =", toml_output) # Should be omitted when empty
def test_round_trip_toml_parsing(self) -> None:
"""Test that parse -> serialize -> parse maintains data integrity"""
# Create original flags
original_tools = BuildTools(
linker=["test-ld"],
c_compiler=["test-gcc"],
cpp_compiler=["test-compiler"],
archiver=["test-ar"],
objcopy=["test-objcopy"],
nm=["test-nm"],
strip=["test-strip"],
ranlib=["test-ranlib"],
)
original_flags = BuildFlags(
defines=["-DROUND_TRIP=1"],
compiler_flags=["-Wall", "-O2"],
include_flags=["-I.", "-Itest"],
link_flags=["-pthread"],
strict_mode_flags=["-Werror"],
tools=original_tools,
archive=ArchiveOptions(flags="rcsD"),
)
# Serialize to TOML
toml_content = original_flags.serialize()
# Write to temporary file
temp_file = self.temp_dir / "roundtrip.toml"
original_flags.to_toml_file(temp_file)
# Parse back from file
parsed_flags = BuildFlags.parse(temp_file, quick_build=False, strict_mode=False)
# Check that all data is preserved
self.assertEqual(parsed_flags.defines, original_flags.defines)
self.assertEqual(parsed_flags.compiler_flags, original_flags.compiler_flags)
self.assertEqual(parsed_flags.include_flags, original_flags.include_flags)
self.assertEqual(parsed_flags.link_flags, original_flags.link_flags)
self.assertEqual(
parsed_flags.strict_mode_flags, original_flags.strict_mode_flags
)
# Check tools - modern command-based approach
self.assertEqual(
parsed_flags.tools.cpp_compiler, original_flags.tools.cpp_compiler
)
self.assertEqual(parsed_flags.tools.archiver, original_flags.tools.archiver)
self.assertEqual(parsed_flags.tools.linker, original_flags.tools.linker)
self.assertEqual(parsed_flags.tools.c_compiler, original_flags.tools.c_compiler)
def test_parse_missing_file(self) -> None:
"""Test parsing non-existent TOML file raises FileNotFoundError"""
nonexistent_file = self.temp_dir / "does_not_exist.toml"
# Should raise FileNotFoundError when file is missing
with self.assertRaises(FileNotFoundError) as context:
BuildFlags.parse(nonexistent_file, quick_build=False, strict_mode=False)
self.assertIn("Required build_flags.toml not found", str(context.exception))
def test_from_toml_file_alias(self) -> None:
"""Test that from_toml_file() is an alias for parse()"""
toml_content = """
[all]
defines = ["-DALIAS_TEST=1"]
[tools]
cpp_compiler = ["alias-compiler"]
linker = ["alias-linker"]
c_compiler = ["clang"]
objcopy = ["alias-objcopy"]
nm = ["alias-nm"]
strip = ["alias-strip"]
ranlib = ["alias-ranlib"]
archiver = ["alias-archiver"]
[archive]
flags = "rcsD"
"""
test_file = self.create_test_toml(toml_content)
# Parse using both methods
flags_parse = BuildFlags.parse(test_file, quick_build=True, strict_mode=False)
flags_alias = BuildFlags.from_toml_file(
test_file, quick_build=True, strict_mode=False
)
# Should be identical
self.assertEqual(flags_parse.defines, flags_alias.defines)
self.assertEqual(flags_parse.tools.cpp_compiler, flags_alias.tools.cpp_compiler)
self.assertEqual(flags_parse.tools.cpp_compiler, ["alias-compiler"])
if __name__ == "__main__":
unittest.main()