Skip to content

Commit

Permalink
pip refactoring: implementation of installed and version (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
romainx committed Mar 15, 2021
1 parent f677e54 commit 45d9753
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 23 deletions.
12 changes: 11 additions & 1 deletion doc/source/modules.rst
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ host

:class:`testinfra.modules.package.Package` class

.. attribute:: pip

:class:`testinfra.modules.pip.Pip` class

.. attribute:: pip_package

:class:`testinfra.modules.pip.PipPackage` class
Expand Down Expand Up @@ -182,11 +186,17 @@ Package
:members:


Pip
~~~~~~~~~~

.. autoclass:: testinfra.modules.pip.Pip
:members:


PipPackage
~~~~~~~~~~

.. autoclass:: testinfra.modules.pip.PipPackage
:members:


Podman
Expand Down
40 changes: 34 additions & 6 deletions test/test_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from ipaddress import IPv4Address
from ipaddress import IPv6Address


from testinfra.modules.socket import parse_socketspec

all_images = pytest.mark.testinfra_hosts(
Expand Down Expand Up @@ -515,13 +516,40 @@ def test_command_execution(host):


def test_pip_package(host):
assert host.pip_package.get_packages()["pip"]["version"] == "18.1"
pytest = host.pip_package.get_packages(pip_path="/v/bin/pip")["pytest"]
assert pytest["version"].startswith("2.")
outdated = host.pip_package.get_outdated_packages(pip_path="/v/bin/pip")["pytest"]
assert outdated["current"] == pytest["version"]
with pytest.warns(DeprecationWarning):
assert host.pip_package.get_packages()["pip"]["version"] == "18.1"
pytest_package = host.pip_package.get_packages(pip_path="/v/bin/pip")["pytest"]
assert pytest_package["version"].startswith("2.")
with pytest.warns(DeprecationWarning):
outdated = host.pip_package.get_outdated_packages(pip_path="/v/bin/pip")[
"pytest"
]
assert outdated["current"] == pytest_package["version"]
assert int(outdated["latest"].split(".")[0]) > 2
with pytest.warns(DeprecationWarning):
assert host.pip_package.check().succeeded


def test_pip(host):
# get_packages
assert host.pip.get_packages()["pip"]["version"] == "18.1"
pytest_package = host.pip.get_packages(pip_path="/v/bin/pip")["pytest"]
assert pytest_package["version"].startswith("2.")
# outdated
outdated = host.pip.get_outdated_packages(pip_path="/v/bin/pip")["pytest"]
assert outdated["current"] == pytest_package["version"]
assert int(outdated["latest"].split(".")[0]) > 2
assert host.pip_package.check().succeeded
# check
assert host.pip.check().succeeded
# is_installed
assert host.pip("pip").is_installed
assert not host.pip("does_not_exist").is_installed
pytest_package = host.pip("pytest", pip_path="/v/bin/pip")
assert pytest_package.is_installed
# version
assert host.pip("pip").version == "18.1"
assert pytest_package.version.startswith("2.")
assert host.pip("does_not_exist").version == ""


def test_environment_home(host):
Expand Down
1 change: 1 addition & 0 deletions testinfra/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"iptables": "iptables:Iptables",
"mount_point": "mountpoint:MountPoint",
"package": "package:Package",
"pip": "pip:Pip",
"pip_package": "pip:PipPackage",
"process": "process:Process",
"puppet_resource": "puppet:PuppetResource",
Expand Down
88 changes: 72 additions & 16 deletions testinfra/modules/pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@

import json
import re
import warnings

from testinfra.modules.base import InstanceModule
from testinfra.modules.base import Module


def _re_match(line, regexp):
Expand All @@ -23,10 +24,38 @@ def _re_match(line, regexp):
return match.groups()


class PipPackage(InstanceModule):
"""Test pip packages status and version"""
class Pip(Module):
"""Test pip package manager and packages"""

def check(self, pip_path="pip"):
def __init__(self, name, pip_path="pip"):
self.name = name
self.pip_path = pip_path
super().__init__()

@property
def is_installed(self):
"""Test if the package is installed
>>> host.package("pip").is_installed
True
"""
return self.run_test("%s show %s", self.pip_path, self.name).rc == 0

@property
def version(self):
"""Return package version as returned by pip
>>> host.package("pip").version
'18.1'
"""
return self.check_output(
"%s show %s | grep Version: | cut -d' ' -f2",
self.pip_path,
self.name,
)

@classmethod
def check(cls, pip_path="pip"):
"""Verify installed packages have compatible dependencies.
>>> cmd = host.pip_package.check()
Expand All @@ -41,20 +70,18 @@ def check(self, pip_path="pip"):
.. _pip check: https://pip.pypa.io/en/stable/reference/pip_check/
.. _9.0.0: https://pip.pypa.io/en/stable/news/#id526
"""
cmd = "{} check".format(pip_path)
return self.run_expect([0, 1], cmd)
return cls.run_expect([0, 1], "%s check", pip_path)

def get_packages(self, pip_path="pip"):
@classmethod
def get_packages(cls, pip_path="pip"):
"""Get all installed packages and versions returned by `pip list`:
>>> host.pip_package.get_packages(pip_path='~/venv/website/bin/pip')
{'Django': {'version': '1.10.2'},
'mywebsite': {'version': '1.0a3', 'path': '/srv/website'},
'psycopg2': {'version': '2.6.2'}}
"""
out = self.run_expect(
[0, 2], "{0} list --no-index --format=json".format(pip_path)
)
out = cls.run_expect([0, 2], "%s list --no-index --format=json", pip_path)
pkgs = {}
if out.rc == 0:
for pkg in json.loads(out.stdout):
Expand All @@ -63,9 +90,7 @@ def get_packages(self, pip_path="pip"):
else:
# pip < 9
output_re = re.compile(r"^(.+) \((.+)\)$")
for line in self.check_output(
"{0} list --no-index".format(pip_path)
).splitlines():
for line in cls.check_output("%s list --no-index", pip_path).splitlines():
if line.startswith("Warning: "):
# Warning: cannot find svn location for ...
continue
Expand All @@ -77,14 +102,15 @@ def get_packages(self, pip_path="pip"):
pkgs[name] = {"version": version}
return pkgs

def get_outdated_packages(self, pip_path="pip"):
@classmethod
def get_outdated_packages(cls, pip_path="pip"):
"""Get all outdated packages with current and latest version
>>> host.pip_package.get_outdated_packages(
... pip_path='~/venv/website/bin/pip')
{'Django': {'current': '1.10.2', 'latest': '1.10.3'}}
"""
out = self.run_expect([0, 2], "{0} list -o --format=json".format(pip_path))
out = cls.run_expect([0, 2], "%s list -o --format=json", pip_path)
pkgs = {}
if out.rc == 0:
for pkg in json.loads(out.stdout):
Expand All @@ -100,11 +126,41 @@ def get_outdated_packages(self, pip_path="pip"):
re.compile(r"^(.+?) \((.+)\) - Latest: (.+) .*$"),
re.compile(r"^(.+?) \(Current: (.+) Latest: (.+) .*$"),
]
for line in self.check_output("{0} list -o".format(pip_path)).splitlines():
for line in cls.check_output("%s list -o", pip_path).splitlines():
if line.startswith("Warning: "):
# Warning: cannot find svn location for ...
continue
output_re = regexpes[1] if "Current:" in line else regexpes[0]
name, current, latest = _re_match(line, output_re)
pkgs[name] = {"current": current, "latest": latest}
return pkgs


class PipPackage(Pip):
""".. deprecated:: 6.2
Use :class:`~testinfra.modules.pip.Pip` instead.
"""

@staticmethod
def _deprecated():
"""Raise a `DeprecationWarning`"""
warnings.warn(
"Calling host.pip_package is deprecated, call host.pip instead",
DeprecationWarning,
)

@classmethod
def check(cls, pip_path="pip"):
PipPackage._deprecated()
return super().check(pip_path=pip_path)

@classmethod
def get_packages(cls, pip_path="pip"):
PipPackage._deprecated()
return super().get_packages(pip_path=pip_path)

@classmethod
def get_outdated_packages(cls, pip_path="pip"):
PipPackage._deprecated()
return super().get_outdated_packages(pip_path=pip_path)

0 comments on commit 45d9753

Please sign in to comment.