tests package

Submodules

tests.conftest module

Fixtures, configuration and helpers for tests.

tests.conftest.PROJECT_ROOT = PosixPath('/Users/kde/code/meta-package-manager')

Repository root, holding the committed artifacts the test_docs guards check and the .git directory whose presence marks a developer checkout.

tests.conftest.pytest_addoption(parser)[source]

Add custom command line options.

Based on Pytest’s documentation examples.

By default, runs non-destructive tests and skips destructive ones.

tests.conftest.pytest_configure(config)[source]

Register custom markers.

tests.conftest.solve_destructive_options(config)[source]

Solve the destructive options to determine which tests to run.

Return type:

tuple[bool, bool]

tests.conftest.pytest_collection_modifyitems(config, items)[source]

Apply collection-time markers and skips.

On top of the command-line destructive-test selection, this classifies the integration layer and quarantines the repo-maintenance guards, so a downstream packager can run the suite with a single selection and no per-module ignore list. See https://mpm.run/packaging/

tests.conftest.pytest_report_header(config, start_path)[source]

Display destructive options status in test report header.

Return type:

tuple[str, ...]

tests.conftest.isolate_user_config(isolated_app_dir)[source]

Hide the developer’s real mpm configuration from the test suite.

Any config.toml in the host configuration folder bleeds into every in-process CLI invocation: a local cpan = false, for instance, silently drops the manager from the default selection, so check_manager_selection assertions that expect the full default set fail locally while passing in CI, which has no such file.

Autouse alias of click-extra’s isolated_app_dir() fixture, which repoints click.get_app_dir()-based config discovery at a fresh empty directory. HOME is left intact so the integration layer keeps detecting the real package managers, and the override does not propagate to subprocesses. Tests that exercise config loading pass --config <path> explicitly, which bypasses the default search pattern and is therefore left untouched.

tests.conftest.invoke(runner)[source]
tests.conftest.stub_run_cli(monkeypatch)[source]

Replace a manager’s run_cli with a canned-output stub.

Returns a stub(manager, output) callable: every run_cli call on manager then returns output without spawning a subprocess. The workhorse of the output-parsing tests (test_manager_*). To assert on the arguments a manager builds instead, see capture_run_cli().

tests.conftest.capture_run_cli(monkeypatch)[source]

Replace a manager’s run_cli with a positional-argument recorder.

Returns a capture(manager, output="") callable, which patches the manager and hands back the list every call’s positional arguments are appended to, so a test can assert on the exact CLI the manager builds.

tests.conftest.fake_pool(monkeypatch)[source]

Yield a single deterministic FakeManager from the pool.

Use for CLI plumbing tests (stats lines, table rendering, exit codes) that need a stable package set regardless of host PATH.

tests.conftest.slow_fake_pool(monkeypatch)[source]

Yield a TimingOutFakeManager whose outdated exceeds --timeout.

Use only for tests that need to verify meta_package_manager.execution.CLIExecutor.run() catches subprocess.TimeoutExpired and logs the expected warning.

tests.conftest.subcmd()[source]

Fixture used in test_cli_*.py files to set the subcommand arguments in all CLI calls.

Must returns a string or an iterable of strings. Defaults to None, which allows tests relying on this fixture to selectively skip running.

tests.conftest.PACKAGE_IDS = {'am': 'appimageupdate', 'antidote': 'zdharma-continuum/null', 'antigen': 'zdharma-continuum/null', 'apk': 'nyancat', 'apm': 'markdown-pdf', 'apt': 'nyancat', 'apt-cyg': 'tree', 'apt-mint': 'nyancat', 'asdf': 'jq', 'basalt': 'hyperupcall/bash-args', 'bin': '/usr/local/bin/bin', 'bpkg': 'bpkg/term', 'brew': 'nyancat', 'bun': 'cowsay', 'cargo': 'fsays', 'cask': 'itsycal', 'cave': 'base/figlet', 'choco': 'hyperfine', 'chromebrew': 'sl', 'clib': 'stephenmathieson/tabs-to-spaces', 'composer': 'ralouphie/getallheaders', 'conda': 'pytz', 'cpan': 'Try::Tiny', 'deb-get': 'deb-get', 'dkp-pacman': 'gba-examples', 'dnf': 'nyancat', 'dnf5': 'nyancat', 'dotnet': 'dotnetsay', 'emerge': 'games-misc/nyancat', 'eopkg': 'sl', 'fink': 'figlet', 'fisher': 'zdharma-continuum/null', 'flatpak': 'org.gnome.Calculator', 'fwupd': 'f95c9218acd12697af946874bfe4239587209232', 'gcloud': 'kubectl', 'gem': 'paint', 'gext': 'caffeine@patapon.info', 'gh-ext': 'mislav/gh-branch', 'ghcup': 'hls-2.9.0.1', 'guix': 'hello', 'haxelib': 'hxjsonast', 'jpm': 'argparse', 'juliaup': '1.6', 'krew': 'ctx', 'lazy': 'lazy.nvim', 'luarocks': 'inspect', 'macports': 'hello', 'mamba': 'zstd', 'mas': '747648890', 'mason': 'stylua', 'micro': 'bounce', 'micromamba': 'zstd', 'miktex': 'fancyhdr', 'mise': 'jq', 'nala': 'nyancat', 'nimble': 'parsetoml', 'nix': 'hello', 'npm': 'ms', 'oh-my-fish': 'bak', 'ollama': 'tinyllama:latest', 'opam': 'zarith', 'opkg': 'lolcat', 'pacaur': 'nyancat', 'pacman': 'nyancat', 'pacstall': 'hello', 'pamac': 'nyancat', 'paru': 'nyancat', 'pikaur': 'nyancat', 'pip': 'pytz', 'pipx': 'pycowsay', 'pixi': 'hyperfine', 'pkcon': 'hello', 'pkg': 'nyancat', 'pkg-tools': 'nyancat', 'pkgin': 'nyancat', 'pnpm': 'ms', 'ports': 'net/nyancat', 'pwsh-gallery': 'Posh-Git', 'pyenv': 'pypy3.11-7.3.20', 'rustup': '1.60.0', 'scoop': 'main/hyperfine', 'sdkman': 'jbang', 'sfsu': 'main/hyperfine', 'sheldon': 'zsh-autosuggestions', 'slapt-get': 'nano', 'snap': 'hello-world', 'soar': 'bat', 'sorcery': 'figlet', 'steamcmd': '1007', 'stew': 'sharkdp/hyperfine', 'sun-tools': 'SUNWzlib', 'swupd': 'curl', 'tazpkg': 'nano', 'tlmgr': 'lipsum', 'topgrade': 'topgrade', 'trizen': 'nyancat', 'urpmi': 'figlet', 'uv': 'pytz', 'uvx': 'pycowsay', 'vagrant': 'hashicorp/bionic64', 'vcpkg': 'zlib', 'vim-pack': 'https://github.com/tpope/vim-sensible', 'volta': 'nanoid', 'vscode': 'tamasfe.even-better-toml', 'vscodium': 'tamasfe.even-better-toml', 'winget': 'sharkdp.hyperfine', 'xbps': 'sl', 'xcodes': '16.2', 'yarn': 'ms', 'yarn-berry': 'ms', 'yay': 'nyancat', 'yazi': 'yazi-rs/plugins:full-border', 'yum': 'nyancat', 'zef': 'hyperize', 'zerobrew': 'nyancat', 'zim': 'zsh-users/zsh-completions', 'zinit': 'zdharma-continuum/null', 'zplug': 'zdharma-continuum/null', 'zypper': 'nyancat'}

Package IDs used by the destructive install/remove tests, one per manager.

Each entry is fed to mpm --<manager_id> install <package_id> immediately followed by mpm --<manager_id> remove <package_id>, so the package is both added to and removed from the host running the test. Each ID is picked to keep that round-trip cheap and free of side effects:

  • Tiny and quick to install, with no dependency tree, no services or daemons, and no /etc configuration: just a self-contained binary.

  • Not a tool the OS, the manager itself, or common scripts are likely to depend on. Ubiquitous utilities (wget, curl, git, jq, …) are avoided: they are usually already installed (so the install step is a no-op) and removing them can break the host.

  • Preferably from the Rust or Go ecosystems, which rarely pull in extra dependencies.

Wherever a manager exposes general-purpose binaries the same low-impact tools are reused for consistency: nyancat (a single-file C binary packaged by nearly every Linux distro, Homebrew, FreeBSD as net/nyancat, OpenBSD and pkgsrc as misc/nyancat), GNU hello for the functional managers (Guix, Nix) and hyperfine for the Windows binary stores and stew. Distros that lack nyancat fall back to sl (Chromebrew, Solus, Void), figlet (another tiny dependency-free C binary, for Exherbo, Fink, Mageia and Source Mage), lolcat (OpenWrt) or nano (Slackware’s official tree carries none of the above, so slapt-get mirrors tazpkg). Language and application managers use the smallest inert package native to their ecosystem (ms for npm and Yarn, pycowsay for the pipx-style installers that require a console-script entry point, …).

Warning

fwupd flashes real firmware. Its ID is a release with no matching device on CI runners, where the install is therefore a no-op. Never run the destructive fwupd test on hardware that the ID actually targets.

Note

A few managers cannot offer a small binary: sdkman only ships full SDKs (jbang is its lightest candidate), and mas needs a signed App Store app (its test is skipped anyway). deb-get has no real per-package install, so it references itself, and topgrade declares no install operation at all, so its round-trip auto-skips. gh-ext references its extension by the owner/repo slug: the only id gh extension install accepts, and the id scheme its installed operation reports.

Only to be used for destructive tests.

tests.conftest.SHORT_FAILURE_TIMEOUT = 10

Seconds to cap a destructive install that is expected to fail.

The managers in INSTALL_REMOVE_BLOCKED_WHEN cannot complete a real install in the test environment. Most fail within a second (a permission error, a missing remote, an empty search), but a few (scoop and sfsu on Windows, pwsh-gallery on macOS) hang with no error until the state-change timeout would otherwise elapse. Capping their CLI calls keeps the doomed attempts cheap: long enough for the fast failures to surface, short enough that the genuine hangs do not dominate the destructive job’s wall-clock.

tests.conftest.gh_unauthenticated()[source]

Whether the gh CLI is present but holds no usable credentials.

gh refuses to run extension install unauthenticated, so the gh-ext destructive round-trip can only succeed where it resolves a token. gh auth token``probes the resolved credentials (stored logins and``GH_TOKEN-style environment variables alike) without any network call. A missing gh is not flagged: selection then finds no available manager and the test already exits on its “No manager selected” path.

Return type:

bool

tests.conftest.cpan_install_blocked()[source]

Whether cpan install cannot complete on this host.

Derived from measured CI results rather than from the permissions of Perl’s site library, which do not predict the outcome: that tree is root-owned on macOS too, yet the round-trip succeeds there. The install fails only on the x86 Linux runners; the arm64 image of the same Ubuntu release ships a Perl the unelevated user can install into, and macOS resolves a writable target of its own.

Keep this keyed on evidence. A flat is_linux was wrong on arm64, and a writability probe would have been wrong on macOS in the other direction. is_x86_64 rather than a negated ARM test, so the pair of images actually measured is what the predicate names: a third Linux architecture would be an unknown, not an assumed failure.

Return type:

bool

tests.conftest.INSTALL_REMOVE_BLOCKED_WHEN: dict[str, bool | Callable[[], bool]] = {'basalt': <functools._lru_cache_wrapper object>, 'choco': <functools._lru_cache_wrapper object>, 'cpan': <function cpan_install_blocked>, 'dnf': <functools._lru_cache_wrapper object>, 'dnf5': <functools._lru_cache_wrapper object>, 'flatpak': <functools._lru_cache_wrapper object>, 'fwupd': True, 'gcloud': <functools._lru_cache_wrapper object>, 'gh-ext': <function gh_unauthenticated>, 'mas': <functools._lru_cache_wrapper object>, 'pixi': <functools._lru_cache_wrapper object>, 'pkcon': <functools._lru_cache_wrapper object>, 'pnpm': <functools._lru_cache_wrapper object>, 'pwsh-gallery': <functools._lru_cache_wrapper object>, 'scoop': <functools._lru_cache_wrapper object>, 'sfsu': <functools._lru_cache_wrapper object>, 'snap': <function <lambda>>, 'steamcmd': True, 'yum': <functools._lru_cache_wrapper object>, 'zypper': <functools._lru_cache_wrapper object>}

Managers that cannot complete a real install, mapped to the condition under which that is true. Read an entry as “<manager> is blocked when <condition>”.

The name carries the polarity so the values do not have to: a bare is_github_ci next to a manager id reads as a statement about that manager unless the mapping says which way round it goes. The inverse shape (listing where each manager is installable) was weighed and dropped: fifteen of these seventeen entries are naturally phrased as “blocked when X”, so inverting would spell them all as negation lambdas to spare the two constants.

Rather than skip these managers (which would also drop their dispatch coverage and any signal that they still fail the expected way), the destructive install/remove test drives each one’s install anyway, caps the doomed CLI call with SHORT_FAILURE_TIMEOUT, and asserts that mpm reports the stable failure: exit code 1 and a Could not install: {package} critical message. The follow-up remove is skipped, since the failed install left nothing to remove and the working managers already cover the removal path.

A condition is either a plain True for the managers no environment can install, or a zero-argument callable evaluated per host: is_linux for the unprivileged Linux runner, is_github_ci for GitHub Actions only (a configured local box can still install), or a live probe like gh_unauthenticated when installability hinges on host state rather than platform. Resolve one through install_remove_blocked() rather than calling it directly, which is what keeps the constants from needing a lambda wrapper.

Note

The assertion deliberately targets mpm’s own Could not install: message rather than the underlying tool’s error. The raw tool output is not a stable contract: it is platform-specific (pwsh-gallery times out on macOS but hits a name-case mismatch on Linux), stage-specific (dnf/zypper/flatpak fail at the search step, never reaching their privileged install errors), and drifts across tool versions, OS images, and locales. mpm’s failure message is identical everywhere, so the test stays robust.

tests.conftest.install_remove_blocked(manager_id)[source]

Whether manager_id cannot complete a real install here.

Resolves the INSTALL_REMOVE_BLOCKED_WHEN condition, which is either a constant or a callable evaluated against the running host. A manager with no entry is not blocked.

Return type:

bool

tests.fake_manager module

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 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 tests.conftest, which monkeypatches meta_package_manager.pool.ManagerPool.select_managers() to yield a single fake instead of the real-manager iteration.

class tests.fake_manager.FakeManager[source]

Bases: PackageManager

Always-available manager with deterministic outputs.

Reports as supported on every platform and short-circuits the discovery properties (cli_path, executable, fresh, 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.

Initialize cli_errors list.

homepage_url: str | None = 'https://example.invalid/fake-manager'

Home page of the project, only used in documentation for reference.

platforms: frozenset[Platform] | Group | Platform | Iterable[Platform | Group] = frozenset({Platform(id='aix', name='IBM AIX'), Platform(id='almalinux', name='AlmaLinux'), Platform(id='alpine', name='Alpine Linux'), Platform(id='altlinux', name='ALT Linux'), Platform(id='amzn', name='Amazon Linux'), Platform(id='android', name='Android'), Platform(id='arch', name='Arch Linux'), Platform(id='buildroot', name='Buildroot'), Platform(id='cachyos', name='CachyOS'), Platform(id='centos', name='CentOS'), Platform(id='chromeos', name='ChromeOS'), Platform(id='clearlinux', name='Clear Linux OS'), Platform(id='cloudlinux', name='CloudLinux OS'), Platform(id='cygwin', name='Cygwin'), Platform(id='debian', name='Debian'), Platform(id='dragonfly_bsd', name='DragonFly BSD'), Platform(id='endeavouros', name='EndeavourOS'), Platform(id='exherbo', name='Exherbo Linux'), Platform(id='fedora', name='Fedora'), Platform(id='freebsd', name='FreeBSD'), Platform(id='generic_linux', name='Generic Linux'), Platform(id='gentoo', name='Gentoo Linux'), Platform(id='guix', name='Guix System'), Platform(id='haiku', name='Haiku'), Platform(id='hurd', name='GNU/Hurd'), Platform(id='ibm_powerkvm', name='IBM PowerKVM'), Platform(id='illumos', name='illumos'), Platform(id='kali', name='Kali Linux'), Platform(id='kvmibm', name='KVM for IBM z Systems'), Platform(id='linuxmint', name='Linux Mint'), Platform(id='macos', name='macOS'), Platform(id='mageia', name='Mageia'), Platform(id='mandriva', name='Mandriva Linux'), Platform(id='manjaro', name='Manjaro Linux'), Platform(id='midnightbsd', name='MidnightBSD'), Platform(id='netbsd', name='NetBSD'), Platform(id='nixos', name='NixOS'), Platform(id='nobara', name='Nobara'), Platform(id='openbsd', name='OpenBSD'), Platform(id='opensuse', name='openSUSE'), Platform(id='openwrt', name='OpenWrt'), Platform(id='oracle', name='Oracle Linux'), Platform(id='os400', name='IBM i'), Platform(id='parallels', name='Parallels'), Platform(id='pidora', name='Pidora'), Platform(id='pikaos', name='PikaOS'), Platform(id='raspbian', name='Raspbian'), Platform(id='rhel', name='RedHat Enterprise Linux'), Platform(id='rocky', name='Rocky Linux'), Platform(id='scientific', name='Scientific Linux'), Platform(id='slackware', name='Slackware'), Platform(id='sles', name='SUSE Linux Enterprise Server'), Platform(id='slitaz', name='SliTaz GNU/Linux'), Platform(id='solaris', name='Solaris'), Platform(id='sourcemage', name='Source Mage GNU/Linux'), Platform(id='sunos', name='SunOS'), Platform(id='tuxedo', name='Tuxedo OS'), Platform(id='ubuntu', name='Ubuntu'), Platform(id='ultramarine', name='Ultramarine'), Platform(id='void', name='Void Linux'), Platform(id='windows', name='Windows'), Platform(id='wsl1', name='Windows Subsystem for Linux v1'), Platform(id='wsl2', name='Windows Subsystem for Linux v2'), Platform(id='xenserver', name='XenServer')})

List of platforms supported by the manager.

Allows for a mishmash of platforms and groups of platforms. Will be normalized into a frozenset of Platform instances at instantiation.

cli_names: tuple[str, ...] = ('fake-mpm',)

List of CLI names the package manager is known as.

This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.

..hint::

This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

requirement: str | None = '>=0'

Version requirement specifier.

Supports a comma-separated range of constraints (e.g. ">=1.20.0,<2.0.0"). A bare version string like "1.20.0" is treated as >=1.20.0.

Parsed by meta_package_manager.version.VersionRange.

Defaults to None, which deactivates version check entirely.

property cli_path: Path[source]

Fully qualified path to the canonical package manager binary.

Try each CLI names provided by cli_names, in each system path provided by cli_search_path. In that order. Then returns the first match.

Executability of the CLI will be separately assessed later by the meta_package_manager.execution.CLIExecutor.executable property below.

property executable: bool[source]

Is the package manager CLI can be executed by the current user?

property fresh: bool[source]

Does the package manager match the version requirement?

property version: TokenizedString | None[source]

Invoke the manager and extract its own reported version string.

Returns a parsed and normalized version in the form of a meta_package_manager.version.TokenizedString instance.

Skipped on platforms where the manager is not supported, even if cli_path resolved to an executable: that binary almost certainly belongs to a different tool that happens to share the same name (e.g. GNU make on macOS getting matched by the FreeBSD ports manager), so probing it would either misreport the version or surface confusing error output.

property installed: Iterator[Package]

List packages currently installed on the system.

Optional. Will be simply skipped by mpm if not implemented.

property outdated: Iterator[Package]

List installed packages with available upgrades.

Optional. Will be simply skipped by mpm if not implemented.

property orphans: Iterator[Package]

List packages installed as dependencies that nothing requires anymore.

The read-only counterpart of the --orphans action flags: where mpm cleanup --orphans removes the orphans, this query only reports them, through the manager’s native listing (pacman --query --deps --unrequired, brew autoremove --dry-run, dnf repoquery --unneeded, …). mpm builds no dependency graph: the manager decides what is orphaned.

Optional. Will be simply skipped by mpm if not implemented.

search(query, extended, exact)[source]

Search packages available for install.

There is no need for this method to be perfect and sensitive to extended and exact parameters. If the package manager is not supporting these kind of options out of the box, just returns the closest subset of matching package you can come up with. Finer refiltering will happens in the meta_package_manager.manager.PackageManager.refiltered_search() method below.

Optional. Will be simply skipped by mpm if not implemented.

Return type:

Iterator[Package]

upgrade_all_cli()[source]

Static upgrade CLIs declare the upgrade capabilities.

Never invoked by meta_package_manager.bar_plugin_renderer.BarPluginRenderer.add_upgrade_cli(), which routes menu actions through mpm itself, but their presence is what makes 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 type:

tuple[str, ...]

upgrade_one_cli(package_id, version=None)[source]

Returns the complete CLI to upgrade one package and one only.

Allows a specific version to be provided.

Return type:

tuple[str, ...]

id: str = 'fakemanager'

Package manager’s ID.

Derived by defaults from the lower-cased class name in which underscores _ are replaced by dashes -.

This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.

name: str = 'FakeManager'

Return package manager’s common name.

Default value is based on class name.

virtual: bool = False

Should we expose the package manager to the user?

Virtual package manager are just skeleton classes used to factorize code among managers of the same family.

class tests.fake_manager.TimingOutFakeManager[source]

Bases: FakeManager

Variant that runs a real subprocess long enough to trip --timeout.

Used by tests.test_cli.test_timeout() to exercise the subprocess.TimeoutExpired branch in 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 timeout so the call is guaranteed to overshoot.

Initialize cli_errors list.

property outdated: Iterator[Package]

List installed packages with available upgrades.

Optional. Will be simply skipped by mpm if not implemented.

cli_names: tuple[str, ...] = ('timingoutfakemanager',)

List of CLI names the package manager is known as.

This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.

..hint::

This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

id: str = 'timingoutfakemanager'

Package manager’s ID.

Derived by defaults from the lower-cased class name in which underscores _ are replaced by dashes -.

This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.

name: str = 'TimingOutFakeManager'

Return package manager’s common name.

Default value is based on class name.

virtual: bool = False

Should we expose the package manager to the user?

Virtual package manager are just skeleton classes used to factorize code among managers of the same family.

tests.test_bar_plugin module

tests.test_bar_plugin.test_normalize_params(param_string, results)[source]
tests.test_bar_plugin.test_check_mpm_missing_binary()[source]

A probe whose binary does not exist must report the error, not crash.

Regression test for the UnboundLocalError on the FileNotFoundError path of check_mpm(), where process is never assigned.

tests.test_bar_plugin.test_renderer_version_diff_colors_by_appearance(monkeypatch, table_rendering, os_appearance, present, absent)[source]

Package lines carry the appearance-appropriate version-diff colors with ansi=true; the prefix gray is constant and non-package lines stay free of escape codes.

tests.test_bar_plugin.test_renderer_table_alignment_survives_ansi(monkeypatch)[source]

Column alignment is computed on visible widths, not raw string lengths.

tests.test_bar_plugin.test_renderer_sanitizes_error_lines(monkeypatch)[source]

ANSI codes captured from a manager’s output are stripped from error lines, which are marked ansi=false and would render them as raw text.

class tests.test_bar_plugin.TestBarPlugin[source]

Bases: object

common_checklist: ClassVar[list] = [('(🎁↑\\d+|📦✓)( ⚠️\\d+)? \\| dropdown=false$', True), ('-{3,5}$', True), ('(--)?🆙 Upgrade all \\S+ packages? \\| shell=\\S+( param\\d+=\\S+)+ refresh=true terminal=(true|false alternate=true)$', False), ('(--)?.+ \\| font=[Mm]enlo size=10 color=red trim=false ansi=false emojize=false( symbolize=false)?$', False)]
test_rendering(submenu_layout, table_rendering)[source]
test_plugin_shell_invocation(shell_args)[source]

Test execution of plugin on different shells.

Do not execute the complete search for outdated packages, just stop at searching for the mpm executable and extract its version.

test_python_shell_invocation(shell_args, python_args)[source]

Test any Python shell invocation is properly configured and all are compatible with plugin requirements.

pytestmark = [Mark(name='skipif', args=(<extra_platforms.pytest._DeferredCondition object>,), kwargs={'reason': 'Requires macOS'}), Mark(name='integration', args=(), kwargs={})]

tests.test_brewfile module

Unit tests of the Brewfile renderer (meta_package_manager.brewfile).

Pure-function tests over duck-typed manager stubs: no CLI invocation, no real manager pool iteration. The mpm dump command driving the renderer is exercised in tests.test_cli_dump.

tests.test_brewfile.test_quote_ascii()[source]
tests.test_brewfile.test_quote_escapes_backslash_and_quote()[source]
tests.test_brewfile.test_quote_non_ascii_passes_through()[source]
tests.test_brewfile.test_format_entry_bare()[source]
tests.test_brewfile.test_format_entry_with_scalar_int()[source]
tests.test_brewfile.test_format_entry_with_list()[source]
tests.test_brewfile.test_format_entry_with_bool()[source]
tests.test_brewfile.test_tap_from_package_id_third_party()[source]
tests.test_brewfile.test_tap_from_package_id_default_tap_returns_none()[source]
tests.test_brewfile.test_tap_from_package_id_bare_name_returns_none()[source]
tests.test_brewfile.test_format_header_lists_coverage_and_skipped()[source]
tests.test_brewfile.test_format_header_empty_coverage_marker()[source]
tests.test_brewfile.test_bundle_entry_types_ordering()[source]
tests.test_brewfile.test_build_brewfile_brew_cask_sorted_alphabetically()[source]
tests.test_brewfile.test_build_brewfile_emits_tap_lines_for_third_party_taps()[source]
tests.test_brewfile.test_build_brewfile_skips_managers_without_entry_type()[source]
tests.test_brewfile.test_build_brewfile_header_records_skipped_counts()[source]
tests.test_brewfile.test_mas_brewfile_entry_uses_name_and_numeric_id()[source]

mas Brewfile entries take the app display name + id: keyword.

tests.test_brewfile.test_mas_brewfile_entry_skips_non_numeric_id()[source]

A mas Package without a numeric adamID cannot round-trip; skip it.

tests.test_brewfile.test_mas_brewfile_entry_falls_back_to_id_when_name_missing()[source]

A mas Package with no display name uses the adamID string as the entry.

tests.test_brewfile.test_vscodium_brewfile_skip_warning_set()[source]

VSCodium’s definition declares no brewfile_entry_type so its extensions are not silently emitted as vscode (which would install to VS Code instead of VSCodium). The warning is the audible signal.

tests.test_brewfile.test_phase_2_to_4_managers_have_brewfile_entry_types()[source]

Every brew-bundle-supported manager except go and krew has a mapping. Catches regressions if a manager’s class is reorganized (or converted to a bundled definition) and the attribute is dropped.

tests.test_brewfile.test_build_brewfile_round_trip_format_is_parseable_by_ruby_inspect()[source]

The emitted lines should round-trip through Ruby String#inspect rules.

quote uses json.dumps(ensure_ascii=False) which produces double-quoted strings with backslash escapes for control chars: a subset of what Ruby’s inspect emits, accepted as a valid Ruby literal.

tests.test_brewfile.test_brew_bundle_check_parses_generated_brewfile(tmp_path)[source]

Pipe build_brewfile() output through brew bundle check to catch DSL drift early.

Uses a synthetic package set so the test runs in milliseconds rather than iterating the real manager pool. The point of the check is format validity (does brew parse what we emit?), not “does mpm see what brew sees on this host?”.

tests.test_cli module

class tests.test_cli.InspectCLIOutput[source]

Bases: object

static evaluate_signals(mid, stdout, stderr)[source]

Search in the CLI output for evidence that a manager has been retained.

..tip:

In the implementation, signals should be roughly sorted from most specific
to more forgiving.
Return type:

Iterator[bool]

check_manager_selection(result, selected=('antidote', 'antigen', 'asdf', 'basalt', 'bin', 'bpkg', 'brew', 'bun', 'cargo', 'cask', 'clib', 'composer', 'conda', 'cpan', 'dkp-pacman', 'dotnet', 'fink', 'fisher', 'gcloud', 'gem', 'gh-ext', 'ghcup', 'haxelib', 'jpm', 'juliaup', 'krew', 'lazy', 'luarocks', 'macports', 'mamba', 'mas', 'mason', 'micro', 'micromamba', 'mise', 'nimble', 'nix', 'npm', 'oh-my-fish', 'ollama', 'opam', 'pip', 'pipx', 'pixi', 'pnpm', 'pwsh-gallery', 'pyenv', 'rustup', 'sdkman', 'sheldon', 'steamcmd', 'stew', 'tlmgr', 'topgrade', 'uv', 'uvx', 'vagrant', 'vcpkg', 'vim-pack', 'vscode', 'vscodium', 'xcodes', 'yarn', 'yarn-berry', 'yazi', 'zef', 'zerobrew', 'zim', 'zinit', 'zplug'), reference_set=('antidote', 'antigen', 'asdf', 'basalt', 'bin', 'bpkg', 'brew', 'bun', 'cargo', 'cask', 'clib', 'composer', 'conda', 'cpan', 'dkp-pacman', 'dotnet', 'fink', 'fisher', 'gcloud', 'gem', 'gh-ext', 'ghcup', 'haxelib', 'jpm', 'juliaup', 'krew', 'lazy', 'luarocks', 'macports', 'mamba', 'mas', 'mason', 'micro', 'micromamba', 'mise', 'nimble', 'nix', 'npm', 'oh-my-fish', 'ollama', 'opam', 'pip', 'pipx', 'pixi', 'pnpm', 'pwsh-gallery', 'pyenv', 'rustup', 'sdkman', 'sheldon', 'steamcmd', 'stew', 'tlmgr', 'topgrade', 'uv', 'uvx', 'vagrant', 'vcpkg', 'vim-pack', 'vscode', 'vscodium', 'xcodes', 'yarn', 'yarn-berry', 'yazi', 'zef', 'zerobrew', 'zim', 'zinit', 'zplug'), strict_selection_match=True)[source]

Check that user-selected managers are found in CLI’s output.

To establish that mpm CLI is properly selecting managers, we search for signals in CLI logs, by matching regular expressions against <stdout> and <stderr>. This strategy close the gap of testing internal code testing and end user experience.

Signals are expected to be implemented for each subcommand by the evaluate_signals() method.

strict_selection_match check that all selected managers are properly reported in CLI output and none are missing.

Caution

At this stage of the CLI execution, the order in which the managers are reported doesn’t matter because:

This explain the use of set() everywhere in this method.

tests.test_cli.test_executable_module()[source]

Try running the CLI as a Python module.

Use the current Python executable so we don’t have to worry about missing dependencies.

tests.test_cli.test_timeout(invoke, slow_fake_pool)[source]

Check that timeout is handled gracefully: command exits 0 and logs a warning.

tests.test_cli.test_summary(invoke, fake_pool, summary_arg, active_summary)[source]

Test the result on all combinations of optional summary options.

tests.test_cli.managers_table_signals(mid, stdout, stderr)[source]

Signals telling whether mid shows up in the mpm managers table.

Lives at module level so both the selection tests below and the dedicated managers subcommand suite (tests.test_cli_managers) share it through downward imports, instead of one test module importing the other sideways.

Return type:

Iterator[bool]

class tests.test_cli.TestManagerSelection[source]

Bases: InspectCLIOutput

Test selection of package managers to use.

Tests are performed here on the mpm managers subcommand, as it is a safe read-only operation that is supposed to work on all platforms, whatever the environment.

There is not need to test all subcommands, as the selection logic and code path is shared by all of them. See the implementation in meta_package_manager.pool.ManagerPool.select_managers().

static evaluate_signals(mid, stdout, stderr)[source]

Search in the CLI output for evidence that a manager has been retained.

..tip:

In the implementation, signals should be roughly sorted from most specific
to more forgiving.
Return type:

Iterator[bool]

test_invalid_manager_selector(invoke, selector)[source]
test_default_all_managers(invoke)[source]

Test all available managers are selected by default.

With no selector to answer about, managers reports its default detected view, so the platform defaults whose CLI is missing are left out. The wider views are covered in tests.test_cli_managers.

test_manager_shortcuts(invoke, manager_id)[source]

Test each manager selection shortcut.

test_conf_file_overrides_defaults(invoke, create_config)[source]
test_conf_file_cli_override(invoke, create_config)[source]
test_conf_and_parameter_mix_keep_order(invoke, create_config)[source]
class tests.test_cli.TestSelectorPrecedence[source]

Bases: InspectCLIOutput

Verify how -m/--<id> (keep) and -x/--no-<id> (drop) compose and override each other for operational subcommands.

Uses installed --dry-run as the stub: it is read-only, fast (the per- manager list invocation is replaced by a printed command line), works on every platform, and crucially still honors both keep and drop selectors. The managers subcommand intentionally ignores drops for the diagnostic inventory view, so it cannot stand in for general selection-precedence tests anymore.

static evaluate_signals(mid, stdout, stderr)[source]

Detect whether mpm reached a manager during the invocation.

Available managers appear in the N package total (brew: 0, ...) stats line; the manager id is matched by its <mid>: <count> slice instead of by tailing the stream because at --verbosity DEBUG a few Reset <logger> to WARNING lines trail the stats line. Unavailable ones surface as Skipped: ... lines tagged with the manager ID in their level prefix (debug:<mid>:): that message is demoted to DEBUG for implicit selection, so test invocations pass --verbosity DEBUG to keep both signals visible.

Return type:

Iterator[bool]

test_selector_precedence(invoke, args, expected)[source]
tests.test_cli.check_packages_payload(result, optional_keys=frozenset({}), reference_set=('antidote', 'antigen', 'asdf', 'basalt', 'bin', 'bpkg', 'brew', 'bun', 'cargo', 'cask', 'clib', 'composer', 'conda', 'cpan', 'dkp-pacman', 'dotnet', 'fink', 'fisher', 'gcloud', 'gem', 'gh-ext', 'ghcup', 'haxelib', 'jpm', 'juliaup', 'krew', 'lazy', 'luarocks', 'macports', 'mamba', 'mas', 'mason', 'micro', 'micromamba', 'mise', 'nimble', 'nix', 'npm', 'oh-my-fish', 'ollama', 'opam', 'pip', 'pipx', 'pixi', 'pnpm', 'pwsh-gallery', 'pyenv', 'rustup', 'sdkman', 'sheldon', 'steamcmd', 'stew', 'tlmgr', 'topgrade', 'uv', 'uvx', 'vagrant', 'vcpkg', 'vim-pack', 'vscode', 'vscodium', 'xcodes', 'yarn', 'yarn-berry', 'yazi', 'zef', 'zerobrew', 'zim', 'zinit', 'zplug'))[source]

Validate the serialized {manager: {id, name, errors, packages}} payload.

The shared shape check of the package-reporting subcommands (installed, outdated, search) in --table-format json mode: every manager entry carries the standard keys (plus the subcommand’s optional_keys, like outdated’s upgrade_all_cli), and every package serializes a subset of the Package fields as strings.

reference_set defaults to the platform’s real default managers, but a caller pinned to fake_pool (like orphans, with no implementing manager on every platform) overrides it to match.

Return type:

None

class tests.test_cli.CLISubCommandTests[source]

Bases: InspectCLIOutput

All these tests runs on each subcommand.

This class doesn’t starts with Test as it is meant to be used as a template, inherited by sub-command specific test cases.

static assert_no_manager_selected(result)[source]

Assert the run stopped on the No manager selected. exit-2 guard.

Shared by every subcommand test that deselects all managers (or selects only managers lacking the operation) and expects the run to refuse to proceed.

Return type:

None

class tests.test_cli.CLITableTests[source]

Bases: object

Test subcommands whose output is a configurable table.

Any table output is also allowed to be rendered in all serialization formats.

columns_registry: tuple = ()

The subcommand’s column registry (a tables.py *_COLUMNS constant).

Set by each subclass so the generic --columns tests below resolve column IDs to their header labels from the same source of truth the CLI uses.

columns_test_pair: tuple[str, str] = ('manager_id', 'package_id')

Two column IDs, guaranteed by the subcommand’s registry, that the generic projection test selects in this order. Overridden by subclasses whose registry has no package columns.

test_columns_projection(invoke, subcmd, fake_pool)[source]

--columns restricts and reorders the table, SQL-SELECT-style.

test_columns_unknown_id(invoke, subcmd, fake_pool)[source]

An unknown column ID fails fast, listing the accepted ones.

test_all_table_rendering(invoke, mode)[source]
test_json_output(invoke, subcmd)[source]

JSON output is expected to be parseable if read from <stdout>.

Debug level messages are redirected to <stderr> and are not supposed to interfere with this behavior.

test_serialized_output(invoke, subcmd, fmt)[source]

All serialization formats produce parseable output on <stdout>.

Debug messages go to <stderr> and must not leak into the structured output. Formats whose optional dependency is not installed are skipped.

class tests.test_cli.CLIQueryTests[source]

Bases: object

Template for inventory subcommands taking an optional positional QUERY.

Runs against the deterministic fake_pool package set. Subclasses keep their own test_query_filter parametrize — the case data is the per-command filtering semantics — and delegate each case’s assertions to check_filtered_ids().

static check_filtered_ids(result, expected_ids)[source]

Assert the serialized payload reports exactly expected_ids.

Return type:

None

test_query_highlight(invoke, subcmd, fake_pool)[source]

The matched substring is wrapped in the theme’s green search style.

tests.test_cli_backup module

tests.test_cli_backup.subcmd()[source]
class tests.test_cli_backup.TestBackup[source]

Bases: CLISubCommandTests

static evaluate_signals(mid, stdout, stderr)[source]

Search in the CLI output for evidence that a manager has been retained.

..tip:

In the implementation, signals should be roughly sorted from most specific
to more forgiving.
test_default_all_managers_output_to_console(invoke, subcmd)[source]
test_output_to_console(invoke, subcmd)[source]
test_output_to_file(invoke, subcmd)[source]
test_output_to_file_creates_missing_parents(invoke, subcmd)[source]

A destination whose directory does not exist yet is created, not an error.

Regression: mpm used to open the target directly, so a path into a missing directory died on an unhandled FileNotFoundError. Adopting click-extra’s prep_path brought the mkdir -p with it.

test_single_manager_file_output(manager_id, invoke, subcmd)[source]

tests.test_cli_dump module

mpm dump CLI tests.

Drives the subcommand against the deterministic fake_pool. The Brewfile renderer it delegates to is unit-tested in tests.test_brewfile, and the snapshot/merge/update flows shared with the backup alias live in tests.test_cli_backup.

tests.test_cli_dump.test_dump_query_filter_narrows_to_matches(invoke, fake_pool)[source]

dump --query keeps only installed packages matching the query.

tests.test_cli_dump.test_dump_without_query_lists_all(invoke, fake_pool)[source]

Without a query, dump snapshots the full installed inventory.

tests.test_cli_install_remove module

tests.test_cli_install_remove.subcmd()[source]
class tests.test_cli_install_remove.TestInstallRemove[source]

Bases: 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.

static evaluate_signals(mid, stdout, stderr)[source]

Search in the CLI output for evidence that a manager has been retained.

..tip:

In the implementation, signals should be roughly sorted from most specific
to more forgiving.
test_no_package_id(invoke, operation)[source]
test_install_unresolved_exits_nonzero(invoke, fake_pool)[source]

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).

test_install_unresolved_zero_exit_opt_out(invoke, fake_pool, flag)[source]

-0/–zero-exit keeps the exit code at 0 on per-package failures; the critical summary stays as the durable record.

test_remove_absent_package_is_idempotent(invoke, fake_pool)[source]

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.

test_single_manager_install_and_remove(invoke, manager_id, package_id)[source]

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 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.

tests.test_cli_installed module

tests.test_cli_installed.subcmd()[source]
class tests.test_cli_installed.TestInstalled[source]

Bases: CLISubCommandTests, CLITableTests, CLIQueryTests

columns_registry: tuple = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier.", max_width=None), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name.", max_width='auto'), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the package.', max_width=None), SortableField.MANAGER_ID), (ColumnSpec(id='installed_version', label='Installed version', description='Version currently installed.', max_width='auto'), SortableField.VERSION))

The subcommand’s column registry (a tables.py *_COLUMNS constant).

Set by each subclass so the generic --columns tests below resolve column IDs to their header labels from the same source of truth the CLI uses.

test_json_parsing(invoke, subcmd)[source]
test_query_filter(invoke, fake_pool, args, expected_ids)[source]

tests.test_cli_managers module

tests.test_cli_managers.subcmd()[source]
class tests.test_cli_managers.TestManagers[source]

Bases: CLISubCommandTests, CLITableTests

columns_registry: tuple = ((ColumnSpec(id='manager_id', label='Manager ID', description="Manager's identifier.", max_width=None), SortableField.MANAGER_ID), (ColumnSpec(id='manager_name', label='Name', description="Manager's common name.", max_width=None), SortableField.MANAGER_NAME), (ColumnSpec(id='supported', label='Supported', description='Support status on the current platform.', max_width='auto'), None), (ColumnSpec(id='cli', label='CLI', description="Location of the manager's binary on the system.", max_width='auto'), None), (ColumnSpec(id='executable', label='Executable', description='Whether the binary is executable.', max_width=None), None), (ColumnSpec(id='version', label='Version', description="Manager's self-reported version, and the unsatisfied requirement when stale.", max_width=None), SortableField.VERSION))

The subcommand’s column registry (a tables.py *_COLUMNS constant).

Set by each subclass so the generic --columns tests below resolve column IDs to their header labels from the same source of truth the CLI uses.

columns_test_pair: tuple[str, str] = ('manager_id', 'version')

Two column IDs, guaranteed by the subcommand’s registry, that the generic projection test selects in this order. Overridden by subclasses whose registry has no package columns.

static evaluate_signals(mid, stdout, stderr)[source]

Search in the CLI output for evidence that a manager has been retained.

..tip:

In the implementation, signals should be roughly sorted from most specific
to more forgiving.
test_all_managers(invoke, subcmd, manager_id)[source]

Check only the selected manager is listed.

test_unsupported_managers(invoke, subcmd, manager_id)[source]

A manager named by the user is reported whatever its state: the default view widens to supported on an explicit selection, so the row spelling out why mpm cannot use it is the answer the selector asked for.

test_view_widths(invoke, subcmd, view_args, expected_ids)[source]

Each view reports its own nested slice of the pool.

test_all_managers_flag_selects_widest_view(invoke, subcmd)[source]

The global flag keeps naming the widest view, as --view all does.

test_default_view_drops_constant_columns(invoke, subcmd)[source]

Supported and Executable are a ✓ on every detected row, so the default view leaves them out and the wider ones bring them back.

test_json_parsing(invoke, subcmd)[source]
tests.test_cli_managers.test_managers_stamps_global_timeout(invoke)[source]

The version probes behind the table’s detection columns fire in the pool’s warm-up round, right after selection, so the managers subcommand must forward the global --timeout for the pool to bind it on each candidate beforehand. An unstamped instance falls back to the 120-second read-only default, letting a wedged binary hold the whole round for that long.

tests.test_cli_orphans module

tests.test_cli_orphans.subcmd()[source]
class tests.test_cli_orphans.TestOrphans[source]

Bases: CLISubCommandTests, CLITableTests, CLIQueryTests

Pin the whole class to fake_pool.

Unlike the other CLITableTests subcommands, orphans is implemented by only a subset of managers (all Linux, BSD or macOS tools), so no default-selected manager implements it on every platform: Windows has none. The inherited real-pool template tests would resolve there to No manager selected. instead of the 0 exit they assert, so the deterministic fake pool stands in on every host.

columns_registry: tuple = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier.", max_width=None), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name.", max_width='auto'), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the package.', max_width=None), SortableField.MANAGER_ID), (ColumnSpec(id='installed_version', label='Installed version', description='Version currently installed.', max_width='auto'), SortableField.VERSION))

The subcommand’s column registry (a tables.py *_COLUMNS constant).

Set by each subclass so the generic --columns tests below resolve column IDs to their header labels from the same source of truth the CLI uses.

test_json_parsing(invoke, subcmd, fake_pool)[source]
test_query_filter(invoke, fake_pool, args, expected_ids)[source]
pytestmark = [Mark(name='usefixtures', args=('fake_pool',), kwargs={})]

tests.test_cli_outdated module

tests.test_cli_outdated.subcmd()[source]
class tests.test_cli_outdated.TestOutdated[source]

Bases: CLISubCommandTests, CLITableTests, CLIQueryTests

columns_registry: tuple = ((ColumnSpec(id='package_id', label='Package ID', description="Package's identifier.", max_width=None), SortableField.PACKAGE_ID), (ColumnSpec(id='package_name', label='Name', description="Package's common name.", max_width='auto'), SortableField.PACKAGE_NAME), (ColumnSpec(id='manager_id', label='Manager', description='Manager reporting the package.', max_width=None), SortableField.MANAGER_ID), (ColumnSpec(id='installed_version', label='Installed version', description='Version currently installed.', max_width='auto'), SortableField.VERSION), (ColumnSpec(id='latest_version', label='Latest version', description='Version available for upgrade.', max_width='auto'), None))

The subcommand’s column registry (a tables.py *_COLUMNS constant).

Set by each subclass so the generic --columns tests below resolve column IDs to their header labels from the same source of truth the CLI uses.

test_json_parsing(invoke, subcmd)[source]
test_plugin_output_keeps_ansi(invoke, subcmd, fake_pool, monkeypatch)[source]

Plugin output keeps its version-diff colors on a non-TTY stream.

The bar plugin captures mpm outdated --plugin-output through a pipe, where echo’s TTY auto-detection would strip every ANSI code: the renderer forces colors so they actually reach SwiftBar and Xbar.

test_query_filter(invoke, fake_pool, args, expected_ids)[source]

tests.test_cli_restore module

tests.test_cli_restore.subcmd(create_config)[source]

Seed common subcommand tests with a dummy file and content to allow the CLI to not fail on required file input.

class tests.test_cli_restore.TestRestore[source]

Bases: CLISubCommandTests

static evaluate_signals(mid, stdout, stderr)[source]

Search in the CLI output for evidence that a manager has been retained.

..tip:

In the implementation, signals should be roughly sorted from most specific
to more forgiving.
test_default_all_managers(invoke, create_config)[source]
test_single_manager(invoke, create_config, manager_id)[source]
test_ignore_unrecognized_manager(invoke, create_config)[source]
test_restore_single_manager(invoke, create_config)[source]
test_restore_excluded_manager(invoke, create_config)[source]
test_empty_manager(invoke, create_config)[source]

tests.test_cli_sbom module

mpm sbom CLI tests.

Drives the subcommand end to end: format resolution, per-standard exports to console and file, the --query filter and --minimal mode. The SPDX and CycloneDX renderers it delegates to are unit-tested in tests.test_sbom.

tests.test_cli_sbom.subcmd()[source]
class tests.test_cli_sbom.TestSBOM[source]

Bases: CLISubCommandTests

static evaluate_signals(mid, stdout, stderr)[source]

Search in the CLI output for evidence that a manager has been retained.

..tip:

In the implementation, signals should be roughly sorted from most specific
to more forgiving.
test_default_spdx_json_output_to_console(invoke, subcmd)[source]
test_unrecognized_extension_without_format(invoke, subcmd)[source]

A target path with no recognized extension must fail before any package collection happens, with an actionable message pointing at --format.

test_unrecognized_extension_with_explicit_format(invoke, subcmd, tmp_path)[source]

An explicit --format overrides extension auto-detection, even when the filename carries no recognizable suffix.

test_output_to_file(invoke, subcmd, export_format, standard_name)[source]
tests.test_cli_sbom.test_sbom_query_filter_narrows_to_matches(invoke, fake_pool)[source]

sbom --query keeps only installed packages matching the query.

tests.test_cli_sbom.test_sbom_without_query_lists_all(invoke, fake_pool)[source]

Without a query, sbom exports the full installed inventory.

tests.test_cli_sbom.test_minimal_mode_skips_metadata_extractor(invoke)[source]

--minimal must avoid the rich-metadata code path so the export matches the historical bare shape even for managers that implement an extractor (Homebrew, pip).

tests.test_cli_sbom.test_cli_import_defers_sbom_libraries()[source]

Importing the CLI must not pull the SBOM writer libraries.

cli.py imports every subcommand module up front to register it, so a module-level import of the writers in cli_sbom would spend their (heavy) third-party import cost on every mpm invocation, for a dependency only mpm sbom ever uses. The imports therefore live inside the subcommand body, the one place in the codebase deliberately breaking the imports-at-module-level rule, and this test is what keeps them from drifting back up.

Runs in a fresh interpreter: the test session has long since imported everything, so sys.modules here proves nothing.

tests.test_cli_sbom.test_sbom_command_loads_the_deferred_libraries(invoke, fake_pool)[source]

The other half of the contract: deferring them must not break the export.

tests.test_cli_upgrade module

tests.test_cli_upgrade.failing_installed_query(monkeypatch)[source]

Break FakeManager.installed, like a misconfigured pnpm breaks its own.

tests.test_cli_upgrade.subcmd()[source]
class tests.test_cli_upgrade.TestUpgrade[source]

Bases: CLISubCommandTests

Test the system-wide upgrade sub-command.

Danger

All tests here should me marked as destructive unless –dry-run parameter is passed.

static evaluate_signals(mid, stdout, stderr)[source]

Search in the CLI output for evidence that a manager has been retained.

..tip:

In the implementation, signals should be roughly sorted from most specific
to more forgiving.
test_all_managers_dry_run_upgrade_all(invoke, all_option)[source]
test_all_managers_upgrade_all(invoke, all_option)[source]
test_single_manager_dry_run_upgrade_all(invoke, manager_id, all_option)[source]
test_single_manager_upgrade_all(invoke, manager_id, all_option)[source]
tests.test_cli_upgrade.test_installed_ids_tolerates_a_failing_cli(fake_pool, failing_installed_query, caplog)[source]

A manager whose installed CLI fails reports no IDs instead of raising.

tests.test_cli_upgrade.test_sourcing_survives_a_failing_manager(invoke, fake_pool, failing_installed_query, subcommand)[source]

tests.test_cli_which module

tests.test_cli_which.subcmd()[source]
class tests.test_cli_which.TestWhich[source]

Bases: CLISubCommandTests, CLITableTests

columns_registry: tuple = ((ColumnSpec(id='manager_id', label='Manager ID', description='Manager whose search path found the binary.', max_width=None), SortableField.MANAGER_ID), (ColumnSpec(id='priority', label='Priority', description="Rank of the match in the manager's search path.", max_width=None), None), (ColumnSpec(id='cli_path', label='CLI path', description='Location of the matched binary.', max_width=None), None), (ColumnSpec(id='symlink', label='Symlink destination', description='Resolved target when the match is a symlink.', max_width=None), None))

The subcommand’s column registry (a tables.py *_COLUMNS constant).

Set by each subclass so the generic --columns tests below resolve column IDs to their header labels from the same source of truth the CLI uses.

columns_test_pair: tuple[str, str] = ('manager_id', 'cli_path')

Two column IDs, guaranteed by the subcommand’s registry, that the generic projection test selects in this order. Overridden by subclasses whose registry has no package columns.

tests.test_cooldown module

tests.test_cooldown.test_release_anchored_cutoff_math(cooldown_input, release_iso, should_pass, monkeypatch)[source]

Verify the cooldown cutoff blocks fresh releases and lets older ones pass.

Pattern borrowed from astral-sh/uv#19475: anchor against real upstream release timestamps (idna 3.6 / 3.7 on PyPI) and a frozen ‘today’ so the arithmetic stays deterministic.

tests.test_cooldown.test_supported_managers_advertise_cooldown(manager_class, env_var)[source]
tests.test_cooldown.test_unsupported_managers_lack_cooldown(manager_class)[source]
tests.test_cooldown.test_timestamp_based_managers_inject_cutoff(manager_class)[source]

Managers whose env var expects an RFC 3339 cutoff timestamp.

tests.test_cooldown.test_npm_injects_integer_days(cooldown, expected_days)[source]

npm’s min-release-age expects an integer count of days, not a timestamp.

tests.test_cooldown.test_yay_advertises_cooldown_while_idle()[source]

While idle, yay reports its structural capability without a version probe.

The import-time COOLDOWN_SUPPORTED_MANAGERS help text reads this for every manager, so it must never shell out to yay --version.

tests.test_cooldown.test_yay_cooldown_version_gate(version, supported)[source]

yay can enforce a cooldown only from v13.0.0, when its Lua hooks landed.

tests.test_cooldown.test_yay_cooldown_undetectable_version()[source]

A yay whose version cannot be parsed cannot enforce a cooldown.

tests.test_cooldown.test_yay_cooldown_policy_gates_both_paths()[source]

The generated policy gates upgrades and installs off the same cutoff.

tests.test_cooldown.test_yay_user_config_dir(env, expected, monkeypatch)[source]

The real config dir follows yay’s own XDG_CONFIG_HOME over HOME precedence.

tests.test_cooldown.test_yay_cooldown_overlay(tmp_path, monkeypatch)[source]

An active cooldown on a recent yay builds a lossless XDG_CONFIG_HOME overlay.

tests.test_cooldown.test_yay_cooldown_overlay_without_user_config(tmp_path, monkeypatch)[source]

With no user config present, the overlay omits the config.json symlink.

tests.test_cooldown.test_yay_cooldown_overlay_survives_manager_gc(tmp_path, monkeypatch)[source]

The overlay must outlive the Yay instance’s garbage collection.

Cleanup is registered with atexit, not weakref.finalize(self, ...): yay re-reads init.lua mid-run, so a GC-tied removal could delete the overlay before yay finishes and silently fail the gate open.

tests.test_cooldown.test_yay_cooldown_epoch_clamped_to_zero(tmp_path, monkeypatch)[source]

A cooldown reaching before 1970 clamps the epoch to 0.

A negative Unix timestamp parses back to nil in yay’s gopher-lua, which silently drops the gate; epoch 0 keeps the floor effective.

tests.test_cooldown.test_yay_cooldown_no_recursion_when_version_resolved_lazily(tmp_path, monkeypatch)[source]

Resolving the version lazily under an active cooldown must not recurse.

version runs yay --version through run(), which injects cooldown_env(), which consults supports_cooldown -> version. The re-entrancy guard must break that loop. Regression for a live RecursionError the pre-seeded-version tests above could not catch.

tests.test_cooldown.test_cooldown_permits_without_cooldown()[source]
tests.test_cooldown.test_cooldown_permits_supported_manager()[source]
tests.test_cooldown.test_cooldown_permits_blocks_unsupported(caplog)[source]
tests.test_cooldown.test_cooldown_permits_allows_unsupported_on_opt_in(caplog)[source]
tests.test_cooldown.test_cli_rejects_invalid_cooldown(invoke)[source]

tests.test_dispatch module

Unit tests for the concurrent operation dispatch helpers.

These exercise meta_package_manager.dispatch.collect_from_managers(), meta_package_manager.dispatch.collect_per_package() and the meta_package_manager.dispatch.OperationTrail they share, in isolation with lightweight stand-ins for the click context and managers, so they need no real package managers and stay hermetic. The context is passed explicitly as ctx= (the helpers otherwise default to click_extra.get_current_context()).

class tests.test_dispatch.FakeContext(jobs, verbosity='INFO')[source]

Bases: object

Minimal stand-in exposing only the meta keys the helper reads.

class tests.test_dispatch.StubManager(manager_id, progress=False)[source]

Bases: object

Minimal stand-in exposing only id, progress and run_cache.

run_cache = None
class tests.test_dispatch.TTYStringIO(initial_value='', newline='\n')[source]

Bases: StringIO

An in-memory text buffer that claims to be an interactive terminal.

isatty()[source]

Return whether this is an ‘interactive’ stream.

Return False if it can’t be determined.

Return type:

bool

tests.test_dispatch.test_runs_sequentially_in_main_thread(jobs, verbosity, manager_count)[source]
tests.test_dispatch.test_runs_concurrently_off_the_main_thread()[source]
tests.test_dispatch.test_preserves_input_order_despite_completion_order()[source]

Results follow the manager order, even when later managers finish first.

tests.test_dispatch.test_suppresses_per_manager_spinners_when_concurrent()[source]

Concurrent mode mutes per-manager spinners (one aggregate stands in).

tests.test_dispatch.test_keeps_per_manager_spinners_when_sequential()[source]

Sequential mode leaves the per-manager spinner gate untouched.

tests.test_dispatch.test_empty_manager_list_returns_empty()[source]
tests.test_dispatch.test_no_finisher_line_off_terminal(capsys)[source]

Off a terminal the aggregate progress bar never draws, so no finisher leaks.

The indicator emits its kept line unconditionally, so the gate must keep it out of pipes, captured output and serialized runs.

tests.test_dispatch.test_finisher_line_when_progress_bar_shown(monkeypatch)[source]

A slow batch on a terminal shows the running count, a ✓ trail and a finisher.

tests.test_dispatch.test_failure_trail_marks_errored_managers(monkeypatch)[source]

A manager whose result carries errors gets a ✗ trail line; others get ✓.

tests.test_dispatch.test_trail_includes_managers_that_finish_before_the_indicator_shows(monkeypatch)[source]

Managers that complete within the show delay still get a trail line.

Regression: the per-manager echo was gated on the live shown state, so a manager that finished before the indicator first drew was dropped from the trail (a 6-manager batch where the quick ones beat the 1s delay showed only the 3 slow ones, above a “Checked 6 managers” finisher). Outcomes are now buffered and flushed once the indicator appears, so the ledger stays complete.

tests.test_dispatch.test_per_package_runs_sequentially_in_main_thread(jobs, verbosity, manager_count)[source]
tests.test_dispatch.test_per_package_runs_concurrently_off_the_main_thread()[source]
tests.test_dispatch.test_per_package_tasks_of_one_manager_share_a_thread()[source]

A manager’s own tasks run serially on one worker, never overlapping.

The core safety invariant: a package manager cannot run two of its own invocations at once, so each lane’s tasks stay on a single thread. Repeating the same manager instance across pairs groups its tasks into one lane.

tests.test_dispatch.test_per_package_empty_is_a_noop()[source]
tests.test_dispatch.test_per_package_finisher_when_progress_bar_shown(monkeypatch)[source]

A slow concurrent batch shows the running count, a ✓ trail and a finisher.

tests.test_dispatch.test_per_package_failure_trail_marks_failed_tasks(monkeypatch)[source]

A failed task gets a ✗ line; the finisher reports the success count.

tests.test_dispatch.test_per_package_no_finisher_off_terminal(capsys)[source]

Off a terminal nothing leaks (the trail and finisher are indicator-gated).

tests.test_dispatch.test_lock_family_members_share_one_lane_when_mutating()[source]

brew and cask (one family) run on a single worker; an outsider gets its own.

tests.test_dispatch.test_lock_family_not_serialized_for_reads()[source]

Reads take no backend lock, so family members keep separate lanes and threads.

tests.test_dispatch.test_lock_family_members_share_one_lane_in_collect_per_package()[source]

A family’s tasks serialize across its managers in the per-package fan-out too.

tests.test_dispatch.test_lock_family_lane_shares_a_run_cache()[source]

A multi-manager family lane injects one shared run_cache, cleared afterwards.

tests.test_dispatch.test_operation_trail_echoes_marks_and_finisher_on_tty(monkeypatch)[source]

On a TTY the ledger echoes a ✓/✗ line per mark, plus a finisher.

The finisher carries an elapsed time only under --time: upstream gates it on OperationTrail’s timer argument, which mpm leaves at its None default so the flag decides. It used to clock unconditionally.

tests.test_dispatch.test_operation_trail_silent_off_terminal(capsys)[source]

Off a terminal the sequential ledger stays silent.

tests.test_docs module

tests.test_docs.stars_update = <module 'stars_update' from '/Users/kde/code/meta-package-manager/docs/stars_update.py'>

Test all non-code artifacts depending on manager definitions.

Covers:
  • Documentation (sphinx, readme, etc.)

  • CI/CD scripts

  • GitHub project config

These tests are mainly there to remind us keep extra stuff in sync on new platform or manager addition.

tests.test_docs.test_unique_local_group_ids()[source]

Check our platform groups have unique identifiers.

tests.test_docs.test_local_groups_no_overlap()[source]

Check our platform groups are mutually exclusive.

tests.test_docs.test_all_platforms_covered_by_local_groups(manager)[source]

Check all platforms supported by managers are covered by a local group.

tests.test_docs.test_project_metadata()[source]
tests.test_docs.test_docs_site_url_matches_pyproject()[source]

The canonical docs origin is declared once, in pyproject.toml.

docs/conf.py reads it from there for html_baseurl, but runtime code cannot: pyproject.toml is not shipped in the wheel, so _docs.DOCS_SITE_URL repeats the literal. A drift would point the readme’s manager links at a different origin than the canonical tags, which is exactly the split-indexing the custom domain exists to avoid.

tests.test_docs.test_changelog()[source]
tests.test_docs.test_labels()[source]
tests.test_docs.test_new_package_manager_issue_template()[source]

Check all platforms groups are referenced in the issue template.

Repo-maintenance guard: the reference set is regenerated from the installed extra_platforms release, whose platform groups a downstream packager cannot be expected to match, so conftest skips it outside a git checkout. It also reads .github/, absent from a wheel install.

tests.test_docs.test_extra_labels_in_pyproject()[source]

Check the generated [tool.repomatic.labels.extra] block in pyproject.toml is consistent with the LABELS registry.

tests.test_docs.test_label_rules_reference_known_labels()[source]

Every file- and content-rule must target a label that exists in the generated [tool.repomatic.labels.extra] block.

A rule naming an unknown label silently applies a label the repository does not have: the stale dnf-based rules left behind when the labels.py group was renamed rpm-based, or per-manager rules (zypper, pacstall) outliving the manager’s absorption into an ecosystem group.

tests.test_docs.test_label_rules_in_pyproject()[source]

Check the generated [tool.repomatic.labels.*] rule blocks in pyproject.toml match a fresh generation from the pool, in the schema repomatic reads.

Drift means a manager was added without running docs/docs_update.py (repomatic’s update-docs job self-heals this on the next push).

The shape is asserted first, and separately from the content, because the two fail for unrelated reasons. repomatic 7.11.0 replaced the array-of-tables form with a label-to-patterns table and ignores the old one with a warning, which dropped all 130 of these rules without failing anything: the generator and the file still agreed with each other, in a schema the consumer had abandoned. mpm does not depend on repomatic, so the shape is all this suite can check on its own.

tests.test_docs.test_pyproject_updates_are_pyproject_fmt_fixpoint(monkeypatch, tmp_path)[source]

The pyproject.toml writers must emit pyproject-fmt-canonical style.

Any deviation makes the update-docs and format-pyproject autofix jobs endlessly rewrite each other’s output. The committed file is canonicalized first, so the assertion only judges the writers’ own output, whatever formatting state the working tree is in.

tests.test_docs.test_benchmark_toml_well_formed()[source]

Check docs/benchmark.toml only encodes flags from the known competitor set and homepage URLs for non-pool managers.

A duplicate manager-id row anywhere in the file (in any table) is a tomllib.TOMLDecodeError at load time, below, rather than a silently overwritten value: no separate assertion is needed to hold that invariant.

tests.test_docs.test_benchmark_homepages_cover_non_pool_managers()[source]

Every non-pool manager listed in benchmark.toml must have a matching homepages entry so the table can link the identifier.

Pool-implemented managers are excluded: their URL is sourced from the class’s homepage_url attribute, and a redundant entry in the TOML would create two sources of truth.

tests.test_docs.test_manager_homepage_url(manager)[source]

Every pool manager defines a non-empty homepage URL.

Sourced by the benchmark table generator to link each manager identifier to its upstream documentation. An empty or malformed URL breaks the rendered table.

tests.test_docs.test_benchmark_table_renders()[source]

Check the Package manager support table generator still produces a well-formed table from the current pool and YAML.

The table is rendered live at Sphinx build time by the {python:render} block in docs/benchmark.md, so there is no checked-in copy to compare against: this test only guards the generator against crashes and structural regressions (a broken YAML entry, a manager without a source file).

tests.test_docs.test_star_history_repos_match_benchmark()[source]

Check the tracked repositories are exactly mpm plus every competitor.

The chart and the benchmark’s own columns must never drift apart: a competitor added to the comparison without a repository to sample would silently go unplotted.

tests.test_docs.test_manager_upstreams_cover_the_pool()[source]

Check every wrapped manager is either measured upstream, or excused.

The two maps partition the pool, so a manager added without a decision on its upstream fails here instead of quietly rendering three empty cells. Both are also checked against the pool from the other side: an ID left behind by a renamed or dropped manager would otherwise sample forever.

tests.test_docs.test_manager_upstreams_store_well_formed()[source]

Check the committed upstream readings keep the shape the index expects.

Guards a second file a scheduled job rewrites unattended: an ID that left the pool, a date in the future or a negative count would all surface here rather than in a rendered table nobody re-reads.

tests.test_docs.test_star_history_store_well_formed()[source]

Check the committed star history keeps the shape its renderer expects.

Guards the file a scheduled job appends to unattended: a duplicated day, a repository that left the benchmark, or an unknown provenance would all surface here rather than as a misdrawn chart.

tests.test_docs.test_star_history_chart_renders()[source]

Check the chart generator still draws every tracked series.

Like the benchmark table, the SVG is generated rather than hand-written, so this guards the generator against crashes and structural regressions.

tests.test_docs.test_star_history_predecessor_stops_at_handover()[source]

Check a forerunner’s plotted line ends where its successor’s begins.

The archived repository still collects the odd star, so its record runs to today. Drawing that tail would put it alongside its successor for the whole width of the chart, reading as two live projects rather than one handover.

tests.test_docs.test_star_history_salvages_truncated_gzip()[source]

Check a gzip stream cut short still yields its star counter.

The archive’s replay service commonly drops the connection after sending most of a page: measured over 25 requests for one capture, every response that was not a bare 503 arrived truncated. gzip.GzipFile needs the trailer and raises, discarding a payload whose counter had already arrived, so the collector decompresses leniently instead.

tests.test_docs.test_star_history_series_start_at_creation()[source]

Check every tracked repository is anchored by a zero-star origin.

The relative chart measures each curve from its repository’s creation, and a competitor backfilled from the archives has no knowable first star: its earliest capture already shows a count. Without the anchor its curve would begin in mid-air, and the relative axis would have nothing to align on.

tests.test_docs.test_binaries_download_table_renders()[source]

Check the latest-release binaries table generator still produces a well-formed table from the binaries catalog.

The table is rendered live at Sphinx build time by the {python:render} block in docs/install.md, so there is no checked-in copy to compare against: this test only guards the generator against crashes and drift in the docs/assets/binaries.csv cell markup it parses.

tests.test_docs.test_fan_out_covers_every_subcommand()[source]

Check the fan-out catalog names every subcommand the CLI registers.

COMMAND_FAN_OUT is the only place a reader learns whether a command parallelizes, and it cannot be derived: the mode is an argument at each CLI call site. Holding it equal to the CLI’s own command list is what stops a new subcommand from silently missing off docs/concurrency.md, and forces whoever adds one to answer the question rather than leave it open.

tests.test_docs.test_concurrency_table_renders()[source]

Check the concurrency table generator produces a well-formed table.

Rendered live at Sphinx build time by the {python:render} block in docs/concurrency.md, so there is no checked-in copy to compare against. Every rendered row must carry a glyph the page’s own legend defines, and the no-fan-out entries must stay out.

tests.test_docs.test_lock_family_backends_are_distinct()[source]

Check no lock family is named after a manager, or after another family.

Mermaid identifies a sankey node by its label, so a family sharing a label with one of its own members would collapse the diagram’s two levels into a single self-linked node. Four backends (conda, pacman, pkg, scoop) lend their name to a manager, which is exactly how that happens.

tests.test_docs.test_lock_families_render()[source]

Check both concurrency-page generators cover every lock family.

The sankey and the table are two readings of the same constant, so a family added to SHARED_LOCK_FAMILIES must surface in both without further edits.

tests.test_docs.test_augmentations_table_renders()[source]

Check the augmentations table generator still produces a well-formed table from the current pool.

The table is rendered live at Sphinx build time by the {python:render} block in docs/augmentations.md, so there is no checked-in copy to compare against: this test only guards the generator against crashes and structural regressions.

tests.test_docs.test_ansi_logo_tracks_the_brand_palette()[source]

Check the terminal mark names the very colors the artwork is drawn in.

meta_package_manager.logo repeats them as literals, the SVG sources living in docs/ and never reaching the wheel. That repetition is how the terminal mark came to shade its faces with a purple the artwork had already dropped, so the sources are what the constants are checked against: the brand SVGs must use these three values, and nothing else.

tests.test_docs.test_manager_logo_assets()[source]

Check every vendored brand mark is normalized, safe and accounted for.

The SVGs are inlined verbatim into the built pages, so this is what keeps a hand edit (or a compromised upstream) from smuggling a script or a remote reference into the documentation. docs/logos_update.py produces this shape; the directory holds nothing else.

tests.test_docs.test_manager_logos_resolve()[source]

Check the logo slug of every manager points at a vendored mark.

Also enforces the reverse: a mark nobody claims is dead weight, mirroring the orphan sweep update_manager_stubs performs on docs/managers/.

tests.test_docs.test_manager_logo_renders(manager)[source]

Check the brand mark of each manager renders as inlinable raw HTML.

tests.test_docs.test_manager_card_renders(manager)[source]

Check each manager’s infobox carries its facts, and its mark when it has one.

tests.test_docs.test_manager_label_badge_color()[source]

Check the stylesheet paints the label badge the color GitHub gives it.

A stylesheet cannot read labels.py, so the hex is written twice: here is where the copy is caught drifting from MANAGER_LABEL_COLOR.

tests.test_docs.test_manager_logo_credits_renders()[source]

Check every vendored mark is credited with its license and source.

Twelve marks carry an attribution-bearing license, so a missing row is a license violation rather than a cosmetic gap.

tests.test_docs.test_manager_stubs_in_sync()[source]

Check the committed page stubs of docs/managers/ match a fresh generation from the pool.

The directory is wholly owned by update_manager_stubs(): one stub per pool manager, nothing else, each byte-identical to its template. Drift means a manager was added or removed without running docs/docs_update.py (repomatic’s update-docs job self-heals this on the next push).

tests.test_docs.test_manager_page_headings_survive_a_build(tmp_path)[source]

Check a real Sphinx build turns the generated headings into sections, under a generated title and below the lede.

The stub carries no heading at all, not even the page title: every one is printed by manager_page() and parsed out of the directive’s output, which only works because myst-parser supports titles in a nested parse. Three ways that can regress, all silent, none visible to a test of the generators alone: the document can lose its title, the sections can vanish, or the lede can be reparented below them, which is what happens the moment anything is printed above the first heading of the parse. So one page is built for real, and its title, heading sequence and infobox position are asserted.

brew is the subject: it is the one manager exercising every section.

Skipped when the docs dependency group is missing, as in the hermetic unit-test environment.

tests.test_docs.test_manager_page_sections_render(manager)[source]

Check every section generator of the per-manager pages produces non-empty, heading-free MyST.

The sections are rendered live at Sphinx build time by the single {python:render} block of docs/managers/<id>.md, so there is no checked-in copy to compare against: this test guards the generators against crashes and locks the heading-free invariant documented on MANAGER_SECTIONS (a heading comes from manager_page() alone).

tests.test_docs.DOCSTRING_FENCE = re.compile('(?ms)^([ \\t]*)(`{3,})(?P<info>[^\\n]*)\\n.*?^[ \\t]*\\2[ \\t]*$', re.MULTILINE|re.DOTALL)

Backtick-fenced block of a docstring, whatever its indentation.

The info string stops at the newline: . spans lines under re.DOTALL, so a greedy one would swallow every fence of the docstring into a single match.

tests.test_docs.PROSE_DIRECTIVES = re.compile('\\{(?!code-block|literalinclude|eval-rst)')

Fence info string of a directive whose body is prose, not code.

An admonition fence (note, warning, caution, seealso, todo) renders its content as regular MyST, so its links matter as much as the surrounding paragraphs’. Only a code block escapes the prose rules.

tests.test_docs.BARE_URL = re.compile('(?<!\\]\\()(?<!<)https?://[^\\s>)\\]`,;\\\'\\"]+')

A URL that is neither a markdown link target nor an autolink.

The single < lookbehind covers autolinks (<https://…>) and, incidentally, angle-bracketed link destinations (](<https://…>)); the latter are rejected outright by ANGLE_LINK_DEST below, so they never need blessing here.

A markdown link whose destination hides in angle brackets.

CommonMark allows the form, but the MyST docstring converter reads a destination only up to the first closing parenthesis, so ](<…>) renders as broken markup — worse than a bare URL. Rewrite the URL to a parenthesis-free variant instead (the man-page query form, say). Lift this ban only when the click-extra floor rises to a release whose converter takes the bracketed form.

tests.test_docs.test_docstrings_carry_no_bare_url()[source]

Every URL in docstring prose must be a link.

MyST’s linkify extension is off, so a bare URL renders as dead plain text, both in the API docs and in the manager pages inlining the class docstrings. URLs inside a fenced block are captured CLI output (fwupd’s See https://… warnings) and are left alone.

The ](<url>) destination form renders as broken markup: see ANGLE_LINK_DEST for the converter limitation and the rewrite to apply.

tests.test_docs.test_manager_traces_render_literal_blocks()[source]

A class-based manager’s reference traces surface exactly the literal installed/outdated/orphans blocks the corpus validates, in terminal-facing form.

manager_traces and the corpus round-trip both read literal_blocks(), so the rendered traces never drift from what the parsers are tested against. TOML managers keep their [samples] traces, covered by test_bundled_parsing.

tests.test_docs.test_manager_changelog_entries()[source]

Check every changelog bullet reaches the manager pages it is scoped to.

The release-history section is rendered live at Sphinx build time, so there is no checked-in copy to compare against: this recounts the (manager, entry) pairs straight from changelog.md and matches them against the index, which catches an entry silently dropped by a scope the parser fails to resolve.

Every pool manager is asserted to have at least one entry, which makes the section double as the lint for a manager shipped without its changelog line.

tests.test_docs.test_manager_support_legend()[source]

Check the glyph legend counts every assessed manager exactly once.

Rendered live at Sphinx build time by the {python:render} block opening docs/managers.md, right above the index table it is the key to. The population it reports replaced a prose summary, so the numbers are asserted against an independent recount of the same two sources.

tests.test_docs.test_managers_index_table_renders()[source]

Check the manager index renders as one well-formed table: every wrapped manager linking to its documentation page, then every declined one linking to its verdict, in the exact same columns.

The table is rendered live at Sphinx build time by the {python:render} block in docs/managers.md, so there is no checked-in copy to compare against.

tests.test_docs.test_brewfile_managers_table_renders()[source]

Check the Brewfile table lists exactly the managers that reach a dump.

--brewfile is an option of one command, so the mapping belongs on that command’s page and nowhere else: it used to be a prose enumeration that had gone stale, repeated by a row on every manager’s own card.

tests.test_docs.test_manager_card_carries_upstream_readings()[source]

Check each sampled upstream reading reaches the card of its manager.

The stars, newest release and newest commit used to be three columns of the index; they now belong to the page devoted to the tool they describe, so this follows them there rather than lapsing when the columns went.

tests.test_docs.test_upstream_badges_cover_every_forge()[source]

Check every sampled repository is hosted where a badge can read it.

A forge missing from UPSTREAM_FORGES costs its managers the whole section, silently. Naming the host here turns that into one failing assertion pointing at the line to add.

tests.test_docs.test_manager_upstream_badges()[source]

Check the upstream section of every sampled manager holds live badges.

Three invariants, each guarding a way the section silently goes wrong: the badges point at the forge family the repository is actually hosted on, the release-gated ones follow the sample rather than guessing (shields answers the same red error for an unreleased project and a missing one), and nothing here repeats a figure the infobox already states.

tests.test_docs.test_matrix_blocks_in_sync()[source]

Check the compatibility matrices embedded in the docs match a fresh regeneration from the git tags.

Drift here means a release changed the Python classifiers or the click-extra requirement before repomatic’s update-docs job refreshed the embedded blocks.

Skipped when click-extra’s [sphinx] extra (pulled by the docs dependency group) is missing, as in the hermetic unit-test environment. Without the full tag history (shallow clone, sdist build), regeneration leaves every block untouched and the test passes vacuously.

tests.test_docs.test_mirror_blocks_in_sync()[source]

Check the <!-- mirror-src --> and :mirror: blocks embedded in the docs and readme match a fresh regeneration from their generators.

Drift here means a generator’s output changed (a manager joined or left the pool, a class moved so the benchmark source-line anchors shifted) before repomatic’s update-docs job refreshed the embedded blocks with click-extra refresh-directives.

Skipped when click-extra’s [sphinx] extra (pulled by the docs dependency group) is missing, as in the hermetic unit-test environment.

tests.test_docs.test_unsupported_page_matches_benchmark()[source]

Check docs/unsupported.md’s sections cover exactly the benchmark’s unsupported set, with matching glyphs, sorted by anchor.

The benchmark renders each unsupported manager as a glyph linking into that page for the reason, so the two must stay in lockstep. A manager listed on one side only produces either a link into a page that never explains it, or a section nothing points at. Both happened while the mechanism was being introduced, hence this test.

The glyph must agree too: ☠️ and are read side by side across the two pages, and ⚠️ is reserved for the wrapped managers of the index, so it must never leak onto a page listing what mpm refused to wrap. The 🛟 fallback marker is derived rather than written, so the page is held to what unsupported_status() computes from the competitor data.

A family section carries one verdict for several tools, so every member is checked against that shared title: grouping tools whose glyphs differ would silently restate one tool’s verdict as another’s.

tests.test_docs.test_unsupported_anchors_match_docutils()[source]

Check the anchors linked from the benchmark are the ones Sphinx emits.

_heading_slug() reimplements what docs/conf.py pins myst_heading_slug_func to, because docutils only reaches this project through the docs dependency group while the benchmark table also renders under the test group. That copy is only safe while the two agree, and a disagreement is invisible: the link renders, resolves to nothing, and drops the reader at the top of the page.

tests.test_docs.test_cooldown_support_statuses_known()[source]

Check every row of the cooldown support table carries a legend glyph.

The “Supported managers” table of docs/cooldown.md is hand-curated and reused on the per-manager pages, so a typo’d or invented status glyph would travel silently. Only the five glyphs its own legend defines are allowed.

tests.test_docs.test_benchmark_operations_rows_have_support()[source]

Check no row of the benchmark’s Operations table is entirely blank.

A row no tool supports carries no information and is usually a leftover from a competitor column that was dropped: the Latest filter, Reshim and Import from external tool rows outlived the mise/asdf columns they were written for. The table is hand-maintained, so nothing else catches it.

tests.test_docs.test_retraction_table_well_formed()[source]

Check the retraction table partitions the pool and travels intact.

The “Retraction paths by registry” table of docs/cooldown.md is hand-curated, and manager_cooldown() reuses each row on the pages of the managers listed in it. Two invariants follow. A manager missing from the table silently loses the section on its page and one listed twice would resolve to whichever registry comes first, so the mapping must be a partition of the pool. And a reused cell renders from docs/managers/, one directory below docs/cooldown.md, so any relative target or same-page anchor it carries would resolve against the manager page instead: the reused cells must link absolutely or not at all.

tests.test_docs.test_retraction_status_reuses_table_row(manager)[source]

Check each manager’s page surfaces its own registry row verbatim.

A registry documenting neither a retraction path nor a publish date leaves the row alone: it renders as a plain line rather than a one-item list.

tests.test_docs.SKILL_SPEC_FIELDS = frozenset({'allowed-tools', 'compatibility', 'description', 'license', 'metadata', 'name'})

The six frontmatter fields the Agent Skills spec defines.

tests.test_docs.SKILL_CLAUDE_CODE_EXTENSIONS = frozenset({'argument-hint'})

The only non-spec frontmatter field a skill may carry.

Mirrors the set kdeldycke/repomatic pins in its own tests/test_skills.py: no spec field expresses an autocomplete hint, and the key degrades to a no-op wherever it is not understood. Do not grow this set. In particular there is no model: (the recommended model rides in compatibility) and no disable-model-invocation:: every skill is model-invocable by design, and what it may actually do is gated by the permission layer, not by frontmatter.

tests.test_docs.SKILL_MODEL_HINT = re.compile('Recommended model: \\w+\\.')

Shape of the model recommendation carried by compatibility.

tests.test_docs.test_add_manager_skill_frontmatter()[source]

Check the bundled skill conforms to the Agent Skills specification.

The frontmatter contract is the one kdeldycke/repomatic enforces on its own skills, kept here so this repository’s single local skill cannot drift away from the upstream convention it was aligned to.

A comma-separated allowed-tools is the trap worth pinning: Claude Code accepts it, so the skill keeps working locally while silently failing the spec, which wants a space-separated string.

tests.test_docs.test_add_manager_skill_include_offset()[source]

Check docs/add-new-manager.md slices the skill at its human body.

The page is a bare :include: of the skill, so the two files share one source and can only disagree through the hard-coded :start-line:. That offset silently absorbs any line added to or removed from the skill’s preamble: too low republishes the frontmatter, the skill’s own # heading or its $ARGUMENTS line, and too high eats the opening paragraph. Neither fails a docs build, which is why it is pinned here instead.

tests.test_docstring_corpus module

Replay documented CLI output back through each manager’s own parser.

Every query method documents a sample invocation and its output in a MyST {code-block} shell-session fence sitting right next to the regex (or JSON parser) that consumes it. This module feeds each harvested block back through the parser it illustrates, asserting the documented example still yields well-formed packages. The harvesting itself lives in meta_package_manager.docstring_corpus, shared with the reference-traces documentation generator.

It is a Tier-1 guard: it proves the documented output still parses (which catches a regex that silently stops matching after an upstream format change, or a docstring that has drifted from the code beside it), not that every field is captured correctly. It authors no fixtures of its own: the corpus is the docstrings.

It covers installed, outdated, orphans and --version blocks. installed, orphans and --version are single-source (one CLI call), fed straight through. outdated may cross-reference two commands (list + latest), so its calls are routed to the right block by a command-dispatching stub. Every shell-session block of these members is a complete fixture that must parse: illustrations (a human-readable variant, an interactive prompt, a narrative) live in a non-harvested console fence instead. See https://github.com/kdeldycke/meta-package-manager/issues/1023.

tests.test_docstring_corpus.test_documented_output_still_parses(manager, member, output, monkeypatch)[source]

The output documented next to a parser must still parse through it.

tests.test_docstring_corpus.test_display_blocks_align_with_raw()[source]

The compiled harvest the docs render and the raw harvest this test replays must agree on structure.

meta_package_manager/_docs.py selects reference-trace blocks by (member, index) from class_display_blocks(), while this test replays the same blocks from class_blocks(). Their per-member block counts must match, or an index would point at a different block on the two sides.

tests.test_docstring_corpus.test_fixtures_carry_no_truncation_marker()[source]

A harvested fixture block must document its output in full.

installed/outdated/orphans/version_regexes blocks are complete samples, so none may abbreviate its output with a (...) marker (an illustration that would truncate belongs under a non-harvested console fence). A bare ... is left alone: real CLI output legitimately contains it, like apt’s Listing... header or a guix store path.

tests.test_docstring_corpus.test_query_fixtures_run_verbatim()[source]

A harvested query fixture must show the exact command mpm runs.

installed/outdated/orphans/version_regexes invocations go through run_cli, which executes an argv directly with no shell, so a documented command joined to another by a shell pipe (| jq to pretty-print JSON, echo n | to feed an interactive prompt) shows an invocation mpm never makes and would render a misleading reference trace. Such a block is an illustration and belongs under a non-harvested console fence.

Re-tokenized with shlex.split() like _documented_commands does for the mutation members, so a pipe inside a quoted argument (PowerShell’s -Command "... | ...", which mpm passes as a single token for pwsh to run internally) stays one token and is not a shell pipe at mpm’s level.

tests.test_docstring_corpus.MUTATION_MEMBERS = ('cleanup_cache', 'cleanup_orphan', 'cleanup_repair', 'doctor_cli', 'install', 'remove', 'remove_orphan', 'sync', 'upgrade_all_cli', 'upgrade_one_cli')

Mutation methods whose docstrings document the exact CLI they run.

tests.test_docstring_corpus.PID_SENTINEL = 'MPM-DOC-SENTINEL'

Stand-in package id; the documented command carries a real example id where the constructed command carries this.

tests.test_docstring_corpus.BUILD_CLI_KWARGS = frozenset({'auto_post_args', 'auto_pre_args', 'auto_pre_cmds', 'override_cli_path', 'override_post_args', 'override_pre_args', 'override_pre_cmds'})

run_cli kwargs forwarded to build_cli when reconstructing the full command. sudo is deliberately not forwarded: escalation depends on platform and policy, so documented sudo prefixes are stripped on the other side.

tests.test_docstring_corpus.test_documented_command_matches_construction(manager, member, documented, monkeypatch)[source]

The command a mutation docstring shows must be the one the method builds.

tests.test_doctor module

Checks for the doctor operation and its dedicated contract.

Health comes from the diagnostic command’s exit code alone, the report merges <stdout> and <stderr>, and the diagnosis never inflates the end-of-run error tally. The argv assertions monkeypatch the binary-resolution seams on the pooled manager singletons, so they run identically on any host.

tests.test_doctor.test_doctor_cli_argv(monkeypatch, manager_id, token)[source]

Each manager’s diagnostic invocation carries its native verb.

tests.test_doctor.test_doctor_supported(manager_id)[source]
tests.test_doctor.test_doctor_unsupported(manager_id)[source]
tests.test_doctor.test_doctor_healthy_merges_streams(monkeypatch)[source]
tests.test_doctor.test_doctor_unhealthy_from_stdout_only_exit(monkeypatch)[source]

A non-zero exit with silent <stderr> (the pip check shape) is tolerated by the failure gate but must still read as unhealthy: the exit code is the verdict.

tests.test_doctor.test_doctor_reclaims_failure_gate_entry(monkeypatch)[source]

The unhealthy exit is the diagnosis, not a plumbing error: the gate’s CLIError is reclaimed so the end-of-run summary is not inflated.

tests.test_doctor.test_doctor_incomplete_run_stays_an_error(monkeypatch)[source]

A spawn that never completed (timeout, interrupt) is a genuine plumbing error: unhealthy, and its CLIError stays in the tally.

tests.test_doctor.test_base_doctor_not_implemented()[source]

A manager without a diagnostic verb propagates NotImplementedError.

class tests.test_doctor.HealthyDoctorFakeManager[source]

Bases: FakeManager

Fake manager whose diagnosis succeeds with a report.

Initialize cli_errors list.

doctor_cli()[source]

Returns the complete CLI running the manager’s native self-diagnosis.

The invocation must be read-only (brew doctor, pip check, pacman --database --check, …): doctor() runs it, never mpm’s mutating machinery. The surveyed doctor verbs share one convention this contract leans on: a non-zero exit code means problems were found.

Optional. Will be simply skipped by mpm if not implemented.

Return type:

tuple[str, ...]

doctor()[source]

Run the native self-diagnosis, returning (healthy, report).

Runs doctor_cli() and interprets the outcome with a contract of its own, distinct from every other operation:

  • Health is the exit code alone. run()’s failure gate tolerates a non-zero exit with a silent <stderr> (a benign status for query parsers), but for a diagnosis that exit is the verdict: pip check reports its conflicts on <stdout> only and would read as healthy under the gate.

  • The report merges both streams. The tools split their findings across them (brew doctor warns on <stderr>), and the report is relayed verbatim to the user: there is nothing to parse.

  • The diagnosis is not an error. The failure-gate entry an unhealthy exit may have accumulated is reclaimed from cli_errors, so the end-of-run error summary is not inflated by a verdict mpm doctor``already reports on its own. The gate’s``WARNING diagnosis relay is skipped for the same reason (doctor sits in the gate’s _DIAGNOSIS_EXEMPT_OPERATIONS): the findings land in the report, verbatim. A run that never completed (timeout, interrupt, missing binary) keeps its entry: that is a genuine plumbing error, and the manager reports unhealthy.

Return type:

tuple[bool, str]

cli_names: tuple[str, ...] = ('healthydoctorfakemanager',)

List of CLI names the package manager is known as.

This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.

..hint::

This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

id: str = 'healthydoctorfakemanager'

Package manager’s ID.

Derived by defaults from the lower-cased class name in which underscores _ are replaced by dashes -.

This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.

name: str = 'HealthyDoctorFakeManager'

Return package manager’s common name.

Default value is based on class name.

virtual: bool = False

Should we expose the package manager to the user?

Virtual package manager are just skeleton classes used to factorize code among managers of the same family.

class tests.test_doctor.SickDoctorFakeManager[source]

Bases: HealthyDoctorFakeManager

Fake manager whose diagnosis reports problems.

Initialize cli_errors list.

doctor()[source]

Run the native self-diagnosis, returning (healthy, report).

Runs doctor_cli() and interprets the outcome with a contract of its own, distinct from every other operation:

  • Health is the exit code alone. run()’s failure gate tolerates a non-zero exit with a silent <stderr> (a benign status for query parsers), but for a diagnosis that exit is the verdict: pip check reports its conflicts on <stdout> only and would read as healthy under the gate.

  • The report merges both streams. The tools split their findings across them (brew doctor warns on <stderr>), and the report is relayed verbatim to the user: there is nothing to parse.

  • The diagnosis is not an error. The failure-gate entry an unhealthy exit may have accumulated is reclaimed from cli_errors, so the end-of-run error summary is not inflated by a verdict mpm doctor``already reports on its own. The gate’s``WARNING diagnosis relay is skipped for the same reason (doctor sits in the gate’s _DIAGNOSIS_EXEMPT_OPERATIONS): the findings land in the report, verbatim. A run that never completed (timeout, interrupt, missing binary) keeps its entry: that is a genuine plumbing error, and the manager reports unhealthy.

Return type:

tuple[bool, str]

cli_names: tuple[str, ...] = ('sickdoctorfakemanager',)

List of CLI names the package manager is known as.

This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.

..hint::

This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

id: str = 'sickdoctorfakemanager'

Package manager’s ID.

Derived by defaults from the lower-cased class name in which underscores _ are replaced by dashes -.

This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.

name: str = 'SickDoctorFakeManager'

Return package manager’s common name.

Default value is based on class name.

virtual: bool = False

Should we expose the package manager to the user?

Virtual package manager are just skeleton classes used to factorize code among managers of the same family.

class tests.test_doctor.SilentDoctorFakeManager[source]

Bases: HealthyDoctorFakeManager

Fake manager whose diagnosis succeeds silently (the apt-get check shape).

Initialize cli_errors list.

doctor()[source]

Run the native self-diagnosis, returning (healthy, report).

Runs doctor_cli() and interprets the outcome with a contract of its own, distinct from every other operation:

  • Health is the exit code alone. run()’s failure gate tolerates a non-zero exit with a silent <stderr> (a benign status for query parsers), but for a diagnosis that exit is the verdict: pip check reports its conflicts on <stdout> only and would read as healthy under the gate.

  • The report merges both streams. The tools split their findings across them (brew doctor warns on <stderr>), and the report is relayed verbatim to the user: there is nothing to parse.

  • The diagnosis is not an error. The failure-gate entry an unhealthy exit may have accumulated is reclaimed from cli_errors, so the end-of-run error summary is not inflated by a verdict mpm doctor``already reports on its own. The gate’s``WARNING diagnosis relay is skipped for the same reason (doctor sits in the gate’s _DIAGNOSIS_EXEMPT_OPERATIONS): the findings land in the report, verbatim. A run that never completed (timeout, interrupt, missing binary) keeps its entry: that is a genuine plumbing error, and the manager reports unhealthy.

Return type:

tuple[bool, str]

cli_names: tuple[str, ...] = ('silentdoctorfakemanager',)

List of CLI names the package manager is known as.

This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.

..hint::

This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

id: str = 'silentdoctorfakemanager'

Package manager’s ID.

Derived by defaults from the lower-cased class name in which underscores _ are replaced by dashes -.

This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.

name: str = 'SilentDoctorFakeManager'

Return package manager’s common name.

Default value is based on class name.

virtual: bool = False

Should we expose the package manager to the user?

Virtual package manager are just skeleton classes used to factorize code among managers of the same family.

tests.test_doctor.test_doctor_healthy_relays_report(invoke, monkeypatch)[source]
tests.test_doctor.test_doctor_unhealthy_exits_nonzero(invoke, monkeypatch)[source]
tests.test_doctor.test_doctor_zero_exit_opt_out(invoke, monkeypatch, flag)[source]

-0/–zero-exit keeps the exit code at 0; the critical summary stays as the durable record.

tests.test_doctor.test_doctor_silent_healthy_prints_no_section(invoke, monkeypatch)[source]

A healthy manager with an empty report gets no stdout section: the trail already says everything.

tests.test_execution module

tests.test_execution.test_operation_timeouts_cover_all_operations()[source]

Every routable operation must have a per-operation timeout default, so the map and the Operations enum never drift apart.

tests.test_execution.test_operation_name_sets_match_enum()[source]

The literal operation-name sets stay subsets of the Operations vocabulary.

_MUTATING_OPERATIONS (plan-mode capture) and _STALL_NOTICE_OPERATIONS (watchdog arming) are spelled as string literals: renaming an Operations member would otherwise silently desync them, stopping plan capture or watchdog arming for the renamed operation.

tests.test_execution.test_read_only_tier_is_shorter_than_mutating_tier()[source]

Read-only probes fail faster than state-changing operations.

tests.test_execution.test_resolve_timeout_explicit_wins(active_operation)[source]

An explicit timeout (--timeout or per-manager override) wins for every operation, including unknown ones.

tests.test_execution.test_resolve_timeout_per_operation(active_operation, expected)[source]

With no explicit timeout, each operation resolves to its tier default.

tests.test_execution.test_resolve_timeout_unknown_operation_falls_back_to_default()[source]

A CLI call with no known operation gets the conservative default.

tests.test_execution.test_make_spinner_disabled_without_progress()[source]

Without the progress opt-in, the spinner is forced off.

tests.test_execution.test_make_spinner_defers_to_tty_with_progress()[source]

With progress on, the spinner is left to auto-detect a TTY at runtime.

tests.test_execution.test_make_spinner_label_includes_manager_and_operation()[source]
tests.test_execution.test_make_spinner_label_without_operation()[source]
tests.test_execution.test_make_spinner_uses_configured_delay()[source]
tests.test_execution.FAIL_ON_STDOUT = "import sys; sys.stdout.write('boom'); sys.exit(8)"

Fails the steamcmd way: a non-zero exit with its message on <stdout> and an empty <stderr>.

tests.test_execution.FAIL_ON_STDERR = "import sys; sys.stderr.write('boom'); sys.exit(8)"

Fails the conventional way: a non-zero exit with its message on <stderr>.

tests.test_execution.test_run_failure_gate(stop_on_error, must_succeed, script, expectation)[source]

The failure gate decides raise / accumulate / tolerate from the exit code, the <stderr> content, and the stop_on_error/must_succeed context.

tests.test_execution.test_cli_error_diagnosis(error, output, expected)[source]

CLIError.diagnosis prefers <stderr>, falls back on <stdout>, states a silent death, and tail-caps with a truncation counter.

tests.test_execution.test_failed_run_relays_diagnosis(stop_on_error, script, expected, caplog)[source]

The failure gate logs the diagnosis at WARNING, tagged with the manager ID, for accumulated and raised failures alike.

tests.test_execution.test_no_diagnosis_relay(script, level, caplog)[source]

No WARNING relay without a genuine failure, nor when DEBUG already streamed the raw output inline.

tests.test_execution.test_exempt_operations_skip_diagnosis_relay(operation, caplog)[source]

Version probes (fired per candidate on selection) and doctor (which relays its report verbatim) fail without the WARNING relay.

tests.test_execution.test_run_cache_replays_identical_command(tmp_path)[source]

With a shared cache, a byte-identical command runs the subprocess once.

tests.test_execution.test_run_cache_disabled_by_default(tmp_path)[source]

Without a cache, identical commands each spawn their own subprocess.

tests.test_execution.test_run_cache_replays_failure_to_every_member(tmp_path)[source]

A cached failure is re-attributed to each peer, though the command runs once.

tests.test_execution.test_run_cache_keeps_distinct_commands_apart(tmp_path)[source]

Only byte-identical invocations collapse; different args each run.

tests.test_execution.test_run_cache_collapses_dry_run(caplog)[source]

Under –dry-run a family’s identical command is announced once, not per member.

tests.test_execution.test_format_plan_command_shell_quotes_env_and_args()[source]

A captured plan command renders as a plain, shell-quoted, runnable line.

tests.test_execution.test_plan_captures_mutating_command_without_running_it(tmp_path)[source]

Plan mode records a state-changing command instead of executing it.

tests.test_execution.test_plan_runs_read_only_operations(tmp_path)[source]

Plan mode executes read-only queries so the plan resolves against real state.

tests.test_execution.test_plan_synthesized_upgrade_all_resolves_reads(invoke, monkeypatch)[source]

mpm --plan upgrade on a synthesized upgrade-all prints the per-package upgrade commands, resolved against a really-executed outdated read.

Guards the read-stamp of the synthesized fallback in PackageManager.upgrade: the whole run is stamped upgrade_all (a mutating operation), so without re-stamping the inner listing as outdated, plan mode captured the read itself — leaving the plan without the actual mutating commands it exists to disclose.

tests.test_execution.test_plan_ignores_force_exec(tmp_path)[source]

A force_exec read runs for real even under plan mode (version probes, etc.).

tests.test_execution.test_run_registers_live_process_then_discards_it()[source]

run() tracks its subprocess while it runs, and drops it once done.

A background call parks in a real subprocess. Once it is registered, terminate_live_processes() unblocks it, and run_cli()’s finally clears the registry: this is the exact path the SIGINT handler drives on Ctrl+C.

tests.test_execution.test_build_cli_escalates_with_sudo_n_when_policy_on()[source]

A privileged op escalates to sudo --non-interactive when the manager’s policy opts in.

tests.test_execution.test_build_cli_no_escalation_when_policy_off()[source]

--no-sudo (policy False) drops escalation even on a privileged op.

tests.test_execution.test_build_cli_no_escalation_off_unix()[source]

A non-UNIX host never escalates (no crash), even with policy on.

tests.test_execution.test_build_cli_marker_required_for_escalation()[source]

Policy on but no per-op marker (sudo=False default) means no escalation.

tests.test_execution.test_npm_sudo_marker_dormant_until_opted_in()[source]

npm marks its global installs privileged, but stays unescalated until the user opts in via –sudo / config (default_sudo is False).

tests.test_execution.test_run_hints_when_sudo_cannot_authenticate(tmp_path, monkeypatch, caplog)[source]

A real sudo --non-interactive that cannot authenticate triggers the actionable hint.

A fake sudo on PATH mimics “no cached credentials”, so the whole build_cli → subprocess → failure-gate → hint path runs for real, without root.

tests.test_execution.test_mutating_subcommands_prime_sudo(invoke, fake_pool, args)[source]

Every mutating subcommand authenticates sudo up front, so a future command cannot silently skip the priming.

tests.test_execution.test_restore_primes_sudo(invoke, fake_pool, tmp_path)[source]

restore fans installs out too, so it also primes sudo up front.

tests.test_execution.test_read_subcommand_does_not_prime_sudo(invoke, fake_pool)[source]

A read-only command never escalates, so it must not prompt for sudo.

Patched at the source module: the explore subcommands import no sudo machinery at all, so any priming would have to reach it there.

tests.test_execution.test_sourced_operation_stamps_active_operation(invoke, fake_pool, monkeypatch, args, expected_operation)[source]

A sourced remove/upgrade <packages> runs its mutating action under the real operation stamp.

Source discovery re-selects the managers with installed, which stamps _active_operation = "installed" on the shared singletons. Each per-package task must therefore re-stamp the mutating operation for the duration of its own attempt (through CLIExecutor.acting_as), or the command would resolve the read-only timeout and skip the escalator stall watchdog (both keyed on _active_operation). This drives the real selection flow and observes the stamp from inside the action itself: it fails if the dispatch stops stamping.

tests.test_execution.test_untied_install_search_runs_under_read_only_stamp(invoke, fake_pool, monkeypatch)[source]

The untied-install priority search runs under the search stamp, not install.

The search is read-only and can never escalate, so it must not resolve the mutating timeout nor arm the internal-escalator stall watchdog (both keyed on _active_operation). Without the stamp, a slow brew search of a cask would be misread as a hidden password prompt.

tests.test_gnome_extension module

Static invariants of the GNOME Shell extension.

The extension is GJS code the Python suite cannot import, so these tests pin the contracts that keep its moving parts in sync: the metadata.json identity, the GSettings schema, the stylesheet classes, the icon set and the argv builders. The behavioral coverage lives in tests/gnome/run-tests.js, executed under a bare gjs interpreter by test_gjs_unit_suite() when one is installed. No CI runner running pytest installs gjs, so that wrapper is a convenience for a developer who has one: in CI the same script is driven directly by the gjs job of the tests-gnome-extension.yaml workflow.

tests.test_gnome_extension.EXPECTED_ICON_STATES = frozenset({'error', 'unknown', 'updates', 'uptodate'})

Values of the State mapping in extension.js, each backed by a mpm-<state>-symbolic.svg icon.

tests.test_gnome_extension.test_metadata_well_formed()[source]
tests.test_gnome_extension.test_gschema_well_formed()[source]
tests.test_gnome_extension.test_settings_keys_in_sync()[source]

Every key read in the JS sources exists in the schema, and vice versa.

Guards against the drift arch-update ships with, where code defaults disagree with schema defaults or keys outlive their last reader.

tests.test_gnome_extension.test_css_classes_in_sync()[source]

Every .mpm-* class referenced in JS is styled, and vice versa.

tests.test_gnome_extension.test_icon_states_in_sync()[source]

The State mapping values match the bundled symbolic icon set.

tests.test_gnome_extension.test_symbolic_icon_exists(state)[source]
tests.test_gnome_extension.test_no_orphan_icons()[source]
tests.test_gnome_extension.test_runtime_argv_long_form()[source]

The argv builders only emit long-form options.

Same rule as for every argv mpm itself constructs at runtime (see the manager classes and meta_package_manager/sudo.py). The only tolerated short form is sh -c, which POSIX defines with no long equivalent.

tests.test_gnome_extension.test_extension_js_shell_free_module()[source]

mpm.js never imports shell modules, so it stays testable under gjs.

tests.test_gnome_extension.test_process_import_boundaries()[source]

The two-process import rules of the extensions.gnome.org guidelines.

The shell process must never load GTK libraries, and the preferences process must never load shell libraries.

tests.test_gnome_extension.test_gjs_unit_suite()[source]

Run the GJS unit suite against the shell-free mpm.js module.

tests.test_help module

Keeps the CLI test plan’s per-command --help roster in sync.

The --help invocations themselves live as [[cases]] entries in tests/cli-test-plan.toml, executed by click-extra test-suite in CI and against the compiled binaries during releases. A static roster drifts as commands are added or removed, so this conformance test compares it against the live command tree and fails naming the missing or orphaned entries.

tests.test_help.test_plan_covers_all_commands()[source]

Every command and subcommand must have a --help case in the plan.

tests.test_manager_apt module

APT-specific tests.

tests.test_manager_apt.test_nonempty_exact_search_results(invoke, exact_search)[source]

tests.test_manager_definition module

Tests for brand-new package managers defined from the [mpm.managers.<id>] configuration section.

tests.test_manager_definition.reset_definitions()[source]

Drop config-defined managers from the singleton pool before and after a test.

The pool is a module-level singleton, so a manager registered by one test would otherwise leak into the next. Mirrors reset_overrides in tests.test_manager_overrides.

tests.test_manager_definition.fake_tool(tmp_path)[source]

Write a minimal POSIX fake package-manager CLI and return its path.

tests.test_manager_definition.test_parse_definition_returns_dataclass()[source]
tests.test_manager_definition.test_parse_definition_accepts_orphan_operations()[source]

The schema recognizes the remove_orphan and cleanup_orphan refinements; remove_orphan requires its {package_id} placeholder like remove.

tests.test_manager_definition.test_parse_definition_rejects_legacy_cleanup()[source]

The monolithic cleanup operation is gone from the schema: declaring it names the category keys that replaced it.

tests.test_manager_definition.test_parse_definition_remove_orphan_requires_package_id()[source]

remove_orphan targets nothing without {package_id}, so it is rejected.

tests.test_manager_definition.test_parse_definition_versionless_catalog_manager()[source]

A tool with no per-package versions, no search command and a Brewfile mapping.

Locks three schema affordances at once: an installed regex may capture only the package ID (Clear Linux bundles and Cygwin listings carry no version), a search may omit {query} to list the whole catalog and rely on client-side refiltering, and the Brewfile export fields land on the built class.

tests.test_manager_definition.test_parse_definition_rejects(section, expected)[source]
tests.test_manager_definition.test_parse_definition_rejects_bad_id()[source]
tests.test_manager_definition.test_parse_definition_multi_binary_and_sudo()[source]

A definition can span sibling binaries, mark privileged operations, and declare an alternate version probe.

tests.test_manager_definition.test_parse_definition_internal_sudo()[source]

A definition can mark its tool as escalating internally (like fink), and the flag lands on the built class without touching the wrapping policy.

tests.test_manager_definition.test_routing_builtin_is_override()[source]
tests.test_manager_definition.test_routing_typo_of_builtin_is_unknown_manager()[source]
tests.test_manager_definition.test_routing_definition_is_validated()[source]
tests.test_manager_definition.test_build_class_identity()[source]
tests.test_manager_definition.test_build_class_implements(operations, implemented, not_implemented)[source]
tests.test_manager_definition.test_factory_regex_parsing(monkeypatch)[source]
tests.test_manager_definition.test_factory_json_parsing(monkeypatch)[source]
tests.test_manager_definition.test_factory_command_substitution(monkeypatch)[source]
tests.test_manager_definition.test_factory_operation_cli(monkeypatch)[source]

An operation carrying its own cli resolves the sibling binary and routes the call through it; a missing sibling is an error, not a silent fallback.

tests.test_manager_definition.test_factory_orphans_query(monkeypatch)[source]

A config-defined manager can declare the orphans read-only query, parsed like installed and advertised as a routable operation.

tests.test_manager_definition.test_factory_orphan_operations(monkeypatch)[source]

A config-defined manager can declare the orphan refinements: remove_orphan substitutes {package_id} like remove, cleanup_orphan runs a bare command like cleanup. Both register as implemented capabilities.

tests.test_manager_definition.test_factory_operation_sudo(monkeypatch)[source]

A sudo = true operation is built privileged; unmarked ones are not.

tests.test_manager_definition.test_factory_query_sudo(monkeypatch)[source]

A query marked sudo = true runs privileged, for tools that gate even their read-only listings behind root (deb-get’s upgradable check).

tests.test_manager_definition.test_factory_search_refinements(monkeypatch, extended, exact, expected_args)[source]

Refinement markers expand in place to their argument lists when active, and to nothing otherwise; declaring them advertises native support.

tests.test_manager_definition.test_factory_search_without_refinements_relies_on_refiltering()[source]

A plain search template advertises no native refinement support.

tests.test_manager_definition.test_factory_json_array_field(monkeypatch)[source]

A key[N] selector picks one element out of a list-valued JSON field, and resolves to nothing on non-list values or out-of-range indexes.

tests.test_manager_definition.test_config_file_is_trusted(tmp_path)[source]
tests.test_manager_definition.test_register_refuses_untrusted_file(tmp_path, reset_definitions, caplog)[source]
tests.test_manager_definition.test_register_refuses_url(reset_definitions, caplog)[source]
tests.test_manager_definition.test_register_rejects_builtin_collision(reset_definitions, caplog)[source]
tests.test_manager_definition.test_register_is_idempotent(reset_definitions)[source]
tests.test_manager_definition.test_factory_functional(tmp_path, fake_tool, reset_definitions)[source]
tests.test_manager_definition.test_factory_functional_version_cli(tmp_path, fake_tool, reset_definitions)[source]

The version probe runs the version_cli binary instead of the main CLI, like OS-versioned tool suites exposing no version flag of their own.

tests.test_manager_definition.test_cli_lists_config_defined_manager(invoke, create_config, tmp_path, fake_tool, reset_definitions)[source]
tests.test_manager_definition.test_cli_rejects_bad_definition(invoke, create_config, reset_definitions)[source]
tests.test_manager_definition.BUNDLED_DEFINITION_FILES = [PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/apt_cyg.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/basalt.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/bpkg.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/bun.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/cargo.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/cave.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/choco.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/chromebrew.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/clib.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/cpan.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/fink.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/gcloud.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/gh_ext.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/haxelib.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/jpm.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/juliaup.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/krew.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/macports.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/micro.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/ollama.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/opam.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/opkg.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/pamac.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/pkg_tools.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/pkgin.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/pyenv.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/rustup.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/slapt_get.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/soar.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/sorcery.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/steamcmd.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/stew.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/swupd.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/tlmgr.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/topgrade.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/urpmi.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/vscode.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/vscodium.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/xcodes.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/yazi.toml'), PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/zerobrew.toml')]

Every definition file mpm ships, discovered exactly like the runtime loader.

tests.test_manager_definition.BUNDLED_FILE_DATA = {PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/apt_cyg.toml'): {'mpm': {'managers': {'apt-cyg': {'homepage_url': 'https://github.com/transcode-open/apt-cyg', 'name': 'apt-cyg', 'operations': {'install': {'args': ['install', '{package_id}']}, 'installed': {'args': ['list'], 'regex': '^\\s*(?P<package_id>\\S+)\\s*$'}, 'remove': {'args': ['remove', '{package_id}']}, 'search': {'args': ['listall', '{query}'], 'regex': '^\\s*(?P<package_id>\\S+)\\s*$'}, 'sync': {'args': ['update']}}, 'platforms': ['cygwin'], 'unmaintained': True, 'unmaintained_message': "The author [stopped maintaining apt-cyg in 2017](https://github.com/transcode-open/apt-cyg/blob/master/status.md), stating 'I am no longer working on apt-cyg' and directing users to fork it instead.", 'version_regexes': ['apt-cyg version (?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'bash\ncoreutils\ntree', 'packages': [{'id': 'bash'}, {'id': 'coreutils'}, {'id': 'tree'}]}], 'search': [{'output': 'tree', 'packages': [{'id': 'tree'}]}], 'version': {'expected': '1', 'output': 'apt-cyg version 1'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/basalt.toml'): {'mpm': {'managers': {'basalt': {'cli_names': ['basalt'], 'homepage_url': 'https://github.com/hyperupcall/basalt', 'name': 'Basalt', 'operations': {'install': {'args': ['global', 'add', '{package_id}']}, 'installed': {'args': ['global', 'list'], 'regex': '^(?:[a-z]+://[^/]+/)?(?P<package_id>[^@\\s]+)@(?P<installed_version>\\S+)$'}, 'remove': {'args': ['global', 'remove', '--force', '{package_id}']}}, 'platforms': ['linux_like', 'macos'], 'requirement': '>=0.10.0', 'version_regexes': ['Version:[ \\t]+v?(?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'https://github.com/hyperupcall/bash-args@8fa6ff556d9c4fca5ce8431ddc571139abcf69a7', 'packages': [{'id': 'hyperupcall/bash-args', 'version': '8fa6ff556d9c4fca5ce8431ddc571139abcf69a7'}]}], 'version': {'expected': '0.10.0', 'output': 'Version: v0.10.0'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/bpkg.toml'): {'mpm': {'managers': {'bpkg': {'cli_names': ['bpkg'], 'homepage_url': 'https://www.bpkg.sh', 'name': 'bpkg', 'operations': {'install': {'args': ['install', '--global', '{package_id}']}}, 'platforms': ['linux_like', 'macos'], 'version_regexes': ['^(?P<version>\\d+\\.\\d+\\.\\d+)']}}}, 'samples': {'version': {'expected': '1.1.4', 'output': '1.1.4'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/bun.toml'): {'mpm': {'managers': {'bun': {'cli_names': ['bun'], 'homepage_url': 'https://bun.sh', 'logo': 'bun', 'name': 'Bun', 'operations': {'install': {'args': ['install', '--global', '{package_id}']}, 'installed': {'args': ['pm', 'ls', '--global'], 'regex': '^[├└]──[ \\t]+(?P<package_id>\\S+)@(?P<installed_version>\\S+)$'}, 'outdated': {'args': ['outdated', '--global'], 'regex': '^\\|[ \\t]*(?P<package_id>\\S+)[ \\t]*\\|[ \\t]*(?P<installed_version>\\d\\S*)[ \\t]*\\|[ \\t]*\\S+[ \\t]*\\|[ \\t]*(?P<latest_version>\\d\\S*)[ \\t]*\\|$'}, 'remove': {'args': ['remove', '--global', '{package_id}']}, 'upgrade_all': {'args': ['update', '--global', '--latest']}, 'upgrade_one': {'args': ['update', '--global', '--latest', '{package_id}']}}, 'platforms': ['linux_like', 'macos', 'windows'], 'requirement': '>=1.2.0', 'version_cli_options': ['--version'], 'version_regexes': ['^(?P<version>\\d+\\.\\d+\\.\\d+\\S*)$']}}}, 'samples': {'installed': [{'output': '/home/user/.bun/install/global node_modules (42)\n├── @sindresorhus/is@8.1.0\n└── cowsay@1.6.0', 'packages': [{'id': '@sindresorhus/is', 'version': '8.1.0'}, {'id': 'cowsay', 'version': '1.6.0'}]}], 'outdated': [{'output': 'bun outdated v1.3.14 (0d9b296a)\n|--------------------------------------|\n| Package  | Current | Update | Latest |\n|----------|---------|--------|--------|\n| cowsay   | 1.5.0   | 1.5.0  | 1.6.0  |\n|--------------------------------------|', 'packages': [{'id': 'cowsay', 'version': '1.6.0'}]}], 'version': {'expected': '1.3.14', 'output': '1.3.14'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/cargo.toml'): {'mpm': {'managers': {'cargo': {'brewfile_entry_type': 'cargo', 'homepage_url': 'https://doc.rust-lang.org/cargo/', 'logo': 'rust', 'name': 'Rust cargo', 'operations': {'install': {'args': ['install', '{package_id}']}, 'installed': {'args': ['install', '--list'], 'regex': '^(?P<package_id>\\S+)\\s+v(?P<installed_version>\\S+):$'}, 'remove': {'args': ['uninstall', '{package_id}']}, 'search': {'args': ['search', '--limit', '100', '{query}'], 'regex': '^(?P<package_id>\\S+)\\s+=\\s+"(?P<latest_version>\\S+)"'}}, 'platforms': ['all_platforms'], 'pre_args': ['--color', 'never', '--quiet'], 'requirement': '>=1.0.0', 'version_regexes': ['cargo\\s+(?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'bore-cli v0.4.0:\n    bore\nripgrep v13.0.0:\n    rg', 'packages': [{'id': 'bore-cli', 'version': '0.4.0'}, {'id': 'ripgrep', 'version': '13.0.0'}]}], 'search': [{'output': 'python = "0.0.0"                  # Python.\npyo3 = "0.16.4"                   # Bindings to Python interpreter\n... and 1664 crates more (use --limit N to see more)', 'packages': [{'id': 'python', 'version': '0.0.0'}, {'id': 'pyo3', 'version': '0.16.4'}]}], 'version': {'expected': '1.59.0', 'output': 'cargo 1.59.0'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/cave.toml'): {'mpm': {'managers': {'cave': {'cli_names': ['cave'], 'default_sudo': True, 'homepage_url': 'https://exherbo.org', 'maintenance_note': '[Paludis / cave](https://exherbo.org) is slow-moving: its last release (`3.0.2`) and commits date to around March 2025, though the project is not archived and carries no retirement notice.', 'name': 'cave', 'operations': {'cleanup_orphan': {'args': ['purge', '--execute'], 'sudo': True}, 'install': {'args': ['resolve', '--execute', '{package_id}'], 'sudo': True}, 'installed': {'args': ['print-ids', '--matching', '*/*::installed', '--format', '%c/%p %v\\n'], 'regex': '^(?P<package_id>\\S+) (?P<installed_version>\\S+)$'}, 'orphans': {'args': ['purge'], 'regex': '^<\\s+(?P<package_id>[^\\s:]+)-(?P<installed_version>\\d[^\\s:]*):'}, 'remove': {'args': ['uninstall', '--execute', '{package_id}'], 'sudo': True}, 'sync': {'args': ['sync'], 'sudo': True}, 'upgrade_all': {'args': ['resolve', '--complete', 'world', '--execute'], 'sudo': True}, 'upgrade_one': {'args': ['resolve', '--execute', '{package_id}'], 'sudo': True}}, 'platforms': ['exherbo'], 'pre_args': ['--colour', 'no'], 'version_regexes': ['(?P<version>\\d+\\.\\d+\\.\\d+)']}}}, 'samples': {'installed': [{'output': 'app-arch/gzip 1.14\nsys-apps/sed 4.9', 'packages': [{'id': 'app-arch/gzip', 'version': '1.14'}, {'id': 'sys-apps/sed', 'version': '4.9'}]}], 'orphans': [{'output': 'Done: 156 metadata, 42 steps\n\nThese are the actions I will take, in order:\n\n<   dev-libs/libpcre-8.45:0::installed\n    Reasons: purge\n\n<   app-misc/tmux-3.3a:0::installed\n    Reasons: purge\n\nTotal: 2 uninstalls\n\nUse --execute to perform the actions I described.', 'packages': [{'id': 'dev-libs/libpcre', 'version': '8.45'}, {'id': 'app-misc/tmux', 'version': '3.3a'}]}], 'version': {'expected': '3.0.1', 'output': 'cave 3.0.1'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/choco.toml'): {'mpm': {'managers': {'choco': {'cli_names': ['choco'], 'homepage_url': 'https://chocolatey.org', 'logo': 'chocolatey', 'name': 'Chocolatey', 'operations': {'install': {'args': ['install', '{package_id}', '--yes', '--limit-output']}, 'installed': {'args': ['list', '--limit-output'], 'regex': '^(?P<package_id>.+)\\|(?P<installed_version>.+)$'}, 'outdated': {'args': ['outdated', '--limit-output'], 'regex': '^(?P<package_id>.+)\\|(?P<installed_version>.+)\\|(?P<latest_version>.+)\\|.+$'}, 'remove': {'args': ['uninstall', '{package_id}', '--yes', '--limit-output']}, 'search': {'args': ['search', '{query}', '--limit-output', '{id_name_only_args}', '{exact_args}'], 'exact_args': ['--exact'], 'id_name_only_args': ['--by-id-only'], 'regex': '^(?P<package_id>.+)\\|(?P<latest_version>.+)$'}, 'upgrade_all': {'args': ['upgrade', 'all', '--yes', '--limit-output']}, 'upgrade_one': {'args': ['upgrade', '{package_id}', '--yes', '--limit-output']}}, 'platforms': ['windows'], 'post_args': ['--no-progress', '--no-color', '--retry-count=3'], 'requirement': '>=2.0.0'}}}, 'samples': {'installed': [{'output': 'adobereader|11.0.10\nccleaner|5.03.5128\nchocolatey|0.9.9.2\nConEmu|14.9.23.0\ngimp|2.8.14.1\ngit|1.9.5.20150114', 'packages': [{'id': 'adobereader', 'version': '11.0.10'}, {'id': 'ccleaner', 'version': '5.03.5128'}, {'id': 'chocolatey', 'version': '0.9.9.2'}, {'id': 'ConEmu', 'version': '14.9.23.0'}, {'id': 'gimp', 'version': '2.8.14.1'}, {'id': 'git', 'version': '1.9.5.20150114'}]}], 'outdated': [{'output': '7zip.commandline|16.02.0.20170209|16.02.0.20170209|false\natom|1.23.3|1.24.0|false\nbulkrenameutility|3.0.0.1|3.0.0.1|false', 'packages': [{'id': '7zip.commandline', 'version': '16.02.0.20170209'}, {'id': 'atom', 'version': '1.24.0'}, {'id': 'bulkrenameutility', 'version': '3.0.0.1'}]}], 'search': [{'output': 'virtualbox|6.1.0\nVirtualBox.ExtensionPack|5.1.10.20161223\nenigmavirtualbox|9.20\nvirtualbox-guest-additions-guest.install|6.1.0', 'packages': [{'id': 'virtualbox', 'version': '6.1.0'}, {'id': 'VirtualBox.ExtensionPack', 'version': '5.1.10.20161223'}, {'id': 'enigmavirtualbox', 'version': '9.20'}, {'id': 'virtualbox-guest-additions-guest.install', 'version': '6.1.0'}]}], 'version': {'expected': '2.2.2', 'output': '2.2.2'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/chromebrew.toml'): {'mpm': {'managers': {'chromebrew': {'cli_names': ['crew'], 'homepage_url': 'https://chromebrew.github.io', 'logo': 'googlechrome', 'name': 'Chromebrew', 'operations': {'install': {'args': ['install', '--no-color', '{package_id}']}, 'installed': {'args': ['list', '--verbose', '--no-color', 'installed'], 'regex': '^(?P<package_id>\\S+)\\s+(?P<installed_version>\\d\\S*)$'}, 'remove': {'args': ['remove', '--no-color', '{package_id}']}, 'search': {'args': ['search', '--no-color', '{query}'], 'regex': '^(?P<package_id>[^\\s:]+): '}, 'sync': {'args': ['update']}, 'upgrade_all': {'args': ['upgrade', '--no-color']}, 'upgrade_one': {'args': ['upgrade', '--no-color', '{package_id}']}}, 'platforms': ['chromeos'], 'version_cli_options': ['version'], 'version_regexes': ['^(?P<version>[\\d.]+)$']}}}, 'samples': {'installed': [{'output': 'Package        Version\n=======\nless           643\nnano           8.2', 'packages': [{'id': 'less', 'version': '643'}, {'id': 'nano', 'version': '8.2'}]}], 'search': [{'output': 'less: GNU less is a paginator\nlesspipe: Filters for less', 'packages': [{'id': 'less'}, {'id': 'lesspipe'}]}], 'version': {'expected': '1.75.0', 'output': '1.75.0'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/clib.toml'): {'mpm': {'managers': {'clib': {'cli_names': ['clib'], 'homepage_url': 'https://github.com/clibs/clib', 'name': 'clib', 'operations': {'install': {'args': ['install', '--global', '{package_id}']}, 'remove': {'args': ['uninstall', '{package_id}']}, 'search': {'args': ['search', '--json', '--no-color', '{query}'], 'fields': {'package_id': 'repo'}, 'format': 'json'}}, 'platforms': ['linux_like', 'macos'], 'version_regexes': ['(?P<version>\\d+\\.\\d+\\.\\d+)']}}}, 'samples': {'search': [{'output': '[\n  {\n    "repo": "willemt/skiplist",\n    "href": "https://github.com/willemt/skiplist",\n    "description": "Dictionary implemented through a skiplist",\n    "category": "data structure"\n  }\n]', 'packages': [{'id': 'willemt/skiplist'}]}], 'version': {'expected': '2.8.7', 'output': '2.8.7'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/cpan.toml'): {'mpm': {'managers': {'cpan': {'extra_env': {'PERL_MM_USE_DEFAULT': '1'}, 'homepage_url': 'https://www.cpan.org', 'logo': 'perl', 'name': 'Perl CPAN', 'operations': {'install': {'args': ['{package_id}'], 'sudo': True}, 'installed': {'args': ['-l'], 'regex': '^(?P<package_id>\\S+)\\t(?:undef|(?P<installed_version>\\S+))$'}, 'outdated': {'args': ['-O'], 'regex': '^(?P<package_id>\\S+)\\s+(?P<installed_version>\\S+)\\s+(?P<latest_version>\\S+)$'}, 'upgrade_all': {'args': ['-u'], 'sudo': True}, 'upgrade_one': {'args': ['-i', '{package_id}'], 'sudo': True}}, 'platforms': ['all_platforms'], 'requirement': '>=1.64', 'version_cli_options': ['-v'], 'version_regexes': ['CPAN\\.pm\\s+version\\s+(?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'Loading internal logger. Log::Log4perl recommended for better logging\nO\t1.03\nErrno\t1.33\nEncode\t3.08_01\nmeta_notation\tundef', 'packages': [{'id': 'O', 'version': '1.03'}, {'id': 'Errno', 'version': '1.33'}, {'id': 'Encode', 'version': '3.08_01'}, {'id': 'meta_notation'}]}], 'outdated': [{'output': "Loading internal logger. Log::Log4perl recommended for better logging\nReading '/Users/kde/.cpan/Metadata'\n  Database was generated on Thu, 26 Mar 2026 13:41:03 GMT\nModule Name                                Local    CPAN\n---------------------------------------------------------------\nAlgorithm::C3                             0.1000  0.1100\nArchive::Tar                              2.3800  3.0400", 'packages': [{'id': 'Algorithm::C3', 'version': '0.1100'}, {'id': 'Archive::Tar', 'version': '3.0400'}]}], 'version': {'expected': '2.28', 'output': '>(info): /usr/bin/cpan script version 1.676, CPAN.pm version 2.28'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/fink.toml'): {'mpm': {'managers': {'fink': {'cli_names': ['fink'], 'homepage_url': 'https://www.finkproject.org', 'internal_sudo': True, 'maintenance_note': '[Fink](https://github.com/fink/fink) is in deep maintenance mode: no engine release since `0.45.6` (February 2022), though its package tree still receives occasional commits.', 'name': 'Fink', 'operations': {'cleanup_cache': {'args': ['--yes', 'cleanup']}, 'install': {'args': ['--yes', 'install', '{package_id}']}, 'installed': {'args': ['list', '--tab', '--installed'], 'regex': '^(?: i |\\(i\\)|\\*i\\*)\\t(?P<package_id>[^\\t]+)\\t(?P<installed_version>[^\\t]+)\\t'}, 'remove': {'args': ['--yes', 'remove', '{package_id}']}, 'search': {'args': ['list', '--tab', '{query}'], 'regex': '^[^\\t]*\\t(?P<package_id>[^\\t]+)\\t(?P<latest_version>[^\\t]*)\\t'}, 'sync': {'args': ['--yes', 'selfupdate']}, 'upgrade_all': {'args': ['--yes', 'update-all']}, 'upgrade_one': {'args': ['--yes', 'update', '{package_id}']}}, 'platforms': ['macos'], 'version_regexes': ['Package manager version: (?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': ' i \tfiglet\t2.2.5-1\tPrints text as ASCII art\n(i)\tnano\t6.2-1\tSmall editor\n   \tlynx\t2.9.0-1\tText browser is not installed', 'packages': [{'id': 'figlet', 'version': '2.2.5-1'}, {'id': 'nano', 'version': '6.2-1'}]}], 'version': {'expected': '0.45.6', 'output': 'Package manager version: 0.45.6'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/gcloud.toml'): {'mpm': {'managers': {'gcloud': {'cli_names': ['gcloud'], 'homepage_url': 'https://cloud.google.com/sdk/gcloud', 'logo': 'googlecloud', 'name': 'Google Cloud CLI', 'operations': {'install': {'args': ['components', 'install', '--quiet', '{package_id}']}, 'installed': {'args': ['components', 'list', '--only-local-state', '--format=value(id,current_version_string)'], 'regex': '^(?P<package_id>[^\\t]+)\\t(?P<installed_version>[^\\t]*)'}, 'outdated': {'args': ['components', 'list', "--filter=state.name='Update Available'", '--format=value(id,current_version_string,latest_version_string)'], 'regex': '^(?P<package_id>[^\\t]+)\\t(?P<installed_version>[^\\t]*)\\t(?P<latest_version>[^\\t]+)'}, 'remove': {'args': ['components', 'remove', '--quiet', '{package_id}']}, 'upgrade_all': {'args': ['components', 'update', '--quiet']}}, 'platforms': ['linux_like', 'macos', 'windows'], 'requirement': '>=170.0.0', 'version_regexes': ['^Google Cloud SDK[ \\t]+(?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'bq\t2.0.83\ncore\t2026.07.24\ngsutil\t5.37', 'packages': [{'id': 'bq', 'version': '2.0.83'}, {'id': 'core', 'version': '2026.07.24'}, {'id': 'gsutil', 'version': '5.37'}]}], 'outdated': [{'output': 'bq\t2.0.83\t2.0.84\ngsutil\t5.37\t5.38', 'packages': [{'id': 'bq', 'version': '2.0.84'}, {'id': 'gsutil', 'version': '5.38'}]}], 'version': {'expected': '578.0.0', 'output': 'Google Cloud SDK 578.0.0\nbq 2.1.36\nbundled-python3-unix 3.14.6\ncore 2026.07.24\ngcloud-crc32c 1.0.0\ngsutil 5.37'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/gh_ext.toml'): {'mpm': {'managers': {'gh-ext': {'cli_names': ['gh'], 'homepage_url': 'https://cli.github.com', 'logo': 'github', 'name': 'GitHub CLI extensions', 'operations': {'install': {'args': ['extension', 'install', '{package_id}']}, 'installed': {'args': ['extension', 'list'], 'regex': '^gh \\S+\\t(?P<package_id>\\S+/\\S+)\\t(?P<installed_version>\\S+)'}, 'remove': {'args': ['extension', 'remove', '{package_id}']}, 'search': {'args': ['extension', 'search', '{query}'], 'regex': '(?P<package_id>\\S+/\\S+)'}, 'upgrade_all': {'args': ['extension', 'upgrade', '--all']}, 'upgrade_one': {'args': ['extension', 'upgrade', '{package_id}']}}, 'platforms': ['linux_like', 'macos', 'windows'], 'requirement': '>=2.0.0', 'version_regexes': ['gh version\\s+(?P<version>\\d+\\.\\d+\\.\\d+)']}}}, 'samples': {'installed': [{'output': 'gh dash\tdlvhdr/gh-dash\tv4.7.0\ngh cockpit\tgithub/gh-cockpit\ta1b2c3d4', 'packages': [{'id': 'dlvhdr/gh-dash', 'version': 'v4.7.0'}, {'id': 'github/gh-cockpit', 'version': 'a1b2c3d4'}]}], 'search': [{'output': 'dlvhdr/gh-dash\tA rich terminal UI for GitHub\n\tvilmibm/gh-screensaver\tScreensavers for your terminal\ninstalled\tcli/gh-webhook\tForward webhooks to localhost', 'packages': [{'id': 'dlvhdr/gh-dash'}, {'id': 'vilmibm/gh-screensaver'}, {'id': 'cli/gh-webhook'}]}], 'version': {'expected': '2.62.0', 'output': 'gh version 2.62.0 (2024-11-14)'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/haxelib.toml'): {'mpm': {'managers': {'haxelib': {'cli_names': ['haxelib'], 'homepage_url': 'https://lib.haxe.org', 'logo': 'haxe', 'name': 'Haxe haxelib', 'operations': {'install': {'args': ['--global', '--always', '--quiet', 'install', '{package_id}']}, 'installed': {'args': ['--global', 'list'], 'regex': '^(?P<package_id>[A-Za-z0-9_.-]+):(?:[^\\[]*\\[(?!dev:)(?P<installed_version>[^\\]]+)\\])?'}, 'remove': {'args': ['--global', 'remove', '{package_id}']}, 'search': {'args': ['--global', '--quiet', 'search', '{query}'], 'regex': '^(?P<package_id>[A-Za-z0-9_.-]{3,})$'}, 'upgrade_all': {'args': ['--global', '--always', '--quiet', 'update']}, 'upgrade_one': {'args': ['--global', '--always', '--quiet', 'update', '{package_id}']}}, 'platforms': ['all_platforms'], 'requirement': '>=4.0.0', 'version_regexes': ['^(?P<version>\\d+\\.\\d+\\.\\d+(?:-[a-z]+(?:\\.\\d+)?)?)']}}}, 'samples': {'installed': [{'output': 'flixel: 5.8.0 [git]\nformat: [3.5.0]\nhamcrest: [3.0.0]\nhxcpp: [4.3.2]\nthx.semver: 0.2.2 [git]', 'packages': [{'id': 'flixel', 'version': 'git'}, {'id': 'format', 'version': '3.5.0'}, {'id': 'hamcrest', 'version': '3.0.0'}, {'id': 'hxcpp', 'version': '4.3.2'}, {'id': 'thx.semver', 'version': 'git'}]}], 'search': [{'output': 'flixel\nflixel-addons\nflixel-ui\n3 libraries found', 'packages': [{'id': 'flixel'}, {'id': 'flixel-addons'}, {'id': 'flixel-ui'}]}], 'version': {'expected': '4.2.0', 'output': '4.2.0'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/jpm.toml'): {'mpm': {'managers': {'jpm': {'cli_names': ['jpm'], 'homepage_url': 'https://github.com/janet-lang/jpm', 'name': 'jpm', 'operations': {'cleanup_cache': {'args': ['clear-cache']}, 'install': {'args': ['install', '{package_id}']}, 'remove': {'args': ['uninstall', '{package_id}']}, 'sync': {'args': ['update-pkgs']}, 'upgrade_all': {'args': ['update-installed']}}, 'platforms': ['linux_like', 'macos', 'windows'], 'version_cli': 'janet', 'version_cli_options': ['-v'], 'version_regexes': ['^(?P<version>\\d+\\.\\d+\\.\\d+)']}}}, 'samples': {'version': {'expected': '1.41.2', 'output': '1.41.2-homebrew'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/juliaup.toml'): {'mpm': {'managers': {'juliaup': {'cli_names': ['juliaup'], 'homepage_url': 'https://github.com/JuliaLang/juliaup', 'logo': 'julia', 'name': 'Julia juliaup', 'operations': {'cleanup_orphan': {'args': ['gc']}, 'install': {'args': ['add', '{package_id}']}, 'installed': {'args': ['status'], 'regex': '^[ \\t]*(?:\\*[ \\t]+)?(?P<package_id>\\S+)[ \\t]+(?P<installed_version>\\d\\S*)'}, 'outdated': {'args': ['status'], 'regex': '^[ \\t]*(?:\\*[ \\t]+)?(?P<package_id>\\S+)[ \\t]+(?P<installed_version>\\d\\S*)[^\\n]*?\\bUpdate to (?P<latest_version>\\S+) available[ \\t]*$'}, 'remove': {'args': ['remove', '{package_id}']}, 'search': {'args': ['list'], 'regex': '^[ \\t]*(?P<package_id>\\S+)[ \\t]+(?P<latest_version>\\d\\S*)[ \\t]*$'}, 'upgrade_all': {'args': ['update']}, 'upgrade_one': {'args': ['update', '{package_id}']}}, 'platforms': ['linux_like', 'macos', 'windows'], 'requirement': '>=1.21.0', 'version_regexes': ['^Juliaup[ \\t]+(?P<version>\\S+)$']}}}, 'samples': {'installed': [{'output': ' Default  Channel       Version                                                        Update\n----------------------------------------------------------------------------------------------\n          1.6           1.6.7+0.x64.apple.darwin14\n          1.9           1.9.4+0.aarch64.apple.darwin14\n          1.10          1.10.11+0.aarch64.apple.darwin14\n          1.11          1.11.9+0.aarch64.apple.darwin14\n          1.12          1.12.6+0.aarch64.apple.darwin14\n          1.13          1.13.0-rc1+0.aarch64.apple.darwin14\n          1.13-nightly  1.13.0-rc1.105\n          nightly       1.14.0-DEV.2820\n          pr62430       1.14.0-DEV.2741 https://github.com/JuliaLang/julia/pull/62430', 'packages': [{'id': '1.6', 'version': '1.6.7+0.x64.apple.darwin14'}, {'id': '1.9', 'version': '1.9.4+0.aarch64.apple.darwin14'}, {'id': '1.10', 'version': '1.10.11+0.aarch64.apple.darwin14'}, {'id': '1.11', 'version': '1.11.9+0.aarch64.apple.darwin14'}, {'id': '1.12', 'version': '1.12.6+0.aarch64.apple.darwin14'}, {'id': '1.13', 'version': '1.13.0-rc1+0.aarch64.apple.darwin14'}, {'id': '1.13-nightly', 'version': '1.13.0-rc1.105'}, {'id': 'nightly', 'version': '1.14.0-DEV.2820'}, {'id': 'pr62430', 'version': '1.14.0-DEV.2741'}]}], 'version': {'expected': '1.21.0', 'output': 'Juliaup 1.21.0'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/krew.toml'): {'mpm': {'managers': {'krew': {'cli_names': ['kubectl-krew', 'krew'], 'extra_env': {'NO_COLOR': '1'}, 'homepage_url': 'https://krew.sigs.k8s.io', 'logo': 'kubernetes', 'name': 'kubectl krew', 'operations': {'install': {'args': ['install', '{package_id}']}, 'installed': {'args': ['list'], 'regex': '^(?P<package_id>[a-z0-9][a-z0-9-]*)$'}, 'remove': {'args': ['uninstall', '{package_id}']}, 'search': {'args': ['search', '{query}'], 'regex': '^(?P<package_id>[a-z0-9][a-z0-9-]*)\\s{2,}\\S'}, 'sync': {'args': ['update']}, 'upgrade_all': {'args': ['upgrade']}, 'upgrade_one': {'args': ['upgrade', '{package_id}']}}, 'platforms': ['all_platforms'], 'requirement': '>=0.4.0', 'version_regexes': ['GitTag\\s+v?(?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'access-matrix\nctx\nns\ntree', 'packages': [{'id': 'access-matrix'}, {'id': 'ctx'}, {'id': 'ns'}, {'id': 'tree'}]}], 'search': [{'output': 'NAME           DESCRIPTION                                          INSTALLED\naccess-matrix  Show an RBAC access matrix for server resources      yes\nktop           A top-like tool for your Kubernetes clusters         no\ntree           Show sub-resources of the Kubernetes object          yes', 'packages': [{'id': 'access-matrix'}, {'id': 'ktop'}, {'id': 'tree'}]}], 'version': {'expected': '0.4.4', 'output': 'OPTION            VALUE\nGitTag            v0.4.4\nGitCommit         343e657\nIndexURI          https://github.com/kubernetes-sigs/krew-index.git\nBasePath          /home/kev/.krew\nIndexPath         /home/kev/.krew/index/default\nInstallPath       /home/kev/.krew/store\nBinPath           /home/kev/.krew/bin\nDetectedPlatform  linux/amd64'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/macports.toml'): {'mpm': {'managers': {'macports': {'cli_names': ['port'], 'cli_search_path': ['/opt/local/bin'], 'default_sudo': True, 'homepage_url': 'https://www.macports.org', 'logo': 'macports', 'name': 'MacPorts', 'operations': {'cleanup_cache': {'args': ['clean', '--all', 'installed'], 'sudo': True}, 'doctor': {'args': ['diagnose', '--quiet']}, 'install': {'args': ['install', '{package_id}'], 'sudo': True}, 'installed': {'args': ['-q', 'installed'], 'regex': '^\\s+(?P<package_id>\\S+)\\s+@(?P<installed_version>[^+\\s]+)\\S*\\s+\\(active\\)'}, 'outdated': {'args': ['-q', 'outdated'], 'regex': '^(?P<package_id>\\S+)\\s+(?P<installed_version>\\S+)\\s+<\\s+(?P<latest_version>\\S+)'}, 'remove': {'args': ['uninstall', '{package_id}'], 'sudo': True}, 'search': {'args': ['search', '--name', '{extended_args}', '{exact_args}', '--line', '{query}'], 'exact_args': ['--exact'], 'extended_args': ['--description'], 'regex': '^(?P<package_id>[^\\t]+)\\t(?P<latest_version>[^\\t]+)\\t[^\\t]+\\t.+$'}, 'sync': {'args': ['sync'], 'sudo': True}, 'upgrade_all': {'args': ['upgrade', 'outdated'], 'sudo': True}, 'upgrade_one': {'args': ['upgrade', '{package_id}'], 'sudo': True}}, 'platforms': ['macos'], 'requirement': '>=2.0.0', 'version_cli_options': ['version'], 'version_regexes': ['Version:\\s+(?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': '  curl @8.7.1_0 (active)\n  python312 @3.12.3_0+lzma+optimizations (active)\n  vim @9.1.0_0 (active)', 'packages': [{'id': 'curl', 'version': '8.7.1_0'}, {'id': 'python312', 'version': '3.12.3_0'}, {'id': 'vim', 'version': '9.1.0_0'}]}], 'outdated': [{'output': 'curl                            8.7.1_0 < 8.8.0_0\npython312                       3.12.3_0 < 3.12.4_0', 'packages': [{'id': 'curl', 'version': '8.8.0_0'}, {'id': 'python312', 'version': '3.12.4_0'}]}], 'search': [{'output': 'MacVim\t9.1.1092\taqua\tMacVim - VIM for macOS\nvim\t9.1.1092\teditors\tVi IMproved', 'packages': [{'id': 'MacVim', 'version': '9.1.1092'}, {'id': 'vim', 'version': '9.1.1092'}]}], 'version': {'expected': '2.12.4', 'output': 'Version: 2.12.4'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/micro.toml'): {'mpm': {'managers': {'micro': {'cli_names': ['micro'], 'homepage_url': 'https://micro-editor.github.io', 'name': 'micro editor', 'operations': {'install': {'args': ['-plugin', 'install', '{package_id}']}, 'installed': {'args': ['-plugin', 'list'], 'regex': '^(?P<package_id>\\S+)(?:[ \\t]+\\(built-in\\)|[ \\t]+\\((?P<installed_version>[^)]+)\\))?$'}, 'remove': {'args': ['-plugin', 'remove', '{package_id}']}, 'search': {'args': ['-plugin', 'available'], 'regex': '^(?P<package_id>\\S+)$'}, 'upgrade_all': {'args': ['-plugin', 'update']}, 'upgrade_one': {'args': ['-plugin', 'update', '{package_id}']}}, 'platforms': ['all_platforms'], 'requirement': '>=2.0.0', 'version_cli_options': ['-version'], 'version_regexes': ['^Version:[ \\t]+(?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'The following plugins are currently installed:\neditorconfig (1.0.0)\nfzf (1.1.1)\nlsp (0.6.2)\nautoclose (built-in)\ncomment (built-in)\ndiff (built-in)', 'packages': [{'id': 'editorconfig', 'version': '1.0.0'}, {'id': 'fzf', 'version': '1.1.1'}, {'id': 'lsp', 'version': '0.6.2'}, {'id': 'autoclose'}, {'id': 'comment'}, {'id': 'diff'}]}], 'version': {'expected': '2.0.12', 'output': 'Version: 2.0.12\nCommit hash: c2cebaa3\nCompiled on May 16, 2024'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/ollama.toml'): {'mpm': {'managers': {'ollama': {'cli_names': ['ollama'], 'homepage_url': 'https://ollama.com', 'logo': 'ollama', 'name': 'Ollama', 'operations': {'install': {'args': ['pull', '{package_id}']}, 'installed': {'args': ['list'], 'regex': '^(?P<package_id>\\S+:\\S+)[ \\t]+[0-9a-f]{12}[ \\t]'}, 'remove': {'args': ['rm', '{package_id}']}, 'upgrade_one': {'args': ['pull', '{package_id}']}}, 'platforms': ['linux_like', 'macos', 'windows'], 'requirement': '>=0.5.0', 'version_regexes': ['version is (?P<version>\\S+)']}}}, 'samples': {'version': {'expected': '0.32.8', 'output': 'ollama version is 0.32.8\nWarning: client version is 0.32.9'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/opam.toml'): {'mpm': {'managers': {'opam': {'cli_names': ['opam'], 'homepage_url': 'https://opam.ocaml.org', 'logo': 'ocaml', 'name': 'OCaml opam', 'operations': {'install': {'args': ['install', '{package_id}']}, 'installed': {'args': ['list', '--installed', '--short', '--columns=name,version'], 'regex': '^(?P<package_id>\\S+)\\s+(?P<installed_version>\\S+)$'}, 'remove': {'args': ['remove', '{package_id}']}, 'search': {'args': ['list', '--search', '{query}', '--short', '--columns=name,version'], 'regex': '^(?P<package_id>\\S+)\\s+(?P<latest_version>\\S+)$'}, 'sync': {'args': ['update']}, 'upgrade_all': {'args': ['upgrade']}, 'upgrade_one': {'args': ['upgrade', '{package_id}']}}, 'platforms': ['bsd', 'linux_like', 'macos'], 'pre_args': ['--color=never', '--yes'], 'requirement': '>=2.0.0', 'version_regexes': ['(?P<version>\\d+(?:\\.\\d+)+\\S*)']}}}, 'samples': {'installed': [{'output': 'base-bigarray base\nbase-threads base\ndune 3.11.1\nocaml 4.14.1\nocamlfind 1.9.6', 'packages': [{'id': 'base-bigarray', 'version': 'base'}, {'id': 'base-threads', 'version': 'base'}, {'id': 'dune', 'version': '3.11.1'}, {'id': 'ocaml', 'version': '4.14.1'}, {'id': 'ocamlfind', 'version': '1.9.6'}]}], 'search': [{'output': 'lwt 5.7.0\nlwt_ppx 2.1.0\nlwt_ssl 1.2.0', 'packages': [{'id': 'lwt', 'version': '5.7.0'}, {'id': 'lwt_ppx', 'version': '2.1.0'}, {'id': 'lwt_ssl', 'version': '1.2.0'}]}], 'version': {'expected': '2.1.5', 'output': '2.1.5'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/opkg.toml'): {'mpm': {'managers': {'opkg': {'homepage_url': 'https://git.yoctoproject.org/cgit/cgit.cgi/opkg/', 'logo': 'openwrt', 'name': 'opkg', 'operations': {'install': {'args': ['install', '{package_id}']}, 'installed': {'args': ['list-installed'], 'regex': '^(?P<package_id>\\S+) - (?P<installed_version>\\S+)'}, 'outdated': {'args': ['list-upgradable'], 'regex': '^(?P<package_id>\\S+) - (?P<installed_version>\\S+) - (?P<latest_version>\\S+)'}, 'remove': {'args': ['remove', '{package_id}']}, 'search': {'args': ['list'], 'regex': '^(?P<package_id>\\S+) - (?P<latest_version>\\S+) - .+$'}, 'sync': {'args': ['update']}, 'upgrade_all': {'args': ['upgrade']}, 'upgrade_one': {'args': ['upgrade', '{package_id}']}}, 'platforms': ['unix_without_macos'], 'requirement': '>=0.2.0', 'version_regexes': ['opkg\\s+version\\s+(?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': '3rd-party-feed-configs - 1.1-r0\naio-grab - 1.0+git71+c79e264-r0', 'packages': [{'id': '3rd-party-feed-configs', 'version': '1.1-r0'}, {'id': 'aio-grab', 'version': '1.0+git71+c79e264-r0'}]}], 'outdated': [{'output': 'openpli-bootlogo - 20190717-r0 - 20190718-r0\nenigma2-hotplug - 2.7+git1720+55c6b34-r0 - 2.7+git1722+daf2f52-r0', 'packages': [{'id': 'openpli-bootlogo', 'version': '20190718-r0'}, {'id': 'enigma2-hotplug', 'version': '2.7+git1722+daf2f52-r0'}]}], 'search': [{'output': 'bash - 5.0-r0 - An sh-compatible command language interpreter\nbusybox - 1.31.0-r0 - Tiny versions of many common UNIX utilities', 'packages': [{'id': 'bash', 'version': '5.0-r0'}, {'id': 'busybox', 'version': '1.31.0-r0'}]}], 'version': {'expected': '0.3.6', 'output': 'opkg version 0.3.6 (libsolv 0.7.5)'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/pamac.toml'): {'mpm': {'managers': {'pamac': {'cli_names': ['pamac'], 'homepage_url': 'https://github.com/manjaro/pamac-cli', 'logo': 'manjaro', 'name': 'Manjaro pamac', 'operations': {'cleanup_cache': {'args': ['clean', '--no-confirm']}, 'cleanup_orphan': {'args': ['remove', '--no-confirm', '--orphans']}, 'install': {'args': ['install', '--no-confirm', '{package_id}']}, 'installed': {'args': ['list', '--installed'], 'regex': '^(?P<package_id>\\S+)\\s+(?P<installed_version>\\S+)'}, 'orphans': {'args': ['list', '--orphans'], 'regex': '^(?P<package_id>\\S+)\\s+(?P<installed_version>\\S+)'}, 'outdated': {'args': ['checkupdates', '--quiet', '--no-aur'], 'regex': '^(?P<package_id>\\S+)\\s{2}(?P<installed_version>\\S+) -> (?P<latest_version>\\S+)$'}, 'remove': {'args': ['remove', '--no-confirm', '{package_id}']}, 'search': {'args': ['search', '--no-aur', '{query}'], 'regex': '^(?P<package_id>\\S+)\\s{2}(?P<latest_version>\\S+)\\s'}, 'upgrade_all': {'args': ['upgrade', '--no-confirm', '--no-aur']}, 'upgrade_one': {'args': ['install', '--no-confirm', '--no-upgrade', '{package_id}']}}, 'platforms': ['linux_like'], 'requirement': '>=11.0.0', 'version_regexes': ['^\\S+\\s+(?P<version>\\d+\\.\\d+\\.\\d+)']}}}, 'samples': {'installed': [{'output': 'plymouth 26.134.222-1  extra  1.2 MB\nventoy   1.1.12-1      extra  16.8 MB', 'packages': [{'id': 'plymouth', 'version': '26.134.222-1'}, {'id': 'ventoy', 'version': '1.1.12-1'}]}], 'outdated': [{'output': 'plymouth  26.134.222-1 -> 26.134.222-2\nventoy  1.1.12-1 -> 1.1.14-1', 'packages': [{'id': 'plymouth', 'version': '26.134.222-2'}, {'id': 'ventoy', 'version': '1.1.14-1'}]}], 'version': {'expected': '11.7.4', 'output': 'pamac-cli 11.7.4  -  libpamac 11.7.4'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/pkg_tools.toml'): {'mpm': {'managers': {'pkg-tools': {'cli_names': ['pkg_info'], 'cli_search_path': ['/usr/sbin'], 'default_sudo': True, 'homepage_url': 'https://man.openbsd.org/pkg_add', 'logo': 'openbsd', 'name': 'OpenBSD pkg tools', 'operations': {'cleanup_orphan': {'args': ['-a', '-I'], 'cli': 'pkg_delete', 'sudo': True}, 'install': {'args': ['-I', '{package_id}'], 'cli': 'pkg_add', 'sudo': True}, 'installed': {'args': ['-I', '-a'], 'regex': '^(?P<package_id>\\S+)-(?P<installed_version>\\d\\S*)\\s'}, 'orphans': {'args': ['-a', '-I', '-n'], 'cli': 'pkg_delete', 'regex': '^(?P<package_id>\\S+)-(?P<installed_version>\\d[^\\s:]*): ok$'}, 'remove': {'args': ['-I', '{package_id}'], 'cli': 'pkg_delete', 'sudo': True}, 'search': {'args': ['-Q', '{query}'], 'regex': '^(?P<package_id>\\S+)-(?P<latest_version>\\d\\S*)'}, 'upgrade_all': {'args': ['-u', '-I'], 'cli': 'pkg_add', 'sudo': True}, 'upgrade_one': {'args': ['-u', '-I', '{package_id}'], 'cli': 'pkg_add', 'sudo': True}}, 'platforms': ['openbsd'], 'version_cli': 'uname', 'version_cli_options': ['-r'], 'version_regexes': ['^(?P<version>[\\d.]+)']}}}, 'samples': {'installed': [{'output': 'unzip-6.0p17        Extract files from ZIP archives\nlunzip-1.14p0       Lzip decompressor', 'packages': [{'id': 'unzip', 'version': '6.0p17'}, {'id': 'lunzip', 'version': '1.14p0'}]}], 'orphans': [{'output': 'autoconf-2.71p2: ok\nautomake-1.16.5: ok', 'packages': [{'id': 'autoconf', 'version': '2.71p2'}, {'id': 'automake', 'version': '1.16.5'}]}], 'search': [{'output': 'lunzip-1.14p0\nunzip-6.0p17 (installed)\nunzip-6.0p17-iconv', 'packages': [{'id': 'lunzip', 'version': '1.14p0'}, {'id': 'unzip', 'version': '6.0p17'}, {'id': 'unzip', 'version': '6.0p17-iconv'}]}], 'version': {'expected': '7.7', 'output': '7.7'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/pkgin.toml'): {'mpm': {'managers': {'pkgin': {'cli_names': ['pkgin'], 'default_sudo': True, 'homepage_url': 'https://pkgin.net', 'logo': 'netbsd', 'name': 'Pkgin', 'operations': {'cleanup_cache': {'args': ['clean'], 'sudo': True}, 'install': {'args': ['-y', 'install', '{package_id}'], 'sudo': True}, 'installed': {'args': ['-p', 'list'], 'regex': '^(?P<package_id>.+)-(?P<installed_version>\\d[^;]*);'}, 'outdated': {'args': ['-p', '-l', '<', 'list'], 'regex': '^(?P<package_id>.+)-(?P<latest_version>\\d[^;]*);<;'}, 'remove': {'args': ['-y', 'remove', '{package_id}'], 'sudo': True}, 'search': {'args': ['search', '{query}'], 'regex': '^(?P<package_id>\\S+)-(?P<latest_version>\\d\\S*)'}, 'sync': {'args': ['update'], 'sudo': True}, 'upgrade_all': {'args': ['-y', 'upgrade'], 'sudo': True}, 'upgrade_one': {'args': ['-y', 'install', '{package_id}'], 'sudo': True}}, 'platforms': ['netbsd'], 'version_cli_options': ['-v'], 'version_regexes': ['pkgin (?P<version>[\\d.]+)']}}}, 'samples': {'installed': [{'output': "lbdb-0.48.1nb1;The little brother's database\nmutt-1.14.5;Text-based MIME mail client", 'packages': [{'id': 'lbdb', 'version': '0.48.1nb1'}, {'id': 'mutt', 'version': '1.14.5'}]}], 'outdated': [{'output': 'abook-0.6.2;<;Text-based addressbook program', 'packages': [{'id': 'abook', 'version': '0.6.2'}]}], 'search': [{'output': 'abook-0.6.1          Text-based addressbook program\nmutt-1.14.5 =        Text-based MIME mail client\n=: package is installed and up-to-date\n<: package is installed but newer version is available\n>: installed package has a greater version than available package', 'packages': [{'id': 'abook', 'version': '0.6.1'}, {'id': 'mutt', 'version': '1.14.5'}]}], 'version': {'expected': '26.4.0', 'output': 'pkgin 26.4.0 (using SQLite 3.45.1)'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/pyenv.toml'): {'mpm': {'managers': {'pyenv': {'cli_names': ['pyenv'], 'homepage_url': 'https://github.com/pyenv/pyenv', 'logo': 'python', 'name': 'Python pyenv', 'operations': {'install': {'args': ['install', '--skip-existing', '{package_id}']}, 'installed': {'args': ['versions', '--bare', '--skip-aliases', '--skip-envs'], 'regex': '^(?P<package_id>\\S+)$'}, 'remove': {'args': ['uninstall', '--force', '{package_id}']}, 'search': {'args': ['install', '--list'], 'regex': '^  (?P<package_id>\\S+)$'}}, 'platforms': ['linux_like', 'macos'], 'requirement': '>=2.3.13', 'version_regexes': ['^pyenv[ \\t]+(?P<version>\\d+(?:\\.\\d+)+)']}}}, 'samples': {'installed': [{'output': '3.11.9\n3.12.11\n3.13.7\n3.14.0rc1\ngraalpy-24.1.1\nminiconda3-latest\npypy3.11-7.3.20', 'packages': [{'id': '3.11.9'}, {'id': '3.12.11'}, {'id': '3.13.7'}, {'id': '3.14.0rc1'}, {'id': 'graalpy-24.1.1'}, {'id': 'miniconda3-latest'}, {'id': 'pypy3.11-7.3.20'}]}], 'search': [{'output': 'Available versions:\n  2.1.3\n  2.2.3\n  2.3.7', 'packages': [{'id': '2.1.3'}, {'id': '2.2.3'}, {'id': '2.3.7'}]}], 'version': {'expected': '2.8.4', 'output': 'pyenv 2.8.4'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/rustup.toml'): {'mpm': {'managers': {'rustup': {'cli_names': ['rustup'], 'extra_env': {'RUSTUP_TERM_COLOR': 'never', 'RUSTUP_TERM_PROGRESS_WHEN': 'never'}, 'homepage_url': 'https://rustup.rs', 'logo': 'rust', 'name': 'Rust rustup', 'operations': {'install': {'args': ['toolchain', 'install', '--no-self-update', '{package_id}']}, 'installed': {'args': ['toolchain', 'list'], 'regex': '^(?P<package_id>\\S+-\\S+)'}, 'outdated': {'args': ['check'], 'regex': '^(?P<package_id>\\S+-\\S+) - [Uu]pdate available\\s*:\\s*(?P<installed_version>[^\\s(]\\S*).*? -> (?P<latest_version>[^\\s(]\\S*)'}, 'remove': {'args': ['toolchain', 'uninstall', '{package_id}']}, 'upgrade_all': {'args': ['update', '--no-self-update']}, 'upgrade_one': {'args': ['update', '--no-self-update', '{package_id}']}}, 'platforms': ['linux_like', 'macos', 'windows'], 'requirement': '>=1.28.0', 'version_regexes': ['^rustup (?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'stable-x86_64-unknown-linux-gnu (active, default)\nnightly-x86_64-unknown-linux-gnu\n1.14-x86_64-unknown-linux-gnu\n1.62.0-x86_64-unknown-linux-gnu\n1.71-x86_64-unknown-linux-gnu', 'packages': [{'id': 'stable-x86_64-unknown-linux-gnu'}, {'id': 'nightly-x86_64-unknown-linux-gnu'}, {'id': '1.14-x86_64-unknown-linux-gnu'}, {'id': '1.62.0-x86_64-unknown-linux-gnu'}, {'id': '1.71-x86_64-unknown-linux-gnu'}]}], 'outdated': [{'output': 'stable-x86_64-unknown-linux-gnu - update available: 1.0.0 (hash-stable-1.0.0) -> 1.1.0 (hash-stable-1.1.0)\nnightly-x86_64-unknown-linux-gnu - up to date: 1.1.0 (hash-nightly-1.1.0)', 'packages': [{'id': 'stable-x86_64-unknown-linux-gnu', 'version': '1.1.0'}]}], 'version': {'expected': '1.29.0', 'output': 'rustup 1.29.0 (28d1352db 2026-03-05)'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/slapt_get.toml'): {'mpm': {'managers': {'slapt-get': {'cli_names': ['slapt-get'], 'default_sudo': True, 'homepage_url': 'https://software.jaos.org/', 'logo': 'slackware', 'name': 'slapt-get', 'operations': {'cleanup_cache': {'args': ['--clean'], 'sudo': True}, 'install': {'args': ['--install', '--no-prompt', '{package_id}'], 'sudo': True}, 'installed': {'args': ['--installed'], 'regex': '^(?P<package_id>.+)-(?P<installed_version>[^-\\s]+)-[^-\\s]+-[^-\\s]+ \\[inst=yes\\]: '}, 'remove': {'args': ['--remove', '--no-prompt', '{package_id}'], 'sudo': True}, 'search': {'args': ['--search', '{query}'], 'regex': '^(?P<package_id>.+)-(?P<latest_version>[^-\\s]+)-[^-\\s]+-[^-\\s]+ \\[inst=(?:yes|no)\\]: '}, 'sync': {'args': ['--update'], 'sudo': True}, 'upgrade_all': {'args': ['--upgrade', '--no-prompt'], 'sudo': True}, 'upgrade_one': {'args': ['--install', '--no-prompt', '{package_id}'], 'sudo': True}}, 'platforms': ['slackware'], 'version_regexes': ['slapt-get version (?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'tree-2.3.2-x86_64-1 [inst=yes]: display directory tree\nutil-linux-2.39-x86_64-1 [inst=yes]: collection of utilities', 'packages': [{'id': 'tree', 'version': '2.3.2'}, {'id': 'util-linux', 'version': '2.39'}]}], 'search': [{'output': 'tree-2.3.2-x86_64-1 [inst=no]: display directory tree', 'packages': [{'id': 'tree', 'version': '2.3.2'}]}], 'version': {'expected': '0.11.12', 'output': 'slapt-get version 0.11.12'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/soar.toml'): {'mpm': {'managers': {'soar': {'cli_names': ['soar'], 'homepage_url': 'https://github.com/pkgforge/soar', 'name': 'Soar', 'operations': {'cleanup_cache': {'args': ['clean', '--cache']}, 'install': {'args': ['install', '-y', '{package_id}']}, 'installed': {'args': ['list-installed'], 'regex': '^(?P<package_id>.+)-(?P<installed_version>\\d[^:]*):'}, 'remove': {'args': ['remove', '-y', '{package_id}']}, 'search': {'args': ['search', '{query}'], 'regex': '^\\[.\\] (?P<package_id>[^#\\s]+)#'}, 'sync': {'args': ['sync']}, 'upgrade_all': {'args': ['update']}, 'upgrade_one': {'args': ['update', '{package_id}']}}, 'platforms': ['linux_like'], 'pre_args': ['--no-color'], 'requirement': '>=0.12.0', 'version_regexes': ['soar\\s+(?P<version>\\d+\\.\\d+\\.\\d+)']}}}, 'samples': {'installed': [{'output': 'bat-0.24.0:soarpkgs (2025-01-15) (1.8 MB)\ngoogle-chrome-131.0:soarpkgs (2025-01-14) (95.2 MB)\n7-zip-24.09:soarpkgs (2025-01-10) (1.5 MB) [Broken]', 'packages': [{'id': 'bat', 'version': '0.24.0'}, {'id': 'google-chrome', 'version': '131.0'}, {'id': '7-zip', 'version': '24.09'}]}], 'search': [{'output': '[+] bat#official:soarpkgs | 0.24.0 | archive - A cat clone (1.8 MB)\n[✓] ripgrep#official:soarpkgs | 14.1.0 | archive - Fast search (4.2 MB)', 'packages': [{'id': 'bat'}, {'id': 'ripgrep'}]}], 'version': {'expected': '0.12.6', 'output': 'soar 0.12.6'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/sorcery.toml'): {'mpm': {'managers': {'sorcery': {'cli_names': ['sorcery'], 'default_sudo': True, 'homepage_url': 'https://sourcemage.org', 'maintenance_note': '[Source Mage](https://sourcemage.org) is a small, slow-moving distribution; its `sorcery` scripts still receive commits but formal releases are rare.', 'name': 'Sorcery', 'operations': {'cleanup_cache': {'args': ['--sweep'], 'cli': 'cleanse', 'sudo': True}, 'install': {'args': ['{package_id}'], 'cli': 'cast', 'sudo': True}, 'installed': {'args': ['-q', 'installed'], 'cli': 'gaze', 'regex': '^(?P<package_id>[^:]+):[^:]*:(?:installed|held):(?P<installed_version>.+)$'}, 'remove': {'args': ['{package_id}'], 'cli': 'dispel', 'sudo': True}, 'search': {'args': ['-q', 'search', '-name', '{query}'], 'cli': 'gaze', 'regex': '^(?P<package_id>\\S+) (?P<latest_version>\\S+) @'}, 'sync': {'args': ['update'], 'cli': 'scribe', 'sudo': True}, 'upgrade_all': {'args': ['upgrade'], 'sudo': True}, 'upgrade_one': {'args': ['{package_id}'], 'cli': 'cast', 'sudo': True}}, 'platforms': ['sourcemage'], 'version_regexes': ['^(?P<version>[\\d.]+)$']}}}, 'samples': {'installed': [{'output': 'cowsay:20240108:installed:3.03\nvim:20230101:held:9.0', 'packages': [{'id': 'cowsay', 'version': '3.03'}, {'id': 'vim', 'version': '9.0'}]}], 'search': [{'output': 'cowsay 3.03 @test\ntree 2.1.1 @stable', 'packages': [{'id': 'cowsay', 'version': '3.03'}, {'id': 'tree', 'version': '2.1.1'}]}], 'version': {'expected': '20240108', 'output': '20240108'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/steamcmd.toml'): {'mpm': {'managers': {'steamcmd': {'cli_names': ['steamcmd'], 'homepage_url': 'https://developer.valvesoftware.com/wiki/SteamCMD', 'logo': 'steam', 'name': 'Valve SteamCMD', 'operations': {'install': {'args': ['+app_update', '{package_id}', 'validate']}, 'upgrade_one': {'args': ['+app_update', '{package_id}', 'validate']}}, 'platforms': ['all_platforms'], 'post_args': ['+quit'], 'version_cli_options': ['+quit'], 'version_regexes': ['Valve Corporation - version (?P<version>\\S+)']}}}, 'samples': {'version': {'expected': '1648077083', 'output': 'Steam Console Client (c) Valve Corporation - version 1648077083'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/stew.toml'): {'mpm': {'managers': {'stew': {'homepage_url': 'https://github.com/marwanhawari/stew', 'maintenance_note': '[stew](https://github.com/marwanhawari/stew) is a single-maintainer project; its last release was `0.6.0` (April 2025) and issues have since accumulated, though it is not archived.', 'name': 'stew', 'operations': {'install': {'args': ['install', '{package_id}']}, 'installed': {'args': ['list', '--tags'], 'regex': '^(?P<package_id>\\S+?):(?:\\S+?/\\S+?@v?(?P<installed_version>\\S+)|\\S+)$'}, 'remove': {'args': ['uninstall', '{package_id}']}, 'upgrade_all': {'args': ['upgrade', '--all']}, 'upgrade_one': {'args': ['upgrade', '{package_id}']}}, 'platforms': ['linux_like', 'macos', 'all_windows'], 'requirement': '>=0.3.0', 'version_regexes': ['stew version v(?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'gh:cli/cli@v2.4.0\nfzf:junegunn/fzf@0.29.0\nhyperfine:https://github.com/sharkdp/hyperfine/releases/download/v1.12.0/hyperfine-v1.12.0-x86_64-apple-darwin.tar.gz\nrg:BurntSushi/ripgrep@13.0.0', 'packages': [{'id': 'gh', 'version': '2.4.0'}, {'id': 'fzf', 'version': '0.29.0'}, {'id': 'hyperfine'}, {'id': 'rg', 'version': '13.0.0'}]}], 'version': {'expected': '0.6.0', 'output': 'stew version v0.6.0'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/swupd.toml'): {'mpm': {'managers': {'swupd': {'default_sudo': True, 'homepage_url': 'https://github.com/clearlinux/swupd-client', 'logo': 'intel', 'name': 'Clear Linux Software Updater', 'operations': {'cleanup_cache': {'args': ['clean'], 'sudo': True}, 'install': {'args': ['bundle-add', '--assume=yes', '{package_id}'], 'sudo': True}, 'installed': {'args': ['bundle-list', '--quiet'], 'regex': '^(?P<package_id>\\S+)$'}, 'remove': {'args': ['bundle-remove', '--assume=yes', '{package_id}'], 'sudo': True}, 'search': {'args': ['bundle-list', '--all', '--quiet'], 'regex': '^(?P<package_id>\\S+)$'}, 'upgrade_all': {'args': ['update', '--assume=yes'], 'sudo': True}}, 'platforms': ['clearlinux'], 'unmaintained': True, 'unmaintained_message': 'Intel discontinued Clear Linux OS in July 2025 and the [swupd-client repository](https://github.com/clearlinux/swupd-client) is archived read-only, so `swupd` no longer receives updates.', 'version_regexes': ['swupd (?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'editors\nos-core\nos-core-update', 'packages': [{'id': 'editors'}, {'id': 'os-core'}, {'id': 'os-core-update'}]}], 'search': [{'output': 'curl\neditors\nos-core', 'packages': [{'id': 'curl'}, {'id': 'editors'}, {'id': 'os-core'}]}], 'version': {'expected': '7.0.0', 'output': 'swupd 7.0.0'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/tlmgr.toml'): {'mpm': {'managers': {'tlmgr': {'cli_names': ['tlmgr'], 'homepage_url': 'https://www.tug.org/texlive/', 'logo': 'latex', 'name': 'TeX Live Manager', 'operations': {'cleanup_cache': {'args': ['backup', '--clean', '--all'], 'sudo': True}, 'doctor': {'args': ['check', 'all']}, 'install': {'args': ['install', '{package_id}'], 'sudo': True}, 'installed': {'args': ['info', '--only-installed', '--data', 'name,localrev'], 'regex': '^(?P<package_id>[^,]+),(?P<installed_version>\\d+)$'}, 'outdated': {'args': ['--machine-readable', 'update', '--list', '--all'], 'regex': '^(?P<package_id>[^\\t]+)\\tu\\t(?P<installed_version>\\d+)\\t(?P<latest_version>\\d+)\\t'}, 'remove': {'args': ['remove', '{package_id}'], 'sudo': True}, 'search': {'args': ['search', '--global', '{query}'], 'regex': '^(?P<package_id>[^\\s:]+) - '}, 'sync': {'args': ['update', '--self'], 'sudo': True}, 'upgrade_all': {'args': ['update', '--all'], 'sudo': True}, 'upgrade_one': {'args': ['update', '{package_id}'], 'sudo': True}}, 'platforms': ['unix', 'windows'], 'requirement': '>=2018', 'version_regexes': ['TeX Live.*version (?P<version>\\d{4})']}}}, 'samples': {'installed': [{'output': 'tlmgr: package repository https://mirror.ctan.org (verified)\nsansmath,15878\ntitlesec,68677', 'packages': [{'id': 'sansmath', 'version': '15878'}, {'id': 'titlesec', 'version': '68677'}]}], 'outdated': [{'output': 'location-url\thttps://mirror.ctan.org\ntotal-bytes\t323544\nend-of-header\nadjmulticol\tu\t62935\t63073\t316000\t-\t-\t-\t-\t-\nend-of-updates\nrunning mktexlsr ...', 'packages': [{'id': 'adjmulticol', 'version': '63073'}]}], 'version': {'expected': '2023', 'output': 'tlmgr revision 66798 (2023-04-08 02:15:21 +0200)\ntlmgr using installation: /usr/local/texlive/2023\nTeX Live (https://tug.org/texlive) version 2023'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/topgrade.toml'): {'mpm': {'managers': {'topgrade': {'cli_names': ['topgrade'], 'homepage_url': 'https://github.com/topgrade-rs/topgrade', 'internal_sudo': True, 'name': 'Topgrade', 'operations': {'upgrade_all': {'args': ['--yes']}}, 'platforms': ['bsd', 'linux_like', 'macos', 'all_windows'], 'requirement': '>=17.0.0', 'version_regexes': ['topgrade\\s+(?P<version>\\S+)']}}}, 'samples': {'version': {'expected': '17.4.0', 'output': 'topgrade 17.4.0'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/urpmi.toml'): {'mpm': {'managers': {'urpmi': {'cli_names': ['urpmi'], 'default_sudo': True, 'extra_env': {'LC_ALL': 'C'}, 'homepage_url': 'https://wiki.mageia.org/en/URPMI', 'name': 'urpmi', 'operations': {'cleanup_cache': {'args': ['--clean'], 'sudo': True}, 'install': {'args': ['--auto', '{package_id}'], 'sudo': True}, 'installed': {'args': ['--query', '--all', '--queryformat', '%{NAME} %{VERSION}-%{RELEASE}\\n'], 'cli': 'rpm', 'regex': '^(?P<package_id>\\S+) (?P<installed_version>\\S+)$'}, 'outdated': {'args': ['--auto-select', '-r'], 'cli': 'urpmq', 'regex': '^(?P<package_id>.+)-(?P<latest_version>[^-]+-[^-]+)$'}, 'remove': {'args': ['--auto', '{package_id}'], 'cli': 'urpme', 'sudo': True}, 'search': {'args': ['--fuzzy', '{query}', '-r'], 'cli': 'urpmq', 'regex': '^(?P<package_id>.+)-(?P<latest_version>[^-]+-[^-]+)$'}, 'sync': {'args': ['-a'], 'cli': 'urpmi.update', 'sudo': True}, 'upgrade_all': {'args': ['--auto-select', '--auto'], 'sudo': True}, 'upgrade_one': {'args': ['--auto', '{package_id}'], 'sudo': True}}, 'platforms': ['mageia', 'mandriva'], 'version_regexes': ['urpmi (?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'sgml-skel 0.7-24.mga9\ndesktop-file-utils 0.26-5.mga9', 'packages': [{'id': 'sgml-skel', 'version': '0.7-24.mga9'}, {'id': 'desktop-file-utils', 'version': '0.26-5.mga9'}]}], 'outdated': [{'output': 'kernel-desktop-6.6.0-1.mga9\nfvwm3-1.0.2-1.1.mga8', 'packages': [{'id': 'kernel-desktop', 'version': '6.6.0-1.mga9'}, {'id': 'fvwm3', 'version': '1.0.2-1.1.mga8'}]}], 'version': {'expected': '8.121.7', 'output': 'urpmi 8.121.7'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/vscode.toml'): {'mpm': {'managers': {'vscode': {'brewfile_entry_type': 'vscode', 'cli_names': ['code'], 'homepage_url': 'https://code.visualstudio.com', 'name': 'Visual Studio Code', 'operations': {'install': {'args': ['--install-extension', '{package_id}']}, 'installed': {'args': ['--list-extensions', '--show-versions'], 'regex': '^(?P<package_id>[^@\\s]+)@(?P<installed_version>\\S+)$'}, 'remove': {'args': ['--uninstall-extension', '{package_id}']}}, 'platforms': ['all_platforms'], 'requirement': '>=1.60.0'}}}, 'samples': {'installed': [{'output': 'ms-python.python@2021.9.1246542782\nms-toolsai.jupyter@2021.8.2041215044\ntamasfe.even-better-toml@0.14.2', 'packages': [{'id': 'ms-python.python', 'version': '2021.9.1246542782'}, {'id': 'ms-toolsai.jupyter', 'version': '2021.8.2041215044'}, {'id': 'tamasfe.even-better-toml', 'version': '0.14.2'}]}], 'version': {'expected': '1.60.2', 'output': '1.60.2\n7f6ab5485bbc008386c4386d08766667e155244e\nx64'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/vscodium.toml'): {'mpm': {'managers': {'vscodium': {'brewfile_skip_warning': 'Skipping {count} VSCodium extension(s) in Brewfile output: brew bundle has no `vscodium` entry type, and emitting them as `vscode` would install them to VS Code instead of VSCodium.', 'cli_names': ['codium'], 'homepage_url': 'https://vscodium.com', 'logo': 'vscodium', 'name': 'VSCodium', 'operations': {'install': {'args': ['--install-extension', '{package_id}']}, 'installed': {'args': ['--list-extensions', '--show-versions'], 'regex': '^(?P<package_id>[^@\\s]+)@(?P<installed_version>\\S+)$'}, 'remove': {'args': ['--uninstall-extension', '{package_id}']}}, 'platforms': ['all_platforms'], 'requirement': '>=1.60.0'}}}, 'samples': {'installed': [{'output': 'tamasfe.even-better-toml@0.14.2', 'packages': [{'id': 'tamasfe.even-better-toml', 'version': '0.14.2'}]}], 'version': {'expected': '1.60.2', 'output': '1.60.2\n7f6ab5485bbc008386c4386d08766667e155244e\nx64'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/xcodes.toml'): {'mpm': {'managers': {'xcodes': {'cli_names': ['xcodes'], 'homepage_url': 'https://github.com/XcodesOrg/xcodes', 'logo': 'xcode', 'name': 'Xcode xcodes', 'operations': {'installed': {'args': ['installed'], 'regex': '^(?P<package_id>[^\\t]+?)(?: [(\\[][^\\t]*)?\\t'}, 'remove': {'args': ['uninstall', '{package_id}']}, 'sync': {'args': ['update']}}, 'platforms': ['macos'], 'requirement': '>=1.0.0', 'version_cli_options': ['version'], 'version_regexes': ['^(?P<version>\\d+(?:\\.\\d+)+)$']}}}, 'samples': {'installed': [{'output': '15.4 (15F31d)\t/Applications/Xcode-15.4.0.app\n16.2 (16C5032a)\t/Applications/Xcode-16.2.0.app\n26.2 (17C52) [Apple Silicon]\t/Applications/Xcode-26.2.0.app\n26.4 Release Candidate (17E192)\t/Applications/Xcode-26.4.0-Release.Candidate.app', 'packages': [{'id': '15.4'}, {'id': '16.2'}, {'id': '26.2'}, {'id': '26.4 Release Candidate'}]}], 'version': {'expected': '2.0.3', 'output': '2.0.3'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/yazi.toml'): {'mpm': {'managers': {'yazi': {'cli_names': ['ya'], 'homepage_url': 'https://yazi-rs.github.io', 'name': 'Yazi ya', 'operations': {'install': {'args': ['pkg', 'add', '{package_id}']}, 'installed': {'args': ['pkg', 'list'], 'regex': '^\\t(?P<package_id>\\S+)[ \\t]+\\((?P<installed_version>[^)]+)\\)$'}, 'remove': {'args': ['pkg', 'delete', '{package_id}']}, 'upgrade_all': {'args': ['pkg', 'upgrade']}, 'upgrade_one': {'args': ['pkg', 'upgrade', '{package_id}']}}, 'platforms': ['all_platforms'], 'requirement': '>=25.2.7', 'version_cli_options': ['--version'], 'version_regexes': ['^Ya[ \\t]+(?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'Plugins:\n\tyazi-rs/plugins:full-border (3f2b882)\nFlavors:\n\tyazi-rs/flavors:catppuccin-mocha (be0b21d)', 'packages': [{'id': 'yazi-rs/plugins:full-border', 'version': '3f2b882'}, {'id': 'yazi-rs/flavors:catppuccin-mocha', 'version': 'be0b21d'}]}], 'version': {'expected': '26.5.6', 'output': 'Ya 26.5.6 (aa52643 2026-05-05)'}}}, PosixPath('/Users/kde/code/meta-package-manager/meta_package_manager/managers/zerobrew.toml'): {'mpm': {'managers': {'zerobrew': {'cli_names': ['zb'], 'cli_search_path': ['/opt/zerobrew/bin'], 'extra_env': {'NO_COLOR': '1'}, 'homepage_url': 'https://github.com/lucasgelfond/zerobrew', 'name': 'zerobrew', 'operations': {'install': {'args': ['install', '{package_id}']}, 'installed': {'args': ['list'], 'regex': '^(?P<package_id>\\S+)\\s+(?P<installed_version>\\S+)$'}, 'outdated': {'args': ['outdated', '--json'], 'fields': {'installed_version': 'installed_versions[0]', 'latest_version': 'current_version', 'package_id': 'name'}, 'format': 'json'}, 'remove': {'args': ['uninstall', '{package_id}']}, 'upgrade_all': {'args': ['upgrade']}, 'upgrade_one': {'args': ['upgrade', '{package_id}']}}, 'platforms': ['linux_like', 'macos'], 'requirement': '>=0.3.0', 'version_regexes': ['zb\\s+(?P<version>\\S+)']}}}, 'samples': {'installed': [{'output': 'jq 1.7.1\nwget 1.24.5', 'packages': [{'id': 'jq', 'version': '1.7.1'}, {'id': 'wget', 'version': '1.24.5'}]}], 'outdated': [{'output': '[\n  {\n    "name": "jq",\n    "installed_versions": [\n      "1.7.1"\n    ],\n    "current_version": "1.7.2"\n  }\n]', 'packages': [{'id': 'jq', 'version': '1.7.2'}]}], 'version': {'expected': '0.3.0', 'output': 'zb 0.3.0'}}}}

Parsed content of each shipped file: the definition and its [samples].

The samples are the source-derived output fixtures feeding test_bundled_version_regex and test_bundled_parsing, so adding a bundled manager (with its samples) extends those tests without touching this module.

tests.test_manager_definition.test_bundled_definitions_are_valid()[source]

Every shipped definition parses and reports a repo-relative TOML source.

tests.test_manager_definition.test_bundled_ids_disjoint_from_builtins()[source]
tests.test_manager_definition.test_bundled_inventory()[source]

The pool registers exactly one bundled manager per shipped TOML file.

Catches a loader-side skip: a malformed file is logged and dropped at load time, so the pool silently missing an ID would be the only trace.

tests.test_manager_definition.test_bundled_registered(toml_path)[source]

Each shipped definition file lands in the pool as a config-defined manager.

Every expectation derives from the file itself, so a new bundled manager is covered without extending any hardcoded list here. The file must carry nothing but its definition and the test samples locking its parsers.

tests.test_manager_definition.test_gh_ext_capabilities(operation, expected)[source]
tests.test_manager_definition.test_bundled_version_regex(manager_id, output, expected)[source]

Lock each bundled definition’s version probe to the output sample shipped in its TOML file, derived from the upstream tool’s own source or documentation.

tests.test_manager_definition.test_soar_capabilities(operation, expected)[source]
tests.test_manager_definition.test_steamcmd_capabilities(operation, expected)[source]

SteamCMD exposes no inventory: only install and per-title upgrade exist, both mapping to the same idempotent +app_update command.

tests.test_manager_definition.test_bundled_parsing(manager_id, operation, output, expected)[source]

Lock each bundled definition’s parsers to the output samples shipped in its TOML file, derived from the upstream tools’ own source code or documentation.

Versions are compared as raw captured strings (omitted from the sample when the regex captures no version group). which is stubbed so operations declaring a sibling cli resolve without the real binary installed.

tests.test_manager_homebrew module

Homebrew-specific tests.

tests.test_manager_homebrew.test_trust_tap_skips_core_packages(manager_class, package_id)[source]

Core formulae and casks live on trusted taps and must not trigger trust.

tests.test_manager_homebrew.test_trust_tap_qualified_package(manager_class, package_id, tap_id)[source]

Tap-qualified IDs are tapped (idempotent) and trusted before install.

tests.test_manager_homebrew.test_upgrade_all_cli_greedy_is_cask_only(manager_class, ignore_auto_updates)[source]

–include-auto-updates adds –greedy to cask’s upgrade-all command only.

brew upgrade rejects --greedy alongside --formula (mutually exclusive options), so brew must never grow the flag.

tests.test_manager_homebrew.test_install_routes_through_trust_tap(manager_class)[source]

install() always calls trust_tap() so the gate is uniform across paths.

tests.test_manager_mas module

Mac App Store parsing tests.

These tests cover the pure-Python JSON-stream parser. They do not invoke mas and are platform-agnostic.

tests.test_manager_mas.test_parse_json_stream(output, expected)[source]

tests.test_manager_mise module

mise output-parsing and CLI-construction tests.

These tests stub run_cli and exercise the pure-Python branches of the Mise manager. They do not invoke the real mise binary.

tests.test_manager_mise.manager()[source]
tests.test_manager_mise.test_installed_yields_packages(manager, stub_run_cli)[source]
tests.test_manager_mise.test_installed_handles_empty(manager, stub_run_cli, output)[source]
tests.test_manager_mise.test_installed_yields_one_package_per_version(manager, stub_run_cli)[source]
tests.test_manager_mise.test_installed_preserves_backend_prefix(manager, stub_run_cli)[source]

Backend-prefixed IDs like pipx:ruff round-trip without rewriting.

tests.test_manager_mise.test_outdated_yields_upgrades(manager, stub_run_cli)[source]
tests.test_manager_mise.test_outdated_handles_empty(manager, stub_run_cli, output)[source]
tests.test_manager_mise.test_search_parses_table(manager, stub_run_cli)[source]
tests.test_manager_mise.test_search_keeps_descriptions_with_inner_spaces(manager, stub_run_cli)[source]

A description containing single-space-separated words is preserved intact: the regex only treats the run of 2+ spaces between columns as a delimiter.

tests.test_manager_mise.test_install_builds_spec(manager, capture_run_cli, version, expected_spec)[source]
tests.test_manager_mise.test_upgrade_all_cli_omits_target(manager)[source]
tests.test_manager_mise.test_upgrade_one_cli_builds_spec(manager, version, expected_spec)[source]
tests.test_manager_mise.test_remove_uses_all_flag(manager, capture_run_cli)[source]

--all matches mpm’s “remove the package, full stop” contract even when mise has multiple versions of the same tool installed.

tests.test_manager_mise.test_sync_runs_plugins_update(manager, capture_run_cli)[source]
tests.test_manager_mise.test_cleanup_runs_cache_clear(manager, capture_run_cli)[source]

tests.test_manager_overrides module

Tests for the per-manager override mechanism driven by the [mpm.managers.<id>] configuration section.

tests.test_manager_overrides.OVERRIDE_TARGET = 'pip'

Manager ID used as a fixture target for override tests.

pip is portable across all test platforms and never unmaintained, so its presence in the pool is stable.

tests.test_manager_overrides.reset_overrides()[source]

Restore the override target’s instance state before and after each test.

The pool is a module-level singleton: any attribute we shadow on a manager instance via apply_manager_overrides() would leak to the next test. This fixture pops every overridable field and every cache-invalidated property from the instance __dict__, and clears the pool’s overridden-fields tracking dict so the next test starts from class defaults.

Cleanup runs both before (in case a prior test in the same xdist worker left the pool dirty) and after (to not pollute subsequent tests).

tests.test_manager_overrides.test_overridable_fields_match_base_attributes()[source]

Every overridable field must exist on the PackageManager base class.

Drift between OVERRIDABLE_FIELDS and the base class is silent at runtime (setattr happily creates new attributes) so this test acts as the safety net.

tests.test_manager_overrides.test_none_is_noop()[source]
tests.test_manager_overrides.test_empty_is_noop()[source]
tests.test_manager_overrides.test_non_dict_top_level_raises()[source]
tests.test_manager_overrides.test_unknown_manager_raises()[source]
tests.test_manager_overrides.test_non_dict_manager_section_raises()[source]
tests.test_manager_overrides.test_unknown_field_raises(reset_overrides)[source]
tests.test_manager_overrides.test_str_tuple_override_replaces_default(reset_overrides)[source]
tests.test_manager_overrides.test_str_tuple_override_coerces_list_to_tuple(reset_overrides)[source]
tests.test_manager_overrides.test_str_tuple_override_rejects_bare_string(reset_overrides)[source]
tests.test_manager_overrides.test_str_tuple_override_rejects_non_string_entries(reset_overrides)[source]
tests.test_manager_overrides.test_bool_override(reset_overrides)[source]
tests.test_manager_overrides.test_bool_override_rejects_int(reset_overrides)[source]
tests.test_manager_overrides.test_int_override(reset_overrides)[source]
tests.test_manager_overrides.test_int_override_rejects_bool(reset_overrides)[source]

A boolean is not an acceptable integer override even though bool subclasses int in Python.

tests.test_manager_overrides.test_int_override_rejects_string(reset_overrides)[source]
tests.test_manager_overrides.test_str_override(reset_overrides)[source]
tests.test_manager_overrides.test_str_override_rejects_int(reset_overrides)[source]
tests.test_manager_overrides.test_dict_override(reset_overrides)[source]
tests.test_manager_overrides.test_dict_override_rejects_non_string_value(reset_overrides)[source]
tests.test_manager_overrides.test_multiple_fields_in_one_call(reset_overrides)[source]
tests.test_manager_overrides.test_cached_property_evicted_on_override(reset_overrides)[source]

Force a cached_property to be computed, then verify the override evicts it.

tests.test_manager_overrides.test_per_manager_timeout_beats_global_default(reset_overrides)[source]

A per-manager timeout override must survive the global setattr loop in _select_managers: the most specific value wins.

tests.test_manager_overrides.test_timeout_binds_detection_phase(reset_overrides, monkeypatch, override, expected)[source]

_select_managers binds the version-detection probes to the resolved timeout before warm_availability runs them: the global --timeout reaches a plain candidate, while a per-manager override still keeps precedence.

tests.test_manager_overrides.test_per_manager_ignore_auto_updates_beats_global_default(reset_overrides)[source]
tests.test_manager_overrides.test_global_default_still_applies_without_override()[source]

Sanity: with no per-manager override, the global default reaches the manager.

tests.test_manager_overrides.test_sudo_override(reset_overrides)[source]
tests.test_manager_overrides.test_sudo_override_rejects_int(reset_overrides)[source]
tests.test_manager_overrides.test_per_manager_sudo_beats_global_default(reset_overrides)[source]

A per-manager sudo override survives the global setattr loop, so a manager can opt out of (or into) escalation regardless of the global --sudo.

tests.test_manager_overrides.test_sudo_is_a_risky_override_field()[source]

[mpm.managers.<id>] sudo = true from an untrusted source can run a binary as root, so it must trip the untrusted-source warning like pre_cmds.

tests.test_manager_overrides.test_cli_loads_manager_overrides(invoke, create_config, reset_overrides)[source]

End-to-end: mpm --config <path> applies overrides from [mpm.managers.<id>].

tests.test_manager_overrides.test_cli_fails_on_unknown_manager_in_config(invoke, create_config)[source]

A typo’d manager ID in the config aborts the CLI with a precise dotted path. Permissive warn-and-skip behavior was removed: the same validator that powers --validate-config runs at normal load time.

tests.test_manager_overrides.test_contribution_hint_fields_subset_of_overridable()[source]

The hint allowlist must only name real overridable fields.

tests.test_manager_overrides.test_apply_returns_hint_for_detection_field(reset_overrides)[source]
tests.test_manager_overrides.test_apply_returns_no_hint_for_preference_field(reset_overrides)[source]

Overriding a preference field (timeout) must not generate a hint: it’s not a detection bug signal.

tests.test_manager_overrides.test_apply_returns_no_hint_when_overrides_empty()[source]
tests.test_manager_overrides.test_apply_raises_on_unknown_manager_so_no_hint_emitted()[source]

Apply aborts on unknown manager IDs via the validator, so no hints leak. The validator runs before any side effects, so the pool is unchanged.

tests.test_manager_overrides.test_apply_returns_hints_for_each_detection_field(reset_overrides)[source]

Multiple detection-related overrides on the same manager produce one hint per field.

tests.test_manager_overrides.test_build_issue_url_targets_bug_template()[source]
tests.test_manager_overrides.test_build_issue_url_asserts_on_oversize_url()[source]

A pathological override value must trip the URL-length assertion rather than silently producing a URL that GitHub would truncate.

tests.test_manager_overrides.test_build_issue_url_url_decodes_back_to_useful_body()[source]

Round-trip: the URL-encoded body must decode back to text mentioning the manager id, the field, and the user’s value.

tests.test_manager_overrides.test_build_issue_url_includes_detected_cli_path_when_available()[source]
tests.test_manager_overrides.test_format_contribution_hints_empty_returns_empty_string()[source]
tests.test_manager_overrides.test_format_contribution_hints_lists_each_with_url()[source]
tests.test_manager_overrides.test_cli_prints_contribution_hint(invoke, create_config, reset_overrides)[source]

End-to-end: a config with a detection-related override surfaces the hint.

tests.test_manager_overrides.test_cli_no_hint_for_preference_override(invoke, create_config, reset_overrides)[source]

Overriding only a preference field must not surface a hint.

tests.test_manager_overrides.test_cli_opt_out_suppresses_hint(invoke, create_config, reset_overrides)[source]

--no-suggest-contribs silences the hint output.

tests.test_manager_overrides.test_cli_opt_out_via_config(invoke, create_config, reset_overrides)[source]

The [mpm] suggest_contribs = false config also silences the hint.

tests.test_manager_overrides.test_dump_manager_overrides_skips_none_values()[source]

Attributes whose current value is None are omitted from the dump.

tests.test_manager_overrides.test_dump_manager_overrides_converts_tuples_to_lists()[source]

Tuples are converted to lists so tomli_w can serialize them.

tests.test_manager_overrides.test_dump_manager_overrides_keys_are_alphabetical()[source]

The output is canonical: dict insertion order matches sorted field names.

tests.test_manager_overrides.test_config_template_round_trips(manager_id)[source]

For every manager, dump → tomli_w → tomllib → converter yields the same value. Catches schema drift between OVERRIDABLE_FIELDS converters and the live attribute types.

tests.test_manager_overrides.test_cli_config_template_one_manager(invoke)[source]

mpm config-template <id> produces a single parseable manager section.

tests.test_manager_overrides.test_cli_config_template_multiple_managers(invoke)[source]

mpm config-template <id1> <id2> dumps each requested manager.

tests.test_manager_overrides.test_cli_config_template_no_args_dumps_all_maintained(invoke)[source]

With no positional args, every maintained manager appears.

tests.test_manager_overrides.test_cli_config_template_unknown_manager_errors(invoke)[source]
tests.test_manager_overrides.test_cli_config_template_output_is_applicable(invoke, reset_overrides)[source]

End-to-end: pipe mpm config-template <id> output back through apply_manager_overrides and confirm the pool is unchanged.

tests.test_manager_pip module

Pip-specific tests for bundled-app detection and interpreter discovery.

tests.test_manager_pip.test_bundled_app_detected_under_cellar()[source]

A sys.prefix under a Homebrew Cellar is conclusive on its own.

tests.test_manager_pip.test_bundled_app_detected_by_brew_installer(prefix)[source]

Outside Cellar, an INSTALLER of brew still flags the bundle.

tests.test_manager_pip.test_brew_installer_case_and_whitespace_insensitive()[source]

The INSTALLER tag is matched case- and whitespace-insensitively.

tests.test_manager_pip.test_user_install_not_flagged(installer)[source]

A user-managed install (pip/uv/…) outside Cellar is not a bundle.

tests.test_manager_pip.test_bundled_app_false_when_mpm_absent()[source]

Running from source (mpm not installed) is not a bundle.

tests.test_manager_pip.test_pip_install_blocked_parses_probe(stdout, expected)[source]

The probe’s 1/0 output maps to blocked/not-blocked.

tests.test_manager_pip.test_pip_install_blocked_keeps_candidate_on_error(error)[source]

A failed probe must not hide a candidate: default to not-blocked.

tests.test_manager_pip.test_search_prepends_current_interpreter_for_user_install()[source]

A normal install probes the running interpreter first.

tests.test_manager_pip.test_search_skips_bundled_interpreter()[source]

A distributor bundle skips the running interpreter and falls back to PATH.

tests.test_manager_pip.test_search_skips_pep668_blocked_candidate()[source]

Externally-managed, non-virtualenv interpreters (PEP 668) are dropped.

tests.test_manager_pip.test_search_unavailable_when_all_candidates_blocked()[source]

With every interpreter blocked, discovery yields nothing (pip unavailable).

tests.test_manager_pipx module

pipx output-parsing and CLI-construction tests.

These tests stub run_cli and exercise the pure-Python branches of the Pipx manager. They do not invoke the real pipx binary.

tests.test_manager_pipx.manager()[source]
tests.test_manager_pipx.test_installed_yields_packages(manager, stub_run_cli)[source]
tests.test_manager_pipx.test_installed_handles_empty(manager, stub_run_cli, output)[source]
tests.test_manager_pipx.test_outdated_version_gate(manager, capture_run_cli, version, expected_call)[source]

The native outdated query needs pipx >= 1.16.0; older ones fall back to enumerating the venvs before probing each with its embedded pip.

tests.test_manager_pipx.test_outdated_native_parses_envelope(manager, stub_run_cli)[source]
tests.test_manager_pipx.test_outdated_native_handles_empty(manager, stub_run_cli, output)[source]
tests.test_manager_pipx.test_outdated_legacy_silences_dependencies(manager, monkeypatch)[source]

The per-venv pip probe only reports the venv’s main package, even when the probe also lists its outdated dependencies.

tests.test_manager_uv module

uv-specific behaviors, starting with the launched-by-uv cache guard.

A parent uv run (or uvx) process keeps a lock on the uv cache for as long as its child lives, so an mpm launched through it must not attempt uv cache clean: the command would wait on its own ancestor for``UV_LOCK_TIMEOUT`` and fail. uv marks its children with the UV environment variable.

tests.test_manager_uv.capture_uv_run_cli(capture_run_cli)[source]

Record the uv manager’s run_cli invocations instead of executing.

tests.test_manager_uv.test_cleanup_cache_skipped_under_uv(monkeypatch, caplog, capture_uv_run_cli)[source]

Launched by uv run, the cache commands are skipped with a warning: they would deadlock on the parent’s cache lock until UV_LOCK_TIMEOUT.

tests.test_manager_uv.test_cleanup_cache_runs_outside_uv(monkeypatch, capture_uv_run_cli)[source]

Outside a uv launcher, the cache clean and prune both run.

tests.test_manager_winget module

WinGet-specific parsing tests.

These tests cover the pure-Python parsing/regex logic. They do not invoke winget.exe and are platform-agnostic.

tests.test_manager_winget.winget()[source]
tests.test_manager_winget.test_store_id_re_matches(package_id)[source]
tests.test_manager_winget.test_store_id_re_rejects(package_id)[source]
tests.test_manager_winget.test_parse_details_ignores_indented_upgrade_line(winget)[source]

Regression test: the package-block split must not fire on indented winget [version] lines under Available Upgrades.

tests.test_manager_winget.test_parse_details_filter_by_source(winget)[source]
tests.test_manager_winget.test_parse_table_handles_short_header_line(winget)[source]

Regression test: winget may emit a header line shorter than the separator (trailing spaces on the last column are trimmed). The separator must drive the column width so data rows that fill the full width do not trip the width assertion.

tests.test_manager_winget.test_parse_table_yields_nothing_on_empty_output(winget)[source]
tests.test_manager_winget.test_build_package_native(winget)[source]
tests.test_manager_winget.test_build_package_store_uses_msstore_sentinel(winget, package_id)[source]
tests.test_manager_winget.test_search_orders_native_before_store(winget, monkeypatch)[source]

Microsoft Store entries must come after winget-native ones in the search output, regardless of the order winget returned them in.

tests.test_managers module

tests.test_managers.test_xkcd_set()[source]
tests.test_managers.test_unmaintained(manager)[source]

An unmaintained manager must document itself with a non-empty unmaintained_message, and any URL embedded in that markdown message must be a well-formed http(s) link.

Conversely, an unmaintained_message is only meaningful on an unmaintained manager.

tests.test_managers.test_maintenance_note(manager)[source]

A maintenance_note is a MyST markdown block whose embedded URLs must be well-formed http(s) links.

It is mutually exclusive with the unmaintained flag: a confirmed-dead manager documents itself with an unmaintained_message instead of a watch note.

tests.test_managers.test_ascii_id(manager)[source]

All package manager IDs should be short ASCII strings.

tests.test_managers.test_name(manager)[source]

Check all managers have a name.

tests.test_managers.test_unique_names()[source]
tests.test_managers.test_homepage_url(manager)[source]
tests.test_managers.test_platforms(manager)[source]

Check platforms is normalized as a frozenset.

tests.test_managers.test_requirement(manager)[source]

Each manager is required to specify a version requirement or None.

tests.test_managers.test_cli_names_type(manager)[source]

Check the pointed CLI name and path are file-system compatible.

tests.test_managers.test_cli_search_path(manager)[source]
tests.test_managers.test_extra_env_type(manager)[source]

Check that definitions environment variables as a dict of strings.

tests.test_managers.test_global_args_type(manager)[source]

Check that definitions returns CLI args as a list of non-empty strings.

tests.test_managers.test_version_cli_options(manager)[source]

Version CLI options must be a list of non empty strings.

tests.test_managers.test_version_regexes(manager)[source]

Version regex is required.

Check it compiles and match has a <version> group.

tests.test_managers.test_version_regex_matches_sample(manager_id, sample_output, expected_version)[source]

Each manager’s version_regexes parses the documented --version output.

Regression guard for the case where re.VERBOSE silently swallowed literal spaces in patterns like guix \(GNU Guix\) ..., leaving the manager unable to detect its own version.

tests.test_managers.test_cli_path(manager)[source]
tests.test_managers.test_query_parts(query, query_parts)[source]
tests.test_managers.test_package_matches(pkg_id, pkg_name, query, extended, exact, expected)[source]
tests.test_managers.CANONICAL_ATTRS = ('scope', 'unmaintained', 'unmaintained_message', 'maintenance_note', 'id', 'name', 'homepage_url', 'logo', 'brewfile_entry_type', 'brewfile_skip_warning', 'platforms', 'virtual', 'sudo', 'default_sudo', 'internal_sudo', 'requirement', 'cooldown_env_var', 'cli_names', 'cli_search_path', 'extra_env', 'pre_cmds', 'pre_args', 'post_args', 'version_cli', 'version_cli_options', 'version_regexes', 'ignore_auto_updates', 'stop_on_error', 'dry_run', 'plan', 'timeout', '_active_operation', 'progress', 'cooldown', 'require_cooldown_support', 'windows_creation_flags', 'windows_processes_to_cleanup', 'cli_errors', '_last_run', 'run_cache', '_NAME_VERSION_REGEXP')

Canonical declaration order of the attributes a manager class may override.

The documented convention every manager module follows: identity first, then the escalation policy, the version requirement, the CLI plumbing, the version probe, and the rarely-overridden toggles. Manager-specific constants (the _*_REGEXP parsers) are not governed and conventionally sit between the attributes and the operations. Consumed by test_content_order, which checks each manager’s relative declaration order against this sequence.

tests.test_managers.collect_props_ref()[source]

Build the canonical member-order reference for manager classes.

Attributes follow CANONICAL_ATTRS; methods follow the base classes’ own declaration order, CLIExecutor’s (binary discovery, version probe, execution engine) before PackageManager’s (helpers, availability, then the operations). The curated attribute list is cross-checked against the base classes so a new base attribute cannot silently escape governance.

tests.test_managers.test_operation_order()[source]

Double check operation IDs are ordered and aligned to the base manager class and CLI implementation.

The subcommands span two section modules: the read-only queries in cli_explore, the state changers and diagnostics in cli_maintenance. Walked in that order, their definitions must follow the enum order.

tests.test_managers.test_content_order(manager_class)[source]

Lint each manager class to check its members follow the canonical order.

Attributes must follow CANONICAL_ATTRS and methods the base classes’ own declaration order (see collect_props_ref()); ungoverned names (manager-specific parsing constants and helpers) may sit anywhere.

tests.test_metadata module

Guard the CI test matrix against drifting away from the project metadata.

repomatic generates the GitHub Actions test matrix from a hard-coded set of Python versions, independently of the project’s requires-python. Nothing forces the two to agree, so a requires-python bump that forgets the matrix, or a repomatic release that shifts its floor, would silently leave the declared floor untested. These tests turn that drift into a failing check, as recommended by repomatic’s test-matrix guide.

tests.test_metadata.PYTHON_CLASSIFIER_PREFIX = 'Programming Language :: Python :: '

Prefix of the trove classifiers enumerating supported Python versions.

tests.test_metadata.test_requires_python_floor_in_classifiers()[source]

The requires-python floor equals the lowest declared classifier.

A network-free companion to the matrix check below: it catches a requires-python bump that forgets to update the trove classifiers, or the reverse, the most common metadata drift.

tests.test_metadata.test_matrix_python_floor_matches_requires_python()[source]

The generated matrix’s lowest Python equals the requires-python floor.

Drives the real repomatic CLI, the same one CI invokes, so the assertion reflects what is actually tested rather than a re-derivation. Skips, instead of failing, when the tool cannot be fetched (offline or sandboxed builds).

The --exclude-newer-package escape hatch is the documented per-package exemption every uvx call carrying this pin needs. The workflow sets UV_EXCLUDE_NEWER for all of its jobs, so a pin just moved to a release published inside that window is unresolvable without it, and uvx exits non-zero. That lands in the skip branch below rather than a failure, which is the worse outcome: this guard would go quiet for a full cooldown period after every upstream bump, exactly when the matrix is most likely to have drifted from requires-python.

tests.test_orphans module

Checks for the two orphan features and their native-command mappings.

remove --orphans (scoped, remove_orphan()) drops one package’s own orphaned dependencies; cleanup --orphans (system-wide, cleanup_orphan()) sweeps every orphaned package. The argv assertions stub run_cli on the pooled manager singleton to capture command tokens without spawning a subprocess, so they run identically on any host.

tests.test_orphans.test_remove_orphan_uses_native_cascade(monkeypatch, manager_id, cascade_token)[source]

remove --orphans maps to each manager’s native remove-plus-orphans verb.

tests.test_orphans.test_plain_remove_no_longer_cascades(monkeypatch, manager_id, cascade_token)[source]

Plain remove keeps orphaned dependencies: dnf and xbps used to cascade.

tests.test_orphans.test_remove_orphan_unsupported_raises_not_implemented(manager_id)[source]

A manager with no scoped cascade leaves remove_orphan unimplemented, so the CLI action catches the NotImplementedError and falls back to a plain removal.

tests.test_orphans.test_cleanup_orphan_uses_native_sweep(monkeypatch, manager_id, sweep_token)[source]

cleanup --orphans maps to each manager’s system-wide orphan sweep.

tests.test_orphans.test_plain_cleanup_never_sweeps(monkeypatch, manager_id, sweep_token, cache_token)[source]

The cleanup composer runs the non-destructive categories only: the orphan sweep stays behind an explicit --orphans, even where a native sweep exists.

tests.test_orphans.test_cleanup_orphan_unsupported_raises_not_implemented(manager_id)[source]

A manager with neither a native sweep nor an orphans query propagates NotImplementedError from the base sweep, so cleanup --orphans skips it.

tests.test_orphans.test_cleanup_orphan_capability_matrix(manager_id, synthesized, supported)[source]

The synthesized sweep covers exactly the managers with an orphan listing and per-package removal but no native sweep verb, mirroring upgrade --all.

tests.test_orphans.test_cleanup_category_support(manager_id, cache_support, repair_support)[source]

Category support is a plain method-override check: every manager declares its cleanup work through category methods, never a monolithic cleanup.

tests.test_orphans.test_cleanup_category_argv(monkeypatch, manager_id, method, token)[source]

The extracted category steps run their native commands.

tests.test_orphans.test_synthesized_sweep_fixpoint(monkeypatch)[source]

The synthesized sweep re-queries between rounds and stops when the listing settles: removing an orphan can orphan its own dependencies.

tests.test_orphans.test_synthesized_sweep_stops_without_progress(monkeypatch)[source]

A removal that keeps failing cannot spin the loop: two identical consecutive listings end the sweep.

tests.test_orphans.test_orphans_parsing(monkeypatch, manager_id, output, expected)[source]

Each manager’s orphans query parses its native listing into packages.

The canned outputs mirror the shell-session samples of the orphans docstrings, so the documented format and the tested format cannot drift apart.

tests.test_orphans.test_orphans_query_unsupported(manager_id)[source]

Managers without a native read-only orphan listing (including cask: casks are never installed as dependencies) do not advertise the orphans operation.

tests.test_orphans.test_orphans_query_supported(manager_id)[source]
tests.test_orphans.test_implements_method_introspection()[source]

implements_method reports overrides of the base NotImplementedError stubs, the same MRO walk the CLI and the docs generator rely on.

tests.test_orphans.test_base_orphan_operations_not_implemented()[source]

Both operations are optional, exactly like remove and cleanup.

class tests.test_orphans.RemovableFakeManager[source]

Bases: FakeManager

Fake manager that removes packages but has no scoped orphan-cascade verb.

Exercises the remove --orphans fallback: remove_orphan stays unimplemented (base NotImplementedError) while remove succeeds, so the CLI must catch the former and fall back to the latter.

Initialize cli_errors list.

remove(package_id)[source]

Remove one package and one only.

Optional. Will be simply skipped by mpm if not implemented.

Return type:

str

cli_names: tuple[str, ...] = ('removablefakemanager',)

List of CLI names the package manager is known as.

This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.

..hint::

This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

id: str = 'removablefakemanager'

Package manager’s ID.

Derived by defaults from the lower-cased class name in which underscores _ are replaced by dashes -.

This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.

name: str = 'RemovableFakeManager'

Return package manager’s common name.

Default value is based on class name.

virtual: bool = False

Should we expose the package manager to the user?

Virtual package manager are just skeleton classes used to factorize code among managers of the same family.

class tests.test_orphans.CleanupFakeManager[source]

Bases: FakeManager

Fake manager with a cache category only: no system-wide orphan sweep.

Initialize cli_errors list.

calls: list[str]
cleanup_cache()[source]

Prune the manager’s caches, downloads and other left-over artifacts.

The cache category of cleanup(), surfaced as mpm cleanup --cache and subtracted by --skip-cache (apt clean, dnf clean all, brew cleanup, npm cache clean, …). The broadest category: for most managers the whole cleanup amounts to it.

Optional. Will be simply skipped by mpm if not implemented.

Return type:

None

cli_names: tuple[str, ...] = ('cleanupfakemanager',)

List of CLI names the package manager is known as.

This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.

..hint::

This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

id: str = 'cleanupfakemanager'

Package manager’s ID.

Derived by defaults from the lower-cased class name in which underscores _ are replaced by dashes -.

This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.

name: str = 'CleanupFakeManager'

Return package manager’s common name.

Default value is based on class name.

virtual: bool = False

Should we expose the package manager to the user?

Virtual package manager are just skeleton classes used to factorize code among managers of the same family.

class tests.test_orphans.OrphanCleanupFakeManager[source]

Bases: CleanupFakeManager

Fake manager that additionally supports the system-wide orphan sweep.

Initialize cli_errors list.

cleanup_orphan()[source]

Remove every orphaned package on the system, sparing the caches.

The system-wide “remove all packages nothing depends on anymore” sweep (apt autoremove, brew autoremove, flatpak uninstall --unused, …). The one cleanup category that removes packages, so it is deliberately kept out of the plain cleanup() composition and only runs on an explicit mpm cleanup --orphans.

Distinct from meta_package_manager.manager.PackageManager.remove_orphan(), which is scoped to one package’s own orphaned dependencies. As with cleanup(), mpm builds no dependency graph: the manager decides what is orphaned.

A manager with no native sweep verb is backfilled by this base implementation when it supports both the orphans query and package removal: list the orphans, remove each one (with remove_orphan() when available, so every listed root takes its own now-orphaned subtree along), then re-query and repeat until the listing settles, since removing an orphan can orphan its own dependencies. The exact pattern of the synthesized full upgrade --all, and the in-process equivalent of Arch’s classic pacman -Rns $(pacman -Qtdq) idiom. The re-query loop stops as soon as a round makes no progress, so removal failures cannot spin it forever.

A manager implementing neither a native sweep nor the orphans query propagates NotImplementedError, and mpm cleanup --orphans simply skips it.

Return type:

None

cli_names: tuple[str, ...] = ('orphancleanupfakemanager',)

List of CLI names the package manager is known as.

This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.

..hint::

This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

id: str = 'orphancleanupfakemanager'

Package manager’s ID.

Derived by defaults from the lower-cased class name in which underscores _ are replaced by dashes -.

This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.

name: str = 'OrphanCleanupFakeManager'

Return package manager’s common name.

Default value is based on class name.

virtual: bool = False

Should we expose the package manager to the user?

Virtual package manager are just skeleton classes used to factorize code among managers of the same family.

class tests.test_orphans.SweepSynthesizingFakeManager[source]

Bases: CleanupFakeManager

Fake manager with cleanup, an orphan listing (inherited) and removal, but no native sweep verb: cleanup --orphans must go through the synthesized base sweep, never the full cleanup.

Initialize cli_errors list.

remove(package_id)[source]

Remove one package and one only.

Optional. Will be simply skipped by mpm if not implemented.

Return type:

str

cli_names: tuple[str, ...] = ('sweepsynthesizingfakemanager',)

List of CLI names the package manager is known as.

This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.

..hint::

This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

id: str = 'sweepsynthesizingfakemanager'

Package manager’s ID.

Derived by defaults from the lower-cased class name in which underscores _ are replaced by dashes -.

This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.

name: str = 'SweepSynthesizingFakeManager'

Return package manager’s common name.

Default value is based on class name.

virtual: bool = False

Should we expose the package manager to the user?

Virtual package manager are just skeleton classes used to factorize code among managers of the same family.

class tests.test_orphans.DecomposedCleanupFakeManager[source]

Bases: CleanupFakeManager

Fake manager with both orphan and cache category steps.

Initialize cli_errors list.

cleanup_orphan()[source]

Remove every orphaned package on the system, sparing the caches.

The system-wide “remove all packages nothing depends on anymore” sweep (apt autoremove, brew autoremove, flatpak uninstall --unused, …). The one cleanup category that removes packages, so it is deliberately kept out of the plain cleanup() composition and only runs on an explicit mpm cleanup --orphans.

Distinct from meta_package_manager.manager.PackageManager.remove_orphan(), which is scoped to one package’s own orphaned dependencies. As with cleanup(), mpm builds no dependency graph: the manager decides what is orphaned.

A manager with no native sweep verb is backfilled by this base implementation when it supports both the orphans query and package removal: list the orphans, remove each one (with remove_orphan() when available, so every listed root takes its own now-orphaned subtree along), then re-query and repeat until the listing settles, since removing an orphan can orphan its own dependencies. The exact pattern of the synthesized full upgrade --all, and the in-process equivalent of Arch’s classic pacman -Rns $(pacman -Qtdq) idiom. The re-query loop stops as soon as a round makes no progress, so removal failures cannot spin it forever.

A manager implementing neither a native sweep nor the orphans query propagates NotImplementedError, and mpm cleanup --orphans simply skips it.

Return type:

None

cli_names: tuple[str, ...] = ('decomposedcleanupfakemanager',)

List of CLI names the package manager is known as.

This list of recognized CLI names is ordered by priority. That way we can influence the search of the right binary.

..hint::

This was helpful in the case of the Python transition from 2.x to 3.x, where multiple versions of the same executable were named python or python3.

By default, this property’s value is derived from the manager’s ID (see the MetaPackageManager.__init__ method above).

id: str = 'decomposedcleanupfakemanager'

Package manager’s ID.

Derived by defaults from the lower-cased class name in which underscores _ are replaced by dashes -.

This ID must be unique among all package manager definitions and lower-case, as they’re used as feature flags for the mpm CLI.

name: str = 'DecomposedCleanupFakeManager'

Return package manager’s common name.

Default value is based on class name.

virtual: bool = False

Should we expose the package manager to the user?

Virtual package manager are just skeleton classes used to factorize code among managers of the same family.

tests.test_orphans.removable_fake_pool(monkeypatch)[source]
tests.test_orphans.test_remove_orphans_falls_back_to_plain_removal(invoke, removable_fake_pool)[source]

remove --orphans on a manager without a cascade verb removes the package only, narrating the skip at INFO and still exiting cleanly.

tests.test_orphans.test_cleanup_orphans_runs_only_the_sweep(invoke, monkeypatch)[source]

cleanup --orphans runs cleanup_orphan, not the full cleanup.

tests.test_orphans.test_cleanup_without_flags_runs_default_categories(invoke, monkeypatch)[source]

Plain cleanup runs the non-destructive default categories only: a native orphan sweep does not join in without an explicit --orphans.

tests.test_orphans.test_cleanup_orphans_skips_manager_without_sweep(invoke, monkeypatch)[source]

A cleanup-capable manager with no orphan sweep at all (an orphan listing but no removal) is skipped by --orphans, not fully cleaned up.

tests.test_orphans.test_cleanup_orphans_runs_synthesized_sweep(invoke, monkeypatch)[source]

cleanup --orphans on a manager without a native sweep verb synthesizes it: the listed orphans are removed one by one, and the full cleanup never runs.

tests.test_orphans.test_cleanup_category_selection(invoke, monkeypatch, args, expected_calls)[source]

The tri-state category flags compose on a decomposed manager.

tests.test_orphans.test_cleanup_cache_category_alone(invoke, monkeypatch)[source]

A cache-only manager runs its single category under --cache and under a plain cleanup alike.

tests.test_orphans.test_plain_cleanup_never_engages_synthesized_sweep(invoke, monkeypatch)[source]

Plain cleanup runs native categories only: the flag pairs default to True (so --help renders each default as its positive side), and only a flag the user actually set may count as a positive and engage the synthesized sweep.

tests.test_orphans.test_cleanup_skip_never_engages_synthesized_sweep(invoke, monkeypatch)[source]

A skip flag subtracts from the native bundle only: on a monolithic manager with a synthesizable sweep, --skip-cache must not surprise-remove packages, so the manager is skipped entirely.

tests.test_orphans.test_cleanup_skip_orphans_keeps_native_categories(invoke, monkeypatch)[source]

--skip-orphans on a manager with a synthesizable-but-not-native sweep keeps its native categories running: only the sweep is subtracted.

tests.test_orphans.test_cleanup_narration_names_categories(invoke, monkeypatch)[source]

The per-manager narration names the categories dispatched to it, matching the / trail labels.

tests.test_orphans.test_cleanup_all_categories_skipped_errors(invoke, monkeypatch)[source]

Skipping every default category is a usage error, not a silent no-op: --skip-orphans is not needed, as orphans is not in the default set.

tests.test_pool module

tests.test_pool.test_manager_definition_inventory()[source]

Check all classes implementing a package manager are accounted for in the pool.

tests.test_pool.test_manager_classes_order()[source]

Check manager classes are ordered by their IDs.

tests.test_pool.test_manager_count()[source]

Check all implemented package managers are accounted for, and unique.

tests.test_pool.test_shared_lock_families_are_disjoint()[source]

No manager may belong to two lock families, or its lane grouping is ambiguous.

tests.test_pool.test_shared_lock_family_members_exist_in_pool()[source]

Every lock-family member must be a real manager id (catches typos).

tests.test_pool.test_lock_families_nest_in_label_groups()[source]

Every lock family must sit inside a single ecosystem label group.

The two constants answer different questions. MANAGER_LABEL_GROUPS groups managers by the packaging ecosystem an issue lands in, SHARED_LOCK_FAMILIES the ones that cannot run at once on a real host. The implication runs one way only: contending for a backend lock means sharing that backend’s ecosystem, so a lock family is always contained in a label group.

Never the reverse, and the gaps are the point: fink and opkg speak dpkg through databases of their own, zerobrew has its own prefix and dkp-pacman its own repositories, while pypi-based, npm-based and the plugin-manager groups share a registry or a host program rather than any lock at all. Asserting equality here would be asserting those four facts away.

Catches a manager wired into a lock family but forgotten in the label groups, which is what it caught for nala.

tests.test_pool.test_delegating_managers_share_their_target_lock()[source]

A manager delegating an operation to another’s CLI runs that manager’s own binary against that manager’s own state, so the two must serialize.

The delegation is declared in the class body (Delegate), which makes this derivable rather than a list to keep in step: wiring a new delegate without a lock family fails here.

tests.test_pool.test_pacman_subclasses_share_the_pacman_lock()[source]

Every manager inheriting from Pacman drives pacman’s own database, so it must serialize with the rest of that family.

dkp-pacman is the sole exemption: devkitPro ships it to sit beside a distribution’s pacman with its own repositories and database, so it contends with nothing. Any other subclass missing from the family is the bug this catches, which is how the AUR helpers went unserialized through 7.6.1.

Inheritance is a sufficient signal, never a necessary one: pamac reaches the same libalpm through a bundled definition and is covered by the family without appearing here.

tests.test_pool.test_cached_pool()[source]
tests.test_pool.test_maintained_managers(manager_id)[source]
tests.test_pool.test_supported_managers(manager_id)[source]
tests.test_pool.test_unsupported_managers(manager_id)[source]
tests.test_pool.test_manager_groups()[source]

Test relationships between manager groups.

tests.test_pool.test_extra_option_allowlist()[source]
tests.test_pool.test_select_managers(kwargs, expected)[source]

We use tuple everywhere so we can check that select_managers() conserve the original order.

tests.test_pool.test_select_managers_timeout_stamping()[source]

The user’s --timeout lands on every selected manager even when unavailable ones are kept (mpm managers), whose version probes fire lazily at rendering time, after selection: an unstamped instance would fall back to the read-only default and let a wedged binary hold each table row for 120 seconds. A per-manager [mpm.managers.<id>] override keeps precedence over the global option.

tests.test_pool.test_warm_availability_skips_without_context()[source]

No active CLI context: leave probing to the lazy, sequential filter loop.

tests.test_pool.test_warm_availability_skips_when_not_concurrent(jobs, verbosity, count)[source]
tests.test_pool.test_warm_availability_probes_concurrently()[source]

With –jobs > 1 and several candidates, probes run off the main thread.

tests.test_pool.test_merge_into_probe_lanes_groups_by_signature()[source]

Managers whose probe may resolve to the same command share a lane.

tests.test_pool.test_merge_into_probe_lanes_partitions_the_whole_pool()[source]

Every manager lands on exactly one lane, whatever the pool grows to.

tests.test_pool.test_warm_availability_shares_one_cache_within_a_lane()[source]

A lane’s managers probe against one shared run_cache, then get their own value back: that dict is what collapses brew and cask both running brew --version into a single subprocess.

tests.test_sbom module

tests.test_sbom.assert_valid_cyclonedx(content, export_format)[source]

Assert a CycloneDX export validates against its schema.

This guarantee used to live in meta_package_manager.sbom.cyclonedx.CycloneDX.export() at runtime. It moved here so the jsonschema-based validation stack (rfc3987-syntax, lark, lxml) stays out of mpm’s runtime dependencies. See meta_package_manager.sbom.

Return type:

None

tests.test_sbom.test_file_autodetect(file_path, expected)[source]
tests.test_sbom.test_normalize_spdx_id(raw_str, expected)[source]
tests.test_sbom.test_minimal_mode_emits_bare_spdx_payload()[source]

Minimal mode must reproduce the legacy bare output: rich metadata is ignored, no relationships beyond DESCRIBES are emitted, and download_location falls back to NOASSERTION.

tests.test_sbom.test_bundled_mode_spdx_populates_rich_fields()[source]

A populated PackageMetadata flows into the SPDX document: license, supplier override, originator, checksum, and a dependency relationship resolved at finalize() time.

tests.test_sbom.test_bundled_mode_cyclonedx_populates_rich_fields()[source]

CycloneDX renderer projects the same metadata into hashes, licenses, supplier, external references, and dependency edges.

tests.test_sbom.test_cyclonedx_compound_license_expression_details(expression, expected_identifiers)[source]

Compound expressions emit LicenseExpression.details with a canonical SPDX URL per identifier, deduped and sorted by identifier.

tests.test_sbom.test_spdx_license_normalization(declared, expected_present)[source]

The SPDX renderer must produce a document that validates against the SPDX schema for every input it accepts.

tests.test_sbom.test_spdx_merges_external_per_package_sbom(tmp_path)[source]

The renderer adopts transitive deps from a per-package upstream SPDX file and records the merge in externalDocumentRefs.

The fixture mirrors the shape of Homebrew’s <prefix>/Cellar/<formula>/<version>/sbom.spdx.json.

tests.test_sbom.test_stats_track_per_manager_and_merge_counts(tmp_path)[source]

SBOM.stats() must reflect what landed in the document.

Two packages from a single manager land normally; one of them carries an external SBOM file containing one transitive dep, which gets spliced in. The SPDX-specific stats then report: inventory=2, in_document=3, merged_documents=1.

tests.test_sbom.test_cyclonedx_stats_count_external_bom_refs(tmp_path)[source]

CycloneDX stats report external BOM refs and dependency edges.

tests.test_sbom.test_spdx_renders_attached_vulnerabilities()[source]

Attached advisories become SECURITY external refs in SPDX.

tests.test_sbom.test_cyclonedx_renders_attached_vulnerabilities()[source]

Attached advisories become a CycloneDX vulnerabilities array entry.

tests.test_sbom.test_shared_advisory_deduplicated_in_cyclonedx()[source]

One advisory affecting two components yields a single record with two affects targets.

tests.test_sbom_vulnerabilities module

Tests for the opt-in online SBOM layer: HTTP cache and OSV adapter.

All network traffic is mocked with respx; nothing here touches the real OSV API. The NetworkClient is constructed with trust_env=False so an ambient proxy in the test environment cannot interfere.

tests.test_sbom_vulnerabilities.client(tmp_path)[source]

A cache-backed NetworkClient isolated to a temp dir, proxy-free.

tests.test_sbom_vulnerabilities.test_network_client_caches_responses(client)[source]

A second identical request is served from disk, not the network.

tests.test_sbom_vulnerabilities.test_network_client_retries_then_raises(client)[source]

Repeated 503s exhaust retries and surface as NetworkError.

tests.test_sbom_vulnerabilities.test_network_client_raises_on_invalid_json(client)[source]

A 200 with a non-JSON body is reported as NetworkError.

tests.test_sbom_vulnerabilities.test_normalize_severity(raw, expected)[source]
tests.test_sbom_vulnerabilities.test_extract_fixed_versions_dedupes()[source]
tests.test_sbom_vulnerabilities.test_normalize_osv_record_full()[source]
tests.test_sbom_vulnerabilities.test_parse_purls_maps_ecosystems(purls, expected_coordinates)[source]
tests.test_sbom_vulnerabilities.test_parse_purls_dedupes_identical_coordinates()[source]
tests.test_sbom_vulnerabilities.test_scan_vulnerabilities_end_to_end(client)[source]

A full batch + detail round-trip, mapping advisories back to purls.

tests.test_sbom_vulnerabilities.test_scan_vulnerabilities_shared_advisory_fans_out(client)[source]

One advisory hitting two purls is fetched once, attached twice.

tests.test_sbom_vulnerabilities.test_scan_vulnerabilities_no_supported_purls(client)[source]

Purls with no OSV ecosystem produce an empty result and no calls.

tests.test_sbom_vulnerabilities.test_scan_vulnerabilities_skips_bad_detail(client)[source]

A detail fetch that 404s drops only that advisory, not the scan.

tests.test_sbom_vulnerabilities.test_vulnerability_dataclass_is_frozen()[source]

Vulnerability instances are immutable, safe to share across purls.

tests.test_sbom_vulnerabilities.test_batch_chunks_beyond_limit(client, monkeypatch)[source]

More than OSV_BATCH_LIMIT coordinates split into multiple batches.

tests.test_sbom_vulnerabilities.test_querybatch_request_shape(client)[source]

The batch payload uses ecosystem+name+version, not raw purls.

tests.test_specifier module

tests.test_specifier.test_purl_map()[source]
tests.test_specifier.props(spec)[source]

Utility to help compares specifiers between themselves.

I.e. all properties of specifiers are the same but the raw_specs property.

tests.test_specifier.test_parse_specs(spec_string, expected)[source]
tests.test_specifier.test_reduce_specs(spec_strings, target_managers, expected)[source]
tests.test_specifier.test_resolve_package_specs(spec_strings, manager_priority, expected)[source]

Distinct explicit managers each survive as their own target, while alias pURLs, version constraints and manager-less specs still reduce to one.

tests.test_sphinx_crossrefs module

Render tests for Sphinx cross-references in the built documentation.

Build the docs once and assert against the real HTML that the generated summary tables deep-link to the sections they describe, and that intersphinx references to Click resolve to the upstream site. This catches drift the moment a {click:config} or {click:tree} directive stops wiring its anchors, or an intersphinx_mapping URL goes stale, neither of which a mock-based test would notice.

tests.test_sphinx_crossrefs.built_docs(tmp_path_factory: TempPathFactory) Path[source]

Build the HTML documentation once and return its output directory.

Builds into a throwaway directory (rather than docs/_build) so the run is hermetic and never clobbers a developer’s local build.

Return type:

Path

tests.test_sphinx_crossrefs.read_html(built_docs, filename)[source]

Read a built HTML page.

Return type:

str

The {click:config} mpm summary table deep-links each option to its section.

The {click:tree} mpm summary table deep-links each command to its section.

tests.test_sphinx_crossrefs.test_intersphinx_click_resolves(built_docs)[source]

Click cross-references resolve to the upstream documentation site.

The links land on the API autodoc pages (typed CLI signatures), not the CLI-parameters page, so scan the whole build rather than a single page.

tests.test_sudo module

Unit tests for the privilege-escalation machinery.

These exercise meta_package_manager.sudo.prime_sudo() and its keepalive with a mocked subprocess.run (no test ever launches a real sudo), the escalation policy inventories across the manager pool, and the meta_package_manager.sudo._StallWatchdog end to end through tests.fake_manager.FakeManager (whose CLI is the Python interpreter, so its subprocesses are real but harmless). The sudo --non-interactive command wrapping of build_cli(), the authentication-failure hint of run() and the CLI wiring of prime_sudo stay in tests.test_execution.

tests.test_sudo.test_default_sudo_matches_system_managers()[source]

Exactly the system package managers (classes, their subclasses and bundled definitions alike) escalate by default; user-level managers do not, the dual-scope language managers (npm, pip, gem, cpan) keep their privileged markers dormant, and the polkit-native daemon clients (flatpak, fwupd, pkcon) never mark an operation at all.

tests.test_sudo.test_internal_sudo_matches_internal_escalators()[source]

Exactly the managers whose CLI runs sudo itself mid-run are marked, and none of them escalates through mpm (an internal escalator is never wrapped).

tests.test_sudo.prime_sudo_env(*, windows=False, root=False, stdin_tty=None, stderr_tty=None)[source]

Patch the whole environment prime_sudo probes, yielding the run mock.

Pins the platform (windows), the effective user (root) and the terminal state (None leaves the real descriptor unpatched, for tests that never reach the TTY check), and replaces subprocess.run so no test ever launches a real sudo. Callers set the mock’s return_value/side_effect to shape the probe and prompt outcomes.

tests.test_sudo.test_prime_sudo_skips_when_no_manager_escalates()[source]
tests.test_sudo.test_prime_sudo_skips_on_windows()[source]
tests.test_sudo.test_prime_sudo_skips_when_root()[source]
tests.test_sudo.test_prime_sudo_skips_on_simulation(simulation_flag)[source]

Neither a dry run nor a plan run executes a state-changing CLI, so both skip the sudo prompt.

tests.test_sudo.test_prime_sudo_warns_without_tty(caplog)[source]

A cold cache off-terminal: the probe still runs first, then one warning names the managers left to fail fast instead of blocking on a prompt.

tests.test_sudo.test_prime_sudo_authenticates_and_keeps_alive_on_tty()[source]

A cold cache on a terminal: probe first, then one branded password prompt, then the keepalive until the context closes.

tests.test_sudo.test_prime_sudo_is_idempotent()[source]

A second call on the same context is a no-op: one probe, ever.

tests.test_sudo.test_prime_sudo_warm_probe_stays_silent_on_tty(capsys)[source]

A warm credential cache (pre-authenticated, NOPASSWD): no notice, no prompt, just the keepalive until the context closes.

tests.test_sudo.test_prime_sudo_warm_probe_keeps_alive_off_tty(caplog)[source]

The probe short-circuits before the terminal check: a CI job with pre-cached credentials gets the keepalive instead of the no-terminal warning.

tests.test_sudo.test_prime_sudo_cold_internal_only_never_prompts_on_tty(capsys, caplog)[source]

A cold cache with only internal escalators (a stock cask/fink selection) probes, then returns without prompting: most such runs never escalate, and the stall notice covers the rare mid-run prompt instead.

tests.test_sudo.test_prime_sudo_cold_internal_only_stays_silent_off_tty(caplog)[source]

Off-terminal, an internal-only selection gets no warning: each manager’s own sudo fails fast and surfaces through its error path.

tests.test_sudo.test_prime_sudo_warns_when_sudo_cannot_run(caplog, probe_error)[source]

A UNIX host whose sudo is missing or not executable gets one warning instead of the probe crashing prime_sudo. Both raise an OSError subclass from the probe.

tests.test_sudo.test_prime_sudo_notice_names_managers_and_subcommand(capsys, manager_ids, expected_notice, expected_prompt)[source]

The password notice names the sorted escalating managers (with a verb agreeing in number) and the subcommand; the sudo prompt is branded alike.

tests.test_sudo.test_sudo_prompt_respects_sudo_constraints(manager_ids)[source]

Whatever the prompt copy becomes, the --prompt argument handed to sudo must stay within sudo’s constraints.

Locks the properties rather than the wording, so a future rewording trips here only if it breaks sudo: every % must be doubled (sudo --prompt expands %h/%H/%p/%u/%U, and a lone % is undefined), the prompt must be a single line (a newline would detach the ask from the input cursor), and it must end with a space so the typed password is not glued to the text.

tests.test_sudo.test_is_sudo_auth_failure(error, expected)[source]
tests.test_sudo.test_stall_watchdog_notices_silent_internal_escalator(monkeypatch, caplog)[source]

A mutating call of an internal escalator, on a terminal with a cold cache, warns once per silence episode, naming the manager and quoting its last output line.

tests.test_sudo.test_stall_watchdog_negative_gates(monkeypatch, caplog, active_operation, internal_sudo, warm_cache, tty)[source]

Each arming gate individually disarms the watchdog: no notice fires even when the call stays silent for far longer than the (shortened) delay.

tests.test_sudo.test_stall_watchdog_tee_gates_debug_lines_at_default_verbosity(caplog)[source]

An armed call still hides child output lines at the default WARNING level: the tee forwards them through the root logger’s level gate instead of bypassing it.

tests.test_sudo.test_stall_watchdog_tee_forwards_verbatim_at_debug(caplog)[source]

At DEBUG verbosity an armed call streams child lines through the tee with the same message, level and manager label as an un-teed run.

tests.test_version module

tests.test_version.reverse_fixtures(table)[source]

Utility method to reverse a tuple of tuples.

tests.test_version.test_token_allowed_instantiation(value)[source]
tests.test_version.test_token_unauthorized_instantiation(value)[source]
tests.test_version.test_token_eq(token, value)[source]
tests.test_version.test_token_ne(token, value)[source]
tests.test_version.test_token_gt(token, value)[source]
tests.test_version.test_token_lt(token, value)[source]
tests.test_version.test_token_ge(token, value)[source]
tests.test_version.test_token_le(token, value)[source]
tests.test_version.test_token_hash()[source]
tests.test_version.test_token_str(value, expected)[source]
tests.test_version.test_token_len(value, expected)[source]
tests.test_version.test_token_int(value, expected)[source]
tests.test_version.test_token_int_none_for_non_integers(value)[source]

Token.integer is None for non-integer tokens.

tests.test_version.test_token_isint(value, expected)[source]
tests.test_version.test_token_repr()[source]
tests.test_version.test_token_format()[source]
tests.test_version.test_token_mixed_type_not_equal()[source]

Integer and string tokens are never equal, even if the string is digit-like when compared cross-type.

tests.test_version.test_tokenized_string_allowed_instantiation(value)[source]
tests.test_version.test_tokenized_string_unauthorized_instantiation(value)[source]
tests.test_version.test_tokenized_string_hash()[source]
tests.test_version.test_tokenized_string_noop_instantiation()[source]
tests.test_version.test_tokenized_string_idempotent_instantiation()[source]
tests.test_version.test_tokenized_string_deepcopy()[source]
tests.test_version.test_tokenized_string_str(value, expected)[source]
tests.test_version.test_tokenized_string_len(value, expected)[source]
tests.test_version.test_tokenized_string_repr()[source]
tests.test_version.test_tokenized_string_format()[source]
tests.test_version.test_tokenized_string_iter()[source]
tests.test_version.test_tokenized_string_from_integer()[source]
tests.test_version.test_tokenized_string_empty_tokens(value)[source]
tests.test_version.test_tokenized_string_comparison_edge_cases()[source]

Comparison with None and unsupported types.

tests.test_version.test_tokenized_string_separators_and_pretty_print(v_string, expected_seps)[source]
tests.test_version.test_tokenized_string_separators_and_lossy_pretty_print(v_string, expected_seps, expected_pp)[source]
tests.test_version.test_version_tokenizer(v_string, v_tuple)[source]
tests.test_version.test_is_version(v_string, expected)[source]
tests.test_version.test_version_comparison_gt(ver1, ver2)[source]
tests.test_version.test_version_comparison_lt(ver1, ver2)[source]
tests.test_version.test_version_comparison_eq(ver1, ver2)[source]
tests.test_version.test_version_sorting(sequence)[source]
tests.test_version.test_version_range(spec, version, expected)[source]
tests.test_version.test_version_range_empty_spec()[source]

Empty spec has no constraints, so any version satisfies it.

tests.test_version.test_version_range_repr(spec, expected)[source]
tests.test_version.test_parse_version_is_tokenized_string()[source]

parse_version is an alias for TokenizedString.

tests.test_version.test_diff_versions(old, new, expected_common, expected_old, expected_new)[source]
tests.test_version.test_version_comparison_gt_known_failures(ver1, ver2)[source]
tests.test_version.test_version_comparison_lt_known_failures(ver1, ver2)[source]
tests.test_version.test_version_comparison_eq_known_failures(ver1, ver2)[source]