Commits

Daniel Holth committed c0cdbce

add statement coverage tests

Comments (0)

Files changed (4)

 mongrel2_wsgi
 -------------
 
-A Mongrel2 handler for WSGI applications based on Paste's copy of the
-CherryPy WSGI server and eventlet.
+A Mongrel2 handler for WSGI applications. Based on the CherryPy WSGI
+server and eventlet.
 
-This is a very early version, just to see what I could hack together to
-see whether Mongrel2 is interesting after all.
-
-On my machine, siege -b reports approximately 1800 transactions/second
+On my machine, `siege -b` reports approximately 1800 transactions/second
 from the test application.

mongrel2_wsgi/server.py

 
 try:
     import simplejson as json
-except ImportError:
+except ImportError: # PRAGMA nocover
     import json
 
 try:
     from cStringIO import StringIO
-except ImportError:
+except ImportError: # PRAGMA nocover
     from StringIO import StringIO
 
 from mongrel2_wsgi.request import Request
 import logging
 log = logging.getLogger(__name__)
 
-comma_separated_headers = set(['Accept', 'Accept-Charset', 'Accept-Encoding',
+# Mongrel2 HTTP headers are all lowercase:
+comma_separated_headers = set(x.lower() for x in ['Accept', 'Accept-Charset', 'Accept-Encoding',
     'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control',
     'Connection', 'Content-Encoding', 'Content-Language', 'Expect',
     'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE',
     def close(self):
         self.connection.send(self.header + ' ')
 
-def read_headers(mongrel2_headers, hdict=None):
+def read_headers(mongrel2_headers, hdict={}):
     """Merge mongrel2's headers into hdict."""
-    if hdict is None:
-        hdict = {}
      
     for k, v in mongrel2_headers.items():
-        # mongrel2 HTTP headers are all lowercase.
+        # Mongrel2 HTTP headers are all lowercase. 
+        # Uppercase values are Mongrel2 variables.
         if not (k[0].islower() and '.' not in k): continue
         if isinstance(v, list):
-            if v in comma_separated_headers:
+            if k in comma_separated_headers:
                 v = ', '.join(v)
             else:
                 v = v[-1]
 
         scheme, authority, path = None, None, self.uri
 
-        if scheme:
-            self.scheme = scheme
+        if scheme: self.scheme = scheme
 
         qs = str(mongrel2_headers.get('QUERY', ''))
         self.qs = qs
         sp = int(self.server.protocol[5]), int(self.server.protocol[7])
         
         if sp[0] != rp[0]:
+            self.request_protocol = "HTTP/1.0" # prevent AttributeError
             self.simple_response("505 HTTP Version Not Supported")
             return
         self.request_protocol = req_protocol
         # then all the http headers
         try:
             read_headers(self.conn.mongrel2_request.headers, self.inheaders)
-        except ValueError, ex:
+        except ValueError, ex: # can read_headers raise this exception?
             self.simple_response("400 Bad Request", ex.args[0])
             return False
         
             # Don't use simple_response here, because it emits headers
             # we don't want. See http://www.cherrypy.org/ticket/951
             msg = self.server.protocol + " 100 Continue\r\n\r\n"
-            try:
-                self.conn.wfile.sendall(msg)
-            except socket.error, x:
-                if x.args[0] not in socket_errors_to_ignore:
-                    raise
+            # try:
+            self.conn.wfile.sendall(msg)
+            # except socket.error, x: # XXX which errors will ZeroMQ raise? import socket...
+            #    if x.args[0] not in socket_errors_to_ignore:
+            #        raise
         return True
 
 class Mongrel2Connection(wsgiserver.HTTPConnection):

mongrel2_wsgi/test_server.py

+"""
+Statement coverage tests for mongrel2_wsgi.server.
+"""
+
 from mongrel2_wsgi import server
 
 import urllib2
 log = logging.getLogger(__name__)
 
 sender_id = 'ee3f2ae1-37e1-4866-a9bc-90cf5db31b9d'
-sub_addr = 'tcp://127.0.0.1:9999'
-pub_addr = 'tcp://127.0.0.1:9998'
+sub_addr = 'tcp://127.0.0.1:52999'
+pub_addr = 'tcp://127.0.0.1:52998'
 
-def test_server():
-    import logging
-    logging.basicConfig(level=logging.DEBUG)
-
-    request_data = """ee3f2ae1-37e1-4866-a9bc-90cf5db31b9d 45965 / 203:{"PATH":"/","user-agent":"Python-urllib/2.6","host":"localhost:6767","x-forwarded-for":"::1","connection":"close","accept-encoding":"identity","METHOD":"GET","VERSION":"HTTP/1.1","URI":"/","PATTERN":"/"},0:,"""
-
-    class MockRecv(object):
-        def recv(self):
-            self.recv = None
-            return request_data
-
-    class MockSend(list):
-        def send(self, data):
-            self.append(data)
-
+def create_test_server():
+    """Return testing Mongrel2WSGIServer."""
     app = validator(demo_app)
-
     s = server.Mongrel2WSGIServer(
             app, 
             sender_id=sender_id, 
             sub_addr=sub_addr,
             pub_addr=pub_addr
             )
+    s.requests = RedPool()
     s.bind()
     s.ready = True
-    s.reqs = MockRecv()
+    return s
+
+class RedPool(object):
+    """Synchronous GreenPool implementation."""
+    def spawn_n(self, fn, *args, **kwargs):
+        fn(*args, **kwargs)
+
+class MockRecv(object):
+    def __init__(self, request_data):
+        self.request_data=request_data
+
+    def recv(self):
+        self.recv = None
+        return self.request_data
+
+class MockSend(list):
+    def send(self, data):
+        self.append(data)
+
+def test_server():
+    request_data = """ee3f2ae1-37e1-4866-a9bc-90cf5db31b9d 45965 / 203:{"PATH":"/","user-agent":"Python-urllib/2.6","host":"localhost:6767","x-forwarded-for":"::1","connection":"close","accept-encoding":"identity","METHOD":"GET","VERSION":"HTTP/1.1","URI":"/","PATTERN":"/"},0:,"""
+
+    s = create_test_server()
+
+    assert 'Mongrel2' in str(s), str(s)
+
+    s.reqs = MockRecv(request_data)
     s.resp = MockSend()
     s.tick()
-    sleep(0.1) 
-    log.debug(s.resp)
+    assert '200 OK\r\n' in s.resp[0], s.resp
 
+    invalid_request_data = """ee3f2ae1-37e1-4866-a9bc-90cf5db31b9d 45965 / 203:{"PATH":"/","user-agent":"Python-urllib/2.6","host":"localhost:6767","x-forwarded-for":"::1","connection":"close","accept-encoding":"identity","METHOD":"GET","VERSION":"HTTP/4.9","URI":"/","PATTERN":"/"},0:,"""
+
+    s.reqs = MockRecv(invalid_request_data)
+    s.resp = MockSend()
+    s.tick()
+    assert '505 HTTP' in s.resp[0], s.resp
+
+def test_start_stop():
+    s = create_test_server()
+    def tick():
+        s.ready = False
+    s.tick = tick
+    s.start()
+    s.stop()
+
+def dont_test_bad_netstring():
+    s = create_test_server()
+    # netstring has incorrect length, will raise IndexError if length
+    # is too long, ValueError if length does not go past the end of the
+    # request_data string, AttributeError if an int was thrown into
+    # the JSON.
+    request_data = """ee3f2ae1-37e1-4866-a9bc-90cf5db31b9d 45965 / 2103:{"PATH":"/","user-agent":"Python-urllib/2.6","host":"localhost:6767","x-forwarded-for":"::1","connectionclose","accept-encoding":"identity","METHOD":"GET","VERSION":"HTTP/1.1","URI":"/","PATTERN":"/"},0:,"""
+    s.reqs = MockRecv(request_data)
+    s.tick()
+    sleep(0.1)
+
+def test_mrbs():
+    s = create_test_server()
+    s.max_request_body_size = 1023
+    request_data = """ee3f2ae1-37e1-4866-a9bc-90cf5db31b9d 45965 / 206:{"PATH":"/","user-agent":"Python-urllib/2.6","host":"localhost:6767","x-forwarded-for":"::1","content-length":"1024","accept-encoding":"identity","METHOD":"GET","VERSION":"HTTP/1.1","URI":"/","PATTERN":"/"},0:,"""
+    s.reqs = MockRecv(request_data)
+    s.resp = MockSend()
+    s.tick()
+    assert '413 Request Entity Too Large' in s.resp[0], s.resp
+
+def test_transfer_encoding():
+    s = create_test_server()
+    request_data = """ee3f2ae1-37e1-4866-a9bc-90cf5db31b9d 45965 / 216:{"PATH":"/","user-agent":"Python-urllib/2.6","host":"localhost:6767","transfer-encoding":"chunked,foo","content-length":"1024","accept-encoding":"identity","METHOD":"GET","VERSION":"HTTP/1.1","URI":"/","PATTERN":"/"},0:,"""
+    s.reqs = MockRecv(request_data)
+    s.resp = MockSend()
+    s.tick()
+    # server does not support the 'foo' transfer encoding:
+    assert '501 Unimplemented' in s.resp[0], s.resp
+
+def test_100_continue():
+    s = create_test_server()
+    request_data = """ee3f2ae1-37e1-4866-a9bc-90cf5db31b9d 45965 / 182:{"PATH":"/","user-agent":"Python-urllib/2.6","host":"localhost:6767","expect":"100-continue","accept-encoding":"identity","METHOD":"GET","VERSION":"HTTP/1.1","URI":"/","PATTERN":"/"},0:,"""
+    s.reqs = MockRecv(request_data)
+    s.resp = MockSend()
+    s.tick()
+    assert '100 Continue\r\n' in s.resp[0], s.resp
+
+def test_mongrel2file():
+    class request:
+        conn_id='foo'
+        sender='bar'
+    assert server.Mongrel2File(None, request).flush() is None
+
+def test_read_headers():
+    mongrel2_headers = {
+            'accept':['foo','bar'],
+            'content-language':'esperanto',
+            'x-men':['superman','wolverine'],
+            'PATH':'/usr/local/bin'}
+    headers = server.read_headers(mongrel2_headers)
+    assert 'foo' in headers['Accept'], headers['Accept']
+    assert 'bar' in headers['Accept'], headers['Accept']
+    assert 'superman' not in headers['X-Men'], headers['X-Men']
+    assert 'PATH' not in headers
+
 [egg_info]
 tag_build = dev
+
+[nosetests]
+match=^test
+nocapture=1
+cover-package=mongrel2_wsgi
+with-coverage=1
+cover-erase=1
+