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
48 changes: 40 additions & 8 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ jobs:
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.12"
- name: Bootstrap patched pip trust
uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust
with:
clients: pip,python
- name: Resolve the public documentation release channel
id: release-channel
env:
Expand All @@ -58,20 +62,48 @@ jobs:
run: |
version=$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
if [ -n "$STABLE_TAG" ]; then
test "$GITHUB_EVENT_NAME" = workflow_dispatch
test "$GITHUB_REF" = refs/heads/main
test "$version" = "$STABLE_TAG"
test "$(gh release view "$STABLE_TAG" --repo "$GITHUB_REPOSITORY" --json isDraft,isPrerelease --jq '(.isDraft == false and .isPrerelease == false)')" = true
test "$(git rev-parse HEAD)" = "$(git rev-parse "refs/tags/$STABLE_TAG^{commit}")"
fi
if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
released=$(gh release view "$STABLE_TAG" --repo "$GITHUB_REPOSITORY" --json isDraft,isPrerelease --jq '(.isDraft == false and .isPrerelease == false)')
test "$released" = true
source_revision=$(git rev-parse HEAD)
tag_revision=$(git rev-parse "refs/tags/$STABLE_TAG^{commit}")
test "$source_revision" = "$tag_revision"
python - "$version" <<'PY'
import json
import re
import sys
import urllib.request

version = sys.argv[1]
with urllib.request.urlopen(
f"https://pypi.org/pypi/durable-workflow/{version}/json", timeout=15,
) as response:
payload = response.read(1_048_577)
if len(payload) > 1_048_576:
raise SystemExit("PyPI metadata exceeds size bound")
package = json.loads(payload)
if package.get("info", {}).get("name") != "durable-workflow" or package["info"].get("version") != version:
raise SystemExit("PyPI package identity differs from selected tag")
kinds = set()
for artifact in package.get("urls", []):
if artifact.get("yanked") is not False:
continue
if not re.fullmatch(r"[0-9a-f]{64}", artifact.get("digests", {}).get("sha256", "")):
raise SystemExit("PyPI artifact digest is missing or invalid")
filename = artifact.get("filename", "")
if not filename.startswith(f"durable_workflow-{version}-") and filename != f"durable_workflow-{version}.tar.gz":
raise SystemExit("PyPI artifact filename differs from selected package")
kinds.add(artifact.get("packagetype"))
if not {"bdist_wheel", "sdist"} <= kinds:
raise SystemExit("Selected stable package has no unyanked wheel and sdist")
PY
echo stable=true >> "$GITHUB_OUTPUT"
else
echo stable=false >> "$GITHUB_OUTPUT"
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Bootstrap patched pip trust
uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust
with:
clients: pip,python
- name: Install package and documentation dependencies
run: pip install -e '.[docs]'
- name: Install Chromium
Expand Down
136 changes: 136 additions & 0 deletions tests/test_docs_release_channel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""Execute the portal's release selector without GitHub or deployment access."""

from __future__ import annotations

import json
import os
import subprocess
import sys
from pathlib import Path
from typing import Any

import pytest
import yaml

pytestmark = pytest.mark.skipif(sys.version_info < (3, 11), reason="The portal selector uses Python 3.12 tomllib")
WORKFLOW = Path(__file__).parents[1] / ".github" / "workflows" / "docs.yml"


def test_reviewed_python_trust_is_ready_before_publication_authority_reads() -> None:
steps = yaml.safe_load(WORKFLOW.read_text())["jobs"]["build"]["steps"]
setup = next(i for i, step in enumerate(steps) if step.get("uses", "").startswith("actions/setup-python@"))
trust = next(i for i, step in enumerate(steps) if step.get("uses", "").startswith(
"durable-workflow/.github/.github/actions/bootstrap-patched-pip@"))
selector = next(i for i, step in enumerate(steps) if step.get("id") == "release-channel")
assert setup < trust < selector
assert steps[trust]["with"]["clients"] == "pip,python"


def select_channel(
root: Path, *, version: str = "2.14.1", tag: str = "", event: str = "workflow_dispatch",
ref: str = "refs/heads/main", published: bool = True, lookup_exit: int = 0,
matching_source: bool = True, registry: dict[str, Any] | None = None,
) -> tuple[subprocess.CompletedProcess[str], dict[str, str], str]:
steps = yaml.safe_load(WORKFLOW.read_text())["jobs"]["build"]["steps"]
validation = next(step["run"] for step in steps if step.get("name") == "Validate the selected stable tag")
selector = next(step["run"] for step in steps if step.get("id") == "release-channel")
tools = root / "tools"
tools.mkdir()
(tools / "python").symlink_to(sys.executable)
github = tools / "gh"
github.write_text(
'#!/bin/sh\nprintf "%s\\n" "$*" >> "$DOCS_TEST_CALLS"\n'
'printf "%s\\n" "$DOCS_TEST_PUBLISHED"\nexit "$DOCS_TEST_EXIT"\n'
)
github.chmod(0o700)
if registry is None:
registry = {"info": {"name": "durable-workflow", "version": "2.14.1"}, "urls": [
{"filename": "durable_workflow-2.14.1-py3-none-any.whl", "packagetype": "bdist_wheel",
"yanked": False, "digests": {"sha256": "1" * 64}},
{"filename": "durable_workflow-2.14.1.tar.gz", "packagetype": "sdist",
"yanked": False, "digests": {"sha256": "2" * 64}},
]}
(tools / "sitecustomize.py").write_text(
'import io, os, urllib.request\n'
'def offline_metadata(url, **kwargs):\n'
' if url != "https://pypi.org/pypi/durable-workflow/2.14.1/json":\n'
' raise RuntimeError("unexpected metadata request")\n'
' return io.BytesIO(os.environ["DOCS_TEST_REGISTRY"].encode())\n'
'urllib.request.urlopen = offline_metadata\n'
)
output, calls = root / "output", root / "calls"
environment = {
"PATH": str(tools) + os.pathsep + os.defpath,
"GIT_CONFIG_GLOBAL": os.devnull, "GIT_CONFIG_NOSYSTEM": "1",
"STABLE_TAG": tag, "GITHUB_EVENT_NAME": event, "GITHUB_REF": ref,
"GITHUB_REPOSITORY": "durable-workflow/sdk-python", "GITHUB_OUTPUT": str(output),
"DOCS_TEST_CALLS": str(calls), "DOCS_TEST_PUBLISHED": str(published).lower(),
"DOCS_TEST_EXIT": str(lookup_exit),
"DOCS_TEST_REGISTRY": json.dumps(registry), "PYTHONPATH": str(tools),
}

def git(*arguments: str) -> None:
subprocess.run(
["git", "-c", "core.hooksPath=/dev/null", "-c", "user.name=Portal test",
"-c", "user.email=portal@example.test", *arguments],
cwd=root, env=environment, check=True, capture_output=True, text=True,
)

(root / "pyproject.toml").write_text(f'[project]\nversion = "{version}"\n')
git("init")
git("add", "pyproject.toml")
git("commit", "-m", "Fixture source")
git("tag", "2.14.1")
if not matching_source:
git("commit", "--allow-empty", "-m", "Unpublished successor")
script = (validation + "\n" if tag else "") + selector
result = subprocess.run(
["bash", "--noprofile", "--norc", "-e", "-o", "pipefail", "-c", script],
cwd=root, env=environment, capture_output=True, text=True,
)
values = dict(line.split("=", 1) for line in output.read_text().splitlines()) if output.exists() else {}
return result, values, calls.read_text() if calls.exists() else ""


@pytest.mark.parametrize("version", ["2.15.0", "2.14.1", "2.16.0.dev1"])
def test_main_source_is_preview_even_with_a_numeric_version(tmp_path: Path, version: str) -> None:
result, values, calls = select_channel(tmp_path, version=version, event="push")
assert result.returncode == 0, result.stderr
assert values == {"stable": "false", "version": version}
assert calls == ""


def test_explicit_published_tag_with_exact_source_can_select_stable(tmp_path: Path) -> None:
result, values, calls = select_channel(tmp_path, tag="2.14.1")
assert result.returncode == 0, result.stderr
assert values == {"stable": "true", "version": "2.14.1"}
assert "release view 2.14.1 --repo durable-workflow/sdk-python" in calls


@pytest.mark.parametrize("change", [
{"published": False}, {"lookup_exit": 1}, {"version": "2.15.0"}, {"matching_source": False},
{"tag": "2.15.0"}, {"tag": "2.14.1-rc.1"}, {"tag": "2.14.1;invalid"},
{"event": "push"}, {"ref": "refs/heads/feature"},
])
def test_unqualified_selection_cannot_emit_stable(tmp_path: Path, change: dict[str, object]) -> None:
result, values, _ = select_channel(tmp_path, **{"tag": "2.14.1", **change}) # type: ignore[arg-type]
assert result.returncode != 0
assert values.get("stable") != "true"


@pytest.mark.parametrize("registry", [
{}, {"info": {"name": "durable-workflow", "version": "2.15.0"}},
{"info": {"name": "other", "version": "2.14.1"}},
{"info": {"name": "durable-workflow", "version": "2.14.1"}, "urls": []},
{"info": {"name": "durable-workflow", "version": "2.14.1"}, "urls": [
{"filename": "durable_workflow-2.14.1.tar.gz", "packagetype": "sdist", "yanked": True}]},
{"info": {"name": "durable-workflow", "version": "2.14.1"}, "urls": [
{"filename": "durable_workflow-2.14.1.tar.gz", "packagetype": "sdist", "yanked": False}]},
{"info": {"name": "durable-workflow", "version": "2.14.1"}, "urls": [
{"filename": "other.tar.gz", "packagetype": "sdist", "yanked": False,
"digests": {"sha256": "2" * 64}}]},
])
def test_registry_identity_or_missing_artifacts_refuse_stable(tmp_path: Path, registry: dict[str, Any]) -> None:
result, values, _ = select_channel(tmp_path, tag="2.14.1", registry=registry)
assert result.returncode != 0
assert values.get("stable") != "true"
Loading