| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -26,8 +26,10 @@ jobs: | |||
| 26 | 26 | permissions: | |
| 27 | 27 | contents: write | |
| 28 | 28 | outputs: | |
| 29 | - tag: ${{ steps.select.outputs.tag }} | ||
| 30 | - commit: ${{ steps.select.outputs.commit }} | ||
| 29 | + # PSR writes its own `tag` output even when released=false. Forward | ||
| 30 | + # only outputs we explicitly set after selecting a source commit. | ||
| 31 | + tag: ${{ steps.select.outputs.release_tag }} | ||
| 32 | + commit: ${{ steps.select.outputs.release_commit }} | ||
| 31 | 33 | steps: | |
| 32 | 34 | - uses: actions/checkout@v4 | |
| 33 | 35 | with: | |
@@ -67,13 +69,13 @@ jobs: | |||
| 67 | 69 | # Check the tag against package metadata without importing/building it. | |
| 68 | 70 | git show "$commit:pyproject.toml" > "$RUNNER_TEMP/release-project.toml" | |
| 69 | 71 | RELEASE_TAG="$tag" python -c 'import os, pathlib, tomllib; data = tomllib.loads((pathlib.Path(os.environ["RUNNER_TEMP"]) / "release-project.toml").read_text()); assert os.environ["RELEASE_TAG"] == "v" + data["project"]["version"]' | |
| 70 | - echo "tag=$tag" >> "$GITHUB_OUTPUT" | ||
| 71 | - echo "commit=$commit" >> "$GITHUB_OUTPUT" | ||
| 72 | + echo "release_tag=$tag" >> "$GITHUB_OUTPUT" | ||
| 73 | + echo "release_commit=$commit" >> "$GITHUB_OUTPUT" | ||
| 72 | 74 | ||
| 73 | 75 | distributions: | |
| 74 | 76 | name: Build release distributions | |
| 75 | 77 | needs: release | |
| 76 | - if: needs.release.outputs.tag != '' | ||
| 78 | + if: needs.release.outputs.tag != '' && needs.release.outputs.commit != '' | ||
| 77 | 79 | uses: ./.github/workflows/wheels.yml | |
| 78 | 80 | with: | |
| 79 | 81 | source-ref: ${{ needs.release.outputs.commit }} | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,5 +1,6 @@ | |||
| 1 | 1 | global-exclude *.pyc *.pyo .DS_Store | |
| 2 | 2 | include scripts/cibuildwheel.toml scripts/check-distributions.py scripts/release-assets.py | |
| 3 | + include .github/workflows/release.yml | ||
| 3 | 4 | recursive-exclude src/pythonnative/native .build/* .gradle/* .cxx/* build/* .swiftpm/* .kotlin/* | |
| 4 | 5 | prune src/pythonnative/native/android/build | |
| 5 | 6 | prune src/pythonnative/native/android/.gradle | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -44,6 +44,7 @@ dev = [ | |||
| 44 | 44 | "mypy>=1.10", | |
| 45 | 45 | "pytest>=8.0", | |
| 46 | 46 | "pytest-asyncio>=0.23", | |
| 47 | + "PyYAML>=6.0", | ||
| 47 | 48 | "Pillow>=10.0", | |
| 48 | 49 | # `pn deps` and `pn run ios` shell out to `sys.executable -m pip` to | |
| 49 | 50 | # resolve and vendor the app's [requirements].packages for the device | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,108 @@ | |||
| 1 | + """Exercise the actual release-selection shell step and its job outputs.""" | ||
| 2 | + | ||
| 3 | + import os | ||
| 4 | + import re | ||
| 5 | + import shutil | ||
| 6 | + import subprocess | ||
| 7 | + import sys | ||
| 8 | + from pathlib import Path | ||
| 9 | + from typing import Any | ||
| 10 | + | ||
| 11 | + import pytest | ||
| 12 | + import yaml | ||
| 13 | + | ||
| 14 | + pytestmark = pytest.mark.skipif(os.name == "nt" or not shutil.which("bash"), reason="Release selection runs on Ubuntu") | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + @pytest.fixture | ||
| 18 | + def release_repo(tmp_path: Path) -> Path: | ||
| 19 | + def git(*args: str) -> None: | ||
| 20 | + subprocess.run(["git", *args], cwd=tmp_path, check=True, capture_output=True) | ||
| 21 | + | ||
| 22 | + git("init", "-b", "main") | ||
| 23 | + git("config", "user.name", "Release test") | ||
| 24 | + git("config", "user.email", "release-test@example.invalid") | ||
| 25 | + git("config", "commit.gpgsign", "false") | ||
| 26 | + git("config", "core.hooksPath", "/dev/null") | ||
| 27 | + (tmp_path / "pyproject.toml").write_text('[project]\nversion = "0.40.0"\n') | ||
| 28 | + git("add", "pyproject.toml") | ||
| 29 | + git("commit", "-m", "chore(release): v0.40.0") | ||
| 30 | + git("tag", "v0.40.0") | ||
| 31 | + git("update-ref", "refs/remotes/origin/main", "HEAD") | ||
| 32 | + git("commit", "--allow-empty", "-m", "build(workflows): repair publishing") | ||
| 33 | + | ||
| 34 | + commands = tmp_path / "commands" | ||
| 35 | + commands.mkdir() | ||
| 36 | + (commands / "python").symlink_to(sys.executable) | ||
| 37 | + # Keep the test local: replace only external tools, retaining real Git and | ||
| 38 | + # the exact shell step from release.yml. PSR always appends its outputs, | ||
| 39 | + # including the existing tag when it reports released=false. | ||
| 40 | + stubs = { | ||
| 41 | + "uv": "#!/bin/sh\nexit 0\n", | ||
| 42 | + "gh": "#!/bin/sh\necho false\n", | ||
| 43 | + "semantic-release": f"#!{sys.executable}\n" | ||
| 44 | + + """import os | ||
| 45 | + from pathlib import Path | ||
| 46 | + import subprocess | ||
| 47 | + new_release = os.environ['TEST_RELEASE_MODE'] == 'new' | ||
| 48 | + version = '0.40.1' if new_release else '0.40.0' | ||
| 49 | + if new_release: | ||
| 50 | + Path('pyproject.toml').write_text('[project]\\nversion = "0.40.1"\\n') | ||
| 51 | + subprocess.run(['git', 'commit', '-am', 'chore(release): v0.40.1'], check=True) | ||
| 52 | + subprocess.run(['git', 'tag', 'v0.40.1'], check=True) | ||
| 53 | + with open(os.environ['GITHUB_OUTPUT'], 'a') as output: | ||
| 54 | + output.write(f'released={str(new_release).lower()}\\nversion={version}\\ntag=v{version}\\n') | ||
| 55 | + """, | ||
| 56 | + } | ||
| 57 | + for name, content in stubs.items(): | ||
| 58 | + path = commands / name | ||
| 59 | + path.write_text(content) | ||
| 60 | + path.chmod(0o755) | ||
| 61 | + return tmp_path | ||
| 62 | + | ||
| 63 | + | ||
| 64 | + def run_selection(repo: Path, mode: str, recovery_tag: str = "") -> tuple[dict[str, str], dict[str, str]]: | ||
| 65 | + workflow: dict[str, Any] = yaml.safe_load( | ||
| 66 | + (Path(__file__).resolve().parents[1] / ".github/workflows/release.yml").read_text() | ||
| 67 | + ) | ||
| 68 | + job = workflow["jobs"]["release"] | ||
| 69 | + step = next(step for step in job["steps"] if step.get("id") == "select") | ||
| 70 | + output_file = repo / "outputs" | ||
| 71 | + output_file.touch() | ||
| 72 | + env = { | ||
| 73 | + **os.environ, | ||
| 74 | + "PATH": str(repo / "commands") + os.pathsep + os.environ["PATH"], | ||
| 75 | + "GITHUB_OUTPUT": str(output_file), | ||
| 76 | + "RUNNER_TEMP": str(repo), | ||
| 77 | + "RECOVERY_TAG": recovery_tag, | ||
| 78 | + "TEST_RELEASE_MODE": mode, | ||
| 79 | + } | ||
| 80 | + subprocess.run(["bash", "-e", "-o", "pipefail", "-c", step["run"]], cwd=repo, env=env, check=True) | ||
| 81 | + step_outputs = dict(line.split("=", 1) for line in output_file.read_text().splitlines()) | ||
| 82 | + job_outputs = {} | ||
| 83 | + for name, expression in job["outputs"].items(): | ||
| 84 | + match = re.fullmatch(r"\$\{\{ steps\.select\.outputs\.(\w+) \}\}", expression) | ||
| 85 | + assert match is not None | ||
| 86 | + job_outputs[name] = step_outputs.get(match[1], "") | ||
| 87 | + return step_outputs, job_outputs | ||
| 88 | + | ||
| 89 | + | ||
| 90 | + def test_no_release_does_not_forward_semantic_release_tag(release_repo: Path) -> None: | ||
| 91 | + step_outputs, job_outputs = run_selection(release_repo, "none") | ||
| 92 | + assert step_outputs["released"] == "false" | ||
| 93 | + assert step_outputs["tag"] == "v0.40.0" | ||
| 94 | + assert job_outputs == {"tag": "", "commit": ""} | ||
| 95 | + | ||
| 96 | + | ||
| 97 | + def test_new_release_selects_the_new_tagged_commit(release_repo: Path) -> None: | ||
| 98 | + _, job_outputs = run_selection(release_repo, "new") | ||
| 99 | + commit = subprocess.check_output(["git", "rev-parse", "v0.40.1^{commit}"], cwd=release_repo, text=True).strip() | ||
| 100 | + assert job_outputs == {"tag": "v0.40.1", "commit": commit} | ||
| 101 | + | ||
| 102 | + | ||
| 103 | + def test_recovery_selects_existing_source_instead_of_main(release_repo: Path) -> None: | ||
| 104 | + _, job_outputs = run_selection(release_repo, "none", "v0.40.0") | ||
| 105 | + commit = subprocess.check_output(["git", "rev-parse", "v0.40.0^{commit}"], cwd=release_repo, text=True).strip() | ||
| 106 | + head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=release_repo, text=True).strip() | ||
| 107 | + assert commit != head | ||
| 108 | + assert job_outputs == {"tag": "v0.40.0", "commit": commit} | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments