Source code for tests.fake_manager
# 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.
"""In-memory package manager for CLI plumbing tests.
Real-manager iteration leaks the host environment into the test suite: a
runner without `apk` skips it, a runner without `brew` skips it, and
assertions about package counts or table rendering become a function of which
binaries happen to be on PATH. The {class}`FakeManager` below sidesteps
that by reporting as available on every platform and yielding a fixed catalog
of packages without ever invoking a subprocess.
Tests opt in via the `fake_pool` fixture in {mod}`tests.conftest`, which
monkeypatches {meth}`meta_package_manager.pool.ManagerPool.select_managers`
to yield a single fake instead of the real-manager iteration.
"""
from __future__ import annotations
import sys
from functools import cached_property
from pathlib import Path
from extra_platforms import ALL_PLATFORMS
from meta_package_manager.manager import PackageManager
from meta_package_manager.version import parse_version
TYPE_CHECKING = False
if TYPE_CHECKING:
from collections.abc import Iterator
from meta_package_manager.package import Package
from meta_package_manager.version import TokenizedString
[docs]
class FakeManager(PackageManager):
"""Always-available manager with deterministic outputs.
Reports as supported on every platform and short-circuits the
discovery properties ({attr}`cli_path`, {attr}`executable`,
{attr}`fresh`, {attr}`version`) so the pool never tries to
introspect a real binary. Subcommand methods yield fixed package
sets so tests can assert on counts and ordering.
"""
homepage_url = "https://example.invalid/fake-manager"
platforms = ALL_PLATFORMS
cli_names = ("fake-mpm",)
requirement = ">=0"
@cached_property
def cli_path(self) -> Path:
return Path(sys.executable)
@cached_property
def executable(self) -> bool:
return True
@cached_property
def fresh(self) -> bool:
return True
@cached_property
def version(self) -> TokenizedString | None:
return parse_version("1.0.0")
@property
def installed(self) -> Iterator[Package]:
yield self.package(id="fake-pkg-alpha", installed_version="1.0.0")
yield self.package(id="fake-pkg-beta", installed_version="2.5.3")
@property
def outdated(self) -> Iterator[Package]:
yield self.package(
id="fake-pkg-alpha",
installed_version="1.0.0",
latest_version="1.1.0",
)
@property
def orphans(self) -> Iterator[Package]:
yield self.package(id="fake-orphan-alpha", installed_version="0.9.1")
[docs]
def search(
self,
query: str,
extended: bool,
exact: bool,
) -> Iterator[Package]:
if "match" in query:
yield self.package(id=f"matched-{query}", latest_version="1.0.0")
[docs]
def upgrade_all_cli(self) -> tuple[str, ...]:
"""Static upgrade CLIs declare the upgrade capabilities.
Never invoked by
{meth}`meta_package_manager.bar_plugin_renderer.BarPluginRenderer.add_upgrade_cli`,
which routes menu actions through `mpm` itself, but their presence is what
makes {func}`meta_package_manager.capabilities.implements` report the
`upgrade` and `upgrade_all` operations as supported, so the fake manager's
menu lines carry an action.
"""
return (str(self.cli_path), "upgrade", "--all")
[docs]
def upgrade_one_cli(
self,
package_id: str,
version: str | None = None,
) -> tuple[str, ...]:
return (str(self.cli_path), "upgrade", package_id)
[docs]
class TimingOutFakeManager(FakeManager):
"""Variant that runs a real subprocess long enough to trip `--timeout`.
Used by {func}`tests.test_cli.test_timeout` to exercise the
{exc}`subprocess.TimeoutExpired` branch in
{meth}`meta_package_manager.execution.CLIExecutor.run`. The Python
interpreter is invoked as the manager's CLI so the test stays
cross-platform; the sleep duration is derived from
{attr}`~meta_package_manager.execution.CLIExecutor.timeout` so the call is
guaranteed to overshoot.
"""
@property
def outdated(self) -> Iterator[Package]:
sleep_for = max((self.timeout or 1) * 10, 5)
self.run_cli("-c", f"import time; time.sleep({sleep_for})")
return iter(())