From c8e9caa8a717ea3e43dffa91b07f7cfd79e057a0 Mon Sep 17 00:00:00 2001 From: johnthagen Date: Wed, 17 Apr 2019 03:34:19 -0400 Subject: [PATCH] Add Subversion.get_vcs_version method (#6390) Add Subversion.get_vcs_version method to return the version of the currently installed Subversion client. --- AUTHORS.txt | 1 + src/pip/_internal/vcs/subversion.py | 34 +++++++++++++++++ tests/lib/__init__.py | 8 ++++ tests/unit/test_vcs.py | 59 ++++++++++++++++++++++++++++- 4 files changed, 101 insertions(+), 1 deletion(-) diff --git a/AUTHORS.txt b/AUTHORS.txt index cf6c11036cb..13bb354bde0 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -214,6 +214,7 @@ Jeremy Stanley Jeremy Zafran Jim Garrison Jivan Amara +John Hagen John-Scott Atlakson Jon Banafato Jon Dufresne diff --git a/src/pip/_internal/vcs/subversion.py b/src/pip/_internal/vcs/subversion.py index 0d57999adcf..01bb16122f7 100644 --- a/src/pip/_internal/vcs/subversion.py +++ b/src/pip/_internal/vcs/subversion.py @@ -8,6 +8,7 @@ from pip._internal.utils.misc import ( display_path, rmtree, split_auth_from_netloc, ) +from pip._internal.utils.typing import MYPY_CHECK_RUNNING from pip._internal.vcs import VersionControl, vcs _svn_xml_url_re = re.compile('url="([^"]+)"') @@ -16,6 +17,9 @@ _svn_info_xml_url_re = re.compile(r'(.*)') +if MYPY_CHECK_RUNNING: + from typing import Optional, Tuple + logger = logging.getLogger(__name__) @@ -33,6 +37,36 @@ def should_add_vcs_url_prefix(cls, remote_url): def get_base_rev_args(rev): return ['-r', rev] + def get_vcs_version(self): + # type: () -> Optional[Tuple[int, ...]] + """Return the version of the currently installed Subversion client. + + :return: A tuple containing the parts of the version information or + ``None`` if the version returned from ``svn`` could not be parsed. + :raises: BadCommand: If ``svn`` is not installed. + """ + # Example versions: + # svn, version 1.10.3 (r1842928) + # compiled Feb 25 2019, 14:20:39 on x86_64-apple-darwin17.0.0 + # svn, version 1.7.14 (r1542130) + # compiled Mar 28 2018, 08:49:13 on x86_64-pc-linux-gnu + version_prefix = 'svn, version ' + version = self.run_command(['--version'], show_stdout=False) + if not version.startswith(version_prefix): + return None + + version = version[len(version_prefix):].split()[0] + version_list = version.split('.') + try: + parsed_version = tuple(map(int, version_list)) + except ValueError: + return None + + if not parsed_version: + return None + + return parsed_version + def export(self, location): """Export the svn repository at the url to the destination location""" url, rev_options = self.get_url_rev_options(self.url) diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py index 0927624f00a..73df9fa407e 100644 --- a/tests/lib/__init__.py +++ b/tests/lib/__init__.py @@ -897,6 +897,14 @@ def is_bzr_installed(): return True +def is_svn_installed(): + try: + subprocess.check_output(('svn', '--version')) + except OSError: + return False + return True + + def need_bzr(fn): return pytest.mark.bzr(need_executable( 'Bazaar', ('bzr', 'version', '--short') diff --git a/tests/unit/test_vcs.py b/tests/unit/test_vcs.py index df21bc1479c..d720e406380 100644 --- a/tests/unit/test_vcs.py +++ b/tests/unit/test_vcs.py @@ -1,7 +1,10 @@ +import os + import pytest from mock import patch from pip._vendor.packaging.version import parse as parse_version +from pip._internal.exceptions import BadCommand from pip._internal.vcs import ( RevOptions, VersionControl, make_vcs_requirement_url, ) @@ -9,7 +12,7 @@ from pip._internal.vcs.git import Git, looks_like_hash from pip._internal.vcs.mercurial import Mercurial from pip._internal.vcs.subversion import Subversion -from tests.lib import pyversion +from tests.lib import is_svn_installed, pyversion if pyversion >= '3': VERBOSE_FALSE = False @@ -17,6 +20,14 @@ VERBOSE_FALSE = 0 +@pytest.mark.skipif( + 'TRAVIS' not in os.environ, + reason='Subversion is only required under Travis') +def test_ensure_svn_available(): + """Make sure that svn is available when running in Travis.""" + assert is_svn_installed() + + @pytest.mark.parametrize('args, expected', [ # Test without subdir. (('git+https://example.com/pkg', 'dev', 'myproj'), @@ -366,3 +377,49 @@ def test_subversion__get_url_rev_options(): def test_get_git_version(): git_version = Git().get_git_version() assert git_version >= parse_version('1.0.0') + + +@pytest.mark.svn +def test_subversion__get_vcs_version(): + """ + Test Subversion.get_vcs_version() against local ``svn``. + """ + version = Subversion().get_vcs_version() + assert len(version) == 3 + for part in version: + assert isinstance(part, int) + assert version[0] >= 1 + + +@pytest.mark.parametrize('svn_output, expected_version', [ + ('svn, version 1.10.3 (r1842928)\n' + ' compiled Feb 25 2019, 14:20:39 on x86_64-apple-darwin17.0.0', + (1, 10, 3)), + ('svn, version 1.9.7 (r1800392)', (1, 9, 7)), + ('svn, version 1.9.7a1 (r1800392)', None), + ('svn, version 1.9 (r1800392)', (1, 9)), + ('svn, version .9.7 (r1800392)', None), + ('svn version 1.9.7 (r1800392)', None), + ('svn 1.9.7', None), + ('svn, version . .', None), + ('', None), +]) +@patch('pip._internal.vcs.subversion.Subversion.run_command') +def test_subversion__get_vcs_version_patched(mock_run_command, svn_output, + expected_version): + """ + Test Subversion.get_vcs_version() against patched output. + """ + mock_run_command.return_value = svn_output + version = Subversion().get_vcs_version() + assert version == expected_version + + +@patch('pip._internal.vcs.subversion.Subversion.run_command') +def test_subversion__get_vcs_version_svn_not_installed(mock_run_command): + """ + Test Subversion.get_vcs_version() when svn is not installed. + """ + mock_run_command.side_effect = BadCommand + with pytest.raises(BadCommand): + Subversion().get_vcs_version()