| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| import hashlib |
| import importlib.util |
| import subprocess |
| import sys |
| import tarfile |
| from argparse import Namespace |
| from pathlib import Path |
| |
| import pytest |
| |
| |
| def _load_release_module(): |
| module_path = Path(__file__).resolve().parent.parent / "scripts" / "apache_release.py" |
| spec = importlib.util.spec_from_file_location("apache_release", module_path) |
| module = importlib.util.module_from_spec(spec) |
| assert spec.loader is not None |
| sys.modules[spec.name] = module |
| spec.loader.exec_module(module) |
| return module |
| |
| |
| release = _load_release_module() |
| |
| |
| def test_build_sdist_uses_artifact_specific_license(monkeypatch, tmp_path): |
| project_files = { |
| "LICENSE": "full source license\nwebsite/src/components/ui/\n", |
| "LICENSE-sdist": "sdist license\nexamples/deep-researcher\n", |
| "pyproject.toml": '[project]\nname = "apache-burr"\n', |
| } |
| output_dir = tmp_path / "artifacts" |
| |
| def fake_check_git_working_tree(): |
| return None |
| |
| def fake_run_command(command, **kwargs): |
| if command[:3] == ["git", "archive", "HEAD"]: |
| archive_path = Path(command[command.index("--output") + 1]) |
| with tarfile.open(archive_path, "w") as archive: |
| for name, contents in project_files.items(): |
| source = tmp_path / name |
| source.write_text(contents, encoding="utf-8") |
| archive.add(source, arcname=name) |
| elif command == ["flit", "build", "--format", "sdist"]: |
| source_tree = Path(kwargs["cwd"]) |
| assert (source_tree / "LICENSE").read_text(encoding="utf-8") == project_files[ |
| "LICENSE-sdist" |
| ] |
| assert not (source_tree / "LICENSE-sdist").exists() |
| |
| built_sdist = source_tree / "dist" / "apache_burr-0.42.0.tar.gz" |
| built_sdist.parent.mkdir() |
| with tarfile.open(built_sdist, "w:gz") as archive: |
| archive.add(source_tree / "LICENSE", arcname="apache_burr-0.42.0/LICENSE") |
| else: |
| pytest.fail(f"Unexpected command: {command}") |
| return subprocess.CompletedProcess(command, 0) |
| |
| monkeypatch.setattr(release, "_check_git_working_tree", fake_check_git_working_tree) |
| monkeypatch.setattr(release, "_source_date_epoch", lambda version, output_dir: None) |
| monkeypatch.setattr(release, "_run_command", fake_run_command) |
| |
| artifact = Path(release._build_sdist_from_git("0.42.0", str(output_dir))) |
| |
| assert artifact == output_dir / "apache-burr-0.42.0-incubating-sdist.tar.gz" |
| with tarfile.open(artifact, "r:gz") as archive: |
| license_text = archive.extractfile("apache_burr-0.42.0/LICENSE").read().decode() |
| assert license_text == project_files["LICENSE-sdist"] |
| assert all(not name.endswith("/LICENSE-sdist") for name in archive.getnames()) |
| |
| |
| def _write_artifact_set(directory: Path, version: str, wheel_name: str = None) -> None: |
| wheel_name = wheel_name or f"apache_burr-{version}-py3-none-any.whl" |
| artifact_names = [ |
| f"apache-burr-{version}-incubating-src.tar.gz", |
| f"apache-burr-{version}-incubating-sdist.tar.gz", |
| wheel_name, |
| ] |
| for artifact_name in artifact_names: |
| artifact_path = directory / artifact_name |
| artifact_path.write_bytes(b"artifact") |
| artifact_path.with_name(f"{artifact_name}.asc").write_text("sig", encoding="utf-8") |
| artifact_path.with_name(f"{artifact_name}.sha512").write_text("sha", encoding="utf-8") |
| |
| |
| def test_parse_rc_label_accepts_supported_formats(): |
| assert release._parse_rc_label("0.42.0-RC1") == ("0.42.0", "1") |
| assert release._parse_rc_label("0.42.0-incubating-RC1") == ("0.42.0", "1") |
| |
| |
| def test_parse_rc_label_rejects_invalid_format(): |
| with pytest.raises(SystemExit): |
| release._parse_rc_label("0.42.0") |
| |
| |
| def test_validate_promotion_artifacts_requires_expected_set(tmp_path): |
| _write_artifact_set(tmp_path, "0.42.0") |
| |
| artifacts = release._validate_promotion_artifacts(str(tmp_path), "0.42.0") |
| |
| assert len(artifacts) == 9 |
| assert any(path.endswith("apache-burr-0.42.0-incubating-src.tar.gz") for path in artifacts) |
| assert any(path.endswith("apache-burr-0.42.0-incubating-sdist.tar.gz") for path in artifacts) |
| assert any(path.endswith("apache_burr-0.42.0-py3-none-any.whl") for path in artifacts) |
| |
| |
| def test_validate_promotion_artifacts_fails_when_companion_missing(tmp_path): |
| _write_artifact_set(tmp_path, "0.42.0") |
| (tmp_path / "apache-burr-0.42.0-incubating-src.tar.gz.asc").unlink() |
| |
| with pytest.raises(SystemExit): |
| release._validate_promotion_artifacts(str(tmp_path), "0.42.0") |
| |
| |
| def test_promotion_target_url_appends_version_subdir(): |
| assert ( |
| release._promotion_target_url( |
| "0.42.0", "https://dist.apache.org/repos/dist/release/incubator/burr" |
| ) |
| == "https://dist.apache.org/repos/dist/release/incubator/burr/0.42.0" |
| ) |
| |
| |
| def test_twine_upload_command_includes_only_sdist_and_wheel(): |
| command = release._twine_upload_command( |
| [ |
| "apache-burr-0.42.0-incubating-src.tar.gz", |
| "apache-burr-0.42.0-incubating-src.tar.gz.asc", |
| "apache-burr-0.42.0-incubating-sdist.tar.gz", |
| "apache_burr-0.42.0-py3-none-any.whl", |
| ] |
| ) |
| |
| assert command == ( |
| "twine upload apache-burr-0.42.0-incubating-sdist.tar.gz " |
| "apache_burr-0.42.0-py3-none-any.whl" |
| ) |
| |
| |
| def test_cmd_promote_rejects_already_promoted_release(monkeypatch): |
| monkeypatch.setattr(release, "_verify_project_root", lambda: None) |
| monkeypatch.setattr(release, "_svn_target_exists", lambda url: True) |
| |
| args = Namespace( |
| rc_label="0.42.0-RC1", |
| apache_id="hari", |
| dry_run=False, |
| dev_svn_root="https://dist.apache.org/repos/dist/dev/incubator/burr", |
| release_svn_root="https://dist.apache.org/repos/dist/release/incubator/burr", |
| ) |
| |
| with pytest.raises(SystemExit): |
| release.cmd_promote(args) |
| |
| |
| def test_cmd_promote_dry_run_uses_server_copy_without_committing(monkeypatch, tmp_path): |
| calls = {"checkout": [], "promote": None} |
| |
| class _TempDir: |
| def __enter__(self): |
| return str(tmp_path) |
| |
| def __exit__(self, exc_type, exc, tb): |
| return False |
| |
| def fake_checkout(url: str, checkout_dir: str): |
| calls["checkout"].append((url, checkout_dir)) |
| Path(checkout_dir).mkdir(parents=True, exist_ok=True) |
| |
| def fake_validate(rc_checkout_dir: str, version: str): |
| assert version == "0.42.0" |
| return [ |
| f"{rc_checkout_dir}/apache-burr-0.42.0-incubating-sdist.tar.gz", |
| f"{rc_checkout_dir}/apache_burr-0.42.0-py3-none-any.whl", |
| ] |
| |
| def fake_promote(source_url, target_url, message, apache_id, dry_run=False): |
| calls["promote"] = (source_url, target_url, message, apache_id, dry_run) |
| return True |
| |
| monkeypatch.setattr(release, "_verify_project_root", lambda: None) |
| monkeypatch.setattr(release.tempfile, "TemporaryDirectory", lambda prefix=None: _TempDir()) |
| monkeypatch.setattr(release, "_svn_target_exists", lambda url: False) |
| monkeypatch.setattr(release, "_svn_checkout", fake_checkout) |
| monkeypatch.setattr(release, "_validate_promotion_artifacts", fake_validate) |
| monkeypatch.setattr(release, "_promote_with_server_copy", fake_promote) |
| |
| args = Namespace( |
| rc_label="0.42.0-RC1", |
| apache_id="hari", |
| dry_run=True, |
| dev_svn_root="https://dist.apache.org/repos/dist/dev/incubator/burr", |
| release_svn_root="https://dist.apache.org/repos/dist/release/incubator/burr", |
| ) |
| |
| assert release.cmd_promote(args) is True |
| # only the RC is checked out; the release tree is never downloaded |
| assert len(calls["checkout"]) == 1 |
| |
| assert calls["checkout"][0][0].endswith("/0.42.0-incubating-RC1") |
| source_url, target_url, message, apache_id, dry_run = calls["promote"] |
| assert source_url.endswith("/0.42.0-incubating-RC1") |
| assert target_url == "https://dist.apache.org/repos/dist/release/incubator/burr/0.42.0" |
| assert apache_id == "hari" |
| assert dry_run is True |
| |
| |
| def test_verify_parser_accepts_skip_signing(): |
| parser = release._build_parser() |
| args = parser.parse_args(["verify", "0.42.0", "0", "--skip-signing"]) |
| assert args.skip_signing is True |
| |
| |
| def test_cmd_verify_skip_signing_succeeds_without_asc_files(tmp_path): |
| version = "0.42.0" |
| content = b"fake artifact content" |
| sha = hashlib.sha512(content).hexdigest() |
| |
| artifact_name = f"apache-burr-{version}-incubating-src.tar.gz" |
| (tmp_path / artifact_name).write_bytes(content) |
| (tmp_path / f"{artifact_name}.sha512").write_text(f"{sha} {artifact_name}\n") |
| # No .asc file — simulates a --skip-signing build |
| |
| args = Namespace(version=version, rc_num="0", artifacts_dir=str(tmp_path), skip_signing=True) |
| assert release.cmd_verify(args) is True |