GRM-37: refactor: Split CI scripts, fix release PYTHONPATH, dynamic runner discovery
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Unit tests for scripts/classify_changes.py."""
|
||||
"""Unit tests for scripts/ci/classify_changes.py."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@@ -6,12 +6,13 @@ import click
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from scripts.classify_changes import (
|
||||
from scripts.ci.classify_changes import (
|
||||
classify_changes,
|
||||
get_changed_files,
|
||||
get_latest_tag,
|
||||
has_user_facing_changes,
|
||||
is_user_facing,
|
||||
is_workflow_only,
|
||||
main,
|
||||
run_git,
|
||||
)
|
||||
@@ -30,8 +31,22 @@ class TestIsUserFacing:
|
||||
def test_workflow_is_not_user_facing(self) -> None:
|
||||
assert is_user_facing(".gitea/workflows/ci.yml") is False
|
||||
|
||||
def test_scripts_are_not_user_facing(self) -> None:
|
||||
assert is_user_facing("scripts/release.py") is False
|
||||
def test_ci_scripts_are_not_user_facing(self) -> None:
|
||||
assert is_user_facing("scripts/ci/release.py") is False
|
||||
|
||||
def test_dev_scripts_are_user_facing(self) -> None:
|
||||
"""Dev scripts (check_test_speed, configure_repo) are NOT in the
|
||||
workflow-only allowlist, so they default to user-facing."""
|
||||
assert is_user_facing("scripts/check_test_speed.py") is True
|
||||
assert is_user_facing("scripts/configure_repo.py") is True
|
||||
assert is_user_facing("scripts/install_checkmake.py") is True
|
||||
|
||||
def test_shell_scripts_are_not_user_facing(self) -> None:
|
||||
assert is_user_facing("scripts/setup.sh") is False
|
||||
assert is_user_facing("scripts/molecule_all.sh") is False
|
||||
|
||||
def test_scripts_init_is_not_user_facing(self) -> None:
|
||||
assert is_user_facing("scripts/__init__.py") is False
|
||||
|
||||
def test_docs_are_not_user_facing(self) -> None:
|
||||
assert is_user_facing("docs/user/getting-started.md") is False
|
||||
@@ -45,6 +60,16 @@ class TestIsUserFacing:
|
||||
def test_makefile_is_not_user_facing(self) -> None:
|
||||
assert is_user_facing("Makefile") is False
|
||||
|
||||
def test_unknown_file_defaults_to_user_facing(self) -> None:
|
||||
"""Safe default: unknown files are user-facing (require release)."""
|
||||
assert is_user_facing("some/new/file.type") is True
|
||||
assert is_user_facing("new_root_file.txt") is True
|
||||
|
||||
def test_is_workflow_only_inverse(self) -> None:
|
||||
assert is_workflow_only(".gitea/workflows/ci.yml") is True
|
||||
assert is_workflow_only("src/gitea_runner_manager/cli.py") is False
|
||||
assert is_workflow_only("pyproject.toml") is False
|
||||
|
||||
|
||||
class TestClassifyChanges:
|
||||
def test_all_user_facing(self) -> None:
|
||||
@@ -78,13 +103,13 @@ class TestClassifyChanges:
|
||||
|
||||
|
||||
class TestGetChangedFiles:
|
||||
@patch("scripts.classify_changes.run_git")
|
||||
@patch("scripts.ci.classify_changes.run_git")
|
||||
def test_returns_file_list(self, mock_run_git: MagicMock) -> None:
|
||||
mock_run_git.return_value = "file1.py\nfile2.py\nfile3.md"
|
||||
result = get_changed_files("v0.1.0", "HEAD")
|
||||
assert result == ["file1.py", "file2.py", "file3.md"]
|
||||
|
||||
@patch("scripts.classify_changes.run_git")
|
||||
@patch("scripts.ci.classify_changes.run_git")
|
||||
def test_empty_when_no_changes(self, mock_run_git: MagicMock) -> None:
|
||||
mock_run_git.return_value = ""
|
||||
result = get_changed_files("v0.1.0", "HEAD")
|
||||
@@ -92,17 +117,17 @@ class TestGetChangedFiles:
|
||||
|
||||
|
||||
class TestHasUserFacingChanges:
|
||||
@patch("scripts.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
def test_true_when_user_facing(self, mock_get: MagicMock) -> None:
|
||||
mock_get.return_value = ["src/gitea_runner_manager/cli.py", "docs/index.md"]
|
||||
assert has_user_facing_changes("v0.1.0", "HEAD") is True
|
||||
|
||||
@patch("scripts.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
def test_false_when_workflow_only(self, mock_get: MagicMock) -> None:
|
||||
mock_get.return_value = [".gitea/workflows/ci.yml", "docs/index.md"]
|
||||
assert has_user_facing_changes("v0.1.0", "HEAD") is False
|
||||
|
||||
@patch("scripts.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
def test_false_when_no_changes(self, mock_get: MagicMock) -> None:
|
||||
mock_get.return_value = []
|
||||
assert has_user_facing_changes("v0.1.0", "HEAD") is False
|
||||
@@ -121,13 +146,13 @@ class TestGetLatestTag:
|
||||
|
||||
|
||||
class TestRunGit:
|
||||
@patch("scripts.classify_changes.subprocess.run")
|
||||
@patch("scripts.ci.classify_changes.subprocess.run")
|
||||
def test_success(self, mock_run: MagicMock) -> None:
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout="file1.py\n", stderr="")
|
||||
result = run_git(["git", "diff", "--name-only", "v0.1.0", "HEAD"])
|
||||
assert result == "file1.py"
|
||||
|
||||
@patch("scripts.classify_changes.subprocess.run")
|
||||
@patch("scripts.ci.classify_changes.subprocess.run")
|
||||
def test_failure_raises(self, mock_run: MagicMock) -> None:
|
||||
mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="git error")
|
||||
with pytest.raises(click.ClickException):
|
||||
@@ -135,23 +160,23 @@ class TestRunGit:
|
||||
|
||||
|
||||
class TestMain:
|
||||
@patch("scripts.classify_changes.get_latest_tag", return_value="")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="")
|
||||
def test_no_tags_outputs_true(self, mock_tag: MagicMock) -> None:
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--quiet"])
|
||||
assert result.exit_code == 0
|
||||
assert "true" in result.output
|
||||
|
||||
@patch("scripts.classify_changes.get_changed_files", return_value=[])
|
||||
@patch("scripts.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
@patch("scripts.ci.classify_changes.get_changed_files", return_value=[])
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_no_changes_outputs_false(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["--quiet"])
|
||||
assert result.exit_code == 0
|
||||
assert "false" in result.output
|
||||
|
||||
@patch("scripts.classify_changes.get_changed_files")
|
||||
@patch("scripts.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_workflow_only_exits_2(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
mock_changes.return_value = [".gitea/workflows/ci.yml", "docs/index.md"]
|
||||
runner = CliRunner()
|
||||
@@ -159,8 +184,8 @@ class TestMain:
|
||||
assert result.exit_code == 2
|
||||
assert "no release needed" in result.output
|
||||
|
||||
@patch("scripts.classify_changes.get_changed_files")
|
||||
@patch("scripts.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_user_facing_exits_0(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
mock_changes.return_value = ["src/gitea_runner_manager/cli.py", "docs/index.md"]
|
||||
runner = CliRunner()
|
||||
@@ -168,7 +193,7 @@ class TestMain:
|
||||
assert result.exit_code == 0
|
||||
assert "release needed" in result.output
|
||||
|
||||
@patch("scripts.classify_changes.get_latest_tag", return_value="")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="")
|
||||
def test_no_tags_non_quiet(self, mock_tag: MagicMock) -> None:
|
||||
"""Non-quiet mode with no tags prints user-facing message."""
|
||||
runner = CliRunner()
|
||||
@@ -176,8 +201,8 @@ class TestMain:
|
||||
assert result.exit_code == 0
|
||||
assert "No tags found" in result.output
|
||||
|
||||
@patch("scripts.classify_changes.get_changed_files", return_value=[])
|
||||
@patch("scripts.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
@patch("scripts.ci.classify_changes.get_changed_files", return_value=[])
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_no_changes_non_quiet(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
"""Non-quiet mode with no changes prints message."""
|
||||
runner = CliRunner()
|
||||
@@ -185,8 +210,8 @@ class TestMain:
|
||||
assert result.exit_code == 0
|
||||
assert "No changes" in result.output
|
||||
|
||||
@patch("scripts.classify_changes.get_changed_files")
|
||||
@patch("scripts.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_quiet_user_facing(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
"""Quiet mode with user-facing changes outputs true."""
|
||||
mock_changes.return_value = ["src/gitea_runner_manager/cli.py"]
|
||||
@@ -195,8 +220,8 @@ class TestMain:
|
||||
assert result.exit_code == 0
|
||||
assert "true" in result.output
|
||||
|
||||
@patch("scripts.classify_changes.get_changed_files")
|
||||
@patch("scripts.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_quiet_workflow_only(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
"""Quiet mode with workflow-only changes outputs false."""
|
||||
mock_changes.return_value = [".gitea/workflows/ci.yml"]
|
||||
@@ -205,8 +230,8 @@ class TestMain:
|
||||
assert result.exit_code == 0
|
||||
assert "false" in result.output
|
||||
|
||||
@patch("scripts.classify_changes.get_changed_files")
|
||||
@patch("scripts.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
@patch("scripts.ci.classify_changes.get_changed_files")
|
||||
@patch("scripts.ci.classify_changes.get_latest_tag", return_value="v0.3.0")
|
||||
def test_with_explicit_base(self, mock_tag: MagicMock, mock_changes: MagicMock) -> None:
|
||||
"""Explicit --base overrides latest tag."""
|
||||
mock_changes.return_value = ["src/gitea_runner_manager/cli.py"]
|
||||
|
||||
Reference in New Issue
Block a user