diff --git a/openstack_requirements/cmds/validate.py b/openstack_requirements/cmds/validate.py index e049ea5adf..92c4fb56a9 100644 --- a/openstack_requirements/cmds/validate.py +++ b/openstack_requirements/cmds/validate.py @@ -61,6 +61,12 @@ def main(): print(msg) error_count += 1 + # Check requirements to satisfy policy. + print('\nChecking requirements on %s' % args.global_requirements) + for msg in requirement.check_reqs_bounds_policy(global_reqs): + print(msg) + error_count += 1 + # Check that all of the items in the global-requirements list # appear in exactly one of the constraints file or the blacklist. print('\nChecking %s' % args.blacklist) diff --git a/openstack_requirements/requirement.py b/openstack_requirements/requirement.py index 3634dcc7ab..42689acaf3 100644 --- a/openstack_requirements/requirement.py +++ b/openstack_requirements/requirement.py @@ -203,3 +203,36 @@ def to_reqs(content, permit_urls=False): yield None, content_line else: yield parse_line(req_line, permit_urls=permit_urls), content_line + + +def check_reqs_bounds_policy(global_reqs): + """Check that the global requirement version specifiers match the policy. + + The policy is defined as + * There needs to be exactly one lower bound (>=1.2 defined) + * There can be one or more excludes (!=1.2.1, !=1.2.2) + * TODO: Clarify (non-) existance of upper caps + """ + + for pkg_requirement in global_reqs.values(): + req = pkg_requirement[0][0] + if req.package: + _specifiers = packaging.specifiers.SpecifierSet(req.specifiers) + lower_bound = set() + for spec in _specifiers: + if spec.operator == '>=': + lower_bound.add(spec) + if len(lower_bound) < 1: + yield ('Requirement %s needs a >= specifier' % req.package) + elif len(lower_bound) > 1: + yield ('Requirement %s has multiple >= specifier' % + req.package) + else: + lower_bound = lower_bound.pop() + for spec in _specifiers: + if spec.operator == '!=': + if not lower_bound.contains(spec.version): + yield('Requirement %s has a !=%s specifier ' + 'that is not >=%s' % (req.package, + spec.version, + lower_bound.version)) diff --git a/openstack_requirements/tests/test_requirement.py b/openstack_requirements/tests/test_requirement.py index 80f8d5919a..ac22f113a8 100644 --- a/openstack_requirements/tests/test_requirement.py +++ b/openstack_requirements/tests/test_requirement.py @@ -194,3 +194,28 @@ class TestToDict(testtools.TestCase): req = requirement.Requirement('Foo_bar', '', '', '', '') self.assertEqual( {'foo-bar': [(req, '')]}, requirement.to_dict([(req, '')])) + + +class TestReqPolicy(testtools.TestCase): + + def test_requirements_policy_pass(self): + content = textwrap.dedent("""\ + cffi>=1.1.1,!=1.1.2 + other>=1.1.1 + """) + reqs = requirement.parse(content) + policy_check = [x for x in requirement.check_reqs_bounds_policy(reqs)] + self.assertEqual(len(policy_check), 0) + + def test_requirements_policy_fail(self): + content = textwrap.dedent("""\ + cffi>=1.1.1,!=1.1.0 + other>=1,>=2,!=1.1.0 + no_lower_bound + """) + reqs = requirement.parse(content) + self.assertEqual([ + 'Requirement cffi has a !=1.1.0 specifier that is not >=1.1.1', + 'Requirement no-lower-bound needs a >= specifier', + 'Requirement other has multiple >= specifier'], + sorted([x for x in requirement.check_reqs_bounds_policy(reqs)]))