Enable python34 tests for nova/tests/unit/objects/test*.py

All tests in nova/tests/unit/objects/test*.py now run with
python3.4 tox target.

* Fix imports in limits.py and urlmap.py
* Add a list() as keys()/values() return iterator in
  availability_zones.py, flavors.py and instance_numa_topology.py
* contextlib.nested is not present in python3, so whip up an
  alternative using ExitStack(). Directly using ExitStack() wont
  work for us.
* Add a few assertJsonEqual in the test cases
* Ensure fingerprinting generates the same exact value in both
  python27 and python34

Blueprint nova-python3
Change-Id: I848c48475189c4b4ad8151e14509020ae7d110a4
This commit is contained in:
Davanum Srinivas 2015-06-02 17:53:35 -04:00 committed by Davanum Srinivas (dims)
parent e4e16e9077
commit 9f698dc296
10 changed files with 108 additions and 36 deletions

View File

@ -33,13 +33,13 @@ process (each process will have its own rate limiting counter).
import collections import collections
import copy import copy
import httplib
import math import math
import re import re
import time import time
from oslo_serialization import jsonutils from oslo_serialization import jsonutils
from oslo_utils import importutils from oslo_utils import importutils
from six.moves import http_client as httplib
import webob.dec import webob.dec
import webob.exc import webob.exc

View File

@ -14,10 +14,15 @@
# under the License. # under the License.
import re import re
import urllib2
from oslo_log import log as logging from oslo_log import log as logging
import paste.urlmap import paste.urlmap
import six
if six.PY3:
from urllib import request as urllib2
else:
import urllib2
from nova.api.openstack import wsgi from nova.api.openstack import wsgi

View File

@ -71,7 +71,7 @@ def _build_metadata_by_host(aggregates, hosts=None):
for host in aggregate.hosts: for host in aggregate.hosts:
if hosts and host not in hosts: if hosts and host not in hosts:
continue continue
metadata[host].add(aggregate.metadata.values()[0]) metadata[host].add(list(aggregate.metadata.values())[0])
return metadata return metadata

View File

@ -352,7 +352,7 @@ def delete_flavor_info(metadata, *prefixes):
# NUMA-related ones that we need to avoid an uglier alternative. This # NUMA-related ones that we need to avoid an uglier alternative. This
# should be replaced by a general split-out of flavor information from # should be replaced by a general split-out of flavor information from
# system_metadata very soon. # system_metadata very soon.
for key in metadata.keys(): for key in list(metadata.keys()):
for prefix in prefixes: for prefix in prefixes:
if key.startswith('%sinstance_type_extra_' % prefix): if key.startswith('%sinstance_type_extra_' % prefix):
del metadata[key] del metadata[key]

View File

@ -91,7 +91,7 @@ class InstanceNUMACell(base.NovaObject,
if threads == 1: if threads == 1:
threads = 0 threads = 0
return map(set, zip(*[iter(cpu_list)] * threads)) return list(map(set, zip(*[iter(cpu_list)] * threads)))
@property @property
def cpu_pinning_requested(self): def cpu_pinning_requested(self):

View File

@ -20,6 +20,8 @@ Allows overriding of flags for use of fakes, and some black magic for
inline callbacks. inline callbacks.
""" """
import contextlib
import datetime import datetime
import eventlet import eventlet
eventlet.monkey_patch(os=False) eventlet.monkey_patch(os=False)
@ -67,6 +69,14 @@ objects.register_all()
_TRUE_VALUES = ('True', 'true', '1', 'yes') _TRUE_VALUES = ('True', 'true', '1', 'yes')
if six.PY3:
@contextlib.contextmanager
def nested(*contexts):
with contextlib.ExitStack() as stack:
yield [stack.enter_context(c) for c in contexts]
else:
nested = contextlib.nested
class SampleNetworks(fixtures.Fixture): class SampleNetworks(fixtures.Fixture):
@ -285,24 +295,25 @@ class TestCase(testtools.TestCase):
observed = jsonutils.loads(observed) observed = jsonutils.loads(observed)
def sort(what): def sort(what):
return sorted(what, def get_key(item):
key=lambda x: str(x) if isinstance( if isinstance(item, (datetime.datetime, set)):
x, set) or isinstance(x, return str(item)
datetime.datetime) else x) if six.PY3 and isinstance(item, dict):
return str(sort(list(six.iterkeys(item)) +
list(six.itervalues(item))))
return str(item) if six.PY3 else item
return sorted(what, key=get_key)
def inner(expected, observed): def inner(expected, observed):
if isinstance(expected, dict) and isinstance(observed, dict): if isinstance(expected, dict) and isinstance(observed, dict):
self.assertEqual(len(expected), len(observed)) self.assertEqual(len(expected), len(observed))
expected_keys = sorted(expected) expected_keys = sorted(expected)
observed_keys = sorted(expected) observed_keys = sorted(observed)
self.assertEqual(expected_keys, observed_keys) self.assertEqual(expected_keys, observed_keys)
expected_values_iter = iter(sort(expected.values())) for key in list(six.iterkeys(expected)):
observed_values_iter = iter(sort(observed.values())) inner(expected[key], observed[key])
for i in range(len(expected)):
inner(next(expected_values_iter),
next(observed_values_iter))
elif (isinstance(expected, (list, tuple, set)) and elif (isinstance(expected, (list, tuple, set)) and
isinstance(observed, (list, tuple, set))): isinstance(observed, (list, tuple, set))):
self.assertEqual(len(expected), len(observed)) self.assertEqual(len(expected), len(observed))

View File

@ -12,8 +12,6 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import contextlib
import mock import mock
import six import six
@ -53,7 +51,7 @@ class _TestBlockDeviceMappingObject(object):
self.flags(enable=False, group='cells') self.flags(enable=False, group='cells')
fake_bdm = self.fake_bdm() fake_bdm = self.fake_bdm()
with contextlib.nested( with test.nested(
mock.patch.object( mock.patch.object(
db, 'block_device_mapping_update', return_value=fake_bdm), db, 'block_device_mapping_update', return_value=fake_bdm),
mock.patch.object( mock.patch.object(
@ -149,7 +147,7 @@ class _TestBlockDeviceMappingObject(object):
values['device_name'] = device_name values['device_name'] = device_name
fake_bdm = fake_block_device.FakeDbBlockDeviceDict(values) fake_bdm = fake_block_device.FakeDbBlockDeviceDict(values)
with contextlib.nested( with test.nested(
mock.patch.object( mock.patch.object(
db, 'block_device_mapping_create', return_value=fake_bdm), db, 'block_device_mapping_create', return_value=fake_bdm),
mock.patch.object( mock.patch.object(
@ -247,7 +245,7 @@ class _TestBlockDeviceMappingObject(object):
self.flags(enable=True, cell_type=cell_type, group='cells') self.flags(enable=True, cell_type=cell_type, group='cells')
else: else:
self.flags(enable=False, group='cells') self.flags(enable=False, group='cells')
with contextlib.nested( with test.nested(
mock.patch.object(db, 'block_device_mapping_destroy'), mock.patch.object(db, 'block_device_mapping_destroy'),
mock.patch.object(cells_rpcapi.CellsAPI, 'bdm_destroy_at_top') mock.patch.object(cells_rpcapi.CellsAPI, 'bdm_destroy_at_top')
) as (bdm_del, cells_destroy): ) as (bdm_del, cells_destroy):

View File

@ -131,7 +131,7 @@ class _TestInstanceObject(object):
exp_cols.remove('pci_requests') exp_cols.remove('pci_requests')
exp_cols.remove('vcpu_model') exp_cols.remove('vcpu_model')
exp_cols.remove('ec2_ids') exp_cols.remove('ec2_ids')
exp_cols = filter(lambda x: 'flavor' not in x, exp_cols) exp_cols = list(filter(lambda x: 'flavor' not in x, exp_cols))
exp_cols.extend(['extra', 'extra.numa_topology', 'extra.pci_requests', exp_cols.extend(['extra', 'extra.numa_topology', 'extra.pci_requests',
'extra.flavor', 'extra.vcpu_model']) 'extra.flavor', 'extra.vcpu_model'])
@ -473,7 +473,7 @@ class _TestInstanceObject(object):
actual_args = mock_update.call_args actual_args = mock_update.call_args
self.assertEqual(self.context, actual_args[0][0]) self.assertEqual(self.context, actual_args[0][0])
self.assertEqual(inst.uuid, actual_args[0][1]) self.assertEqual(inst.uuid, actual_args[0][1])
self.assertEqual(actual_args[0][2].keys(), ['vcpu_model']) self.assertEqual(list(actual_args[0][2].keys()), ['vcpu_model'])
self.assertJsonEqual(jsonutils.dumps( self.assertJsonEqual(jsonutils.dumps(
test_vcpu_model.fake_vcpumodel.obj_to_primitive()), test_vcpu_model.fake_vcpumodel.obj_to_primitive()),
actual_args[0][2]['vcpu_model']) actual_args[0][2]['vcpu_model'])
@ -1003,9 +1003,9 @@ class _TestInstanceObject(object):
expected = {} expected = {}
for key in unicode_attributes: for key in unicode_attributes:
inst[key] = u'\u2603' inst[key] = u'\u2603'
expected[key] = '?' expected[key] = b'?'
primitive = inst.obj_to_primitive(target_version='1.6') primitive = inst.obj_to_primitive(target_version='1.6')
self.assertEqual(expected, primitive['nova_object.data']) self.assertJsonEqual(expected, primitive['nova_object.data'])
self.assertEqual('1.6', primitive['nova_object.version']) self.assertEqual('1.6', primitive['nova_object.version'])
def test_compat_pci_devices(self): def test_compat_pci_devices(self):
@ -1652,7 +1652,7 @@ class _TestInstanceListObject(object):
inst_list._context = self.context inst_list._context = self.context
inst_list.objects = insts inst_list.objects = insts
faulty = inst_list.fill_faults() faulty = inst_list.fill_faults()
self.assertEqual(faulty, ['uuid1']) self.assertEqual(list(faulty), ['uuid1'])
self.assertEqual(inst_list[0].fault.message, self.assertEqual(inst_list[0].fault.message,
db_faults['uuid1'][0]['message']) db_faults['uuid1'][0]['message'])
self.assertIsNone(inst_list[1].fault) self.assertIsNone(inst_list[1].fault)

View File

@ -498,8 +498,8 @@ class _TestObject(object):
error = None error = None
try: try:
base.NovaObject.obj_class_from_name('MyObj', '1.25') base.NovaObject.obj_class_from_name('MyObj', '1.25')
except exception.IncompatibleObjectVersion as error: except exception.IncompatibleObjectVersion as ex:
pass error = ex
self.assertIsNotNone(error) self.assertIsNotNone(error)
self.assertEqual('1.6', error.kwargs['supported']) self.assertEqual('1.6', error.kwargs['supported'])
@ -646,7 +646,7 @@ class _TestObject(object):
myobj_fields = (['foo', 'bar', 'missing', myobj_fields = (['foo', 'bar', 'missing',
'readonly', 'rel_object', 'readonly', 'rel_object',
'rel_objects', 'mutable_default'] + 'rel_objects', 'mutable_default'] +
base_fields) list(base_fields))
myobj3_fields = ['new_field'] myobj3_fields = ['new_field']
self.assertTrue(issubclass(TestSubclassedObject, MyObj)) self.assertTrue(issubclass(TestSubclassedObject, MyObj))
self.assertEqual(len(myobj_fields), len(MyObj.fields)) self.assertEqual(len(myobj_fields), len(MyObj.fields))
@ -1192,11 +1192,19 @@ object_relationships = {
class TestObjectVersions(test.NoDBTestCase): class TestObjectVersions(test.NoDBTestCase):
@staticmethod
def _is_method(thing):
# NOTE(dims): In Python3, The concept of 'unbound methods' has
# been removed from the language. When referencing a method
# as a class attribute, you now get a plain function object.
# so let's check for both
return inspect.isfunction(thing) or inspect.ismethod(thing)
def _find_remotable_method(self, cls, thing, parent_was_remotable=False): def _find_remotable_method(self, cls, thing, parent_was_remotable=False):
"""Follow a chain of remotable things down to the original function.""" """Follow a chain of remotable things down to the original function."""
if isinstance(thing, classmethod): if isinstance(thing, classmethod):
return self._find_remotable_method(cls, thing.__get__(None, cls)) return self._find_remotable_method(cls, thing.__get__(None, cls))
elif inspect.ismethod(thing) and hasattr(thing, 'remotable'): elif self._is_method(thing) and hasattr(thing, 'remotable'):
return self._find_remotable_method(cls, thing.original_fn, return self._find_remotable_method(cls, thing.original_fn,
parent_was_remotable=True) parent_was_remotable=True)
elif parent_was_remotable: elif parent_was_remotable:
@ -1210,12 +1218,12 @@ class TestObjectVersions(test.NoDBTestCase):
def _get_fingerprint(self, obj_name): def _get_fingerprint(self, obj_name):
obj_classes = base.NovaObjectRegistry.obj_classes() obj_classes = base.NovaObjectRegistry.obj_classes()
obj_class = obj_classes[obj_name][0] obj_class = obj_classes[obj_name][0]
fields = obj_class.fields.items() fields = list(obj_class.fields.items())
fields.sort() fields.sort()
methods = [] methods = []
for name in dir(obj_class): for name in dir(obj_class):
thing = getattr(obj_class, name) thing = getattr(obj_class, name)
if inspect.ismethod(thing) or isinstance(thing, classmethod): if self._is_method(thing) or isinstance(thing, classmethod):
method = self._find_remotable_method(obj_class, thing) method = self._find_remotable_method(obj_class, thing)
if method: if method:
methods.append((name, inspect.getargspec(method))) methods.append((name, inspect.getargspec(method)))
@ -1231,14 +1239,26 @@ class TestObjectVersions(test.NoDBTestCase):
sorted(obj_class.child_versions.items()))) sorted(obj_class.child_versions.items())))
else: else:
relevant_data = (fields, methods) relevant_data = (fields, methods)
fingerprint = '%s-%s' % (obj_class.VERSION, relevant_data = repr(relevant_data)
hashlib.md5(str(relevant_data)).hexdigest()) if six.PY3:
relevant_data = relevant_data.encode('utf-8')
fingerprint = '%s-%s' % (
obj_class.VERSION, hashlib.md5(relevant_data).hexdigest())
return fingerprint return fingerprint
def test_find_remotable_method(self):
class MyObject(object):
@base.remotable
def my_method(self):
return 'Hello World!'
thing = self._find_remotable_method(MyObject,
getattr(MyObject, 'my_method'))
self.assertIsNotNone(thing)
def test_versions(self): def test_versions(self):
fingerprints = {} fingerprints = {}
obj_classes = base.NovaObjectRegistry.obj_classes() obj_classes = base.NovaObjectRegistry.obj_classes()
for obj_name in obj_classes: for obj_name in sorted(obj_classes, key=lambda x: x[0]):
fingerprints[obj_name] = self._get_fingerprint(obj_name) fingerprints[obj_name] = self._get_fingerprint(obj_name)
if os.getenv('GENERATE_HASHES'): if os.getenv('GENERATE_HASHES'):

40
tox.ini
View File

@ -44,7 +44,45 @@ commands =
find . -type f -name "*.pyc" -delete find . -type f -name "*.pyc" -delete
python -m testtools.run \ python -m testtools.run \
nova.tests.unit.db.test_db_api \ nova.tests.unit.db.test_db_api \
nova.tests.unit.test_versions nova.tests.unit.test_versions \
nova.tests.unit.objects.test_agent \
nova.tests.unit.objects.test_aggregate \
nova.tests.unit.objects.test_bandwidth_usage \
nova.tests.unit.objects.test_block_device \
nova.tests.unit.objects.test_cell_mapping \
nova.tests.unit.objects.test_compute_node \
nova.tests.unit.objects.test_dns_domain \
nova.tests.unit.objects.test_ec2 \
nova.tests.unit.objects.test_external_event \
nova.tests.unit.objects.test_fields \
nova.tests.unit.objects.test_fixed_ip \
nova.tests.unit.objects.test_flavor \
nova.tests.unit.objects.test_floating_ip \
nova.tests.unit.objects.test_hv_spec \
nova.tests.unit.objects.test_instance \
nova.tests.unit.objects.test_instance_action \
nova.tests.unit.objects.test_instance_fault \
nova.tests.unit.objects.test_instance_group \
nova.tests.unit.objects.test_instance_info_cache \
nova.tests.unit.objects.test_instance_mapping \
nova.tests.unit.objects.test_instance_numa_topology \
nova.tests.unit.objects.test_instance_pci_requests \
nova.tests.unit.objects.test_keypair \
nova.tests.unit.objects.test_migration \
nova.tests.unit.objects.test_network \
nova.tests.unit.objects.test_network_request \
nova.tests.unit.objects.test_numa \
nova.tests.unit.objects.test_objects \
nova.tests.unit.objects.test_pci_device \
nova.tests.unit.objects.test_pci_device_pool \
nova.tests.unit.objects.test_quotas \
nova.tests.unit.objects.test_security_group \
nova.tests.unit.objects.test_security_group_rule \
nova.tests.unit.objects.test_service \
nova.tests.unit.objects.test_tag \
nova.tests.unit.objects.test_vcpu_model \
nova.tests.unit.objects.test_virt_cpu_topology \
nova.tests.unit.objects.test_virtual_interface
[testenv:functional] [testenv:functional]
usedevelop = True usedevelop = True