diff --git a/hab/errors.py b/hab/errors.py index c7ce05e..e292686 100644 --- a/hab/errors.py +++ b/hab/errors.py @@ -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""" diff --git a/hab/parsers/distro.py b/hab/parsers/distro.py index 0c1c69d..0b26a3e 100644 --- a/hab/parsers/distro.py +++ b/hab/parsers/distro.py @@ -1,6 +1,7 @@ from packaging.requirements import Requirement from packaging.specifiers import SpecifierSet +from ..errors import InvalidRequirementError from .hab_base import HabBase @@ -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 diff --git a/hab/solvers.py b/hab/solvers.py index 10c7d6c..27c9bff 100644 --- a/hab/solvers.py +++ b/hab/solvers.py @@ -3,7 +3,7 @@ from packaging.requirements import Requirement -from .errors import MaxRedirectError +from .errors import InvalidRequirementError, MaxRedirectError logger = logging.getLogger(__name__) @@ -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. diff --git a/tests/test_resolver.py b/tests/test_resolver.py index 39b7593..194d84c 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -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 @@ -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. @@ -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", ( diff --git a/tests/test_solver.py b/tests/test_solver.py index e18b9b3..ac30b55 100644 --- a/tests/test_solver.py +++ b/tests/test_solver.py @@ -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 @@ -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()