Skip to content

Commit 952966e

Browse files
committed
pytest_plugin(feat): Add async fixtures and doctest support
what: - Add async_git_repo, async_hg_repo, async_svn_repo fixtures - Add asyncio to doctest_namespace for async doctests - Add conditional pytest_asyncio import
1 parent 0edb74c commit 952966e

1 file changed

Lines changed: 142 additions & 0 deletions

File tree

src/libvcs/pytest_plugin.py

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from __future__ import annotations
44

5+
import asyncio
56
import functools
67
import getpass
78
import pathlib
@@ -18,6 +19,18 @@
1819
from libvcs.sync.hg import HgSync
1920
from libvcs.sync.svn import SvnSync
2021

22+
# Async support - conditional import
23+
try:
24+
import pytest_asyncio
25+
26+
from libvcs.sync._async.git import AsyncGitSync
27+
from libvcs.sync._async.hg import AsyncHgSync
28+
from libvcs.sync._async.svn import AsyncSvnSync
29+
30+
HAS_PYTEST_ASYNCIO = True
31+
except ImportError:
32+
HAS_PYTEST_ASYNCIO = False
33+
2134

2235
class MaxUniqueRepoAttemptsExceeded(exc.LibVCSException):
2336
"""Raised when exceeded threshold of attempts to find a unique repo destination."""
@@ -780,6 +793,133 @@ def svn_repo(
780793
return svn_repo
781794

782795

796+
# =============================================================================
797+
# Async Fixtures
798+
# =============================================================================
799+
800+
if HAS_PYTEST_ASYNCIO:
801+
802+
@pytest_asyncio.fixture
803+
@skip_if_git_missing
804+
async def async_git_repo(
805+
remote_repos_path: pathlib.Path,
806+
projects_path: pathlib.Path,
807+
git_remote_repo: pathlib.Path,
808+
set_gitconfig: pathlib.Path,
809+
) -> t.AsyncGenerator[AsyncGitSync, None]:
810+
"""Pre-made async git clone of remote repo checked out to user's projects dir.
811+
812+
Async equivalent of :func:`git_repo` fixture.
813+
814+
Examples
815+
--------
816+
>>> @pytest.mark.asyncio
817+
... async def test_git_operations(async_git_repo):
818+
... status = await async_git_repo.cmd.status()
819+
"""
820+
remote_repo_name = unique_repo_name(remote_repos_path=projects_path)
821+
new_checkout_path = projects_path / remote_repo_name
822+
master_copy = remote_repos_path / "git_repo"
823+
824+
if master_copy.exists():
825+
shutil.copytree(master_copy, new_checkout_path)
826+
yield AsyncGitSync(
827+
url=f"file://{git_remote_repo}",
828+
path=new_checkout_path,
829+
)
830+
return
831+
832+
sync_repo = GitSync(
833+
url=f"file://{git_remote_repo}",
834+
path=master_copy,
835+
remotes={
836+
"origin": GitRemote(
837+
name="origin",
838+
push_url=f"file://{git_remote_repo}",
839+
fetch_url=f"file://{git_remote_repo}",
840+
),
841+
},
842+
)
843+
sync_repo.obtain()
844+
shutil.copytree(master_copy, new_checkout_path)
845+
yield AsyncGitSync(url=f"file://{git_remote_repo}", path=new_checkout_path)
846+
847+
@pytest_asyncio.fixture
848+
@skip_if_hg_missing
849+
async def async_hg_repo(
850+
remote_repos_path: pathlib.Path,
851+
projects_path: pathlib.Path,
852+
hg_remote_repo: pathlib.Path,
853+
set_hgconfig: pathlib.Path,
854+
) -> t.AsyncGenerator[AsyncHgSync, None]:
855+
"""Pre-made async hg clone of remote repo checked out to user's projects dir.
856+
857+
Async equivalent of :func:`hg_repo` fixture.
858+
859+
Examples
860+
--------
861+
>>> @pytest.mark.asyncio
862+
... async def test_hg_operations(async_hg_repo):
863+
... status = await async_hg_repo.cmd.status()
864+
"""
865+
remote_repo_name = unique_repo_name(remote_repos_path=projects_path)
866+
new_checkout_path = projects_path / remote_repo_name
867+
master_copy = remote_repos_path / "hg_repo"
868+
869+
if master_copy.exists():
870+
shutil.copytree(master_copy, new_checkout_path)
871+
yield AsyncHgSync(
872+
url=f"file://{hg_remote_repo}",
873+
path=new_checkout_path,
874+
)
875+
return
876+
877+
sync_repo = HgSync(
878+
url=f"file://{hg_remote_repo}",
879+
path=master_copy,
880+
)
881+
sync_repo.obtain()
882+
shutil.copytree(master_copy, new_checkout_path)
883+
yield AsyncHgSync(url=f"file://{hg_remote_repo}", path=new_checkout_path)
884+
885+
@pytest_asyncio.fixture
886+
@skip_if_svn_missing
887+
async def async_svn_repo(
888+
remote_repos_path: pathlib.Path,
889+
projects_path: pathlib.Path,
890+
svn_remote_repo: pathlib.Path,
891+
) -> t.AsyncGenerator[AsyncSvnSync, None]:
892+
"""Pre-made async svn checkout of remote repo.
893+
894+
Async equivalent of :func:`svn_repo` fixture.
895+
896+
Examples
897+
--------
898+
>>> @pytest.mark.asyncio
899+
... async def test_svn_operations(async_svn_repo):
900+
... status = await async_svn_repo.cmd.status()
901+
"""
902+
remote_repo_name = unique_repo_name(remote_repos_path=projects_path)
903+
new_checkout_path = projects_path / remote_repo_name
904+
master_copy = remote_repos_path / "svn_repo"
905+
906+
if master_copy.exists():
907+
shutil.copytree(master_copy, new_checkout_path)
908+
yield AsyncSvnSync(
909+
url=f"file://{svn_remote_repo}",
910+
path=new_checkout_path,
911+
)
912+
return
913+
914+
sync_repo = SvnSync(
915+
url=f"file://{svn_remote_repo}",
916+
path=str(master_copy),
917+
)
918+
sync_repo.obtain()
919+
shutil.copytree(master_copy, new_checkout_path)
920+
yield AsyncSvnSync(url=f"file://{svn_remote_repo}", path=new_checkout_path)
921+
922+
783923
@pytest.fixture
784924
def add_doctest_fixtures(
785925
request: pytest.FixtureRequest,
@@ -798,6 +938,8 @@ def add_doctest_fixtures(
798938

799939
if not isinstance(request._pyfuncitem, DoctestItem): # Only run on doctest items
800940
return
941+
# Add asyncio for async doctests
942+
doctest_namespace["asyncio"] = asyncio
801943
doctest_namespace["tmp_path"] = tmp_path
802944
if shutil.which("git"):
803945
doctest_namespace["create_git_remote_repo"] = functools.partial(

0 commit comments

Comments
 (0)