diff --git a/tests/tools/private/release/BUILD.bazel b/tests/tools/private/release/BUILD.bazel index 5ac912a955..317a4626c9 100644 --- a/tests/tools/private/release/BUILD.bazel +++ b/tests/tools/private/release/BUILD.bazel @@ -4,7 +4,10 @@ load("//tests/support/pytest_test:pytest_test.bzl", "pytest_test") py_library( name = "release_test_helper", - srcs = ["release_test_helper.py"], + srcs = [ + "conftest.py", + "release_test_helper.py", + ], target_compatible_with = SUPPORTS_BZLMOD, deps = [ "//tools/private/release:mock_gh", diff --git a/tests/tools/private/release/conftest.py b/tests/tools/private/release/conftest.py new file mode 100644 index 0000000000..f8798c11c7 --- /dev/null +++ b/tests/tools/private/release/conftest.py @@ -0,0 +1,50 @@ +import dataclasses +from unittest.mock import MagicMock + +import pytest + +pytest_plugins = ["tests.tools.private.release.release_test_helper"] + + +@dataclasses.dataclass +class AutoPatchCmdHelpers: + """Dataclass holding mocked command helpers.""" + + run_cmd: MagicMock + run_git: MagicMock + run_gh: MagicMock + + +@pytest.fixture(name="mock_run_cmd") +def fixture_mock_run_cmd(mocker): + """Fixture to patch shell.run_cmd and its imports in git and gh modules.""" + mock = mocker.patch("tools.private.release.shell.run_cmd") + mocker.patch("tools.private.release.git.run_cmd", mock) + mocker.patch("tools.private.release.gh.run_cmd", mock) + return mock + + +@pytest.fixture(name="mock_run_git") +def fixture_mock_run_git(mocker): + """Fixture to patch Git._run_git.""" + return mocker.patch("tools.private.release.git.Git._run_git") + + +@pytest.fixture(name="mock_run_gh") +def fixture_mock_run_gh(mocker): + """Fixture to patch GitHub._run_gh.""" + return mocker.patch("tools.private.release.gh.GitHub._run_gh") + + +@pytest.fixture(name="auto_patch_cmd_helpers", autouse=True) +def fixture_auto_patch_cmd_helpers(mock_run_cmd, mock_run_git, mock_run_gh): + """Automatically patches run_cmd, Git, and GitHub CLI helpers. + + This prevents tests from executing command line tools that could have + side-effects. + """ + return AutoPatchCmdHelpers( + run_cmd=mock_run_cmd, + run_git=mock_run_git, + run_gh=mock_run_gh, + ) diff --git a/tests/tools/private/release/gh_test.py b/tests/tools/private/release/gh_test.py index ccc61e7f21..b8c346004b 100644 --- a/tests/tools/private/release/gh_test.py +++ b/tests/tools/private/release/gh_test.py @@ -1,6 +1,8 @@ import pytest +from tools.private.release import shell from tools.private.release.gh import GitHub +from tools.private.release.git import Git pytest_plugins = ["tests.tools.private.release.release_test_helper"] @@ -59,3 +61,19 @@ def test_resolve_pr_number_invalid(mocker, gh): with pytest.raises(ValueError, match="Could not resolve PR reference"): gh.resolve_pr_number("invalid-ref") mock_run_cmd.assert_not_called() + + +def test_auto_patched_helpers_prevent_real_execution(auto_patch_cmd_helpers): + # Calling run_cmd directly hits the mock + shell.run_cmd("echo", "test") + auto_patch_cmd_helpers.run_cmd.assert_called_with("echo", "test") + + # Git._run_git hits the mock + git = Git(".") + git._run_git("status") + auto_patch_cmd_helpers.run_git.assert_called_with("status") + + # GitHub._run_gh hits the mock + gh_obj = GitHub("foo/bar") + gh_obj._run_gh("issue", "list") + auto_patch_cmd_helpers.run_gh.assert_called_with("issue", "list")