import json import os import pathlib import subprocess import sys import tarfile import tempfile import unittest import unittest.mock import modulus.cli import modulus.config class upload_packages_test(unittest.TestCase): def context(self, repo_root=None, package_name="sample"): config = { "package": { "name": package_name, "artifact": { "enabled": False, "targets": [], "install_prefix": "", "cmake_definitions": {}, "conan_options": {}, "registry_url": "https://example.test/api/packages/acme/generic", "upload_env": "SAMPLE_PACKAGE_UPLOAD", }, }, "tooling": { "default_target": "native", "targets": { "native": { "profile": "build/build_tools/conan/profiles/native", "build_dir": "build/build_native", "can_run": True, "build_type": "Release", }, }, "docker": { "image": "sample-build:dev", "marker": "/opt/sample-build-image", }, "upload_env": "SAMPLE_CONAN_UPLOAD", "remote": { "name": "origin", "url": "https://example.test/conan", }, "run": { "enabled": False, "target": "native", "executable": "sample", "env": {}, "args": [], "ports": [], }, }, "tests": { "name": "sample", }, } return modulus.cli.build_context(repo_root or pathlib.Path(os.getcwd()), config) def test_cli_import_does_not_import_conan_recipe_module(self): repo = pathlib.Path(__file__).resolve().parents[1] command = [ sys.executable, "-c", "import sys; import modulus.cli; raise SystemExit(1 if 'modulus.recipe' in sys.modules else 0)", ] completed = subprocess.run(command, cwd=repo, check=False) self.assertEqual(completed.returncode, 0) def test_project_root_build_is_rejected(self): with tempfile.TemporaryDirectory() as directory: root = pathlib.Path(directory) context = self.context(root) context.config["tooling"]["targets"]["native"]["build_dir"] = "." with self.assertRaisesRegex(modulus.config.config_error, "project and source roots"): modulus.cli.build_context(root, context.config) def test_configure_and_lsp_refresh_project_editor_settings(self): with tempfile.TemporaryDirectory() as directory: root = pathlib.Path(directory) fragments = [ {"CompileFlags": {"CompilationDatabase": "build/native"}}, {"If": {"PathMatch": "src/pico/.*"}, "CompileFlags": {"CompilationDatabase": "build/pico"}}, ] config = {"package": {"name": "sample"}, "tooling": {"editor": {"clangd": fragments}}} (root / "modulus.json").write_text(json.dumps(config), encoding="utf-8") with unittest.mock.patch.object(modulus.cli, "find_repo_root", return_value=root): for command in ("configure", "lsp"): with self.subTest(command=command), unittest.mock.patch.object(modulus.cli, command, return_value=0): (root / ".clangd").write_text("stale", encoding="utf-8") self.assertEqual(modulus.cli.main(command), 0) content = (root / ".clangd").read_text(encoding="utf-8").split("\n", 1)[1] self.assertEqual([json.loads(part) for part in content.split("\n---\n")], fragments) before = {name: (root / name).stat().st_mtime_ns for name in modulus.export.editor_files.values()} self.assertEqual(modulus.cli.main(command), 0) self.assertEqual(before, {name: (root / name).stat().st_mtime_ns for name in before}) def test_lsp_rejects_source_root_before_cache_cleanup(self): with tempfile.TemporaryDirectory() as directory: root = pathlib.Path(directory) context = self.context(root) cache = root / "CMakeCache.txt" cache.write_text("keep", encoding="utf-8") with unittest.mock.patch.object(modulus.cli, "clear_lsp_cmake_cache") as clear: for build_dir in (".", "alias", "subproject"): if build_dir == "alias": (root / "alias").symlink_to(root, target_is_directory=True) with self.subTest(build_dir=build_dir), self.assertRaises(modulus.config.config_error): modulus.cli.configure_lsp_database(context, {"source_dir": "subproject", "build_dir": build_dir}) clear.assert_not_called() self.assertEqual(cache.read_text(encoding="utf-8"), "keep") @unittest.mock.patch.object(modulus.cli, "upload_all_packages", return_value=37) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) def test_failed_upload_does_not_force_by_default(self, configure, upload): context = self.context() with unittest.mock.patch.dict(os.environ, {}, clear=True): status = modulus.cli.upload_packages(context) self.assertEqual(status, 37) configure.assert_called_once_with(context, authenticate=True) upload.assert_called_once_with(context, "origin", force=False) @unittest.mock.patch.object(modulus.cli, "upload_all_packages") @unittest.mock.patch.object(modulus.cli, "configure_conan_remote") def test_replace_requires_guard_env(self, configure, upload): with unittest.mock.patch.dict(os.environ, {}, clear=True): status = modulus.cli.upload_packages(self.context(), replace=True) self.assertEqual(status, 1) configure.assert_not_called() upload.assert_not_called() @unittest.mock.patch.object(modulus.cli, "upload_all_packages", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) def test_replace_forces_upload_when_guarded(self, configure, upload): context = self.context() with unittest.mock.patch.dict( os.environ, { "MODULUS_ALLOW_PACKAGE_REPLACE": "1", }, clear=True, ): status = modulus.cli.upload_packages(context, replace=True) self.assertEqual(status, 0) configure.assert_called_once_with(context, authenticate=True) upload.assert_called_once_with(context, "origin", force=True) @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_upload_defaults_to_latest_reference(self, run): with unittest.mock.patch.dict(os.environ, {}, clear=True): status = modulus.cli.upload_all_packages(self.context(), "origin") self.assertEqual(status, 0) self.assertIn("sample/latest:*", run.call_args.args[1]) self.assertNotIn("--force", run.call_args.args[1]) @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_replace_upload_uses_conan_force(self, run): status = modulus.cli.upload_all_packages(self.context(), "origin", force=True) self.assertEqual(status, 0) self.assertIn("--force", run.call_args.args[1]) def test_checked_in_ci_overwrites_latest_on_master(self): workflow = (pathlib.Path(__file__).resolve().parents[1] / ".gitea" / "workflows" / "ci.yml").read_text(encoding="utf-8") self.assertIn("gitea.ref == 'refs/heads/master'", workflow) self.assertIn("workflow_dispatch", workflow) self.assertIn("MODULUS_ALLOW_PACKAGE_REPLACE", workflow) self.assertIn("--replace", workflow) self.assertNotIn("--seed-dependencies", workflow) self.assertNotIn("--no-remote", workflow) self.assertNotIn("CONAN_PACKAGE_API_URL", workflow) self.assertNotIn("VERSION_TAG", workflow) self.assertIn("CONAN_HOME: build/build_common/conan_cache", workflow) self.assertNotIn("./tools/bench.py", workflow) self.assertNotIn("--examples", workflow) self.assertNotIn("--volume \"${PWD}:${PWD}\"", workflow) self.assertNotIn("CONAN_PUBLIC_REMOTE_URL", workflow) self.assertNotIn("docs.py", workflow) def test_removed_public_commands_are_rejected(self): with tempfile.TemporaryDirectory() as repo_dir: repo = pathlib.Path(repo_dir) (repo / "modulus.json").write_text( '{"package":{"name":"sample"}}', encoding="utf-8", ) previous_cwd = pathlib.Path.cwd() os.chdir(repo) try: for command in ("image", "docs", "shell", "docker_support"): with self.assertRaisesRegex(SystemExit, "unknown modulus command"): modulus.cli.main(command) finally: os.chdir(previous_cwd) def test_docker_passthrough_includes_replace_env(self): passthrough = modulus.cli.docker_passthrough_env(self.context()) self.assertIn("MODULUS_ALLOW_PACKAGE_REPLACE", passthrough) self.assertIn("PACKAGE_REGISTRY_USER", passthrough) self.assertIn("PACKAGE_REGISTRY_TOKEN", passthrough) self.assertIn("SAMPLE_PACKAGE_UPLOAD", passthrough) self.assertNotIn("CONAN_PACKAGE_VERSION", passthrough) self.assertNotIn("MODULUS_EXPORT_RUNTIME", passthrough) self.assertNotIn("GITHUB_TOKEN", passthrough) self.assertNotIn("GITEA_TOKEN", passthrough) def test_run_ports_default_to_empty_list(self): config = modulus.config.normalize({ "package": { "name": "sample", }, }) self.assertEqual(config["tooling"]["run"]["ports"], []) def test_run_accepts_docker_publish_mapping(self): config = modulus.config.normalize({ "package": { "name": "sample", }, "tooling": { "run": { "ports": ["127.0.0.1:8000:8000/tcp"], }, }, }) self.assertEqual(config["tooling"]["run"]["ports"], ["127.0.0.1:8000:8000/tcp"]) def test_run_rejects_invalid_docker_publish_mapping(self): with self.assertRaisesRegex(modulus.config.config_error, "tooling.run.ports\\[0\\]"): modulus.config.normalize({ "package": { "name": "sample", }, "tooling": { "run": { "ports": ["127.0.0.1:8000:8000/tcp --privileged"], }, }, }) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_run_is_disabled_by_default(self, docker): status = modulus.cli.product_run(self.context(), []) self.assertEqual(status, 1) docker.assert_not_called() @unittest.mock.patch.object(modulus.cli, "run", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_run_executes_configured_product_executable(self, docker, run): with tempfile.TemporaryDirectory() as repo_dir: repo = pathlib.Path(repo_dir) context = self.context(repo_root=repo) context.run_settings.update({ "enabled": True, "target": "native", "executable": "sample-app", "env": { "SAMPLE_WEB_ROOT": "build/web", }, "args": ["--serve"], }) context.default_target.build_dir.mkdir(parents=True) executable = context.default_target.build_dir / "sample-app" executable.write_text("#!/bin/sh\n", encoding="utf-8") status = modulus.cli.product_run( context, [ "--", "--port", "8000", ], ) self.assertEqual(status, 0) self.assertEqual( run.call_args.args[1], [ executable, "--serve", "--port", "8000", ], ) self.assertEqual(run.call_args.kwargs["env_overrides"], {"SAMPLE_WEB_ROOT": "build/web"}) docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "docker_run", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=False) def test_run_publishes_configured_ports_when_wrapped_by_docker(self, docker, docker_run): context = self.context() context.run_settings.update({ "enabled": True, "ports": ["127.0.0.1:8000:8000/tcp"], }) status = modulus.cli.product_run(context, ["--target", "native"]) self.assertEqual(status, 0) docker_run.assert_called_once_with( context, [ f"./{context.tools_rel}/run.py", "--target", "native", ], docker_args=[ "--publish", "127.0.0.1:8000:8000/tcp", ], ) docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_build_image_honors_docker_platform_env(self, run): context = self.context() with unittest.mock.patch.dict( os.environ, { "MODULUS_DOCKER_PLATFORM": "linux/arm64", }, clear=True, ): status = modulus.cli.build_image(context) self.assertEqual(status, 0) self.assertEqual( run.call_args.args[1], [ "docker", "build", "--platform", "linux/arm64", "--file", context.dockerfile, "--tag", "sample-build:dev", "--label", f"{modulus.cli.modulus.clangd.image_label}={modulus.cli.modulus.clangd.recipe_digest(context)}", ".", ], ) @unittest.mock.patch.object(modulus.cli, "ensure_build_image", return_value=0) @unittest.mock.patch.object(modulus.cli, "run", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_container", return_value=False) def test_docker_run_honors_docker_platform_env(self, in_container, run, ensure_image): context = self.context() with unittest.mock.patch.dict( os.environ, { "MODULUS_DOCKER_PLATFORM": "linux/arm64", }, clear=True, ): status = modulus.cli.docker_run(context, ["true"]) self.assertEqual(status, 0) self.assertIn("--platform", run.call_args.args[1]) self.assertIn("linux/arm64", run.call_args.args[1]) ensure_image.assert_called_once_with(context) in_container.assert_called_once() def test_conan_remote_url_uses_config_by_default(self): with unittest.mock.patch.dict(os.environ, {}, clear=True): self.assertEqual(modulus.cli.conan_remote_url(self.context()), "https://example.test/conan") def test_conan_remote_url_uses_config_in_ci(self): with unittest.mock.patch.dict( os.environ, { "CI": "true", }, clear=True, ): self.assertEqual(modulus.cli.conan_remote_url(self.context()), "https://example.test/conan") def test_conan_credentials_only_uses_conan_environment(self): with unittest.mock.patch.dict( os.environ, { "CONAN_USER": "dev", "CONAN_TOKEN": "secret", "GITEA_TOKEN": "ignored", }, clear=True, ): user, token = modulus.cli.conan_credentials() self.assertEqual(user, "dev") self.assertEqual(token, "secret") @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_configure_remote_skips_login_without_conan_credentials(self, run): with unittest.mock.patch.dict(os.environ, {}, clear=True): status = modulus.cli.configure_conan_remote(self.context()) self.assertEqual(status, 0) self.assertEqual(len(run.call_args_list), 1) self.assertEqual( run.call_args_list[0].args[1], [ "conan", "remote", "add", "-f", "origin", "https://example.test/conan", ], ) @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_configure_remote_rejects_partial_conan_credentials(self, run): with unittest.mock.patch.dict(os.environ, {"CONAN_USER": "dev"}, clear=True): status = modulus.cli.configure_conan_remote(self.context()) self.assertEqual(status, 1) run.assert_not_called() @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_configure_remote_skips_login_until_authentication_is_requested(self, run): context = self.context() with unittest.mock.patch.dict( os.environ, { "CONAN_USER": "dev", "CONAN_TOKEN": "secret", }, clear=True, ): status = modulus.cli.configure_conan_remote(context) self.assertEqual(status, 0) self.assertEqual(len(run.call_args_list), 1) self.assertEqual( run.call_args_list[0].args[1], [ "conan", "remote", "add", "-f", "origin", "https://example.test/conan", ], ) @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_configure_remote_logs_in_with_hidden_token_when_authenticating(self, run): context = self.context() with unittest.mock.patch.dict( os.environ, { "CONAN_USER": "dev", "CONAN_TOKEN": "secret", }, clear=True, ): status = modulus.cli.configure_conan_remote(context, authenticate=True) self.assertEqual(status, 0) self.assertEqual( run.call_args_list[0].args[1], [ "conan", "remote", "add", "-f", "origin", "https://example.test/conan", ], ) self.assertEqual( run.call_args_list[1].args[1], [ "conan", "remote", "login", "origin", "dev", "-p", "secret", ], ) self.assertEqual( run.call_args_list[1].kwargs["display_command"], [ "conan", "remote", "login", "origin", "dev", "-p", "", ], ) def multi_target_context(self): config = { "package": { "name": "sample", "artifact": { "enabled": False, "targets": [], "install_prefix": "", "cmake_definitions": {}, "conan_options": {}, "registry_url": "https://example.test/api/packages/acme/generic", "upload_env": "SAMPLE_PACKAGE_UPLOAD", }, }, "tooling": { "default_target": "debug", "targets": { "debug": { "profile": "build/build_tools/conan/profiles/debug", "build_dir": "build/build_debug", "can_run": True, "build_type": "Debug", }, "release": { "profile": "build/build_tools/conan/profiles/release", "build_dir": "build/build_release", "can_run": True, "build_type": "Release", }, }, "docker": { "image": "sample-build:dev", "marker": "/opt/sample-build-image", }, "upload_env": "SAMPLE_CONAN_UPLOAD", "remote": { "name": "origin", "url": "https://example.test/conan", }, "run": { "enabled": False, "target": "debug", "executable": "sample", "env": {}, "args": [], }, }, "tests": { "name": "sample", }, } return modulus.cli.build_context(pathlib.Path(os.getcwd()), config) def test_selected_targets_default_to_default_target_only(self): context = self.multi_target_context() selected = context.selected_targets( [], False, ) self.assertEqual([target.name for target in selected], ["debug"]) def test_selected_targets_all_returns_every_target(self): context = self.multi_target_context() selected = context.selected_targets( [], True, ) self.assertEqual([target.name for target in selected], ["debug", "release"]) @unittest.mock.patch.object(modulus.cli, "run", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_build_can_target_one_cmake_target(self, docker, run): directory = tempfile.TemporaryDirectory() self.addCleanup(directory.cleanup) context = self.context(pathlib.Path(directory.name)) context.default_target.build_dir.mkdir(parents=True, exist_ok=True) (context.default_target.build_dir / "CMakeCache.txt").write_text("", encoding="utf-8") (context.default_target.build_dir / "build.ninja").write_text("", encoding="utf-8") status = modulus.cli.build( context, [ "--target", "native", "--cmake-target", "sample-example", ], ) self.assertEqual(status, 0) self.assertEqual( run.call_args.args[1], [ "cmake", "--build", context.default_target.build_dir, "--target", "sample-example", "--parallel", "2", ], ) docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "run", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_one", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_build_auto_configures_missing_target(self, docker, configure_remote, configure_one, run): with tempfile.TemporaryDirectory() as repo_dir: context = self.context(repo_root=pathlib.Path(repo_dir)) status = modulus.cli.build(context, []) self.assertEqual(status, 0) configure_remote.assert_called_once_with(context) configure_one.assert_called_once_with(context, context.default_target, update=False) self.assertEqual( run.call_args.args[1], [ "cmake", "--build", context.default_target.build_dir, "--parallel", "2", ], ) docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "run", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_one", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_build_skips_configure_when_target_is_configured(self, docker, configure_remote, configure_one, run): with tempfile.TemporaryDirectory() as repo_dir: context = self.context(repo_root=pathlib.Path(repo_dir)) context.default_target.build_dir.mkdir(parents=True) (context.default_target.build_dir / "CMakeCache.txt").write_text("", encoding="utf-8") (context.default_target.build_dir / "build.ninja").write_text("", encoding="utf-8") status = modulus.cli.build(context, []) self.assertEqual(status, 0) configure_remote.assert_not_called() configure_one.assert_not_called() self.assertEqual( run.call_args.args[1], [ "cmake", "--build", context.default_target.build_dir, "--parallel", "2", ], ) docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "run", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_one", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_build_reconfigures_when_target_cmake_definition_differs(self, docker, configure_remote, configure_one, run): with tempfile.TemporaryDirectory() as repo_dir: context = self.context(repo_root=pathlib.Path(repo_dir)) context.default_target.cmake_definitions = { "SAMPLE_WITH_WEB": True, } context.default_target.build_dir.mkdir(parents=True) (context.default_target.build_dir / "CMakeCache.txt").write_text("SAMPLE_WITH_WEB:BOOL=OFF\n", encoding="utf-8") (context.default_target.build_dir / "build.ninja").write_text("", encoding="utf-8") status = modulus.cli.build(context, []) self.assertEqual(status, 0) configure_remote.assert_called_once_with(context) configure_one.assert_called_once_with(context, context.default_target, update=False) self.assertEqual( run.call_args.args[1], [ "cmake", "--build", context.default_target.build_dir, "--parallel", "2", ], ) docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "run", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_one", return_value=37) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_build_stops_when_auto_configure_fails(self, docker, configure_remote, configure_one, run): with tempfile.TemporaryDirectory() as repo_dir: context = self.context(repo_root=pathlib.Path(repo_dir)) status = modulus.cli.build(context, []) self.assertEqual(status, 37) configure_remote.assert_called_once_with(context) configure_one.assert_called_once_with(context, context.default_target, update=False) run.assert_not_called() docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "configure_one", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_configure_no_remote_skips_remote_setup(self, docker, configure_remote, configure_one): with tempfile.TemporaryDirectory() as repo_dir: context = self.context(repo_root=pathlib.Path(repo_dir)) status = modulus.cli.configure( context, [ "--target", "native", "--no-remote", ], ) self.assertEqual(status, 0) configure_remote.assert_not_called() configure_one.assert_called_once_with(context, context.default_target, False, no_remote=True) docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "upload_packages", return_value=0) @unittest.mock.patch.object(modulus.cli, "create_package", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_package_no_remote_skips_remote_setup_and_upload(self, docker, configure_remote, create_package, upload): context = self.context() with unittest.mock.patch.dict( os.environ, { "SAMPLE_CONAN_UPLOAD": "1", }, clear=True, ): status = modulus.cli.package( context, [ "--target", "native", "--no-remote", ], ) self.assertEqual(status, 0) configure_remote.assert_not_called() create_package.assert_called_once_with(context, context.default_target, False, no_remote=True) upload.assert_not_called() docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "upload_packages", return_value=0) @unittest.mock.patch.object(modulus.cli, "create_package", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_package_configures_remote_once_before_upload(self, docker, configure_remote, create_package, upload): context = self.context() with unittest.mock.patch.dict( os.environ, { "CONAN_USER": "dev", "CONAN_TOKEN": "secret", "SAMPLE_CONAN_UPLOAD": "1", }, clear=True, ): status = modulus.cli.package( context, [ "--target", "native", ], ) self.assertEqual(status, 0) configure_remote.assert_called_once_with(context) create_package.assert_called_once_with(context, context.default_target, False, no_remote=False) upload.assert_called_once_with(context, replace=False) docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "upload_packages", return_value=1) @unittest.mock.patch.object(modulus.cli, "create_package", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_package_defers_conan_upload_credentials_until_after_create(self, docker, configure_remote, create_package, upload): context = self.context() with unittest.mock.patch.dict( os.environ, { "SAMPLE_CONAN_UPLOAD": "1", }, clear=True, ): status = modulus.cli.package( context, [ "--target", "native", ], ) self.assertEqual(status, 1) configure_remote.assert_called_once_with(context) create_package.assert_called_once_with(context, context.default_target, False, no_remote=False) upload.assert_called_once_with(context, replace=False) docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "create_package", return_value=0) @unittest.mock.patch.object(modulus.cli, "run", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_package_rejects_partial_conan_credentials_before_create(self, docker, run, create_package): context = self.context() with unittest.mock.patch.dict( os.environ, { "CONAN_USER": "dev", "SAMPLE_CONAN_UPLOAD": "1", }, clear=True, ): status = modulus.cli.package( context, [ "--target", "native", ], ) self.assertEqual(status, 1) create_package.assert_not_called() run.assert_not_called() docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "clear_conan_deps") @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_configure_one_passes_no_remote_to_conan_install(self, run, clear): context = self.context() status = modulus.cli.configure_one(context, context.default_target, update=False, no_remote=True) self.assertEqual(status, 0) conan_command = run.call_args_list[0].args[1] self.assertIn("--no-remote", conan_command) self.assertIn("--build=missing", conan_command) self.assertNotIn("-s", conan_command) self.assertNotIn("-c", conan_command) self.assertNotIn("glfw/*:with_x11=False", conan_command) self.assertIn(f"-DCMAKE_TOOLCHAIN_FILE={context.default_target.build_dir / 'conan' / 'conan_toolchain.cmake'}", run.call_args_list[1].args[1]) self.assertIn("-DCMAKE_BUILD_TYPE=Release", run.call_args_list[1].args[1]) clear.assert_called_once() @unittest.mock.patch.object(modulus.cli, "clear_conan_deps") @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_configure_one_passes_target_cmake_definitions(self, run, clear): context = self.context() context.default_target.cmake_definitions = { "SAMPLE_WITH_WEB": True, "SAMPLE_MODE": "dev", "SAMPLE_LIMIT": 2, } status = modulus.cli.configure_one(context, context.default_target, update=False) self.assertEqual(status, 0) cmake_command = run.call_args_list[1].args[1] self.assertIn("-DSAMPLE_WITH_WEB=ON", cmake_command) self.assertIn("-DSAMPLE_MODE=dev", cmake_command) self.assertIn("-DSAMPLE_LIMIT=2", cmake_command) clear.assert_called_once() @unittest.mock.patch.object(modulus.cli, "configure_lsp_database", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_one", return_value=0) @unittest.mock.patch.object(modulus.cli, "configure_conan_remote", return_value=0) @unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True) def test_lsp_configures_default_target_and_extra_databases(self, docker, configure_remote, configure_one, configure_lsp): context = self.context() database = { "name": "pico", "source_dir": ".", "build_dir": "build/build_pico_debug", "generator": "Ninja", "cmake_definitions": { "VAMP_PICO_FIRMWARE_PROJECT": True, }, "env": {}, "unset_env": ["CC", "CXX"], } context.lsp_settings = { "enabled": True, "compile_databases": [database], } status = modulus.cli.lsp(context, []) self.assertEqual(status, 0) configure_remote.assert_called_once_with(context) configure_one.assert_called_once_with(context, context.default_target, False, no_remote=False) configure_lsp.assert_called_once_with(context, database) docker.assert_called_once() @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_configure_lsp_database_passes_cmake_definitions_and_unsets_env(self, run): with tempfile.TemporaryDirectory() as repo_dir: repo = pathlib.Path(repo_dir) context = self.context(repo_root=repo) status = modulus.cli.configure_lsp_database( context, { "name": "pico", "source_dir": ".", "build_dir": "build/build_pico_debug", "generator": "Ninja", "cmake_definitions": { "VAMP_PICO_FIRMWARE_PROJECT": True, }, "env": { "PICO_SDK_PATH": "/opt/pico-sdk", }, "unset_env": ["CC", "CXX"], }, ) self.assertEqual(status, 0) command = run.call_args.args[1] self.assertIn("-DVAMP_PICO_FIRMWARE_PROJECT=ON", command) self.assertEqual(run.call_args.kwargs["env_overrides"], {"PICO_SDK_PATH": "/opt/pico-sdk"}) self.assertEqual(run.call_args.kwargs["env_unset"], ["CC", "CXX"]) @unittest.mock.patch.object(modulus.cli, "clear_conan_deps") @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_configure_one_allows_public_repos_to_build_missing_dependencies(self, run, clear): context = self.context() status = modulus.cli.configure_one(context, context.default_target, update=False) self.assertEqual(status, 0) conan_command = run.call_args_list[0].args[1] self.assertIn("--build=missing", conan_command) clear.assert_called_once() @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_create_package_passes_no_remote_to_conan_create(self, run): with tempfile.TemporaryDirectory() as repo_dir: context = self.context(repo_root=pathlib.Path(repo_dir)) status = modulus.cli.create_package(context, context.default_target, update=False, no_remote=True) self.assertEqual(status, 0) self.assertIn("&:with_tests=False", run.call_args.args[1]) self.assertIn("--build=sample/*", run.call_args.args[1]) self.assertIn("--build=missing", run.call_args.args[1]) self.assertIn("--no-remote", run.call_args.args[1]) @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_create_package_allows_public_repos_to_build_missing_dependencies(self, run): with tempfile.TemporaryDirectory() as repo_dir: context = self.context(repo_root=pathlib.Path(repo_dir)) status = modulus.cli.create_package(context, context.default_target, update=False) self.assertEqual(status, 0) self.assertIn("--build=sample/*", run.call_args.args[1]) self.assertIn("--build=missing", run.call_args.args[1]) def test_create_artifact_package_installs_archives_and_checksums_tree(self): with tempfile.TemporaryDirectory() as repo_dir: repo = pathlib.Path(repo_dir) context = self.context(repo_root=repo) context.package["artifact"].update({ "enabled": True, "targets": ["native"], "install_prefix": "usr", "cmake_definitions": { "SAMPLE_WITH_WEB": True, }, "conan_options": { "with_appliance_adapters": True, }, }) def fake_run(run_context, command, **kwargs): if command[:2] == ["cmake", "--install"]: install_root = modulus.cli.package_artifact_build_root(context, context.default_target) / "stage" / "usr" (install_root / "bin").mkdir(parents=True) (install_root / "bin" / "sample").write_text("binary\n", encoding="utf-8") return 0 with unittest.mock.patch.object(modulus.cli, "run", side_effect=fake_run) as run: status, artifacts = modulus.cli.create_artifact_package( context, context.default_target, "abc123", update=False, no_remote=True, ) self.assertEqual(status, 0) archive = repo / "build" / "packages" / "sample-abc123-native.tar.gz" checksum = archive.with_name(archive.name + ".sha256") self.assertEqual(artifacts, [archive, checksum]) self.assertTrue(archive.is_file()) self.assertTrue(checksum.is_file()) with tarfile.open(archive, "r:gz") as input_archive: self.assertIn("usr/bin/sample", input_archive.getnames()) self.assertIn(archive.name, checksum.read_text(encoding="utf-8")) conan_command = run.call_args_list[0].args[1] self.assertEqual(conan_command[0:3], ["conan", "install", "."]) self.assertIn("&:with_appliance_adapters=True", conan_command) self.assertIn("&:with_tests=False", conan_command) self.assertIn("--no-remote", conan_command) self.assertIn("--build=missing", conan_command) cmake_command = run.call_args_list[1].args[1] self.assertIn("-DSAMPLE_WITH_WEB=ON", cmake_command) self.assertIn(f"-DCMAKE_INSTALL_PREFIX={repo / 'build' / 'build_common' / 'artifacts' / 'native' / 'stage' / 'usr'}", cmake_command) @unittest.mock.patch.object(modulus.cli, "run", return_value=0) def test_generic_artifact_upload_uses_package_registry_credentials(self, run): context = self.context() archive = pathlib.Path("build/packages/sample-abc123-native.tar.gz") with unittest.mock.patch.dict( os.environ, { "PACKAGE_REGISTRY_USER": "dev", "PACKAGE_REGISTRY_TOKEN": "secret", }, clear=True, ): status = modulus.cli.upload_generic_artifact(context, "abc123", archive) self.assertEqual(status, 0) self.assertEqual( run.call_args.args[1], [ "curl", "--fail", "--silent", "--show-error", "--user", "dev:secret", "--upload-file", archive, "https://example.test/api/packages/acme/generic/sample/abc123/sample-abc123-native.tar.gz", ], ) self.assertEqual( run.call_args.kwargs["display_command"][5], "dev:", ) if __name__ == "__main__": unittest.main()