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_docsguards check and the.gitdirectory 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.solve_destructive_options(config)[source]¶
Solve the destructive options to determine which tests to run.
- 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.
- tests.conftest.isolate_user_config(isolated_app_dir)[source]¶
Hide the developer’s real
mpmconfiguration from the test suite.Any
config.tomlin the host configuration folder bleeds into every in-process CLI invocation: a localcpan = false, for instance, silently drops the manager from the default selection, socheck_manager_selectionassertions 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 repointsclick.get_app_dir()-based config discovery at a fresh empty directory.HOMEis 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.stub_run_cli(monkeypatch)[source]¶
Replace a manager’s
run_cliwith a canned-output stub.Returns a
stub(manager, output)callable: everyrun_clicall onmanagerthen returnsoutputwithout spawning a subprocess. The workhorse of the output-parsing tests (test_manager_*). To assert on the arguments a manager builds instead, seecapture_run_cli().
- tests.conftest.capture_run_cli(monkeypatch)[source]¶
Replace a manager’s
run_cliwith 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
FakeManagerfrom 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
TimingOutFakeManagerwhoseoutdatedexceeds--timeout.Use only for tests that need to verify
meta_package_manager.execution.CLIExecutor.run()catchessubprocess.TimeoutExpiredand logs the expected warning.
- tests.conftest.subcmd()[source]¶
Fixture used in
test_cli_*.pyfiles 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 bympm --<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
/etcconfiguration: 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 asnet/nyancat, OpenBSD and pkgsrc asmisc/nyancat), GNUhellofor the functional managers (Guix, Nix) andhyperfinefor the Windows binary stores andstew. Distros that lacknyancatfall back tosl(Chromebrew, Solus, Void),figlet(another tiny dependency-free C binary, for Exherbo, Fink, Mageia and Source Mage),lolcat(OpenWrt) ornano(Slackware’s official tree carries none of the above, soslapt-getmirrorstazpkg). Language and application managers use the smallest inert package native to their ecosystem (msfor npm and Yarn,pycowsayfor the pipx-style installers that require a console-script entry point, …).Warning
fwupdflashes 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 destructivefwupdtest on hardware that the ID actually targets.Note
A few managers cannot offer a small binary:
sdkmanonly ships full SDKs (jbangis its lightest candidate), andmasneeds a signed App Store app (its test is skipped anyway).deb-gethas no real per-package install, so it references itself, andtopgradedeclares no install operation at all, so its round-trip auto-skips.gh-extreferences its extension by theowner/reposlug: the only idgh extension installaccepts, and the id scheme itsinstalledoperation 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_WHENcannot 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 (scoopandsfsuon Windows,pwsh-galleryon 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
ghCLI is present but holds no usable credentials.ghrefuses to runextension installunauthenticated, 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 missingghis not flagged: selection then finds no available manager and the test already exits on its “No manager selected” path.- Return type:
- tests.conftest.cpan_install_blocked()[source]¶
Whether
cpan installcannot 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_linuxwas wrong on arm64, and a writability probe would have been wrong on macOS in the other direction.is_x86_64rather 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:
- 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_cinext 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
installanyway, caps the doomed CLI call withSHORT_FAILURE_TIMEOUT, and asserts that mpm reports the stable failure: exit code1and aCould not install: {package}critical message. The follow-upremoveis skipped, since the failed install left nothing to remove and the working managers already cover the removal path.A condition is either a plain
Truefor the managers no environment can install, or a zero-argument callable evaluated per host:is_linuxfor the unprivileged Linux runner,is_github_cifor GitHub Actions only (a configured local box can still install), or a live probe likegh_unauthenticatedwhen installability hinges on host state rather than platform. Resolve one throughinstall_remove_blocked()rather than calling it directly, which is what keeps the constants from needing alambdawrapper.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-gallerytimes out on macOS but hits a name-case mismatch on Linux), stage-specific (dnf/zypper/flatpakfail 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_idcannot complete a real install here.Resolves the
INSTALL_REMOVE_BLOCKED_WHENcondition, which is either a constant or a callable evaluated against the running host. A manager with no entry is not blocked.- Return type:
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:
PackageManagerAlways-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_errorslist.- 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
frozensetofPlatforminstances 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
pythonorpython3.
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 bycli_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.executableproperty below.
- 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.TokenizedStringinstance.Skipped on platforms where the manager is not supported, even if
cli_pathresolved to an executable: that binary almost certainly belongs to a different tool that happens to share the same name (e.g. GNUmakeon macOS getting matched by the FreeBSDportsmanager), 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
--orphansaction flags: wherempm cleanup --orphansremoves 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
extendedandexactparameters. 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 themeta_package_manager.manager.PackageManager.refiltered_search()method below.Optional. Will be simply skipped by mpm if not implemented.
- 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 throughmpmitself, but their presence is what makesmeta_package_manager.capabilities.implements()report theupgradeandupgrade_alloperations as supported, so the fake manager’s menu lines carry an action.
- upgrade_one_cli(package_id, version=None)[source]¶
Returns the complete CLI to upgrade one package and one only.
Allows a specific
versionto be provided.
- 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.
- class tests.fake_manager.TimingOutFakeManager[source]¶
Bases:
FakeManagerVariant that runs a real subprocess long enough to trip
--timeout.Used by
tests.test_cli.test_timeout()to exercise thesubprocess.TimeoutExpiredbranch inmeta_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 fromtimeoutso the call is guaranteed to overshoot.Initialize
cli_errorslist.- 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
pythonorpython3.
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.
tests.test_bar_plugin module¶
- 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
UnboundLocalErroron theFileNotFoundErrorpath ofcheck_mpm(), whereprocessis 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=falseand 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_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_mas_brewfile_entry_uses_name_and_numeric_id()[source]¶
masBrewfile 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_typeso its extensions are not silently emitted asvscode(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
goandkrewhas 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#inspectrules.quoteusesjson.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 throughbrew bundle checkto 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.
- 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
mpmCLI 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_matchcheck 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:
<stdout>and<stderr>gets mangledwe may introduce parallel execution of managers in the future
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
midshows up in thempm managerstable.Lives at module level so both the selection tests below and the dedicated
managerssubcommand suite (tests.test_cli_managers) share it through downward imports, instead of one test module importing the other sideways.
- class tests.test_cli.TestManagerSelection[source]¶
Bases:
InspectCLIOutputTest selection of package managers to use.
Tests are performed here on the
mpm managerssubcommand, 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.
- class tests.test_cli.TestSelectorPrecedence[source]¶
Bases:
InspectCLIOutputVerify how
-m/--<id>(keep) and-x/--no-<id>(drop) compose and override each other for operational subcommands.Uses
installed --dry-runas 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. Themanagerssubcommand 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
mpmreached 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 DEBUGa fewReset <logger> to WARNINGlines trail the stats line. Unavailable ones surface asSkipped: ...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 DEBUGto keep both signals visible.
- 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 jsonmode: every manager entry carries the standard keys (plus the subcommand’soptional_keys, likeoutdated’supgrade_all_cli), and every package serializes a subset of thePackagefields as strings.reference_setdefaults to the platform’s real default managers, but a caller pinned tofake_pool(likeorphans, with no implementing manager on every platform) overrides it to match.- Return type:
- class tests.test_cli.CLISubCommandTests[source]¶
Bases:
InspectCLIOutputAll these tests runs on each subcommand.
This class doesn’t starts with
Testas it is meant to be used as a template, inherited by sub-command specific test cases.
- class tests.test_cli.CLITableTests[source]¶
Bases:
objectTest 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*_COLUMNSconstant).Set by each subclass so the generic
--columnstests 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]¶
--columnsrestricts 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.
- class tests.test_cli.CLIQueryTests[source]¶
Bases:
objectTemplate for inventory subcommands taking an optional positional
QUERY.Runs against the deterministic
fake_poolpackage set. Subclasses keep their owntest_query_filterparametrize — the case data is the per-command filtering semantics — and delegate each case’s assertions tocheck_filtered_ids().
tests.test_cli_backup module¶
- 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_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’sprep_pathbrought themkdir -pwith it.
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_install_remove module¶
- class tests.test_cli_install_remove.TestInstallRemove[source]¶
Bases:
CLISubCommandTestsInstall and remove operations are siblings and sensible, so we regroup them under the same test suite.
Tip
Where we can, we invoke the
installsubcommand to installmpmwith 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_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 theinstallcommand incli.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.
removeonly 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_matchis set toFalsebecause theinstallsubcommand will not try all managers selected by default. So a strict match is not possible.That’s because
installsubcommand 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 blockedinstallis capped atSHORT_FAILURE_TIMEOUTand asserted to fail the stable mpm way (exit1plus aCould not install:``message). It runs at–verbosity INFO``: the minimum level at which theInstallation prioritydispatch signal is visible (the defaultWARNINGwould hide it and makecheck_manager_selection()pass vacuously). No follow-upremove: the failed install left nothing to remove, and the working managers already cover the removal path.
tests.test_cli_installed module¶
- 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*_COLUMNSconstant).Set by each subclass so the generic
--columnstests below resolve column IDs to their header labels from the same source of truth the CLI uses.
tests.test_cli_managers module¶
- 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*_COLUMNSconstant).Set by each subclass so the generic
--columnstests 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_unsupported_managers(invoke, subcmd, manager_id)[source]¶
A manager named by the user is reported whatever its state: the default view widens to
supportedon an explicit selection, so the row spelling out whympmcannot 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 alldoes.
- 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
managerssubcommand must forward the global--timeoutfor 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¶
- class tests.test_cli_orphans.TestOrphans[source]¶
Bases:
CLISubCommandTests,CLITableTests,CLIQueryTestsPin the whole class to
fake_pool.Unlike the other
CLITableTestssubcommands,orphansis 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 toNo manager selected.instead of the0exit 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*_COLUMNSconstant).Set by each subclass so the generic
--columnstests below resolve column IDs to their header labels from the same source of truth the CLI uses.
- pytestmark = [Mark(name='usefixtures', args=('fake_pool',), kwargs={})]¶
tests.test_cli_outdated module¶
- 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*_COLUMNSconstant).Set by each subclass so the generic
--columnstests below resolve column IDs to their header labels from the same source of truth the CLI uses.
- 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-outputthrough a pipe, whereecho’s TTY auto-detection would strip every ANSI code: the renderer forces colors so they actually reach SwiftBar and Xbar.
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
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.
- 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_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.
- tests.test_cli_sbom.test_sbom_query_filter_narrows_to_matches(invoke, fake_pool)[source]¶
sbom --querykeeps only installed packages matching the query.
- tests.test_cli_sbom.test_sbom_without_query_lists_all(invoke, fake_pool)[source]¶
Without a query,
sbomexports the full installed inventory.
- tests.test_cli_sbom.test_minimal_mode_skips_metadata_extractor(invoke)[source]¶
--minimalmust 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.pyimports every subcommand module up front to register it, so a module-level import of the writers incli_sbomwould spend their (heavy) third-party import cost on everympminvocation, for a dependency onlympm sbomever 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.moduleshere proves nothing.
tests.test_cli_search module¶
- class tests.test_cli_search.TestSearch[source]¶
Bases:
CLISubCommandTests,CLITableTests- 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 match.', max_width=None), SortableField.MANAGER_ID), (ColumnSpec(id='latest_version', label='Latest version', description='Latest version available.', max_width='auto'), SortableField.VERSION), (ColumnSpec(id='description', label='Description', description='Package description, for managers that provide one. Out of the default selection: select it explicitly or pass --description.', max_width='auto'), None))¶
The subcommand’s column registry (a
tables.py*_COLUMNSconstant).Set by each subclass so the generic
--columnstests below resolve column IDs to their header labels from the same source of truth the CLI uses.
- test_description_column_selection(invoke, fake_pool)[source]¶
The description column hides by default, appears with
--description, and an explicit--columnsselection is authoritative either way.
- test_unicode_search(invoke)[source]¶
Check
mpmis accepting unicode as search query.masis the only manager we have that is accepting unicode characters for its package names. We perform a search with it so we can provempmis supporting it too:$ mpm --mas search 钉 ╭────────────┬───────────────────┬─────────┬────────────────╮ │ Package ID │ Name │ Manager │ Latest version │ ├────────────┼───────────────────┼─────────┼────────────────┤ │ 1435447041 │ 钉钉 - 让进步发生 │ mas │ 7.6.57 │ ╰────────────┴───────────────────┴─────────┴────────────────╯
Test originates from #16.
Caution
Test is skipped on GitHub Actions as
masdoes not have access there to a registered account on the App Store. So the search returns no results.
tests.test_cli_upgrade module¶
- tests.test_cli_upgrade.failing_installed_query(monkeypatch)[source]¶
Break
FakeManager.installed, like a misconfiguredpnpmbreaks its own.
- class tests.test_cli_upgrade.TestUpgrade[source]¶
Bases:
CLISubCommandTestsTest the system-wide upgrade sub-command.
Danger
All tests here should me marked as destructive unless –dry-run parameter is passed.
tests.test_cli_which module¶
- 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*_COLUMNSconstant).Set by each subclass so the generic
--columnstests below resolve column IDs to their header labels from the same source of truth the CLI uses.
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_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-ageexpects 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_MANAGERShelp text reads this for every manager, so it must never shell out toyay --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, notweakref.finalize(self, ...): yay re-readsinit.luamid-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.
versionrunsyay --versionthroughrun(), which injectscooldown_env(), which consultssupports_cooldown->version. The re-entrancy guard must break that loop. Regression for a liveRecursionErrorthe pre-seeded-version tests above could not catch.
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:
objectMinimal stand-in exposing only the
metakeys the helper reads.
- class tests.test_dispatch.StubManager(manager_id, progress=False)[source]¶
Bases:
objectMinimal stand-in exposing only
id,progressandrun_cache.- run_cache = None¶
- class tests.test_dispatch.TTYStringIO(initial_value='', newline='\n')[source]¶
Bases:
StringIOAn in-memory text buffer that claims to be an interactive terminal.
- 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_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
shownstate, 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]¶
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_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).
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.
A family’s tasks serialize across its managers in the per-package fan-out too.
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 onOperationTrail’stimerargument, which mpm leaves at itsNonedefault so the flag decides. It used to clock unconditionally.
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_docs_site_url_matches_pyproject()[source]¶
The canonical docs origin is declared once, in
pyproject.toml.docs/conf.pyreads it from there forhtml_baseurl, but runtime code cannot:pyproject.tomlis not shipped in the wheel, so_docs.DOCS_SITE_URLrepeats 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_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_platformsrelease, whose platform groups a downstream packager cannot be expected to match, soconftestskips 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 inpyproject.tomlis consistent with theLABELSregistry.
- 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-basedrules left behind when thelabels.pygroup was renamedrpm-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 inpyproject.tomlmatch a fresh generation from the pool, in the schema repomatic reads.Drift means a manager was added without running
docs/docs_update.py(repomatic’supdate-docsjob 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.0replaced 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.tomlwriters must emitpyproject-fmt-canonical style.Any deviation makes the
update-docsandformat-pyprojectautofix 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.tomlonly 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.TOMLDecodeErrorat 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.tomlmust have a matchinghomepagesentry so the table can link the identifier.Pool-implemented managers are excluded: their URL is sourced from the class’s
homepage_urlattribute, 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 supporttable 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 indocs/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
mpmplus 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
503arrived truncated.gzip.GzipFileneeds 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 indocs/install.md, so there is no checked-in copy to compare against: this test only guards the generator against crashes and drift in thedocs/assets/binaries.csvcell 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_OUTis 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 offdocs/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 indocs/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_FAMILIESmust 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 indocs/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.logorepeats them as literals, the SVG sources living indocs/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.pyproduces this shape; the directory holds nothing else.
- tests.test_docs.test_manager_logos_resolve()[source]¶
Check the
logoslug 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_stubsperforms ondocs/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 fromMANAGER_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 runningdocs/docs_update.py(repomatic’supdate-docsjob 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 becausemyst-parsersupports 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.brewis the subject: it is the one manager exercising every section.Skipped when the
docsdependency 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 ofdocs/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 onMANAGER_SECTIONS(a heading comes frommanager_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 underre.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 byANGLE_LINK_DESTbelow, so they never need blessing here.
- tests.test_docs.ANGLE_LINK_DEST = re.compile('\\]\\(<')¶
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
linkifyextension 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’sSee https://…warnings) and are left alone.
- tests.test_docs.test_docstrings_carry_no_angle_bracket_link_destination()[source]¶
The
](<url>)destination form renders as broken markup: seeANGLE_LINK_DESTfor 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_tracesand the corpus round-trip both readliteral_blocks(), so the rendered traces never drift from what the parsers are tested against. TOML managers keep their[samples]traces, covered bytest_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.mdand 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 openingdocs/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 indocs/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.
--brewfileis 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_FORGEScosts 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-docsjob refreshed the embedded blocks.Skipped when click-extra’s
[sphinx]extra (pulled by thedocsdependency 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-docsjob refreshed the embedded blocks withclick-extra refresh-directives.Skipped when click-extra’s
[sphinx]extra (pulled by thedocsdependency 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’sunsupportedset, with matching glyphs, sorted by anchor.The benchmark renders each
unsupportedmanager 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 whatmpmrefused to wrap. The🛟fallback marker is derived rather than written, so the page is held to whatunsupported_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 whatdocs/conf.pypinsmyst_heading_slug_functo, becausedocutilsonly 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.mdis 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,ReshimandImport from external toolrows outlived themise/asdfcolumns 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.mdis hand-curated, andmanager_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 fromdocs/managers/, one directory belowdocs/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/repomaticpins in its owntests/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 nomodel:(the recommended model rides incompatibility) and nodisable-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/repomaticenforces 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-toolsis 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.mdslices 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$ARGUMENTSline, 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.pyselects reference-trace blocks by (member, index) fromclass_display_blocks(), while this test replays the same blocks fromclass_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_regexesblocks are complete samples, so none may abbreviate its output with a(...)marker (an illustration that would truncate belongs under a non-harvestedconsolefence). A bare...is left alone: real CLI output legitimately contains it, like apt’sListing...header or aguixstore 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_regexesinvocations go throughrun_cli, which executes an argv directly with no shell, so a documented command joined to another by a shell pipe (| jqto 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-harvestedconsolefence.Re-tokenized with
shlex.split()like_documented_commandsdoes 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_clikwargs forwarded tobuild_cliwhen reconstructing the full command.sudois deliberately not forwarded: escalation depends on platform and policy, so documentedsudoprefixes are stripped on the other side.
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_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
CLIErroris 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
CLIErrorstays 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:
FakeManagerFake manager whose diagnosis succeeds with a report.
Initialize
cli_errorslist.- 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.
- 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 checkreports 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 doctorwarns 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 (doctorsits 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.
- 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
pythonorpython3.
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.
- class tests.test_doctor.SickDoctorFakeManager[source]¶
Bases:
HealthyDoctorFakeManagerFake manager whose diagnosis reports problems.
Initialize
cli_errorslist.- 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 checkreports 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 doctorwarns 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 (doctorsits 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.
- 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
pythonorpython3.
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.
- class tests.test_doctor.SilentDoctorFakeManager[source]¶
Bases:
HealthyDoctorFakeManagerFake manager whose diagnosis succeeds silently (the apt-get check shape).
Initialize
cli_errorslist.- 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 checkreports 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 doctorwarns 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 (doctorsits 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.
- 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
pythonorpython3.
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.
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
Operationsenum never drift apart.
- tests.test_execution.test_operation_name_sets_match_enum()[source]¶
The literal operation-name sets stay subsets of the
Operationsvocabulary._MUTATING_OPERATIONS(plan-mode capture) and_STALL_NOTICE_OPERATIONS(watchdog arming) are spelled as string literals: renaming anOperationsmember 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 (
--timeoutor 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.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 thestop_on_error/must_succeedcontext.
- tests.test_execution.test_cli_error_diagnosis(error, output, expected)[source]¶
CLIError.diagnosisprefers<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 upgradeon a synthesized upgrade-all prints the per-package upgrade commands, resolved against a really-executedoutdatedread.Guards the read-stamp of the synthesized fallback in
PackageManager.upgrade: the whole run is stampedupgrade_all(a mutating operation), so without re-stamping the inner listing asoutdated, 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
finallyclears 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-interactivewhen 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-interactivethat cannot authenticate triggers the actionable hint.A fake
sudoonPATHmimics “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]¶
restorefans 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 (throughCLIExecutor.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
searchstamp, notinstall.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 slowbrew searchof 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
Statemapping inextension.js, each backed by ampm-<state>-symbolic.svgicon.
- 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
Statemapping values match the bundled symbolic icon set.
- tests.test_gnome_extension.test_runtime_argv_long_form()[source]¶
The argv builders only emit long-form options.
Same rule as for every argv
mpmitself constructs at runtime (see the manager classes andmeta_package_manager/sudo.py). The only tolerated short form issh -c, which POSIX defines with no long equivalent.
- tests.test_gnome_extension.test_extension_js_shell_free_module()[source]¶
mpm.jsnever imports shell modules, so it stays testable under gjs.
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_manager_apt module¶
APT-specific tests.
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_overridesintests.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_accepts_orphan_operations()[source]¶
The schema recognizes the
remove_orphanandcleanup_orphanrefinements;remove_orphanrequires its{package_id}placeholder likeremove.
- tests.test_manager_definition.test_parse_definition_rejects_legacy_cleanup()[source]¶
The monolithic
cleanupoperation 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_orphantargets 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
installedregex may capture only the package ID (Clear Linux bundles and Cygwin listings carry no version), asearchmay 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_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_build_class_implements(operations, implemented, not_implemented)[source]¶
- tests.test_manager_definition.test_factory_operation_cli(monkeypatch)[source]¶
An operation carrying its own
cliresolves 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
orphansread-only query, parsed likeinstalledand 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_orphansubstitutes{package_id}likeremove,cleanup_orphanruns a bare command likecleanup. Both register as implemented capabilities.
- tests.test_manager_definition.test_factory_operation_sudo(monkeypatch)[source]¶
A
sudo = trueoperation is built privileged; unmarked ones are not.
- tests.test_manager_definition.test_factory_query_sudo(monkeypatch)[source]¶
A query marked
sudo = trueruns 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_register_refuses_untrusted_file(tmp_path, reset_definitions, caplog)[source]¶
- tests.test_manager_definition.test_register_rejects_builtin_collision(reset_definitions, caplog)[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_clibinary 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_regexandtest_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_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_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_steamcmd_capabilities(operation, expected)[source]¶
SteamCMD exposes no inventory: only install and per-title upgrade exist, both mapping to the same idempotent
+app_updatecommand.
- 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).
whichis stubbed so operations declaring a siblingcliresolve 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_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_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.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:ruffround-trip without rewriting.
- 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_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.
pipis 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
poolis a module-level singleton: any attribute we shadow on a manager instance viaapply_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
PackageManagerbase class.Drift between
OVERRIDABLE_FIELDSand the base class is silent at runtime (setattrhappily creates new attributes) so this test acts as the safety net.
- tests.test_manager_overrides.test_str_tuple_override_coerces_list_to_tuple(reset_overrides)[source]¶
- tests.test_manager_overrides.test_str_tuple_override_rejects_non_string_entries(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
boolsubclassesintin Python.
- 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
timeoutoverride must survive the globalsetattrloop in_select_managers: the most specific value wins.
- tests.test_manager_overrides.test_timeout_binds_detection_phase(reset_overrides, monkeypatch, override, expected)[source]¶
_select_managersbinds the version-detection probes to the resolved timeout beforewarm_availabilityruns them: the global--timeoutreaches 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_per_manager_sudo_beats_global_default(reset_overrides)[source]¶
A per-manager
sudooverride survives the globalsetattrloop, 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 = truefrom 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-configruns 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_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_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_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_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-contribssilences the hint output.
- tests.test_manager_overrides.test_cli_opt_out_via_config(invoke, create_config, reset_overrides)[source]¶
The
[mpm] suggest_contribs = falseconfig also silences the hint.
- tests.test_manager_overrides.test_dump_manager_overrides_skips_none_values()[source]¶
Attributes whose current value is
Noneare omitted from the dump.
- tests.test_manager_overrides.test_dump_manager_overrides_converts_tuples_to_lists()[source]¶
Tuples are converted to lists so
tomli_wcan 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_FIELDSconverters 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_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.prefixunder a Homebrew Cellar is conclusive on its own.
- tests.test_manager_pip.test_bundled_app_detected_by_brew_installer(prefix)[source]¶
Outside Cellar, an
INSTALLERofbrewstill flags the bundle.
- tests.test_manager_pip.test_brew_installer_case_and_whitespace_insensitive()[source]¶
The
INSTALLERtag 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 (
mpmnot installed) is not a bundle.
- tests.test_manager_pip.test_pip_install_blocked_parses_probe(stdout, expected)[source]¶
The probe’s
1/0output 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.
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_pwsh_gallery module¶
PowerShell Gallery parsing tests.
These tests cover the pure-Python parsing/quoting logic. They do not invoke
pwsh and are platform-agnostic.
- tests.test_manager_pwsh_gallery.test_installed_handles_empty_pipeline(manager, stub_run_cli)[source]¶
A fresh shell with no resources installed produces empty stdout.
- tests.test_manager_pwsh_gallery.test_search_passes_wildcard_pattern(manager, capture_run_cli)[source]¶
Fuzzy search wraps the query with
*wildcards in the expression.
- tests.test_manager_pwsh_gallery.test_search_exact_drops_wildcards(manager, capture_run_cli)[source]¶
Exact search passes the query verbatim, without surrounding wildcards.
- tests.test_manager_pwsh_gallery.test_install_builds_expected_expression(manager, capture_run_cli)[source]¶
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
uvmanager’srun_cliinvocations instead of executing.
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.test_parse_details_ignores_indented_upgrade_line(winget)[source]¶
Regression test: the package-block split must not fire on indented
winget [version]lines underAvailable Upgrades.
- 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_managers module¶
- 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-formedhttp(s)link.Conversely, an
unmaintained_messageis only meaningful on an unmaintained manager.
- tests.test_managers.test_maintenance_note(manager)[source]¶
A
maintenance_noteis a MyST markdown block whose embedded URLs must be well-formedhttp(s)links.It is mutually exclusive with the
unmaintainedflag: a confirmed-dead manager documents itself with anunmaintained_messageinstead 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_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_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_regexesparses the documented--versionoutput.Regression guard for the case where
re.VERBOSEsilently swallowed literal spaces in patterns likeguix \(GNU Guix\) ..., leaving the manager unable to detect its own version.
- 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
_*_REGEXPparsers) are not governed and conventionally sit between the attributes and the operations. Consumed bytest_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) beforePackageManager’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 incli_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_ATTRSand methods the base classes’ own declaration order (seecollect_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-pythonfloor equals the lowest declared classifier.A network-free companion to the matrix check below: it catches a
requires-pythonbump 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-pythonfloor.Drives the real
repomaticCLI, 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-packageescape hatch is the documented per-package exemption everyuvxcall carrying this pin needs. The workflow setsUV_EXCLUDE_NEWERfor 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 theskipbranch 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 fromrequires-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 --orphansmaps 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
removekeeps 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_orphanunimplemented, so the CLI action catches theNotImplementedErrorand falls back to a plain removal.
- tests.test_orphans.test_cleanup_orphan_uses_native_sweep(monkeypatch, manager_id, sweep_token)[source]¶
cleanup --orphansmaps 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
cleanupcomposer 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
orphansquery propagatesNotImplementedErrorfrom the base sweep, socleanup --orphansskips 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
orphansquery parses its native listing into packages.The canned outputs mirror the
shell-sessionsamples of theorphansdocstrings, 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
orphansoperation.
- tests.test_orphans.test_implements_method_introspection()[source]¶
implements_methodreports overrides of the baseNotImplementedErrorstubs, 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
removeandcleanup.
- class tests.test_orphans.RemovableFakeManager[source]¶
Bases:
FakeManagerFake manager that removes packages but has no scoped orphan-cascade verb.
Exercises the
remove --orphansfallback:remove_orphanstays unimplemented (baseNotImplementedError) whileremovesucceeds, so the CLI must catch the former and fall back to the latter.Initialize
cli_errorslist.- remove(package_id)[source]¶
Remove one package and one only.
Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- 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
pythonorpython3.
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.
- class tests.test_orphans.CleanupFakeManager[source]¶
Bases:
FakeManagerFake manager with a cache category only: no system-wide orphan sweep.
Initialize
cli_errorslist.- cleanup_cache()[source]¶
Prune the manager’s caches, downloads and other left-over artifacts.
The cache category of
cleanup(), surfaced asmpm cleanup --cacheand 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:
- 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
pythonorpython3.
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.
- class tests.test_orphans.OrphanCleanupFakeManager[source]¶
Bases:
CleanupFakeManagerFake manager that additionally supports the system-wide orphan sweep.
Initialize
cli_errorslist.- 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 plaincleanup()composition and only runs on an explicitmpm cleanup --orphans.Distinct from
meta_package_manager.manager.PackageManager.remove_orphan(), which is scoped to one package’s own orphaned dependencies. As withcleanup(), 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
orphansquery and package removal: list the orphans, remove each one (withremove_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 fullupgrade --all, and the in-process equivalent of Arch’s classicpacman -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
orphansquery propagatesNotImplementedError, andmpm cleanup --orphanssimply skips it.- Return type:
- 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
pythonorpython3.
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.
- class tests.test_orphans.SweepSynthesizingFakeManager[source]¶
Bases:
CleanupFakeManagerFake manager with cleanup, an orphan listing (inherited) and removal, but no native sweep verb:
cleanup --orphansmust go through the synthesized base sweep, never the fullcleanup.Initialize
cli_errorslist.- remove(package_id)[source]¶
Remove one package and one only.
Optional. Will be simply skipped by mpm if not implemented.
- Return type:
- 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
pythonorpython3.
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.
- class tests.test_orphans.DecomposedCleanupFakeManager[source]¶
Bases:
CleanupFakeManagerFake manager with both orphan and cache category steps.
Initialize
cli_errorslist.- 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 plaincleanup()composition and only runs on an explicitmpm cleanup --orphans.Distinct from
meta_package_manager.manager.PackageManager.remove_orphan(), which is scoped to one package’s own orphaned dependencies. As withcleanup(), 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
orphansquery and package removal: list the orphans, remove each one (withremove_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 fullupgrade --all, and the in-process equivalent of Arch’s classicpacman -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
orphansquery propagatesNotImplementedError, andmpm cleanup --orphanssimply skips it.- Return type:
- 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
pythonorpython3.
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.
- tests.test_orphans.test_remove_orphans_falls_back_to_plain_removal(invoke, removable_fake_pool)[source]¶
remove --orphanson a manager without a cascade verb removes the package only, narrating the skip atINFOand still exiting cleanly.
- tests.test_orphans.test_cleanup_orphans_runs_only_the_sweep(invoke, monkeypatch)[source]¶
cleanup --orphansrunscleanup_orphan, not the fullcleanup.
- tests.test_orphans.test_cleanup_without_flags_runs_default_categories(invoke, monkeypatch)[source]¶
Plain
cleanupruns 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 --orphanson a manager without a native sweep verb synthesizes it: the listed orphans are removed one by one, and the fullcleanupnever 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
--cacheand under a plaincleanupalike.
- tests.test_orphans.test_plain_cleanup_never_engages_synthesized_sweep(invoke, monkeypatch)[source]¶
Plain
cleanupruns native categories only: the flag pairs default toTrue(so--helprenders 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-cachemust not surprise-remove packages, so the manager is skipped entirely.
- tests.test_orphans.test_cleanup_skip_orphans_keeps_native_categories(invoke, monkeypatch)[source]¶
--skip-orphanson a manager with a synthesizable-but-not-native sweep keeps its native categories running: only the sweep is subtracted.
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.
No manager may belong to two lock families, or its lane grouping is ambiguous.
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_GROUPSgroups managers by the packaging ecosystem an issue lands in,SHARED_LOCK_FAMILIESthe 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:
finkandopkgspeak dpkg through databases of their own,zerobrewhas its own prefix anddkp-pacmanits own repositories, whilepypi-based,npm-basedand 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.
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.
Every manager inheriting from
Pacmandrives pacman’s own database, so it must serialize with the rest of that family.dkp-pacmanis the sole exemption: devkitPro ships it to sit beside a distribution’spacmanwith 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 through7.6.1.Inheritance is a sufficient signal, never a necessary one:
pamacreaches the samelibalpmthrough a bundled definition and is covered by the family without appearing here.
- 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
--timeoutlands 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_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.
A lane’s managers probe against one shared
run_cache, then get their own value back: that dict is what collapsesbrewandcaskboth runningbrew --versioninto 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 thejsonschema-based validation stack (rfc3987-syntax,lark,lxml) stays out ofmpm’s runtime dependencies. Seemeta_package_manager.sbom.- Return type:
- 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
DESCRIBESare emitted, anddownload_locationfalls back toNOASSERTION.
- tests.test_sbom.test_bundled_mode_spdx_populates_rich_fields()[source]¶
A populated
PackageMetadataflows into the SPDX document: license, supplier override, originator, checksum, and a dependency relationship resolved atfinalize()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.detailswith 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.
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_parse_purls_maps_ecosystems(purls, expected_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.
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_specifier module¶
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:
- tests.test_sphinx_crossrefs.read_html(built_docs, filename)[source]¶
Read a built HTML page.
- Return type:
- tests.test_sphinx_crossrefs.test_config_summary_links_to_option_sections(built_docs, option)[source]¶
The
{click:config} mpmsummary table deep-links each option to its section.
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
sudoitself 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_sudoprobes, yielding therunmock.Pins the platform (
windows), the effective user (root) and the terminal state (Noneleaves the real descriptor unpatched, for tests that never reach the TTY check), and replacessubprocess.runso no test ever launches a realsudo. Callers set the mock’sreturn_value/side_effectto shape the probe and prompt outcomes.
- 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
--promptargument handed tosudomust 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 --promptexpands%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_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_version module¶
- tests.test_version.test_token_int_none_for_non_integers(value)[source]¶
Token.integerisNonefor non-integer tokens.
- 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_comparison_edge_cases()[source]¶
Comparison with
Noneand 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_range_empty_spec()[source]¶
Empty spec has no constraints, so any version satisfies it.
- tests.test_version.test_parse_version_is_tokenized_string()[source]¶
parse_versionis an alias forTokenizedString.