Files
2026-07-13 13:22:34 +08:00

280 lines
9.0 KiB
Python

from __future__ import annotations
import subprocess
from pathlib import Path
from unittest.mock import patch
import pytest
from clint.utils import ALLOWED_EXTS, _git_ls_files, resolve_paths
@pytest.fixture
def git_repo(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
"""Create and initialize a git repository in a temporary directory."""
subprocess.check_call(["git", "init"], cwd=tmp_path, stdout=subprocess.DEVNULL)
subprocess.check_call(["git", "config", "user.email", "test@example.com"], cwd=tmp_path)
subprocess.check_call(["git", "config", "user.name", "Test User"], cwd=tmp_path)
monkeypatch.chdir(tmp_path)
return tmp_path
def test_resolve_paths_with_real_git_repo_tracked_and_untracked(git_repo: Path) -> None:
tracked_py = git_repo / "tracked.py"
tracked_md = git_repo / "tracked.md"
tracked_py.write_text("# tracked python file")
tracked_md.write_text("# tracked markdown file")
subprocess.check_call(["git", "add", "tracked.py", "tracked.md"])
subprocess.check_call(["git", "commit", "-m", "Add tracked files"])
untracked_py = git_repo / "untracked.py"
untracked_rst = git_repo / "untracked.rst"
untracked_py.write_text("# untracked python file")
untracked_rst.write_text("# untracked rst file")
gitignore = git_repo / ".gitignore"
gitignore.write_text("ignored.py\n")
ignored_py = git_repo / "ignored.py"
ignored_py.write_text("# ignored file")
result = resolve_paths([Path(".")])
expected_paths = [
Path("tracked.md"),
Path("tracked.py"),
Path("untracked.py"),
Path("untracked.rst"),
]
assert result == expected_paths
assert Path("ignored.py") not in result
def test_resolve_paths_with_real_git_repo_specific_pathspecs(git_repo: Path) -> None:
subdir = git_repo / "subdir"
subdir.mkdir()
root_py = git_repo / "root.py"
subdir_py = subdir / "sub.py"
subdir_md = subdir / "sub.md"
root_py.write_text("# root file")
subdir_py.write_text("# subdir python file")
subdir_md.write_text("# subdir markdown file")
subprocess.check_call(["git", "add", "root.py"])
subprocess.check_call(["git", "commit", "-m", "Add root file"])
result = resolve_paths([Path("subdir")])
expected_paths = [Path("subdir/sub.md"), Path("subdir/sub.py")]
assert result == expected_paths
assert Path("root.py") not in result
def test_resolve_paths_with_real_git_repo_untracked_only(git_repo: Path) -> None:
untracked1 = git_repo / "untracked1.py"
untracked2 = git_repo / "untracked2.md"
untracked1.write_text("# untracked file 1")
untracked2.write_text("# untracked file 2")
result = resolve_paths([Path(".")])
expected_paths = [Path("untracked1.py"), Path("untracked2.md")]
assert result == expected_paths
def test_resolve_paths_with_real_git_repo_tracked_only(git_repo: Path) -> None:
tracked1 = git_repo / "tracked1.py"
tracked2 = git_repo / "tracked2.md"
tracked1.write_text("# tracked file 1")
tracked2.write_text("# tracked file 2")
subprocess.check_call(["git", "add", "tracked1.py", "tracked2.md"])
subprocess.check_call(["git", "commit", "-m", "Add tracked files"])
result = resolve_paths([Path(".")])
expected_paths = [Path("tracked1.py"), Path("tracked2.md")]
assert result == expected_paths
def test_resolve_paths_with_real_git_repo_removed_tracked_file(git_repo: Path) -> None:
tracked1 = git_repo / "tracked1.py"
tracked2 = git_repo / "tracked2.md"
tracked1.write_text("# tracked file 1")
tracked2.write_text("# tracked file 2")
subprocess.check_call(["git", "add", "tracked1.py", "tracked2.md"])
subprocess.check_call(["git", "commit", "-m", "Add tracked files"])
tracked1.unlink()
result = resolve_paths([Path(".")])
expected_paths = [Path("tracked2.md")]
assert result == expected_paths
def test_git_ls_files_success() -> None:
mock_output = "file1.py\ndir/file2.md\nfile3.ipynb\n"
with patch("subprocess.check_output", return_value=mock_output) as mock_check_output:
result = _git_ls_files([Path(".")])
mock_check_output.assert_called_once()
expected = [Path("file1.py"), Path("dir/file2.md"), Path("file3.ipynb")]
assert result == expected
def test_git_ls_files_empty_output() -> None:
with patch("subprocess.check_output", return_value="") as mock_check_output:
result = _git_ls_files([Path(".")])
mock_check_output.assert_called_once()
assert result == []
def test_git_ls_files_with_pathspecs() -> None:
mock_output = "file1.py\n"
with patch("subprocess.check_output", return_value=mock_output) as mock_check_output:
result = _git_ls_files([Path("dir1"), Path("file.py")])
mock_check_output.assert_called_once()
assert result == [Path("file1.py")]
def test_git_ls_files_subprocess_error() -> None:
with patch(
"subprocess.check_output", side_effect=subprocess.CalledProcessError(1, "git")
) as mock_check_output:
with pytest.raises(RuntimeError, match="Failed to list git files"):
_git_ls_files([Path(".")])
mock_check_output.assert_called_once()
def test_git_ls_files_os_error() -> None:
with patch(
"subprocess.check_output", side_effect=OSError("git not found")
) as mock_check_output:
with pytest.raises(RuntimeError, match="Failed to list git files"):
_git_ls_files([Path(".")])
mock_check_output.assert_called_once()
def test_resolve_paths_default_current_dir() -> None:
mock_output = "file1.py\nfile2.md\n"
with (
patch("subprocess.check_output", return_value=mock_output) as mock_check_output,
patch("pathlib.Path.exists", return_value=True),
):
result = resolve_paths([])
mock_check_output.assert_called_once()
expected = [Path("file1.py"), Path("file2.md")]
assert result == expected
def test_resolve_paths_filters_by_extension() -> None:
mock_output = "file1.py\nfile2.md\nfile3.txt\nfile4.ipynb\nfile5.mdx\nfile6.js\nfile7.rst\n"
with (
patch("subprocess.check_output", return_value=mock_output) as mock_check_output,
patch("pathlib.Path.exists", return_value=True),
):
result = resolve_paths([Path(".")])
mock_check_output.assert_called_once()
expected = [
Path("file1.py"),
Path("file2.md"),
Path("file4.ipynb"),
Path("file5.mdx"),
Path("file7.rst"),
]
assert result == expected
def test_resolve_paths_case_insensitive_extensions() -> None:
mock_output = "file1.PY\nfile2.MD\nfile3.IPYNB\nfile4.py\nfile5.RST\n"
with (
patch("subprocess.check_output", return_value=mock_output) as mock_check_output,
patch("pathlib.Path.exists", return_value=True),
):
result = resolve_paths([Path(".")])
mock_check_output.assert_called_once()
expected = [
Path("file1.PY"),
Path("file2.MD"),
Path("file3.IPYNB"),
Path("file4.py"),
Path("file5.RST"),
]
assert result == expected
def test_resolve_paths_returns_sorted_list() -> None:
mock_output = "z_file.py\na_file.md\nm_file.ipynb\n"
with (
patch("subprocess.check_output", return_value=mock_output) as mock_check_output,
patch("pathlib.Path.exists", return_value=True),
):
result = resolve_paths([Path(".")])
mock_check_output.assert_called_once()
expected = [Path("a_file.md"), Path("m_file.ipynb"), Path("z_file.py")]
assert result == expected
def test_resolve_paths_deduplicates_results() -> None:
mock_output = "file1.py\nfile1.py\nfile2.md\nfile2.md\n"
with (
patch("subprocess.check_output", return_value=mock_output) as mock_check_output,
patch("pathlib.Path.exists", return_value=True),
):
result = resolve_paths([Path(".")])
mock_check_output.assert_called_once()
expected = [Path("file1.py"), Path("file2.md")]
assert result == expected
def test_resolve_paths_with_multiple_pathspecs() -> None:
mock_output = "dir1/file1.py\ndir2/file2.md\nfile3.ipynb\n"
with (
patch("subprocess.check_output", return_value=mock_output) as mock_check_output,
patch("pathlib.Path.exists", return_value=True),
):
result = resolve_paths([Path("dir1"), Path("file3.ipynb")])
mock_check_output.assert_called_once()
expected = [Path("dir1/file1.py"), Path("dir2/file2.md"), Path("file3.ipynb")]
assert result == expected
def test_resolve_paths_includes_rst_files() -> None:
mock_output = "README.rst\ndocs/index.rst\nsetup.py\n"
with (
patch("subprocess.check_output", return_value=mock_output) as mock_check_output,
patch("pathlib.Path.exists", return_value=True),
):
result = resolve_paths([Path(".")])
mock_check_output.assert_called_once()
expected = [Path("README.rst"), Path("docs/index.rst"), Path("setup.py")]
assert result == expected
def test_allowed_extensions_constant() -> None:
expected = {".md", ".mdx", ".rst", ".py", ".ipynb"}
assert ALLOWED_EXTS == expected