22
33from __future__ import annotations
44
5+ import asyncio
56import functools
67import getpass
78import pathlib
1819from libvcs .sync .hg import HgSync
1920from 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
2235class 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
784924def 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