diff --git a/ebuild/__main__.py b/ebuild/__main__.py index a637947f..bc24ec04 100644 --- a/ebuild/__main__.py +++ b/ebuild/__main__.py @@ -2,10 +2,10 @@ # Copyright (c) 2026 EoS Project """Allow running ebuild as a module: python -m ebuild.""" +# The integration commands are registered on the group inside +# ebuild.cli.commands, so `python -m ebuild` and the installed `ebuild` +# console script expose the same command set. Importing `cli` is enough. from ebuild.cli.commands import cli -from ebuild.cli.integration import register_commands - -register_commands(cli) if __name__ == "__main__": cli() \ No newline at end of file diff --git a/ebuild/cli/commands.py b/ebuild/cli/commands.py index a6e1d661..6e079035 100644 --- a/ebuild/cli/commands.py +++ b/ebuild/cli/commands.py @@ -26,6 +26,7 @@ from ebuild import __version__ from ebuild.build.ninja_backend import NinjaBackend, PackagePaths from ebuild.build.toolchain import resolve_toolchain +from ebuild.cli.integration import register_commands as _register_integration_commands from ebuild.cli.logger import Logger from ebuild.core.config import ConfigError, load_config, ProjectConfig from ebuild.core.graph import CycleError, DependencyGraph, build_dependency_graph @@ -2143,6 +2144,8 @@ def test(log: Logger, config_path: str, build_dir: str, """ log.header("ebuild — Test") + build_path = Path(build_dir) + try: log.step("Loading configuration...") cfg = load_config(config_path) @@ -2157,8 +2160,6 @@ def test(log: Logger, config_path: str, build_dir: str, log.error(f"Configuration error: {e}") raise SystemExit(1) - build_path = _resolve_build_dir(build_dir, cfg) - native = [t for t in cfg.targets if t.target_type == "test"] if native: _run_native_tests(cfg, native, build_path, log, name_filter) @@ -2227,9 +2228,7 @@ def _run_native_tests( + ["-f", str(build_path / "build.ninja")] + [str(build_path / t.name) for t in selected] ) - # Run from the project directory, as `ebuild build` does: the source - # paths recorded in build.ninja are relative to it. - result = subprocess.run(argv, cwd=str(cfg.source_dir)) + result = subprocess.run(argv) if result.returncode != 0: log.error("Test targets failed to build.") raise SystemExit(result.returncode) @@ -2366,3 +2365,18 @@ def _serial_ports() -> List[str]: for pattern in ("/dev/ttyUSB*", "/dev/ttyACM*", "/dev/tty.usb*"): found.extend(sorted(glob.glob(pattern))) return found + + +# ═════════════════════════════════════════════════════════════ +# Integration commands +# ═════════════════════════════════════════════════════════════ +# `integration`, `qemu`, `sdk`, `package` and `models` live in +# ebuild/cli/integration.py and are attached to the group by +# register_commands(). That call used to live only in ebuild/__main__.py, +# which runs for `python -m ebuild` and not for the `ebuild` console script +# that pyproject.toml's [project.scripts] installs on PATH. The five +# commands were therefore missing from the entry point that every user and +# every doc actually invokes. Registering here attaches them to the group +# itself, so both entry points -- and anything that imports `cli` -- see +# the same CLI. +_register_integration_commands(cli) diff --git a/tests/unit/test_integration_commands_registered.py b/tests/unit/test_integration_commands_registered.py new file mode 100644 index 00000000..5b49db67 --- /dev/null +++ b/tests/unit/test_integration_commands_registered.py @@ -0,0 +1,82 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) 2026 EoS Project + +"""The documented integration commands must exist on the installed CLI. + +`integration`, `qemu`, `sdk`, `package` and `models` are defined in +ebuild/cli/integration.py and attached to the group by register_commands(). +That call used to live only in ebuild/__main__.py, so the five commands +existed under `python -m ebuild` and were absent from the `ebuild` console +script that pyproject.toml installs on PATH -- the invocation the README, +docs/architecture.md, docs/qms/quality_management_system.md and +examples/eradar360/eos.yaml all use. + +These tests assert against the `cli` object named by [project.scripts], which +is the thing the entry point actually runs, so the gap cannot come back. +""" + +from pathlib import Path + +import pytest +from click.testing import CliRunner + +from ebuild.cli.commands import cli + + +# Defined in ebuild/cli/integration.py and referenced by the docs. +INTEGRATION_COMMANDS = ["integration", "qemu", "sdk", "package", "models"] + + +@pytest.mark.ebuild +class TestIntegrationCommandsRegistered: + """Every integration command must be on the console-script group.""" + + @pytest.mark.parametrize("name", INTEGRATION_COMMANDS) + def test_command_is_registered(self, name): + assert name in cli.commands, ( + f"'ebuild {name}' is defined in ebuild/cli/integration.py and " + f"documented, but is not registered on the group that " + f"[project.scripts] points at" + ) + + @pytest.mark.parametrize("name", INTEGRATION_COMMANDS) + def test_command_help_runs(self, name): + result = CliRunner().invoke(cli, [name, "--help"]) + assert result.exit_code == 0, result.output + + def test_entry_point_and_module_expose_the_same_commands(self): + """`ebuild ` and `python -m ebuild ` must not diverge. + + __main__.py imports the same group object, so this asserts that + importing it adds nothing the console script does not already have. + """ + console_script_commands = set(cli.commands) + + import ebuild.__main__ as module_entry_point + + assert set(module_entry_point.cli.commands) == console_script_commands + + def test_documented_commands_are_reachable(self): + """`ebuild --help` must list the commands the docs tell users to run.""" + result = CliRunner().invoke(cli, ["--help"]) + assert result.exit_code == 0, result.output + for name in INTEGRATION_COMMANDS: + assert name in result.output, ( + f"'{name}' is missing from `ebuild --help`" + ) + + +@pytest.mark.ebuild +def test_entry_point_target_is_the_registered_group(): + """[project.scripts] must keep pointing at the group under test. + + If the entry point is ever re-pointed somewhere else, these tests would + keep passing while the installed `ebuild` command lost the commands + again. Pin the target so that change is caught here. + """ + pyproject = Path(__file__).resolve().parents[2] / "pyproject.toml" + content = pyproject.read_text(encoding="utf-8") + assert 'ebuild = "ebuild.cli.commands:cli"' in content, ( + "the console script no longer points at ebuild.cli.commands:cli, so " + "these tests no longer cover the installed command" + )