Skip to main content

Bulk-run checks across many charm repositories with a dependency swapped out.

Project description

hyrum

Named after Hyrum's law: once you have enough users, every observable behaviour of your code is depended on by somebody. This tool exists to find out who that "somebody" is — by running a proposed dependency change against a fleet of consumer repos before you ship it.

Bulk-run a check (typically lint or unit tests) across many charm repositories, optionally swapping out one of their dependencies first — for example, pointing every charm's ops dependency at a development branch of the operator repo to see which charms it breaks.

The primary use case today is swapping out ops (and its optional testing / tracing companions). The patcher layer is built as an abstraction so other dependencies (e.g. individual charm libraries) can be swapped in later without rewriting the runner.

Two runner backends are supported:

  • tox — runs tox -e <env> in each charm.
  • make — runs make <target> in each charm.

The runner is auto-detected per charm (tox.ini → tox, Makefile → make), with a CLI flag to force a specific one.

Status

Early-stage carve-out from charm-analysis/tools/super-tox.py. Scope during the 26.10 cycle is lint and unit tests only — integration tests are explicitly out of scope.

Non-goals

  • Running integration tests.
  • Acting as a general-purpose CI orchestrator.

Host prerequisites

A non-trivial fraction of charms in the curated list pull C/Rust extensions that pip / uv will build from source if no wheel is available for the host's Python. On a fresh Ubuntu host, missing build tools surface as failed outcomes with messages like "command 'x86_64-linux-gnu-gcc' failed: No such file" or "fatal error: Python.h / ffi.h: No such file", which is noise rather than a charm regression.

To get a clean signal against the curated charm list, install:

sudo apt-get install -y \
    build-essential \
    pkg-config \
    libffi-dev \
    libpq-dev \
    libmariadb-dev \
    python3-dev   # or python3.<minor>-dev matching the Python uv selects

# Poetry is invoked by ~5 % of charms' tox envs; install it if you want
# those to run instead of failing with "No such file or directory:
# 'poetry'".
uv tool install poetry

A handful of charms shell out to other tools such as yq or go from their tox env or Makefile. Those aren't installed up-front since they only affect a few charms in the curated list; they show up as failed (not patcher_error) with a command not found line in the per-charm log. Install the missing tool to surface the underlying charm result.

Some charms also pull C/Rust extensions whose latest releases pre-date the host's Python version. PyO3 < 0.23 can't build against Python 3.14 unless you opt in with the stable-ABI escape hatch (unit in testenv:unit.… is the tox env name hyrum invokes via tox -e unit, i.e. the charm's unit-test environment):

export PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1
export TOX_OVERRIDE='testenv:unit.pass_env+=PYO3_USE_ABI3_FORWARD_COMPATIBILITY'

If you also want -Werror semantics (warnings promoted to errors), inject PYTHONWARNINGS=error via pass_env, not set_env:

export PYTHONWARNINGS=error
export TOX_OVERRIDE='testenv:unit.pass_env+=PYTHONWARNINGS;testenv:unit.pass_env+=PYO3_USE_ABI3_FORWARD_COMPATIBILITY'

The intuitive form set_env+=PYTHONWARNINGS=error looks correct but silently drops anything the charm's [testenv] set via set_env (most commonly PYTHONPATH), so tests that do from charm import … fail at collection with ModuleNotFoundError — a misleading "regression" that isn't a warning at all. pass_env+= doesn't touch set_env, so the charm's PYTHONPATH stays intact and the warning still propagates.

Empirically (Ubuntu Resolute, system Python 3.14, 145 runnable charms in the curated list as of 2026-05): a host with none of these installed passes ~40 %; adding build-essential + python3.14-dev lifts that to ~60 %; the full apt list above gets to ~64 %; the PyO3 forward-compat flag adds ~3 % more, topping out around 67 %. The residual ~33 % is genuine charm-side breakage (test failures, dependencies pinned to versions that don't build on the host Python) and is not something hyrum itself can move.

Usage

# Install (editable, with the lint/static/unit dependency groups for
# ruff, pyright, pytest, …):
uv sync --all-groups

# Populate the local cache with every charm in charm-list/charms.csv
# (shallow clones, pulls for repos that already exist):
hyrum get-charms

# Run `tox -e unit` across every charm in the default cache
# (~/.cache/hyrum/charms), with ops swapped to the `fix/X` branch of
# canonical/operator. Override the charms directory with --charms-dir or
# the HYRUM_CHARMS environment variable.
hyrum check unit --workers 8 --patch 'ops @ canonical:fix/X'

# --patch takes a PEP 508 requirement; may be given multiple times.
# Other accepted forms for ops: a PyPI version (`ops==2.17.0`), a
# `git+<url>[@branch]` reference (`ops @ git+https://…/operator@fix/X`), a
# plain `https://…/operator[@branch]` URL, or a local checkout
# (`ops @ /path/to/operator`, `ops @ ~/operator`,
# `ops @ file:///path/to/operator`). The `owner:branch` shorthand is
# ops-only.
#
# Swap any other dependency the same way, e.g.:
#   hyrum check unit --patch 'requests==2.31.0'
#   hyrum check unit --patch 'requests @ git+https://github.com/psf/requests@main'
# Patching ops is the default if no --patch is given; pass an explicit
# --patch for another package (without ops) to leave ops alone.

# Force the make runner (default is auto-detect: tox.ini -> tox,
# Makefile -> make, fall back to the other if the target is missing):
hyrum check unit --runner make

# Skip the dependency swap; just check how the charms behave as-pinned:
hyrum check unit --no-patch

# Only run for charms that use the Scenario testing framework:
hyrum check unit --framework scenario

# Always exit 0, even if some charms fail (default is exit non-zero on
# any failure):
hyrum check unit --no-fail

# Dump each charm's stdout, stderr, and run metadata to a per-charm
# file under the given directory for offline triage:
hyrum check unit --log-dir ~/hyrum-runs/logs

Output statuses:

status meaning
passed the runner exited 0
failed the runner exited non-zero
no_target tox env / make target not present in this charm (skipped, not failed)
timeout killed after --timeout seconds
patcher_error the dependency swap could not be applied (distinct from a tox failure)
skipped filtered out before the run (regex, ignore-list, no runnable target, …)

Dependency-swap scope

Today only the ops family (with optional testing / tracing extras → ops-scenario / ops-tracing) is handled by the built-in patcher. The patcher layer is a Patcher protocol so a future charm-library patcher (vendored lib/charms/…/v<n>/<file>.py swapped from a git source) can plug in without changes elsewhere.

Configuration

hyrum.toml (path overridable via --config) supports an [ignore] table that maps a category to a list of repo paths to skip. Categories are free-form; their name shows up in the run output as the skip reason. Example:

[ignore]
expensive = ["argo-operators", "mysql-router-k8s"]
manual    = ["opensearch-operator"]

License

Apache 2.0. See LICENSE.txt.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hyrum-1.0.0a1.tar.gz (144.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hyrum-1.0.0a1-py3-none-any.whl (65.1 kB view details)

Uploaded Python 3

File details

Details for the file hyrum-1.0.0a1.tar.gz.

File metadata

  • Download URL: hyrum-1.0.0a1.tar.gz
  • Upload date:
  • Size: 144.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for hyrum-1.0.0a1.tar.gz
Algorithm Hash digest
SHA256 e960735bc60864afa56febf8bd398f85209371cd804d5b929e44a81e311c68bf
MD5 cff0cce6cb01bb637072cf4fe09f45ea
BLAKE2b-256 e78594040e9c6b152a26616c13aa341dc0c5c69fabbec90949da70a5effe7aac

See more details on using hashes here.

Provenance

The following attestation bundles were made for hyrum-1.0.0a1.tar.gz:

Publisher: publish.yaml on canonical/hyrum

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file hyrum-1.0.0a1-py3-none-any.whl.

File metadata

  • Download URL: hyrum-1.0.0a1-py3-none-any.whl
  • Upload date:
  • Size: 65.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for hyrum-1.0.0a1-py3-none-any.whl
Algorithm Hash digest
SHA256 430a96136744d8dc7f3e8cfa33148a7058072b1e6c90622c1c1bfef3afe5e895
MD5 7ba102df3f1e394016da20cb048f0835
BLAKE2b-256 38e7594c180a094388686ab8ade1948b01d34c91d532a70b8b107dfdce7079b1

See more details on using hashes here.

Provenance

The following attestation bundles were made for hyrum-1.0.0a1-py3-none-any.whl:

Publisher: publish.yaml on canonical/hyrum

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page