requirements/openstack_requirements/tests/test_update.py
Jamie Lennox 5033a0f716 Allow maintaining extras from project
A project needs to be able to specify that it depends on a project
including extras for that project and still have it's version maintained
by global-requirements.

Currently if the requirements are seen to mismatch then the string from
global requirements is dropped in place of the project string, but this
drops any extras specified by project.

In the case that extras are different and the version needs updating add
a new combined requirement to the project with the original extras and
the new version.

Closes-Bug: #1567809
Change-Id: Ife48b7963a5e6706289f1b9a47cb95fae7f0bc22
2016-04-14 15:01:13 +10:00

438 lines
19 KiB
Python

# Copyright 2013 IBM Corp.
#
# 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.
from __future__ import print_function
import StringIO
import sys
import textwrap
import fixtures
import testscenarios
import testtools
from testtools import matchers
from openstack_requirements.cmds import update
from openstack_requirements import project
from openstack_requirements import requirement
from openstack_requirements.tests import common
load_tests = testscenarios.load_tests_apply_scenarios
class SmokeTest(testtools.TestCase):
def test_project(self):
global_env = self.useFixture(common.GlobalRequirements())
global_reqs = common._file_to_list(global_env.req_file)
# This is testing our test input data. Perhaps remove? (lifeless)
self.assertIn("jsonschema!=1.4.0,<2,>=1.0.0", global_reqs)
# And test the end to end call of update.py, UI and all.
self.project = self.useFixture(common.project_fixture)
capture = StringIO.StringIO()
update.main(['--source', global_env.root, self.project.root], capture)
reqs = common._file_to_list(self.project.req_file)
# ensure various updates take
self.assertIn("jsonschema!=1.4.0,<2,>=1.0.0", reqs)
self.assertIn("python-keystoneclient>=0.4.1", reqs)
self.assertIn("SQLAlchemy<=0.7.99,>=0.7", reqs)
expected = ('Version change for: greenlet, SQLAlchemy, eventlet, PasteDeploy, routes, WebOb, wsgiref, boto, kombu, pycrypto, python-swiftclient, lxml, jsonschema, python-keystoneclient\n' # noqa
"""Updated %(project)s/requirements.txt:
greenlet>=0.3.1 -> greenlet>=0.3.2
SQLAlchemy>=0.7.8,<=0.7.99 -> SQLAlchemy<=0.7.99,>=0.7
eventlet>=0.9.12 -> eventlet>=0.12.0
PasteDeploy -> PasteDeploy>=1.5.0
routes -> Routes>=1.12.3
WebOb>=1.2 -> WebOb<1.3,>=1.2.3
wsgiref -> wsgiref>=0.1.2
boto -> boto>=2.4.0
kombu>2.4.7 -> kombu>=2.4.8
pycrypto>=2.1.0alpha1 -> pycrypto>=2.6
python-swiftclient>=1.2,<2 -> python-swiftclient>=1.2
lxml -> lxml>=2.3
jsonschema -> jsonschema!=1.4.0,<2,>=1.0.0
python-keystoneclient>=0.2.0 -> python-keystoneclient>=0.4.1
Version change for: mox, mox3, testrepository, testtools
Updated %(project)s/test-requirements.txt:
mox==0.5.3 -> mox>=0.5.3
mox3==0.7.3 -> mox3>=0.7.0
testrepository>=0.0.13 -> testrepository>=0.0.17
testtools>=0.9.27 -> testtools>=0.9.32
""") % dict(project=self.project.root)
self.assertEqual(expected, capture.getvalue())
class UpdateTest(testtools.TestCase):
def test_project(self):
reqs = common.project_file(
self.fail, common.project_project, 'requirements.txt')
# ensure various updates take
self.assertIn("jsonschema!=1.4.0,<2,>=1.0.0", reqs)
self.assertIn("python-keystoneclient>=0.4.1", reqs)
self.assertIn("SQLAlchemy<=0.7.99,>=0.7", reqs)
def test_requirements_header(self):
_REQS_HEADER = [
'# The order of packages is significant, because pip processes '
'them in the order',
'# of appearance. Changing the order has an impact on the overall '
'integration',
'# process, which may cause wedges in the gate later.',
]
reqs = common.project_file(
self.fail, common.project_project, 'requirements.txt')
self.assertEqual(_REQS_HEADER, reqs[:3])
def test_project_with_oslo(self):
reqs = common.project_file(
self.fail, common.oslo_project, 'requirements.txt')
oslo_tar = ("-f http://tarballs.openstack.org/oslo.config/"
"oslo.config-1.2.0a3.tar.gz#egg=oslo.config-1.2.0a3")
self.assertIn(oslo_tar, reqs)
def test_test_project(self):
reqs = common.project_file(
self.fail, common.project_project, 'test-requirements.txt')
self.assertIn("testtools>=0.9.32", reqs)
self.assertIn("testrepository>=0.0.17", reqs)
# make sure we didn't add something we shouldn't
self.assertNotIn("sphinxcontrib-pecanwsme>=0.2", reqs)
def test_install_setup(self):
setup_contents = common.project_file(
self.fail, common.project_project, 'setup.py', suffix='global')
self.assertIn("# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO"
" - DO NOT EDIT", setup_contents)
def test_no_install_setup(self):
actions = update._process_project(
common.oslo_project, common.global_reqs, None, None, None,
False)
for action in actions:
if type(action) is project.File:
self.assertNotEqual(action.filename, 'setup.py')
# These are tests which don't need to run the project update in advance
def test_requirement_not_in_global(self):
actions = update._process_project(
common.bad_project, common.global_reqs, None, None, None, False)
errors = [a for a in actions if type(a) is project.Error]
msg = u"'thisisnotarealdepedency' is not in global-requirements.txt"
self.assertEqual([project.Error(message=msg)], errors)
def test_requirement_not_in_global_non_fatal(self):
reqs = common.project_file(
self.fail, common.bad_project, 'requirements.txt',
non_std_reqs=True)
self.assertNotIn("thisisnotarealdependency", reqs)
def test_requirement_soft_update(self):
reqs = common.project_file(
self.fail, common.bad_project, 'requirements.txt',
softupdate=True)
self.assertIn("thisisnotarealdepedency", reqs)
# testing output
def test_non_verbose_output(self):
actions = update._process_project(
common.project_project, common.global_reqs, None, None, None,
False)
capture = StringIO.StringIO()
project.write(
common.project_project, actions, capture, False, True)
expected = ('Version change for: greenlet, SQLAlchemy, eventlet, PasteDeploy, routes, WebOb, wsgiref, boto, kombu, pycrypto, python-swiftclient, lxml, jsonschema, python-keystoneclient\n' # noqa
"""Updated %(project)s/requirements.txt:
greenlet>=0.3.1 -> greenlet>=0.3.2
SQLAlchemy>=0.7.8,<=0.7.99 -> SQLAlchemy<=0.7.99,>=0.7
eventlet>=0.9.12 -> eventlet>=0.12.0
PasteDeploy -> PasteDeploy>=1.5.0
routes -> Routes>=1.12.3
WebOb>=1.2 -> WebOb<1.3,>=1.2.3
wsgiref -> wsgiref>=0.1.2
boto -> boto>=2.4.0
kombu>2.4.7 -> kombu>=2.4.8
pycrypto>=2.1.0alpha1 -> pycrypto>=2.6
python-swiftclient>=1.2,<2 -> python-swiftclient>=1.2
lxml -> lxml>=2.3
jsonschema -> jsonschema!=1.4.0,<2,>=1.0.0
python-keystoneclient>=0.2.0 -> python-keystoneclient>=0.4.1
Version change for: mox, mox3, testrepository, testtools
Updated %(project)s/test-requirements.txt:
mox==0.5.3 -> mox>=0.5.3
mox3==0.7.3 -> mox3>=0.7.0
testrepository>=0.0.13 -> testrepository>=0.0.17
testtools>=0.9.27 -> testtools>=0.9.32
""") % dict(project=common.project_project['root'])
self.assertEqual(expected, capture.getvalue())
def test_verbose_output(self):
actions = update._process_project(
common.project_project, common.global_reqs, None, None, None,
False)
capture = StringIO.StringIO()
project.write(
common.project_project, actions, capture, True, True)
expected = ("""Syncing %(project)s/requirements.txt
Version change for: greenlet, SQLAlchemy, eventlet, PasteDeploy, routes, WebOb, wsgiref, boto, kombu, pycrypto, python-swiftclient, lxml, jsonschema, python-keystoneclient\n""" # noqa
"""Updated %(project)s/requirements.txt:
greenlet>=0.3.1 -> greenlet>=0.3.2
SQLAlchemy>=0.7.8,<=0.7.99 -> SQLAlchemy<=0.7.99,>=0.7
eventlet>=0.9.12 -> eventlet>=0.12.0
PasteDeploy -> PasteDeploy>=1.5.0
routes -> Routes>=1.12.3
WebOb>=1.2 -> WebOb<1.3,>=1.2.3
wsgiref -> wsgiref>=0.1.2
boto -> boto>=2.4.0
kombu>2.4.7 -> kombu>=2.4.8
pycrypto>=2.1.0alpha1 -> pycrypto>=2.6
python-swiftclient>=1.2,<2 -> python-swiftclient>=1.2
lxml -> lxml>=2.3
jsonschema -> jsonschema!=1.4.0,<2,>=1.0.0
python-keystoneclient>=0.2.0 -> python-keystoneclient>=0.4.1
Syncing %(project)s/test-requirements.txt
Version change for: mox, mox3, testrepository, testtools
Updated %(project)s/test-requirements.txt:
mox==0.5.3 -> mox>=0.5.3
mox3==0.7.3 -> mox3>=0.7.0
testrepository>=0.0.13 -> testrepository>=0.0.17
testtools>=0.9.27 -> testtools>=0.9.32
Syncing setup.py
""") % dict(project=common.project_project['root'])
self.assertEqual(expected, capture.getvalue())
class TestMain(testtools.TestCase):
def test_smoke(self):
def check_params(
root, source, suffix, softupdate, hacking, stdout, verbose,
non_std_reqs):
self.expectThat(root, matchers.Equals('/dev/zero'))
self.expectThat(source, matchers.Equals('/dev/null'))
self.expectThat(suffix, matchers.Equals(''))
self.expectThat(softupdate, matchers.Equals(None))
self.expectThat(hacking, matchers.Equals(None))
self.expectThat(stdout, matchers.Equals(sys.stdout))
self.expectThat(verbose, matchers.Equals(None))
self.expectThat(non_std_reqs, matchers.Equals(True))
with fixtures.EnvironmentVariable('NON_STANDARD_REQS', '1'):
update.main(
['--source', '/dev/null', '/dev/zero'], _worker=check_params)
def test_suffix(self):
def check_params(
root, source, suffix, softupdate, hacking, stdout, verbose,
non_std_reqs):
self.expectThat(suffix, matchers.Equals('global'))
update.main(['-o', 'global', '/dev/zero'], _worker=check_params)
class TestSyncRequirementsFile(testtools.TestCase):
def test_multiple_lines_in_global_one_in_project(self):
global_content = textwrap.dedent("""\
foo<2;python_version=='2.7'
foo>1;python_version!='2.7'
""")
project_content = textwrap.dedent("""\
foo
""")
global_reqs = requirement.parse(global_content)
project_reqs = list(requirement.to_reqs(project_content))
actions, reqs = update._sync_requirements_file(
global_reqs, project_reqs, 'f', False, False, False)
self.assertEqual(requirement.Requirements([
requirement.Requirement(
'foo', '', '<2', "python_version=='2.7'", ''),
requirement.Requirement(
'foo', '', '>1', "python_version!='2.7'", '')]),
reqs)
self.assertEqual(project.StdOut(
" foo "
"-> foo<2;python_version=='2.7'\n"), actions[2])
self.assertEqual(project.StdOut(
" "
"-> foo>1;python_version!='2.7'\n"), actions[3])
self.assertThat(actions, matchers.HasLength(4))
def test_multiple_lines_separated_in_project_nochange(self):
global_content = textwrap.dedent("""\
foo<2;python_version=='2.7'
foo>1;python_version!='2.7'
""")
project_content = textwrap.dedent("""\
foo<2;python_version=='2.7'
# mumbo gumbo
foo>1;python_version!='2.7'
""")
global_reqs = requirement.parse(global_content)
project_reqs = list(requirement.to_reqs(project_content))
actions, reqs = update._sync_requirements_file(
global_reqs, project_reqs, 'f', False, False, False)
self.assertEqual(requirement.Requirements([
requirement.Requirement(
'foo', '', '<2', "python_version=='2.7'", ''),
requirement.Requirement(
'foo', '', '>1', "python_version!='2.7'", ''),
requirement.Requirement(
'', '', '', '', "# mumbo gumbo")]),
reqs)
self.assertThat(actions, matchers.HasLength(0))
def test_multiple_lines_separated_in_project(self):
global_content = textwrap.dedent("""\
foo<2;python_version=='2.7'
foo>1;python_version!='2.7'
""")
project_content = textwrap.dedent("""\
foo<1.8;python_version=='2.7'
# mumbo gumbo
foo>0.9;python_version!='2.7'
""")
global_reqs = requirement.parse(global_content)
project_reqs = list(requirement.to_reqs(project_content))
actions, reqs = update._sync_requirements_file(
global_reqs, project_reqs, 'f', False, False, False)
self.assertEqual(requirement.Requirements([
requirement.Requirement(
'foo', '', '<2', "python_version=='2.7'", ''),
requirement.Requirement(
'foo', '', '>1', "python_version!='2.7'", ''),
requirement.Requirement(
'', '', '', '', "# mumbo gumbo")]),
reqs)
self.assertEqual(project.StdOut(
" foo<1.8;python_version=='2.7' -> "
"foo<2;python_version=='2.7'\n"), actions[2])
self.assertEqual(project.StdOut(
" foo>0.9;python_version!='2.7' -> "
"foo>1;python_version!='2.7'\n"), actions[3])
self.assertThat(actions, matchers.HasLength(4))
def test_multiple_lines_nochange(self):
global_content = textwrap.dedent("""\
foo<2;python_version=='2.7'
foo>1;python_version!='2.7'
""")
project_content = textwrap.dedent("""\
foo<2;python_version=='2.7'
foo>1;python_version!='2.7'
""")
global_reqs = requirement.parse(global_content)
project_reqs = list(requirement.to_reqs(project_content))
actions, reqs = update._sync_requirements_file(
global_reqs, project_reqs, 'f', False, False, False)
self.assertEqual(requirement.Requirements([
requirement.Requirement(
'foo', '', '<2', "python_version=='2.7'", ''),
requirement.Requirement(
'foo', '', '>1', "python_version!='2.7'", '')]),
reqs)
self.assertThat(actions, matchers.HasLength(0))
def test_single_global_multiple_in_project(self):
global_content = textwrap.dedent("""\
foo>1
""")
project_content = textwrap.dedent("""\
foo<2;python_version=='2.7'
foo>1;python_version!='2.7'
""")
global_reqs = requirement.parse(global_content)
project_reqs = list(requirement.to_reqs(project_content))
actions, reqs = update._sync_requirements_file(
global_reqs, project_reqs, 'f', False, False, False)
self.assertEqual(requirement.Requirements([
requirement.Requirement('foo', '', '>1', "", '')]),
reqs)
self.assertEqual(project.StdOut(
" foo<2;python_version=='2.7' -> foo>1\n"), actions[2])
self.assertEqual(project.StdOut(
" foo>1;python_version!='2.7' -> \n"), actions[3])
self.assertThat(actions, matchers.HasLength(4))
def test_unparseable_line(self):
global_content = textwrap.dedent("""\
foo
""")
project_content = textwrap.dedent("""\
foo
-e git://git.openstack.org/openstack/neutron.git#egg=neutron
""")
global_reqs = requirement.parse(global_content)
project_reqs = list(requirement.to_reqs(project_content))
actions, reqs = update._sync_requirements_file(
global_reqs, project_reqs, 'f', False, False, False)
n = '-e git://git.openstack.org/openstack/neutron.git#egg=neutron'
self.assertEqual(requirement.Requirements([
requirement.Requirement('foo', '', '', '', ''),
requirement.Requirement('', '', '', '', n)]),
reqs)
def test_extras_kept(self):
global_content = textwrap.dedent("""\
oslo.db>1.4.1
""")
project_content = textwrap.dedent("""\
oslo.db[fixture,mysql]>1.3
""")
global_reqs = requirement.parse(global_content)
project_reqs = list(requirement.to_reqs(project_content))
actions, reqs = update._sync_requirements_file(
global_reqs, project_reqs, 'f', False, False, False)
self.assertEqual(requirement.Requirements([
requirement.Requirement(
'oslo.db', '', '>1.4.1', '', '', ['fixture', 'mysql'])]),
reqs)
self.assertThat(actions, matchers.HasLength(3))
self.assertEqual(project.StdOut(
" oslo.db[fixture,mysql]>1.3 -> "
"oslo.db[fixture,mysql]>1.4.1\n"), actions[2])
class TestCopyRequires(testtools.TestCase):
def test_extras_no_change(self):
global_content = textwrap.dedent(u"""\
foo<2;python_version=='2.7' # BSD
foo>1;python_version!='2.7'
freddy
""")
setup_cfg = textwrap.dedent(u"""\
[metadata]
name = openstack.requirements
[extras]
test =
foo<2:python_version=='2.7' # BSD
foo>1:python_version!='2.7'
opt =
freddy
""")
proj = {}
proj['root'] = '/dev/null'
proj['requirements'] = {}
proj['setup.cfg'] = setup_cfg
global_reqs = requirement.parse(global_content)
actions = update._copy_requires(
u'', False, False, proj, global_reqs, False)
self.assertEqual([
project.Verbose('Syncing extra [opt]'),
project.Verbose('Syncing extra [test]'),
project.File('setup.cfg', setup_cfg)], actions)