diff --git a/.github/workflows/push.yaml b/.github/workflows/push.yaml index 8605abf3c..3ae5c698b 100644 --- a/.github/workflows/push.yaml +++ b/.github/workflows/push.yaml @@ -84,6 +84,7 @@ jobs: FOLDER: docs/_build/html # The folder the action should deploy. Publish: runs-on: ubuntu-latest + needs: Test if: | (github.repository == 'PolicyEngine/policyengine-core') && (github.event.head_commit.message == 'Update PolicyEngine Core') diff --git a/changelog.d/fix-publish-needs-test.fixed.md b/changelog.d/fix-publish-needs-test.fixed.md new file mode 100644 index 000000000..e37fd7989 --- /dev/null +++ b/changelog.d/fix-publish-needs-test.fixed.md @@ -0,0 +1 @@ +The release workflow now publishes to PyPI only after the Test job passes, instead of running Publish beside Test. diff --git a/tests/core/test_release_tagging.py b/tests/core/test_release_tagging.py index 64b88f3f3..8ebc66c59 100644 --- a/tests/core/test_release_tagging.py +++ b/tests/core/test_release_tagging.py @@ -1,9 +1,11 @@ +import re import shutil import subprocess import sys from pathlib import Path import pytest +import yaml ROOT = Path(__file__).resolve().parents[2] @@ -154,3 +156,32 @@ def test_publish_workflow_tags_only_after_pypi_succeeds(): assert "fetch-depth: 0" in publish_job assert publish_job.index(tag_step) > publish_job.index(publish_step) assert "publish-git-tag.sh || true" not in publish_job + + +def test_publish_workflow_publishes_only_after_tests_pass(): + # Without ``needs: Test``, Publish ran beside Test, so a release could + # reach PyPI while its tests were still running or after they failed. + jobs = yaml.safe_load(PUSH_WORKFLOW.read_text())["jobs"] + publishing = [ + name + for name, job in jobs.items() + if any( + "gh-action-pypi-publish" in step.get("uses", "") + for step in job.get("steps", []) + ) + ] + + assert publishing == ["Publish"] + needs = jobs["Publish"].get("needs", []) + assert "Test" in ([needs] if isinstance(needs, str) else needs) + # A job that needs Test is skipped whenever Test is, so both must run on + # the same commits for releases to happen at all. + assert jobs["Publish"]["if"] == jobs["Test"]["if"] + # A status function such as always() would let Publish run after a + # failed Test; without one, Publish runs only when Test succeeds. + # (Expression function names are matched case-insensitively.) + assert not re.search( + r"\b(always|failure|cancelled|success)\s*\(", + jobs["Publish"]["if"], + re.IGNORECASE, + )