Source code for tests.test_cli_install_remove

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

from __future__ import annotations

import re

import pytest

from meta_package_manager.pool import pool

from .conftest import (
    SHORT_FAILURE_TIMEOUT,
    install_remove_blocked,
    maintained_manager_ids_and_dummy_package,
)
from .test_cli import CLISubCommandTests


[docs] @pytest.fixture def subcmd(): return "install", "arrow"
[docs] class TestInstallRemove(CLISubCommandTests): """Install and remove operations are siblings and sensible, so we regroup them under the same test suite. ```{tip} Where we can, we invoke the `install` subcommand to install `mpm` with itself, so we can [test externally contributed packaging](https://github.com/kdeldycke/meta-package-manager/issues/527). ``` """
[docs] @staticmethod def evaluate_signals(mid, stdout, stderr): yield from ( # install announces the managers it will try, in priority order. bool( re.search( rf"Installation priority:.*\b{mid}\b", stderr, ), ), # remove dispatches to each manager that has the package installed. bool( re.search( rf"Remove \S+ with\b.*\b{mid}\b", stderr, ), ), )
[docs] @pytest.mark.parametrize("operation", ("install", "remove")) def test_no_package_id(self, invoke, operation): result = invoke(operation) assert result.exit_code == 2 assert not result.stdout assert "Error: Missing argument 'PACKAGES_SPECS...'." in result.stderr
[docs] def test_install_unresolved_exits_nonzero(self, invoke, fake_pool): """install exits non-zero when no manager can provide the requested package. Guards against a regression where a failed install was swallowed and the command still exited `0` (see the `install` command in `cli.py`). """ result = invoke("install", "package-provided-by-no-manager") assert result.exit_code == 1
[docs] @pytest.mark.parametrize("flag", ("--zero-exit", "-0")) def test_install_unresolved_zero_exit_opt_out(self, invoke, fake_pool, flag): """-0/--zero-exit keeps the exit code at 0 on per-package failures; the critical summary stays as the durable record.""" result = invoke(flag, "install", "package-provided-by-no-manager") assert result.exit_code == 0 assert "Could not install: package-provided-by-no-manager." in result.stderr
[docs] def test_remove_absent_package_is_idempotent(self, invoke, fake_pool): """Removing a package no manager has installed is a no-op, not a failure. `remove` only fails when a manager that *has* the package fails to remove it. """ result = invoke("remove", "package-installed-by-no-manager") assert result.exit_code == 0
[docs] @pytest.mark.destructive() @maintained_manager_ids_and_dummy_package def test_single_manager_install_and_remove(self, invoke, manager_id, package_id): """Test the installation and removal of a package with each manager. ```{caution} `strict_selection_match` is set to `False` because the `install` subcommand will not try all managers selected by default. So a strict match is not possible. That's because `install` subcommand try each user-selected manager until it find one providing the package we seek to install, after which the process stop. ``` ```{note} Managers that cannot complete a real install in a given environment are not skipped: they are listed in `INSTALL_REMOVE_BLOCKED_WHEN` (`conftest.py`) and driven anyway. The blocked `install` is capped at `SHORT_FAILURE_TIMEOUT` and asserted to fail the stable mpm way (exit `1` plus a ``Could not install:` message). It runs at `--verbosity INFO``: the minimum level at which the `Installation priority` dispatch signal is visible (the default `WARNING` would hide it and make {meth}`check_manager_selection` pass vacuously). No follow-up `remove`: the failed install left nothing to remove, and the working managers already cover the removal path. ``` """ if install_remove_blocked(manager_id): # This manager cannot complete a real install here. Drive the doomed install # anyway, capped at SHORT_FAILURE_TIMEOUT, and assert it fails the stable mpm # way. No remove leg: the failed install left nothing to remove, and the # working managers below already cover the removal path. result = invoke( f"--{manager_id}", "--verbosity", "INFO", "--timeout", str(SHORT_FAILURE_TIMEOUT), "install", package_id, ) # The manager is not available on this host: mpm selected nothing. This # supersedes the blocker expectation, which only applies once the manager is # present and reaches its (doomed) install. if result.exit_code == 2: self.assert_no_manager_selected(result) return assert result.exit_code == 1 assert f"Could not install: {package_id}" in result.stderr, ( f"{manager_id} install: expected a 'Could not install:' failure in " f"stderr, got:\n{result.stderr}" ) self.check_manager_selection( result, {manager_id}, reference_set=pool.all_manager_ids, strict_selection_match=False, ) return for command in ("install", "remove"): result = invoke(f"--{manager_id}", command, package_id) # The manager is not available on this host: mpm selected nothing. if result.exit_code == 2: self.assert_no_manager_selected(result) continue # cargo, conda, npm and winget installs are flaky on the CI runners: # they depend on live registries (crates.io, the Anaconda channels, # npm, the winget community source) plus a healthy toolchain, and # regularly surface transient failures (conda's solver in particular # times out or fails to resolve on the Windows runner). Accept exit 1 # like test_single_manager_upgrade_all does for the same reason; the # check_manager_selection below still asserts mpm dispatched to the # manager. if manager_id in {"cargo", "conda", "npm", "winget"}: assert result.exit_code in (0, 1) else: assert result.exit_code == 0 self.check_manager_selection( result, {manager_id}, reference_set=pool.all_manager_ids, strict_selection_match=False, )