Package
publish / publish (push) Successful in 10m29s

This commit is contained in:
khodowany
2026-09-30 19:45:17 -05:00
parent 83232175e3
commit 3efbe5020a
28 changed files with 641 additions and 188 deletions
+6 -6
View File
@@ -1,20 +1,20 @@
add_executable(modulus-test
add_executable(modulus_test
test.cpp
)
target_include_directories(modulus-test PRIVATE
target_include_directories(modulus_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(modulus-test PRIVATE
target_link_libraries(modulus_test PRIVATE
modulus::modulus
)
target_compile_features(modulus-test PRIVATE cxx_std_23)
target_compile_definitions(modulus-test PRIVATE
target_compile_features(modulus_test PRIVATE cxx_std_23)
target_compile_definitions(modulus_test PRIVATE
$<$<CONFIG:Debug>:MODULUS_EXPECT_DEBUG_ASSERTS>
$<$<CONFIG:Release>:MODULUS_EXPECT_NDEBUG>
$<$<CONFIG:RelWithDebInfo>:MODULUS_EXPECT_NDEBUG>
$<$<CONFIG:MinSizeRel>:MODULUS_EXPECT_NDEBUG>
)
add_test(NAME modulus COMMAND modulus-test)
add_test(NAME modulus COMMAND modulus_test)
set_tests_properties(modulus PROPERTIES TIMEOUT 20)
add_custom_target(check
COMMAND ${CMAKE_CTEST_COMMAND}
+56
View File
@@ -0,0 +1,56 @@
import os
import pathlib
import tempfile
import unittest
import unittest.mock
import urllib.error
import modulus.artifacts
import modulus.cli
import modulus.config
class ArtifactTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.root = pathlib.Path(self.temporary.name)
config = modulus.config.normalize({'package': {'name': 'example', 'artifact': {'enabled': True, 'targets': ['arm_64_gcc12_release']}}})
self.context = modulus.cli.build_context(self.root, config)
self.directory = self.root / 'build/packages'
self.directory.mkdir(parents=True)
self.archive = self.directory / modulus.artifacts.archive_name('example', 'arm_64_gcc12_release')
self.archive.write_bytes(b'package')
self.checksum = modulus.artifacts.write_checksum(self.archive)
self.enterContext(unittest.mock.patch.dict(os.environ, {'PACKAGE_REGISTRY_USER': 'test', 'PACKAGE_REGISTRY_TOKEN': 'secret'}))
def test_discovery_uses_the_same_names_as_creation(self):
self.assertEqual(self.archive.name, 'example_arm_64_gcc12_release.tar.gz')
self.assertEqual(modulus.artifacts.discover_artifacts(self.directory, 'example', ['arm_64_gcc12_release']), [self.archive, self.checksum])
def test_missing_or_invalid_checksums_never_touch_registry(self):
with unittest.mock.patch.object(modulus.artifacts, 'request') as request:
self.archive.write_bytes(b'corrupt')
with self.assertRaisesRegex(SystemExit, 'checksum mismatch'):
modulus.artifacts.main(self.context, [])
self.checksum.unlink()
with self.assertRaisesRegex(SystemExit, 'missing artifact'):
modulus.artifacts.main(self.context, [])
request.assert_not_called()
def test_replacement_and_cleanup_are_ordered_after_validation(self):
events = []
with unittest.mock.patch.object(modulus.artifacts, 'list_versions', return_value=['latest', 'old']), \
unittest.mock.patch.object(modulus.artifacts, 'delete_version', side_effect=lambda *args: events.append(('delete', args[-2]))), \
unittest.mock.patch.object(modulus.artifacts, 'upload_file', side_effect=lambda *args: events.append(('upload', args[2].name))):
self.assertEqual(modulus.artifacts.main(self.context, []), 0)
self.assertEqual(events, [('delete', 'latest'), ('upload', self.archive.name), ('upload', self.checksum.name), ('delete', 'old')])
def test_failed_upload_does_not_delete_other_slots(self):
with unittest.mock.patch.object(modulus.artifacts, 'list_versions', return_value=['latest', 'old']), \
unittest.mock.patch.object(modulus.artifacts, 'delete_version') as delete, \
unittest.mock.patch.object(modulus.artifacts, 'upload_file', side_effect=SystemExit('upload failed')):
with self.assertRaisesRegex(SystemExit, 'upload failed'):
modulus.artifacts.main(self.context, [])
self.assertEqual([call.args[-2] for call in delete.call_args_list], ['latest'])
def test_request_errors_and_credentials_are_not_logged(self):
for code in (401, 500):
with unittest.mock.patch.object(modulus.artifacts.urllib.request, 'urlopen', side_effect=urllib.error.HTTPError('https://example.test', code, 'failed', {}, None)):
with self.assertRaises(SystemExit) as caught:
modulus.artifacts.request('PUT', 'https://example.test', 'secret')
self.assertNotIn('secret', str(caught.exception))
+1 -1
View File
@@ -114,7 +114,7 @@ class clangd_test(unittest.TestCase):
patch.object(subprocess, "Popen", return_value=process), contextlib.redirect_stderr(io.StringIO()):
self.assertEqual(modulus.clangd.prepare(self.context), 1)
self.assertFalse(cache.exists())
self.assertEqual(list(cache.parent.glob(".headers-*")), [])
self.assertEqual(list(cache.parent.glob(".headers_*")), [])
def test_cleanup_always_waits_for_child_even_if_docker_removal_times_out(self):
process = MagicMock()
process.wait.side_effect = [0, subprocess.TimeoutExpired("docker", 5), 0]
+1 -1
View File
@@ -138,7 +138,7 @@ class container_clangd_test(unittest.TestCase):
session.close()
def test_eof_and_termination_remove_container(self):
def containers():
return set(subprocess.check_output(["docker", "ps", "-q", "--filter", "name=modulus-clangd-"], text=True).split())
return set(subprocess.check_output(["docker", "ps", "-q", "--filter", "name=modulus_clangd_"], text=True).split())
before = containers()
for terminate in (False, True):
session = lsp_session(self.roots()[0])
+13 -54
View File
@@ -58,7 +58,7 @@ class upload_packages_test(unittest.TestCase):
"name": "sample",
},
}
return modulus.cli.build_context(repo_root or pathlib.Path(os.getcwd()), config)
return modulus.cli.build_context(repo_root or pathlib.Path(os.getcwd()), modulus.config.normalize(config))
def test_cli_import_does_not_import_conan_recipe_module(self):
repo = pathlib.Path(__file__).resolve().parents[1]
command = [
@@ -345,6 +345,17 @@ class upload_packages_test(unittest.TestCase):
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")
@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_publish_forwards_credentials_but_python_does_not(self, in_container, run, ensure_image):
context = self.context()
with unittest.mock.patch.dict(os.environ, {"PACKAGE_REGISTRY_TOKEN": "secret"}, clear=True):
modulus.cli.docker_run(context, ["tools/publish.py"])
self.assertIn("PACKAGE_REGISTRY_TOKEN", run.call_args.args[1])
self.assertNotIn("secret", run.call_args.args[1])
modulus.cli.docker_run(context, ["tools/python.py", "-m", "generator"])
self.assertNotIn("PACKAGE_REGISTRY_TOKEN", run.call_args.args[1])
def test_conan_remote_url_uses_config_in_ci(self):
with unittest.mock.patch.dict(
os.environ,
@@ -905,7 +916,7 @@ class upload_packages_test(unittest.TestCase):
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, update=False, no_remote=True)
self.assertEqual(status, 0)
archive = repo / "build" / "packages" / "sample-native.tar.gz"
archive = repo / "build" / "packages" / "sample_native.tar.gz"
checksum = archive.with_name(archive.name + ".sha256")
self.assertEqual(artifacts, [archive, checksum])
self.assertTrue(archive.is_file())
@@ -922,58 +933,6 @@ class upload_packages_test(unittest.TestCase):
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)
@unittest.mock.patch.object(modulus.cli, "run_capture")
@unittest.mock.patch.object(pathlib.Path, "is_file", return_value=True)
def test_generic_artifact_upload_uses_package_registry_credentials(self, exists, capture, run):
context = self.context()
archive = pathlib.Path("build/packages/sample-native.tar.gz")
with unittest.mock.patch.dict(
os.environ,
{
"PACKAGE_REGISTRY_USER": "dev",
"PACKAGE_REGISTRY_TOKEN": "secret",
},
clear=True,
):
for response in ("204", "404"):
capture.return_value = subprocess.CompletedProcess([], 0, stdout=response, stderr="")
self.assertEqual(modulus.cli.upload_generic_artifact(context, archive), 0)
self.assertEqual(capture.call_args.args[1], ["curl", "--silent", "--show-error", "--user", "dev:secret", "--request", "DELETE", "--output", os.devnull, "--write-out", "%{http_code}", "https://example.test/api/packages/acme/generic/sample/latest/sample-native.tar.gz"])
self.assertEqual(capture.call_args.kwargs["display_command"][4], "dev:<hidden>")
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/latest/sample-native.tar.gz",
],
)
self.assertEqual(
run.call_args.kwargs["display_command"][5],
"dev:<hidden>",
)
@unittest.mock.patch.object(modulus.cli, "package_registry_credentials", return_value=("dev", "secret"))
@unittest.mock.patch.object(pathlib.Path, "is_file", return_value=True)
@unittest.mock.patch.object(modulus.cli, "run_capture")
@unittest.mock.patch.object(modulus.cli, "run")
def test_artifact_upload_stops_if_replacement_fails(self, run, capture, exists, credentials):
for status, response in ((0, "401"), (0, "500"), (7, "000")):
with self.subTest(status=status, response=response):
capture.return_value = subprocess.CompletedProcess([], status, stdout=response, stderr="")
self.assertNotEqual(modulus.cli.upload_generic_artifact(self.context(), pathlib.Path("sample-native.tar.gz")), 0)
run.assert_not_called()
@unittest.mock.patch.object(modulus.cli, "run_capture")
def test_missing_artifact_does_not_touch_registry(self, capture):
with tempfile.TemporaryDirectory() as temporary:
self.assertEqual(modulus.cli.upload_generic_artifact(self.context(), pathlib.Path(temporary) / "missing.tar.gz"), 1)
capture.assert_not_called()
@unittest.mock.patch.object(modulus.cli, "in_docker", return_value=True)
@unittest.mock.patch.object(modulus.cli, "create_artifact_package", return_value=(0, []))
def test_artifact_command_needs_no_release_argument(self, create, docker):
+5 -5
View File
@@ -47,11 +47,11 @@ class modulus_export_test(unittest.TestCase):
(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" / "format.py").write_text("#!/usr/bin/env python3\n", encoding="utf-8")
(package / "tools" / "docker" / "format-requirements.txt").write_text("autopep8==2.3.2\n", encoding="utf-8")
(package / "tools" / "docker" / "format_requirements.txt").write_text("autopep8==2.3.2\n", encoding="utf-8")
(package / "tools" / "docker" / "Dockerfile").write_text("FROM scratch\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 / "lib" / "cmake" / "modulus" / "modulusConfig.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):
@@ -205,8 +205,8 @@ class modulus_export_test(unittest.TestCase):
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.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):
@@ -233,7 +233,7 @@ class modulus_export_test(unittest.TestCase):
self.assertTrue((runtime / "modulus" / "formatting.py").is_file())
self.assertTrue((runtime / "modulus" / "format_layout.py").is_file())
self.assertTrue((repo / "build/build_tools/format.py").is_file())
self.assertTrue((repo / "build/build_tools/docker/format-requirements.txt").is_file())
self.assertTrue((repo / "build/build_tools/docker/format_requirements.txt").is_file())
self.assertFalse((runtime / "CMakeLists.txt").exists())
self.assertFalse((runtime / "README.md").exists())
self.assertFalse((runtime / "conanfile.py").exists())
+58
View File
@@ -0,0 +1,58 @@
import pathlib
import tempfile
import unittest
import unittest.mock
import modulus.filesystem
class PublicationTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.addCleanup(self.temporary.cleanup)
self.root = pathlib.Path(self.temporary.name)
self.source, self.target = self.root / 'new', self.root / 'installed'
for path, text in ((self.source, 'new'), (self.target, 'old')):
path.mkdir()
(path / 'content').write_text(text)
def test_publish_replaces_complete_directory(self):
with modulus.filesystem.publish_directories([(self.source, self.target)]):
self.assertEqual((self.target / 'content').read_text(), 'new')
self.assertFalse(self.source.exists())
self.assertEqual(list(self.root.iterdir()), [self.target])
def test_failure_restores_previous_directory(self):
with self.assertRaisesRegex(RuntimeError, 'failed'):
with modulus.filesystem.publish_directories([(self.source, self.target)]):
raise RuntimeError('failed')
self.assertEqual((self.target / 'content').read_text(), 'old')
self.assertFalse(list(self.root.glob('.publish_*')))
def test_failed_second_replacement_rolls_back_first(self):
second = self.root / 'second'
second.mkdir()
rename = pathlib.Path.rename
def fail(path, destination):
if path == second:
raise OSError('failed')
return rename(path, destination)
with unittest.mock.patch.object(pathlib.Path, 'rename', fail), self.assertRaises(OSError):
with modulus.filesystem.publish_directories([(self.source, self.target), (second, self.root / 'last')]):
pass
self.assertEqual((self.target / 'content').read_text(), 'old')
def test_overlaps_and_symlinks_rejected_before_replacement(self):
link = self.root / 'link'
link.symlink_to(self.target, target_is_directory=True)
for target in (self.source, self.source / 'child', self.source / '..' / 'new', self.root, link, link / 'child'):
with self.subTest(target=target), self.assertRaises(ValueError):
with modulus.filesystem.publish_directories([(self.source, target)]):
pass
self.assertEqual((self.target / 'content').read_text(), 'old')
def test_failed_rollback_retains_the_previous_directory_for_recovery(self):
rename = pathlib.Path.rename
def fail(path, destination):
if path.name == 'previous':
raise OSError('restore failed')
return rename(path, destination)
with unittest.mock.patch.object(pathlib.Path, 'rename', fail), self.assertRaisesRegex(RuntimeError, 'recovery directories retained'):
with modulus.filesystem.publish_directories([(self.source, self.target)]):
raise RuntimeError('publication failed')
backup = next(self.root.glob('.publish_*/previous/content'))
self.assertEqual(backup.read_text(), 'old')
+59
View File
@@ -0,0 +1,59 @@
import pathlib
import subprocess
import sys
import tempfile
import unittest
import unittest.mock
import modulus.cli
import modulus.config
import modulus.python
class PythonTests(unittest.TestCase):
def context(self, root, groups):
return modulus.cli.build_context(root, modulus.config.normalize({'package': {'name': 'example'}, 'tooling': {'python': {'groups': groups}}}))
def test_optional_packages_are_not_needed_by_default(self):
with tempfile.TemporaryDirectory() as temporary, unittest.mock.patch.object(subprocess, 'run') as run:
context = self.context(pathlib.Path(temporary), [])
self.assertEqual(modulus.python.interpreter(context, install=True), pathlib.Path(sys.executable))
run.assert_not_called()
def test_conan_reuses_the_project_environment_and_preserves_offline_mode(self):
with tempfile.TemporaryDirectory() as temporary:
context = self.context(pathlib.Path(temporary), ['assets'])
environment = modulus.cli.subprocess_environment(context, ['conan', 'install', '.', '--no-remote'])
self.assertEqual(environment['MODULUS_PYTHON_COMMON_DIR'], str(context.common_dir))
self.assertEqual(environment['MODULUS_PYTHON_OFFLINE'], '1')
environment = modulus.cli.subprocess_environment(context, ['conan', 'install', '.'])
self.assertEqual(environment['MODULUS_PYTHON_OFFLINE'], '0')
def test_missing_dependencies_require_configure_and_never_download_during_build(self):
with tempfile.TemporaryDirectory() as temporary, unittest.mock.patch.object(subprocess, 'run') as run:
context = self.context(pathlib.Path(temporary), ['assets'])
for options in ({}, {'install': True, 'offline': True}):
with self.assertRaisesRegex(RuntimeError, 'configure'):
modulus.python.interpreter(context, **options)
run.assert_not_called()
def test_unknown_or_duplicate_groups_are_rejected(self):
for groups in (['unknown'], ['assets', 'assets'], 'assets'):
with self.assertRaises(modulus.config.config_error):
self.context(pathlib.Path('/tmp'), groups)
def test_matching_environment_is_reused_and_changed_pins_require_configure(self):
with tempfile.TemporaryDirectory() as temporary, unittest.mock.patch.object(subprocess, 'run') as run:
context = self.context(pathlib.Path(temporary), ['assets'])
executable = context.common_dir / 'python/bin/python3'
executable.parent.mkdir(parents=True)
executable.touch()
marker = executable.parents[1] / 'requirements.sha256'
marker.write_text(modulus.python.identity(modulus.python.requirement_files(context)))
self.assertEqual(modulus.python.interpreter(context, offline=True), executable)
with unittest.mock.patch.object(modulus.python, 'identity', return_value='changed'):
with self.assertRaisesRegex(RuntimeError, 'configure'):
modulus.python.interpreter(context)
run.assert_not_called()
def test_runner_imports_exported_package_even_with_bootstrap_in_working_directory(self):
source = pathlib.Path(__file__).resolve().parents[1]
with tempfile.TemporaryDirectory() as temporary:
root = pathlib.Path(temporary)
(root / 'modulus.py').write_text("raise AssertionError('bootstrap shadowed package')")
result = subprocess.run([sys.executable, str(source / 'tools/python_runner.py'), str(source), '-c', 'import modulus.filesystem; print("loaded")'], cwd=root, text=True, capture_output=True)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), 'loaded')