From a5a7701a816ecba83811689cb2d7a01d77661684 Mon Sep 17 00:00:00 2001 From: Joonyoung Cho Date: Mon, 31 Aug 2026 13:36:48 -0700 Subject: [PATCH] fix(cli): register the integration commands on the installed entry point Signed-off-by: Joonyoung Cho --- ebuild/__main__.py | 6 +- ebuild/cli/commands.py | 16 ++++ .../test_integration_commands_registered.py | 82 +++++++++++++++++++ 3 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 tests/unit/test_integration_commands_registered.py 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 3c4fed9e..75a1eefd 100644 --- a/ebuild/cli/commands.py +++ b/ebuild/cli/commands.py @@ -28,6 +28,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 @@ -2408,3 +2409,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" + )