diff --git a/tests/test_git.py b/tests/test_git.py index 8d0967504..36f911ccf 100644 --- a/tests/test_git.py +++ b/tests/test_git.py @@ -4,11 +4,19 @@ import os import pathlib import textwrap +from typing import Callable import pytest +from pytest_mock import MockerFixture + from libvcs import exc -from libvcs.git import GitRemote, convert_pip_url as git_convert_pip_url, extract_status +from libvcs.git import ( + GitRemote, + GitRepo, + convert_pip_url as git_convert_pip_url, + extract_status, +) from libvcs.shortcuts import create_repo_from_pip_url from libvcs.util import run, which @@ -16,6 +24,10 @@ pytestmark = pytest.mark.skip(reason="git is not available") +RepoTestFactory = Callable[..., GitRepo] +RepoTestFactoryLazyKwargs = Callable[..., dict] + + @pytest.fixture(autouse=True, scope="module") def gitconfig(user_path: pathlib.Path): gitconfig = user_path / ".gitconfig" @@ -37,7 +49,31 @@ def gitconfig_default(monkeypatch: pytest.MonkeyPatch, user_path: pathlib.Path): monkeypatch.setenv("HOME", str(user_path)) -def test_repo_git_obtain_initial_commit_repo(tmp_path: pathlib.Path): +@pytest.mark.parametrize( + # Postpone evaluation of options so fixture variables can interpolate + "constructor,lazy_constructor_options", + [ + [ + GitRepo, + lambda bare_repo_dir, tmp_path, **kwargs: { + "url": f"file://{bare_repo_dir}", + "repo_dir": tmp_path / "obtaining a bare repo", + }, + ], + [ + create_repo_from_pip_url, + lambda bare_repo_dir, tmp_path, **kwargs: { + "pip_url": f"git+file://{bare_repo_dir}", + "repo_dir": tmp_path / "obtaining a bare repo", + }, + ], + ], +) +def test_repo_git_obtain_initial_commit_repo( + tmp_path: pathlib.Path, + constructor: RepoTestFactory, + lazy_constructor_options: RepoTestFactoryLazyKwargs, +): """initial commit repos return 'initial'. note: this behaviors differently from git(1)'s use of the word "bare". @@ -48,26 +84,39 @@ def test_repo_git_obtain_initial_commit_repo(tmp_path: pathlib.Path): run(["git", "init", repo_name], cwd=tmp_path) bare_repo_dir = tmp_path / repo_name - - git_repo = create_repo_from_pip_url( - **{ - "pip_url": f"git+file://{bare_repo_dir}", - "repo_dir": tmp_path / "obtaining a bare repo", - } - ) + git_repo: GitRepo = constructor(**lazy_constructor_options(**locals())) git_repo.obtain() assert git_repo.get_revision() == "initial" -def test_repo_git_obtain_full(tmp_path: pathlib.Path, git_remote): - git_repo = create_repo_from_pip_url( - **{ - "pip_url": f"git+file://{git_remote}", - "repo_dir": tmp_path / "myrepo", - } - ) - +@pytest.mark.parametrize( + # Postpone evaluation of options so fixture variables can interpolate + "constructor,lazy_constructor_options", + [ + [ + GitRepo, + lambda git_remote, tmp_path, **kwargs: { + "url": f"file://{git_remote}", + "repo_dir": tmp_path / "myrepo", + }, + ], + [ + create_repo_from_pip_url, + lambda git_remote, tmp_path, **kwargs: { + "pip_url": f"git+file://{git_remote}", + "repo_dir": tmp_path / "myrepo", + }, + ], + ], +) +def test_repo_git_obtain_full( + tmp_path: pathlib.Path, + git_remote, + constructor: RepoTestFactory, + lazy_constructor_options: RepoTestFactoryLazyKwargs, +): + git_repo: GitRepo = constructor(**lazy_constructor_options(**locals())) git_repo.obtain() test_repo_revision = run(["git", "rev-parse", "HEAD"], cwd=git_remote) @@ -76,14 +125,34 @@ def test_repo_git_obtain_full(tmp_path: pathlib.Path, git_remote): assert os.path.exists(tmp_path / "myrepo") -def test_repo_update_handle_cases(tmp_path: pathlib.Path, git_remote, mocker): - git_repo = create_repo_from_pip_url( - **{ - "pip_url": f"git+file://{git_remote}", - "repo_dir": tmp_path / "myrepo", - } - ) - +@pytest.mark.parametrize( + # Postpone evaluation of options so fixture variables can interpolate + "constructor,lazy_constructor_options", + [ + [ + GitRepo, + lambda git_remote, tmp_path, **kwargs: { + "url": f"file://{git_remote}", + "repo_dir": tmp_path / "myrepo", + }, + ], + [ + create_repo_from_pip_url, + lambda git_remote, tmp_path, **kwargs: { + "pip_url": f"git+file://{git_remote}", + "repo_dir": tmp_path / "myrepo", + }, + ], + ], +) +def test_repo_update_handle_cases( + tmp_path: pathlib.Path, + git_remote: pathlib.Path, + mocker: MockerFixture, + constructor: RepoTestFactory, + lazy_constructor_options: RepoTestFactoryLazyKwargs, +): + git_repo: GitRepo = constructor(**lazy_constructor_options(**locals())) git_repo.obtain() # clone initial repo mocka = mocker.spy(git_repo, "run") git_repo.update_repo() @@ -98,7 +167,35 @@ def test_repo_update_handle_cases(tmp_path: pathlib.Path, git_remote, mocker): assert mocker.call(["symbolic-ref", "--short", "HEAD"]) not in mocka.mock_calls -def test_progress_callback(tmp_path: pathlib.Path, git_remote, mocker): +@pytest.mark.parametrize( + # Postpone evaluation of options so fixture variables can interpolate + "constructor,lazy_constructor_options", + [ + [ + GitRepo, + lambda git_remote, tmp_path, progress_callback, **kwargs: { + "url": f"file://{git_remote}", + "repo_dir": tmp_path / "myrepo", + "progress_callback": progress_callback, + }, + ], + [ + create_repo_from_pip_url, + lambda git_remote, tmp_path, progress_callback, **kwargs: { + "pip_url": f"git+file://{git_remote}", + "repo_dir": tmp_path / "myrepo", + "progress_callback": progress_callback, + }, + ], + ], +) +def test_progress_callback( + tmp_path: pathlib.Path, + git_remote: pathlib.Path, + mocker: MockerFixture, + constructor: RepoTestFactory, + lazy_constructor_options: RepoTestFactoryLazyKwargs, +): def progress_callback_spy(output, timestamp): assert isinstance(output, str) assert isinstance(timestamp, datetime.datetime) @@ -110,27 +207,43 @@ def progress_callback_spy(output, timestamp): run(["git", "rev-parse", "HEAD"], cwd=git_remote) # create a new repo with the repo as a remote - git_repo = create_repo_from_pip_url( - **{ - "pip_url": f"git+file://{git_remote}", - "repo_dir": tmp_path / "myrepo", - "progress_callback": progress_callback, - } - ) + git_repo: GitRepo = constructor(**lazy_constructor_options(**locals())) git_repo.obtain() assert progress_callback.called -def test_remotes(repos_path, git_remote): +@pytest.mark.parametrize( + # Postpone evaluation of options so fixture variables can interpolate + "constructor,lazy_constructor_options", + [ + [ + GitRepo, + lambda git_remote, repos_path, repo_name, **kwargs: { + "url": f"file://{git_remote}", + "repo_dir": repos_path / repo_name, + }, + ], + [ + create_repo_from_pip_url, + lambda git_remote, repos_path, repo_name, **kwargs: { + "pip_url": f"git+file://{git_remote}", + "repo_dir": repos_path / repo_name, + }, + ], + ], +) +def test_remotes( + repos_path: pathlib.Path, + git_remote: pathlib.Path, + constructor: RepoTestFactory, + lazy_constructor_options: RepoTestFactoryLazyKwargs, +): repo_name = "myrepo" remote_name = "myremote" remote_url = "https://localhost/my/git/repo.git" - git_repo = create_repo_from_pip_url( - pip_url=f"git+file://{git_remote}", - repo_dir=repos_path / repo_name, - ) + git_repo: GitRepo = constructor(**lazy_constructor_options(**locals())) git_repo.obtain() git_repo.set_remote(name=remote_name, url=remote_url) @@ -160,17 +273,39 @@ def test_git_get_url_and_rev_from_pip_url(): assert rev == "eucalyptus" -def test_remotes_preserves_git_ssh(repos_path, git_remote): +@pytest.mark.parametrize( + # Postpone evaluation of options so fixture variables can interpolate + "constructor,lazy_constructor_options", + [ + [ + GitRepo, + lambda git_remote, repo_dir, **kwargs: { + "url": f"file://{git_remote}", + "repo_dir": str(repo_dir), + }, + ], + [ + create_repo_from_pip_url, + lambda git_remote, repo_dir, **kwargs: { + "pip_url": f"git+file://{git_remote}", + "repo_dir": repo_dir, + }, + ], + ], +) +def test_remotes_preserves_git_ssh( + repos_path: pathlib.Path, + git_remote: pathlib.Path, + constructor: RepoTestFactory, + lazy_constructor_options: RepoTestFactoryLazyKwargs, +): # Regression test for #14 repo_name = "myexamplegit" repo_dir = repos_path / repo_name remote_name = "myremote" remote_url = "git+ssh://git@github.com/tony/AlgoXY.git" + git_repo: GitRepo = constructor(**lazy_constructor_options(**locals())) - git_repo = create_repo_from_pip_url( - pip_url=f"git+file://{git_remote}", - repo_dir=repo_dir, - ) git_repo.obtain() git_repo.set_remote(name=remote_name, url=remote_url) @@ -180,7 +315,31 @@ def test_remotes_preserves_git_ssh(repos_path, git_remote): ) -def test_private_ssh_format(pip_url_kwargs): +@pytest.mark.parametrize( + # Postpone evaluation of options so fixture variables can interpolate + "constructor,lazy_constructor_options", + [ + [ + GitRepo, + lambda bare_repo_dir, tmp_path, **kwargs: { + "url": f"file://{bare_repo_dir}", + "repo_dir": tmp_path / "obtaining a bare repo", + }, + ], + [ + create_repo_from_pip_url, + lambda bare_repo_dir, tmp_path, **kwargs: { + "pip_url": f"git+file://{bare_repo_dir}", + "repo_dir": tmp_path / "obtaining a bare repo", + }, + ], + ], +) +def test_private_ssh_format( + pip_url_kwargs: dict, + constructor: RepoTestFactory, + lazy_constructor_options: RepoTestFactoryLazyKwargs, +): pip_url_kwargs.update( **{"pip_url": "git+ssh://github.com:/tmp/omg/private_ssh_repo"} ) @@ -190,14 +349,14 @@ def test_private_ssh_format(pip_url_kwargs): excinfo.match(r"is malformatted") -def test_ls_remotes(git_repo): +def test_ls_remotes(git_repo: GitRepo): remotes = git_repo.remotes() assert "origin" in remotes assert "origin" in git_repo.remotes(flat=True) -def test_get_remotes(git_repo): +def test_get_remotes(git_repo: GitRepo): assert "origin" in git_repo.remotes() @@ -207,7 +366,7 @@ def test_get_remotes(git_repo): ["myrepo", "file:///apples"], ], ) -def test_set_remote(git_repo, repo_name, new_repo_url): +def test_set_remote(git_repo: GitRepo, repo_name: str, new_repo_url: str): mynewremote = git_repo.set_remote(name=repo_name, url="file:///") assert "file:///" in mynewremote, "set_remote returns remote" @@ -229,13 +388,13 @@ def test_set_remote(git_repo, repo_name, new_repo_url): ), "Running remove_set should overwrite previous remote" -def test_get_git_version(git_repo): +def test_get_git_version(git_repo: GitRepo): expected_version = git_repo.run(["--version"]).replace("git version ", "") assert git_repo.get_git_version() assert expected_version == git_repo.get_git_version() -def test_get_current_remote_name(git_repo): +def test_get_current_remote_name(git_repo: GitRepo): assert git_repo.get_current_remote_name() == "origin" new_branch = "another-branch-with-no-upstream" @@ -328,7 +487,7 @@ def test_extract_status(): ], ], ) -def test_extract_status_b(fixture, expected_result): +def test_extract_status_b(fixture: str, expected_result: dict): assert ( extract_status(textwrap.dedent(fixture))._asdict().items() >= expected_result.items() @@ -378,7 +537,7 @@ def test_extract_status_b(fixture, expected_result): ], ], ) -def test_extract_status_c(fixture, expected_result): +def test_extract_status_c(fixture: str, expected_result: dict): assert ( expected_result.items() <= extract_status(textwrap.dedent(fixture))._asdict().items()