@@ -34,15 +34,33 @@ def __init__(self, attempts: int, *args: object) -> None:
3434 reason = "git is not available" ,
3535)
3636skip_if_svn_missing = pytest .mark .skipif (
37- not shutil .which ("svn" ),
38- reason = "svn is not available" ,
37+ not shutil .which ("svn" ) or not shutil . which ( "svnadmin" ) ,
38+ reason = "svn or svnadmin is not available" ,
3939)
4040skip_if_hg_missing = pytest .mark .skipif (
4141 not shutil .which ("hg" ),
4242 reason = "hg is not available" ,
4343)
4444
4545
46+ def _skip_if_git_missing () -> None :
47+ """Skip the calling fixture when the ``git`` binary is unavailable."""
48+ if not shutil .which ("git" ):
49+ pytest .skip (reason = "git is not available" )
50+
51+
52+ def _skip_if_svn_missing () -> None :
53+ """Skip the calling fixture when ``svn`` or ``svnadmin`` is unavailable."""
54+ if not shutil .which ("svn" ) or not shutil .which ("svnadmin" ):
55+ pytest .skip (reason = "svn or svnadmin is not available" )
56+
57+
58+ def _skip_if_hg_missing () -> None :
59+ """Skip the calling fixture when the ``hg`` binary is unavailable."""
60+ if not shutil .which ("hg" ):
61+ pytest .skip (reason = "hg is not available" )
62+
63+
4664DEFAULT_VCS_NAME = "Test user"
4765DEFAULT_VCS_EMAIL = "test@example.com"
4866
@@ -103,7 +121,7 @@ def __next__(self) -> str:
103121
104122def pytest_ignore_collect (collection_path : pathlib .Path , config : pytest .Config ) -> bool :
105123 """Skip tests if VCS binaries are missing."""
106- if not shutil .which ("svn" ) and any (
124+ if ( not shutil .which ("svn" ) or not shutil . which ( "svnadmin" ) ) and any (
107125 needle in str (collection_path ) for needle in ["svn" , "subversion" ]
108126 ):
109127 return True
@@ -145,7 +163,6 @@ def set_home(
145163
146164
147165@pytest .fixture (scope = "session" )
148- @skip_if_git_missing
149166def vcs_gitconfig (
150167 user_path : pathlib .Path ,
151168 vcs_email : str ,
@@ -173,7 +190,6 @@ def vcs_gitconfig(
173190
174191
175192@pytest .fixture
176- @skip_if_git_missing
177193def set_vcs_gitconfig (
178194 monkeypatch : pytest .MonkeyPatch ,
179195 vcs_gitconfig : pathlib .Path ,
@@ -185,7 +201,6 @@ def set_vcs_gitconfig(
185201
186202
187203@pytest .fixture (scope = "session" )
188- @skip_if_hg_missing
189204def vcs_hgconfig (
190205 user_path : pathlib .Path ,
191206 vcs_user : str ,
@@ -209,7 +224,6 @@ def vcs_hgconfig(
209224
210225
211226@pytest .fixture
212- @skip_if_hg_missing
213227def set_vcs_hgconfig (
214228 monkeypatch : pytest .MonkeyPatch ,
215229 vcs_hgconfig : pathlib .Path ,
@@ -338,11 +352,11 @@ def empty_git_bare_repo_path(libvcs_test_cache_path: pathlib.Path) -> pathlib.Pa
338352
339353
340354@pytest .fixture (scope = "session" )
341- @skip_if_git_missing
342355def empty_git_bare_repo (
343356 empty_git_bare_repo_path : pathlib .Path ,
344357) -> pathlib .Path :
345358 """Return factory to create git remote repo to for clone / push purposes."""
359+ _skip_if_git_missing ()
346360 if (
347361 empty_git_bare_repo_path .exists ()
348362 and (empty_git_bare_repo_path / ".git" ).exists ()
@@ -357,11 +371,11 @@ def empty_git_bare_repo(
357371
358372
359373@pytest .fixture (scope = "session" )
360- @skip_if_git_missing
361374def empty_git_repo (
362375 empty_git_repo_path : pathlib .Path ,
363376) -> pathlib .Path :
364377 """Return factory to create git remote repo to for clone / push purposes."""
378+ _skip_if_git_missing ()
365379 if empty_git_repo_path .exists () and (empty_git_repo_path / ".git" ).exists ():
366380 return empty_git_repo_path
367381
@@ -373,12 +387,12 @@ def empty_git_repo(
373387
374388
375389@pytest .fixture (scope = "session" )
376- @skip_if_git_missing
377390def create_git_remote_bare_repo (
378391 remote_repos_path : pathlib .Path ,
379392 empty_git_bare_repo : pathlib .Path ,
380393) -> CreateRepoFn :
381394 """Return factory to create git remote repo to for clone / push purposes."""
395+ _skip_if_git_missing ()
382396
383397 def fn (
384398 remote_repos_path : pathlib .Path = remote_repos_path ,
@@ -402,12 +416,12 @@ def fn(
402416
403417
404418@pytest .fixture (scope = "session" )
405- @skip_if_git_missing
406419def create_git_remote_repo (
407420 remote_repos_path : pathlib .Path ,
408421 empty_git_repo : pathlib .Path ,
409422) -> CreateRepoFn :
410423 """Return factory to create git remote repo to for clone / push purposes."""
424+ _skip_if_git_missing ()
411425
412426 def fn (
413427 remote_repos_path : pathlib .Path = remote_repos_path ,
@@ -455,13 +469,13 @@ def git_remote_repo_single_commit_post_init(
455469
456470
457471@pytest .fixture (scope = "session" )
458- @skip_if_git_missing
459472def git_remote_repo (
460473 create_git_remote_repo : CreateRepoFn ,
461474 vcs_gitconfig : pathlib .Path ,
462475 git_commit_envvars : GitCommitEnvVars ,
463476) -> pathlib .Path :
464477 """Copy the session-scoped Git repository to a temporary directory."""
478+ _skip_if_git_missing ()
465479 # TODO: Cache the effect of of this in a session-based repo
466480 repo_path = create_git_remote_repo ()
467481 git_remote_repo_single_commit_post_init (
@@ -519,15 +533,11 @@ def empty_svn_repo_path(libvcs_test_cache_path: pathlib.Path) -> pathlib.Path:
519533
520534
521535@pytest .fixture (scope = "session" )
522- @skip_if_svn_missing
523536def empty_svn_repo (
524537 empty_svn_repo_path : pathlib .Path ,
525538) -> pathlib .Path :
526539 """Return factory to create svn remote repo to for clone / push purposes."""
527- if not shutil .which ("svn" ) or not shutil .which ("svnadmin" ):
528- pytest .skip (
529- reason = "svn is not available" ,
530- )
540+ _skip_if_svn_missing ()
531541
532542 if empty_svn_repo_path .exists () and (empty_svn_repo_path / "conf" ).exists ():
533543 return empty_svn_repo_path
@@ -540,12 +550,12 @@ def empty_svn_repo(
540550
541551
542552@pytest .fixture (scope = "session" )
543- @skip_if_svn_missing
544553def create_svn_remote_repo (
545554 remote_repos_path : pathlib .Path ,
546555 empty_svn_repo : pathlib .Path ,
547556) -> CreateRepoFn :
548557 """Pre-made svn repo, bare, used as a file:// remote to checkout and commit to."""
558+ _skip_if_svn_missing ()
549559
550560 def fn (
551561 remote_repos_path : pathlib .Path = remote_repos_path ,
@@ -572,20 +582,20 @@ def fn(
572582
573583
574584@pytest .fixture (scope = "session" )
575- @skip_if_svn_missing
576585def svn_remote_repo (
577586 create_svn_remote_repo : CreateRepoFn ,
578587) -> pathlib .Path :
579588 """Pre-made. Local file:// based SVN server."""
589+ _skip_if_svn_missing ()
580590 return create_svn_remote_repo ()
581591
582592
583593@pytest .fixture (scope = "session" )
584- @skip_if_svn_missing
585594def svn_remote_repo_with_files (
586595 create_svn_remote_repo : CreateRepoFn ,
587596) -> pathlib .Path :
588597 """Pre-made. Local file:// based SVN server."""
598+ _skip_if_svn_missing ()
589599 repo_path = create_svn_remote_repo ()
590600 svn_remote_repo_single_commit_post_init (remote_repo_path = repo_path )
591601 return repo_path
@@ -629,11 +639,11 @@ def empty_hg_repo_path(libvcs_test_cache_path: pathlib.Path) -> pathlib.Path:
629639
630640
631641@pytest .fixture (scope = "session" )
632- @skip_if_hg_missing
633642def empty_hg_repo (
634643 empty_hg_repo_path : pathlib .Path ,
635644) -> pathlib .Path :
636645 """Return factory to create hg remote repo to for clone / push purposes."""
646+ _skip_if_hg_missing ()
637647 if empty_hg_repo_path .exists () and (empty_hg_repo_path / ".hg" ).exists ():
638648 return empty_hg_repo_path
639649
@@ -645,13 +655,13 @@ def empty_hg_repo(
645655
646656
647657@pytest .fixture (scope = "session" )
648- @skip_if_hg_missing
649658def create_hg_remote_repo (
650659 remote_repos_path : pathlib .Path ,
651660 empty_hg_repo : pathlib .Path ,
652661 vcs_hgconfig : pathlib .Path ,
653662) -> CreateRepoFn :
654663 """Pre-made hg repo, bare, used as a file:// remote to checkout and commit to."""
664+ _skip_if_hg_missing ()
655665
656666 def fn (
657667 remote_repos_path : pathlib .Path = remote_repos_path ,
@@ -681,13 +691,13 @@ def fn(
681691
682692
683693@pytest .fixture (scope = "session" )
684- @skip_if_hg_missing
685694def hg_remote_repo (
686695 remote_repos_path : pathlib .Path ,
687696 create_hg_remote_repo : CreateRepoFn ,
688697 vcs_hgconfig : pathlib .Path ,
689698) -> pathlib .Path :
690699 """Pre-made, file-based repo for push and pull."""
700+ _skip_if_hg_missing ()
691701 repo_path = create_hg_remote_repo ()
692702 hg_remote_repo_single_commit_post_init (
693703 remote_repo_path = repo_path ,
@@ -790,20 +800,18 @@ def add_doctest_fixtures(
790800 doctest_namespace : dict [str , t .Any ],
791801 tmp_path : pathlib .Path ,
792802 set_home : pathlib .Path ,
793- git_commit_envvars : GitCommitEnvVars ,
794- vcs_hgconfig : pathlib .Path ,
795- create_git_remote_repo : CreateRepoFn ,
796- create_svn_remote_repo : CreateRepoFn ,
797- create_hg_remote_repo : CreateRepoFn ,
798- git_repo : pathlib .Path ,
799803) -> None :
800804 """Harness pytest fixtures to pytest's doctest namespace."""
801805 from _pytest .doctest import DoctestItem
802806
803807 if not isinstance (request ._pyfuncitem , DoctestItem ): # Only run on doctest items
804808 return
805809 doctest_namespace ["tmp_path" ] = tmp_path
810+ # Request the per-VCS fixtures lazily so a missing binary only drops that
811+ # VCS's doctest helpers -- it does not skip doctests for the others.
806812 if shutil .which ("git" ):
813+ git_commit_envvars = request .getfixturevalue ("git_commit_envvars" )
814+ create_git_remote_repo = request .getfixturevalue ("create_git_remote_repo" )
807815 doctest_namespace ["create_git_remote_repo" ] = functools .partial (
808816 create_git_remote_repo ,
809817 remote_repo_post_init = functools .partial (
@@ -813,14 +821,17 @@ def add_doctest_fixtures(
813821 init_cmd_args = None ,
814822 )
815823 doctest_namespace ["create_git_remote_repo_bare" ] = create_git_remote_repo
816- doctest_namespace ["example_git_repo" ] = git_repo
817- if shutil .which ("svn" ):
824+ doctest_namespace ["example_git_repo" ] = request .getfixturevalue ("git_repo" )
825+ if shutil .which ("svn" ) and shutil .which ("svnadmin" ):
826+ create_svn_remote_repo = request .getfixturevalue ("create_svn_remote_repo" )
818827 doctest_namespace ["create_svn_remote_repo_bare" ] = create_svn_remote_repo
819828 doctest_namespace ["create_svn_remote_repo" ] = functools .partial (
820829 create_svn_remote_repo ,
821830 remote_repo_post_init = svn_remote_repo_single_commit_post_init ,
822831 )
823832 if shutil .which ("hg" ):
833+ vcs_hgconfig = request .getfixturevalue ("vcs_hgconfig" )
834+ create_hg_remote_repo = request .getfixturevalue ("create_hg_remote_repo" )
824835 doctest_namespace ["create_hg_remote_repo_bare" ] = create_hg_remote_repo
825836 doctest_namespace ["create_hg_remote_repo" ] = functools .partial (
826837 create_hg_remote_repo ,
0 commit comments