From d7dddd19da79602f3961f8ca4054de6fb0f4b2c8 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 7 Oct 2015 17:38:12 +0200 Subject: [PATCH] Port WSGI tests to Python 3 * Replace dict.keys()[0] with list(data.keys())[0]. On Python 3, dict.keys() now returns a view which is not indexable. * Skip SSL tests on Python 3. Tests hang for an unknown reason, they must be fixed later. * Fix unit tests: HTTP body type is bytes, not Unicode. * Debug.print_generator(): on Python 3, write into sys.stdout.buffer instead of sys.stdout, because HTTP body type is bytes not Unicode. * ResponseObject: encode serializer output to UTF-8 if it's Unicode. * tox.ini: add the following tests to Python 3.4 - cinder.tests.unit.api.openstack.test_wsgi - cinder.tests.unit.wsgi Note: Ignore pylint error E1101 on sys.stdout.buffer. pylint on Python 2 complains that the buffer attribute doesn't exist, whereas the code is only executed on Python 3 and the attribute exists on Python 3. Related-Bug: #1505103 Partial-Implements: blueprint cinder-python3 Change-Id: I0db0e04010e41be71192a2e4db13829114ad6eef --- cinder/api/openstack/wsgi.py | 9 +++++--- cinder/tests/unit/api/openstack/test_wsgi.py | 22 +++++++++---------- .../tests/unit/wsgi/test_eventlet_server.py | 2 ++ cinder/tests/unit/wsgi/test_wsgi.py | 13 ++++++----- cinder/wsgi/common.py | 10 +++++++-- tests-py3.txt | 2 ++ 6 files changed, 36 insertions(+), 22 deletions(-) diff --git a/cinder/api/openstack/wsgi.py b/cinder/api/openstack/wsgi.py index 54af9801e..f2b3215bb 100644 --- a/cinder/api/openstack/wsgi.py +++ b/cinder/api/openstack/wsgi.py @@ -425,7 +425,7 @@ class XMLDictSerializer(DictSerializer): def default(self, data): # We expect data to contain a single key which is the XML root. - root_key = data.keys()[0] + root_key = list(data.keys())[0] doc = minidom.Document() node = self._to_xml_node(doc, self.metadata, root_key, data[root_key]) @@ -678,7 +678,10 @@ class ResponseObject(object): response.headers[hdr] = value response.headers['Content-Type'] = content_type if self.obj is not None: - response.body = serializer.serialize(self.obj) + body = serializer.serialize(self.obj) + if isinstance(body, six.text_type): + body = body.encode('utf-8') + response.body = body return response @@ -710,7 +713,7 @@ def action_peek_json(body): raise exception.MalformedRequestBody(reason=msg) # Return the action and the decoded body... - return decoded.keys()[0] + return list(decoded.keys())[0] def action_peek_xml(body): diff --git a/cinder/tests/unit/api/openstack/test_wsgi.py b/cinder/tests/unit/api/openstack/test_wsgi.py index e462bbf03..e156007ee 100644 --- a/cinder/tests/unit/api/openstack/test_wsgi.py +++ b/cinder/tests/unit/api/openstack/test_wsgi.py @@ -23,13 +23,13 @@ from cinder.tests.unit.api import fakes class RequestTest(test.TestCase): def test_content_type_missing(self): request = wsgi.Request.blank('/tests/123', method='POST') - request.body = "" + request.body = b"" self.assertIsNone(request.get_content_type()) def test_content_type_unsupported(self): request = wsgi.Request.blank('/tests/123', method='POST') request.headers["Content-Type"] = "text/html" - request.body = "asdf
" + request.body = b"asdf
" self.assertRaises(exception.InvalidContentType, request.get_content_type) @@ -206,10 +206,10 @@ class DictSerializerTest(test.TestCase): class XMLDictSerializerTest(test.TestCase): def test_xml(self): input_dict = dict(servers=dict(a=(2, 3))) - expected_xml = '(2,3)' + expected_xml = b'(2,3)' serializer = wsgi.XMLDictSerializer(xmlns="asdf") result = serializer.serialize(input_dict) - result = result.replace('\n', '').replace(' ', '') + result = result.replace(b'\n', b'').replace(b' ', b'') self.assertEqual(expected_xml, result) @@ -317,7 +317,7 @@ class ResourceTest(test.TestCase): req = webob.Request.blank('/tests') app = fakes.TestRouter(Controller()) response = req.get_response(app) - self.assertEqual('off', response.body) + self.assertEqual(b'off', response.body) self.assertEqual(200, response.status_int) def test_resource_not_authorized(self): @@ -443,7 +443,7 @@ class ResourceTest(test.TestCase): request = wsgi.Request.blank('/', method='POST') request.headers['Content-Type'] = 'application/none' - request.body = 'foo' + request.body = b'foo' content_type, body = resource.get_body(request) self.assertIsNone(content_type) @@ -458,7 +458,7 @@ class ResourceTest(test.TestCase): resource = wsgi.Resource(controller) request = wsgi.Request.blank('/', method='POST') - request.body = 'foo' + request.body = b'foo' content_type, body = resource.get_body(request) self.assertIsNone(content_type) @@ -474,7 +474,7 @@ class ResourceTest(test.TestCase): request = wsgi.Request.blank('/', method='POST') request.headers['Content-Type'] = 'application/json' - request.body = '' + request.body = b'' content_type, body = resource.get_body(request) self.assertIsNone(content_type) @@ -490,11 +490,11 @@ class ResourceTest(test.TestCase): request = wsgi.Request.blank('/', method='POST') request.headers['Content-Type'] = 'application/json' - request.body = 'foo' + request.body = b'foo' content_type, body = resource.get_body(request) self.assertEqual('application/json', content_type) - self.assertEqual('foo', body) + self.assertEqual(b'foo', body) def test_deserialize_badtype(self): class Controller(object): @@ -952,7 +952,7 @@ class ResponseObjectTest(test.TestCase): self.assertEqual('header1', response.headers['X-header1']) self.assertEqual('header2', response.headers['X-header2']) self.assertEqual(202, response.status_int) - self.assertEqual(mtype, response.body) + self.assertEqual(mtype, response.body.decode('utf-8')) class ValidBodyTest(test.TestCase): diff --git a/cinder/tests/unit/wsgi/test_eventlet_server.py b/cinder/tests/unit/wsgi/test_eventlet_server.py index 600cd7e68..f14a0bb25 100644 --- a/cinder/tests/unit/wsgi/test_eventlet_server.py +++ b/cinder/tests/unit/wsgi/test_eventlet_server.py @@ -205,6 +205,7 @@ class TestWSGIServer(test.TestCase): self.assertFalse(buf) server.stop() + @testtools.skipIf(six.PY3, "bug/1505103: test hangs on Python 3") def test_app_using_ssl(self): CONF.set_default("ssl_cert_file", os.path.join(TEST_VAR_DIR, 'certificate.crt')) @@ -229,6 +230,7 @@ class TestWSGIServer(test.TestCase): @testtools.skipIf(not _ipv6_configured(), "Test requires an IPV6 configured interface") + @testtools.skipIf(six.PY3, "bug/1505103: test hangs on Python 3") def test_app_using_ipv6_and_ssl(self): CONF.set_default("ssl_cert_file", os.path.join(TEST_VAR_DIR, 'certificate.crt')) diff --git a/cinder/tests/unit/wsgi/test_wsgi.py b/cinder/tests/unit/wsgi/test_wsgi.py index 63f055016..9444ace6b 100644 --- a/cinder/tests/unit/wsgi/test_wsgi.py +++ b/cinder/tests/unit/wsgi/test_wsgi.py @@ -39,12 +39,13 @@ class Test(test.TestCase): def __call__(self, environ, start_response): start_response("200", [("X-Test", "checking")]) - return ['Test result'] + return [b'Test result'] - with mock.patch('sys.stdout', new=six.StringIO()): + with mock.patch('sys.stdout', new=six.StringIO()) as mock_stdout: + mock_stdout.buffer = six.BytesIO() application = wsgi.Debug(Application()) result = webob.Request.blank('/').get_response(application) - self.assertEqual("Test result", result.body) + self.assertEqual(b"Test result", result.body) def test_router(self): @@ -53,7 +54,7 @@ class Test(test.TestCase): def __call__(self, environ, start_response): start_response("200", []) - return ['Router result'] + return [b'Router result'] class Router(wsgi.Router): """Test router.""" @@ -64,6 +65,6 @@ class Test(test.TestCase): super(Router, self).__init__(mapper) result = webob.Request.blank('/test').get_response(Router()) - self.assertEqual("Router result", result.body) + self.assertEqual(b"Router result", result.body) result = webob.Request.blank('/bad').get_response(Router()) - self.assertNotEqual("Router result", result.body) + self.assertNotEqual(b"Router result", result.body) diff --git a/cinder/wsgi/common.py b/cinder/wsgi/common.py index 2fd4e6abe..0c2db6bc7 100644 --- a/cinder/wsgi/common.py +++ b/cinder/wsgi/common.py @@ -20,6 +20,7 @@ from oslo_config import cfg from oslo_log import log as logging from paste import deploy import routes.middleware +import six import webob.dec import webob.exc @@ -193,8 +194,13 @@ class Debug(Middleware): """Iterator that prints the contents of a wrapper string.""" print(('*' * 40) + ' BODY') # noqa for part in app_iter: - sys.stdout.write(part) - sys.stdout.flush() + if six.PY3: + sys.stdout.flush() + sys.stdout.buffer.write(part) # pylint: disable=E1101 + sys.stdout.buffer.flush() # pylint: disable=E1101 + else: + sys.stdout.write(part) + sys.stdout.flush() yield part print() # noqa diff --git a/tests-py3.txt b/tests-py3.txt index aaf5e2456..0e3d34319 100644 --- a/tests-py3.txt +++ b/tests-py3.txt @@ -1,3 +1,4 @@ +cinder.tests.unit.api.openstack.test_wsgi cinder.tests.unit.image.test_cache cinder.tests.unit.image.test_glance cinder.tests.unit.keymgr.test_mock_key_mgr @@ -106,3 +107,4 @@ cinder.tests.unit.windows.test_vhdutils cinder.tests.unit.windows.test_windows cinder.tests.unit.windows.test_windows_remotefs cinder.tests.unit.windows.test_windows_utils +cinder.tests.unit.wsgi -- 2.45.2