blob: a2172b3ceca6b1ddc77f6915d121b295065bd21b [file] [log] [blame]
# 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.
from __future__ import annotations
import pytest
class FakeDirEntry:
def __init__(self, name: str, *, is_dir: bool):
self.name = name
self._is_dir = is_dir
def is_dir(self) -> bool:
return self._is_dir
@pytest.fixture
def rc_cmd():
"""Lazy import the rc command module."""
import airflow_breeze.commands.release_candidate_command as module
return module
def test_remove_old_releases_only_collects_rc_directories(monkeypatch, rc_cmd):
version = "2.10.0rc3"
task_sdk_version = "1.0.6rc3"
repo_root = "/repo/root"
# Arrange: entries include current RC, old RC directories, a matching "file", and non-RC directory.
entries = [
FakeDirEntry(version, is_dir=True), # current RC: should be skipped
FakeDirEntry("2.10.0rc2", is_dir=True), # old RC dir: should be included
FakeDirEntry("2.10.0rc1", is_dir=True), # old RC dir: should be included
FakeDirEntry("2.10.0rc0", is_dir=False), # matches pattern but not a directory: excluded
FakeDirEntry("not-a-rc", is_dir=True), # directory but not matching pattern: excluded
]
chdir_calls: list[str] = []
console_messages: list[str] = []
run_command_calls: list[list[str]] = []
def fake_confirm_action(prompt: str, **_kwargs) -> bool:
# First prompt decides whether we scan. We want to.
if prompt == "Do you want to look for old RCs to remove?":
return True
# For each candidate, we decline removal to avoid running svn commands.
if prompt.startswith("Remove old RC ") or prompt.startswith("Remove old Task SDK RC "):
return False
raise AssertionError(f"Unexpected confirm prompt: {prompt}")
def fake_path_exists(path: str) -> bool:
# Task SDK path doesn't exist in this test
return False
monkeypatch.setattr(rc_cmd.os, "chdir", lambda path: chdir_calls.append(path))
monkeypatch.setattr(rc_cmd.os, "scandir", lambda: iter(entries))
monkeypatch.setattr(rc_cmd.os.path, "exists", fake_path_exists)
monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": console_messages.append(str(msg)))
monkeypatch.setattr(rc_cmd, "run_command", lambda cmd, **_kwargs: run_command_calls.append(cmd))
# Act
rc_cmd.remove_old_releases(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root)
# Assert: only directory entries matching RC_PATTERN, excluding current version, and sorted.
assert f"{repo_root}/asf-dist/dev/airflow" in chdir_calls
assert repo_root in chdir_calls
assert (
"The following old Airflow releases should be removed: ['2.10.0rc1', '2.10.0rc2']" in console_messages
)
assert run_command_calls == []
def test_remove_old_releases_returns_early_when_user_declines(monkeypatch, rc_cmd):
version = "2.10.0rc3"
task_sdk_version = "1.0.6rc3"
repo_root = "/repo/root"
confirm_prompts: list[str] = []
def fake_confirm_action(prompt: str, **_kwargs) -> bool:
confirm_prompts.append(prompt)
return False
def should_not_be_called(*_args, **_kwargs):
raise AssertionError("This should not have been called when user declines the initial prompt.")
monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
monkeypatch.setattr(rc_cmd.os, "chdir", should_not_be_called)
monkeypatch.setattr(rc_cmd.os, "scandir", should_not_be_called)
monkeypatch.setattr(rc_cmd, "console_print", should_not_be_called)
monkeypatch.setattr(rc_cmd, "run_command", should_not_be_called)
rc_cmd.remove_old_releases(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root)
assert confirm_prompts == ["Do you want to look for old RCs to remove?"]
def test_remove_old_releases_removes_confirmed_old_releases(monkeypatch, rc_cmd):
version = "3.1.5rc3"
task_sdk_version = "1.0.6rc3"
repo_root = "/repo/root"
# Unsorted on purpose to verify sorting before prompting/removing.
entries = [
FakeDirEntry("3.1.5rc2", is_dir=True),
FakeDirEntry(version, is_dir=True),
FakeDirEntry("3.1.0rc1", is_dir=True),
]
chdir_calls: list[str] = []
console_messages: list[str] = []
run_command_calls: list[tuple[list[str], dict]] = []
confirm_prompts: list[str] = []
def fake_confirm_action(prompt: str, **_kwargs) -> bool:
confirm_prompts.append(prompt)
if prompt == "Do you want to look for old RCs to remove?":
return True
if prompt == "Remove old RC 3.1.0rc1?":
return True
if prompt == "Remove old RC 3.1.5rc2?":
return False
raise AssertionError(f"Unexpected confirm prompt: {prompt}")
def fake_path_exists(path: str) -> bool:
# Task SDK path doesn't exist in this test
return False
monkeypatch.setattr(rc_cmd.os, "chdir", lambda path: chdir_calls.append(path))
monkeypatch.setattr(rc_cmd.os, "scandir", lambda: iter(entries))
monkeypatch.setattr(rc_cmd.os.path, "exists", fake_path_exists)
monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": console_messages.append(str(msg)))
def fake_run_command(cmd: list[str], **kwargs):
run_command_calls.append((cmd, kwargs))
monkeypatch.setattr(rc_cmd, "run_command", fake_run_command)
rc_cmd.remove_old_releases(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root)
assert chdir_calls == [f"{repo_root}/asf-dist/dev/airflow", repo_root]
assert confirm_prompts == [
"Do you want to look for old RCs to remove?",
"Remove old RC 3.1.0rc1?",
"Remove old RC 3.1.5rc2?",
]
assert (
"The following old Airflow releases should be removed: ['3.1.0rc1', '3.1.5rc2']" in console_messages
)
assert "Removing old Airflow release 3.1.0rc1" in console_messages
assert "Removing old Airflow release 3.1.5rc2" in console_messages
assert "[success]Old releases removed" in console_messages
# Only rc1 was confirmed, so we should run rm+commit for rc1 only.
assert run_command_calls == [
(["svn", "rm", "3.1.0rc1"], {"check": True}),
(["svn", "commit", "-m", "Remove old release: 3.1.0rc1"], {"check": True}),
]
def test_remove_old_releases_removes_task_sdk_releases(monkeypatch, rc_cmd):
version = "3.1.5rc3"
task_sdk_version = "1.0.6rc3"
repo_root = "/repo/root"
# Airflow entries
airflow_entries = [
FakeDirEntry(version, is_dir=True),
FakeDirEntry("3.1.5rc2", is_dir=True),
]
# Task SDK entries
task_sdk_entries = [
FakeDirEntry(task_sdk_version, is_dir=True), # current RC: should be skipped
FakeDirEntry("1.0.6rc2", is_dir=True), # old RC dir: should be included
FakeDirEntry("1.0.6rc1", is_dir=True), # old RC dir: should be included
]
chdir_calls: list[str] = []
console_messages: list[str] = []
run_command_calls: list[tuple[list[str], dict]] = []
confirm_prompts: list[str] = []
scandir_call_count = 0
def fake_confirm_action(prompt: str, **_kwargs) -> bool:
confirm_prompts.append(prompt)
if prompt == "Do you want to look for old RCs to remove?":
return True
# Decline all removals to avoid running svn commands
if prompt.startswith("Remove old RC ") or prompt.startswith("Remove old Task SDK RC "):
return False
raise AssertionError(f"Unexpected confirm prompt: {prompt}")
def fake_path_exists(path: str) -> bool:
# Task SDK path exists in this test
return path == f"{repo_root}/asf-dist/dev/airflow/task-sdk"
def fake_scandir():
nonlocal scandir_call_count
scandir_call_count += 1
# First call is for Airflow, second is for Task SDK
if scandir_call_count == 1:
return iter(airflow_entries)
if scandir_call_count == 2:
return iter(task_sdk_entries)
raise AssertionError("Unexpected scandir call")
monkeypatch.setattr(rc_cmd.os, "chdir", lambda path: chdir_calls.append(path))
monkeypatch.setattr(rc_cmd.os, "scandir", fake_scandir)
monkeypatch.setattr(rc_cmd.os.path, "exists", fake_path_exists)
monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": console_messages.append(str(msg)))
monkeypatch.setattr(rc_cmd, "run_command", lambda cmd, **_kwargs: run_command_calls.append((cmd, {})))
rc_cmd.remove_old_releases(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root)
assert f"{repo_root}/asf-dist/dev/airflow" in chdir_calls
assert f"{repo_root}/asf-dist/dev/airflow/task-sdk" in chdir_calls
assert repo_root in chdir_calls
assert "The following old Airflow releases should be removed: ['3.1.5rc2']" in console_messages
assert (
"The following old Task SDK releases should be removed: ['1.0.6rc1', '1.0.6rc2']" in console_messages
)
assert "[success]Old releases removed" in console_messages
# No removals were confirmed, so no svn commands should be run
assert run_command_calls == []
def test_remove_old_releases_removes_both_airflow_and_task_sdk_releases(monkeypatch, rc_cmd):
version = "3.1.5rc3"
task_sdk_version = "1.0.6rc3"
repo_root = "/repo/root"
# Airflow entries
airflow_entries = [
FakeDirEntry(version, is_dir=True),
FakeDirEntry("3.1.5rc2", is_dir=True),
]
# Task SDK entries
task_sdk_entries = [
FakeDirEntry(task_sdk_version, is_dir=True),
FakeDirEntry("1.0.6rc2", is_dir=True),
FakeDirEntry("1.0.6rc1", is_dir=True),
]
chdir_calls: list[str] = []
console_messages: list[str] = []
run_command_calls: list[tuple[list[str], dict]] = []
confirm_prompts: list[str] = []
scandir_call_count = 0
def fake_confirm_action(prompt: str, **_kwargs) -> bool:
confirm_prompts.append(prompt)
if prompt == "Do you want to look for old RCs to remove?":
return True
# Confirm removal of one Airflow and one Task SDK release
if prompt == "Remove old RC 3.1.5rc2?":
return True
if prompt == "Remove old Task SDK RC 1.0.6rc1?":
return True
# Decline others
if prompt.startswith("Remove old RC ") or prompt.startswith("Remove old Task SDK RC "):
return False
raise AssertionError(f"Unexpected confirm prompt: {prompt}")
def fake_path_exists(path: str) -> bool:
return path == f"{repo_root}/asf-dist/dev/airflow/task-sdk"
def fake_scandir():
nonlocal scandir_call_count
scandir_call_count += 1
if scandir_call_count == 1:
return iter(airflow_entries)
if scandir_call_count == 2:
return iter(task_sdk_entries)
raise AssertionError("Unexpected scandir call")
monkeypatch.setattr(rc_cmd.os, "chdir", lambda path: chdir_calls.append(path))
monkeypatch.setattr(rc_cmd.os, "scandir", fake_scandir)
monkeypatch.setattr(rc_cmd.os.path, "exists", fake_path_exists)
monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": console_messages.append(str(msg)))
def fake_run_command(cmd: list[str], **kwargs):
run_command_calls.append((cmd, kwargs))
monkeypatch.setattr(rc_cmd, "run_command", fake_run_command)
rc_cmd.remove_old_releases(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root)
assert chdir_calls == [
f"{repo_root}/asf-dist/dev/airflow",
f"{repo_root}/asf-dist/dev/airflow/task-sdk",
repo_root,
]
assert "The following old Airflow releases should be removed: ['3.1.5rc2']" in console_messages
assert (
"The following old Task SDK releases should be removed: ['1.0.6rc1', '1.0.6rc2']" in console_messages
)
assert "Removing old Airflow release 3.1.5rc2" in console_messages
assert "Removing old Task SDK release 1.0.6rc1" in console_messages
assert "Removing old Task SDK release 1.0.6rc2" in console_messages
assert "[success]Old releases removed" in console_messages
# Both Airflow and Task SDK removals were confirmed
assert run_command_calls == [
(["svn", "rm", "3.1.5rc2"], {"check": True}),
(["svn", "commit", "-m", "Remove old release: 3.1.5rc2"], {"check": True}),
(["svn", "rm", "1.0.6rc1"], {"check": True}),
(["svn", "commit", "-m", "Remove old Task SDK release: 1.0.6rc1"], {"check": True}),
]
def test_move_artifacts_to_svn_returns_early_when_user_declines(monkeypatch, rc_cmd):
"""Test that function returns early when user declines initial prompt."""
version = "2.10.0rc3"
version_without_rc = "2.10.0"
task_sdk_version = "1.0.6rc3"
task_sdk_version_without_rc = "1.0.6"
repo_root = "/repo/root"
confirm_prompts: list[str] = []
def fake_confirm_action(prompt: str, **kwargs):
confirm_prompts.append(prompt)
return False
def should_not_be_called(*_args, **_kwargs):
raise AssertionError("This should not have been called when user declines the initial prompt.")
monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
monkeypatch.setattr(rc_cmd.os, "chdir", should_not_be_called)
monkeypatch.setattr(rc_cmd, "console_print", should_not_be_called)
monkeypatch.setattr(rc_cmd, "run_command", should_not_be_called)
rc_cmd.move_artifacts_to_svn(
version=version,
version_without_rc=version_without_rc,
task_sdk_version=task_sdk_version,
task_sdk_version_without_rc=task_sdk_version_without_rc,
repo_root=repo_root,
)
assert confirm_prompts == ["Do you want to move artifacts to SVN?"]
def test_move_artifacts_to_svn_completes_successfully(monkeypatch, rc_cmd):
"""Test that function completes successfully when user confirms."""
version = "2.10.0rc3"
version_without_rc = "2.10.0"
task_sdk_version = "1.0.6rc3"
task_sdk_version_without_rc = "1.0.6"
repo_root = "/repo/root"
chdir_calls: list[str] = []
console_messages: list[str] = []
run_command_calls: list[tuple[list[str] | str, dict]] = []
confirm_prompts: list[str] = []
def fake_confirm_action(prompt: str, **kwargs):
confirm_prompts.append(prompt)
return True
def fake_chdir(path: str):
chdir_calls.append(path)
def fake_run_command(cmd: list[str] | str, **kwargs):
run_command_calls.append((cmd, kwargs))
monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
monkeypatch.setattr(rc_cmd.os, "chdir", fake_chdir)
monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": console_messages.append(str(msg)))
monkeypatch.setattr(rc_cmd, "run_command", fake_run_command)
rc_cmd.move_artifacts_to_svn(
version=version,
version_without_rc=version_without_rc,
task_sdk_version=task_sdk_version,
task_sdk_version_without_rc=task_sdk_version_without_rc,
repo_root=repo_root,
)
assert confirm_prompts == ["Do you want to move artifacts to SVN?"]
assert chdir_calls == [f"{repo_root}/asf-dist/dev/airflow"]
# Verify svn mkdir for airflow version
assert any(
cmd == ["svn", "mkdir", version] and kwargs.get("check") is True for cmd, kwargs in run_command_calls
)
# Verify mv command for airflow artifacts
assert any(
cmd == f"mv {repo_root}/dist/*{version_without_rc}* {version}/"
and kwargs.get("check") is True
and kwargs.get("shell") is True
for cmd, kwargs in run_command_calls
)
# Verify svn mkdir for task-sdk version
assert any(cmd == ["svn", "mkdir", f"task-sdk/{task_sdk_version}"] for cmd, kwargs in run_command_calls)
# Verify mv command for task-sdk artifacts
assert any(
cmd == f"mv {repo_root}/dist/*{task_sdk_version_without_rc}* task-sdk/{task_sdk_version}/"
and kwargs.get("check") is True
and kwargs.get("shell") is True
for cmd, kwargs in run_command_calls
)
assert "[success]Moved artifacts to SVN:" in console_messages
# Verify ls commands
assert any(cmd == [f"ls {version}/"] for cmd, kwargs in run_command_calls)
assert any(cmd == [f"ls task-sdk/{task_sdk_version}/"] for cmd, kwargs in run_command_calls)
def test_push_artifacts_to_asf_repo_returns_early_when_user_declines(monkeypatch, rc_cmd):
"""Test that function returns early when user declines initial prompt."""
version = "2.10.0rc3"
task_sdk_version = "1.0.6rc3"
repo_root = "/repo/root"
confirm_prompts: list[str] = []
def fake_confirm_action(prompt: str, **kwargs):
confirm_prompts.append(prompt)
if kwargs.get("abort") and not prompt.startswith("Do you want to push"):
# Simulate abort behavior
import sys
sys.exit(1)
return False
def should_not_be_called(*_args, **_kwargs):
raise AssertionError("This should not have been called when user declines the initial prompt.")
monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
monkeypatch.setattr(rc_cmd, "get_dry_run", lambda: False)
monkeypatch.setattr(rc_cmd.os, "chdir", should_not_be_called)
monkeypatch.setattr(rc_cmd, "console_print", should_not_be_called)
monkeypatch.setattr(rc_cmd, "run_command", should_not_be_called)
rc_cmd.push_artifacts_to_asf_repo(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root)
assert confirm_prompts == ["Do you want to push artifacts to ASF repo?"]
def test_push_artifacts_to_asf_repo_completes_successfully(monkeypatch, rc_cmd):
"""Test that function completes successfully when user confirms all prompts."""
version = "2.10.0rc3"
task_sdk_version = "1.0.6rc3"
repo_root = "/repo/root"
chdir_calls: list[str] = []
console_messages: list[str] = []
run_command_calls: list[tuple[list[str] | str, dict]] = []
confirm_prompts: list[str] = []
def fake_confirm_action(prompt: str, **kwargs):
confirm_prompts.append(prompt)
return True
def fake_chdir(path: str):
chdir_calls.append(path)
def fake_run_command(cmd: list[str] | str, **kwargs):
run_command_calls.append((cmd, kwargs))
monkeypatch.setattr(rc_cmd, "confirm_action", fake_confirm_action)
monkeypatch.setattr(rc_cmd, "get_dry_run", lambda: False)
monkeypatch.setattr(rc_cmd.os, "chdir", fake_chdir)
monkeypatch.setattr(rc_cmd, "console_print", lambda msg="": console_messages.append(str(msg)))
monkeypatch.setattr(rc_cmd, "run_command", fake_run_command)
rc_cmd.push_artifacts_to_asf_repo(version=version, task_sdk_version=task_sdk_version, repo_root=repo_root)
assert confirm_prompts == [
"Do you want to push artifacts to ASF repo?",
"Do you want to continue?",
"Do you want to continue?",
]
assert chdir_calls == [f"{repo_root}/asf-dist/dev/airflow"]
ls_calls = [(cmd, kwargs) for cmd, kwargs in run_command_calls if cmd == ["ls"]]
assert len(ls_calls) == 2 # Two ls calls
assert any(kwargs.get("cwd") == f"{repo_root}/asf-dist/dev/airflow/{version}" for cmd, kwargs in ls_calls)
assert any(
kwargs.get("cwd") == f"{repo_root}/asf-dist/dev/airflow/task-sdk/{task_sdk_version}"
for cmd, kwargs in ls_calls
)
assert any(
cmd == f"svn add {version}/* task-sdk/{task_sdk_version}/*" for cmd, kwargs in run_command_calls
)
assert any(
cmd == ["svn", "commit", "-m", f"Add artifacts for Airflow {version} and Task SDK {task_sdk_version}"]
for cmd, kwargs in run_command_calls
)
assert "[success]Files pushed to svn" in console_messages
assert (
"Verify that the files are available here: https://dist.apache.org/repos/dist/dev/airflow/"
in console_messages
)