Mariano Mara avatar Mariano Mara committed 00bf3a0 Merge

Merging + deleting some lines that are unreachable (https://bitbucket.org/marplatense/webobtestcoverage/issue/5/impossible-to-enter-a-conditional-branch) + adding test for FakeCGIBody.readline and FakeCGIBody.readlines()

Comments (0)

Files changed (13)

 from mext.test_suite import TestSuite
 suite = TestSuite('tests', coverage=True, pkg='webob')
 
-doctests = ['test_dec', 'test_request', 'test_response', 'html_escape', 'multidict']
+doctests = ['test_dec', 'test_request', 'test_response']
 doctests += map('../docs/'.__add__, ['do-it-yourself', 'file-example', 'index', 'reference'])
 map(suite.add_doctest, doctests)
-map(suite.add_nosetest, ['test_request', 'test_response', 'test_multidict'])
+suite.add_unittest('test_multidict')
+map(suite.add_nosetest, [
+    'test_request', 'test_response', 'test_headers',
+    'test_cookies', 'test_exc',
+    'test_misc', 'test_datetime_utils',
+])
 
 
 if __name__ == '__main__':
     suite.run_text()
+
+

tests/html_escape.txt

-
-
-  >>> from webob import html_escape
-  >>> html_escape(None)
-  ''
-  >>> html_escape(' ')
-  ' '
-  >>> html_escape('è')
-  'è'
-  >>> html_escape(u'\xe9')
-  'é'
-
-  >>> class HTML(object):
-  ...   def __html__(self):
-  ...       return '<div>hello</div>'
-
-  >>> html_escape(HTML())
-  '<div>hello</div>'
-
-  >>> class Unicode(object):
-  ...   def __unicode__(self):
-  ...       return u'\xe9'
-
-  >>> html_escape(Unicode())
-  '&#233;'
-
-  >>> class UnsafeAttrs(object):
-  ...     attr = 'value'
-  ...     def __getattr__(self):
-  ...         return self.attr
-  ...     def __repr__(self):
-  ...         return '<UnsafeAttrs>'
-
-  >>> html_escape(UnsafeAttrs())
-  '&lt;UnsafeAttrs&gt;'

tests/multidict.txt

-
-    >>> from webob.multidict import *
-    >>> d = MultiDict(a=1, b=2)
-    >>> d['a']
-    1
-    >>> d.getall('c')
-    []
-    >>> d.add('a', 2)
-    >>> d['a']
-    2
-    >>> d.getall('a')
-    [1, 2]
-    >>> d['b'] = 4
-    >>> d.getall('b')
-    [4]
-    >>> d.keys()
-    ['a', 'a', 'b']
-    >>> d.items()
-    [('a', 1), ('a', 2), ('b', 4)]
-    >>> d.mixed() == {'a': [1, 2], 'b': 4}
-    True
-    >>> MultiDict([('a', 'b')], c=2)
-    MultiDict([('a', 'b'), ('c', 2)])
-    >>> MultiDict(1,2,3)
-    Traceback (most recent call last):
-      File "<stdin>", line 1, in <module>
-      File "webob/multidict.py", line 29, in __init__
-        "MultiDict can only be called with one positional argument")
-    TypeError: MultiDict can only be called with one positional argument
-    >>> d.getone('a')
-    Traceback (most recent call last):
-      File "<stdin>", line 1, in <module>
-      File "webob/multidict.py", line 113, in getone
-        raise KeyError('Multiple values match %r: %r' % (key, v))
-    KeyError: "Multiple values match 'a': [1, 2]"
-    >>> d.getone('b')
-    4
-    >>> d.getone('g')
-    Traceback (most recent call last):
-      File "<stdin>", line 1, in <module>
-      File "webob/multidict.py", line 111, in getone
-        raise KeyError('Key not found: %r' % key)
-    KeyError: "Key not found: 'g'"
-    >>> MultiDict.view_list(None)
-    Traceback (most recent call last):
-      File "<stdin>", line 1, in <module>
-      File "webob/multidict.py", line 51, in view_list
-        % (cls.__name__, lst))
-    TypeError: MultiDict.view_list(obj) takes only actual list objects, not None
-    >>> d.dict_of_lists()
-    {'a': [1, 2], 'b': [4]}
-    >>> 'b' in d
-    True
-    >>> 'e' in d
-    False
-    >>> d.clear()
-    >>> 'b' in d
-    False
-    >>> d['a'] = 4
-    >>> d.add('a', 5)
-    >>> e = d.copy()
-    >>> 'a' in e
-    True
-    >>> e.clear()
-    >>> e['f'] = 42
-    >>> d.update(e)
-    >>> d
-    MultiDict([('a', 4), ('a', 5), ('f', 42)])
-    >>> f = d.pop('a')
-    >>> f
-    4
-    >>> d['a']
-    5
-    >>> d.pop('g', 42)
-    42
-    >>> d.pop('n')
-    Traceback (most recent call last):
-      File "<stdin>", line 1, in <module>
-      File "webob/multidict.py", line 195, in pop
-        raise KeyError(key)
-    KeyError: 'n'
-    >>> d.pop(4, 2, 3)
-    Traceback (most recent call last):
-      File "<stdin>", line 1, in <module>
-      File "webob/multidict.py", line 186, in pop
-        + repr(1 + len(args))
-    TypeError: pop expected at most 2 arguments, got 3
-    >>> d.setdefault('g', []).append(4)
-    >>> d
-    MultiDict([('a', 5), ('f', 42), ('g', [4])])
-    
-    >>> import cgi
-    >>> fs = cgi.FieldStorage()
-    >>> fs.filename = '\xc3\xb8'
-    >>> plain = MultiDict(key='\xc3\xb8', fs=fs)
-    >>> ua = UnicodeMultiDict(multi=plain, encoding='utf-8')
-    >>> ua.getall('key')
-    [u'\xf8']
-    >>> ua.getall('fs')
-    [FieldStorage(None, u'\xf8', [])]
-    >>> ub = UnicodeMultiDict(multi=ua, encoding='utf-8')
-    >>> ub.getall('key')
-    [u'\xf8']
-    >>> ub.getall('fs')
-    [FieldStorage(None, u'\xf8', [])]

tests/test_cookies.py

 from nose.tools import ok_, assert_raises, eq_
 
 def test_cookie():
-    """Testing several missing features of cookies.Cookie.
-    * repr version
-    * ignoring a key-value for Cookie when key == $
+    """
+        Test cookie parsing, serialization and `repr`
     """
     c = cookies.Cookie() # empty cookie
     eq_(repr(c), '<Cookie: []>')
     # a cookie with one value
-    c = cookies.Cookie('dismiss-top=6') 
+    c = cookies.Cookie('dismiss-top=6')
     eq_(repr(c), "<Cookie: [<Morsel: dismiss-top='6'>]>")
-    c = cookies.Cookie('dismiss-top=6;') 
+    c = cookies.Cookie('dismiss-top=6;')
     eq_(repr(c), "<Cookie: [<Morsel: dismiss-top='6'>]>")
-    # more complex cookie
-    new_c = "<Cookie: [<Morsel: a='42'>, <Morsel: CP='null*'>, "\
-    "<Morsel: PHPSESSID='0a539d42abc001cdc762809248d4beed'>, "\
-    "<Morsel: dismiss-top='6'>]>"
-    c = cookies.Cookie("dismiss-top=6; CP=null*; "\
-                       "PHPSESSID=0a539d42abc001cdc762809248d4beed; a=42")
-    ok_(repr(c), new_c)
-    eq_(c.serialize(), 
-        'CP=null*, PHPSESSID=0a539d42abc001cdc762809248d4beed, a=42, '
+    # more complex cookie, (also mixing commas and semicolons)
+    c = cookies.Cookie('dismiss-top=6; CP=null*, '\
+                       'PHPSESSID=0a539d42abc001cdc762809248d4beed, a="42,"')
+    c_dict = dict((k,v.value) for k,v in c.items())
+    eq_(c_dict, {'a': '42,',
+        'CP': 'null*',
+        'PHPSESSID': '0a539d42abc001cdc762809248d4beed',
+        'dismiss-top': '6'
+    })
+    eq_(c.serialize(),
+        'CP=null*, PHPSESSID=0a539d42abc001cdc762809248d4beed, a="42,", '
         'dismiss-top=6')
-    # data with key==$
-    c = cookies.Cookie('dismiss-top=6; CP=null*; $=42'\
-                       'PHPSESSID=0a539d42abc001cdc762809248d4beed; a=42')
-    ok_('$' not in c, 'Key $ should have been ignored')
-    c = cookies.Cookie('$=a; dismiss-top=6; CP=null*; $=42'\
-                       'PHPSESSID=0a539d42abc001cdc762809248d4beed; a=42')
-    ok_('$' not in c, 'Key $ should have been ignored')
+    # reserved keys ($xx)
+    c = cookies.Cookie('dismiss-top=6; CP=null*; $version=42; a=42')
+    assert '$version' not in c
+    c = cookies.Cookie('$reserved=42; a=$42')
+    eq_(c.keys(), ['a'])
 
 def test_serialize_cookie_date():
-    """Testing webob.cookies.serialize_cookie_date.
-    Missing scenarios:
-        * input value is an str, should be returned verbatim
-        * input value is an int, should be converted to timedelta and we should
-          continue the rest of the process
+    """
+        Testing webob.cookies.serialize_cookie_date.
+        Missing scenarios:
+            * input value is an str, should be returned verbatim
+            * input value is an int, should be converted to timedelta and we should
+              continue the rest of the process
     """
     ok_(cookies.serialize_cookie_date('Tue, 04-Jan-2011 13:43:50 GMT')==\
         'Tue, 04-Jan-2011 13:43:50 GMT', 'We passed a string, should get the '
         'same one')
-    ok_(cookies.serialize_cookie_date(None)==None, 'We passed None, should '
+    ok_(cookies.serialize_cookie_date(None) is None, 'We passed None, should '
         'get None')
-    ok_(cookies.serialize_cookie_date(timedelta(seconds=10))==\
-        cookies.serialize_cookie_date(10), 'Passing a int to method should '
-        'return the same result as passing a timedelta')
+
+    cdate_delta = cookies.serialize_cookie_date(timedelta(seconds=10))
+    cdate_int = cookies.serialize_cookie_date(10)
+    eq_(cdate_delta, cdate_int,
+        'Passing a int to method should return the same result as passing a timedelta'
+    )
 
 def test_ch_unquote():
-    """Inner method _ch_unquote in cookies._unquote is not tested"""
-    str_ = u'"'+u'hello world'+u'"'
-    v = cookies._unquote(str_)
-    ok_(v==u'hello world', 'Wrong output from _unquote. Expected: %r, '
-        'Got: %r' % (u'hello world', v))
-    str_ = u'hello world'
-    v = cookies._unquote(str_)
-    ok_(v==u'hello world', 'Wrong output from _unquote. Expected: %r, '
-        'Got: %r' % (u'hello world', v))
-    str_ = u'"'+u'hello world'
-    v = cookies._unquote(str_)
-    ok_(v==u'\"hello world', 'Wrong output from _unquote. Expected: %r, '
-        'Got: %r' % (u'\"hello world', v))
-    # example extracted from webob.cookies
-    ok_(cookies._unquote(r'"a\"\377"')=='a"\xff')
-
+    eq_(cookies._unquote(u'"hello world'), u'"hello world')
+    eq_(cookies._unquote(u'hello world'), u'hello world')
+    for unq, q in [
+        ('hello world', '"hello world"'),
+        # quotation mark is escaped w/ backslash
+        ('"', r'"\""'),
+        # misc byte escaped as octal
+        ('\xff', r'"\377"'),
+        # combination
+        ('a"\xff', r'"a\"\377"'),
+    ]:
+        eq_(cookies._unquote(q), unq)
+        eq_(cookies._quote(unq), q)

tests/test_datetime_utils.py

 # -*- coding: UTF-8 -*-
 
 import calendar
-import datetime
+from datetime import *
 from rfc822 import formatdate
 from webob import datetime_utils
-from nose.tools import ok_, assert_raises
+from nose.tools import ok_, eq_, assert_raises
 
 def test_parse_date():
     """Testing datetime_utils.parse_date.
         "to parse_date. We should get None but instead we got %s" %\
         ret)
     ret = datetime_utils.parse_date('Mon, 20 Nov 1995 19:12:08 -0500')
-    ok_(ret==datetime.datetime(1995, 11, 21, 0, 12, 8, 
-                               tzinfo=datetime_utils.UTC), 
-        'We passed a valid RFC2822 date but we did not get what we expected.'
-        ' Expected (1995, 11, 21, 0, 12, 8, tzinfo=UTC) but we got %s' % ret)
+    eq_(ret, datetime(1995, 11, 21, 0, 12, 8, tzinfo=datetime_utils.UTC))
     ret = datetime_utils.parse_date('Mon, 20 Nov 1995 19:12:08')
-    ok_(ret==datetime.datetime(1995, 11, 20, 19, 12, 8, 
-                               tzinfo=datetime_utils.UTC), 
-        'We passed a valid RFC2822 date but we did not get what we expected.'
-        ' Expected (1995, 11, 20, 19, 12, 8, tzinfo=UTC) but we got %s' % ret)
+    eq_(ret, datetime(1995, 11, 20, 19, 12, 8, tzinfo=datetime_utils.UTC))
 
 def test_serialize_date():
     """Testing datetime_utils.serialize_date
         * passing an invalid object, should raise ValueError
     """
     ret = datetime_utils.serialize_date(u'Mon, 20 Nov 1995 19:12:08 GMT')
-    ok_(ret=='Mon, 20 Nov 1995 19:12:08 GMT' and type(ret)==str, 
-        'we passed an unicode valid date but we did not get the same value as '
-        'str. Expected: %s, got %s' % ('Mon, 20 Nov 1995 19:12:08 GMT', ret))
-    ok_(formatdate(calendar.timegm((datetime.datetime.now()+datetime.timedelta(1)).\
-                                   timetuple()))==datetime_utils.\
-        serialize_date(datetime.timedelta(1)), 'Passing a timedelta, should '
-       'return now + timedelta')
+    assert type(ret) is (str)
+    eq_(ret, 'Mon, 20 Nov 1995 19:12:08 GMT')
+    dt = formatdate(calendar.timegm((datetime.now()+timedelta(1)).timetuple()))
+    eq_(dt, datetime_utils.serialize_date(timedelta(1)))
     assert_raises(ValueError, datetime_utils.serialize_date, None)
 
 def test_parse_date_delta():
     ok_(datetime_utils.parse_date_delta(None) is None, 'Passing none value, '
         'should return None')
     ret = datetime_utils.parse_date_delta('Mon, 20 Nov 1995 19:12:08 -0500')
-    ok_(ret==datetime.datetime(1995, 11, 21, 0, 12, 8, 
-                               tzinfo=datetime_utils.UTC), 'We passed a '
-        'string so parse_date_delta delegate the result to parse_date')
+    eq_(ret, datetime(1995, 11, 21, 0, 12, 8, tzinfo=datetime_utils.UTC))
 
 def test_serialize_date_delta():
     """Testing datetime_utils.serialize_date_delta
           the task to serialize_date
     """
     ret = datetime_utils.serialize_date_delta(u'Mon, 20 Nov 1995 19:12:08 GMT')
-    ok_(ret=='Mon, 20 Nov 1995 19:12:08 GMT' and type(ret)==str, 
-        'we passed an unicode valid date but we did not get the same value as '
-        'str. Expected: %s, got %s' % ('Mon, 20 Nov 1995 19:12:08 GMT', ret))
+    assert type(ret) is (str)
+    eq_(ret, 'Mon, 20 Nov 1995 19:12:08 GMT')
 
 def test_UTC():
     """Test missing function in _UTC"""
     x = datetime_utils.UTC
-    ok_(x.tzname(datetime.datetime.now())=='UTC')
+    ok_(x.tzname(datetime.now())=='UTC')
 
 

tests/test_escape.txt

-A test for html_escape:
-
-    >>> from webob import html_escape
-
-The usual unsafe characters are escaped:
-
-    >>> html_escape('these chars: < > & "')
-    'these chars: &lt; &gt; &amp; &quot;'
-
-XXX The apostrophe is *not* escaped, which some might consider to be
-a serious bug (see, e.g. http://www.cvedetails.com/cve/CVE-2010-2480/)
-
-##  >>> html_escape("'")
-##  "&#39;"
-
-Unicode strings are also converted to ASCII:
-
-    >>> html_escape(u'the majestic m\xf8ose')
-    'the majestic m&#248;ose'
-
-8-bit strings are passed through
-
-    >>> html_escape(u'the majestic m\xf8ose'.encode('utf-8'))
-    'the majestic m\xc3\xb8ose'
-
-``None`` is treated specially, and returns the empty string.
-
-    >>> html_escape(None)
-    ''
-
-Objects that define a ``__html__`` method handle their own escaping
-
-    >>> class Markup(object):
-    ...     def __html__(self):
-    ...         return '<blink>:</blink>'
-    >>> html_escape(Markup())
-    '<blink>:</blink>'
-
-Things that are not strings are converted to strings and then escaped
-
-    >>> html_escape(42)
-    '42'
-    >>> html_escape(Exception("expected a '<'."))
-    "expected a '&lt;'."
-
-If an object implements both ``__str__`` and ``__unicode__``, the latter
-is preferred
-
-    >>> class SuperMoose(object):
-    ...     def __str__(self):
-    ...         return u'm\xf8ose'.encode('UTF-8')
-    ...     def __unicode__(self):
-    ...         return u'm\xf8ose'
-    >>> html_escape(SuperMoose())
-    'm&#248;ose'
-

tests/test_exc.py

-import webob
-import webob.exc
-import webob.dec
+import sys
+from webob import *
+from webob.dec import wsgify
+from webob.exc import *
 
-import sys
-
-@webob.dec.wsgify
+@wsgify
 def method_not_allowed_app(req):
     if req.method != 'GET':
         if sys.version_info > (2,5):
-            raise webob.exc.HTTPMethodNotAllowed()
+            raise HTTPMethodNotAllowed()
         else:
-            raise webob.exc.HTTPMethodNotAllowed().exception
+            raise HTTPMethodNotAllowed().exception
     return 'hello!'
 
 def test_exception_with_unicode_data():
-    req = webob.Request.blank('/', method=u'POST')
+    req = Request.blank('/', method=u'POST')
     res = req.get_response(method_not_allowed_app)
     assert res.status_int == 405
 
 def test_WSGIHTTPException_headers():
-    from webob.exc import WSGIHTTPException
     exc = WSGIHTTPException(headers=[('Set-Cookie', 'a=1'),
                                      ('Set-Cookie', 'a=2')])
     mixed = exc.headers.mixed()

tests/test_headers.py

 # -*- coding: UTF-8 -*-
 from webob import headers
-from nose.tools import ok_,assert_raises
+from nose.tools import ok_, assert_raises, eq_ as eq
 
 class TestError(Exception):
     pass
     """Testing set_default for ResponseHeaders"""
     d = headers.ResponseHeaders(a=1)
     res = d.setdefault('b', 1)
-    ok_(res==1 and d['b']==1)
+    assert res == d['b'] == 1
     res = d.setdefault('b', 10)
-    ok_(res==1 and d['b']==1)
+    assert res == d['b'] == 1
     res = d.setdefault('B', 10)
-    ok_(res==1 and d['b']==1 and 'B' in d and d['B']==1)
+    assert res == d['b'] == d['B'] == 1
 
 def test_pop():
     """Testing if pop return TypeError when more than len(*args)>1 plus other
     assorted tests"""
     d = headers.ResponseHeaders(a=1, b=2, c=3, d=4)
-    try:
-        d.pop('a', *('z', 'y'))
-        raise(TestError('We did not get an error'))
-    except TypeError, e:
-        ok_(e.args[0]=='pop expected at most 2 arguments, got 3', 'Did not '
-            'raise the expected error. We got %r' % e.args[0])
-    else:
-        raise(TestError('We did not get the expected error. We got %r' % \
-                        e.args[0]))
-    ok_(d.pop('a')==1 and 'a' not in d)
-    ok_(d.pop('B')==2 and 'b' not in d)
-    ok_(d.pop('c', *('u',))==3 and 'c' not in d)
-    ok_(d.pop('e', *('u',))=='u' and 'e' not in d)
-    try:
-        d.pop('z')
-        raise(TestError('We did not get an error'))
-    except KeyError, e:
-        ok_(e.args[0]=='z')
-    else:
-        raise(TestError('We did not get the expected error. We got %r' % \
-                        e.args[0]))
+    assert_raises(TypeError, d.pop, 'a', 'z', 'y')
+    assert d.pop('a') == 1
+    assert 'a' not in d
+    assert d.pop('B') == 2
+    assert 'b' not in d
+    assert d.pop('c', 'u') == 3
+    assert 'c' not in d
+    assert d.pop('e', 'u') =='u'
+    assert 'e' not in d
+    assert_raises(KeyError, d.pop, 'z')
 
-def  test_delitem_environheaders():
-    """The name of this method pretty much explains it all"""
-    d = headers.EnvironHeaders({'CONTENT_LENGTH':10})
+def test_delitem_environheaders():
+    d = headers.EnvironHeaders({'CONTENT_LENGTH': '10'})
     del d['CONTENT-LENGTH']
-    ok_('CONTENT-LENGTH' not in d)
-    ok_(len(d)==0)
+    assert not d
     assert_raises(KeyError, d.__delitem__, 'CONTENT-LENGTH')

tests/test_misc.py

+import cgi
+from webob import html_escape
+from webob.multidict import *
+from nose.tools import eq_ as eq, assert_raises
+
+
+def test_html_escape():
+    for v, s in [
+        # unsafe chars
+        ('these chars: < > & "', 'these chars: &lt; &gt; &amp; &quot;'),
+        (' ', ' '),
+        ('&egrave;', '&amp;egrave;'),
+        # The apostrophe is *not* escaped, which some might consider to be
+        # a serious bug (see, e.g. http://www.cvedetails.com/cve/CVE-2010-2480/)
+        (u'the majestic m\xf8ose', 'the majestic m&#248;ose'),
+        #("'", "&#39;")
+
+        # 8-bit strings are passed through
+        (u'\xe9', '&#233;'),
+        (u'the majestic m\xf8ose'.encode('utf-8'), 'the majestic m\xc3\xb8ose'),
+
+        # ``None`` is treated specially, and returns the empty string.
+        (None, ''),
+
+        # Objects that define a ``__html__`` method handle their own escaping
+        (t_esc_HTML(), '<div>hello</div>'),
+
+        # Things that are not strings are converted to strings and then escaped
+        (42, '42'),
+        (Exception("expected a '<'."), "expected a '&lt;'."),
+
+        # If an object implements both ``__str__`` and ``__unicode__``, the latter
+        # is preferred
+        (t_esc_SuperMoose(), 'm&#248;ose'),
+        (t_esc_Unicode(), '&#233;'),
+        (t_esc_UnsafeAttrs(), '&lt;UnsafeAttrs&gt;'),
+    ]:
+        eq(html_escape(v), s)
+
+
+class t_esc_HTML(object):
+    def __html__(self):
+        return '<div>hello</div>'
+
+
+class t_esc_Unicode(object):
+    def __unicode__(self):
+        return u'\xe9'
+
+class t_esc_UnsafeAttrs(object):
+    attr = 'value'
+    def __getattr__(self):
+        return self.attr
+    def __repr__(self):
+        return '<UnsafeAttrs>'
+
+class t_esc_SuperMoose(object):
+    def __str__(self):
+        return u'm\xf8ose'.encode('UTF-8')
+    def __unicode__(self):
+        return u'm\xf8ose'
+
+
+
+
+
+
+def test_multidict():
+    d = MultiDict(a=1, b=2)
+    eq(d['a'], 1)
+    eq(d.getall('c'), [])
+
+    d.add('a', 2)
+    eq(d['a'], 2)
+    eq(d.getall('a'), [1, 2])
+
+    d['b'] = 4
+    eq(d.getall('b'), [4])
+    eq(d.keys(), ['a', 'a', 'b'])
+    eq(d.items(), [('a', 1), ('a', 2), ('b', 4)])
+    eq(d.mixed(), {'a': [1, 2], 'b': 4})
+
+    # test getone
+
+    # KeyError: "Multiple values match 'a': [1, 2]"
+    assert_raises(KeyError, d.getone, 'a')
+    eq(d.getone('b'), 4)
+    # KeyError: "Key not found: 'g'"
+    assert_raises(KeyError, d.getone, 'g')
+
+    eq(d.dict_of_lists(), {'a': [1, 2], 'b': [4]})
+    assert 'b' in d
+    assert 'e' not in d
+    d.clear()
+    assert 'b' not in d
+    d['a'] = 4
+    d.add('a', 5)
+    e = d.copy()
+    assert 'a' in e
+    e.clear()
+    e['f'] = 42
+    d.update(e)
+    eq(d, MultiDict([('a', 4), ('a', 5), ('f', 42)]))
+    f = d.pop('a')
+    eq(f, 4)
+    eq(d['a'], 5)
+
+
+    eq(d.pop('g', 42), 42)
+    assert_raises(KeyError, d.pop, 'n')
+    # TypeError: pop expected at most 2 arguments, got 3
+    assert_raises(TypeError, d.pop, 4, 2, 3)
+    d.setdefault('g', []).append(4)
+    eq(d, MultiDict([('a', 5), ('f', 42), ('g', [4])]))
+
+
+
+def test_multidict_init():
+    d = MultiDict([('a', 'b')], c=2)
+    eq(repr(d), "MultiDict([('a', 'b'), ('c', 2)])")
+    eq(d, MultiDict([('a', 'b')], c=2))
+
+    # TypeError: MultiDict can only be called with one positional argument
+    assert_raises(TypeError, MultiDict, 1, 2, 3)
+
+    # TypeError: MultiDict.view_list(obj) takes only actual list objects, not None
+    assert_raises(TypeError, MultiDict.view_list, None)
+
+
+
+def test_multidict_cgi():
+    fs = cgi.FieldStorage()
+    fs.filename = '\xc3\xb8'
+    plain = MultiDict(key='\xc3\xb8', fs=fs)
+    ua = UnicodeMultiDict(multi=plain, encoding='utf-8')
+    eq(ua.getall('key'), [u'\xf8'])
+    eq(repr(ua.getall('fs')), "[FieldStorage(None, u'\\xf8', [])]")
+    ub = UnicodeMultiDict(multi=ua, encoding='utf-8')
+    eq(ub.getall('key'), [u'\xf8'])
+    eq(repr(ub.getall('fs')), "[FieldStorage(None, u'\\xf8', [])]")

File contents unchanged.

tests/test_request.txt

     d
     --foobar--
 
+FakeCGIBody have both readline and readlines methods:
+
+    >>> req_ = Request.blank('/posty')
+    >>> req_.method = 'POST'
+    >>> req_.content_type = 'multipart/form-data; boundary="foobar"'
+    >>> req_.body = '''\
+    ... --foobar
+    ... Content-Disposition: form-data; name="a"
+    ...
+    ... b
+    ... --foobar
+    ... Content-Disposition: form-data; name="upload"; filename="test.html"
+    ... Content-Type: text/html
+    ...
+    ... <html>Some text...</html>
+    ... --foobar--
+    ... '''
+    >>> req_.str_POST
+    MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html'))])
+    >>> print req_.body.replace('\r', '') # doctest: +REPORT_UDIFF
+    --foobar
+    Content-Disposition: form-data; name="a"
+    <BLANKLINE>
+    b
+    --foobar
+    Content-Disposition: form-data; name="upload"; filename="test.html"
+    Content-type: text/html
+    <BLANKLINE>
+    <html>Some text...</html>
+    --foobar--
+    >>> req_.POST['c'] = 'd'
+    >>> req_.str_POST
+    MultiDict([('a', 'b'), ('upload', FieldStorage('upload', 'test.html')), ('c', 'd')])
+    >>> req_.body_file.readline()
+    '--foobar\r\n'
+    >>> [n.replace('\r', '') for n in req_.body_file.readlines()]
+    ['Content-Disposition: form-data; name="a"\n', '\n', 'b\n', '--foobar\n', 'Content-Disposition: form-data; name="upload"; filename="test.html"\n', 'Content-type: text/html\n', '\n', '<html>Some text...</html>\n', '--foobar\n', 'Content-Disposition: form-data; name="c"\n', '\n', 'd\n', '--foobar--']
+
 Also reparsing works through the fake body:
 
     >>> del req.environ['webob._parsed_post_vars']
-import string, re
-import time
+import re, time, string
 from datetime import datetime, date, timedelta
 
 __all__ = ['Cookie']
                 if ckey:
                     self[ckey][key] = _unquote(val)
             elif key[0] == '$':
+                # RFC2109: NAMEs that begin with $ are reserved for other uses
+                # and must not be used by applications.
                 continue
             else:
                 self[key] = _unquote(val)
 #
 
 _re_quoted = r'"(?:[^\"]|\.)*"'  # any doublequoted string
-_legal_special_chars = "~!@#$%^&*()_+=-`.?|:/(){}<>',"
+_legal_special_chars = "~!@#$%^&*()_+=-`.?|:/(){}<>'"
 _re_legal_char  = r"[\w\d%s]" % ''.join(map(r'\%s'.__mod__, _legal_special_chars))
 _re_expires_val = r"\w{3},\s[\w\d-]{9,11}\s[\d:]{8}\sGMT"
 _rx_cookie = re.compile(
 
 _trans_noop = ''.join(chr(x) for x in xrange(256))
 
+# these chars can be in cookie value w/o causing it to be quoted
 _no_escape_special_chars = "!#$%&'*+-.^_`|~/"
 _no_escape_chars = string.ascii_letters + string.digits + _no_escape_special_chars
-#_no_escape_chars = string.ascii_letters + string.digits + _legal_special_chars
+# these chars never need to be quoted
 _escape_noop_chars = _no_escape_chars+':, '
+# this is a map used to escape the values
 _escape_map = dict((chr(i), '\\%03o' % i) for i in xrange(256))
 _escape_map.update(zip(_escape_noop_chars, _escape_noop_chars))
 _escape_map['"'] = '\\"'
     if needs_quoting(v):
         return '"' + ''.join(map(_escape_char, v)) + '"'
     return v
-
-
-
-#print _quote(serialize_cookie_date(0))
-
-#assert _quote('a"\xff') == r'"a\"\377"'
-#assert _unquote(r'"a\"\377"') == 'a"\xff'
-
-#print repr(Cookie('foo=bar'))
-# c = Cookie('bad_cookie=; expires="... GMT"; Max-Age=0; Path=/')
-# print c
-#print c['bad_cookie'].items()
         or a temporary file.
         """
         length = self.content_length
-        if length:
+        if length is not None:
             did_copy = self._copy_body_tempfile()
             if not did_copy:
                 self.body = self.body_file.read(length)
             if not line:
                 # end of headers
                 break
-            if line and (line.startswith(' ') or line.startswith('\t')):
-                # Continuation
-                assert last_header is not None
-                headers[last_header] = '%s, %s' % (headers[last_header], line.strip())
             else:
                 try:
                     last_header, value = line.split(':', 1)
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.