358 lines
12 KiB
Python
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()
|