Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions .github/workflows/coverage-floor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ on:
type: boolean
default: false
postgres_image:
description: "Docker image for the postgres service. Default 'postgres:16'. A bare Docker Official Image name like that one (no '/') is pulled from Google's Docker Hub mirror as mirror.gcr.io/library/<name>, because Docker Hub rate-limits the anonymous pulls GitHub-hosted runners make. A reference with a '/' is pulled exactly as given, so pass a Docker Hub image outside library/ in its mirror form, e.g. 'mirror.gcr.io/pgvector/pgvector:pg16' when tests need the vector extension."
description: "Docker image for the postgres service. Default 'postgres:16'. A bare Docker Official Image name like that one (no '/') is pulled from Google's Docker Hub mirror as mirror.gcr.io/library/<name>, because Docker Hub rate-limits the anonymous pulls GitHub-hosted runners make. A reference with a '/' is pulled exactly as given, so pass a Docker Hub image outside library/ in its mirror form, e.g. 'mirror.gcr.io/pgvector/pgvector:pg16' when tests need the vector extension. Pulled only when services_postgres is true."
required: false
type: string
default: "postgres:16"
Expand All @@ -122,7 +122,7 @@ on:
type: boolean
default: false
redis_image:
description: "Docker image for the redis service. Default 'redis:7'. Pulled like postgres_image: a bare name from mirror.gcr.io/library/, a reference with a '/' exactly as given."
description: "Docker image for the redis service. Default 'redis:7'. Pulled like postgres_image: a bare name from mirror.gcr.io/library/, a reference with a '/' exactly as given, and only when services_redis is true."
required: false
type: string
default: "redis:7"
Expand Down Expand Up @@ -216,6 +216,11 @@ jobs:
measure:
name: Measure coverage and enforce floor
runs-on: ubuntu-latest
# A service starts only when its services_* input asks: GitHub skips a
# service whose image is an empty string, so a caller that never uses a
# service pulls nothing at all. Pinned by
# selftest/test_coverage_floor_services.py.
#
# Docker Hub rate-limits anonymous pulls per IP, and GitHub-hosted runners
# share egress IPs: on 2026-10-09 dotclaude#479 (run 37991904725) and #485
# failed before their first step on `docker pull postgres:16` ->
Expand All @@ -229,7 +234,7 @@ jobs:
# Pinned by selftest/test_job_images_off_docker_hub.py.
services:
postgres:
image: ${{ contains(inputs.postgres_image || 'postgres:16', '/') && inputs.postgres_image || format('mirror.gcr.io/library/{0}', inputs.postgres_image || 'postgres:16') }}
image: ${{ inputs.services_postgres && (contains(inputs.postgres_image || 'postgres:16', '/') && inputs.postgres_image || format('mirror.gcr.io/library/{0}', inputs.postgres_image || 'postgres:16')) || '' }}
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
Expand All @@ -242,7 +247,7 @@ jobs:
--health-timeout 5s
--health-retries 5
redis:
image: ${{ contains(inputs.redis_image || 'redis:7', '/') && inputs.redis_image || format('mirror.gcr.io/library/{0}', inputs.redis_image || 'redis:7') }}
image: ${{ inputs.services_redis && (contains(inputs.redis_image || 'redis:7', '/') && inputs.redis_image || format('mirror.gcr.io/library/{0}', inputs.redis_image || 'redis:7')) || '' }}
ports:
- 6379:6379
options: >-
Expand Down
81 changes: 81 additions & 0 deletions selftest/test_coverage_floor_services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Guard: coverage-floor.yml starts a service container only when asked.

2026-10-09: both service images were unconditional, so every caller pulled
postgres:16 and redis:7 from Docker Hub on every run, although
services_postgres / services_redis gated only what the tests saw (the
DATABASE_URL / REDIS_URL exports). Docker Hub's unauthenticated pull limit
then failed the job before it measured anything (topcoder1/dotclaude#485's
coverage-floor runs, among others), and many callers require this check.
Only inbox_superpilot opts in; every other caller pulled two images it never
used.

GitHub skips a service whose image is an empty string, so each image is gated
on its input. Switched on, a service pulls from Google's Docker Hub mirror
(test_job_images_off_docker_hub.py pins where each image comes from). The
image expressions are rendered here the way Actions renders them (`resolve` in
that file), with the inputs a caller gets: the workflow's declared defaults,
the caller's values on top. The expected images are hardcoded.
"""

import pathlib

import pytest
import yaml

from selftest.test_job_images_off_docker_hub import resolve

REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent
WORKFLOW = REPO_ROOT / ".github" / "workflows" / "coverage-floor.yml"

# What each service's default image resolves to once it is switched on.
PG = "mirror.gcr.io/library/postgres:16"
REDIS = "mirror.gcr.io/library/redis:7"


def _images(caller):
"""Each service's image, as Actions renders it for a caller passing `caller`."""
workflow = yaml.safe_load(WORKFLOW.read_text())
declared = workflow.get("on", workflow.get(True))["workflow_call"]["inputs"]
inputs = {name: spec.get("default") for name, spec in declared.items()}
inputs.update(caller)
services = workflow["jobs"]["measure"]["services"]
return {
name: resolve(service["image"], inputs) for name, service in services.items()
}


@pytest.mark.parametrize(
"caller, expected",
[
# A caller that sets neither input (all but inbox_superpilot) pulls nothing.
({}, {"postgres": "", "redis": ""}),
# An image alone does not start its service.
(
{"postgres_image": "pgvector/pgvector:pg16", "redis_image": "redis:7"},
{"postgres": "", "redis": ""},
),
({"services_postgres": True}, {"postgres": PG, "redis": ""}),
({"services_redis": True}, {"postgres": "", "redis": REDIS}),
# inbox_superpilot's caller (an image with a '/' is pulled as given).
(
{
"services_postgres": True,
"services_redis": True,
"postgres_image": "pgvector/pgvector:pg16",
},
{"postgres": "pgvector/pgvector:pg16", "redis": REDIS},
),
# An empty image input still falls back to the default image.
(
{
"services_postgres": True,
"postgres_image": "",
"services_redis": True,
"redis_image": "",
},
{"postgres": PG, "redis": REDIS},
),
],
)
def test_a_service_starts_only_when_its_input_asks(caller, expected):
assert _images(caller) == expected
33 changes: 27 additions & 6 deletions selftest/test_job_images_off_docker_hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
namespace such as pgvector/pgvector) is pulled exactly as given: GitHub's
expression language cannot split a string to tell the two apart.

Each service also starts only when its services_* input is true: switched off,
its image is '' and nothing is pulled at all (test_coverage_floor_services.py
pins that). So the guard and REQUIRED switch a service on before asking where
its image comes from.

Two layers:

1. The guard (`docker_hub_pulls`) resolves every image a job pulls (each
Expand Down Expand Up @@ -239,13 +244,24 @@ def _declared_inputs(workflow: dict) -> dict:

def _scenarios(template: str, declared: dict):
defaults = {name: spec.get("default") for name, spec in declared.items()}
names = sorted(set(re.findall(r"inputs\.([A-Za-z_][\w-]*)", template, re.I)))
# A boolean input an image reads switches its service on or off. A caller
# that passes an image uses that service, so those scenarios switch it on;
# left off, the image is '' and nothing is pulled.
on = {
name: True
for name in names
if (declared.get(name) or {}).get("type") == "boolean"
}
yield "inputs empty (this repo's own runs)", {}
yield "caller passes nothing (declared defaults)", defaults
for name in sorted(set(re.findall(r"inputs\.([A-Za-z_][\w-]*)", template, re.I))):
if on:
yield f"caller switches on {', '.join(on)}", {**defaults, **on}
for name in names:
if (declared.get(name) or {}).get("type", "string") != "string":
continue
for sample in BARE_SAMPLES:
yield f"caller passes {name}: {sample}", {**defaults, name: sample}
yield f"caller passes {name}: {sample}", {**defaults, **on, name: sample}


def docker_hub_pulls(text: str) -> list:
Expand All @@ -261,11 +277,13 @@ def docker_hub_pulls(text: str) -> list:
return found


# The image slots known to exist, the input that feeds each, and what a caller
# value resolves to (None = `inputs` empty). Hardcoded, never read from the
# The image slots known to exist, the input that switches each service on, the
# input that feeds its image, and what a caller value resolves to once the
# service is on (None = no image input). Hardcoded, never read from the
# workflow: derived expectations cannot fail when the workflow narrows.
REQUIRED = {
("coverage-floor.yml", "measure", "services.postgres"): (
"services_postgres",
"postgres_image",
{
None: MIRROR + "postgres:16",
Expand All @@ -279,6 +297,7 @@ def docker_hub_pulls(text: str) -> list:
},
),
("coverage-floor.yml", "measure", "services.redis"): (
"services_redis",
"redis_image",
{
None: MIRROR + "redis:7",
Expand All @@ -296,15 +315,17 @@ def _check_required(workflow_name: str, text: str) -> None:
(job_id, slot): template
for job_id, slot, template in image_slots(yaml.safe_load(text))
}
for (name, job_id, slot), (input_name, cases) in REQUIRED.items():
for (name, job_id, slot), (switch, input_name, cases) in REQUIRED.items():
if name != workflow_name:
continue
assert (job_id, slot) in slots, (
f"{name}: jobs.{job_id}.{slot} not found; the guard must find the "
"images it is known to check"
)
for value, want in cases.items():
inputs = {} if value is None else {input_name: value}
inputs = (
{switch: True} if value is None else {switch: True, input_name: value}
)
got = resolve(slots[(job_id, slot)], inputs)
assert got == want, (
f"{name} jobs.{job_id}.{slot}: {input_name}={value!r} -> {got!r}, want {want!r}"
Expand Down
Loading