Repository: falconry/falcon

Issue:
test: Add Unicode chars to logging test



Discussion:

Resolving patch:
diff --git a/falcon/request.py b/falcon/request.py
--- a/falcon/request.py
+++ b/falcon/request.py
@@ -16,12 +16,15 @@
 
 """
 
-import sys
 from datetime import datetime
 
+import six
+
 from falcon.request_helpers import *
 from falcon.exceptions import *
-import six
+
+DEFAULT_ERROR_LOG_FORMAT = ('{0:%Y-%m-%d %H:%M:%S} [FALCON] [ERROR]'
+                            ' {1} {2}?{3} => {4}\n')
 
 
 class Request(object):
@@ -50,21 +53,23 @@ def __init__(self, env):
 
         """
 
-        self.app = env['SCRIPT_NAME']
+        self._wsgierrors = env['wsgi.errors']
         self.body = env['wsgi.input']
+
+        self.protocol = env['wsgi.url_scheme']
+        self.app = env['SCRIPT_NAME']
         self.method = env['REQUEST_METHOD']
         self.path = env['PATH_INFO'] or '/'
-        self.protocol = env['wsgi.url_scheme']
         self.query_string = query_string = env['QUERY_STRING']
+
         self._params = parse_query_string(query_string)
         self._headers = parse_headers(env)
-        self._wsgierrors = env['wsgi.errors']
 
     def log_error(self, message):
         """Log an error to wsgi.error
 
-        Prepends timestamp and request info to message, and writes the result
-        out to the WSGI server's error stream (wsgi.error).
+        Prepends timestamp and request info to message, and writes the
+        result out to the WSGI server's error stream (wsgi.error).
 
         Args:
             message: A string describing the problem. If a byte-string and
@@ -72,11 +77,13 @@ def log_error(self, message):
                 as UTF-8.
 
         """
-        u = six.text_type
+        if not six.PY3 and isinstance(message, unicode):
+            message = message.encode('utf-8')
+
         log_line = (
-            u('{0:%Y-%m-%d %H:%M:%S} [FALCON] [ERROR] {1} {2}?{3} => {4}\n').
-            format(datetime.now(), self.method, self.path, self.query_string,
-                   message)
+            DEFAULT_ERROR_LOG_FORMAT.
+            format(datetime.now(), self.method, self.path,
+                   self.query_string, message)
         )
 
         self._wsgierrors.write(log_line)


Test patch:
diff --git a/tests/dump_wsgi.py b/tests/dump_wsgi.py
--- a/tests/dump_wsgi.py
+++ b/tests/dump_wsgi.py
@@ -1,4 +1,11 @@
+import pdb
+from wsgiref.simple_server import make_server
+
+
 def application(environ, start_response):
+    wsgi_errors = environ['wsgi.errors']
+    pdb.set_trace()
+
     start_response("200 OK", [
         ('Content-Type', 'text/plain')])
 
@@ -13,9 +20,7 @@ def application(environ, start_response):
 
 app = application
 
-if __name__ == '__main__':
-    import wsgiref
-    from wsgiref.simple_server import make_server
 
+if __name__ == '__main__':
     server = make_server('localhost', 8000, application)
     server.serve_forever()
diff --git a/requirements.txt b/tests/requirements.txt
similarity index 100%
rename from requirements.txt
rename to tests/requirements.txt
diff --git a/tests/test_wsgi_errors.py b/tests/test_wsgi_errors.py
--- a/tests/test_wsgi_errors.py
+++ b/tests/test_wsgi_errors.py
@@ -1,6 +1,14 @@
 import io
+import six
 from . import helpers
 
+unicode_message = u'Unicode: \x80'
+
+if six.PY3:
+    str_message = 'Unicode all the way: \x80'
+else:
+    str_message = 'UTF-8: \xc2\x80'
+
 
 class BombResource:
 
@@ -14,7 +22,10 @@ def on_head(self, req, resp):
 class LoggerResource:
 
     def on_get(self, req, resp):
-        req.log_error('Internet crashed')
+        req.log_error(unicode_message)
+
+    def on_head(self, req, resp):
+        req.log_error(str_message)
 
 
 class TestWSGIError(helpers.TestSuite):
@@ -25,24 +36,46 @@ def prepare(self):
 
         self.api.add_route('/bomb', self.tehbomb)
         self.api.add_route('/logger', self.tehlogger)
-        self.wsgierrors = io.StringIO()
+
+        self.wsgierrors_buffer = io.BytesIO()
+
+        if six.PY3:
+            # Simulate Gunicorn's behavior under Python 3
+            self.wsgierrors = io.TextIOWrapper(self.wsgierrors_buffer,
+                                               line_buffering=True)
+        else:
+            # WSGI servers typically present an open file object,
+            # with undefined encoding, so do the encoding manually.
+            self.wsgierrors = self.wsgierrors_buffer
 
     def test_exception_logged(self):
         self._simulate_request('/bomb', wsgierrors=self.wsgierrors)
-        log = self.wsgierrors.getvalue()
 
-        self.assertIn('IOError', log)
+        log = self.wsgierrors_buffer.getvalue()
+        self.assertIn(b'IOError', log)
 
     def test_exception_logged_with_details(self):
         self._simulate_request('/bomb', wsgierrors=self.wsgierrors,
                                method='HEAD')
-        log = self.wsgierrors.getvalue()
 
-        self.assertIn('MemoryError', log)
-        self.assertIn('remember a thing', log)
+        log = self.wsgierrors_buffer.getvalue()
 
-    def test_responder_logged(self):
+        self.assertIn(b'MemoryError', log)
+        self.assertIn(b'remember a thing', log)
+
+    def test_responder_logged_unicode(self):
         self._simulate_request('/logger', wsgierrors=self.wsgierrors)
-        log = self.wsgierrors.getvalue()
 
-        self.assertIn('Internet crashed\n', log)
+        log = self.wsgierrors_buffer.getvalue()
+        self.assertIn(unicode_message.encode('utf-8'), log)
+
+    def test_responder_logged_str(self):
+        self._simulate_request('/logger', wsgierrors=self.wsgierrors,
+                               method='HEAD')
+
+        log = self.wsgierrors_buffer.getvalue()
+
+        if six.PY3:
+            self.assertIn(str_message.encode('utf-8'), log)
+        else:
+            self.assertIn(str_message, log)