Added more valid request tests.

Found and fixed a couple read and readline related bugs.
This commit is contained in:
Paul J. Davis 2010-06-02 22:36:20 -04:00
parent ae025cd22b
commit 5af1273fc2
60 changed files with 188 additions and 87 deletions

View File

@ -3,6 +3,8 @@
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import sys
try:
from cStringIO import StringIO
except ImportError:
@ -177,50 +179,69 @@ class Body(object):
if not ret:
raise StopIteration()
return ret
def getsize(self, size):
if size is None:
return sys.maxint
elif not isinstance(size, (int, long)):
raise TypeError("size must be an integral type")
elif size < 0:
return sys.maxint
return size
def read(self, size=None):
if size is not None and not isinstance(size, (int, long)):
raise TypeError("size must be an integral type")
size = self.getsize(size)
if size == 0:
return ""
if size is not None and size < self.buf.tell():
if size < self.buf.tell():
data = self.buf.getvalue()
ret, rest = data[:size], data[size:]
self.buf.truncate(0)
self.buf.write(rest)
return ret
if size > 0:
size -= self.buf.tell()
else:
size = None
ret = self.buf.getvalue() + self.reader.read(size=size)
while size > self.buf.tell():
data = self.reader.read(1024)
if not len(data):
break
self.buf.write(data)
data = self.buf.getvalue()
ret, rest = data[:size], data[size:]
self.buf.truncate(0)
self.buf.write(rest)
return ret
def readline(self, size=None):
size = self.getsize(size)
if size == 0:
return ""
if size < 0:
size = None
idx = -1
idx = self.buf.getvalue().find("\n")
while idx < 0:
data = self.reader.read(1024)
if not len(data):
break
self.buf.write(data)
if size is not None and self.buf.tell() > size:
idx = self.buf.getvalue().find("\n")
if size < self.buf.tell():
break
idx = self.buf.getvalue().find("\r\n")
# If we didn't find it, and we got here, we've
# exceeded size or run out of data.
if idx < 0:
rlen = min(size, self.buf.tell())
else:
rlen = idx + 1
if idx < 0 and size is not None:
idx = size
elif idx < 0:
idx = self.buf.tell()
# If rlen is beyond our size threshold, trim back
if rlen > size:
rlen = size
data = self.buf.getvalue()
ret, rest = data[:idx], data[idx:]
ret, rest = data[:rlen], data[rlen:]
self.buf.truncate(0)
self.buf.write(rest)
return ret

View File

@ -1,25 +0,0 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import t
import treq
import glob
import os
dirname = os.path.dirname(__file__)
reqdir = os.path.join(dirname, "requests")
def load_py(fname):
config = globals().copy()
config["uri"] = treq.uri
execfile(fname, config)
return config["request"]
def test_http_parser():
for fname in glob.glob(os.path.join(reqdir, "*.http")):
expect = load_py(os.path.splitext(fname)[0] + ".py")
req = treq.request(fname, expect)
for case in req.gen_cases():
yield case

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -
#
# This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information.
import t
import treq
import glob
import os
dirname = os.path.dirname(__file__)
reqdir = os.path.join(dirname, "requests", "valid")
def load_py(fname):
config = globals().copy()
config["uri"] = treq.uri
execfile(fname, config)
return config["request"]
def a_case(fname):
expect = load_py(os.path.splitext(fname)[0] + ".py")
req = treq.request(fname, expect)
for case in req.gen_cases():
case[0](*case[1:])
def test_http_parser():
for fname in glob.glob(os.path.join(reqdir, "*.http")):
if os.getenv("GUNS_BLAZING"):
expect = load_py(os.path.splitext(fname)[0] + ".py")
req = treq.request(fname, expect)
for case in req.gen_cases():
yield case
else:
yield (a_case, fname)

View File

@ -0,0 +1,4 @@
GET /first HTTP/1.1\r\n
\r\n
GET /second HTTP/1.1\r\n
\r\n

View File

@ -0,0 +1,17 @@
req1 = {
"method": "GET",
"uri": uri("/first"),
"version": (1, 1),
"headers": [],
"body": ""
}
req2 = {
"method": "GET",
"uri": uri("/second"),
"version": (1, 1),
"headers": [],
"body": ""
}
request = [req1, req2]

View File

@ -0,0 +1,4 @@
GET /first HTTP/1.0\r\n
\r\n
GET /second HTTP/1.1\r\n
\r\n

View File

@ -0,0 +1,7 @@
request = {
"method": "GET",
"uri": uri("/first"),
"version": (1, 0),
"headers": [],
"body": ""
}

View File

@ -0,0 +1,5 @@
GET /first HTTP/1.0\r\n
Content-Length: 24\r\n
\r\n
GET /second HTTP/1.1\r\n
\r\n

View File

@ -0,0 +1,7 @@
request = {
"method": "GET",
"uri": uri("/first"),
"version": (1, 0),
"headers": [('CONTENT-LENGTH', '24')],
"body": "GET /second HTTP/1.1\r\n\r\n"
}

View File

@ -0,0 +1,5 @@
GET /first HTTP/1.1\r\n
Connection: Close\r\n
\r\n
GET /second HTTP/1.1\r\n
\r\n

View File

@ -0,0 +1,7 @@
request = {
"method": "GET",
"uri": uri("/first"),
"version": (1, 1),
"headers": [("CONNECTION", "Close")],
"body": ""
}

View File

@ -0,0 +1,5 @@
GET /first HTTP/1.0\r\n
Connection: Keep-Alive\r\n
\r\n
GET /second HTTP/1.1\r\n
\r\n

View File

@ -0,0 +1,17 @@
req1 = {
"method": "GET",
"uri": uri("/first"),
"version": (1, 0),
"headers": [("CONNECTION", "Keep-Alive")],
"body": ""
}
req2 = {
"method": "GET",
"uri": uri("/second"),
"version": (1, 1),
"headers": [],
"body": ""
}
request = [req1, req2]

View File

@ -0,0 +1,11 @@
POST /two_chunks_mult_zero_end HTTP/1.1\r\n
Transfer-Encoding: chunked\r\n
\r\n
5\r\n
hello\r\n
6\r\n
world\r\n
000\r\n
\r\n
GET /second HTTP/1.1\r\n
\r\n

View File

@ -0,0 +1,19 @@
req1 = {
"method": "POST",
"uri": uri("/two_chunks_mult_zero_end"),
"version": (1, 1),
"headers": [
("TRANSFER-ENCODING", "chunked")
],
"body": "hello world"
}
req2 = {
"method": "GET",
"uri": uri("/second"),
"version": (1, 1),
"headers": [],
"body": ""
}
request = [req1, req2]

View File

@ -89,7 +89,7 @@ class request(object):
return 1
def size_small_random(self):
return random.randint(0, 2)
return random.randint(0, 4)
def size_random(self):
return random.randint(1, 4096)
@ -117,6 +117,7 @@ class request(object):
if not data:
count -= 1
if count <= 0:
print "BOD: %r" % body
raise AssertionError("Unexpected apparent EOF")
if len(body):
@ -201,14 +202,7 @@ class request(object):
for sz in sizers
for sn in senders
]
# Strip out match_readlines, match_iter for all but one sizer
cfgs = [
(mt, sz, sn)
for (mt, sz, sn) in cfgs
if mt in [self.match_readlines, self.match_iter]
and sz != self.size_all
]
ret = []
for (mt, sz, sn) in cfgs:
mtn = mt.func_name[6:]
@ -223,43 +217,12 @@ class request(object):
def check(self, sender, sizer, matcher):
cases = self.expect[:]
ended = False
try:
p = RequestParser(sender())
except Exception, e:
if not isinstance(cases[0], Exception):
raise
self.same_error(e, cases[0])
t.eq(len(casese), 1)
while True:
try:
req = p.next()
except StopIteration, e:
t.eq(len(cases), 0)
ended = True
break
except ParseException, e:
if not issubclass(cases[0], Exception):
raise
self.same_error(e, cases.pop(0))
t.eq(len(cases), 0)
return
else:
self.same(req, sizer, matcher, cases.pop(0))
p = RequestParser(sender())
for req in p:
self.same(req, sizer, matcher, cases.pop(0))
t.eq(len(cases), 0)
t.eq(ended, True)
def same(self, req, sizer, matcher, exp):
if isinstance(req, Exception):
self.same_error(req, exp)
else:
self.same_obj(req, sizer, matcher, exp)
def same_error(self, req, exp):
t.istype(req, Exception)
t.istype(req, exp)
def same_obj(self, req, sizer, matcher, exp):
t.eq(req.method, exp["method"])
t.eq(req.uri, exp["uri"]["raw"])
t.eq(req.scheme, exp["uri"]["scheme"])