From 6300d0ddc50f219ce39f6c90a84bdd4720fe6fda Mon Sep 17 00:00:00 2001 From: Leynos Date: Sun, 21 Sep 2025 12:30:45 +0100 Subject: [PATCH] Add missing docstrings to release-to-pypi-uv modules --- .../scripts/check_github_release.py | 16 ++ .../scripts/confirm_release.py | 14 ++ .../scripts/determine_release.py | 16 ++ .../scripts/publish_release.py | 7 + .../scripts/validate_toml_versions.py | 17 ++ .../scripts/write_summary.py | 13 ++ .../tests/test_check_github_release.py | 73 +++++++++ .../tests/test_confirm_release.py | 30 ++++ .../tests/test_determine_release.py | 94 +++++++++++ .../tests/test_publish_release.py | 34 ++++ .../tests/test_validate_toml_versions.py | 153 ++++++++++++++++++ .../tests/test_write_summary.py | 32 ++++ 12 files changed, 499 insertions(+) diff --git a/.github/actions/release-to-pypi-uv/scripts/check_github_release.py b/.github/actions/release-to-pypi-uv/scripts/check_github_release.py index 5f3cd963..cb02bf25 100644 --- a/.github/actions/release-to-pypi-uv/scripts/check_github_release.py +++ b/.github/actions/release-to-pypi-uv/scripts/check_github_release.py @@ -99,6 +99,22 @@ def main( token: str = TOKEN_OPTION, repo: str = REPO_OPTION, ) -> None: + """Check that the GitHub release for ``tag`` is published. + + Parameters + ---------- + tag : str + Release tag to validate. + token : str + Token used to authenticate the GitHub API request. + repo : str + Repository slug in ``owner/name`` form where the release should exist. + + Raises + ------ + typer.Exit + Raised when the release is missing or not ready for publication. + """ try: data = _fetch_release(repo, tag, token) name = _validate_release(tag, data) diff --git a/.github/actions/release-to-pypi-uv/scripts/confirm_release.py b/.github/actions/release-to-pypi-uv/scripts/confirm_release.py index 893e0b44..e1450a86 100644 --- a/.github/actions/release-to-pypi-uv/scripts/confirm_release.py +++ b/.github/actions/release-to-pypi-uv/scripts/confirm_release.py @@ -14,6 +14,20 @@ def main(expected: str = EXPECTED_OPTION, confirm: str = CONFIRM_OPTION) -> None: + """Validate that the provided confirmation string matches ``expected``. + + Parameters + ---------- + expected : str + Confirmation phrase that must be entered to proceed. + confirm : str + User-supplied confirmation string collected from workflow input. + + Raises + ------ + typer.Exit + Raised when the supplied confirmation does not match ``expected``. + """ if confirm != expected: typer.echo( f"::error::Confirmation failed. Set the 'confirm' input to: {expected}", diff --git a/.github/actions/release-to-pypi-uv/scripts/determine_release.py b/.github/actions/release-to-pypi-uv/scripts/determine_release.py index 5e6aff1f..39b7e5a9 100644 --- a/.github/actions/release-to-pypi-uv/scripts/determine_release.py +++ b/.github/actions/release-to-pypi-uv/scripts/determine_release.py @@ -24,6 +24,22 @@ def _emit_outputs(dest: Path, tag: str, version: str) -> None: def main(tag: str | None = TAG_OPTION, github_output: Path = GITHUB_OUTPUT_OPTION) -> None: + """Resolve the release tag and write outputs for downstream steps. + + Parameters + ---------- + tag : str | None + Tag supplied via workflow input when the workflow is not running on a + tag reference. + github_output : Path + Path to the ``GITHUB_OUTPUT`` file that receives the resolved values. + + Raises + ------ + typer.Exit + Raised when no tag can be determined or the tag is not SemVer + compliant. + """ ref_type = os.getenv("GITHUB_REF_TYPE", "") ref_name = os.getenv("GITHUB_REF_NAME", "") diff --git a/.github/actions/release-to-pypi-uv/scripts/publish_release.py b/.github/actions/release-to-pypi-uv/scripts/publish_release.py index ffa717a0..799b9e57 100644 --- a/.github/actions/release-to-pypi-uv/scripts/publish_release.py +++ b/.github/actions/release-to-pypi-uv/scripts/publish_release.py @@ -51,6 +51,13 @@ def _extend_sys_path() -> None: def main(index: str = INDEX_OPTION) -> None: + """Publish the built distributions with uv. + + Parameters + ---------- + index : str + Optional package index name or URL to pass to ``uv publish``. + """ if index: typer.echo(f"Publishing with uv to index '{index}'") run_cmd(["uv", "publish", "--index", index]) diff --git a/.github/actions/release-to-pypi-uv/scripts/validate_toml_versions.py b/.github/actions/release-to-pypi-uv/scripts/validate_toml_versions.py index 2eea2ada..0f83bd65 100644 --- a/.github/actions/release-to-pypi-uv/scripts/validate_toml_versions.py +++ b/.github/actions/release-to-pypi-uv/scripts/validate_toml_versions.py @@ -77,6 +77,23 @@ def main( pattern: str = PATTERN_OPTION, fail_on_dynamic: str = FAIL_ON_DYNAMIC_OPTION, ) -> None: + """Confirm that project versions in TOML files match the release version. + + Parameters + ---------- + version : str + Semantic version resolved for the release tag. + pattern : str + Glob pattern used to discover ``pyproject.toml`` files to inspect. + fail_on_dynamic : str + String flag that controls whether dynamic versions should raise an + error. + + Raises + ------ + typer.Exit + Raised when TOML files cannot be read or contain mismatched versions. + """ files = list(_iter_files(pattern)) if not files: typer.echo(f"::warning::No TOML files matched pattern {pattern}") diff --git a/.github/actions/release-to-pypi-uv/scripts/write_summary.py b/.github/actions/release-to-pypi-uv/scripts/write_summary.py index d9a8f801..1840c76c 100644 --- a/.github/actions/release-to-pypi-uv/scripts/write_summary.py +++ b/.github/actions/release-to-pypi-uv/scripts/write_summary.py @@ -23,6 +23,19 @@ def main( environment_name: str = ENV_OPTION, summary_path: Path = SUMMARY_OPTION, ) -> None: + """Append release details to the GitHub step summary file. + + Parameters + ---------- + tag : str + Resolved release tag to report. + index : str + Optional package index identifier provided to the publish step. + environment_name : str + Name of the deployment environment associated with the release. + summary_path : Path + File path to ``GITHUB_STEP_SUMMARY`` that should receive the content. + """ index_label = index or "pypi (default)" heading = "## Release summary\n" lines = [ diff --git a/.github/actions/release-to-pypi-uv/tests/test_check_github_release.py b/.github/actions/release-to-pypi-uv/tests/test_check_github_release.py index fc217b30..4b4b7b3d 100644 --- a/.github/actions/release-to-pypi-uv/tests/test_check_github_release.py +++ b/.github/actions/release-to-pypi-uv/tests/test_check_github_release.py @@ -28,10 +28,28 @@ def read(self) -> bytes: @pytest.fixture(name="module") def fixture_module() -> Any: + """Load the ``check_github_release`` script for testing. + + Returns + ------- + Any + Imported module object exposing the ``main`` entrypoint. + """ return load_script_module("check_github_release") def test_success(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str], module: Any) -> None: + """Confirm that a published release prints a success message. + + Parameters + ---------- + monkeypatch : pytest.MonkeyPatch + Fixture used to replace ``urllib.request.urlopen``. + capsys : pytest.CaptureFixture[str] + Captures standard output and error from the command execution. + module : Any + Script module under test. + """ def fake_urlopen(request: Any, timeout: float = 30) -> DummyResponse: # noqa: ANN401 return DummyResponse({"draft": False, "prerelease": False, "name": "1.2.3"}) @@ -44,6 +62,17 @@ def fake_urlopen(request: Any, timeout: float = 30) -> DummyResponse: # noqa: A def test_draft_release(monkeypatch: pytest.MonkeyPatch, module: Any, capsys: pytest.CaptureFixture[str]) -> None: + """Fail when the release is still marked as a draft. + + Parameters + ---------- + monkeypatch : pytest.MonkeyPatch + Fixture used to replace ``urllib.request.urlopen``. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures emitted error output. + """ def fake_urlopen(request: Any, timeout: float = 30) -> DummyResponse: # noqa: ANN401 return DummyResponse({"draft": True, "prerelease": False, "name": "draft"}) @@ -57,6 +86,17 @@ def fake_urlopen(request: Any, timeout: float = 30) -> DummyResponse: # noqa: A def test_prerelease(monkeypatch: pytest.MonkeyPatch, module: Any, capsys: pytest.CaptureFixture[str]) -> None: + """Fail when the release is published as a prerelease. + + Parameters + ---------- + monkeypatch : pytest.MonkeyPatch + Fixture used to replace ``urllib.request.urlopen``. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures emitted error output. + """ def fake_urlopen(request: Any, timeout: float = 30) -> DummyResponse: # noqa: ANN401 return DummyResponse({"draft": False, "prerelease": True, "name": "pre"}) @@ -70,6 +110,17 @@ def fake_urlopen(request: Any, timeout: float = 30) -> DummyResponse: # noqa: A def test_missing_release(monkeypatch: pytest.MonkeyPatch, module: Any, capsys: pytest.CaptureFixture[str]) -> None: + """Raise an error when the requested release does not exist. + + Parameters + ---------- + monkeypatch : pytest.MonkeyPatch + Fixture used to replace ``urllib.request.urlopen``. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures emitted error output. + """ def fake_urlopen(request: Any, timeout: float = 30) -> Any: # noqa: ANN401 raise module.urllib.error.HTTPError( url=str(request.full_url), @@ -89,6 +140,17 @@ def fake_urlopen(request: Any, timeout: float = 30) -> Any: # noqa: ANN401 def test_permission_denied(monkeypatch: pytest.MonkeyPatch, module: Any, capsys: pytest.CaptureFixture[str]) -> None: + """Surface permission errors from the GitHub API. + + Parameters + ---------- + monkeypatch : pytest.MonkeyPatch + Fixture used to replace ``urllib.request.urlopen``. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures emitted error output. + """ detail = b"forbidden" error = module.urllib.error.HTTPError( url="https://api.github.com", @@ -111,6 +173,17 @@ def raising_urlopen(request: Any, timeout: float = 30) -> Any: # noqa: ANN401 def test_retries_then_success(monkeypatch: pytest.MonkeyPatch, module: Any, capsys: pytest.CaptureFixture[str]) -> None: + """Retry transient failures before succeeding. + + Parameters + ---------- + monkeypatch : pytest.MonkeyPatch + Fixture used to replace network calls and sleep behaviour. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures command output for assertions. + """ attempts: list[int] = [] def fake_urlopen(request: Any, timeout: float = 30) -> DummyResponse: # noqa: ANN401 diff --git a/.github/actions/release-to-pypi-uv/tests/test_confirm_release.py b/.github/actions/release-to-pypi-uv/tests/test_confirm_release.py index e5109a98..a5f49f30 100644 --- a/.github/actions/release-to-pypi-uv/tests/test_confirm_release.py +++ b/.github/actions/release-to-pypi-uv/tests/test_confirm_release.py @@ -14,6 +14,22 @@ def run_confirm(tmp_path: Path, expected: str, confirm: str) -> subprocess.CompletedProcess[str]: + """Execute the confirmation script with the provided values. + + Parameters + ---------- + tmp_path : Path + Temporary directory used as the working directory for the script. + expected : str + Expected confirmation string supplied via environment variable. + confirm : str + Confirmation value provided to the workflow input. + + Returns + ------- + subprocess.CompletedProcess[str] + Result from invoking the script with ``uv run``. + """ env = base_env(tmp_path) env["EXPECTED"] = expected env["INPUT_CONFIRM"] = confirm @@ -31,6 +47,13 @@ def run_confirm(tmp_path: Path, expected: str, confirm: str) -> subprocess.Compl def test_confirmation_success(tmp_path: Path) -> None: + """Accept when the confirmation matches the expected phrase. + + Parameters + ---------- + tmp_path : Path + Temporary directory provided by pytest. + """ result = run_confirm(tmp_path, expected="release v1.2.3", confirm="release v1.2.3") assert result.returncode == 0, result.stderr @@ -38,6 +61,13 @@ def test_confirmation_success(tmp_path: Path) -> None: def test_confirmation_failure(tmp_path: Path) -> None: + """Reject confirmation attempts with mismatched input. + + Parameters + ---------- + tmp_path : Path + Temporary directory provided by pytest. + """ result = run_confirm(tmp_path, expected="release v1.2.3", confirm="nope") assert result.returncode == 1 diff --git a/.github/actions/release-to-pypi-uv/tests/test_determine_release.py b/.github/actions/release-to-pypi-uv/tests/test_determine_release.py index ea006ca7..1bc36668 100644 --- a/.github/actions/release-to-pypi-uv/tests/test_determine_release.py +++ b/.github/actions/release-to-pypi-uv/tests/test_determine_release.py @@ -13,6 +13,20 @@ def run_script(script: Path, *, env: dict[str, str]) -> subprocess.CompletedProcess[str]: + """Execute the determine-release script with the provided environment. + + Parameters + ---------- + script : Path + Path to the script to execute with ``uv run``. + env : dict[str, str] + Environment variables to use when invoking the script. + + Returns + ------- + subprocess.CompletedProcess[str] + Result object capturing stdout, stderr, and the return code. + """ cmd = ["uv", "run", "--script", str(script)] return subprocess.run( # noqa: S603 cmd, @@ -26,6 +40,18 @@ def run_script(script: Path, *, env: dict[str, str]) -> subprocess.CompletedProc def base_env(tmp_path: Path) -> dict[str, str]: + """Construct the base environment used by the release script tests. + + Parameters + ---------- + tmp_path : Path + Temporary working directory provided by pytest. + + Returns + ------- + dict[str, str] + Environment mapping that mimics the workflow runtime configuration. + """ merged = {**os.environ} root = str(Path(__file__).resolve().parents[4]) prev = os.environ.get("PYTHONPATH", "") @@ -37,6 +63,18 @@ def base_env(tmp_path: Path) -> dict[str, str]: def read_outputs(tmp_path: Path) -> dict[str, str]: + """Read ``GITHUB_OUTPUT`` key/value pairs written by the script. + + Parameters + ---------- + tmp_path : Path + Temporary working directory containing the output file. + + Returns + ------- + dict[str, str] + Parsed mapping of output names to their recorded values. + """ out = {} output_file = tmp_path / "out.txt" if not output_file.exists(): @@ -49,6 +87,13 @@ def read_outputs(tmp_path: Path) -> dict[str, str]: def test_resolves_tag_from_ref(tmp_path: Path) -> None: + """Resolve the release tag from the Git reference metadata. + + Parameters + ---------- + tmp_path : Path + Temporary directory provided by pytest. + """ env = base_env(tmp_path) env["GITHUB_REF_TYPE"] = "tag" env["GITHUB_REF_NAME"] = "v1.2.3" @@ -63,6 +108,13 @@ def test_resolves_tag_from_ref(tmp_path: Path) -> None: def test_resolves_tag_from_input(tmp_path: Path) -> None: + """Resolve the release tag from the workflow input when provided. + + Parameters + ---------- + tmp_path : Path + Temporary directory provided by pytest. + """ env = base_env(tmp_path) env["INPUT_TAG"] = "v2.0.0" @@ -76,6 +128,13 @@ def test_resolves_tag_from_input(tmp_path: Path) -> None: def test_rejects_invalid_tag(tmp_path: Path) -> None: + """Reject release tags that do not follow the expected SemVer format. + + Parameters + ---------- + tmp_path : Path + Temporary directory provided by pytest. + """ env = base_env(tmp_path) env["GITHUB_REF_TYPE"] = "tag" env["GITHUB_REF_NAME"] = "release-1.0.0" @@ -88,6 +147,13 @@ def test_rejects_invalid_tag(tmp_path: Path) -> None: def test_errors_when_no_tag_and_not_on_tag_ref(tmp_path: Path) -> None: + """Exit with an error when no release tag can be resolved. + + Parameters + ---------- + tmp_path : Path + Temporary directory provided by pytest. + """ env = base_env(tmp_path) env.pop("GITHUB_REF_TYPE", None) env.pop("GITHUB_REF_NAME", None) @@ -101,6 +167,13 @@ def test_errors_when_no_tag_and_not_on_tag_ref(tmp_path: Path) -> None: def test_errors_when_ref_type_missing(tmp_path: Path) -> None: + """Exit with an error when ``GITHUB_REF_TYPE`` is missing. + + Parameters + ---------- + tmp_path : Path + Temporary directory provided by pytest. + """ env = base_env(tmp_path) env.pop("GITHUB_REF_TYPE", None) env["GITHUB_REF_NAME"] = "v1.2.3" @@ -113,6 +186,13 @@ def test_errors_when_ref_type_missing(tmp_path: Path) -> None: def test_errors_when_ref_name_missing(tmp_path: Path) -> None: + """Exit with an error when ``GITHUB_REF_NAME`` is missing. + + Parameters + ---------- + tmp_path : Path + Temporary directory provided by pytest. + """ env = base_env(tmp_path) env["GITHUB_REF_TYPE"] = "tag" env.pop("GITHUB_REF_NAME", None) @@ -125,6 +205,13 @@ def test_errors_when_ref_name_missing(tmp_path: Path) -> None: def test_errors_when_ref_name_empty(tmp_path: Path) -> None: + """Exit with an error when ``GITHUB_REF_NAME`` is empty. + + Parameters + ---------- + tmp_path : Path + Temporary directory provided by pytest. + """ env = base_env(tmp_path) env["GITHUB_REF_TYPE"] = "tag" env["GITHUB_REF_NAME"] = "" @@ -137,6 +224,13 @@ def test_errors_when_ref_name_empty(tmp_path: Path) -> None: def test_errors_on_malformed_version_tag(tmp_path: Path) -> None: + """Exit with an error when the tag omits components of the version. + + Parameters + ---------- + tmp_path : Path + Temporary directory provided by pytest. + """ env = base_env(tmp_path) env["GITHUB_REF_TYPE"] = "tag" env["GITHUB_REF_NAME"] = "v1.2" diff --git a/.github/actions/release-to-pypi-uv/tests/test_publish_release.py b/.github/actions/release-to-pypi-uv/tests/test_publish_release.py index 80616673..5eb0e1d7 100644 --- a/.github/actions/release-to-pypi-uv/tests/test_publish_release.py +++ b/.github/actions/release-to-pypi-uv/tests/test_publish_release.py @@ -12,6 +12,13 @@ @pytest.fixture(name="publish_module") def fixture_publish_module() -> Any: + """Load the ``publish_release`` script and adjust its import path. + + Returns + ------- + Any + Imported module with ``run_cmd`` exposed for monkeypatching. + """ module = load_script_module("publish_release") # Ensure cmd_utils is importable by mimicking script behaviour if str(REPO_ROOT) not in module.sys.path: # type: ignore[attr-defined] @@ -20,6 +27,15 @@ def fixture_publish_module() -> Any: def test_publish_default_index(monkeypatch: pytest.MonkeyPatch, publish_module: Any) -> None: + """Use the default PyPI index when no custom index is provided. + + Parameters + ---------- + monkeypatch : pytest.MonkeyPatch + Fixture used to replace ``run_cmd`` during the test. + publish_module : Any + Script module under test. + """ calls: list[list[str]] = [] def fake_run_cmd(args: list[str], **_: object) -> None: @@ -33,6 +49,15 @@ def fake_run_cmd(args: list[str], **_: object) -> None: def test_publish_custom_index(monkeypatch: pytest.MonkeyPatch, publish_module: Any) -> None: + """Invoke ``uv publish`` with the provided custom index. + + Parameters + ---------- + monkeypatch : pytest.MonkeyPatch + Fixture used to replace ``run_cmd`` during the test. + publish_module : Any + Script module under test. + """ calls: list[list[str]] = [] def fake_run_cmd(args: list[str], **_: object) -> None: @@ -46,6 +71,15 @@ def fake_run_cmd(args: list[str], **_: object) -> None: def test_publish_run_cmd_error(monkeypatch: pytest.MonkeyPatch, publish_module: Any) -> None: + """Propagate exceptions raised by ``run_cmd``. + + Parameters + ---------- + monkeypatch : pytest.MonkeyPatch + Fixture used to replace ``run_cmd`` during the test. + publish_module : Any + Script module under test. + """ class DummyError(Exception): pass diff --git a/.github/actions/release-to-pypi-uv/tests/test_validate_toml_versions.py b/.github/actions/release-to-pypi-uv/tests/test_validate_toml_versions.py index 0cb64e72..5a2c4c0d 100644 --- a/.github/actions/release-to-pypi-uv/tests/test_validate_toml_versions.py +++ b/.github/actions/release-to-pypi-uv/tests/test_validate_toml_versions.py @@ -11,11 +11,32 @@ @pytest.fixture(name="module") def fixture_module() -> Any: + """Load the ``validate_toml_versions`` script for tests. + + Returns + ------- + Any + Imported module object exposing helper functions and the CLI entrypoint. + """ return load_script_module("validate_toml_versions") @pytest.fixture() def project_root(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: + """Use a temporary directory as the working tree for each test. + + Parameters + ---------- + tmp_path : Path + Temporary directory provided by pytest. + monkeypatch : pytest.MonkeyPatch + Fixture used to update the working directory during the test. + + Returns + ------- + Path + Path to the temporary working tree for the current test. + """ monkeypatch.chdir(tmp_path) return tmp_path @@ -32,6 +53,17 @@ def _invoke_main(module: Any, **kwargs: Any) -> None: def test_passes_when_versions_match(project_root: Path, module: Any, capsys: pytest.CaptureFixture[str]) -> None: + """Pass when project versions match the resolved release version. + + Parameters + ---------- + project_root : Path + Temporary project directory for the test run. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures output from the command execution. + """ _write_pyproject( project_root / "pkg", """ @@ -48,6 +80,17 @@ def test_passes_when_versions_match(project_root: Path, module: Any, capsys: pyt def test_fails_on_mismatch(project_root: Path, module: Any, capsys: pytest.CaptureFixture[str]) -> None: + """Fail when a TOML file contains a mismatched version string. + + Parameters + ---------- + project_root : Path + Temporary project directory for the test run. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures output from the command execution. + """ _write_pyproject( project_root / "pkg", """ @@ -65,6 +108,17 @@ def test_fails_on_mismatch(project_root: Path, module: Any, capsys: pytest.Captu def test_dynamic_version_failure(project_root: Path, module: Any, capsys: pytest.CaptureFixture[str]) -> None: + """Fail when dynamic versions are disallowed and the project uses them. + + Parameters + ---------- + project_root : Path + Temporary project directory for the test run. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures output from the command execution. + """ _write_pyproject( project_root / "pkg", """ @@ -88,6 +142,19 @@ def test_dynamic_version_failure_for_truthy_variants( capsys: pytest.CaptureFixture[str], truthy: str, ) -> None: + """Fail whenever dynamic versions are disallowed with truthy inputs. + + Parameters + ---------- + project_root : Path + Temporary project directory for the test run. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures output from the command execution. + truthy : str + Variant of the ``fail_on_dynamic`` flag expected to trigger a failure. + """ _write_pyproject( project_root / "pkg", """ @@ -105,6 +172,17 @@ def test_dynamic_version_failure_for_truthy_variants( def test_fails_on_parse_error(project_root: Path, module: Any, capsys: pytest.CaptureFixture[str]) -> None: + """Surface parse failures encountered when reading TOML files. + + Parameters + ---------- + project_root : Path + Temporary project directory for the test run. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures output from the command execution. + """ target = project_root / "pkg" target.mkdir() (target / "pyproject.toml").write_text("this is not TOML") @@ -121,6 +199,17 @@ def test_dynamic_version_allowed_when_flag_false( module: Any, capsys: pytest.CaptureFixture[str], ) -> None: + """Allow dynamic versions when the flag explicitly disables failures. + + Parameters + ---------- + project_root : Path + Temporary project directory for the test run. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures output from the command execution. + """ _write_pyproject( project_root / "pkg", """ @@ -143,6 +232,19 @@ def test_dynamic_version_allowed_for_falsey_variants( capsys: pytest.CaptureFixture[str], falsey: str, ) -> None: + """Allow dynamic versions for all supported falsey flag values. + + Parameters + ---------- + project_root : Path + Temporary project directory for the test run. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures output from the command execution. + falsey : str + Representation of ``fail_on_dynamic`` that should be treated as false. + """ _write_pyproject( project_root / "pkg", """ @@ -163,6 +265,17 @@ def test_dynamic_version_allowed_when_flag_unset( module: Any, capsys: pytest.CaptureFixture[str], ) -> None: + """Allow dynamic versions when the flag is omitted entirely. + + Parameters + ---------- + project_root : Path + Temporary project directory for the test run. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures output from the command execution. + """ _write_pyproject( project_root / "pkg", """ @@ -183,6 +296,17 @@ def test_missing_project_section_is_ignored( module: Any, capsys: pytest.CaptureFixture[str], ) -> None: + """Ignore files lacking a ``[project]`` table when validating versions. + + Parameters + ---------- + project_root : Path + Temporary project directory for the test run. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures output from the command execution. + """ _write_pyproject( project_root / "pkg", """ @@ -203,6 +327,17 @@ def test_multiple_toml_files_mixed_validity( module: Any, capsys: pytest.CaptureFixture[str], ) -> None: + """Fail when any discovered TOML file contains a mismatched version. + + Parameters + ---------- + project_root : Path + Temporary project directory for the test run. + module : Any + Script module under test. + capsys : pytest.CaptureFixture[str] + Captures output from the command execution. + """ _write_pyproject( project_root / "pkg_valid", """ @@ -229,9 +364,27 @@ def test_multiple_toml_files_mixed_validity( @pytest.mark.parametrize("value", ["true", "TRUE", "Yes", "1", "on"]) def test_parse_bool_truthy_values(module: Any, value: str) -> None: + """Interpret various truthy strings as ``True`` when parsing flags. + + Parameters + ---------- + module : Any + Script module under test. + value : str + Truthy string representation to parse. + """ assert module._parse_bool(value) is True @pytest.mark.parametrize("value", [None, "", "false", "no", "0", "off", "n"]) def test_parse_bool_falsey_values(module: Any, value: str | None) -> None: + """Interpret falsey strings and ``None`` as ``False`` when parsing flags. + + Parameters + ---------- + module : Any + Script module under test. + value : str | None + Falsey value to parse with ``_parse_bool``. + """ assert module._parse_bool(value) is False diff --git a/.github/actions/release-to-pypi-uv/tests/test_write_summary.py b/.github/actions/release-to-pypi-uv/tests/test_write_summary.py index 04918f9d..985d87fe 100644 --- a/.github/actions/release-to-pypi-uv/tests/test_write_summary.py +++ b/.github/actions/release-to-pypi-uv/tests/test_write_summary.py @@ -12,10 +12,26 @@ @pytest.fixture(name="write_module") def fixture_write_module() -> Any: + """Load the ``write_summary`` script for testing. + + Returns + ------- + Any + Imported module object exposing the ``main`` entrypoint. + """ return load_script_module("write_summary") def test_write_summary_appends_markdown(tmp_path: Path, write_module: Any) -> None: + """Append a new summary when the file is initially empty. + + Parameters + ---------- + tmp_path : Path + Temporary directory containing the summary file. + write_module : Any + Script module under test. + """ summary_path = tmp_path / "summary.md" write_module.main( @@ -32,6 +48,15 @@ def test_write_summary_appends_markdown(tmp_path: Path, write_module: Any) -> No def test_write_summary_handles_existing_content(tmp_path: Path, write_module: Any) -> None: + """Preserve existing summary content while appending new entries. + + Parameters + ---------- + tmp_path : Path + Temporary directory containing the summary file. + write_module : Any + Script module under test. + """ summary_path = tmp_path / "summary.md" summary_path.write_text("Existing\n", encoding="utf-8") @@ -48,6 +73,13 @@ def test_write_summary_handles_existing_content(tmp_path: Path, write_module: An def test_write_summary_raises_on_io_error(write_module: Any) -> None: + """Propagate I/O errors encountered when writing the summary file. + + Parameters + ---------- + write_module : Any + Script module under test. + """ summary_path = Path("/nonexistent/path/summary.md") with pytest.raises(OSError):