Skip to content
Open
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
42 changes: 37 additions & 5 deletions run_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,36 @@ def run_blurb_release(db: ReleaseShelf) -> None:
)


def check_cpython_repo_branch(db: ReleaseShelf) -> None:
current_branch = subprocess.check_output(
shlex.split("git branch --show-current"), text=True, cwd=db["git_repo"]
).strip()
expected_branch = db["release"].branch
if current_branch != expected_branch:
raise ReleaseException(
f"CPython repository is on {current_branch} branch, "
f"expected {expected_branch}"
)


def check_cpython_repo_age(db: ReleaseShelf) -> None:
# %ct = committer date, UNIX timestamp (for example, "1768300016")
timestamp = subprocess.check_output(
shlex.split('git log -1 --format="%ct"'), text=True, cwd=db["git_repo"]
).strip()
age_seconds = time.time() - int(timestamp.strip())
is_old = age_seconds > 86400 # 1 day

# cr = committer date, relative (for example, "3 days ago")
out = subprocess.check_output(
shlex.split('git log -1 --format="%cr"'), text=True, cwd=db["git_repo"]
)
print(f"Last CPython commit was {out.strip()}")

if is_old and not ask_question("Continue with old repo?"):
raise ReleaseException("CPython repository is old")


def check_cpython_repo_is_clean(db: ReleaseShelf) -> None:
if subprocess.check_output(["git", "status", "--porcelain"], cwd=db["git_repo"]):
raise ReleaseException("Git repository is not clean")
Expand Down Expand Up @@ -1381,23 +1411,25 @@ def _api_key(api_key: str) -> str:
),
Task(check_sigstore_client, "Checking Sigstore CLI"),
Task(check_buildbots, "Check buildbots are good"),
Task(check_cpython_repo_is_clean, "Checking Git repository is clean"),
Task(check_cpython_repo_branch, "Checking CPython repository branch"),
Task(check_cpython_repo_age, "Checking CPython repository age"),
Task(check_cpython_repo_is_clean, "Checking CPython repository is clean"),
*(
[Task(check_magic_number, "Checking the magic number is up-to-date")]
if magic
else []
),
Task(prepare_temporary_branch, "Checking out a temporary release branch"),
Task(run_blurb_release, "Run blurb release"),
Task(check_cpython_repo_is_clean, "Checking Git repository is clean"),
Task(check_cpython_repo_is_clean, "Checking CPython repository is clean"),
Task(prepare_pydoc_topics, "Preparing pydoc topics"),
Task(bump_version, "Bump version"),
Task(bump_version_in_docs, "Bump version in docs"),
Task(check_cpython_repo_is_clean, "Checking Git repository is clean"),
Task(check_cpython_repo_is_clean, "Checking CPython repository is clean"),
Task(run_autoconf, "Running autoconf"),
Task(check_cpython_repo_is_clean, "Checking Git repository is clean"),
Task(check_cpython_repo_is_clean, "Checking CPython repository is clean"),
Task(check_pyspecific, "Checking pyspecific"),
Task(check_cpython_repo_is_clean, "Checking Git repository is clean"),
Task(check_cpython_repo_is_clean, "Checking CPython repository is clean"),
Task(create_tag, "Create tag"),
Task(push_to_local_fork, "Push new tags and branches to private fork"),
Task(start_build_release, "Start the build-release workflow"),
Expand Down
96 changes: 90 additions & 6 deletions tests/test_run_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
import contextlib
import io
import tarfile
from contextlib import nullcontext as does_not_raise
from pathlib import Path
from typing import cast

import pytest

import run_release
from release import ReleaseShelf, Tag
from run_release import ReleaseException


@pytest.mark.parametrize(
Expand All @@ -26,8 +28,7 @@ def test_check_sigstore_version_success(version) -> None:
)
def test_check_sigstore_version_exception(version) -> None:
with pytest.raises(
run_release.ReleaseException,
match="Sigstore version not detected or not valid",
ReleaseException, match="Sigstore version not detected or not valid"
):
run_release.check_sigstore_version(version)

Expand All @@ -46,21 +47,104 @@ def test_extract_github_owner(url: str, expected: str) -> None:

def test_invalid_extract_github_owner() -> None:
with pytest.raises(
run_release.ReleaseException,
ReleaseException,
match="Could not parse GitHub owner from 'origin' remote URL: "
"https://example.com",
):
run_release.extract_github_owner("https://example.com")


@pytest.mark.parametrize(
["release_tag", "git_current_branch", "expectation"],
[
# Success cases
("3.15.0rc1", "3.15\n", does_not_raise()),
("3.15.0b1", "3.15\n", does_not_raise()),
("3.15.0a6", "main\n", does_not_raise()),
("3.14.3", "3.14\n", does_not_raise()),
("3.13.12", "3.13\n", does_not_raise()),
# Failure cases
(
"3.15.0rc1",
"main\n",
pytest.raises(ReleaseException, match="on main branch, expected 3.15"),
),
(
"3.15.0b1",
"main\n",
pytest.raises(ReleaseException, match="on main branch, expected 3.15"),
),
(
"3.15.0a6",
"3.14\n",
pytest.raises(ReleaseException, match="on 3.14 branch, expected main"),
),
(
"3.14.3",
"main\n",
pytest.raises(ReleaseException, match="on main branch, expected 3.14"),
),
],
)
def test_check_cpython_repo_branch(
monkeypatch, release_tag: str, git_current_branch: str, expectation
) -> None:
# Arrange
db = {"release": Tag(release_tag), "git_repo": "/fake/repo"}
monkeypatch.setattr(
run_release.subprocess,
"check_output",
lambda *args, **kwargs: git_current_branch,
)

# Act / Assert
with expectation:
run_release.check_cpython_repo_branch(cast(ReleaseShelf, db))


@pytest.mark.parametrize(
["age_seconds", "user_continues", "expectation"],
[
# Recent repo (< 1 day) - no question asked
(3600, None, does_not_raise()),
# Old repo (> 1 day) + user says yes
(90000, True, does_not_raise()),
# Old repo (> 1 day) + user says no
(90000, False, pytest.raises(ReleaseException, match="repository is old")),
],
)
def test_check_cpython_repo_age(
monkeypatch, age_seconds: int, user_continues: bool | None, expectation
) -> None:
# Arrange
db = {"release": Tag("3.15.0a6"), "git_repo": "/fake/repo"}
current_time = 1700000000
commit_timestamp = current_time - age_seconds

def fake_check_output(cmd, **kwargs):
cmd_str = " ".join(cmd)
if "%ct" in cmd_str:
return f"{commit_timestamp}\n"
if "%cr" in cmd_str:
return "some time ago\n"
return ""

monkeypatch.setattr(run_release.subprocess, "check_output", fake_check_output)
monkeypatch.setattr(run_release.time, "time", lambda: current_time)
if user_continues is not None:
monkeypatch.setattr(run_release, "ask_question", lambda _: user_continues)

# Act / Assert
with expectation:
run_release.check_cpython_repo_age(cast(ReleaseShelf, db))


def test_check_magic_number() -> None:
db = {
"release": Tag("3.14.0rc1"),
"git_repo": str(Path(__file__).parent / "magicdata"),
}
with pytest.raises(
run_release.ReleaseException, match="Magic numbers in .* don't match"
):
with pytest.raises(ReleaseException, match="Magic numbers in .* don't match"):
run_release.check_magic_number(cast(ReleaseShelf, db))


Expand Down
Loading