Files
computer-playbook/tests/external/repository/test_packages_available.py
kevinveenbirkenbachandClaude Opus 5 00784105fb fix(test): stop the package probes from throttling themselves
archlinux.org answers 429 and resets connections when the suite fires its
16 workers at it: 22 arch probes in parallel, 12 answered and 4 refused in
a reproduction inside the test container, while each name resolves fine on
its own. Those 22 self-inflicted failures then took the warning path,
because an unreachable index is supposed to say nothing about the
declaration — so the suite reported green on 22 packages it never checked.

_get now holds at most two requests per host and retries 429 and 5xx with
backoff; a DNS failure is not retried, it is not transient. That alone
removed all 22 warnings, 45 down to 23, with the runtime unchanged.

The remaining 23 are declared: a third-party repository the role
configures itself, or a name the index legitimately does not list because
the package is virtual. Outcome carries that as a flag now, so those stay
warnings while anything else that could not be checked fails the suite and
names each case. Verified by pointing the arch probe at an unreachable
host: 94 packages reported as unchecked, and the run went red.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 19:05:20 +02:00

110 lines
3.6 KiB
Python

"""External: every declared package resolves in its distribution's index.
Queries the official package index of each default distribution in
parallel instead of installing anything, so the whole registry is checked
in seconds rather than one container build per package.
A package that the index positively reports as absent fails the test. A
network or index error is reported as a warning, because an unreachable
mirror says nothing about the declaration.
"""
from __future__ import annotations
import concurrent.futures
import unittest
import warnings
from utils.packages.registry import build_registry, resolve
from utils.packages.schema import DISTRO_FAMILY
from . import PROJECT_ROOT
from ._package_probes import (
_MAX_WORKERS,
Outcome,
PackageAvailabilityWarning,
Probe,
_probe,
)
def _collect_probes() -> list[Probe]:
probes: list[Probe] = []
for package_id, declaration in sorted(build_registry(PROJECT_ROOT).items()):
for distro in sorted(DISTRO_FAMILY):
spec = resolve(declaration, distro)
if spec is None:
continue
probes.extend(
Probe(
package_id,
distro,
name,
spec.repo,
spec.source,
spec.virtual,
)
for name in spec.names
)
return probes
class TestPackagesAvailable(unittest.TestCase):
def test_declared_packages_exist_in_their_index(self) -> None:
probes = _collect_probes()
if not probes:
self.skipTest("no packages declared")
outcomes: list[Outcome] = []
with concurrent.futures.ThreadPoolExecutor(_MAX_WORKERS) as pool:
outcomes.extend(pool.map(_probe, probes))
unknown = [o for o in outcomes if o.available is None]
for outcome in (o for o in unknown if o.declared):
warnings.warn(
f"{outcome.probe.package_id} / {outcome.probe.distro} / "
f"{outcome.probe.name}: {outcome.detail}",
PackageAvailabilityWarning,
stacklevel=1,
)
unverified = [o for o in unknown if not o.declared]
if unverified:
self.fail(
f"{len(unverified)} package(s) could not be checked at all, so "
"reporting this run green would claim a verification that did "
"not happen. Fix the index access, or declare the exemption "
"(third-party repo / virtual) where the package is declared:\n"
+ "\n".join(
f" - {o.probe.package_id} / {o.probe.distro} / "
f"{o.probe.name}: {o.detail}"
for o in sorted(
unverified, key=lambda o: (o.probe.package_id, o.probe.distro)
)
)
)
missing = [o for o in outcomes if o.available is False]
if not missing:
return
lines = [
(
f"{len(missing)} declared package(s) were not found in the index "
f"of their distribution (probed {len(probes)}, "
f"{len(unknown)} inconclusive):"
)
]
lines.extend(
f" - {outcome.probe.package_id} / {outcome.probe.distro}: "
f"'{outcome.probe.name}' absent from {outcome.detail}"
for outcome in sorted(
missing, key=lambda o: (o.probe.package_id, o.probe.distro)
)
)
self.fail("\n".join(lines))
if __name__ == "__main__":
unittest.main()