Skip to content

Commit

Permalink
All tests pass under Python-3
Browse files Browse the repository at this point in the history
  • Loading branch information
kedder committed Mar 19, 2013
1 parent 3ebb335 commit 7310f88
Show file tree
Hide file tree
Showing 15 changed files with 156 additions and 93 deletions.
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -59,6 +59,7 @@
'pytz',
'WebTest >= 2.0.2',
'WSGIProxy2',
'six',
],
extras_require={
'test': tests_require,
Expand Down
36 changes: 18 additions & 18 deletions src/zope/testbrowser/README.txt
Expand Up @@ -30,7 +30,7 @@ applications. It can be imported from ``zope.testbrowser.wsgi``:
>>> from zope.testbrowser.wsgi import Browser
>>> from zope.testbrowser.testing import demo_app
>>> browser = Browser('http://localhost/', wsgi_app=demo_app)
>>> print browser.contents
>>> print(browser.contents)
Hello world!
...

Expand Down Expand Up @@ -125,7 +125,7 @@ Page Contents

The contents of the current page are available:

>>> print browser.contents
>>> print(browser.contents)
<html>
<head>
<title>Simple Page</title>
Expand Down Expand Up @@ -205,7 +205,7 @@ library):

The headers can be accessed as a string:

>>> print browser.headers
>>> print(browser.headers)
Status: 200 OK
Content-Length: 123
Content-Type: text/html;charset=utf-8
Expand Down Expand Up @@ -236,9 +236,9 @@ a few examples.
'bar'
>>> browser.cookies.keys()
['foo']
>>> browser.cookies.values()
>>> list(browser.cookies.values())
['bar']
>>> browser.cookies.items()
>>> list(browser.cookies.items())
[('foo', 'bar')]
>>> 'foo' in browser.cookies
True
Expand All @@ -254,9 +254,9 @@ a few examples.
>>> sorted(browser.cookies.items())
[('foo', 'bar'), ('sha', 'zam')]
>>> browser.open('http://localhost/get_cookie.html')
>>> print browser.headers.get('set-cookie')
>>> print(browser.headers.get('set-cookie'))
None
>>> print browser.contents # server got the cookie change
>>> print(browser.contents) # server got the cookie change
foo: bar
sha: zam
>>> sorted(browser.cookies.items())
Expand Down Expand Up @@ -842,9 +842,9 @@ The various types of controls are demonstrated here.
True
>>> ctrl.value is None
True
>>> import cStringIO
>>> import io

>>> ctrl.add_file(cStringIO.StringIO('File contents'),
>>> ctrl.add_file(io.BytesIO(b'File contents'),
... 'text/plain', 'test.txt')

The file control (like the other controls) also knows if it is disabled
Expand Down Expand Up @@ -1109,7 +1109,7 @@ Both the submit and image type should be clickable and submit the form:

>>> browser.getControl('Text Control').value = 'Other Text'
>>> browser.getControl('Submit').click()
>>> print browser.contents
>>> print(browser.contents)
<html>
...
<em>Other Text</em>
Expand All @@ -1136,7 +1136,7 @@ All the above also holds true for the image control:
>>> browser.open('http://localhost/@@/testbrowser/controls.html')
>>> browser.getControl('Text Control').value = 'Other Text'
>>> browser.getControl(name='image-value').click()
>>> print browser.contents
>>> print(browser.contents)
<html>
...
<em>Other Text</em>
Expand All @@ -1161,7 +1161,7 @@ But when sending an image, you can also specify the coordinate you clicked:

>>> browser.open('http://localhost/@@/testbrowser/controls.html')
>>> browser.getControl(name='image-value').click((50,25))
>>> print browser.contents
>>> print(browser.contents)
<html>
...
<em>50</em>
Expand Down Expand Up @@ -1232,7 +1232,7 @@ browser, you can get control objects, but limited to the current form...
...and submit the form.

>>> form.submit('Submit')
>>> print browser.contents
>>> print(browser.contents)
<html>
...
<em>First Text</em>
Expand Down Expand Up @@ -1319,7 +1319,7 @@ helpful when testing Ajax methods.
Let's visit a page that echos some interesting values from it's request:

>>> browser.open('http://localhost/echo.html')
>>> print browser.contents
>>> print(browser.contents)
HTTP_ACCEPT_LANGUAGE: en-US
HTTP_CONNECTION: close
HTTP_HOST: localhost
Expand All @@ -1333,7 +1333,7 @@ and an optional content type. If we just pass a string, then
a URL-encoded query string is assumed:

>>> browser.post('http://localhost/echo.html', 'x=1&y=2')
>>> print browser.contents
>>> print(browser.contents)
CONTENT_LENGTH: 7
CONTENT_TYPE: application/x-www-form-urlencoded
HTTP_ACCEPT_LANGUAGE: en-US
Expand All @@ -1352,7 +1352,7 @@ We can pass a content-type explicitly:

>>> browser.post('http://localhost/echo.html',
... '{"x":1,"y":2}', 'application/x-javascript')
>>> print browser.contents
>>> print(browser.contents)
CONTENT_LENGTH: 13
CONTENT_TYPE: application/x-javascript
HTTP_ACCEPT_LANGUAGE: en-US
Expand Down Expand Up @@ -1482,11 +1482,11 @@ Depending on the scheme of the request the variable wsgi.url_scheme will be set
correctly on the request:

>>> browser.open('http://localhost/echo_one.html?var=wsgi.url_scheme')
>>> print browser.contents
>>> print(browser.contents)
'http'

>>> browser.open('https://localhost/echo_one.html?var=wsgi.url_scheme')
>>> print browser.contents
>>> print(browser.contents)
'https'

see http://www.python.org/dev/peps/pep-3333/ for details.
4 changes: 4 additions & 0 deletions src/zope/testbrowser/_compat.py
Expand Up @@ -24,14 +24,18 @@
if PYTHON2:
import Cookie as httpcookies
import urlparse
from urllib import quote as url_quote
import httplib as httpclient
import urllib2 as urllib_request
from urllib import urlencode
from UserDict import DictMixin
class MutableMapping(object, DictMixin):
pass
else:
import http.cookies as httpcookies
import urllib.parse as urlparse
from urllib.parse import quote as url_quote
import urllib.request as urllib_request
from urllib.parse import urlencode
import http.client as httpclient
from collections import MutableMapping
25 changes: 15 additions & 10 deletions src/zope/testbrowser/browser.py
Expand Up @@ -18,18 +18,17 @@
import sys
import re
import time
import urlparse
import io
import robotparser
from contextlib import contextmanager

import six
from six.moves import urllib_robotparser

from zope.interface import implementer
from wsgiproxy.proxies import TransparentProxy
from bs4 import BeautifulSoup

from zope.testbrowser import interfaces
from zope.testbrowser._compat import httpclient, PYTHON2, urllib_request
from zope.testbrowser._compat import httpclient, PYTHON2, urllib_request, urlparse
import zope.testbrowser.cookies

import webtest
Expand Down Expand Up @@ -70,7 +69,7 @@ def _assertAllowed(self, url):
# Unrestricted mode: retrieve robots.txt and check against it
robotsurl = urlparse.urlunsplit((parsed.scheme, parsed.netloc,
'/robots.txt', '', ''))
rp = robotparser.RobotFileParser()
rp = urllib_robotparser.RobotFileParser()
rp.set_url(robotsurl)
rp.read()
if not rp.can_fetch("*", url):
Expand Down Expand Up @@ -194,20 +193,24 @@ def goBack(self, count=1):
def contents(self):
"""See zope.testbrowser.interfaces.IBrowser"""
if self._response is not None:
return self._response.body
return self.toStr(self._response.body)
else:
return None

@property
def headers(self):
"""See zope.testbrowser.interfaces.IBrowser"""
resptxt = []
resptxt.append(b'Status: '+self._response.status)
resptxt.append('Status: %s' % self._response.status)
for h, v in sorted(self._response.headers.items()):
resptxt.append(str("%s: %s" % (h, v)))

stream = io.BytesIO(b'\n'.join(resptxt))
return httpclient.HTTPMessage(stream)
inp = '\n'.join(resptxt)
stream = io.BytesIO(inp.encode('latin1'))
if PYTHON2:
return httpclient.HTTPMessage(stream)
else:
return httpclient.parse_headers(stream)

@property
def cookies(self):
Expand Down Expand Up @@ -464,6 +467,8 @@ def toStr(self, s):
return None
if PYTHON2 and not isinstance(s, bytes):
return s.encode(self._response.charset)
if not PYTHON2 and isinstance(s, bytes):
return s.decode(self._response.charset)
return s

def controlFactory(name, wtcontrols, elemindex, browser, include_subcontrols=False):
Expand Down Expand Up @@ -1235,7 +1240,7 @@ def pystonesPerSecond(self):
# http://www.zope.org/Collectors/Zope/2268
from test import pystone
if self._pystones_per_second == None:
self._pystones_per_second = pystone.pystones(pystone.LOOPS/10)[1]
self._pystones_per_second = pystone.pystones(pystone.LOOPS//10)[1]
return self._pystones_per_second

def _getTime(self):
Expand Down
40 changes: 32 additions & 8 deletions src/zope/testbrowser/cookies.py
Expand Up @@ -16,8 +16,9 @@
import time
import urllib

from zope.testbrowser._compat import (httpcookies, urlparse,
from zope.testbrowser._compat import (httpcookies, urlparse, url_quote,
MutableMapping, urllib_request)
import six
import pytz
import zope.interface
from zope.testbrowser import interfaces, utils
Expand All @@ -30,12 +31,14 @@ class _StubHTTPMessage(object):
def __init__(self, cookies):
self._cookies = cookies

def getheaders(self, name):
def getheaders(self, name, default=[]):
if name.lower() != 'set-cookie':
return []
return default
else:
return self._cookies

get_all = getheaders


class _StubResponse(object):
def __init__(self, cookies):
Expand Down Expand Up @@ -108,7 +111,10 @@ def header(self):
if not request.has_header('Cookie'):
return ''

return request.get_header('Cookie')
hdr = request.get_header('Cookie')
# We need a predictable order of cookies for tests, so we reparse and
# sort the header here.
return '; '.join(sorted(hdr.split('; ')))

def __str__(self):
return self.header
Expand All @@ -129,7 +135,7 @@ def _raw_cookies(self):

# Sort cookies so that the longer paths would come first. This allows
# masking parent cookies.
cookies.sort(key=lambda c: len(c.path), reverse=True)
cookies.sort(key=lambda c: (len(c.path), len(c.domain)), reverse=True)
return cookies

def _get_cookies(self, key=None):
Expand Down Expand Up @@ -311,7 +317,8 @@ def _setCookie(self, name, value, domain, expires, path, secure, comment,
'cannot create cookie without request or domain')
c = httpcookies.SimpleCookie()
name = str(name)
c[name] = value.encode('utf8')
# Cookie value must be native string
c[name] = value.encode('utf8') if not six.PY3 else value
if secure:
c[name]['secure'] = True
if domain:
Expand All @@ -321,7 +328,7 @@ def _setCookie(self, name, value, domain, expires, path, secure, comment,
if expires:
c[name]['expires'] = expiration_string(expires)
if comment:
c[name]['comment'] = urllib.quote(
c[name]['comment'] = url_quote(
comment.encode('utf-8'), safe="/?:@&+")
if port:
c[name]['port'] = port
Expand Down Expand Up @@ -360,7 +367,7 @@ def _is_expired(self, value, now): # now = int(time.time())
return True
elif value <= dnow:
return True
elif isinstance(value, basestring):
elif isinstance(value, six.string_types):
if datetime.datetime.fromtimestamp(
utils.http2time(value),
pytz.UTC) <= dnow:
Expand All @@ -378,3 +385,20 @@ def clearAllSession(self):

def clearAll(self):
self._jar.clear()

def pop(self, k, *args):
"""See zope.interface.common.mapping.IExtendedWriteMapping
"""
# Python3' MutableMapping doesn't offer pop() with variable arguments,
# so we are reimplementing it here as defined in IExtendedWriteMapping
super(Cookies, self).pop(k, *args)

def itervalues(self):
# Method, missing in Py3' MutableMapping, but required by
# IIterableMapping
return self.values()

def iterkeys(self):
# Method, missing in Py3' MutableMapping, but required by
# IIterableMapping
return self.keys()

0 comments on commit 7310f88

Please sign in to comment.