diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18c1833..6c1c833 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -306,7 +306,10 @@ jobs: - name: Install # Every extra: a runnable guide block that exports to OpenTelemetry or opens the # gateway's client needs the extra installed, and a block that skipped would be a - # sample nobody ran. + # sample nobody ran. `postgres` too, for a different reason: the readiness block records + # what `pytest --collect-only` finds, and 76 tests are collected only when psycopg is + # importable. `docs.txt` therefore locks the same extras `ci.txt` does, and + # `test_the_docs_lock_covers_the_suite_the_check_job_runs` holds the two together. run: | pip install --require-hashes -r ctrlrun/requirements/docs.txt pip install --no-deps --no-build-isolation -e ./ctrlrun diff --git a/requirements/docs.txt b/requirements/docs.txt index 1933ea7..5d43ee2 100644 --- a/requirements/docs.txt +++ b/requirements/docs.txt @@ -1,5 +1,5 @@ # This file was autogenerated by uv via the following command: -# uv pip compile --universal --generate-hashes --python-version 3.11 --output-file requirements/docs.txt pyproject.toml --extra dev --extra gateway --extra otel --extra identity requirements/in/docs.in requirements/in/backend.in +# uv pip compile --universal --generate-hashes --python-version 3.11 --output-file requirements/docs.txt pyproject.toml --extra dev --extra gateway --extra otel --extra identity --extra postgres requirements/in/docs.in requirements/in/backend.in anyio==4.15.1 \ --hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101 \ --hash=sha256:9f28306018cbd6d329e64a36d58256edff76dd996fe423bc957326e578b82a94 @@ -713,6 +713,10 @@ protobuf==7.36.1 \ # via # googleapis-common-protos # opentelemetry-proto +psycopg==3.3.5 \ + --hash=sha256:ce5aa5cdb4f9379f00f487590e5890bfa7df9a164648c969ffa628505e21af4e \ + --hash=sha256:d0a3d9ccf5788af054cbd745278cb02401b5c312aeaafbf2c6144460aec47da4 + # via ctrlrun (pyproject.toml) pycparser==3.0 ; implementation_name != 'PyPy' and platform_python_implementation != 'PyPy' \ --hash=sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29 \ --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 @@ -854,6 +858,11 @@ typing-extensions==4.16.0 \ # opentelemetry-exporter-otlp-proto-http # opentelemetry-sdk # opentelemetry-semantic-conventions + # psycopg +tzdata==2026.4 ; sys_platform == 'win32' \ + --hash=sha256:c2169a8b0a7a5e9674da5a135ccdfb2b3e671b333ed9fed17b41f73c34476e81 \ + --hash=sha256:f1b8bd365d8d210c55353f4d7f8d6d8561c0ba50d704b700d195a9424bba0d79 + # via psycopg urllib3==2.7.0 \ --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \ --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897 diff --git a/scripts/lock.sh b/scripts/lock.sh index f4cc365..fe68d5a 100755 --- a/scripts/lock.sh +++ b/scripts/lock.sh @@ -23,12 +23,17 @@ compile() { } extras="--extra dev --extra gateway --extra otel --extra identity" +# `ci.txt` and `docs.txt` both take `--extra postgres`, and for different reasons. The `check` +# job needs psycopg to run the Postgres tests; the `docs` job needs it to *collect* them, because +# the readiness block records what `pytest --collect-only` finds and 76 tests exist only when +# psycopg is importable. A docs lock with fewer extras counts a smaller suite than the one that +# ran and fails the audit against a number that was right. # shellcheck disable=SC2086 compile requirements/ci.txt pyproject.toml $extras --extra postgres requirements/in/backend.in # shellcheck disable=SC2086 compile requirements/adapters.txt pyproject.toml $extras requirements/in/adapters.in requirements/in/backend.in # shellcheck disable=SC2086 -compile requirements/docs.txt pyproject.toml $extras requirements/in/docs.in requirements/in/backend.in +compile requirements/docs.txt pyproject.toml $extras --extra postgres requirements/in/docs.in requirements/in/backend.in compile requirements/fuzz.txt pyproject.toml requirements/in/backend.in compile requirements/build.txt requirements/in/build.in requirements/in/backend.in compile requirements/atheris.txt requirements/in/atheris.in requirements/in/backend.in diff --git a/tests/test_repository_signals.py b/tests/test_repository_signals.py index 052f514..665973a 100644 --- a/tests/test_repository_signals.py +++ b/tests/test_repository_signals.py @@ -192,6 +192,33 @@ def test_codeql_does_not_gate_a_merge(): assert "Nothing here gates a merge" in workflow +def test_the_docs_lock_covers_the_suite_the_check_job_runs(): + """The readiness block records what `pytest --collect-only` finds, and the Postgres tests are + collected only when psycopg is importable. A `docs` job installed from a lock with fewer + extras than the `check` job's counts a smaller suite than the one that ran, and fails the + audit against a number that was right. + + Asserted over the **locks**, not over a `pip install -e .[...]` string in the workflow: since + the pinned-install change both jobs install from `requirements/*.txt` by hash, and the extras + live in `scripts/lock.sh`. A test reading the workflow would now read nothing. + """ + root = Path(__file__).resolve().parents[1] + + def distributions(lock: str) -> set[str]: + text = (root / "requirements" / lock).read_text(encoding="utf-8") + return { + line.split("==")[0].strip().lower() + for line in text.splitlines() + if line and not line.startswith((" ", "#", "-")) + } + + missing = distributions("ci.txt") - distributions("docs.txt") + assert missing == set(), ( + f"the docs job installs from a lock missing {sorted(missing)}, so it collects a smaller " + "suite than the check job runs" + ) + + # --- community files -----------------------------------------------------------------------