mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 02:54:26 +00:00
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>
110 lines
3.6 KiB
Python
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()
|