Skip to content

matrix

matrix

Multi-distro test-matrix engine shared by the terok-* siblings.

Historically each sibling carried its own ~500-line run-matrix.sh plus a set of Containerfiles, ~90% identical across repos and already drifting. This package is the single engine; a consuming repo keeps only a declarative tests/containers/matrix.yml and (optionally) per-slot Containerfile fragments.

Collaborators, in reading order:

  • catalog — facts about the shared slot images (expected podman versions, non-systemd slots, slot kinds);
  • config — the matrix.yml schema and loader (MatrixConfig);
  • contract — the consumer conftest's half of the TEROK_EXPECT capability contract (check_capability_contract);
  • inner — generates the scripts that run inside a slot's test container;
  • runner — host-side build/run/prune;
  • cli — the terok-matrix entry point (uv run terok-matrix from a consuming repo's root).

External tooling (the superbuild TUI, CI matrix generation) should not shell-parse anything: load the same matrix.yml via load_config or ask the CLI (terok-matrix --slots-json / --image-prefix).

SLOTS = {'debian12': SlotSpec(expected_podman='4.3.1'), 'ubuntu2404': SlotSpec(expected_podman='4.9.3'), 'ubuntu2604': SlotSpec(expected_podman='5.7.0'), 'debian13': SlotSpec(expected_podman='5.4.2'), 'fedora43': SlotSpec(expected_podman='5.8.4'), 'fedora44': SlotSpec(expected_podman='5.8.4'), 'podman': SlotSpec(expected_podman='latest', user='podman'), 'alpine': SlotSpec(expected_podman='5.3.2', non_systemd=True), 'void': SlotSpec(expected_podman='latest', non_systemd=True), 'mageia': SlotSpec(expected_podman='4.9.5'), 'nix': SlotSpec(kind=(SlotKind.NIX))} module-attribute

__all__ = ['SLOTS', 'MatrixConfig', 'MatrixConfigError', 'SlotKind', 'SlotSpec', 'binary_on_path', 'check_capability_contract', 'load_config', 'tcp_reachable'] module-attribute

SlotKind

Bases: StrEnum

How a slot's test container is driven.

CONTAINER = 'container' class-attribute instance-attribute

NIX = 'nix' class-attribute instance-attribute

SlotSpec(expected_podman='latest', non_systemd=False, user='testrunner', kind=SlotKind.CONTAINER) dataclass

Facts about one matrix slot's base image.

Parameters:

Name Type Description Default
expected_podman str

Distro-shipped podman version the slot is pinned to; "latest" for rolling images (and for slot kinds that never read it, like nix).

'latest'
non_systemd bool

The runner hard-fails the slot if systemd is present — these slots exist to prove the systemd-free floor.

False
user str

Non-root user baked into the image (uid 1000).

'testrunner'
kind SlotKind

Driving mode, see SlotKind.

CONTAINER

expected_podman = 'latest' class-attribute instance-attribute

non_systemd = False class-attribute instance-attribute

user = 'testrunner' class-attribute instance-attribute

kind = SlotKind.CONTAINER class-attribute instance-attribute

MatrixConfig(image_prefix, flavor, groups, expect, slots, phases, containers_dir, repo_root) dataclass

A repo's whole matrix declaration, resolved and validated.

Parameters:

Name Type Description Default
image_prefix str

Image/container name prefix and prune-label value.

required
flavor str

Shared Containerfile family (podman or dbus).

required
groups tuple[str, ...]

Dependency groups synced before testing.

required
expect tuple[str, ...]

TEROK_EXPECT capability contract; empty = not exported.

required
slots dict[str, SlotConfig]

Selected slots in declaration order.

required
phases tuple[Phase, ...]

Repo-level test flow.

required
containers_dir Path

Directory of the matrix.yml (fragments live here).

required
repo_root Path

Build context and bind-mounted source tree.

required

image_prefix instance-attribute

flavor instance-attribute

groups instance-attribute

expect instance-attribute

slots instance-attribute

phases instance-attribute

containers_dir instance-attribute

repo_root instance-attribute

slot_groups(name)

Dependency groups effective for slot name.

Source code in src/terok_util/matrix/config.py
def slot_groups(self, name: str) -> tuple[str, ...]:
    """Dependency groups effective for slot ``name``."""
    override = self.slots[name].groups
    return self.groups if override is None else override

slot_expect(name)

Capability contract effective for slot name.

Source code in src/terok_util/matrix/config.py
def slot_expect(self, name: str) -> tuple[str, ...]:
    """Capability contract effective for slot ``name``."""
    override = self.slots[name].expect
    return self.expect if override is None else override

slot_phases(name)

Test flow effective for slot name.

Source code in src/terok_util/matrix/config.py
def slot_phases(self, name: str) -> tuple[Phase, ...]:
    """Test flow effective for slot ``name``."""
    override = self.slots[name].phases
    return self.phases if override is None else override

MatrixConfigError

Bases: ValueError

A matrix.yml failed validation.

load_config(path)

Load and validate a matrix.yml.

Parameters:

Name Type Description Default
path Path

The config file; its grandparent directory is taken as the repo root (<root>/tests/containers/matrix.yml).

required

Raises:

Type Description
MatrixConfigError

On unknown keys, unknown slots, or a phase that is neither a command phase nor a pytest phase.

Source code in src/terok_util/matrix/config.py
def load_config(path: Path) -> MatrixConfig:
    """Load and validate a ``matrix.yml``.

    Args:
        path: The config file; its grandparent directory is taken as the
            repo root (``<root>/tests/containers/matrix.yml``).

    Raises:
        MatrixConfigError: On unknown keys, unknown slots, or a phase that
            is neither a command phase nor a pytest phase.
    """
    raw = yaml_load(path.read_text(encoding="utf-8"))
    if not isinstance(raw, dict):
        raise MatrixConfigError(f"{path}: expected a YAML mapping at top level")

    data = _Section(dict(raw), where=str(path))
    flavor = data.string("flavor")
    if flavor not in FLAVORS:
        raise MatrixConfigError(f"{path}: unknown flavor '{flavor}'; known: {list(FLAVORS)}")
    config = MatrixConfig(
        image_prefix=data.string("image-prefix"),
        flavor=flavor,
        groups=data.strings("groups", default=("test",)),
        expect=data.strings("expect", default=()),
        slots=_parse_slots(data.mapping("slots"), where=str(path)),
        phases=_parse_phases(data.list_of_maps("phases"), where=str(path)),
        containers_dir=path.parent.resolve(),
        repo_root=path.parent.parent.parent.resolve(),
    )
    data.reject_leftovers()
    return config

binary_on_path(name)

Whether name resolves on the sbin-extended PATH.

Source code in src/terok_util/matrix/contract.py
def binary_on_path(name: str) -> bool:
    """Whether *name* resolves on the sbin-extended ``PATH``."""
    search = os.pathsep.join([os.environ.get("PATH", ""), *_SBIN_DIRS])
    return shutil.which(name, path=search) is not None

check_capability_contract(probes)

Verify TEROK_EXPECT against probes; return the failure, if any.

Parameters:

Name Type Description Default
probes Mapping[str, Callable[[], bool]]

Capability name to presence probe, the repo-specific half of the contract.

required

Returns:

Type Description
str | None

A human-readable failure message when the contract names unknown

str | None

capabilities or a declared capability is missing; None when

str | None

the contract holds (or none is declared — dev-machine runs).

Source code in src/terok_util/matrix/contract.py
def check_capability_contract(probes: Mapping[str, Callable[[], bool]]) -> str | None:
    """Verify ``TEROK_EXPECT`` against *probes*; return the failure, if any.

    Args:
        probes: Capability name to presence probe, the repo-specific half
            of the contract.

    Returns:
        A human-readable failure message when the contract names unknown
        capabilities or a declared capability is missing; ``None`` when
        the contract holds (or none is declared — dev-machine runs).
    """
    expected = {c for c in os.environ.get(EXPECT_ENV, "").split(",") if c}
    if not expected:
        return None
    unknown = expected - probes.keys()
    if unknown:
        return f"{EXPECT_ENV} names unknown capabilities: {sorted(unknown)}"
    missing = sorted(cap for cap in expected if not probes[cap]())
    if missing:
        return "matrix capability contract broken - expected but missing: " + ", ".join(missing)
    return None

tcp_reachable(ip, port, timeout=5.0)

Whether a TCP connection to ip:port succeeds within timeout.

Source code in src/terok_util/matrix/contract.py
def tcp_reachable(ip: str, port: int, timeout: float = 5.0) -> bool:
    """Whether a TCP connection to ``ip:port`` succeeds within *timeout*."""
    try:
        with socket.create_connection((ip, port), timeout=timeout):
            return True
    except OSError:
        return False