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