requirements/openstack_requirements/tests/test_check.py
Doug Hellmann 4bff2d214d remove optimization for values unchanged from the branch
When a requirement setting is changed, require that all requirements
follow the rules.

Without this change, it is possible to partially update the dependency
list for a project in a way that leaves some of the dependencies out
of compliance. With this change, all dependencies must be compliant in
order to update any of them.

Change-Id: I154245339a36618ac2e9a5922bc37121d44bca29
Signed-off-by: Doug Hellmann <doug@doughellmann.com>
2018-04-10 11:18:16 -04:00

583 lines
19 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import textwrap
from openstack_requirements import check
from openstack_requirements import requirement
import fixtures
import testtools
class TestIsReqInGlobalReqs(testtools.TestCase):
def setUp(self):
super(TestIsReqInGlobalReqs, self).setUp()
self._stdout_fixture = fixtures.StringStream('stdout')
self.stdout = self.useFixture(self._stdout_fixture).stream
self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.stdout))
self.global_reqs = check.get_global_reqs(textwrap.dedent("""
name>=1.2,!=1.4
withmarker>=1.5;python_version=='3.5'
withmarker>=1.2,!=1.4;python_version=='2.7'
"""))
print('global_reqs', self.global_reqs)
def test_match(self):
req = requirement.parse('name>=1.2,!=1.4')['name'][0][0]
self.assertTrue(
check._is_requirement_in_global_reqs(
req,
self.global_reqs['name'],
)
)
def test_match_with_markers(self):
req = requirement.parse(textwrap.dedent("""
withmarker>=1.5;python_version=='3.5'
"""))['withmarker'][0][0]
self.assertTrue(
check._is_requirement_in_global_reqs(
req,
self.global_reqs['withmarker'],
)
)
def test_name_mismatch(self):
req = requirement.parse('wrongname>=1.2,!=1.4')['wrongname'][0][0]
self.assertFalse(
check._is_requirement_in_global_reqs(
req,
self.global_reqs['name'],
)
)
def test_min_mismatch(self):
req = requirement.parse('name>=1.3,!=1.4')['name'][0][0]
self.assertTrue(
check._is_requirement_in_global_reqs(
req,
self.global_reqs['name'],
)
)
def test_extra_exclusion(self):
req = requirement.parse('name>=1.2,!=1.4,!=1.5')['name'][0][0]
self.assertFalse(
check._is_requirement_in_global_reqs(
req,
self.global_reqs['name'],
)
)
def test_missing_exclusion(self):
req = requirement.parse('name>=1.2')['name'][0][0]
self.assertTrue(
check._is_requirement_in_global_reqs(
req,
self.global_reqs['name'],
)
)
class TestGetExclusions(testtools.TestCase):
def test_none(self):
req = list(check.get_global_reqs('name>=1.2')['name'])[0]
self.assertEqual(
set(),
check._get_exclusions(req),
)
def test_one(self):
req = list(check.get_global_reqs('name>=1.2,!=1.4')['name'])[0]
self.assertEqual(
set(['!=1.4']),
check._get_exclusions(req),
)
def test_cap(self):
req = list(check.get_global_reqs('name>=1.2,!=1.4,<2.0')['name'])[0]
self.assertEqual(
set(['!=1.4', '<2.0']),
check._get_exclusions(req),
)
class TestValidateOne(testtools.TestCase):
def setUp(self):
super(TestValidateOne, self).setUp()
self._stdout_fixture = fixtures.StringStream('stdout')
self.stdout = self.useFixture(self._stdout_fixture).stream
self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.stdout))
def test_unchanged(self):
# If the line matches the value in the branch list everything
# is OK.
reqs = [
r
for r, line in requirement.parse('name>=1.2,!=1.4')['name']
]
global_reqs = check.get_global_reqs('name>=1.2,!=1.4')
self.assertFalse(
check._validate_one(
'name',
reqs=reqs,
blacklist=requirement.parse(''),
global_reqs=global_reqs,
)
)
def test_blacklisted(self):
# If the package is blacklisted, everything is OK.
reqs = [
r
for r, line in requirement.parse('name>=1.2,!=1.4')['name']
]
global_reqs = check.get_global_reqs('name>=1.2,!=1.4')
self.assertFalse(
check._validate_one(
'name',
reqs=reqs,
blacklist=requirement.parse('name'),
global_reqs=global_reqs,
)
)
def test_blacklisted_mismatch(self):
# If the package is blacklisted, it doesn't matter if the
# version matches.
reqs = [
r
for r, line in requirement.parse('name>=1.5')['name']
]
global_reqs = check.get_global_reqs('name>=1.2,!=1.4')
self.assertFalse(
check._validate_one(
'name',
reqs=reqs,
blacklist=requirement.parse('name'),
global_reqs=global_reqs,
)
)
def test_not_in_global_list(self):
# If the package is not in the global list, that is an error.
reqs = [
r
for r, line in requirement.parse('name>=1.2,!=1.4')['name']
]
global_reqs = check.get_global_reqs('')
self.assertTrue(
check._validate_one(
'name',
reqs=reqs,
blacklist=requirement.parse(''),
global_reqs=global_reqs,
)
)
def test_new_item_matches_global_list(self):
# If the new item matches the global list exactly that is OK.
reqs = [
r
for r, line in requirement.parse('name>=1.2,!=1.4')['name']
]
global_reqs = check.get_global_reqs('name>=1.2,!=1.4')
self.assertFalse(
check._validate_one(
'name',
reqs=reqs,
blacklist=requirement.parse(''),
global_reqs=global_reqs,
)
)
def test_new_item_lower_min(self):
# If the new item has a lower minimum value than the global
# list, that is OK.
reqs = [
r
for r, line in requirement.parse('name>=1.1,!=1.4')['name']
]
global_reqs = check.get_global_reqs('name>=1.2,!=1.4')
self.assertFalse(
check._validate_one(
'name',
reqs=reqs,
blacklist=requirement.parse(''),
global_reqs=global_reqs,
)
)
def test_new_item_extra_exclusion(self):
# If the new item includes an exclusion that is not present in
# the global list that is not OK.
reqs = [
r
for r, line in requirement.parse('name>=1.2,!=1.4,!=1.5')['name']
]
global_reqs = check.get_global_reqs('name>=1.2,!=1.4')
self.assertTrue(
check._validate_one(
'name',
reqs=reqs,
blacklist=requirement.parse(''),
global_reqs=global_reqs,
)
)
def test_new_item_missing_exclusion(self):
# If the new item does not include an exclusion that is
# present in the global list that is OK.
reqs = [
r
for r, line in requirement.parse('name>=1.2')['name']
]
global_reqs = check.get_global_reqs('name>=1.2,!=1.4')
self.assertFalse(
check._validate_one(
'name',
reqs=reqs,
blacklist=requirement.parse(''),
global_reqs=global_reqs,
)
)
def test_new_item_matches_global_list_with_extra(self):
# If the global list has multiple entries for an item with
# different "extra" specifiers, the values must all be in the
# requirements file.
r_content = textwrap.dedent("""
name>=1.5;python_version=='3.5'
name>=1.2,!=1.4;python_version=='2.6'
""")
reqs = [
r
for r, line in requirement.parse(r_content)['name']
]
global_reqs = check.get_global_reqs(textwrap.dedent("""
name>=1.5;python_version=='3.5'
name>=1.2,!=1.4;python_version=='2.6'
"""))
self.assertFalse(
check._validate_one(
'name',
reqs=reqs,
blacklist=requirement.parse(''),
global_reqs=global_reqs,
)
)
def test_new_item_missing_extra_line(self):
# If the global list has multiple entries for an item with
# different "extra" specifiers, the values must all be in the
# requirements file.
r_content = textwrap.dedent("""
name>=1.2,!=1.4;python_version=='2.6'
""")
reqs = [
r
for r, line in requirement.parse(r_content)['name']
]
global_reqs = check.get_global_reqs(textwrap.dedent("""
name>=1.5;python_version=='3.5'
name>=1.2,!=1.4;python_version=='2.6'
"""))
self.assertTrue(
check._validate_one(
'name',
reqs=reqs,
blacklist=requirement.parse(''),
global_reqs=global_reqs,
)
)
def test_new_item_mismatches_global_list_with_extra(self):
# If the global list has multiple entries for an item with
# different "extra" specifiers, the values must all be in the
# requirements file.
r_content = textwrap.dedent("""
name>=1.5;python_version=='3.6'
name>=1.2,!=1.4;python_version=='2.6'
""")
reqs = [
r
for r, line in requirement.parse(r_content)['name']
]
global_reqs = check.get_global_reqs(textwrap.dedent("""
name>=1.5;python_version=='3.5'
name>=1.2,!=1.4;python_version=='2.6'
"""))
self.assertTrue(
check._validate_one(
'name',
reqs=reqs,
blacklist=requirement.parse(''),
global_reqs=global_reqs,
)
)
class TestValidateLowerConstraints(testtools.TestCase):
def setUp(self):
super(TestValidateLowerConstraints, self).setUp()
self._stdout_fixture = fixtures.StringStream('stdout')
self.stdout = self.useFixture(self._stdout_fixture).stream
self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.stdout))
def test_no_constraints_file(self):
constraints_content = None
project_data = {
'requirements': {'requirements.txt': 'name>=1.2,!=1.4'},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertFalse(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse(''),
)
)
def test_no_min(self):
constraints_content = textwrap.dedent("""
name==1.2
""")
project_data = {
'requirements': {'requirements.txt': 'name!=1.4'},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertFalse(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse(''),
)
)
def test_matches(self):
constraints_content = textwrap.dedent("""
name==1.2
""")
project_data = {
'requirements': {'requirements.txt': 'name>=1.2,!=1.4'},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertFalse(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse(''),
)
)
def test_not_constrained(self):
constraints_content = textwrap.dedent("""
""")
project_data = {
'requirements': {'requirements.txt': 'name>=1.2,!=1.4'},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertTrue(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse(''),
)
)
def test_mismatch_blacklisted(self):
constraints_content = textwrap.dedent("""
name==1.2
""")
project_data = {
'requirements': {'requirements.txt': 'name>=1.3,!=1.4'},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertFalse(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse('name'),
)
)
def test_lower_bound_lower(self):
constraints_content = textwrap.dedent("""
name==1.2
""")
project_data = {
'requirements': {'requirements.txt': 'name>=1.1,!=1.4'},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertTrue(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse(''),
)
)
def test_lower_bound_higher(self):
constraints_content = textwrap.dedent("""
name==1.2
""")
project_data = {
'requirements': {'requirements.txt': 'name>=1.3,!=1.4'},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertTrue(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse(''),
)
)
def test_constrained_version_excluded(self):
constraints_content = textwrap.dedent("""
name==1.2
""")
project_data = {
'requirements': {'requirements.txt': 'name>=1.1,!=1.2'},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertTrue(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse(''),
)
)
def test_constraints_with_markers(self):
constraints_content = textwrap.dedent("""
name==1.1;python_version=='2.7'
name==2.0;python_version=='3.5'
name==2.0;python_version=='3.6'
""")
project_data = {
'requirements': {
'requirements.txt': textwrap.dedent("""
name>=1.1,!=1.2;python_version=='2.7'
name>=2.0;python_version=='3.5'
name>=2.0;python_version=='3.6'
"""),
},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertFalse(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse(''),
)
)
def test_constraints_with_markers_missing_one_req(self):
constraints_content = textwrap.dedent("""
name==1.1;python_version=='2.7'
name==2.0;python_version=='3.5'
name==2.0;python_version=='3.6'
""")
project_data = {
'requirements': {
'requirements.txt': textwrap.dedent("""
name>=1.1,!=1.2;python_version=='2.7'
name>=2.0;python_version=='3.5'
"""),
},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertFalse(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse(''),
)
)
def test_constraints_with_markers_missing_one_marker(self):
constraints_content = textwrap.dedent("""
name==1.1;python_version=='2.7'
name==2.0;python_version=='3.5'
""")
project_data = {
'requirements': {
'requirements.txt': textwrap.dedent("""
name>=1.1,!=1.2;python_version=='2.7'
name>=2.0;python_version=='3.5'
name>=2.0;python_version=='3.6'
"""),
},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertTrue(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse(''),
)
)
def test_complex_marker_evaluation(self):
constraints_content = textwrap.dedent("""
name===0.8.0;python_version=='2.7'
name===1.0.0;python_version>='3.0'
""")
project_data = {
'requirements': {
'requirements.txt': textwrap.dedent("""
name>=0.8.0;python_version<'3.0' # BSD
name>=1.0.0;python_version>='3.0' # BSD
"""),
},
'lower-constraints.txt': constraints_content,
}
head_reqs = check.RequirementsList('testproj', project_data)
head_reqs.process(False)
self.assertFalse(
check.validate_lower_constraints(
req_list=head_reqs,
constraints=project_data['lower-constraints.txt'],
blacklist=requirement.parse(''),
)
)