salt/tests/pytests/unit/modules/test_win_pkg.py
Pedro Algarvio 261e3e825d
Run pyupgrade against the files changed on the merge forward
Signed-off-by: Pedro Algarvio <palgarvio@vmware.com>
2023-07-08 21:32:10 +01:00

756 lines
25 KiB
Python

"""
Tests for the win_pkg module
"""
import logging
import pytest
import salt.modules.config as config
import salt.modules.cp as cp
import salt.modules.pkg_resource as pkg_resource
import salt.modules.win_pkg as win_pkg
import salt.utils.data
import salt.utils.platform
import salt.utils.win_reg as win_reg
from salt.exceptions import MinionError
from tests.support.mock import MagicMock, patch
pytestmark = [
pytest.mark.windows_whitelisted,
pytest.mark.skip_unless_on_windows,
]
@pytest.fixture
def configure_loader_modules(minion_opts):
pkg_info = {
"latest": {
"full_name": "Nullsoft Install System",
"installer": "http://download.sourceforge.net/project/nsis/nsis-setup.exe",
"install_flags": "/S",
"uninstaller": "%PROGRAMFILES(x86)%\\NSIS\\uninst-nsis.exe",
"uninstall_flags": "/S",
"msiexec": False,
"reboot": False,
},
"3.03": {
"full_name": "Nullsoft Install System",
"installer": "http://download.sourceforge.net/project/nsis/NSIS%203/3.03/nsis-3.03-setup.exe",
"install_flags": "/S",
"uninstaller": "%PROGRAMFILES(x86)%\\NSIS\\uninst-nsis.exe",
"uninstall_flags": "/S",
"msiexec": False,
"reboot": False,
},
"3.02": {
"full_name": "Nullsoft Install System",
"installer": "http://download.sourceforge.net/project/nsis/NSIS%203/3.02/nsis-3.02-setup.exe",
"install_flags": "/S",
"uninstaller": "%PROGRAMFILES(x86)%\\NSIS\\uninst-nsis.exe",
"uninstall_flags": "/S",
"msiexec": False,
"reboot": False,
},
}
opts = minion_opts
opts["master_uri"] = "localhost"
return {
cp: {"__opts__": opts},
win_pkg: {
"_get_latest_package_version": MagicMock(return_value="3.03"),
"_get_package_info": MagicMock(return_value=pkg_info),
"__salt__": {
"config.valid_fileproto": config.valid_fileproto,
"cp.hash_file": cp.hash_file,
"pkg_resource.add_pkg": pkg_resource.add_pkg,
"pkg_resource.parse_targets": pkg_resource.parse_targets,
"pkg_resource.sort_pkglist": pkg_resource.sort_pkglist,
"pkg_resource.stringify": pkg_resource.stringify,
},
"__utils__": {
"reg.key_exists": win_reg.key_exists,
"reg.list_keys": win_reg.list_keys,
"reg.read_value": win_reg.read_value,
"reg.value_exists": win_reg.value_exists,
},
},
pkg_resource: {"__grains__": {"os": "Windows"}},
}
def test_pkg__get_reg_software():
result = win_pkg._get_reg_software()
assert isinstance(result, dict)
found_python = False
search = "Python 3"
for key in result:
if search in key:
found_python = True
assert found_python
def test_pkg__get_reg_software_noremove():
search = "test_pkg_noremove"
key = f"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{search}"
win_reg.set_value(hive="HKLM", key=key, vname="DisplayName", vdata=search)
win_reg.set_value(hive="HKLM", key=key, vname="DisplayVersion", vdata="1.0.0")
win_reg.set_value(
hive="HKLM", key=key, vname="NoRemove", vtype="REG_DWORD", vdata="1"
)
try:
result = win_pkg._get_reg_software()
assert isinstance(result, dict)
found = False
search = "test_pkg"
for item in result:
if search in item:
found = True
assert found is True
finally:
win_reg.delete_key_recursive(hive="HKLM", key=key)
assert not win_reg.key_exists(hive="HKLM", key=key)
def test_pkg__get_reg_software_noremove_not_present():
search = "test_pkg_noremove_not_present"
key = f"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{search}"
win_reg.set_value(hive="HKLM", key=key, vname="DisplayName", vdata=search)
win_reg.set_value(hive="HKLM", key=key, vname="DisplayVersion", vdata="1.0.0")
try:
result = win_pkg._get_reg_software()
assert isinstance(result, dict)
found = False
for item in result:
if search in item:
found = True
assert found is False
finally:
win_reg.delete_key_recursive(hive="HKLM", key=key)
assert not win_reg.key_exists(hive="HKLM", key=key)
def test_pkg_install_not_found():
"""
Test pkg.install when the Version is NOT FOUND in the Software
Definition
"""
ret_reg = {"Nullsoft Install System": "3.03"}
# The 2nd time it's run with stringify
se_list_pkgs = {"nsis": ["3.03"]}
with patch.object(win_pkg, "list_pkgs", return_value=se_list_pkgs), patch.object(
win_pkg, "_get_reg_software", return_value=ret_reg
):
expected = {"nsis": {"not found": "3.01"}}
result = win_pkg.install(name="nsis", version="3.01")
assert expected == result
def test_pkg_install_rollback():
"""
test pkg.install rolling back to a previous version
"""
ret_reg = {"Nullsoft Install System": "3.03"}
# The 2nd time it's run, pkg.list_pkgs uses with stringify
se_list_pkgs = [{"nsis": ["3.03"]}, {"nsis": "3.02"}]
with patch.object(win_pkg, "list_pkgs", side_effect=se_list_pkgs), patch.object(
win_pkg, "_get_reg_software", return_value=ret_reg
), patch.dict(
win_pkg.__salt__, {"cp.is_cached": MagicMock(return_value=False)}
), patch.dict(
win_pkg.__salt__,
{"cp.cache_file": MagicMock(return_value="C:\\fake\\path.exe")},
), patch.dict(
win_pkg.__salt__, {"cmd.run_all": MagicMock(return_value={"retcode": 0})}
):
expected = {"nsis": {"new": "3.02", "old": "3.03"}}
result = win_pkg.install(name="nsis", version="3.02")
assert expected == result
def test_pkg_install_existing():
"""
test pkg.install when the package is already installed
no version passed
"""
ret_reg = {"Nullsoft Install System": "3.03"}
# The 2nd time it's run, pkg.list_pkgs uses with stringify
se_list_pkgs = {"nsis": ["3.03"]}
with patch.object(win_pkg, "list_pkgs", return_value=se_list_pkgs), patch.object(
win_pkg, "_get_reg_software", return_value=ret_reg
), patch.dict(
win_pkg.__salt__,
{
"cmd.run_all": MagicMock(return_value={"retcode": 0}),
"cp.cache_file": MagicMock(return_value="C:\\fake\\path.exe"),
"cp.is_cached": MagicMock(return_value=True),
},
):
expected = {}
result = win_pkg.install(name="nsis")
assert expected == result
def test_pkg_install_latest():
"""
test pkg.install when the package is already installed
no version passed
"""
ret_reg = {"Nullsoft Install System": "3.03"}
# The 2nd time it's run, pkg.list_pkgs uses with stringify
se_list_pkgs = [{"nsis": ["3.03"]}, {"nsis": "3.04"}]
mock_cache_file = MagicMock(return_value="C:\\fake\\path.exe")
with patch.object(win_pkg, "list_pkgs", side_effect=se_list_pkgs), patch.object(
win_pkg, "_get_reg_software", return_value=ret_reg
), patch.dict(
win_pkg.__salt__,
{
"cmd.run_all": MagicMock(return_value={"retcode": 0}),
"cp.cache_file": mock_cache_file,
"cp.is_cached": MagicMock(return_value=False),
},
):
expected = {"nsis": {"new": "3.04", "old": "3.03"}}
result = win_pkg.install(name="nsis", version="latest")
assert expected == result
mock_cache_file.assert_called_once_with(
"http://download.sourceforge.net/project/nsis/nsis-setup.exe",
saltenv="base",
source_hash=None,
verify_ssl=True,
use_etag=True,
)
def test_pkg_install_latest_is_cached():
"""
test pkg.install when the package is already installed
no version passed
"""
ret_reg = {"Nullsoft Install System": "3.03"}
# The 2nd time it's run, pkg.list_pkgs uses with stringify
se_list_pkgs = [{"nsis": ["3.03"]}, {"nsis": "3.04"}]
mock_cache_file = MagicMock(return_value="C:\\fake\\path.exe")
with patch.object(win_pkg, "list_pkgs", side_effect=se_list_pkgs), patch.object(
win_pkg, "_get_reg_software", return_value=ret_reg
), patch.dict(
win_pkg.__salt__,
{
"cmd.run_all": MagicMock(return_value={"retcode": 0}),
"cp.cache_file": mock_cache_file,
"cp.is_cached": MagicMock(return_value=True),
},
):
expected = {"nsis": {"new": "3.04", "old": "3.03"}}
result = win_pkg.install(name="nsis", version="latest")
assert expected == result
mock_cache_file.assert_called_once_with(
"http://download.sourceforge.net/project/nsis/nsis-setup.exe",
saltenv="base",
source_hash=None,
verify_ssl=True,
use_etag=True,
)
def test_pkg_install_existing_with_version():
"""
test pkg.install when the package is already installed
A version is passed
"""
ret_reg = {"Nullsoft Install System": "3.03"}
# The 2nd time it's run, pkg.list_pkgs uses with stringify
se_list_pkgs = {"nsis": ["3.03"]}
with patch.object(win_pkg, "list_pkgs", return_value=se_list_pkgs), patch.object(
win_pkg, "_get_reg_software", return_value=ret_reg
), patch.dict(
win_pkg.__salt__,
{
"cmd.run_all": MagicMock(return_value={"retcode": 0}),
"cp.cache_file": MagicMock(return_value="C:\\fake\\path.exe"),
"cp.is_cached": MagicMock(return_value=False),
},
):
expected = {}
result = win_pkg.install(name="nsis", version="3.03")
assert expected == result
def test_pkg_install_name():
"""
test pkg.install name extra_install_flags
"""
ret__get_package_info = {
"3.03": {
"uninstaller": "%program.exe",
"reboot": False,
"msiexec": False,
"installer": "runme.exe",
"uninstall_flags": "/S",
"locale": "en_US",
"install_flags": "/s",
"full_name": "Firebox 3.03 (x86 en-US)",
}
}
mock_cmd_run_all = MagicMock(return_value={"retcode": 0})
with patch.object(
salt.utils.data, "is_true", MagicMock(return_value=True)
), patch.object(
win_pkg, "_get_package_info", MagicMock(return_value=ret__get_package_info)
), patch.dict(
win_pkg.__salt__,
{
"pkg_resource.parse_targets": MagicMock(
return_value=[{"firebox": "3.03"}, None]
),
"cp.is_cached": MagicMock(return_value="C:\\fake\\path.exe"),
"cmd.run_all": mock_cmd_run_all,
},
):
win_pkg.install(
name="firebox",
version="3.03",
extra_install_flags="-e True -test_flag True",
)
assert "-e True -test_flag True" in str(mock_cmd_run_all.call_args[0])
def test_pkg_install_verify_ssl_false():
"""
test pkg.install using verify_ssl=False
"""
ret_reg = {"Nullsoft Install System": "3.03"}
# The 2nd time it's run, pkg.list_pkgs uses with stringify
se_list_pkgs = [{"nsis": ["3.03"]}, {"nsis": "3.02"}]
mock_cache_file = MagicMock(return_value="C:\\fake\\path.exe")
with patch.object(win_pkg, "list_pkgs", side_effect=se_list_pkgs), patch.object(
win_pkg, "_get_reg_software", return_value=ret_reg
), patch.dict(
win_pkg.__salt__,
{
"cmd.run_all": MagicMock(return_value={"retcode": 0}),
"cp.cache_file": mock_cache_file,
"cp.is_cached": MagicMock(return_value=False),
"cp.hash_file": MagicMock(return_value={"hsum": "abc123"}),
},
):
expected = {"nsis": {"new": "3.02", "old": "3.03"}}
result = win_pkg.install(name="nsis", version="3.02", verify_ssl=False)
mock_cache_file.assert_called_once_with(
"http://download.sourceforge.net/project/nsis/NSIS%203/3.02/nsis-3.02-setup.exe",
saltenv="base",
source_hash="abc123",
verify_ssl=False,
use_etag=True,
)
assert expected == result
def test_pkg_install_single_pkg():
"""
test pkg.install pkg with extra_install_flags
"""
ret__get_package_info = {
"3.03": {
"uninstaller": "%program.exe",
"reboot": False,
"msiexec": False,
"installer": "runme.exe",
"uninstall_flags": "/S",
"locale": "en_US",
"install_flags": "/s",
"full_name": "Firebox 3.03 (x86 en-US)",
}
}
mock_cmd_run_all = MagicMock(return_value={"retcode": 0})
with patch.object(
salt.utils.data, "is_true", MagicMock(return_value=True)
), patch.object(
win_pkg, "_get_package_info", MagicMock(return_value=ret__get_package_info)
), patch.dict(
win_pkg.__salt__,
{
"pkg_resource.parse_targets": MagicMock(
return_value=[{"firebox": "3.03"}, None]
),
"cp.is_cached": MagicMock(return_value="C:\\fake\\path.exe"),
"cmd.run_all": mock_cmd_run_all,
},
):
win_pkg.install(
pkgs=["firebox"],
version="3.03",
extra_install_flags="-e True -test_flag True",
)
assert "-e True -test_flag True" in str(mock_cmd_run_all.call_args[0])
def test_pkg_install_log_message(caplog):
"""
test pkg.install pkg with extra_install_flags
"""
ret__get_package_info = {
"3.03": {
"uninstaller": "%program.exe",
"reboot": False,
"msiexec": False,
"installer": "runme.exe",
"uninstall_flags": "/S",
"locale": "en_US",
"install_flags": "/s",
"full_name": "Firebox 3.03 (x86 en-US)",
}
}
mock_cmd_run_all = MagicMock(return_value={"retcode": 0})
with patch.object(
salt.utils.data, "is_true", MagicMock(return_value=True)
), patch.object(
win_pkg, "_get_package_info", MagicMock(return_value=ret__get_package_info)
), patch.dict(
win_pkg.__salt__,
{
"pkg_resource.parse_targets": MagicMock(
return_value=[{"firebox": "3.03"}, None]
),
"cp.is_cached": MagicMock(return_value="C:\\fake\\path.exe"),
"cmd.run_all": mock_cmd_run_all,
},
), caplog.at_level(
logging.DEBUG
):
win_pkg.install(
pkgs=["firebox"],
version="3.03",
extra_install_flags="-e True -test_flag True",
)
assert (
'PKG : cmd: C:\\WINDOWS\\system32\\cmd.exe /c "runme.exe" /s -e '
"True -test_flag True"
).lower() in [x.lower() for x in caplog.messages]
assert "PKG : pwd: ".lower() in [x.lower() for x in caplog.messages]
assert "PKG : retcode: 0" in caplog.messages
def test_pkg_install_multiple_pkgs():
"""
test pkg.install pkg with extra_install_flags
"""
ret__get_package_info = {
"3.03": {
"uninstaller": "%program.exe",
"reboot": False,
"msiexec": False,
"installer": "runme.exe",
"uninstall_flags": "/S",
"locale": "en_US",
"install_flags": "/s",
"full_name": "Firebox 3.03 (x86 en-US)",
}
}
mock_cmd_run_all = MagicMock(return_value={"retcode": 0})
with patch.object(
salt.utils.data, "is_true", MagicMock(return_value=True)
), patch.object(
win_pkg, "_get_package_info", MagicMock(return_value=ret__get_package_info)
), patch.dict(
win_pkg.__salt__,
{
"pkg_resource.parse_targets": MagicMock(
return_value=[{"firebox": "3.03", "got": "3.03"}, None]
),
"cp.is_cached": MagicMock(return_value="C:\\fake\\path.exe"),
"cmd.run_all": mock_cmd_run_all,
},
):
win_pkg.install(
pkgs=["firebox", "got"], extra_install_flags="-e True -test_flag True"
)
assert "-e True -test_flag True" not in str(mock_cmd_run_all.call_args[0])
def test_pkg_install_minion_error_https():
"""
Test pkg.install when cp.cache_file encounters a minion error
"""
ret__get_package_info = {
"3.03": {
"uninstaller": "%program.exe",
"reboot": False,
"msiexec": False,
"installer": "https://repo.test.com/runme.exe",
"uninstall_flags": "/S",
"locale": "en_US",
"install_flags": "/s",
"full_name": "Firebox 3.03 (x86 en-US)",
}
}
err_msg = (
"Error: [Errno 11001] getaddrinfo failed reading"
" https://repo.test.com/runme.exe"
)
mock_none = MagicMock(return_value=None)
mock_minion_error = MagicMock(side_effect=MinionError(err_msg))
mock_parse = MagicMock(return_value=[{"firebox": "3.03"}, None])
with patch.object(
salt.utils.data, "is_true", MagicMock(return_value=True)
), patch.object(
win_pkg, "_get_package_info", MagicMock(return_value=ret__get_package_info)
), patch.dict(
win_pkg.__salt__,
{
"pkg_resource.parse_targets": mock_parse,
"cp.is_cached": mock_none,
"cp.cache_file": mock_minion_error,
},
):
result = win_pkg.install(
name="firebox",
version="3.03",
)
expected = (
"Failed to cache https://repo.test.com/runme.exe\nError: [Errno 11001]"
" getaddrinfo failed reading https://repo.test.com/runme.exe"
)
assert result == expected
def test_pkg_install_minion_error_salt():
"""
Test pkg.install when cp.cache_file encounters a minion error
"""
ret__get_package_info = {
"3.03": {
"uninstaller": "%program.exe",
"reboot": False,
"msiexec": False,
"installer": "salt://software/runme.exe",
"uninstall_flags": "/S",
"locale": "en_US",
"install_flags": "/s",
"full_name": "Firebox 3.03 (x86 en-US)",
}
}
err_msg = "Error: [Errno 1] failed reading salt://software/runme.exe"
mock_none = MagicMock(return_value=None)
mock_minion_error = MagicMock(side_effect=MinionError(err_msg))
mock_parse = MagicMock(return_value=[{"firebox": "3.03"}, None])
with patch.object(
salt.utils.data, "is_true", MagicMock(return_value=True)
), patch.object(
win_pkg, "_get_package_info", MagicMock(return_value=ret__get_package_info)
), patch.dict(
win_pkg.__salt__,
{
"cp.cache_file": mock_minion_error,
"cp.is_cached": mock_none,
"cp.hash_file": MagicMock(return_value={"hsum": "abc123"}),
"pkg_resource.parse_targets": mock_parse,
},
):
result = win_pkg.install(
name="firebox",
version="3.03",
)
expected = (
"Failed to cache salt://software/runme.exe\n"
"Error: [Errno 1] failed reading salt://software/runme.exe"
)
assert result == expected
def test_pkg_install_minion_error_salt_cache_dir():
"""
Test pkg.install when cp.cache_dir encounters a minion error
"""
ret__get_package_info = {
"3.03": {
"uninstaller": "%program.exe",
"reboot": False,
"msiexec": False,
"installer": "salt://software/runme.exe",
"cache_dir": True,
"uninstall_flags": "/S",
"locale": "en_US",
"install_flags": "/s",
"full_name": "Firebox 3.03 (x86 en-US)",
}
}
err_msg = "Error: [Errno 1] failed reading salt://software"
mock_minion_error = MagicMock(side_effect=MinionError(err_msg))
with patch.object(
salt.utils.data, "is_true", MagicMock(return_value=True)
), patch.object(
win_pkg, "_get_package_info", MagicMock(return_value=ret__get_package_info)
), patch.dict(
win_pkg.__salt__,
{
"cp.cache_dir": mock_minion_error,
"cp.hash_file": MagicMock(return_value={"hsum": "abc123"}),
},
):
result = win_pkg.install(
name="firebox",
version="3.03",
)
expected = (
"Failed to cache salt://software\n"
"Error: [Errno 1] failed reading salt://software"
)
assert result == expected
def test_pkg_remove_log_message(caplog):
"""
test pkg.remove pkg logging
"""
ret__get_package_info = {
"3.03": {
"uninstaller": "%program.exe",
"reboot": False,
"msiexec": False,
"installer": "runme.exe",
"uninstall_flags": "/S",
"locale": "en_US",
"install_flags": "/s",
"full_name": "Firebox 3.03 (x86 en-US)",
}
}
mock_cmd_run_all = MagicMock(return_value={"retcode": 0})
se_list_pkgs = {"firebox": ["3.03"]}
with patch.object(win_pkg, "list_pkgs", return_value=se_list_pkgs), patch.object(
salt.utils.data, "is_true", MagicMock(return_value=True)
), patch.object(
win_pkg, "_get_package_info", MagicMock(return_value=ret__get_package_info)
), patch.dict(
win_pkg.__salt__,
{
"pkg_resource.parse_targets": MagicMock(
return_value=[{"firebox": "3.03"}, None]
),
"cp.is_cached": MagicMock(return_value="C:\\fake\\path.exe"),
"cmd.run_all": mock_cmd_run_all,
},
), caplog.at_level(
logging.DEBUG
):
win_pkg.remove(
pkgs=["firebox"],
)
assert (
'PKG : cmd: C:\\WINDOWS\\system32\\cmd.exe /c "%program.exe" /S'
).lower() in [x.lower() for x in caplog.messages]
assert "PKG : pwd: ".lower() in [x.lower() for x in caplog.messages]
assert "PKG : retcode: 0" in caplog.messages
def test_pkg_remove_minion_error_salt_cache_dir():
"""
Test pkg.remove when cp.cache_dir encounters a minion error
"""
ret__get_package_info = {
"3.03": {
"uninstaller": "salt://software/runme.exe",
"reboot": False,
"msiexec": False,
"installer": "salt://software/runme.exe",
"cache_dir": True,
"uninstall_flags": "/U /S",
"locale": "en_US",
"install_flags": "/s",
"full_name": "Firebox 3.03 (x86 en-US)",
}
}
err_msg = "Error: [Errno 1] failed reading salt://software"
mock_minion_error = MagicMock(side_effect=MinionError(err_msg))
mock_parse = MagicMock(return_value=[{"firebox": "3.03"}, None])
se_list_pkgs = {"firebox": ["3.03"]}
with patch.object(win_pkg, "list_pkgs", return_value=se_list_pkgs), patch.object(
salt.utils.data, "is_true", MagicMock(return_value=True)
), patch.object(
win_pkg, "_get_package_info", MagicMock(return_value=ret__get_package_info)
), patch.dict(
win_pkg.__salt__,
{
"cp.cache_dir": mock_minion_error,
"cp.hash_file": MagicMock(return_value={"hsum": "abc123"}),
"pkg_resource.parse_targets": mock_parse,
},
):
result = win_pkg.remove(name="firebox")
expected = (
"Failed to cache salt://software\n"
"Error: [Errno 1] failed reading salt://software"
)
assert result == expected
def test_pkg_remove_minion_error_salt():
"""
Test pkg.remove when cp.cache_file encounters a minion error
"""
ret__get_package_info = {
"3.03": {
"uninstaller": "salt://software/runme.exe",
"reboot": False,
"msiexec": False,
"installer": "salt://software/runme.exe",
"uninstall_flags": "/U /S",
"locale": "en_US",
"install_flags": "/s",
"full_name": "Firebox 3.03 (x86 en-US)",
}
}
err_msg = "Error: [Errno 1] failed reading salt://software/runme.exe"
mock_minion_error = MagicMock(side_effect=MinionError(err_msg))
mock_none = MagicMock(return_value=None)
mock_parse = MagicMock(return_value=[{"firebox": "3.03"}, None])
se_list_pkgs = {"firebox": ["3.03"]}
with patch.object(win_pkg, "list_pkgs", return_value=se_list_pkgs), patch.object(
salt.utils.data, "is_true", MagicMock(return_value=True)
), patch.object(
win_pkg, "_get_package_info", MagicMock(return_value=ret__get_package_info)
), patch.dict(
win_pkg.__salt__,
{
"cp.cache_file": mock_minion_error,
"cp.hash_file": MagicMock(return_value={"hsum": "abc123"}),
"cp.is_cached": mock_none,
"pkg_resource.parse_targets": mock_parse,
},
):
result = win_pkg.remove(name="firebox")
expected = (
"Failed to cache salt://software/runme.exe\n"
"Error: [Errno 1] failed reading salt://software/runme.exe"
)
assert result == expected
@pytest.mark.parametrize(
"v1,v2,expected",
(
("2.24.0", "2.23.0.windows.1", 1),
("2.23.0.windows.2", "2.23.0.windows.1", 1),
),
)
def test__reverse_cmp_pkg_versions(v1, v2, expected):
result = win_pkg._reverse_cmp_pkg_versions(v1, v2)
assert result == expected, "cmp({}, {}) should be {}, got {}".format(
v1, v2, expected, result
)