Run tests in py34 environment

A lot of fixes to be compatible with python 3:
- fix encoding/decoding errors
- fix issues with comparison
- use `reload`, `reraise`, ext. modules from six
- use items() instead of iteritems()
- add a new file with py3 specific test requirements
- drop passing the arbitrary arguments to object.__new__ method.
  See bug [1] for more details.
- add a workaround to bug in `mock` library
- add py33 and py34 test environment to tox.ini

[1] http://bugs.python.org/issue1683368

Change-Id: I90936cb6b6eaaf4b5e1ce67732caec3c8bdc1cc2
This commit is contained in:
Victor Sergeyev 2014-12-30 13:50:27 +02:00
parent 1961523996
commit 70062322a2
50 changed files with 236 additions and 125 deletions

View File

@ -185,7 +185,7 @@ class DriversController(rest.RestController):
# choose to expose below it.
driver_dict = pecan.request.dbapi.get_active_driver_dict()
for name, hosts in driver_dict.iteritems():
for name, hosts in driver_dict.items():
if name == driver_name:
return Driver.convert_with_links(name, list(hosts))

View File

@ -34,10 +34,13 @@ JSONPATCH_EXCEPTIONS = (jsonpatch.JsonPatchException,
def validate_limit(limit):
if limit is not None and limit <= 0:
if limit is None:
return CONF.api.max_limit
if limit <= 0:
raise wsme.exc.ClientSideError(_("Limit must be positive"))
return min(CONF.api.max_limit, limit) or CONF.api.max_limit
return min(CONF.api.max_limit, limit)
def validate_sort_dir(sort_dir):

View File

@ -26,6 +26,7 @@ import json
from xml import etree as et
from oslo_log import log
import six
import webob
from ironic.common.i18n import _
@ -83,7 +84,11 @@ class ParsableErrorMiddleware(object):
+ '</error_message>']
state['headers'].append(('Content-Type', 'application/xml'))
else:
if six.PY3:
app_iter = [i.decode('utf-8') for i in app_iter]
body = [json.dumps({'error_message': '\n'.join(app_iter)})]
if six.PY3:
body = [item.encode('utf-8') for item in body]
state['headers'].append(('Content-Type', 'application/json'))
state['headers'].append(('Content-Length', str(len(body[0]))))
else:

View File

@ -190,6 +190,8 @@ def list_partitions(device):
output = utils.execute(
'parted', '-s', '-m', device, 'unit', 'MiB', 'print',
use_standard_locale=True)[0]
if isinstance(output, bytes):
output = output.decode("utf-8")
lines = [line for line in output.split('\n') if line.strip()][2:]
# Example of line: 1:1.00MiB:501MiB:500MiB:ext4::boot
fields = ('start', 'end', 'size', 'filesystem', 'flags')

View File

@ -80,7 +80,7 @@ class IronicException(Exception):
# kwargs doesn't match a variable in the message
# log the issue and the kwargs
LOG.exception(_LE('Exception in string format operation'))
for name, value in kwargs.iteritems():
for name, value in kwargs.items():
LOG.error("%s: %s" % (name, value))
if CONF.fatal_exception_format_errors:

View File

@ -25,6 +25,7 @@ from glanceclient import client
from glanceclient import exc as glance_exc
from oslo_config import cfg
import sendfile
import six
import six.moves.urllib.parse as urlparse
from ironic.common import exception
@ -146,7 +147,7 @@ class BaseImageService(object):
else:
new_exc = _translate_image_exception(
args[0], exc_value)
raise new_exc, None, exc_trace
six.reraise(type(new_exc), new_exc, exc_trace)
@check_image_service
def _detail(self, method='list', **kwargs):

View File

@ -151,7 +151,7 @@ def _get_api_server():
if not _GLANCE_API_SERVER:
_GLANCE_API_SERVER = _get_api_server_iterator()
return _GLANCE_API_SERVER.next()
return six.next(_GLANCE_API_SERVER)
def parse_image_ref(image_href):

View File

@ -18,6 +18,7 @@ import hashlib
import threading
from oslo_config import cfg
import six
from ironic.common import exception
from ironic.common.i18n import _
@ -105,6 +106,8 @@ class HashRing(object):
def _get_partition(self, data):
try:
if six.PY3 and data is not None:
data = data.encode('utf-8')
key_hash = hashlib.md5(data)
hashed_key = self._hash2int(key_hash)
position = bisect.bisect(self._partitions, hashed_key)
@ -180,7 +183,7 @@ class HashRingManager(object):
rings = {}
d2c = self.dbapi.get_active_driver_dict()
for driver_name, hosts in d2c.iteritems():
for driver_name, hosts in d2c.items():
rings[driver_name] = HashRing(hosts)
return rings

View File

@ -288,10 +288,10 @@ def sanitize_hostname(hostname):
if isinstance(hostname, six.text_type):
hostname = hostname.encode('latin-1', 'ignore')
hostname = re.sub('[ _]', '-', hostname)
hostname = re.sub('[^\w.-]+', '', hostname)
hostname = re.sub(b'[ _]', b'-', hostname)
hostname = re.sub(b'[^\w.-]+', b'', hostname)
hostname = hostname.lower()
hostname = hostname.strip('.-')
hostname = hostname.strip(b'.-')
return hostname

View File

@ -1751,7 +1751,7 @@ class ConductorManager(periodic_task.PeriodicTasks):
def get_vendor_passthru_metadata(route_dict):
d = {}
for method, metadata in route_dict.iteritems():
for method, metadata in route_dict.items():
# 'func' is the vendor method reference, ignore it
d[method] = {k: metadata[k] for k in metadata if k != 'func'}
return d

View File

@ -132,7 +132,12 @@ class BaseInterface(object):
# the conductor. We use __new__ instead of __init___
# to avoid breaking backwards compatibility with all the drivers.
# We want to return all steps, regardless of priority.
instance = super(BaseInterface, cls).__new__(cls, *args, **kwargs)
super_new = super(BaseInterface, cls).__new__
if super_new is object.__new__:
instance = super_new(cls)
else:
instance = super_new(cls, *args, **kwargs)
instance.clean_steps = []
for n, method in inspect.getmembers(instance, inspect.ismethod):
if getattr(method, '_is_clean_step', False):
@ -548,7 +553,11 @@ class VendorInterface(object):
"""
def __new__(cls, *args, **kwargs):
inst = super(VendorInterface, cls).__new__(cls, *args, **kwargs)
super_new = super(VendorInterface, cls).__new__
if super_new is object.__new__:
inst = super_new(cls)
else:
inst = super_new(cls, *args, **kwargs)
inst.vendor_routes = {}
inst.driver_routes = {}

View File

@ -151,7 +151,9 @@ def parse_driver_info(node):
for param in REQUIRED_PROPERTIES:
value = info.get(param)
if value:
d_info[param[4:]] = six.binary_type(value)
if not isinstance(value, six.binary_type):
value = value.encode()
d_info[param[4:]] = value
else:
missing_info.append(param)
@ -166,7 +168,9 @@ def parse_driver_info(node):
if protocol not in AMT_PROTOCOL_PORT_MAP:
raise exception.InvalidParameterValue(_("Invalid "
"protocol %s.") % protocol)
d_info[param[4:]] = six.binary_type(protocol)
if not isinstance(value, six.binary_type):
protocol = protocol.encode()
d_info[param[4:]] = protocol
return d_info

View File

@ -139,11 +139,11 @@ def make_persistent_password_file(path, password):
utils.delete_if_exists(path)
with open(path, 'wb') as file:
os.chmod(path, 0o600)
file.write(password)
file.write(password.encode())
return path
except Exception as e:
utils.delete_if_exists(path)
raise exception.PasswordFileFailedToCreate(error=str(e))
raise exception.PasswordFileFailedToCreate(error=e)
def get_shellinabox_console_url(port):

View File

@ -505,7 +505,7 @@ def _get_configdrive(configdrive, node_uuid):
data = configdrive
try:
data = six.StringIO(base64.b64decode(data))
data = six.BytesIO(base64.b64decode(data))
except TypeError:
error_msg = (_('Config drive for node %s is not base64 encoded '
'or the content is malformed.') % node_uuid)

View File

@ -26,6 +26,7 @@ import uuid
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_log import log as logging
import six
from ironic.common import exception
from ironic.common.glance_service import service_utils
@ -107,8 +108,9 @@ class ImageCache(object):
else:
# NOTE(vdrok): Doing conversion of href in case it's unicode
# string, UUID cannot be generated for unicode strings on python 2.
href_encoded = href.encode('utf-8') if six.PY2 else href
master_file_name = str(uuid.uuid5(uuid.NAMESPACE_URL,
href.encode('utf-8')))
href_encoded))
master_path = os.path.join(self.master_dir, master_file_name)
if CONF.parallel_image_downloads:
@ -281,7 +283,7 @@ class ImageCache(object):
"threshold %(expected)d"),
{'dir': self.master_dir, 'actual': total_size,
'expected': self._cache_size})
return max(amount, 0)
return max(amount, 0) if amount is not None else 0
def _find_candidates_for_deletion(master_dir):
@ -379,7 +381,7 @@ def cleanup(priority):
"""Decorator method for adding cleanup priority to a class."""
def _add_property_to_class_func(cls):
_cache_cleanup_list.append((priority, cls))
_cache_cleanup_list.sort(reverse=True)
_cache_cleanup_list.sort(reverse=True, key=lambda tuple_: tuple_[0])
return cls
return _add_property_to_class_func

View File

@ -380,7 +380,7 @@ def _exec_ipmitool(driver_info, command):
except processutils.ProcessExecutionError as e:
with excutils.save_and_reraise_exception() as ctxt:
err_list = [x for x in IPMITOOL_RETRYABLE_FAILURES
if x in e.message]
if x in e.args[0]]
if ((time.time() > end_time) or
(num_tries == 0) or
not err_list):

View File

@ -90,7 +90,7 @@ def parse_driver_info(node):
"Missing the following iRMC parameters in node's"
" driver_info: %s.") % missing_info)
req = {key: value for key, value in info.iteritems()
req = {key: value for key, value in info.items()
if key in REQUIRED_PROPERTIES}
# corresponding config names don't have 'irmc_' prefix
opt = {param: info.get(param, CONF.irmc.get(param[len('irmc_'):]))

View File

@ -277,7 +277,7 @@ def _cache_ramdisk_kernel(ctx, node, pxe_info):
os.path.join(pxe_utils.get_root_dir(), node.uuid))
LOG.debug("Fetching necessary kernel and ramdisk for node %s",
node.uuid)
deploy_utils.fetch_images(ctx, TFTPImageCache(), pxe_info.values(),
deploy_utils.fetch_images(ctx, TFTPImageCache(), list(pxe_info.values()),
CONF.force_raw_images)

View File

@ -579,8 +579,8 @@ class Management(base.ManagementInterface):
except seamicro_client_exception.ClientException as ex:
LOG.error(_LE("Seamicro set boot device failed for node "
"%(node)s with the following error: %(error)s"),
{'node': task.node.uuid, 'error': ex.message})
raise exception.IronicException(message=ex.message)
{'node': task.node.uuid, 'error': ex.args[0]})
raise exception.IronicException(message=ex.args[0])
def get_boot_device(self, task):
"""Get the current boot device for the task's node.

View File

@ -314,7 +314,7 @@ def _parse_driver_info(node):
# Only one credential may be set (avoids complexity around having
# precedence etc).
if len(filter(None, (password, key_filename, key_contents))) != 1:
if len([v for v in (password, key_filename, key_contents) if v]) != 1:
raise exception.InvalidParameterValue(_(
"SSHPowerDriver requires one and only one of password, "
"key_contents and key_filename to be set."))

View File

@ -54,7 +54,7 @@ def make_class_properties(cls):
for name, field in supercls.fields.items():
if name not in cls.fields:
cls.fields[name] = field
for name, typefn in cls.fields.iteritems():
for name, typefn in cls.fields.items():
def getter(self, name=name):
attrname = get_attrname(name)
@ -394,7 +394,7 @@ class IronicObject(object):
@property
def obj_fields(self):
return self.fields.keys() + self.obj_extra_fields
return list(self.fields) + self.obj_extra_fields
# dictish syntactic sugar
def iteritems(self):
@ -402,7 +402,7 @@ class IronicObject(object):
NOTE(danms): May be removed in the future.
"""
for name in self.fields.keys() + self.obj_extra_fields:
for name in list(self.fields) + self.obj_extra_fields:
if (hasattr(self, get_attrname(name)) or
name in self.obj_extra_fields):
yield name, getattr(self, name)

View File

@ -32,3 +32,14 @@ eventlet.monkey_patch(os=False)
# The code below enables nosetests to work with i18n _() blocks
import six.moves.builtins as __builtin__
setattr(__builtin__, '_', lambda x: x)
# NOTE(viktors): We can't use mock as third-party library in python 3.4 because
# of bug https://code.google.com/p/mock/issues/detail?id=234
# so let's use mock from standard library in python 3.x
import six
if six.PY3:
import sys
import unittest.mock
sys.modules['mock'] = unittest.mock

View File

@ -19,6 +19,7 @@ import json
import mock
from oslo import messaging
from oslo_config import cfg
import six
from webob import exc as webob_exc
from ironic.api.controllers import root
@ -137,7 +138,8 @@ class TestNoExceptionTracebackHook(base.FunctionalTest):
# rare thing (happens due to wrong deserialization settings etc.)
# we don't care about this garbage.
expected_msg = ("Remote error: %s %s"
% (test_exc_type, self.MSG_WITHOUT_TRACE) + "\n[u'")
% (test_exc_type, self.MSG_WITHOUT_TRACE)
+ ("\n[u'" if six.PY2 else "\n['"))
actual_msg = json.loads(response.json['error_message'])['faultstring']
self.assertEqual(expected_msg, actual_msg)

View File

@ -74,7 +74,7 @@ class FakeMemcache(object):
def remove_internal(values, internal):
# NOTE(yuriyz): internal attributes should not be posted, except uuid
int_attr = [attr.lstrip('/') for attr in internal if attr != '/uuid']
return dict([(k, v) for (k, v) in values.iteritems() if k not in int_attr])
return {k: v for (k, v) in values.items() if k not in int_attr}
def node_post_data(**kw):

View File

@ -45,10 +45,10 @@ class TestListDrivers(base.FunctionalTest):
expected = sorted([
{'name': self.d1, 'hosts': [self.h1]},
{'name': self.d2, 'hosts': [self.h1, self.h2]},
])
], key=lambda d: d['name'])
data = self.get_json('/drivers')
self.assertThat(data['drivers'], HasLength(2))
drivers = sorted(data['drivers'])
drivers = sorted(data['drivers'], key=lambda d: d['name'])
for i in range(len(expected)):
d = drivers[i]
self.assertEqual(expected[i]['name'], d['name'])

View File

@ -22,6 +22,7 @@ import mock
from oslo_config import cfg
from oslo_utils import timeutils
from oslo_utils import uuidutils
import six
from six.moves.urllib import parse as urlparse
from testtools.matchers import HasLength
from wsme import types as wtypes
@ -1197,6 +1198,8 @@ class TestPost(test_api_base.FunctionalTest):
is_async=True):
expected_status = 202 if is_async else 200
expected_return_value = json.dumps(return_value)
if six.PY3:
expected_return_value = expected_return_value.encode('utf-8')
node = obj_utils.create_test_node(self.context)
info = {'foo': 'bar'}
@ -1212,6 +1215,8 @@ class TestPost(test_api_base.FunctionalTest):
is_async=True):
expected_status = 202 if is_async else 200
expected_return_value = json.dumps(return_value)
if six.PY3:
expected_return_value = expected_return_value.encode('utf-8')
node = obj_utils.create_test_node(self.context, name='node-109')
info = {'foo': 'bar'}
@ -1276,9 +1281,7 @@ class TestPost(test_api_base.FunctionalTest):
with mock.patch.object(
rpcapi.ConductorAPI, 'vendor_passthru') as mock_vendor:
mock_vendor.side_effect = exception.UnsupportedDriverExtension(
{'driver': node.driver,
'node': uuid,
'extension': 'test'})
**{'driver': node.driver, 'node': uuid, 'extension': 'test'})
response = self.post_json('/nodes/%s/vendor_passthru/test' % uuid,
info, expect_errors=True)
mock_vendor.assert_called_once_with(
@ -1470,7 +1473,7 @@ class TestDelete(test_api_base.FunctionalTest):
mock_get.return_value = node
response = self.delete('/nodes/%s/maintenance' % node.uuid)
self.assertEqual(202, response.status_int)
self.assertEqual('', response.body)
self.assertEqual(b'', response.body)
self.assertEqual(False, node.maintenance)
self.assertEqual(None, node.maintenance_reason)
mock_get.assert_called_once_with(mock.ANY, node.uuid)
@ -1488,7 +1491,7 @@ class TestDelete(test_api_base.FunctionalTest):
response = self.delete('/nodes/%s/maintenance' % node.name,
headers={api_base.Version.string: "1.5"})
self.assertEqual(202, response.status_int)
self.assertEqual('', response.body)
self.assertEqual(b'', response.body)
self.assertEqual(False, node.maintenance)
self.assertEqual(None, node.maintenance_reason)
mock_get.assert_called_once_with(mock.ANY, node.name)
@ -1523,7 +1526,7 @@ class TestPut(test_api_base.FunctionalTest):
response = self.put_json('/nodes/%s/states/power' % self.node.uuid,
{'target': states.POWER_ON})
self.assertEqual(202, response.status_code)
self.assertEqual('', response.body)
self.assertEqual(b'', response.body)
self.mock_cnps.assert_called_once_with(mock.ANY,
self.node.uuid,
states.POWER_ON,
@ -1545,7 +1548,7 @@ class TestPut(test_api_base.FunctionalTest):
{'target': states.POWER_ON},
headers={api_base.Version.string: "1.5"})
self.assertEqual(202, response.status_code)
self.assertEqual('', response.body)
self.assertEqual(b'', response.body)
self.mock_cnps.assert_called_once_with(mock.ANY,
self.node.uuid,
states.POWER_ON,
@ -1577,7 +1580,7 @@ class TestPut(test_api_base.FunctionalTest):
ret = self.put_json('/nodes/%s/states/provision' % self.node.uuid,
{'target': states.ACTIVE})
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
self.mock_dnd.assert_called_once_with(
mock.ANY, self.node.uuid, False, None, 'test-topic')
# Check location header
@ -1597,7 +1600,7 @@ class TestPut(test_api_base.FunctionalTest):
{'target': states.ACTIVE},
headers={api_base.Version.string: "1.5"})
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
self.mock_dnd.assert_called_once_with(
mock.ANY, self.node.uuid, False, None, 'test-topic')
@ -1605,7 +1608,7 @@ class TestPut(test_api_base.FunctionalTest):
ret = self.put_json('/nodes/%s/states/provision' % self.node.uuid,
{'target': states.ACTIVE, 'configdrive': 'foo'})
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
self.mock_dnd.assert_called_once_with(
mock.ANY, self.node.uuid, False, 'foo', 'test-topic')
# Check location header
@ -1628,7 +1631,7 @@ class TestPut(test_api_base.FunctionalTest):
ret = self.put_json('/nodes/%s/states/provision' % node.uuid,
{'target': states.DELETED})
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
self.mock_dntd.assert_called_once_with(
mock.ANY, node.uuid, 'test-topic')
# Check location header
@ -1656,7 +1659,7 @@ class TestPut(test_api_base.FunctionalTest):
ret = self.put_json('/nodes/%s/states/provision' % node.uuid,
{'target': states.DELETED})
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
self.mock_dntd.assert_called_once_with(
mock.ANY, node.uuid, 'test-topic')
# Check location header
@ -1678,7 +1681,7 @@ class TestPut(test_api_base.FunctionalTest):
ret = self.put_json('/nodes/%s/states/provision' % node.uuid,
{'target': states.ACTIVE})
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
self.mock_dnd.assert_called_once_with(
mock.ANY, node.uuid, False, None, 'test-topic')
# Check location header
@ -1711,7 +1714,7 @@ class TestPut(test_api_base.FunctionalTest):
{'target': states.VERBS['provide']},
headers={api_base.Version.string: "1.4"})
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
mock_dpa.assert_called_once_with(mock.ANY, self.node.uuid,
states.VERBS['provide'],
'test-topic')
@ -1736,7 +1739,7 @@ class TestPut(test_api_base.FunctionalTest):
{'target': states.VERBS['manage']},
headers={api_base.Version.string: "1.4"})
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
mock_dpa.assert_called_once_with(mock.ANY, self.node.uuid,
states.VERBS['manage'],
'test-topic')
@ -1759,7 +1762,7 @@ class TestPut(test_api_base.FunctionalTest):
ret = self.put_json('/nodes/%s/states/console' % self.node.uuid,
{'enabled': "true"})
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
mock_scm.assert_called_once_with(mock.ANY, self.node.uuid,
True, 'test-topic')
# Check location header
@ -1781,7 +1784,7 @@ class TestPut(test_api_base.FunctionalTest):
{'enabled': "true"},
headers={api_base.Version.string: "1.5"})
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
mock_scm.assert_called_once_with(mock.ANY, self.node.uuid,
True, 'test-topic')
@ -1791,7 +1794,7 @@ class TestPut(test_api_base.FunctionalTest):
ret = self.put_json('/nodes/%s/states/console' % self.node.uuid,
{'enabled': "false"})
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
mock_scm.assert_called_once_with(mock.ANY, self.node.uuid,
False, 'test-topic')
# Check location header
@ -1846,7 +1849,7 @@ class TestPut(test_api_base.FunctionalTest):
ret = self.put_json('/nodes/%s/management/boot_device'
% self.node.uuid, {'boot_device': device})
self.assertEqual(204, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
mock_sbd.assert_called_once_with(mock.ANY, self.node.uuid,
device, persistent=False,
topic='test-topic')
@ -1858,7 +1861,7 @@ class TestPut(test_api_base.FunctionalTest):
% self.node.name, {'boot_device': device},
headers={api_base.Version.string: "1.5"})
self.assertEqual(204, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
mock_sbd.assert_called_once_with(mock.ANY, self.node.uuid,
device, persistent=False,
topic='test-topic')
@ -1883,7 +1886,7 @@ class TestPut(test_api_base.FunctionalTest):
ret = self.put_json('/nodes/%s/management/boot_device?persistent=True'
% self.node.uuid, {'boot_device': device})
self.assertEqual(204, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
mock_sbd.assert_called_once_with(mock.ANY, self.node.uuid,
device, persistent=True,
topic='test-topic')
@ -1912,7 +1915,7 @@ class TestPut(test_api_base.FunctionalTest):
ret = self.put_json('/nodes/%s/maintenance' % node_ident,
request_body, headers=headers)
self.assertEqual(202, ret.status_code)
self.assertEqual('', ret.body)
self.assertEqual(b'', ret.body)
self.assertEqual(True, self.node.maintenance)
self.assertEqual(reason, self.node.maintenance_reason)
mock_get.assert_called_once_with(mock.ANY, node_ident)

View File

@ -17,6 +17,7 @@
import mock
from oslo_utils import uuidutils
import six
import webtest
import wsme
from wsme import types as wtypes
@ -134,7 +135,7 @@ class TestJsonPatchType(base.TestCase):
'value': {'cat': 'meow'}}]
ret = self._patch_json(valid_patches, False)
self.assertEqual(200, ret.status_int)
self.assertEqual(sorted(valid_patches), sorted(ret.json))
self.assertItemsEqual(valid_patches, ret.json)
def test_cannot_update_internal_attr(self):
patch = [{'path': '/internal', 'op': 'replace', 'value': 'foo'}]
@ -255,7 +256,8 @@ class TestJsonType(base.TestCase):
vts = str(types.jsontype)
self.assertIn(str(wtypes.text), vts)
self.assertIn(str(int), vts)
self.assertIn(str(long), vts)
if six.PY2:
self.assertIn(str(long), vts)
self.assertIn(str(float), vts)
self.assertIn(str(types.BooleanType), vts)
self.assertIn(str(list), vts)

View File

@ -124,7 +124,7 @@ class TestCase(testtools.TestCase):
def config(self, **kw):
"""Override config options for a test."""
group = kw.pop('group', None)
for k, v in kw.iteritems():
for k, v in kw.items():
CONF.set_override(k, v, group)
def path_get(self, project_file=None):

View File

@ -1311,7 +1311,7 @@ class DoNodeDeployTearDownTestCase(_ServiceSetUpMixin,
self.assertRaises(exception.SwiftOperationError,
manager.do_node_deploy, task,
self.service.conductor.id,
configdrive='fake config drive')
configdrive=b'fake config drive')
node.refresh()
self.assertEqual(states.DEPLOYFAIL, node.provision_state)
self.assertEqual(states.ACTIVE, node.target_provision_state)
@ -3573,7 +3573,7 @@ class StoreConfigDriveTestCase(tests_base.TestCase):
group='conductor')
mock_swift.return_value.get_temp_url.return_value = 'http://1.2.3.4'
manager._store_configdrive(self.node, 'foo')
manager._store_configdrive(self.node, b'foo')
mock_swift.assert_called_once_with()
mock_swift.return_value.create_object.assert_called_once_with(

View File

@ -91,8 +91,8 @@ class DracClientTestCase(base.TestCase):
# assert the XML was merged
result_string = ElementTree.tostring(result)
self.assertIn('<item1>test1</item1>', result_string)
self.assertIn('<item2>test2</item2>', result_string)
self.assertIn(b'<item1>test1</item1>', result_string)
self.assertIn(b'<item2>test2</item2>', result_string)
mock_options = mock_client_pywsman.ClientOptions.return_value
mock_options.set_flags.assert_called_once_with(

View File

@ -20,6 +20,7 @@ import tempfile
import mock
from oslo_config import cfg
from oslo_utils import importutils
import six
from ironic.common import exception
from ironic.common import images
@ -35,6 +36,10 @@ from ironic.tests.objects import utils as obj_utils
ilo_client = importutils.try_import('proliantutils.ilo.client')
ilo_error = importutils.try_import('proliantutils.exception')
if six.PY3:
import io
file = io.BytesIO
CONF = cfg.CONF

View File

@ -19,6 +19,7 @@ import tempfile
import mock
from oslo_config import cfg
import six
from ironic.common import boot_devices
from ironic.common import exception
@ -44,6 +45,10 @@ from ironic.tests.db import utils as db_utils
from ironic.tests.objects import utils as obj_utils
if six.PY3:
import io
file = io.BytesIO
INFO_DICT = db_utils.get_test_ilo_info()
CONF = cfg.CONF

View File

@ -195,7 +195,7 @@ class ConsoleUtilsTestCase(db_base.DbTestCase):
'tempdir': tempfile.gettempdir(),
'node_uuid': self.info['uuid']}
password = ''.join([random.choice(string.ascii_letters)
for n in xrange(16)])
for n in range(16)])
console_utils.make_persistent_password_file(filepath, password)
# make sure file exists
self.assertTrue(os.path.exists(filepath))

View File

@ -46,7 +46,7 @@ from ironic.tests.db import base as db_base
from ironic.tests.db import utils as db_utils
from ironic.tests.objects import utils as obj_utils
_PXECONF_DEPLOY = """
_PXECONF_DEPLOY = b"""
default deploy
label deploy
@ -97,7 +97,7 @@ COM32 chain.c32
append mbr:0x12345678
"""
_IPXECONF_DEPLOY = """
_IPXECONF_DEPLOY = b"""
#!ipxe
dhcp
@ -166,7 +166,7 @@ append mbr:0x12345678
boot
"""
_UEFI_PXECONF_DEPLOY = """
_UEFI_PXECONF_DEPLOY = b"""
default=deploy
image=deploy_kernel
@ -1169,7 +1169,7 @@ class MakePartitionsTestCase(tests_base.TestCase):
self.ephemeral_mb, self.configdrive_mb)
parted_call = mock.call(*cmd, run_as_root=True, check_exit_code=[0])
mock_exc.assert_has_calls(parted_call)
mock_exc.assert_has_calls([parted_call])
@mock.patch.object(utils, 'get_dev_block_size')

View File

@ -23,6 +23,7 @@ import uuid
import mock
from oslo_utils import uuidutils
import six
from ironic.common import exception
from ironic.common import image_service
@ -131,8 +132,8 @@ class TestImageCacheFetch(base.TestCase):
def test_fetch_image_not_uuid(self, mock_download, mock_clean_up,
mock_fetch):
href = u'http://abc.com/ubuntu.qcow2'
href_converted = str(uuid.uuid5(uuid.NAMESPACE_URL,
href.encode('utf-8')))
href_encoded = href.encode('utf-8') if six.PY2 else href
href_converted = str(uuid.uuid5(uuid.NAMESPACE_URL, href_encoded))
master_path = os.path.join(self.master_dir, href_converted)
self.cache.fetch_image(href, self.dest_path)
self.assertFalse(mock_fetch.called)
@ -195,7 +196,7 @@ class TestImageCacheCleanUp(base.TestCase):
files = [os.path.join(self.master_dir, str(i))
for i in range(2)]
for filename in files:
open(filename, 'wb').write('X')
open(filename, 'wb').write(b'X')
new_current_time = time.time() + 900
with mock.patch.object(time, 'time', lambda: new_current_time):
self.cache.clean_up(amount=1)

View File

@ -223,8 +223,8 @@ class IPMINativeDriverTestCase(db_base.DbTestCase):
self.assertEqual(expected, self.driver.power.get_properties())
self.assertEqual(expected, self.driver.management.get_properties())
expected = ipminative.COMMON_PROPERTIES.keys()
expected += ipminative.CONSOLE_PROPERTIES.keys()
expected = list(ipminative.COMMON_PROPERTIES)
expected += list(ipminative.CONSOLE_PROPERTIES)
self.assertEqual(sorted(expected),
sorted(self.driver.console.get_properties().keys()))
self.assertEqual(sorted(expected),

View File

@ -251,7 +251,7 @@ class IPMIToolPrivateMethodTestCase(db_base.DbTestCase):
ValueError,
self._test__make_password_file,
mock_sleep, 12345, ValueError('we should fail'))
self.assertEqual(result.message, 'we should fail')
self.assertEqual('we should fail', result.args[0])
@mock.patch.object(tempfile, 'NamedTemporaryFile',
new=mock.MagicMock(side_effect=OSError('Test Error')))
@ -270,7 +270,7 @@ class IPMIToolPrivateMethodTestCase(db_base.DbTestCase):
result = self.assertRaises(
OverflowError,
self._test__make_password_file, mock_sleep, 12345)
self.assertEqual(result.message, 'Test Error')
self.assertEqual('Test Error', result.args[0])
def test__make_password_file_write_exception(self, mock_sleep):
# Test exception in _make_password_file for write()

View File

@ -400,8 +400,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
pxe._cache_ramdisk_kernel(self.context, self.node, fake_pxe_info)
mock_ensure_tree.assert_called_with(expected_path)
mock_fetch_image.assert_called_once_with(self.context, mock.ANY,
fake_pxe_info.values(), True)
mock_fetch_image.assert_called_once_with(
self.context, mock.ANY, list(fake_pxe_info.values()), True)
@mock.patch.object(pxe, 'TFTPImageCache', lambda: None)
@mock.patch.object(fileutils, 'ensure_tree')
@ -415,7 +415,7 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase):
pxe._cache_ramdisk_kernel(self.context, self.node, fake_pxe_info)
mock_ensure_tree.assert_called_with(expected_path)
mock_fetch_image.assert_called_once_with(self.context, mock.ANY,
fake_pxe_info.values(),
list(fake_pxe_info.values()),
True)
@mock.patch.object(pxe.LOG, 'error')

View File

@ -33,6 +33,7 @@ import sys
import mock
from oslo_utils import importutils
import six
from ironic.drivers.modules import ipmitool
from ironic.tests.drivers import third_party_driver_mock_specs as mock_specs
@ -54,7 +55,7 @@ if not seamicroclient:
# if anything has loaded the seamicro driver yet, reload it now that
# the external library has been mocked
if 'ironic.drivers.modules.seamicro' in sys.modules:
reload(sys.modules['ironic.drivers.modules.seamicro'])
six.moves.reload_module(sys.modules['ironic.drivers.modules.seamicro'])
# IPMITool driver checks the system for presence of 'ipmitool' binary during
# __init__. We bypass that check in order to run the unit tests, which do not
@ -81,7 +82,7 @@ if not pyghmi:
p.ipmi.command.boot_devices = {'pxe': 4}
if 'ironic.drivers.modules.ipminative' in sys.modules:
reload(sys.modules['ironic.drivers.modules.ipminative'])
six.moves.reload_module(sys.modules['ironic.drivers.modules.ipminative'])
proliantutils = importutils.try_import('proliantutils')
if not proliantutils:
@ -94,7 +95,7 @@ if not proliantutils:
command_exception = type('IloCommandNotSupportedError', (Exception,), {})
proliantutils.exception.IloCommandNotSupportedError = command_exception
if 'ironic.drivers.ilo' in sys.modules:
reload(sys.modules['ironic.drivers.ilo'])
six.moves.reload_module(sys.modules['ironic.drivers.ilo'])
# attempt to load the external 'pywsman' library, which is required by
@ -106,9 +107,9 @@ if not pywsman:
# Now that the external library has been mocked, if anything had already
# loaded any of the drivers, reload them.
if 'ironic.drivers.modules.drac' in sys.modules:
reload(sys.modules['ironic.drivers.modules.drac'])
six.moves.reload_module(sys.modules['ironic.drivers.modules.drac'])
if 'ironic.drivers.modules.amt' in sys.modules:
reload(sys.modules['ironic.drivers.modules.amt'])
six.moves.reload_module(sys.modules['ironic.drivers.modules.amt'])
# attempt to load the external 'iboot' library, which is required by
@ -122,7 +123,7 @@ if not iboot:
# if anything has loaded the iboot driver yet, reload it now that the
# external library has been mocked
if 'ironic.drivers.modules.iboot' in sys.modules:
reload(sys.modules['ironic.drivers.modules.iboot'])
six.moves.reload_module(sys.modules['ironic.drivers.modules.iboot'])
# attempt to load the external 'pysnmp' library, which is required by
@ -148,7 +149,7 @@ if not pysnmp:
# if anything has loaded the snmp driver yet, reload it now that the
# external library has been mocked
if 'ironic.drivers.modules.snmp' in sys.modules:
reload(sys.modules['ironic.drivers.modules.snmp'])
six.moves.reload_module(sys.modules['ironic.drivers.modules.snmp'])
# attempt to load the external 'scciclient' library, which is required by
@ -168,7 +169,7 @@ if not scciclient:
# if anything has loaded the iRMC driver yet, reload it now that the
# external library has been mocked
if 'ironic.drivers.modules.irmc' in sys.modules:
reload(sys.modules['ironic.drivers.modules.irmc'])
six.moves.reload_module(sys.modules['ironic.drivers.modules.irmc'])
pyremotevbox = importutils.try_import('pyremotevbox')
if not pyremotevbox:
@ -178,7 +179,8 @@ if not pyremotevbox:
pyremotevbox.exception.VmInWrongPowerState = Exception
sys.modules['pyremotevbox'] = pyremotevbox
if 'ironic.drivers.modules.virtualbox' in sys.modules:
reload(sys.modules['ironic.drivers.modules.virtualbox'])
six.moves.reload_module(
sys.modules['ironic.drivers.modules.virtualbox'])
ironic_discoverd = importutils.try_import('ironic_discoverd')
@ -189,4 +191,5 @@ if not ironic_discoverd:
sys.modules['ironic_discoverd'] = ironic_discoverd
sys.modules['ironic_discoverd.client'] = ironic_discoverd.client
if 'ironic.drivers.modules.discoverd' in sys.modules:
reload(sys.modules['ironic.drivers.modules.discoverd'])
six.moves.reload_module(
sys.modules['ironic.drivers.modules.discoverd'])

View File

@ -310,14 +310,10 @@ class _TestObject(object):
class Foo(base.IronicObject):
fields = {'foobar': int}
obj = Foo(self.context)
# NOTE(danms): Can't use assertRaisesRegexp() because of py26
raised = False
try:
obj.foobar
except NotImplementedError as ex:
raised = True
self.assertTrue(raised)
self.assertTrue('foobar' in str(ex))
self.assertRaisesRegexp(
NotImplementedError, "Cannot load 'foobar' in the base class",
getattr, obj, 'foobar')
def test_loaded_in_primitive(self):
obj = MyObj(self.context)
@ -467,7 +463,7 @@ class _TestObject(object):
self.assertRaises(AttributeError, obj.get, 'nothing', 3)
def test_object_inheritance(self):
base_fields = base.IronicObject.fields.keys()
base_fields = list(base.IronicObject.fields)
myobj_fields = ['foo', 'bar', 'missing'] + base_fields
myobj3_fields = ['new_field']
self.assertTrue(issubclass(TestSubclassedObject, MyObj))

View File

@ -20,8 +20,9 @@ from ironic.tests import base
class TestIronicException(base.TestCase):
def test____init__(self):
expected = '\xc3\xa9\xe0\xaf\xb2\xe0\xbe\x84'
expected = b'\xc3\xa9\xe0\xaf\xb2\xe0\xbe\x84'
if six.PY3:
expected = expected.decode('utf-8')
message = chr(233) + chr(0x0bf2) + chr(3972)
else:
message = unichr(233) + unichr(0x0bf2) + unichr(3972)

View File

@ -27,7 +27,6 @@ from oslo_context import context
from oslo_serialization import jsonutils
import testtools
from ironic.common import exception
from ironic.common.glance_service import base_image_service
from ironic.common.glance_service import service_utils
@ -381,8 +380,8 @@ class TestGlanceImageService(base.TestCase):
self.assertEqual(2, num_images)
# Check the image is marked as deleted.
num_images = reduce(lambda x, y: x + (0 if y['deleted'] else 1),
self.service.detail(), 0)
num_images = len([x for x in self.service.detail()
if not x['deleted']])
self.assertEqual(1, num_images)
def test_show_passes_through_to_client(self):
@ -499,8 +498,8 @@ class TestGlanceImageService(base.TestCase):
"""A client that returns a file url."""
(outfd, s_tmpfname) = tempfile.mkstemp(prefix='directURLsrc')
outf = os.fdopen(outfd, 'w')
inf = open('/dev/urandom', 'r')
outf = os.fdopen(outfd, 'wb')
inf = open('/dev/urandom', 'rb')
for i in range(10):
_data = inf.read(1024)
outf.write(_data)

View File

@ -16,6 +16,7 @@ import shutil
import mock
import requests
import sendfile
import six
import six.moves.builtins as __builtin__
from ironic.common import exception
@ -23,6 +24,10 @@ from ironic.common.glance_service.v1 import image_service as glance_v1_service
from ironic.common import image_service
from ironic.tests import base
if six.PY3:
import io
file = io.BytesIO
class HttpImageServiceTestCase(base.TestCase):
def setUp(self):
@ -167,6 +172,7 @@ class FileImageServiceTestCase(base.TestCase):
_validate_mock.return_value = self.href_path
stat_mock.return_value.st_dev = 'dev1'
file_mock = mock.MagicMock(spec=file)
file_mock.name = 'file'
input_mock = mock.MagicMock(spec=file)
open_mock.return_value = input_mock
self.service.download(self.href, file_mock)
@ -208,6 +214,7 @@ class FileImageServiceTestCase(base.TestCase):
_validate_mock.return_value = self.href_path
stat_mock.return_value.st_dev = 'dev1'
file_mock = mock.MagicMock(spec=file)
file_mock.name = 'file'
input_mock = mock.MagicMock(spec=file)
open_mock.return_value = input_mock
self.assertRaises(exception.ImageDownloadFailed,

View File

@ -22,6 +22,7 @@ import shutil
import mock
from oslo_concurrency import processutils
from oslo_config import cfg
import six
import six.moves.builtins as __builtin__
from ironic.common import exception
@ -32,6 +33,10 @@ from ironic.common import utils
from ironic.openstack.common import imageutils
from ironic.tests import base
if six.PY3:
import io
file = io.BytesIO
CONF = cfg.CONF

View File

@ -18,6 +18,7 @@ import os
import mock
from oslo_config import cfg
import six
from ironic.common import pxe_utils
from ironic.conductor import task_manager
@ -84,7 +85,7 @@ class TestPXEUtils(db_base.DbTestCase):
expected_template = open(
'ironic/tests/drivers/pxe_config.template').read().rstrip()
self.assertEqual(unicode(expected_template), rendered_template)
self.assertEqual(six.text_type(expected_template), rendered_template)
def test__build_pxe_config_with_agent(self):
@ -94,7 +95,7 @@ class TestPXEUtils(db_base.DbTestCase):
expected_template = open(
'ironic/tests/drivers/agent_pxe_config.template').read().rstrip()
self.assertEqual(unicode(expected_template), rendered_template)
self.assertEqual(six.text_type(expected_template), rendered_template)
def test__build_ipxe_config(self):
# NOTE(lucasagomes): iPXE is just an extension of the PXE driver,
@ -112,7 +113,7 @@ class TestPXEUtils(db_base.DbTestCase):
expected_template = open(
'ironic/tests/drivers/ipxe_config.template').read().rstrip()
self.assertEqual(unicode(expected_template), rendered_template)
self.assertEqual(six.text_type(expected_template), rendered_template)
def test__build_elilo_config(self):
pxe_opts = self.pxe_options
@ -124,7 +125,7 @@ class TestPXEUtils(db_base.DbTestCase):
'ironic/tests/drivers/elilo_efi_pxe_config.template'
).read().rstrip()
self.assertEqual(unicode(expected_template), rendered_template)
self.assertEqual(six.text_type(expected_template), rendered_template)
@mock.patch('ironic.common.utils.create_link_without_raise', autospec=True)
@mock.patch('ironic.common.utils.unlink_without_raise', autospec=True)
@ -346,8 +347,8 @@ class TestPXEUtils(db_base.DbTestCase):
{'opt_name': 'bootfile-name',
'opt_value': expected_boot_script_url}]
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertEqual(sorted(expected_info),
sorted(pxe_utils.dhcp_options_for_instance(task)))
self.assertItemsEqual(expected_info,
pxe_utils.dhcp_options_for_instance(task))
self.config(dhcp_provider='neutron', group='dhcp')
expected_boot_script_url = 'http://192.0.3.2:1234/boot.ipxe'
@ -360,8 +361,8 @@ class TestPXEUtils(db_base.DbTestCase):
{'opt_name': 'bootfile-name',
'opt_value': expected_boot_script_url}]
with task_manager.acquire(self.context, self.node.uuid) as task:
self.assertEqual(sorted(expected_info),
sorted(pxe_utils.dhcp_options_for_instance(task)))
self.assertItemsEqual(expected_info,
pxe_utils.dhcp_options_for_instance(task))
@mock.patch('ironic.common.utils.rmtree_without_raise', autospec=True)
@mock.patch('ironic.common.utils.unlink_without_raise', autospec=True)

View File

@ -14,6 +14,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import six
from ironic.common import states
from ironic.tests import base
@ -28,10 +30,10 @@ class StatesTest(base.TestCase):
This is specified in db/sqlalchemy/models.py
"""
for key, value in states.__dict__.iteritems():
for key, value in states.__dict__.items():
# Assumption: A state variable name is all UPPERCASE and contents
# are a string.
if key.upper() == key and isinstance(value, basestring):
if key.upper() == key and isinstance(value, six.string_types):
self.assertTrue(
(len(value) <= 15),
"Value for state: {} is greater than 15 characters".format(

View File

@ -12,11 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import __builtin__
import sys
import mock
from oslo_config import cfg
import six
from six.moves import builtins as __builtin__
from swiftclient import client as swift_client
from swiftclient import exceptions as swift_exception
from swiftclient import utils as swift_utils
@ -27,6 +28,10 @@ from ironic.tests import base
CONF = cfg.CONF
if six.PY3:
import io
file = io.BytesIO
@mock.patch.object(swift_client, 'Connection', autospec=True)
class SwiftTestCase(base.TestCase):
@ -46,7 +51,7 @@ class SwiftTestCase(base.TestCase):
# The constructor of SwiftAPI accepts arguments whose
# default values are values of some config options above. So reload
# the module to make sure the required values are set.
reload(sys.modules['ironic.common.swift'])
six.moves.reload_module(sys.modules['ironic.common.swift'])
def test___init__(self, connection_mock):
swift.SwiftAPI()

View File

@ -100,7 +100,7 @@ exit 1
self.assertRaises(processutils.ProcessExecutionError,
utils.execute,
tmpfilename, tmpfilename2, attempts=10,
process_input='foo',
process_input=b'foo',
delay_on_retry=False)
except OSError as e:
if e.errno == errno.EACCES:
@ -151,7 +151,7 @@ grep foo
try:
utils.execute(tmpfilename,
tmpfilename2,
process_input='foo',
process_input=b'foo',
attempts=2)
except OSError as e:
if e.errno == errno.EACCES:
@ -205,27 +205,27 @@ grep foo
class GenericUtilsTestCase(base.TestCase):
def test_hostname_unicode_sanitization(self):
hostname = u"\u7684.test.example.com"
self.assertEqual("test.example.com",
self.assertEqual(b"test.example.com",
utils.sanitize_hostname(hostname))
def test_hostname_sanitize_periods(self):
hostname = "....test.example.com..."
self.assertEqual("test.example.com",
self.assertEqual(b"test.example.com",
utils.sanitize_hostname(hostname))
def test_hostname_sanitize_dashes(self):
hostname = "----test.example.com---"
self.assertEqual("test.example.com",
self.assertEqual(b"test.example.com",
utils.sanitize_hostname(hostname))
def test_hostname_sanitize_characters(self):
hostname = "(#@&$!(@*--#&91)(__=+--test-host.example!!.com-0+"
self.assertEqual("91----test-host.example.com-0",
self.assertEqual(b"91----test-host.example.com-0",
utils.sanitize_hostname(hostname))
def test_hostname_translate(self):
hostname = "<}\x1fh\x10e\x08l\x02l\x05o\x12!{>"
self.assertEqual("hello", utils.sanitize_hostname(hostname))
self.assertEqual(b"hello", utils.sanitize_hostname(hostname))
def test_read_cached_file(self):
with mock.patch.object(
@ -273,8 +273,8 @@ class GenericUtilsTestCase(base.TestCase):
fake_context_manager.__enter__.assert_called_once_with()
def test_hash_file(self):
data = 'Mary had a little lamb, its fleece as white as snow'
flo = six.StringIO(data)
data = b'Mary had a little lamb, its fleece as white as snow'
flo = six.BytesIO(data)
h1 = utils.hash_file(flo)
h2 = hashlib.sha1(data).hexdigest()
self.assertEqual(h1, h2)

22
test-requirements-py3.txt Normal file
View File

@ -0,0 +1,22 @@
# 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.
hacking>=0.9.2,<0.10
coverage>=3.6
discover
fixtures>=0.3.14
mock>=1.0
Babel>=1.3
oslotest>=1.5.1 # Apache-2.0
psycopg2
PyMySQL>=0.6.2 # MIT License
python-ironicclient>=0.2.1
python-subunit>=0.0.18
testrepository>=0.0.18
testtools>=0.9.36,!=1.2.0
# Doc requirements
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
sphinxcontrib-pecanwsme>=0.8
oslosphinx>=2.5.0 # Apache-2.0

14
tox.ini
View File

@ -1,7 +1,7 @@
[tox]
minversion = 1.6
skipsdist = True
envlist = py27,pep8
envlist = py27,py34,pep8
[testenv]
usedevelop = True
@ -23,6 +23,18 @@ deps = {[testenv]deps}
pydot2
commands = {toxinidir}/tools/states_to_dot.py -f {toxinidir}/doc/source/images/states.png
[testenv:py34]
# NOTE(viktors): we must change default connection string for MySQL because
# we use a different DB connector (PyMySQL, not MySQLdb) in py3x
# env. So we should put new DB URLs in the env variable. This
# will allow to run tests, that require MySQL database,
# for example DB migration tests.
setenv =
{[testenv]setenv}
OS_TEST_DBAPI_ADMIN_CONNECTION=mysql+pymysql://openstack_citest:openstack_citest@localhost/;postgresql://openstack_citest:openstack_citest@localhost/postgres;sqlite://
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements-py3.txt
[testenv:pep8]
commands =
flake8 {posargs}