GRM-51: fix: release pipeline determinism with lock, rebase, and consistent classification
This commit is contained in:
@@ -252,3 +252,63 @@ class TestMain:
|
||||
result = runner.invoke(main, ["--base", "v0.2.0", "--head", "HEAD"])
|
||||
assert result.exit_code == 0
|
||||
assert "release needed" in result.output
|
||||
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_check_ansible_true(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
"""--check ansible with Ansible changes outputs true."""
|
||||
mock_changes.return_value = ["ansible/tasks/main.yml", ".gitea/workflows/ci.yml"]
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--check", "ansible", "--quiet"])
|
||||
assert result.exit_code == 0
|
||||
assert "true" in result.output
|
||||
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_check_ansible_false(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
"""--check ansible with no Ansible changes outputs false."""
|
||||
mock_changes.return_value = ["src/gitea_runner_manager/cli.py", ".gitea/workflows/ci.yml"]
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--check", "ansible", "--quiet"])
|
||||
assert result.exit_code == 0
|
||||
assert "false" in result.output
|
||||
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_check_user_facing_true(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
"""--check user-facing with user-facing changes outputs true."""
|
||||
mock_changes.return_value = ["src/gitea_runner_manager/cli.py", ".gitea/workflows/ci.yml"]
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--check", "user-facing", "--quiet"])
|
||||
assert result.exit_code == 0
|
||||
assert "true" in result.output
|
||||
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_check_user_facing_false(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
"""--check user-facing with only workflow changes outputs false."""
|
||||
mock_changes.return_value = [".gitea/workflows/ci.yml", "tests/test_foo.py"]
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--check", "user-facing", "--quiet"])
|
||||
assert result.exit_code == 0
|
||||
assert "false" in result.output
|
||||
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_check_ansible_non_quiet(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
"""--check ansible in non-quiet mode prints file list."""
|
||||
mock_changes.return_value = ["ansible/tasks/main.yml"]
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--check", "ansible"])
|
||||
assert result.exit_code == 0
|
||||
assert "Ansible changes detected" in result.output
|
||||
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_check_user_facing_non_quiet(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
"""--check user-facing in non-quiet mode prints file list."""
|
||||
mock_changes.return_value = ["src/gitea_runner_manager/cli.py"]
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--check", "user-facing"])
|
||||
assert result.exit_code == 0
|
||||
assert "User-facing changes detected" in result.output
|
||||
|
||||
+31
-19
@@ -99,37 +99,29 @@ class TestGetChangelog:
|
||||
|
||||
class TestHasUnreleasedChanges:
|
||||
@patch("scripts.ci.release.get_latest_tag")
|
||||
def test_with_bumped_version_no_changes(self, mock_latest: MagicMock) -> None:
|
||||
mock_latest.return_value = "v0.2.0"
|
||||
assert has_unreleased_changes(bumped_version="0.2.0") is False
|
||||
|
||||
@patch("scripts.ci.release.get_latest_tag")
|
||||
def test_with_bumped_version_has_changes(self, mock_latest: MagicMock) -> None:
|
||||
mock_latest.return_value = "v0.2.0"
|
||||
assert has_unreleased_changes(bumped_version="0.3.0") is True
|
||||
|
||||
@patch("scripts.ci.release.get_latest_tag")
|
||||
def test_with_bumped_version_no_tags(self, mock_latest: MagicMock) -> None:
|
||||
def test_no_tags_has_changes(self, mock_latest: MagicMock) -> None:
|
||||
mock_latest.return_value = ""
|
||||
assert has_unreleased_changes(bumped_version="0.1.0") is True
|
||||
assert has_unreleased_changes() is True
|
||||
|
||||
@patch("scripts.ci.release.get_latest_tag")
|
||||
@patch("scripts.ci.release.run_cmd")
|
||||
def test_without_bumped_version_no_changes(self, mock_run_cmd: MagicMock, mock_latest: MagicMock) -> None:
|
||||
mock_run_cmd.return_value = MagicMock(returncode=0, stdout="v0.2.0\n")
|
||||
def test_no_commits_since_tag(self, mock_run_cmd: MagicMock, mock_latest: MagicMock) -> None:
|
||||
mock_run_cmd.return_value = MagicMock(returncode=0, stdout="")
|
||||
mock_latest.return_value = "v0.2.0"
|
||||
assert has_unreleased_changes() is False
|
||||
|
||||
@patch("scripts.ci.release.get_latest_tag")
|
||||
@patch("scripts.ci.release.run_cmd")
|
||||
def test_without_bumped_version_has_changes(self, mock_run_cmd: MagicMock, mock_latest: MagicMock) -> None:
|
||||
mock_run_cmd.return_value = MagicMock(returncode=0, stdout="v0.3.0\n")
|
||||
def test_commits_since_tag(self, mock_run_cmd: MagicMock, mock_latest: MagicMock) -> None:
|
||||
mock_run_cmd.return_value = MagicMock(returncode=0, stdout="abc123 fix: bug\n")
|
||||
mock_latest.return_value = "v0.2.0"
|
||||
assert has_unreleased_changes() is True
|
||||
|
||||
@patch("scripts.ci.release.get_latest_tag")
|
||||
@patch("scripts.ci.release.run_cmd")
|
||||
def test_cliff_fails_returns_false(self, mock_run_cmd: MagicMock) -> None:
|
||||
def test_git_log_fails_returns_false(self, mock_run_cmd: MagicMock, mock_latest: MagicMock) -> None:
|
||||
mock_run_cmd.return_value = MagicMock(returncode=1, stdout="")
|
||||
mock_latest.return_value = "v0.2.0"
|
||||
assert has_unreleased_changes() is False
|
||||
|
||||
|
||||
@@ -313,6 +305,22 @@ class TestMain:
|
||||
assert result.exit_code == 0
|
||||
assert "Dry-run mode" in result.output
|
||||
|
||||
@patch.dict("os.environ", {})
|
||||
@patch("scripts.ci.release.has_user_facing_changes", return_value=True)
|
||||
@patch("scripts.ci.release.run_cmd")
|
||||
def test_release_lock_skips_when_head_is_release_commit(self, mock_run_cmd: MagicMock, mock_uf: MagicMock) -> None:
|
||||
"""If HEAD is already a release commit, should skip to prevent duplicate releases."""
|
||||
# First call: git rev-parse (master), second: git log -1 (release commit)
|
||||
mock_run_cmd.side_effect = [
|
||||
MagicMock(returncode=0, stdout="master\n", stderr=""),
|
||||
MagicMock(returncode=0, stdout="release: v0.5.0\n", stderr=""),
|
||||
]
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, [])
|
||||
assert result.exit_code == 0
|
||||
assert "already a release commit" in result.output
|
||||
assert "Skipping" in result.output
|
||||
|
||||
@patch.dict("os.environ", {})
|
||||
@patch("scripts.ci.release.has_user_facing_changes", return_value=True)
|
||||
@patch("scripts.ci.release.has_unreleased_changes", return_value=False)
|
||||
@@ -543,10 +551,11 @@ class TestMain:
|
||||
mock_user: MagicMock,
|
||||
) -> None:
|
||||
"""If tests fail, release aborts — no commit, no tag."""
|
||||
# First call: git rev-parse (master), then make lint-ruff (success),
|
||||
# then make pytest-cov (failure)
|
||||
# Calls: git rev-parse (master), git log -1 (release lock check),
|
||||
# make lint-ruff (success), make pytest-cov (failure)
|
||||
mock_run_cmd.side_effect = [
|
||||
MagicMock(returncode=0, stdout="master\n", stderr=""),
|
||||
MagicMock(returncode=0, stdout="GRM-50 fix: something\n", stderr=""),
|
||||
MagicMock(returncode=0, stdout="", stderr=""),
|
||||
MagicMock(returncode=1, stdout="", stderr="test failure"),
|
||||
]
|
||||
@@ -582,8 +591,11 @@ class TestMain:
|
||||
mock_user: MagicMock,
|
||||
) -> None:
|
||||
"""If lint fails, release aborts — no commit, no tag."""
|
||||
# Calls: git rev-parse (master), git log -1 (release lock check),
|
||||
# make lint-ruff (failure)
|
||||
mock_run_cmd.side_effect = [
|
||||
MagicMock(returncode=0, stdout="master\n", stderr=""),
|
||||
MagicMock(returncode=0, stdout="GRM-50 fix: something\n", stderr=""),
|
||||
MagicMock(returncode=1, stdout="", stderr="lint error"),
|
||||
]
|
||||
runner = CliRunner()
|
||||
|
||||
Reference in New Issue
Block a user