Files
modulus/tests/test_export.py
T
khodowany 2750dd7f3a
publish / publish (push) Successful in 53m2s
Initial public khodo snapshot
2026-09-04 19:30:11 -05:00

353 lines
14 KiB
Python

import json
import pathlib
import tempfile
import unittest
import modulus.config
import modulus.export
class modulus_export_test(unittest.TestCase):
def config(self):
return {
"package": {
"name": "sample",
},
"boost": {
"ref": "boost/1.91.0",
"components": ["json"],
"default_options": {
"boost/*:without_charconv": False,
},
},
"tests": {
"name": "sample",
},
}
def write_config(self, root, config):
(root / "modulus.json").write_text(json.dumps(config), encoding="utf-8")
def write_package(self, package):
for directory in (
"modulus",
"tools/docker",
"tools/editor",
"include/modulus",
"tests",
"lib/cmake/modulus",
"share/modulus",
"bin",
):
(package / directory).mkdir(parents=True)
(package / "conanfile.py").write_text("", encoding="utf-8")
(package / "modulus.json").write_text("{}", encoding="utf-8")
(package / "modulus" / "cli.py").write_text("", encoding="utf-8")
(package / "modulus" / "config.py").write_text("", encoding="utf-8")
(package / "modulus" / "export.py").write_text("", encoding="utf-8")
(package / "tools" / "configure.py").write_text("#!/usr/bin/env python3\n", encoding="utf-8")
(package / "tools" / "lsp.py").write_text("#!/usr/bin/env python3\n", encoding="utf-8")
(package / "tools" / "run.py").write_text("#!/usr/bin/env python3\n", encoding="utf-8")
(package / "tools" / "docker" / "Dockerfile").write_text("FROM scratch\n", encoding="utf-8")
(package / "tools" / "editor" / "clangd.yaml").write_text("CompileFlags: {}\n", encoding="utf-8")
(package / "tools" / "editor" / "clang-format").write_text("DisableFormat: true\n", encoding="utf-8")
(package / "tools" / "editor" / "pyrightconfig.json").write_text("{}\n", encoding="utf-8")
(package / "include" / "modulus" / "types.hpp").write_text("", encoding="utf-8")
(package / "tests" / "test.cpp").write_text("", encoding="utf-8")
(package / "lib" / "cmake" / "modulus" / "modulus-config.cmake").write_text("", encoding="utf-8")
(package / "share" / "modulus" / "duplicate.py").write_text("", encoding="utf-8")
(package / "bin" / "modulus-tool").write_text("", encoding="utf-8")
def test_boost_requirement_options_enable_configured_components(self):
config = modulus.config.normalize(self.config())
boost_options = modulus.config.boost_requirement_options(config)
self.assertEqual(modulus.config.boost_ref(config), "boost/1.91.0")
self.assertIs(boost_options["without_json"], False)
self.assertIs(boost_options["without_container"], False)
self.assertIs(boost_options["without_charconv"], False)
self.assertIs(boost_options["without_atomic"], True)
def test_default_targets_include_architecture_and_build_type_matrix(self):
config = modulus.config.normalize(self.config())
targets = config["tooling"]["targets"]
self.assertEqual(
sorted(targets),
[
"arm_64_gcc12_debug",
"arm_64_gcc12_release",
"x86_64_gcc12_debug",
"x86_64_gcc12_release",
],
)
self.assertEqual(config["tooling"]["default_target"], "x86_64_gcc12_debug")
self.assertEqual(targets["x86_64_gcc12_debug"]["build_type"], "Debug")
self.assertFalse(targets["arm_64_gcc12_release"]["can_run"])
self.assertEqual(targets["x86_64_gcc12_release"]["profile"], "build/build_tools/conan/profiles/x86_64_gcc12_release")
self.assertFalse(config["package"]["artifact"]["enabled"])
self.assertFalse(config["tooling"]["run"]["enabled"])
self.assertFalse(config["tooling"]["lsp"]["enabled"])
self.assertEqual(config["tooling"]["lsp"]["compile_databases"], [])
def test_product_artifact_and_run_config_are_validated(self):
config = self.config()
config["package"]["artifact"] = {
"enabled": True,
"targets": ["x86_64_gcc12_release"],
"install_prefix": "usr",
"cmake_definitions": {
"SAMPLE_WITH_WEB": True,
},
"conan_options": {
"with_appliance_adapters": True,
},
"upload_env": "SAMPLE_PACKAGE_UPLOAD",
}
config["tooling"] = {
"run": {
"enabled": True,
"target": "x86_64_gcc12_debug",
"executable": "sample",
"env": {
"SAMPLE_WEB_ROOT": "build/web",
},
},
}
normalized = modulus.config.normalize(config)
self.assertEqual(normalized["package"]["artifact"]["install_prefix"], "usr")
self.assertEqual(normalized["package"]["artifact"]["registry_url"], "https://git.thyme-labs.com/api/packages/khodo/generic")
self.assertEqual(normalized["tooling"]["run"]["args"], [])
config["package"]["artifact"]["targets"] = ["missing"]
with self.assertRaisesRegex(modulus.config.config_error, "unknown target"):
modulus.config.normalize(config)
def test_target_cmake_definitions_are_validated_and_defaulted(self):
config = self.config()
config["tooling"] = {
"default_target": "native",
"targets": {
"native": {
"profile": "build/build_tools/conan/profiles/native",
"build_dir": "build/build_native",
"can_run": True,
"build_type": "Debug",
"cmake_definitions": {
"SAMPLE_WITH_WEB": True,
"SAMPLE_MODE": "dev",
},
},
"release": {
"profile": "build/build_tools/conan/profiles/release",
"build_dir": "build/build_release",
"can_run": True,
"build_type": "Release",
},
},
}
normalized = modulus.config.normalize(config)
self.assertEqual(
normalized["tooling"]["targets"]["native"]["cmake_definitions"],
{
"SAMPLE_WITH_WEB": True,
"SAMPLE_MODE": "dev",
},
)
self.assertEqual(normalized["tooling"]["targets"]["release"]["cmake_definitions"], {})
config["tooling"]["targets"]["native"]["cmake_definitions"] = {
"SAMPLE_BAD": [
"invalid",
],
}
with self.assertRaisesRegex(modulus.config.config_error, "tooling.targets.native.cmake_definitions"):
modulus.config.normalize(config)
def test_lsp_compile_databases_are_validated_and_defaulted(self):
config = self.config()
config["tooling"] = {
"lsp": {
"enabled": True,
"compile_databases": [
{
"name": "pico",
"build_dir": "build/build_pico_debug",
"cmake_definitions": {
"VAMP_PICO_FIRMWARE_PROJECT": True,
},
"unset_env": [
"CC",
"CXX",
],
},
],
},
}
normalized = modulus.config.normalize(config)
database = normalized["tooling"]["lsp"]["compile_databases"][0]
self.assertEqual(database["source_dir"], ".")
self.assertEqual(database["generator"], "Ninja")
self.assertEqual(database["env"], {})
self.assertEqual(database["unset_env"], ["CC", "CXX"])
config["tooling"]["lsp"]["compile_databases"][0]["unset_env"] = ["bad-name"]
with self.assertRaisesRegex(modulus.config.config_error, "tooling.lsp.compile_databases\\[0\\].unset_env"):
modulus.config.normalize(config)
def test_checked_in_profile_overrides_exported_default_profile(self):
with tempfile.TemporaryDirectory() as repo_dir:
repo = pathlib.Path(repo_dir)
self.write_config(repo, self.config())
profile = repo / "conan" / "profiles" / "x86_64_gcc12_release"
profile.parent.mkdir(parents=True)
profile.write_text("[settings]\n", encoding="utf-8")
config = modulus.config.load_repo_config(repo)
self.assertEqual(config["tooling"]["targets"]["x86_64_gcc12_release"]["profile"], "conan/profiles/x86_64_gcc12_release")
def test_docker_defaults_use_shared_modulus_marker(self):
config = modulus.config.normalize(self.config())
self.assertEqual(config["tooling"]["docker"]["image"], "modulus-build:dev")
self.assertEqual(config["tooling"]["docker"]["marker"], "/opt/modulus-build-image")
self.assertNotIn("apt_architectures", config["tooling"]["docker"])
self.assertNotIn("apt_packages", config["tooling"]["docker"])
def test_export_project_copies_runtime_and_exported_tools(self):
with tempfile.TemporaryDirectory() as repo_dir, tempfile.TemporaryDirectory() as package_dir:
repo = pathlib.Path(repo_dir)
package = pathlib.Path(package_dir)
self.write_config(repo, self.config())
self.write_package(package)
modulus.export.sync_project(repo, package)
self.assertFalse((repo / "modulus.py").exists())
self.assertFalse((repo / "build" / "build_common" / "manifest.json").exists())
self.assertTrue((repo / "build" / "build_tools" / "configure.py").is_file())
self.assertTrue((repo / "build" / "build_tools" / "lsp.py").is_file())
self.assertTrue((repo / "build" / "build_tools" / "run.py").is_file())
self.assertTrue((repo / "build" / "build_tools" / "docker" / "Dockerfile").is_file())
self.assertFalse((repo / "build" / "build_tools" / "docs").exists())
self.assertFalse((repo / "build" / "build_tools" / "lsp").exists())
for source_name, destination_name in modulus.export.editor_files.items():
self.assertEqual((repo / destination_name).read_text(encoding="utf-8"), (package / "tools" / "editor" / source_name).read_text(encoding="utf-8"))
runtime = repo / "build" / "build_common" / "modulus"
self.assertTrue((runtime / "modulus" / "cli.py").is_file())
self.assertTrue((runtime / "modulus" / "config.py").is_file())
self.assertTrue((runtime / "modulus" / "export.py").is_file())
self.assertFalse((runtime / "CMakeLists.txt").exists())
self.assertFalse((runtime / "README.md").exists())
self.assertFalse((runtime / "conanfile.py").exists())
self.assertFalse((runtime / "cmake").exists())
self.assertFalse((runtime / "include").exists())
self.assertFalse((runtime / "schemas").exists())
self.assertFalse((runtime / "src").exists())
self.assertFalse((runtime / "tools").exists())
self.assertFalse((runtime / "tests").exists())
self.assertFalse((runtime / "package").exists())
self.assertFalse((runtime / "lib").exists())
self.assertFalse((runtime / "share").exists())
self.assertFalse((runtime / "bin").exists())
def test_root_editor_files_match_export_templates(self):
root = pathlib.Path(__file__).resolve().parents[1]
for source_name, destination_name in modulus.export.editor_files.items():
self.assertEqual((root / destination_name).read_text(encoding="utf-8"), (root / "tools" / "editor" / source_name).read_text(encoding="utf-8"))
def test_clangd_template_uses_default_target_compile_database_and_disables_clang_tidy(self):
root = pathlib.Path(__file__).resolve().parents[1]
config = modulus.config.normalize(self.config())
default_target = config["tooling"]["targets"][config["tooling"]["default_target"]]
template = (root / "tools" / "editor" / "clangd.yaml").read_text(encoding="utf-8")
self.assertIn(f"CompilationDatabase: {default_target['build_dir']}", template)
self.assertIn("Diagnostics:", template)
self.assertIn("ClangTidy:", template)
self.assertIn("Remove: ['*']", template)
self.assertNotIn("bugprone-*", template)
self.assertNotIn("performance-*", template)
self.assertNotIn("readability-*", template)
def test_export_requires_packaged_tools(self):
with tempfile.TemporaryDirectory() as repo_dir, tempfile.TemporaryDirectory() as package_dir:
repo = pathlib.Path(repo_dir)
package = pathlib.Path(package_dir)
self.write_config(repo, self.config())
(package / "modulus").mkdir()
(package / "modulus" / "cli.py").write_text("", encoding="utf-8")
with self.assertRaisesRegex(RuntimeError, "missing tools directory"):
modulus.export.sync_project(repo, package)
def test_normalize_rejects_invalid_cmake_values(self):
config = self.config()
config["package"]["name"] = "BadName"
with self.assertRaisesRegex(modulus.config.config_error, "package.name"):
modulus.config.normalize(config)
config = self.config()
config["docs"] = {}
with self.assertRaisesRegex(modulus.config.config_error, "unknown keys: docs"):
modulus.config.normalize(config)
def test_unknown_top_level_config_keys_are_rejected(self):
config = self.config()
config["old_config"] = {}
with self.assertRaisesRegex(modulus.config.config_error, "unknown keys: old_config"):
modulus.config.normalize(config)
def test_removed_setup_generation_fields_are_rejected(self):
config = self.config()
config["schema_version"] = 1
with self.assertRaisesRegex(modulus.config.config_error, "schema_version"):
modulus.config.normalize(config)
config = self.config()
config["package"]["cmake_target"] = "sample::sample"
with self.assertRaisesRegex(modulus.config.config_error, "cmake_target"):
modulus.config.normalize(config)
config = self.config()
config["exports"] = ["include/*.hpp"]
with self.assertRaisesRegex(modulus.config.config_error, "exports"):
modulus.config.normalize(config)
config = self.config()
config["dependencies"] = [
{
"ref": "openssl/4.0.1",
},
]
with self.assertRaisesRegex(modulus.config.config_error, "dependencies"):
modulus.config.normalize(config)
config = self.config()
config["examples"] = [
{
"target": "sample-example",
},
]
with self.assertRaisesRegex(modulus.config.config_error, "examples"):
modulus.config.normalize(config)
config = self.config()
config["tooling"] = {
"docker": {
"apt_packages": ["libdrm-dev"],
},
}
with self.assertRaisesRegex(modulus.config.config_error, "apt_packages"):
modulus.config.normalize(config)
config = self.config()
config["tooling"] = {
"targets": {
"native": {
"profile": "build/profile",
"build_dir": "build/native",
"options": {
"glfw/*:with_x11": False,
},
},
},
}
with self.assertRaisesRegex(modulus.config.config_error, "options"):
modulus.config.normalize(config)
config = self.config()
config["tooling"] = {
"remote": {
"update_by_default": True,
},
}
with self.assertRaisesRegex(modulus.config.config_error, "update_by_default"):
modulus.config.normalize(config)
if __name__ == "__main__":
unittest.main()