From 30bacbdd374a90b33736bd7ba69e64039d6e6ede Mon Sep 17 00:00:00 2001
From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com>
Date: Mon, 8 Nov 2021 18:07:06 +0300
Subject: [PATCH] Add rpm_vercmp python library for version comparison
 - 3002.2 (#449)

* Add rpm_vercmp python library for version comparison

* Add rpm-vercmp to tiamat builds

* Put GPG tests back to test_rpm_lowpkg

Co-authored-by: Megan Wilhite <mwilhite@vmware.com>
---
 changelog/60814.added                         |   1 +
 requirements/static/ci/linux.in               |   1 +
 requirements/static/ci/py3.5/linux.txt        |   1 +
 requirements/static/ci/py3.6/docs.txt         |   1 +
 requirements/static/ci/py3.6/linux.txt        |   1 +
 requirements/static/ci/py3.7/docs.txt         |   1 +
 requirements/static/ci/py3.7/linux.txt        |   1 +
 requirements/static/ci/py3.8/docs.txt         |   1 +
 requirements/static/ci/py3.8/linux.txt        |   1 +
 requirements/static/ci/py3.9/docs.txt         |   1 +
 requirements/static/ci/py3.9/linux.txt        |   1 +
 requirements/static/pkg/linux.in              |   1 +
 requirements/static/pkg/py3.5/linux.txt       |   1 +
 requirements/static/pkg/py3.6/linux.txt       |   1 +
 requirements/static/pkg/py3.7/linux.txt       |   1 +
 requirements/static/pkg/py3.8/linux.txt       |   1 +
 requirements/static/pkg/py3.9/linux.txt       |   1 +
 salt/modules/rpm_lowpkg.py                    |  21 +-
 tests/pytests/unit/modules/test_rpm_lowpkg.py | 545 ++++++++++++++++++
 tests/unit/modules/test_rpm_lowpkg.py         | 486 ----------------
 20 files changed, 582 insertions(+), 487 deletions(-)
 create mode 100644 changelog/60814.added
 create mode 100644 tests/pytests/unit/modules/test_rpm_lowpkg.py
 delete mode 100644 tests/unit/modules/test_rpm_lowpkg.py

diff --git a/changelog/60814.added b/changelog/60814.added
new file mode 100644
index 0000000000..7a9ffe1b25
--- /dev/null
+++ b/changelog/60814.added
@@ -0,0 +1 @@
+Add the python rpm-vercmp library in the rpm_lowpkg.py module.
diff --git a/requirements/static/ci/linux.in b/requirements/static/ci/linux.in
index c3dbbf7cd0..99e10fbc0b 100644
--- a/requirements/static/ci/linux.in
+++ b/requirements/static/ci/linux.in
@@ -26,6 +26,7 @@ paramiko>=2.1.6
 pycurl
 pygit2<=0.28.2; python_version < '3.8'
 pygit2>=1.2.0; python_version >= '3.8'
+rpm-vercmp
 pyiface
 pyinotify
 python-etcd>0.4.2
diff --git a/requirements/static/ci/py3.5/linux.txt b/requirements/static/ci/py3.5/linux.txt
index 6b64d844db..82450fa2be 100644
--- a/requirements/static/ci/py3.5/linux.txt
+++ b/requirements/static/ci/py3.5/linux.txt
@@ -203,6 +203,7 @@ rfc3987==1.3.8
 rsa==4.0                  # via google-auth
 s3transfer==0.3.3         # via boto3
 scp==0.13.2               # via junos-eznc
+rpm-vercmp==0.1.2
 setproctitle==1.1.10
 setuptools-scm==3.2.0
 six==1.15.0
diff --git a/requirements/static/ci/py3.6/docs.txt b/requirements/static/ci/py3.6/docs.txt
index 29ea37911e..3dd44628a9 100644
--- a/requirements/static/ci/py3.6/docs.txt
+++ b/requirements/static/ci/py3.6/docs.txt
@@ -28,3 +28,4 @@ sphinxcontrib-jsmath==1.0.1  # via sphinx
 sphinxcontrib-qthelp==1.0.3  # via sphinx
 sphinxcontrib-serializinghtml==1.1.4  # via sphinx
 urllib3==1.25.9           # via requests
+rpm-vercmp==0.1.2
diff --git a/requirements/static/ci/py3.6/linux.txt b/requirements/static/ci/py3.6/linux.txt
index 55800bfa25..8733ac1602 100644
--- a/requirements/static/ci/py3.6/linux.txt
+++ b/requirements/static/ci/py3.6/linux.txt
@@ -207,6 +207,7 @@ rfc3987==1.3.8
 rsa==4.0                  # via google-auth
 s3transfer==0.3.3         # via boto3
 scp==0.13.2               # via junos-eznc, napalm, netmiko
+rpm-vercmp==0.1.2
 setproctitle==1.1.10
 setuptools-scm==3.2.0
 six==1.15.0
diff --git a/requirements/static/ci/py3.7/docs.txt b/requirements/static/ci/py3.7/docs.txt
index 6c8ec97880..6c08a48484 100644
--- a/requirements/static/ci/py3.7/docs.txt
+++ b/requirements/static/ci/py3.7/docs.txt
@@ -18,6 +18,7 @@ pygments==2.6.1           # via sphinx
 pyparsing==2.4.7          # via packaging
 pytz==2019.3              # via babel
 requests==2.23.0          # via sphinx
+rpm-vercmp==0.1.2
 six==1.15.0               # via packaging
 snowballstemmer==2.0.0    # via sphinx
 sphinx==3.0.1
diff --git a/requirements/static/ci/py3.7/linux.txt b/requirements/static/ci/py3.7/linux.txt
index c3490e6ba6..e8b0cdd804 100644
--- a/requirements/static/ci/py3.7/linux.txt
+++ b/requirements/static/ci/py3.7/linux.txt
@@ -205,6 +205,7 @@ rfc3987==1.3.8
 rsa==4.0                  # via google-auth
 s3transfer==0.3.3         # via boto3
 scp==0.13.2               # via junos-eznc, napalm, netmiko
+rpm-vercmp==0.1.2
 setproctitle==1.1.10
 setuptools-scm==3.2.0
 six==1.15.0
diff --git a/requirements/static/ci/py3.8/docs.txt b/requirements/static/ci/py3.8/docs.txt
index b3ee53b4b5..178d52fd88 100644
--- a/requirements/static/ci/py3.8/docs.txt
+++ b/requirements/static/ci/py3.8/docs.txt
@@ -18,6 +18,7 @@ pygments==2.4.2           # via sphinx
 pyparsing==2.4.0          # via packaging
 pytz==2019.1              # via babel
 requests==2.22.0          # via sphinx
+rpm-vercmp==0.1.2
 six==1.15.0               # via packaging
 snowballstemmer==1.2.1    # via sphinx
 sphinx==3.0.4
diff --git a/requirements/static/ci/py3.8/linux.txt b/requirements/static/ci/py3.8/linux.txt
index da66159c3e..75e3d72b36 100644
--- a/requirements/static/ci/py3.8/linux.txt
+++ b/requirements/static/ci/py3.8/linux.txt
@@ -205,6 +205,7 @@ rfc3987==1.3.8
 rsa==4.0                  # via google-auth
 s3transfer==0.3.3         # via boto3
 scp==0.13.2               # via junos-eznc, napalm, netmiko
+rpm-vercmp==0.1.2
 setproctitle==1.1.10
 setuptools-scm==3.2.0
 six==1.15.0
diff --git a/requirements/static/ci/py3.9/docs.txt b/requirements/static/ci/py3.9/docs.txt
index 79ebb08d1b..d9e7fdbee0 100644
--- a/requirements/static/ci/py3.9/docs.txt
+++ b/requirements/static/ci/py3.9/docs.txt
@@ -18,6 +18,7 @@ pygments==2.4.2           # via sphinx
 pyparsing==2.4.0          # via packaging
 pytz==2019.1              # via babel
 requests==2.22.0          # via sphinx
+rpm-vercmp==0.1.2
 six==1.15.0               # via packaging
 snowballstemmer==1.2.1    # via sphinx
 sphinx==3.0.4
diff --git a/requirements/static/ci/py3.9/linux.txt b/requirements/static/ci/py3.9/linux.txt
index d11c63ce7a..fe95d02273 100644
--- a/requirements/static/ci/py3.9/linux.txt
+++ b/requirements/static/ci/py3.9/linux.txt
@@ -205,6 +205,7 @@ rfc3987==1.3.8
 rsa==4.0                  # via google-auth
 s3transfer==0.3.3         # via boto3
 scp==0.13.2               # via junos-eznc, napalm, netmiko
+rpm-vercmp==0.1.2
 setproctitle==1.1.10
 setuptools-scm==3.2.0
 six==1.15.0
diff --git a/requirements/static/pkg/linux.in b/requirements/static/pkg/linux.in
index ff88bf10c4..d03b2d3225 100644
--- a/requirements/static/pkg/linux.in
+++ b/requirements/static/pkg/linux.in
@@ -6,5 +6,6 @@ backports.ssl_match_hostname>=3.7.0.1; python_version < '3.7'
 pyopenssl>=19.0.0
 python-dateutil>=2.8.0
 python-gnupg>=0.4.4
+rpm-vercmp
 setproctitle>=1.1.10
 timelib>=0.2.5
diff --git a/requirements/static/pkg/py3.5/linux.txt b/requirements/static/pkg/py3.5/linux.txt
index 8c52ce232e..e33f626de9 100644
--- a/requirements/static/pkg/py3.5/linux.txt
+++ b/requirements/static/pkg/py3.5/linux.txt
@@ -31,6 +31,7 @@ pytz==2020.1              # via tempora
 pyyaml==5.3.1
 pyzmq==18.0.1 ; python_version < "3.9"
 requests==2.21.0
+rpm-vercmp==0.1.2
 setproctitle==1.1.10
 six==1.15.0               # via cheroot, cherrypy, cryptography, more-itertools, pyopenssl, python-dateutil, tempora
 tempora==1.14.1           # via portend
diff --git a/requirements/static/pkg/py3.6/linux.txt b/requirements/static/pkg/py3.6/linux.txt
index e8370c3fcc..f8a5d2ef3e 100644
--- a/requirements/static/pkg/py3.6/linux.txt
+++ b/requirements/static/pkg/py3.6/linux.txt
@@ -31,6 +31,7 @@ pytz==2020.1              # via tempora
 pyyaml==5.3.1
 pyzmq==18.0.1 ; python_version < "3.9"
 requests==2.21.0
+rpm-vercmp==0.1.2
 setproctitle==1.1.10
 six==1.15.0               # via cheroot, cherrypy, cryptography, more-itertools, pyopenssl, python-dateutil, tempora
 tempora==1.14.1           # via portend
diff --git a/requirements/static/pkg/py3.7/linux.txt b/requirements/static/pkg/py3.7/linux.txt
index 49e6cdc2c4..0ae93dc5a3 100644
--- a/requirements/static/pkg/py3.7/linux.txt
+++ b/requirements/static/pkg/py3.7/linux.txt
@@ -30,6 +30,7 @@ pytz==2020.1              # via tempora
 pyyaml==5.3.1
 pyzmq==18.0.1 ; python_version < "3.9"
 requests==2.21.0
+rpm-vercmp==0.1.2
 setproctitle==1.1.10
 six==1.15.0               # via cheroot, cherrypy, cryptography, more-itertools, pyopenssl, python-dateutil, tempora
 tempora==1.14.1           # via portend
diff --git a/requirements/static/pkg/py3.8/linux.txt b/requirements/static/pkg/py3.8/linux.txt
index cfa3134780..895dcbe6f6 100644
--- a/requirements/static/pkg/py3.8/linux.txt
+++ b/requirements/static/pkg/py3.8/linux.txt
@@ -30,6 +30,7 @@ pytz==2020.1              # via tempora
 pyyaml==5.3.1
 pyzmq==19.0.0 ; python_version < "3.9"
 requests==2.21.0
+rpm-vercmp==0.1.2
 setproctitle==1.1.10
 six==1.15.0               # via cheroot, cherrypy, cryptography, more-itertools, pyopenssl, python-dateutil, tempora
 tempora==1.14.1           # via portend
diff --git a/requirements/static/pkg/py3.9/linux.txt b/requirements/static/pkg/py3.9/linux.txt
index 4e2ffc2c7e..af119f0e4d 100644
--- a/requirements/static/pkg/py3.9/linux.txt
+++ b/requirements/static/pkg/py3.9/linux.txt
@@ -30,6 +30,7 @@ pytz==2020.1              # via tempora
 pyyaml==5.3.1
 pyzmq==19.0.2 ; python_version >= "3.9"
 requests==2.21.0
+rpm-vercmp==0.1.2
 setproctitle==1.1.10
 six==1.15.0               # via cheroot, cherrypy, cryptography, more-itertools, pyopenssl, python-dateutil, tempora
 tempora==1.14.1           # via portend
diff --git a/salt/modules/rpm_lowpkg.py b/salt/modules/rpm_lowpkg.py
index 57f336bacf..025cfdfab0 100644
--- a/salt/modules/rpm_lowpkg.py
+++ b/salt/modules/rpm_lowpkg.py
@@ -36,6 +36,13 @@ try:
 except ImportError:
     HAS_RPMUTILS = False
 
+try:
+    import rpm_vercmp
+
+    HAS_PY_RPM = True
+except ImportError:
+    HAS_PY_RPM = False
+
 
 log = logging.getLogger(__name__)
 
@@ -717,6 +724,8 @@ def version_cmp(ver1, ver2, ignore_epoch=False):
                     "labelCompare function. Not using rpm.labelCompare for "
                     "version comparison."
                 )
+        elif HAS_PY_RPM:
+            cmp_func = rpm_vercmp.vercmp
         else:
             log.warning(
                 "Please install a package that provides rpm.labelCompare for "
@@ -785,7 +794,17 @@ def version_cmp(ver1, ver2, ignore_epoch=False):
             if not ver1_r or not ver2_r:
                 ver1_r = ver2_r = ""
 
-            cmp_result = cmp_func((ver1_e, ver1_v, ver1_r), (ver2_e, ver2_v, ver2_r))
+            if HAS_PY_RPM:
+                # handle epoch version comparison first
+                # rpm_vercmp.vercmp does not handle epoch version comparison
+                ret = salt.utils.versions.version_cmp(ver1_e, ver2_e)
+                if ret in (1, -1):
+                    return ret
+                cmp_result = cmp_func(ver1, ver2)
+            else:
+                cmp_result = cmp_func(
+                    (ver1_e, ver1_v, ver1_r), (ver2_e, ver2_v, ver2_r)
+                )
             if cmp_result not in (-1, 0, 1):
                 raise CommandExecutionError(
                     "Comparison result '{}' is invalid".format(cmp_result)
diff --git a/tests/pytests/unit/modules/test_rpm_lowpkg.py b/tests/pytests/unit/modules/test_rpm_lowpkg.py
new file mode 100644
index 0000000000..c9d1ac2b1c
--- /dev/null
+++ b/tests/pytests/unit/modules/test_rpm_lowpkg.py
@@ -0,0 +1,545 @@
+"""
+    :codeauthor: Jayesh Kariya <jayeshk@saltstack.com>
+"""
+
+
+import datetime
+import pytest
+import salt.modules.cmdmod
+import salt.modules.rpm_lowpkg as rpm
+import salt.utils.path
+from tests.support.mock import MagicMock, patch
+
+# pylint: disable=unused-import
+try:
+    import rpm as rpm_lib
+
+    HAS_RPM = True
+except ImportError:
+    HAS_RPM = False
+
+try:
+    import rpm_vercmp
+
+    HAS_PY_RPM = True
+except ImportError:
+    HAS_PY_RPM = False
+# pylint: enable=unused-import
+
+
+def _called_with_root(mock):
+    cmd = " ".join(mock.call_args[0][0])
+    return cmd.startswith("rpm --root /")
+
+
+@pytest.fixture
+def configure_loader_modules():
+    return {rpm: {"rpm": MagicMock(return_value=MagicMock)}}
+
+
+# 'list_pkgs' function tests: 2
+
+
+def test_list_pkgs():
+    """
+    Test if it list the packages currently installed in a dict
+    """
+    mock = MagicMock(return_value="")
+    with patch.dict(rpm.__salt__, {"cmd.run": mock}):
+        assert rpm.list_pkgs() == {}
+        assert not _called_with_root(mock)
+
+
+def test_list_pkgs_root():
+    """
+    Test if it list the packages currently installed in a dict,
+    called with root parameter
+    """
+    mock = MagicMock(return_value="")
+    with patch.dict(rpm.__salt__, {"cmd.run": mock}):
+        rpm.list_pkgs(root="/")
+        assert _called_with_root(mock)
+
+
+# 'verify' function tests: 2
+
+
+def test_verify():
+    """
+    Test if it runs an rpm -Va on a system, and returns the
+    results in a dict
+    """
+    mock = MagicMock(
+        return_value={"stdout": "", "stderr": "", "retcode": 0, "pid": 12345}
+    )
+    with patch.dict(rpm.__salt__, {"cmd.run_all": mock}):
+        assert rpm.verify("httpd") == {}
+        assert not _called_with_root(mock)
+
+
+def test_verify_root():
+    """
+    Test if it runs an rpm -Va on a system, and returns the
+    results in a dict, called with root parameter
+    """
+    mock = MagicMock(
+        return_value={"stdout": "", "stderr": "", "retcode": 0, "pid": 12345}
+    )
+    with patch.dict(rpm.__salt__, {"cmd.run_all": mock}):
+        rpm.verify("httpd", root="/")
+        assert _called_with_root(mock)
+
+
+# 'file_list' function tests: 2
+
+
+def test_file_list():
+    """
+    Test if it list the files that belong to a package.
+    """
+    mock = MagicMock(return_value="")
+    with patch.dict(rpm.__salt__, {"cmd.run": mock}):
+        assert rpm.file_list("httpd") == {"errors": [], "files": []}
+        assert not _called_with_root(mock)
+
+
+def test_file_list_root():
+    """
+    Test if it list the files that belong to a package, using the
+    root parameter.
+    """
+
+    mock = MagicMock(return_value="")
+    with patch.dict(rpm.__salt__, {"cmd.run": mock}):
+        rpm.file_list("httpd", root="/")
+        assert _called_with_root(mock)
+
+
+# 'file_dict' function tests: 2
+
+
+def test_file_dict():
+    """
+    Test if it list the files that belong to a package
+    """
+    mock = MagicMock(return_value="")
+    with patch.dict(rpm.__salt__, {"cmd.run": mock}):
+        assert rpm.file_dict("httpd") == {"errors": [], "packages": {}}
+        assert not _called_with_root(mock)
+
+
+def test_file_dict_root():
+    """
+    Test if it list the files that belong to a package
+    """
+    mock = MagicMock(return_value="")
+    with patch.dict(rpm.__salt__, {"cmd.run": mock}):
+        rpm.file_dict("httpd", root="/")
+        assert _called_with_root(mock)
+
+
+# 'owner' function tests: 1
+
+
+def test_owner():
+    """
+    Test if it return the name of the package that owns the file.
+    """
+    assert rpm.owner() == ""
+
+    ret = "file /usr/bin/salt-jenkins-build is not owned by any package"
+    mock = MagicMock(return_value=ret)
+    with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
+        assert rpm.owner("/usr/bin/salt-jenkins-build") == ""
+        assert not _called_with_root(mock)
+
+    ret = {
+        "/usr/bin/vim": "vim-enhanced-7.4.160-1.e17.x86_64",
+        "/usr/bin/python": "python-2.7.5-16.e17.x86_64",
+    }
+    mock = MagicMock(
+        side_effect=[
+            "python-2.7.5-16.e17.x86_64",
+            "vim-enhanced-7.4.160-1.e17.x86_64",
+        ]
+    )
+    with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
+        assert rpm.owner("/usr/bin/python", "/usr/bin/vim") == ret
+        assert not _called_with_root(mock)
+
+
+def test_owner_root():
+    """
+    Test if it return the name of the package that owns the file,
+    using the parameter root.
+    """
+    assert rpm.owner() == ""
+
+    ret = "file /usr/bin/salt-jenkins-build is not owned by any package"
+    mock = MagicMock(return_value=ret)
+    with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
+        rpm.owner("/usr/bin/salt-jenkins-build", root="/")
+        assert _called_with_root(mock)
+
+
+# 'checksum' function tests: 2
+
+
+def test_checksum():
+    """
+    Test if checksum validate as expected
+    """
+    ret = {
+        "file1.rpm": True,
+        "file2.rpm": False,
+        "file3.rpm": False,
+    }
+
+    mock = MagicMock(side_effect=[True, 0, True, 1, False, 0])
+    with patch.dict(rpm.__salt__, {"file.file_exists": mock, "cmd.retcode": mock}):
+        assert rpm.checksum("file1.rpm", "file2.rpm", "file3.rpm") == ret
+        assert not _called_with_root(mock)
+
+
+def test_checksum_root():
+    """
+    Test if checksum validate as expected, using the parameter
+    root
+    """
+    mock = MagicMock(side_effect=[True, 0])
+    with patch.dict(rpm.__salt__, {"file.file_exists": mock, "cmd.retcode": mock}):
+        rpm.checksum("file1.rpm", root="/")
+        assert _called_with_root(mock)
+
+
+@pytest.mark.parametrize("rpm_lib", ["HAS_RPM", "HAS_PY_RPM", "rpmdev-vercmp"])
+def test_version_cmp_rpm_all_libraries(rpm_lib):
+    """
+    Test package version when each library is installed
+    """
+    rpmdev = salt.utils.path.which("rpmdev-vercmp")
+    patch_cmd = patch.dict(rpm.__salt__, {"cmd.run_all": salt.modules.cmdmod.run_all})
+    if rpm_lib == "rpmdev-vercmp":
+        if rpmdev:
+            patch_rpm = patch("salt.modules.rpm_lowpkg.HAS_RPM", False)
+            patch_py_rpm = patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False)
+        else:
+            pytest.skip("The rpmdev-vercmp binary is not installed")
+    elif rpm_lib == "HAS_RPM":
+        if HAS_RPM:
+            patch_rpm = patch("salt.modules.rpm_lowpkg.HAS_RPM", True)
+            patch_py_rpm = patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False)
+        else:
+            pytest.skip("The RPM lib is not installed, skipping")
+    elif rpm_lib == "HAS_PY_RPM":
+        if HAS_PY_RPM:
+            patch_rpm = patch("salt.modules.rpm_lowpkg.HAS_RPM", False)
+            patch_py_rpm = patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", True)
+        else:
+            pytest.skip("The Python RPM lib is not installed, skipping")
+
+    with patch_rpm, patch_py_rpm, patch_cmd:
+        assert -1 == rpm.version_cmp("1", "2")
+        assert -1 == rpm.version_cmp("2.9.1-6.el7_2.3", "2.9.1-6.el7.4")
+        assert 1 == rpm.version_cmp("3.2", "3.0")
+        assert 0 == rpm.version_cmp("3.0", "3.0")
+        assert 1 == rpm.version_cmp("1:2.9.1-6.el7_2.3", "2.9.1-6.el7.4")
+        assert -1 == rpm.version_cmp("1:2.9.1-6.el7_2.3", "1:2.9.1-6.el7.4")
+        assert 1 == rpm.version_cmp("2:2.9.1-6.el7_2.3", "1:2.9.1-6.el7.4")
+        assert 0 == rpm.version_cmp("3:2.9.1-6.el7.4", "3:2.9.1-6.el7.4")
+        assert -1 == rpm.version_cmp("3:2.9.1-6.el7.4", "3:2.9.1-7.el7.4")
+        assert 1 == rpm.version_cmp("3:2.9.1-8.el7.4", "3:2.9.1-7.el7.4")
+
+
+@patch("salt.modules.rpm_lowpkg.HAS_RPM", True)
+@patch("salt.modules.rpm_lowpkg.rpm.labelCompare", return_value=-1)
+@patch("salt.modules.rpm_lowpkg.log")
+def test_version_cmp_rpm(mock_log, mock_labelCompare):
+    """
+    Test package version if RPM-Python is installed
+
+    :return:
+    """
+    assert -1 == rpm.version_cmp("1", "2")
+    assert not mock_log.warning.called
+    assert mock_labelCompare.called
+
+
+@patch("salt.modules.rpm_lowpkg.HAS_RPM", False)
+@patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", True)
+@patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False)
+@patch("salt.modules.rpm_lowpkg.rpmUtils", create=True)
+@patch("salt.modules.rpm_lowpkg.log")
+def test_version_cmp_rpmutils(mock_log, mock_rpmUtils):
+    """
+    Test package version if rpmUtils.miscutils called
+
+    :return:
+    """
+    mock_rpmUtils.miscutils = MagicMock()
+    mock_rpmUtils.miscutils.compareEVR = MagicMock(return_value=-1)
+    assert -1 == rpm.version_cmp("1", "2")
+    assert mock_log.warning.called
+    assert mock_rpmUtils.miscutils.compareEVR.called
+    assert (
+        mock_log.warning.mock_calls[0][1][0]
+        == "Please install a package that provides rpm.labelCompare for more accurate version comparisons."
+    )
+
+
+@patch("salt.modules.rpm_lowpkg.HAS_RPM", False)
+@patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", False)
+@patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False)
+@patch("salt.utils.path.which", return_value=True)
+@patch("salt.modules.rpm_lowpkg.log")
+def test_version_cmp_rpmdev_vercmp(mock_log, mock_which):
+    """
+    Test package version if rpmdev-vercmp is installed
+
+    :return:
+    """
+    mock__salt__ = MagicMock(return_value={"retcode": 12})
+    with patch.dict(rpm.__salt__, {"cmd.run_all": mock__salt__}):
+        assert -1 == rpm.version_cmp("1", "2")
+        assert mock__salt__.called
+        assert mock_log.warning.called
+        assert (
+            mock_log.warning.mock_calls[0][1][0]
+            == "Please install a package that provides rpm.labelCompare for more accurate version comparisons."
+        )
+        assert (
+            mock_log.warning.mock_calls[1][1][0]
+            == "Installing the rpmdevtools package may surface dev tools in production."
+        )
+
+
+@patch("salt.modules.rpm_lowpkg.HAS_RPM", False)
+@patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", False)
+@patch("salt.modules.rpm_lowpkg.HAS_PY_RPM", False)
+@patch("salt.utils.versions.version_cmp", return_value=-1)
+@patch("salt.utils.path.which", return_value=False)
+@patch("salt.modules.rpm_lowpkg.log")
+def test_version_cmp_python(mock_log, mock_which, mock_version_cmp):
+    """
+    Test package version if falling back to python
+
+    :return:
+    """
+    assert -1 == rpm.version_cmp("1", "2")
+    assert mock_version_cmp.called
+    assert mock_log.warning.called
+    assert (
+        mock_log.warning.mock_calls[0][1][0]
+        == "Please install a package that provides rpm.labelCompare for more accurate version comparisons."
+    )
+    assert (
+        mock_log.warning.mock_calls[1][1][0]
+        == "Falling back on salt.utils.versions.version_cmp() for version comparisons"
+    )
+
+
+def test_list_gpg_keys_no_info():
+    """
+    Test list_gpg_keys with no extra information
+    """
+    mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"]))
+    with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
+        assert rpm.list_gpg_keys() == ["gpg-pubkey-1", "gpg-pubkey-2"]
+        assert not _called_with_root(mock)
+
+
+def test_list_gpg_keys_no_info_root():
+    """
+    Test list_gpg_keys with no extra information and root
+    """
+    mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"]))
+    with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
+        assert rpm.list_gpg_keys(root="/mnt") == ["gpg-pubkey-1", "gpg-pubkey-2"]
+        assert _called_with_root(mock)
+
+
+@patch("salt.modules.rpm_lowpkg.info_gpg_key")
+def test_list_gpg_keys_info(info_gpg_key):
+    """
+    Test list_gpg_keys with extra information
+    """
+    info_gpg_key.side_effect = lambda x, root: {"Description": "key for {}".format(x)}
+    mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"]))
+    with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
+        assert rpm.list_gpg_keys(info=True) == {
+            "gpg-pubkey-1": {"Description": "key for gpg-pubkey-1"},
+            "gpg-pubkey-2": {"Description": "key for gpg-pubkey-2"},
+        }
+        assert not _called_with_root(mock)
+
+
+def test_info_gpg_key():
+    """
+    Test info_gpg_keys from a normal output
+    """
+    info = """Name        : gpg-pubkey
+Version     : 3dbdc284
+Release     : 53674dd4
+Architecture: (none)
+Install Date: Fri 08 Mar 2019 11:57:44 AM UTC
+Group       : Public Keys
+Size        : 0
+License     : pubkey
+Signature   : (none)
+Source RPM  : (none)
+Build Date  : Mon 05 May 2014 10:37:40 AM UTC
+Build Host  : localhost
+Packager    : openSUSE Project Signing Key <opensuse@opensuse.org>
+Summary     : gpg(openSUSE Project Signing Key <opensuse@opensuse.org>)
+Description :
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: rpm-4.14.2.1 (NSS-3)
+
+mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G
+3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ
+93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO
+mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig
+oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD
+VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl
+Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC
+GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C
+hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI
+CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha
+Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr
+hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk
+4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a
+5v4gbqOcigKaFs9Lc3Bj8b/lE10Y
+=i2TA
+-----END PGP PUBLIC KEY BLOCK-----
+
+"""
+    mock = MagicMock(return_value=info)
+    with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
+        assert rpm.info_gpg_key("key") == {
+            "Name": "gpg-pubkey",
+            "Version": "3dbdc284",
+            "Release": "53674dd4",
+            "Architecture": None,
+            "Install Date": datetime.datetime(2019, 3, 8, 11, 57, 44),
+            "Group": "Public Keys",
+            "Size": 0,
+            "License": "pubkey",
+            "Signature": None,
+            "Source RPM": None,
+            "Build Date": datetime.datetime(2014, 5, 5, 10, 37, 40),
+            "Build Host": "localhost",
+            "Packager": "openSUSE Project Signing Key <opensuse@opensuse.org>",
+            "Summary": "gpg(openSUSE Project Signing Key <opensuse@opensuse.org>)",
+            "Description": """-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: rpm-4.14.2.1 (NSS-3)
+
+mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G
+3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ
+93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO
+mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig
+oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD
+VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl
+Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC
+GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C
+hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI
+CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha
+Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr
+hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk
+4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a
+5v4gbqOcigKaFs9Lc3Bj8b/lE10Y
+=i2TA
+-----END PGP PUBLIC KEY BLOCK-----""",
+        }
+        assert not _called_with_root(mock)
+
+
+def test_info_gpg_key_extended():
+    """
+    Test info_gpg_keys from an extended output
+    """
+    info = """Name        : gpg-pubkey
+Version     : 3dbdc284
+Release     : 53674dd4
+Architecture: (none)
+Install Date: Fri 08 Mar 2019 11:57:44 AM UTC
+Group       : Public Keys
+Size        : 0
+License     : pubkey
+Signature   : (none)
+Source RPM  : (none)
+Build Date  : Mon 05 May 2014 10:37:40 AM UTC
+Build Host  : localhost
+Packager    : openSUSE Project Signing Key <opensuse@opensuse.org>
+Summary     : gpg(openSUSE Project Signing Key <opensuse@opensuse.org>)
+Description :
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: rpm-4.14.2.1 (NSS-3)
+
+mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G
+3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ
+93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO
+mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig
+oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD
+VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl
+Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC
+GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C
+hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI
+CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha
+Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr
+hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk
+4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a
+5v4gbqOcigKaFs9Lc3Bj8b/lE10Y
+=i2TA
+-----END PGP PUBLIC KEY BLOCK-----
+
+Distribution: (none)
+"""
+    mock = MagicMock(return_value=info)
+    with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
+        assert rpm.info_gpg_key("key") == {
+            "Name": "gpg-pubkey",
+            "Version": "3dbdc284",
+            "Release": "53674dd4",
+            "Architecture": None,
+            "Install Date": datetime.datetime(2019, 3, 8, 11, 57, 44),
+            "Group": "Public Keys",
+            "Size": 0,
+            "License": "pubkey",
+            "Signature": None,
+            "Source RPM": None,
+            "Build Date": datetime.datetime(2014, 5, 5, 10, 37, 40),
+            "Build Host": "localhost",
+            "Packager": "openSUSE Project Signing Key <opensuse@opensuse.org>",
+            "Summary": "gpg(openSUSE Project Signing Key <opensuse@opensuse.org>)",
+            "Description": """-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: rpm-4.14.2.1 (NSS-3)
+
+mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G
+3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ
+93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO
+mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig
+oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD
+VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl
+Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC
+GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C
+hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI
+CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha
+Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr
+hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk
+4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a
+5v4gbqOcigKaFs9Lc3Bj8b/lE10Y
+=i2TA
+-----END PGP PUBLIC KEY BLOCK-----""",
+            "Distribution": None,
+        }
+        assert not _called_with_root(mock)
+
+
+def test_remove_gpg_key():
+    """
+    Test remove_gpg_key
+    """
+    mock = MagicMock(return_value=0)
+    with patch.dict(rpm.__salt__, {"cmd.retcode": mock}):
+        assert rpm.remove_gpg_key("gpg-pubkey-1")
+        assert not _called_with_root(mock)
diff --git a/tests/unit/modules/test_rpm_lowpkg.py b/tests/unit/modules/test_rpm_lowpkg.py
deleted file mode 100644
index 84020263ea..0000000000
--- a/tests/unit/modules/test_rpm_lowpkg.py
+++ /dev/null
@@ -1,486 +0,0 @@
-"""
-    :codeauthor: Jayesh Kariya <jayeshk@saltstack.com>
-"""
-
-import datetime
-
-import salt.modules.rpm_lowpkg as rpm
-from tests.support.mixins import LoaderModuleMockMixin
-from tests.support.mock import MagicMock, patch
-from tests.support.unit import TestCase
-
-
-def _called_with_root(mock):
-    cmd = " ".join(mock.call_args[0][0])
-    return cmd.startswith("rpm --root /")
-
-
-def _called_with_root(mock):
-    cmd = " ".join(mock.call_args[0][0])
-    return cmd.startswith("rpm --root /")
-
-
-class RpmTestCase(TestCase, LoaderModuleMockMixin):
-    """
-    Test cases for salt.modules.rpm
-    """
-
-    def setup_loader_modules(self):
-        return {rpm: {"rpm": MagicMock(return_value=MagicMock)}}
-
-    # 'list_pkgs' function tests: 2
-
-    def test_list_pkgs(self):
-        """
-        Test if it list the packages currently installed in a dict
-        """
-        mock = MagicMock(return_value="")
-        with patch.dict(rpm.__salt__, {"cmd.run": mock}):
-            self.assertDictEqual(rpm.list_pkgs(), {})
-            self.assertFalse(_called_with_root(mock))
-
-    def test_list_pkgs_root(self):
-        """
-        Test if it list the packages currently installed in a dict,
-        called with root parameter
-        """
-        mock = MagicMock(return_value="")
-        with patch.dict(rpm.__salt__, {"cmd.run": mock}):
-            rpm.list_pkgs(root="/")
-            self.assertTrue(_called_with_root(mock))
-
-    # 'verify' function tests: 2
-
-    def test_verify(self):
-        """
-        Test if it runs an rpm -Va on a system, and returns the
-        results in a dict
-        """
-        mock = MagicMock(
-            return_value={"stdout": "", "stderr": "", "retcode": 0, "pid": 12345}
-        )
-        with patch.dict(rpm.__salt__, {"cmd.run_all": mock}):
-            self.assertDictEqual(rpm.verify("httpd"), {})
-            self.assertFalse(_called_with_root(mock))
-
-    def test_verify_root(self):
-        """
-        Test if it runs an rpm -Va on a system, and returns the
-        results in a dict, called with root parameter
-        """
-        mock = MagicMock(
-            return_value={"stdout": "", "stderr": "", "retcode": 0, "pid": 12345}
-        )
-        with patch.dict(rpm.__salt__, {"cmd.run_all": mock}):
-            rpm.verify("httpd", root="/")
-            self.assertTrue(_called_with_root(mock))
-
-    # 'file_list' function tests: 2
-
-    def test_file_list(self):
-        """
-        Test if it list the files that belong to a package.
-        """
-        mock = MagicMock(return_value="")
-        with patch.dict(rpm.__salt__, {"cmd.run": mock}):
-            self.assertDictEqual(rpm.file_list("httpd"), {"errors": [], "files": []})
-            self.assertFalse(_called_with_root(mock))
-
-    def test_file_list_root(self):
-        """
-        Test if it list the files that belong to a package, using the
-        root parameter.
-        """
-
-        mock = MagicMock(return_value="")
-        with patch.dict(rpm.__salt__, {"cmd.run": mock}):
-            rpm.file_list("httpd", root="/")
-            self.assertTrue(_called_with_root(mock))
-
-    # 'file_dict' function tests: 2
-
-    def test_file_dict(self):
-        """
-        Test if it list the files that belong to a package
-        """
-        mock = MagicMock(return_value="")
-        with patch.dict(rpm.__salt__, {"cmd.run": mock}):
-            self.assertDictEqual(rpm.file_dict("httpd"), {"errors": [], "packages": {}})
-            self.assertFalse(_called_with_root(mock))
-
-    def test_file_dict_root(self):
-        """
-        Test if it list the files that belong to a package
-        """
-        mock = MagicMock(return_value="")
-        with patch.dict(rpm.__salt__, {"cmd.run": mock}):
-            self.assertDictEqual(rpm.file_dict("httpd"), {"errors": [], "packages": {}})
-            self.assertFalse(_called_with_root(mock))
-
-    def test_file_dict_root(self):
-        """
-        Test if it list the files that belong to a package
-        """
-        mock = MagicMock(return_value="")
-        with patch.dict(rpm.__salt__, {"cmd.run": mock}):
-            rpm.file_dict("httpd", root="/")
-            self.assertTrue(_called_with_root(mock))
-
-    # 'owner' function tests: 1
-
-    def test_owner(self):
-        """
-        Test if it return the name of the package that owns the file.
-        """
-        self.assertEqual(rpm.owner(), "")
-
-        ret = "file /usr/bin/salt-jenkins-build is not owned by any package"
-        mock = MagicMock(return_value=ret)
-        with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
-            self.assertEqual(rpm.owner("/usr/bin/salt-jenkins-build"), "")
-            self.assertFalse(_called_with_root(mock))
-
-        ret = {
-            "/usr/bin/vim": "vim-enhanced-7.4.160-1.e17.x86_64",
-            "/usr/bin/python": "python-2.7.5-16.e17.x86_64",
-        }
-        mock = MagicMock(
-            side_effect=[
-                "python-2.7.5-16.e17.x86_64",
-                "vim-enhanced-7.4.160-1.e17.x86_64",
-            ]
-        )
-        with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
-            self.assertDictEqual(rpm.owner("/usr/bin/python", "/usr/bin/vim"), ret)
-            self.assertFalse(_called_with_root(mock))
-
-    def test_owner_root(self):
-        """
-        Test if it return the name of the package that owns the file,
-        using the parameter root.
-        """
-        self.assertEqual(rpm.owner(), "")
-
-        ret = "file /usr/bin/salt-jenkins-build is not owned by any package"
-        mock = MagicMock(return_value=ret)
-        with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
-            rpm.owner("/usr/bin/salt-jenkins-build", root="/")
-            self.assertTrue(_called_with_root(mock))
-
-    # 'checksum' function tests: 2
-
-    def test_checksum(self):
-        """
-        Test if checksum validate as expected
-        """
-        ret = {
-            "file1.rpm": True,
-            "file2.rpm": False,
-            "file3.rpm": False,
-        }
-
-        mock = MagicMock(side_effect=[True, 0, True, 1, False, 0])
-        with patch.dict(rpm.__salt__, {"file.file_exists": mock, "cmd.retcode": mock}):
-            self.assertDictEqual(
-                rpm.checksum("file1.rpm", "file2.rpm", "file3.rpm"), ret
-            )
-            self.assertFalse(_called_with_root(mock))
-
-    def test_checksum_root(self):
-        """
-        Test if checksum validate as expected, using the parameter
-        root
-        """
-        mock = MagicMock(side_effect=[True, 0])
-        with patch.dict(rpm.__salt__, {"file.file_exists": mock, "cmd.retcode": mock}):
-            rpm.checksum("file1.rpm", root="/")
-            self.assertTrue(_called_with_root(mock))
-
-    @patch("salt.modules.rpm_lowpkg.HAS_RPM", True)
-    @patch("salt.modules.rpm_lowpkg.rpm.labelCompare", return_value=-1)
-    @patch("salt.modules.rpm_lowpkg.log")
-    def test_version_cmp_rpm(self, mock_log, mock_labelCompare):
-        """
-        Test package version if RPM-Python is installed
-
-        :return:
-        """
-        self.assertEqual(-1, rpm.version_cmp("1", "2"))
-        self.assertEqual(mock_log.warning.called, False)
-        self.assertEqual(mock_labelCompare.called, True)
-
-    @patch("salt.modules.rpm_lowpkg.HAS_RPM", False)
-    @patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", True)
-    @patch("salt.modules.rpm_lowpkg.rpmUtils", create=True)
-    @patch("salt.modules.rpm_lowpkg.log")
-    def test_version_cmp_rpmutils(self, mock_log, mock_rpmUtils):
-        """
-        Test package version if rpmUtils.miscutils called
-
-        :return:
-        """
-        mock_rpmUtils.miscutils = MagicMock()
-        mock_rpmUtils.miscutils.compareEVR = MagicMock(return_value=-1)
-        self.assertEqual(-1, rpm.version_cmp("1", "2"))
-        self.assertEqual(mock_log.warning.called, True)
-        self.assertEqual(mock_rpmUtils.miscutils.compareEVR.called, True)
-        self.assertEqual(
-            mock_log.warning.mock_calls[0][1][0],
-            "Please install a package that provides rpm.labelCompare for more accurate version comparisons.",
-        )
-
-    @patch("salt.modules.rpm_lowpkg.HAS_RPM", False)
-    @patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", False)
-    @patch("salt.utils.path.which", return_value=True)
-    @patch("salt.modules.rpm_lowpkg.log")
-    def test_version_cmp_rpmdev_vercmp(self, mock_log, mock_which):
-        """
-        Test package version if rpmdev-vercmp is installed
-
-        :return:
-        """
-        mock__salt__ = MagicMock(return_value={"retcode": 12})
-        with patch.dict(rpm.__salt__, {"cmd.run_all": mock__salt__}):
-            self.assertEqual(-1, rpm.version_cmp("1", "2"))
-            self.assertEqual(mock__salt__.called, True)
-            self.assertEqual(mock_log.warning.called, True)
-            self.assertEqual(
-                mock_log.warning.mock_calls[0][1][0],
-                "Please install a package that provides rpm.labelCompare for more accurate version comparisons.",
-            )
-            self.assertEqual(
-                mock_log.warning.mock_calls[1][1][0],
-                "Installing the rpmdevtools package may surface dev tools in production.",
-            )
-
-    @patch("salt.modules.rpm_lowpkg.HAS_RPM", False)
-    @patch("salt.modules.rpm_lowpkg.HAS_RPMUTILS", False)
-    @patch("salt.utils.versions.version_cmp", return_value=-1)
-    @patch("salt.utils.path.which", return_value=False)
-    @patch("salt.modules.rpm_lowpkg.log")
-    def test_version_cmp_python(self, mock_log, mock_which, mock_version_cmp):
-        """
-        Test package version if falling back to python
-
-        :return:
-        """
-        with patch(
-            "salt.modules.rpm_lowpkg.rpm.labelCompare", MagicMock(return_value=0)
-        ), patch("salt.modules.rpm_lowpkg.HAS_RPM", False):
-            self.assertEqual(
-                -1, rpm.version_cmp("1", "2")
-            )  # mock returns -1, a python implementation was called
-
-    def test_list_gpg_keys_no_info(self):
-        """
-        Test list_gpg_keys with no extra information
-        """
-        mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"]))
-        with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
-            self.assertEqual(rpm.list_gpg_keys(), ["gpg-pubkey-1", "gpg-pubkey-2"])
-            self.assertFalse(_called_with_root(mock))
-
-    def test_list_gpg_keys_no_info_root(self):
-        """
-        Test list_gpg_keys with no extra information and root
-        """
-        mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"]))
-        with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
-            self.assertEqual(
-                rpm.list_gpg_keys(root="/mnt"), ["gpg-pubkey-1", "gpg-pubkey-2"]
-            )
-            self.assertTrue(_called_with_root(mock))
-
-    @patch("salt.modules.rpm_lowpkg.info_gpg_key")
-    def test_list_gpg_keys_info(self, info_gpg_key):
-        """
-        Test list_gpg_keys with extra information
-        """
-        info_gpg_key.side_effect = lambda x, root: {
-            "Description": "key for {}".format(x)
-        }
-        mock = MagicMock(return_value="\n".join(["gpg-pubkey-1", "gpg-pubkey-2"]))
-        with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
-            self.assertEqual(
-                rpm.list_gpg_keys(info=True),
-                {
-                    "gpg-pubkey-1": {"Description": "key for gpg-pubkey-1"},
-                    "gpg-pubkey-2": {"Description": "key for gpg-pubkey-2"},
-                },
-            )
-            self.assertFalse(_called_with_root(mock))
-
-    def test_info_gpg_key(self):
-        """
-        Test info_gpg_keys from a normal output
-        """
-        info = """Name        : gpg-pubkey
-Version     : 3dbdc284
-Release     : 53674dd4
-Architecture: (none)
-Install Date: Fri 08 Mar 2019 11:57:44 AM UTC
-Group       : Public Keys
-Size        : 0
-License     : pubkey
-Signature   : (none)
-Source RPM  : (none)
-Build Date  : Mon 05 May 2014 10:37:40 AM UTC
-Build Host  : localhost
-Packager    : openSUSE Project Signing Key <opensuse@opensuse.org>
-Summary     : gpg(openSUSE Project Signing Key <opensuse@opensuse.org>)
-Description :
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: rpm-4.14.2.1 (NSS-3)
-
-mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G
-3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ
-93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO
-mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig
-oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD
-VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl
-Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC
-GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C
-hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI
-CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha
-Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr
-hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk
-4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a
-5v4gbqOcigKaFs9Lc3Bj8b/lE10Y
-=i2TA
------END PGP PUBLIC KEY BLOCK-----
-
-"""
-        mock = MagicMock(return_value=info)
-        with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
-            self.assertEqual(
-                rpm.info_gpg_key("key"),
-                {
-                    "Name": "gpg-pubkey",
-                    "Version": "3dbdc284",
-                    "Release": "53674dd4",
-                    "Architecture": None,
-                    "Install Date": datetime.datetime(2019, 3, 8, 11, 57, 44),
-                    "Group": "Public Keys",
-                    "Size": 0,
-                    "License": "pubkey",
-                    "Signature": None,
-                    "Source RPM": None,
-                    "Build Date": datetime.datetime(2014, 5, 5, 10, 37, 40),
-                    "Build Host": "localhost",
-                    "Packager": "openSUSE Project Signing Key <opensuse@opensuse.org>",
-                    "Summary": "gpg(openSUSE Project Signing Key <opensuse@opensuse.org>)",
-                    "Description": """-----BEGIN PGP PUBLIC KEY BLOCK-----
-Version: rpm-4.14.2.1 (NSS-3)
-
-mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G
-3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ
-93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO
-mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig
-oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD
-VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl
-Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC
-GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C
-hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI
-CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha
-Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr
-hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk
-4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a
-5v4gbqOcigKaFs9Lc3Bj8b/lE10Y
-=i2TA
------END PGP PUBLIC KEY BLOCK-----""",
-                },
-            )
-            self.assertFalse(_called_with_root(mock))
-
-    def test_info_gpg_key_extended(self):
-        """
-        Test info_gpg_keys from an extended output
-        """
-        info = """Name        : gpg-pubkey
-Version     : 3dbdc284
-Release     : 53674dd4
-Architecture: (none)
-Install Date: Fri 08 Mar 2019 11:57:44 AM UTC
-Group       : Public Keys
-Size        : 0
-License     : pubkey
-Signature   : (none)
-Source RPM  : (none)
-Build Date  : Mon 05 May 2014 10:37:40 AM UTC
-Build Host  : localhost
-Packager    : openSUSE Project Signing Key <opensuse@opensuse.org>
-Summary     : gpg(openSUSE Project Signing Key <opensuse@opensuse.org>)
-Description :
------BEGIN PGP PUBLIC KEY BLOCK-----
-Version: rpm-4.14.2.1 (NSS-3)
-
-mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G
-3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ
-93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO
-mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig
-oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD
-VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl
-Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC
-GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C
-hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI
-CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha
-Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr
-hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk
-4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a
-5v4gbqOcigKaFs9Lc3Bj8b/lE10Y
-=i2TA
------END PGP PUBLIC KEY BLOCK-----
-
-Distribution: (none)
-"""
-        mock = MagicMock(return_value=info)
-        with patch.dict(rpm.__salt__, {"cmd.run_stdout": mock}):
-            self.assertEqual(
-                rpm.info_gpg_key("key"),
-                {
-                    "Name": "gpg-pubkey",
-                    "Version": "3dbdc284",
-                    "Release": "53674dd4",
-                    "Architecture": None,
-                    "Install Date": datetime.datetime(2019, 3, 8, 11, 57, 44),
-                    "Group": "Public Keys",
-                    "Size": 0,
-                    "License": "pubkey",
-                    "Signature": None,
-                    "Source RPM": None,
-                    "Build Date": datetime.datetime(2014, 5, 5, 10, 37, 40),
-                    "Build Host": "localhost",
-                    "Packager": "openSUSE Project Signing Key <opensuse@opensuse.org>",
-                    "Summary": "gpg(openSUSE Project Signing Key <opensuse@opensuse.org>)",
-                    "Description": """-----BEGIN PGP PUBLIC KEY BLOCK-----
-Version: rpm-4.14.2.1 (NSS-3)
-
-mQENBEkUTD8BCADWLy5d5IpJedHQQSXkC1VK/oAZlJEeBVpSZjMCn8LiHaI9Wq3G
-3Vp6wvsP1b3kssJGzVFNctdXt5tjvOLxvrEfRJuGfqHTKILByqLzkeyWawbFNfSQ
-93/8OunfSTXC1Sx3hgsNXQuOrNVKrDAQUqT620/jj94xNIg09bLSxsjN6EeTvyiO
-mtE9H1J03o9tY6meNL/gcQhxBvwuo205np0JojYBP0pOfN8l9hnIOLkA0yu4ZXig
-oKOVmf4iTjX4NImIWldT+UaWTO18NWcCrujtgHueytwYLBNV5N0oJIP2VYuLZfSD
-VYuPllv7c6O2UEOXJsdbQaVuzU1HLocDyipnABEBAAG0NG9wZW5TVVNFIFByb2pl
-Y3QgU2lnbmluZyBLZXkgPG9wZW5zdXNlQG9wZW5zdXNlLm9yZz6JATwEEwECACYC
-GwMGCwkIBwMCBBUCCAMEFgIDAQIeAQIXgAUCU2dN1AUJHR8ElQAKCRC4iy/UPb3C
-hGQrB/9teCZ3Nt8vHE0SC5NmYMAE1Spcjkzx6M4r4C70AVTMEQh/8BvgmwkKP/qI
-CWo2vC1hMXRgLg/TnTtFDq7kW+mHsCXmf5OLh2qOWCKi55Vitlf6bmH7n+h34Sha
-Ei8gAObSpZSF8BzPGl6v0QmEaGKM3O1oUbbB3Z8i6w21CTg7dbU5vGR8Yhi9rNtr
-hqrPS+q2yftjNbsODagaOUb85ESfQGx/LqoMePD+7MqGpAXjKMZqsEDP0TbxTwSk
-4UKnF4zFCYHPLK3y/hSH5SEJwwPY11l6JGdC1Ue8Zzaj7f//axUs/hTC0UZaEE+a
-5v4gbqOcigKaFs9Lc3Bj8b/lE10Y
-=i2TA
------END PGP PUBLIC KEY BLOCK-----""",
-                    "Distribution": None,
-                },
-            )
-            self.assertFalse(_called_with_root(mock))
-
-    def test_remove_gpg_key(self):
-        """
-        Test remove_gpg_key
-        """
-        mock = MagicMock(return_value=0)
-        with patch.dict(rpm.__salt__, {"cmd.retcode": mock}):
-            self.assertTrue(rpm.remove_gpg_key("gpg-pubkey-1"))
-            self.assertFalse(_called_with_root(mock))
-- 
2.33.1


