diff --git a/CHANGES b/CHANGES index 7e72123..bfd51c8 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,15 @@ $ pipx install --suffix=@next g --pip-args '\--pre' --force +### Fixes + +- Fix `g` when running outside of a VCS directory (#24) + +### Tests + +- Use declarative, typed `NamedTuple`-style for `test_command_line` fixtures + (#24) + ### Development - poetry: 1.8.1 -> 1.8.2 @@ -49,14 +58,17 @@ _Maintenance only, no bug fixes, or new features_ --exec 'poetry run ruff check --select ALL . --fix --unsafe-fixes --preview --show-fixes; poetry run ruff format .; git add src tests; git commit --amend --no-edit' \ origin/master ``` + - poetry: 1.7.1 -> 1.8.1 See also: https://github.com/python-poetry/poetry/blob/1.8.1/CHANGELOG.md + - ruff 0.2.2 -> 0.3.0 (#22) Related formattings. Update CI to use `ruff check .` instead of `ruff .`. See also: https://github.com/astral-sh/ruff/blob/v0.3.0/CHANGELOG.md + - Strengthen linting (#21) - Add flake8-commas (COM) diff --git a/src/g/__init__.py b/src/g/__init__.py index 0091582..7386812 100755 --- a/src/g/__init__.py +++ b/src/g/__init__.py @@ -1,6 +1,8 @@ #!/usr/bin/env python """Package for g.""" +import io +import logging import os import pathlib import subprocess @@ -12,8 +14,11 @@ vcspath_registry = {".git": "git", ".svn": "svn", ".hg": "hg"} +log = logging.getLogger(__name__) + def find_repo_type(path: t.Union[pathlib.Path, str]) -> t.Optional[str]: + """Detect repo type looking upwards.""" for _path in [*list(pathlib.Path(path).parents), pathlib.Path(path)]: for p in _path.iterdir(): if p.is_dir() and p.name in vcspath_registry: @@ -45,6 +50,13 @@ def run( if cmd_args is DEFAULT: cmd_args = sys.argv[1:] + logging.basicConfig(level=logging.INFO, format="%(message)s") + + if cmd is None: + msg = "No VCS found in current directory." + log.info(msg) + return None + assert isinstance(cmd_args, (tuple, list)) assert isinstance(cmd, (str, bytes, pathlib.Path)) diff --git a/tests/test_cli.py b/tests/test_cli.py index 97f2b7f..262f15a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,5 +1,7 @@ """Tests for g's CLI package.""" +import enum +import pathlib import subprocess import typing as t from unittest.mock import patch @@ -20,6 +22,13 @@ def get_output( return exc.output +class EnvFlag(enum.Enum): + """Environmental conditions to simulate in test case.""" + + Git = "Git" # Inside a git directory (like this repo) + Empty = "Empty" # Empty directory (e.g. `tmp_path`) + + @pytest.mark.parametrize( ("argv_args", "expect_cmd"), [ @@ -27,18 +36,81 @@ def get_output( (["g", "--help"], "git --help"), ], ) +class CommandLineTestFixture(t.NamedTuple): + """Test fixture for CLI params, environment, and expected result.""" + + # pytest internal + test_id: str + + # env data + env: EnvFlag + + # test data + argv_args: t.List[str] + + # results + expect_cmd: t.Optional[str] + + +TEST_FIXTURES: t.List[CommandLineTestFixture] = [ + CommandLineTestFixture( + test_id="g-cmd-inside-git-dir", + env=EnvFlag.Git, + argv_args=["g"], + expect_cmd="git", + ), + CommandLineTestFixture( + test_id="g-cmd-help-inside-git-dir", + env=EnvFlag.Git, + argv_args=["g --help"], + expect_cmd="git --help", + ), + CommandLineTestFixture( + test_id="g-cmd-inside-empty-dir", + env=EnvFlag.Empty, + argv_args=["g"], + expect_cmd=None, + ), + CommandLineTestFixture( + test_id="g-cmd-help-inside-empty-dir", + env=EnvFlag.Empty, + argv_args=["g --help"], + expect_cmd=None, + ), +] + + +@pytest.mark.parametrize( + list(CommandLineTestFixture._fields), + TEST_FIXTURES, + ids=[f.test_id for f in TEST_FIXTURES], +) def test_command_line( # capsys: pytest.CaptureFixture[str], + test_id: str, + env: EnvFlag, argv_args: t.List[str], - expect_cmd: str, + expect_cmd: t.Optional[str], + monkeypatch: pytest.MonkeyPatch, + tmp_path: pathlib.Path, ) -> None: """Basic CLI usage.""" from g import sys as gsys + if env == EnvFlag.Git: + pass + elif env == EnvFlag.Empty: + monkeypatch.chdir(str(tmp_path)) + with patch.object(gsys, "argv", argv_args): proc = run(wait=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - assert proc is not None - assert proc.stdout is not None - captured = proc.stdout.read() + if expect_cmd is None: + assert proc is None + else: + assert proc is not None + assert proc.stdout is not None + captured = proc.stdout.read() - assert captured == get_output(expect_cmd, shell=True, stderr=subprocess.STDOUT) + assert captured == get_output( + expect_cmd, shell=True, stderr=subprocess.STDOUT + )