Skip to content

Commit

Permalink
Raise descriptive error if a distro can't be found
Browse files Browse the repository at this point in the history
Consolidate solver error testing into tests_solvers.py.
  • Loading branch information
MHendricks committed Sep 29, 2023
1 parent 27084d5 commit 772dafa
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 36 deletions.
4 changes: 4 additions & 0 deletions hab/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ class MaxRedirectError(RequirementError):
"""The maximum number of redirects was reached without resolving successfully."""


class InvalidRequirementError(RequirementError):
"""Raised if unable to resolve a given requirement."""


class InvalidVersionError(LookupError):
"""Provides info on resolving why it was unable to generate a valid version number"""

Expand Down
3 changes: 2 additions & 1 deletion hab/parsers/distro.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from packaging.requirements import Requirement
from packaging.specifiers import SpecifierSet

from ..errors import InvalidRequirementError
from .hab_base import HabBase


Expand All @@ -13,7 +14,7 @@ def latest_version(self, specifier):
try:
version = max(versions)
except ValueError:
raise Exception(
raise InvalidRequirementError(
f'Unable to find a valid version for "{specifier}" in versions '
f'[{", ".join([str(v) for v in self.versions.keys()])}]'
) from None
Expand Down
12 changes: 10 additions & 2 deletions hab/solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from packaging.requirements import Requirement

from .errors import MaxRedirectError
from .errors import InvalidRequirementError, MaxRedirectError

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -135,9 +135,17 @@ def _resolve(
req = req.specifier & invalid.specifier

logger.debug("Checking requirement: {}".format(req))

# Attempt to find a version, raises a exception if no version was found
version = self.resolver.distros[name].latest_version(req)
try:
dist = self.resolver.distros[name]
except KeyError:
raise InvalidRequirementError(
f"Unable to find a distro for requirement: {req}"
) from None
version = dist.latest_version(req)
logger.debug("Found Version: {}".format(version.name))

if version.distros and version not in processed:
# Check if updated requirements have forced us to re-evaluate
# our requirements.
Expand Down
33 changes: 0 additions & 33 deletions tests/test_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from packaging.requirements import Requirement

from hab import NotSet, Resolver, Site, utils
from hab.errors import MaxRedirectError
from hab.solvers import Solver


Expand Down Expand Up @@ -393,26 +392,6 @@ def test_resolve_requirements_simple(resolver):
assert resolver.find_distro("the_dcc==1.2").name == "the_dcc==1.2"


def test_solver_errors(resolver):
"""Test that the correct errors are raised"""

# Check that if we exceed max_redirects a MaxRedirectError is raised
# Note: To have a stable test, the order of requirements matters. So this needs to
# use a list or OrderedDict to guarantee that the_dcc==1.2 requirements are
# processed before the_dcc_plugin_b which specifies the_dcc<1.2 forcing a redirect.
requirements = OrderedDict(
(
("the_dcc", Requirement("the_dcc")),
("the_dcc_plugin_b", Requirement("the_dcc_plugin_b==0.9")),
)
)

solver = Solver(requirements, resolver)
solver.max_redirects = 0
with pytest.raises(MaxRedirectError):
solver.resolve()


def test_resolve_requirements_recalculate(resolver):
"""The first pick "the_dcc==1.2" gets discarded by plugin_b. Make sure the correct
distros are picked.
Expand Down Expand Up @@ -506,18 +485,6 @@ def test_resolve_requirements_markers(resolver, platform, marker):
assert set(ret.keys()) == set(check)


def test_resolve_requirements_errors(resolver):
# This requirement is not possible because the_dcc_plugin_b requires the_dcc<1.2
requirements = {
Requirement("the_dcc>1.1"): None,
Requirement("the_dcc_plugin_b<1.0"): None,
}

# TODO: Use a custom exception not Exception
with pytest.raises(Exception):
resolver.resolve_requirements(requirements)


@pytest.mark.parametrize(
"forced,check",
(
Expand Down
56 changes: 56 additions & 0 deletions tests/test_solver.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from collections import OrderedDict

import pytest
from packaging.requirements import Requirement

from hab.errors import InvalidRequirementError, MaxRedirectError
from hab.solvers import Solver


Expand Down Expand Up @@ -31,3 +34,56 @@
def test_simplify_requirements(helpers, value, check):
ret = Solver.simplify_requirements(value)
helpers.assert_requirements_equal(ret, check)


@pytest.mark.parametrize(
"requirements,match",
(
(
{"no_existant_distro": Requirement("no_existant_distro")},
"Unable to find a distro for requirement: no_existant_distro",
),
# Testing marker output. Using Invalid so this test works on all platforms
(
{"no_exist": Requirement("no_exist;platform_system!='Invalid'")},
'Unable to find a distro for requirement: no_exist; platform_system != "Invalid"',
),
(
{"the_dcc": Requirement("the_dcc==0.0.0")},
r'Unable to find a valid version for "the_dcc==0.0.0" in versions \[.+\]',
),
(
# This requirement is not possible because the_dcc_plugin_b requires the_dcc<1.2
{
"the_dcc": Requirement("the_dcc>1.1"),
"the_dcc_plugin_b": Requirement("the_dcc_plugin_b<1.0"),
},
r'Unable to find a valid version for "the_dcc<1.2,>1.1" in versions \[.+\]',
),
),
)
def test_invalid_requirement_errors(resolver, requirements, match):
"""Test that the correct error is raised if an invalid or missing requirement
is specified."""
with pytest.raises(InvalidRequirementError, match=match):
resolver.resolve_requirements(requirements)


def test_solver_errors(resolver):
"""Test that the correct errors are raised"""

# Check that if we exceed max_redirects a MaxRedirectError is raised
# Note: To have a stable test, the order of requirements matters. So this needs to
# use a list or OrderedDict to guarantee that the_dcc==1.2 requirements are
# processed before the_dcc_plugin_b which specifies the_dcc<1.2 forcing a redirect.
requirements = OrderedDict(
(
("the_dcc", Requirement("the_dcc")),
("the_dcc_plugin_b", Requirement("the_dcc_plugin_b==0.9")),
)
)

solver = Solver(requirements, resolver)
solver.max_redirects = 0
with pytest.raises(MaxRedirectError, match="Redirect limit of 0 reached"):
solver.resolve()

0 comments on commit 772dafa

Please sign in to comment.