Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/68525.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix `pip.installed` state managed pre-release upgrades
1 change: 1 addition & 0 deletions requirements/pytest.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mock >= 3.0.0
packaging
# PyTest
docker >= 7.1.0; python_version >= '3.8'
docker < 7.1.0; python_version < '3.8'
Expand Down
6 changes: 5 additions & 1 deletion salt/modules/pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -1654,7 +1654,11 @@ def list_all_versions(
pip_version = version(bin_env=bin_env, cwd=cwd, user=user)
if salt.utils.versions.compare(ver1=pip_version, oper=">=", ver2="21.2"):
regex = re.compile(r"\s*Available versions: (.*)")
cmd.extend(["index", "versions", pkg])
# pre-release versions are not included by default
if any([include_alpha, include_beta, include_rc]):
cmd.extend(["index", "versions", "--pre", pkg])
else:
cmd.extend(["index", "versions", pkg])
else:
if salt.utils.versions.compare(ver1=pip_version, oper=">=", ver2="20.3"):
cmd.append("--use-deprecated=legacy-resolver")
Expand Down
94 changes: 94 additions & 0 deletions tests/pytests/functional/modules/test_pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
from contextlib import contextmanager

import pytest
from packaging.version import parse as parse_version

import salt.utils.platform
from salt.exceptions import CommandNotFoundError
from salt.modules import cmdmod
from salt.modules import pip as pipmod
from salt.modules.virtualenv_mod import KNOWN_BINARY_NAMES
from tests.support.helpers import VirtualEnv, patched_environ
from tests.support.mock import MagicMock

pytestmark = [
pytest.mark.slow_test,
Expand All @@ -20,6 +24,55 @@
]


def mock_pip_versions_cmds(*args, **kwargs):
"""Function to mock cmd.run_all for pip commands that get package versions but pass-thru other invocations.

Used by `test_list_available_packages_with_pre_releases_flags` because otherwise we must rely on the presence of
pre-release versions of a package in an uncontrolled package index.

This can be removed if there were an index that we could guarantee had pre-release versions of a package for any
caller.
"""

def _is_pip_versions_cmd(cmd):
pip_args = None
for i, arg in enumerate(cmd):
if arg == "pip" or arg.endswith("salt-pip"):
pip_args = cmd[i + 1 :]
if pip_args is None:
return False
if pip_args[0] == "--use-deprecated=legacy-resolver":
pip_args = pip_args[1:]
if (
pip_args[:2] == ["index", "versions"]
or pip_args[0] == "install"
and pip_args[1].endswith("==versions")
):
return True
return False

if _is_pip_versions_cmd(args[0]):
versions = "1.0.0, 1.0.1rc1, 1.0.2b1, 1.0.3.a1"
return {
"stdout": "\n".join(
[
f"Available versions: {versions}",
f"Could not find a version (from versions: {versions})",
]
)
}
return cmdmod.run_all(*args, **kwargs)


@pytest.fixture
def configure_loader_modules():
return {
pipmod: {
"__salt__": {"cmd.run_all": MagicMock(side_effect=mock_pip_versions_cmds)}
}
}


@pytest.fixture
def venv(tmp_path):
with VirtualEnv(venv_dir=tmp_path / "the-venv") as venv:
Expand Down Expand Up @@ -113,6 +166,47 @@ def test_list_available_packages_with_index_url(pip, pip_version, tmp_path):
assert available_versions


@pytest.mark.parametrize(
"pip_version",
(
pytest.param(
"pip==9.0.3",
marks=pytest.mark.skipif(
sys.version_info >= (3, 10),
reason="'pip==9.0.3' is not available on Py >= 3.10",
),
),
"pip<20.0",
"pip<21.0",
"pip>=21.0",
),
)
@pytest.mark.parametrize("include_alpha", (True, False))
@pytest.mark.parametrize("include_beta", (True, False))
@pytest.mark.parametrize("include_rc", (True, False))
def test_list_available_packages_with_pre_releases_flags(
venv, pip, pip_version, include_alpha, include_beta, include_rc
):
"""Tests that pre-release versions are returned when flags enable them.

Note: relies on the `configure_loader_modules` fixture to mock the versions we test with.
"""
venv.install("-U", pip_version)
versions = pipmod.list_all_versions(
"foo",
bin_env=str(venv.venv_bin_dir),
include_alpha=include_alpha,
include_beta=include_beta,
include_rc=include_rc,
)

has_prerelease = any(map(lambda v: v.is_prerelease, map(parse_version, versions)))
if any([include_alpha, include_beta, include_rc]):
assert has_prerelease
else:
assert not has_prerelease


def test_issue_2087_missing_pip(venv, pip, modules):
# Let's remove the pip binary
pip_bin = venv.venv_bin_dir / "pip"
Expand Down