wsgi_intercept-0.5.1/ 0000755 0000765 0000024 00000000000 11660472720 014770 5 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/AUTHORS.txt 0000644 0000765 0000024 00000000166 11660467065 016667 0 ustar cdent staff 0000000 0000000 AUTHORS
Titus Brown
Kumar McMillan
CONTRIBUTORS
David "Whit" Morris
Jeffrey Cousens
Gary Bernhardt
Peter Henderson
wsgi_intercept-0.5.1/CHANGELOG.txt 0000644 0000765 0000024 00000001662 11660467065 017033 0 ustar cdent staff 0000000 0000000
0.5.0
-----
- issue19: Better HTTPS support in httplib.
0.4.0
-----
- issue13: ported a bugfix from twill that fixed the content response getting duplicated when using the Django [and possibly other?] wsgi handlers (reported by Ed Summers)
0.3.4
-----
- issue11: fixed bug where query string and path info was unquoted when it shouldn't be
0.3.3
-----
- issue9: fixed major bug where HTTPS was not supported by all intercept mechanisms except for zope.textbrowser (this was originally reported by Robert Leftwich on the twill list).
0.3.2
-----
- issue7: fixed masked exception in httplib2 (patch by Gary Bernhardt)
- issue8: fixed missing QUERY_STRING in WSGI environment and un-closed WSGI iterator (patch by Gary Bernhardt)
0.3.1
-----
- issue3: made urllib2 intercept compatible with python 2.5 (patch by Jeffrey Cousens)
- issue4: removed unnecessary import statement in wsgi_intercept.zope_testbrowser
0.3
---
- first release wsgi_intercept-0.5.1/docs/ 0000755 0000765 0000024 00000000000 11660472720 015720 5 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/docs/index.rst 0000644 0000765 0000024 00000000465 11660467065 017574 0 ustar cdent staff 0000000 0000000 ===============================================================================
wsgi_intercept: installs a WSGI application in place of a real URI for testing.
===============================================================================
.. contents::
.. include_docstring:: ./wsgi_intercept/__init__.py
wsgi_intercept-0.5.1/docs/README.txt 0000644 0000765 0000024 00000000437 11660467065 017430 0 ustar cdent staff 0000000 0000000
Build docs as HTML with::
python setup.py build_docs
To publish docs to stdout in Google Code wiki format::
python setup.py publish_docs --google-user=x --google-password=x
Just use literally "x" for user / pass since logging in and publishing is not implemented. Yea! wsgi_intercept-0.5.1/LICENSE.txt 0000644 0000765 0000024 00000002150 11660467065 016617 0 ustar cdent staff 0000000 0000000 MIT License
see http://en.wikipedia.org/wiki/MIT_License
Copyright (c) 2007 Titus Brown, Kumar McMillan
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE. wsgi_intercept-0.5.1/PKG-INFO 0000644 0000765 0000024 00000022127 11660472720 016071 0 ustar cdent staff 0000000 0000000 Metadata-Version: 1.0
Name: wsgi_intercept
Version: 0.5.1
Summary: installs a WSGI application in place of a real URI for testing.
Home-page: http://code.google.com/p/wsgi-intercept/
Author: Titus Brown, Kumar McMillan
Author-email: kumar.mcmillan@gmail.com
License: MIT License
Description: Introduction
============
Testing a WSGI application normally involves starting a server at a local host and port, then pointing your test code to that address. Instead, this library lets you intercept calls to any specific host/port combination and redirect them into a `WSGI application`_ importable by your test program. Thus, you can avoid spawning multiple processes or threads to test your Web app.
How Does It Work?
=================
``wsgi_intercept`` works by replacing ``httplib.HTTPConnection`` with a subclass, ``wsgi_intercept.WSGI_HTTPConnection``. This class then redirects specific server/port combinations into a WSGI application by emulating a socket. If no intercept is registered for the host and port requested, those requests are passed on to the standard handler.
The functions ``add_wsgi_intercept(host, port, app_create_fn, script_name='')`` and ``remove_wsgi_intercept(host,port)`` specify which URLs should be redirect into what applications. Note especially that ``app_create_fn`` is a *function object* returning a WSGI application; ``script_name`` becomes ``SCRIPT_NAME`` in the WSGI app's environment, if set.
Install
=======
::
easy_install wsgi_intercept
(The ``easy_install`` command is bundled with the setuptools_ module)
To use a `development version`_ of wsgi_intercept, run::
easy_install http://wsgi-intercept.googlecode.com/svn/trunk
.. _setuptools: http://cheeseshop.python.org/pypi/setuptools/
.. _development version: http://wsgi-intercept.googlecode.com/svn/trunk/#egg=wsgi_intercept-dev
Packages Intercepted
====================
Unfortunately each of the Web testing frameworks uses its own specific mechanism for making HTTP call-outs, so individual implementations are needed. Below are the packages supported and how to create an intercept.
urllib2
-------
urllib2_ is a standard Python module, and ``urllib2.urlopen`` is a pretty
normal way to open URLs.
The following code will install the WSGI intercept stuff as a default
urllib2 handler: ::
>>> from wsgi_intercept.urllib2_intercept import install_opener
>>> install_opener() #doctest: +ELLIPSIS
>>> import wsgi_intercept
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> import urllib2
>>> urllib2.urlopen('http://some_host:80/').read()
'WSGI intercept successful!\n'
The only tricky bit in there is that different handler classes need to
be constructed for Python 2.3 and Python 2.4, because the httplib
interface changed between those versions.
.. _urllib2: http://docs.python.org/lib/module-urllib2.html
httplib2
--------
httplib2_ is a 3rd party extension of the built-in ``httplib``. To intercept
requests, it is similar to urllib2::
>>> from wsgi_intercept.httplib2_intercept import install
>>> install()
>>> import wsgi_intercept
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> import httplib2
>>> resp, content = httplib2.Http().request('http://some_host:80/', 'GET')
>>> content
'WSGI intercept successful!\n'
(Contributed by `David "Whit" Morris`_.)
.. _httplib2: http://code.google.com/p/httplib2/
.. _David "Whit" Morris: http://public.xdi.org/=whit
webtest
-------
webtest_ is an extension to ``unittest`` that has some nice functions for
testing Web sites.
To install the WSGI intercept handler, do ::
>>> import wsgi_intercept.webtest_intercept
>>> class WSGI_Test(wsgi_intercept.webtest_intercept.WebCase):
... HTTP_CONN = wsgi_intercept.WSGI_HTTPConnection
... HOST='localhost'
... PORT=80
...
... def setUp(self):
... wsgi_intercept.add_wsgi_intercept(self.HOST, self.PORT, create_fn)
...
>>>
.. _webtest: http://www.cherrypy.org/file/trunk/cherrypy/test/webtest.py
webunit
-------
webunit_ is another unittest-like framework that contains nice functions
for Web testing. (funkload_ uses webunit, too.)
webunit needed to be patched to support different scheme handlers.
The patched package is in webunit/wsgi_webunit/, and the only
file that was changed was webunittest.py; the original is in
webunittest-orig.py.
To install the WSGI intercept handler, do ::
>>> from httplib import HTTP
>>> import wsgi_intercept.webunit_intercept
>>> class WSGI_HTTP(HTTP):
... _connection_class = wsgi_intercept.WSGI_HTTPConnection
...
>>> class WSGI_WebTestCase(wsgi_intercept.webunit_intercept.WebTestCase):
... scheme_handlers = dict(http=WSGI_HTTP)
...
... def setUp(self):
... wsgi_intercept.add_wsgi_intercept('127.0.0.1', 80, create_fn)
...
>>>
.. _webunit: http://mechanicalcat.net/tech/webunit/
mechanize
---------
mechanize_ is John J. Lee's port of Perl's WWW::Mechanize to Python.
It mimics a browser. (It's also what's behind twill_.)
>>> import wsgi_intercept.mechanize_intercept
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> b = wsgi_intercept.mechanize_intercept.Browser()
>>> response = b.open('http://some_host:80')
>>> response.read()
'WSGI intercept successful!\n'
.. _mechanize: http://wwwsearch.sf.net/
zope.testbrowser
----------------
zope.testbrowser_ is a prettified interface to mechanize_ that is used
primarily for testing Zope applications.
zope.testbrowser is also pretty easy ::
>>> import wsgi_intercept.zope_testbrowser
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> b = wsgi_intercept.zope_testbrowser.WSGI_Browser('http://some_host:80/')
>>> b.contents
'WSGI intercept successful!\n'
.. _zope.testbrowser: http://www.python.org/pypi/zope.testbrowser
History
=======
Pursuant to Ian Bicking's `"best Web testing framework"`_ post,
Titus Brown put together an `in-process HTTP-to-WSGI interception mechanism`_ for
his own Web testing system, twill_. Because the mechanism is pretty
generic -- it works at the httplib level -- Titus decided to try adding it into
all of the *other* Python Web testing frameworks.
This is the result.
Mocking your HTTP Server
========================
Marc Hedlund has gone one further, and written a full-blown mock HTTP
server for wsgi_intercept. Combined with wsgi_intercept itself, this
lets you entirely replace client calls to a server with a mock setup
that hits neither the network nor server code. You can see his work
in the file ``mock_http.py``. Run ``mock_http.py`` to see a test.
.. _twill: http://www.idyll.org/~t/www-tools/twill.html
.. _"best Web testing framework": http://blog.ianbicking.org/best-of-the-web-app-test-frameworks.html
.. _in-process HTTP-to-WSGI interception mechanism: http://www.advogato.org/person/titus/diary.html?start=119
.. _WSGI application: http://www.python.org/peps/pep-0333.html
.. _funkload: http://funkload.nuxeo.org/
Project Home
============
If you aren't already there, this project lives on `Google Code`_. Please submit all bugs, patches, failing tests, et cetera using the `Issue Tracker`_
.. _Google Code: http://code.google.com/p/wsgi-intercept/
.. _Issue Tracker: http://code.google.com/p/wsgi-intercept/issues/list
Platform: UNKNOWN
wsgi_intercept-0.5.1/README.txt 0000644 0000765 0000024 00000000013 11660467065 016466 0 ustar cdent staff 0000000 0000000 see ./docs/ wsgi_intercept-0.5.1/run-tests 0000644 0000765 0000024 00000000203 11660467065 016660 0 ustar cdent staff 0000000 0000000 #!/bin/sh
if [ -e "`which tox`" ]; then
tox $@
else
echo "**** install tox to run the tests http://codespeak.net/tox/"
fi
wsgi_intercept-0.5.1/setup.cfg 0000644 0000765 0000024 00000000235 11660472720 016611 0 ustar cdent staff 0000000 0000000 [global]
command_packages = wsgi_intercept.setup_cmd
[nosetests]
verbosity = 2
with-doctest = 1
[egg_info]
tag_build =
tag_date = 0
tag_svn_revision = 0
wsgi_intercept-0.5.1/setup.py 0000644 0000765 0000024 00000003261 11660467065 016512 0 ustar cdent staff 0000000 0000000
from setuptools import setup, find_packages
import compiler, pydoc
from compiler import visitor
class ModuleVisitor(object):
def __init__(self):
self.mod_doc = None
self.mod_version = None
def default(self, node):
for child in node.getChildNodes():
self.visit(child)
def visitModule(self, node):
self.mod_doc = node.doc
self.default(node)
def visitAssign(self, node):
if self.mod_version:
return
asn = node.nodes[0]
assert asn.name == '__version__', (
"expected __version__ node: %s" % asn)
self.mod_version = node.expr.value
self.default(node)
def get_module_meta(modfile):
ast = compiler.parseFile(modfile)
modnode = ModuleVisitor()
visitor.walk(ast, modnode)
if modnode.mod_doc is None:
raise RuntimeError(
"could not parse doc string from %s" % modfile)
if modnode.mod_version is None:
raise RuntimeError(
"could not parse __version__ from %s" % modfile)
return (modnode.mod_version,) + pydoc.splitdoc(modnode.mod_doc)
version, description, long_description = get_module_meta("./wsgi_intercept/__init__.py")
setup(
name = 'wsgi_intercept',
version = version,
author = 'Titus Brown, Kumar McMillan',
author_email = 'kumar.mcmillan@gmail.com',
description = description,
url="http://code.google.com/p/wsgi-intercept/",
long_description = long_description,
license = 'MIT License',
packages = find_packages(),
test_suite = "nose.collector",
tests_require=['nose', 'Paste', 'httplib2', 'mechanize', 'mechanoid', 'WebTest', 'zope.testbrowser', 'webunit'],
) wsgi_intercept-0.5.1/tox.ini 0000644 0000765 0000024 00000000272 11660470757 016314 0 ustar cdent staff 0000000 0000000
[tox]
envlist=py27
[testenv]
deps=nose
Paste
httplib2
mechanize
mechanoid
WebTest
zope.testbrowser
webunit
docutils
commands=
nosetests []
wsgi_intercept-0.5.1/wsgi_intercept/ 0000755 0000765 0000024 00000000000 11660472720 020016 5 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/wsgi_intercept/__init__.py 0000644 0000765 0000024 00000046672 11660472640 022147 0 ustar cdent staff 0000000 0000000
"""installs a WSGI application in place of a real URI for testing.
Introduction
============
Testing a WSGI application normally involves starting a server at a local host and port, then pointing your test code to that address. Instead, this library lets you intercept calls to any specific host/port combination and redirect them into a `WSGI application`_ importable by your test program. Thus, you can avoid spawning multiple processes or threads to test your Web app.
How Does It Work?
=================
``wsgi_intercept`` works by replacing ``httplib.HTTPConnection`` with a subclass, ``wsgi_intercept.WSGI_HTTPConnection``. This class then redirects specific server/port combinations into a WSGI application by emulating a socket. If no intercept is registered for the host and port requested, those requests are passed on to the standard handler.
The functions ``add_wsgi_intercept(host, port, app_create_fn, script_name='')`` and ``remove_wsgi_intercept(host,port)`` specify which URLs should be redirect into what applications. Note especially that ``app_create_fn`` is a *function object* returning a WSGI application; ``script_name`` becomes ``SCRIPT_NAME`` in the WSGI app's environment, if set.
Install
=======
::
easy_install wsgi_intercept
(The ``easy_install`` command is bundled with the setuptools_ module)
To use a `development version`_ of wsgi_intercept, run::
easy_install http://wsgi-intercept.googlecode.com/svn/trunk
.. _setuptools: http://cheeseshop.python.org/pypi/setuptools/
.. _development version: http://wsgi-intercept.googlecode.com/svn/trunk/#egg=wsgi_intercept-dev
Packages Intercepted
====================
Unfortunately each of the Web testing frameworks uses its own specific mechanism for making HTTP call-outs, so individual implementations are needed. Below are the packages supported and how to create an intercept.
urllib2
-------
urllib2_ is a standard Python module, and ``urllib2.urlopen`` is a pretty
normal way to open URLs.
The following code will install the WSGI intercept stuff as a default
urllib2 handler: ::
>>> from wsgi_intercept.urllib2_intercept import install_opener
>>> install_opener() #doctest: +ELLIPSIS
>>> import wsgi_intercept
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> import urllib2
>>> urllib2.urlopen('http://some_host:80/').read()
'WSGI intercept successful!\\n'
The only tricky bit in there is that different handler classes need to
be constructed for Python 2.3 and Python 2.4, because the httplib
interface changed between those versions.
.. _urllib2: http://docs.python.org/lib/module-urllib2.html
httplib2
--------
httplib2_ is a 3rd party extension of the built-in ``httplib``. To intercept
requests, it is similar to urllib2::
>>> from wsgi_intercept.httplib2_intercept import install
>>> install()
>>> import wsgi_intercept
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> import httplib2
>>> resp, content = httplib2.Http().request('http://some_host:80/', 'GET')
>>> content
'WSGI intercept successful!\\n'
(Contributed by `David "Whit" Morris`_.)
.. _httplib2: http://code.google.com/p/httplib2/
.. _David "Whit" Morris: http://public.xdi.org/=whit
webtest
-------
webtest_ is an extension to ``unittest`` that has some nice functions for
testing Web sites.
To install the WSGI intercept handler, do ::
>>> import wsgi_intercept.webtest_intercept
>>> class WSGI_Test(wsgi_intercept.webtest_intercept.WebCase):
... HTTP_CONN = wsgi_intercept.WSGI_HTTPConnection
... HOST='localhost'
... PORT=80
...
... def setUp(self):
... wsgi_intercept.add_wsgi_intercept(self.HOST, self.PORT, create_fn)
...
>>>
.. _webtest: http://www.cherrypy.org/file/trunk/cherrypy/test/webtest.py
webunit
-------
webunit_ is another unittest-like framework that contains nice functions
for Web testing. (funkload_ uses webunit, too.)
webunit needed to be patched to support different scheme handlers.
The patched package is in webunit/wsgi_webunit/, and the only
file that was changed was webunittest.py; the original is in
webunittest-orig.py.
To install the WSGI intercept handler, do ::
>>> from httplib import HTTP
>>> import wsgi_intercept.webunit_intercept
>>> class WSGI_HTTP(HTTP):
... _connection_class = wsgi_intercept.WSGI_HTTPConnection
...
>>> class WSGI_WebTestCase(wsgi_intercept.webunit_intercept.WebTestCase):
... scheme_handlers = dict(http=WSGI_HTTP)
...
... def setUp(self):
... wsgi_intercept.add_wsgi_intercept('127.0.0.1', 80, create_fn)
...
>>>
.. _webunit: http://mechanicalcat.net/tech/webunit/
mechanize
---------
mechanize_ is John J. Lee's port of Perl's WWW::Mechanize to Python.
It mimics a browser. (It's also what's behind twill_.)
>>> import wsgi_intercept.mechanize_intercept
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> b = wsgi_intercept.mechanize_intercept.Browser()
>>> response = b.open('http://some_host:80')
>>> response.read()
'WSGI intercept successful!\\n'
.. _mechanize: http://wwwsearch.sf.net/
zope.testbrowser
----------------
zope.testbrowser_ is a prettified interface to mechanize_ that is used
primarily for testing Zope applications.
zope.testbrowser is also pretty easy ::
>>> import wsgi_intercept.zope_testbrowser
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> b = wsgi_intercept.zope_testbrowser.WSGI_Browser('http://some_host:80/')
>>> b.contents
'WSGI intercept successful!\\n'
.. _zope.testbrowser: http://www.python.org/pypi/zope.testbrowser
History
=======
Pursuant to Ian Bicking's `"best Web testing framework"`_ post,
Titus Brown put together an `in-process HTTP-to-WSGI interception mechanism`_ for
his own Web testing system, twill_. Because the mechanism is pretty
generic -- it works at the httplib level -- Titus decided to try adding it into
all of the *other* Python Web testing frameworks.
This is the result.
Mocking your HTTP Server
========================
Marc Hedlund has gone one further, and written a full-blown mock HTTP
server for wsgi_intercept. Combined with wsgi_intercept itself, this
lets you entirely replace client calls to a server with a mock setup
that hits neither the network nor server code. You can see his work
in the file ``mock_http.py``. Run ``mock_http.py`` to see a test.
.. _twill: http://www.idyll.org/~t/www-tools/twill.html
.. _"best Web testing framework": http://blog.ianbicking.org/best-of-the-web-app-test-frameworks.html
.. _in-process HTTP-to-WSGI interception mechanism: http://www.advogato.org/person/titus/diary.html?start=119
.. _WSGI application: http://www.python.org/peps/pep-0333.html
.. _funkload: http://funkload.nuxeo.org/
Project Home
============
If you aren't already there, this project lives on `Google Code`_. Please submit all bugs, patches, failing tests, et cetera using the `Issue Tracker`_
.. _Google Code: http://code.google.com/p/wsgi-intercept/
.. _Issue Tracker: http://code.google.com/p/wsgi-intercept/issues/list
"""
__version__ = '0.5.1'
import sys
from httplib import HTTPConnection
import urllib
from cStringIO import StringIO
import traceback
debuglevel = 0
# 1 basic
# 2 verbose
####
#
# Specify which hosts/ports to target for interception to a given WSGI app.
#
# For simplicity's sake, intercept ENTIRE host/port combinations;
# intercepting only specific URL subtrees gets complicated, because we don't
# have that information in the HTTPConnection.connect() function that does the
# redirection.
#
# format: key=(host, port), value=(create_app, top_url)
#
# (top_url becomes the SCRIPT_NAME)
_wsgi_intercept = {}
def add_wsgi_intercept(host, port, app_create_fn, script_name=''):
"""
Add a WSGI intercept call for host:port, using the app returned
by app_create_fn with a SCRIPT_NAME of 'script_name' (default '').
"""
_wsgi_intercept[(host, port)] = (app_create_fn, script_name)
def remove_wsgi_intercept(*args):
"""
Remove the WSGI intercept call for (host, port). If no arguments are given, removes all intercepts
"""
global _wsgi_intercept
if len(args)==0:
_wsgi_intercept = {}
else:
key = (args[0], args[1])
if _wsgi_intercept.has_key(key):
del _wsgi_intercept[key]
#
# make_environ: behave like a Web server. Take in 'input', and behave
# as if you're bound to 'host' and 'port'; build an environment dict
# for the WSGI app.
#
# This is where the magic happens, folks.
#
def make_environ(inp, host, port, script_name):
"""
Take 'inp' as if it were HTTP-speak being received on host:port,
and parse it into a WSGI-ok environment dictionary. Return the
dictionary.
Set 'SCRIPT_NAME' from the 'script_name' input, and, if present,
remove it from the beginning of the PATH_INFO variable.
"""
#
# parse the input up to the first blank line (or its end).
#
environ = {}
method_line = inp.readline()
content_type = None
content_length = None
cookies = []
for line in inp:
if not line.strip():
break
k, v = line.strip().split(':', 1)
v = v.lstrip()
#
# take care of special headers, and for the rest, put them
# into the environ with HTTP_ in front.
#
if k.lower() == 'content-type':
content_type = v
elif k.lower() == 'content-length':
content_length = v
elif k.lower() == 'cookie' or k.lower() == 'cookie2':
cookies.append(v)
else:
h = k.upper()
h = h.replace('-', '_')
environ['HTTP_' + h] = v
if debuglevel >= 2:
print 'HEADER:', k, v
#
# decode the method line
#
if debuglevel >= 2:
print 'METHOD LINE:', method_line
method, url, protocol = method_line.split(' ')
# clean the script_name off of the url, if it's there.
if not url.startswith(script_name):
script_name = '' # @CTB what to do -- bad URL. scrap?
else:
url = url[len(script_name):]
url = url.split('?', 1)
path_info = url[0]
query_string = ""
if len(url) == 2:
query_string = url[1]
if debuglevel:
print "method: %s; script_name: %s; path_info: %s; query_string: %s" % (method, script_name, path_info, query_string)
r = inp.read()
inp = StringIO(r)
#
# fill out our dictionary.
#
environ.update({ "wsgi.version" : (1,0),
"wsgi.url_scheme": "http",
"wsgi.input" : inp, # to read for POSTs
"wsgi.errors" : StringIO(),
"wsgi.multithread" : 0,
"wsgi.multiprocess" : 0,
"wsgi.run_once" : 0,
"PATH_INFO" : path_info,
"QUERY_STRING" : query_string,
"REMOTE_ADDR" : '127.0.0.1',
"REQUEST_METHOD" : method,
"SCRIPT_NAME" : script_name,
"SERVER_NAME" : host,
"SERVER_PORT" : str(port),
"SERVER_PROTOCOL" : protocol,
})
#
# query_string, content_type & length are optional.
#
if query_string:
environ['QUERY_STRING'] = query_string
if content_type:
environ['CONTENT_TYPE'] = content_type
if debuglevel >= 2:
print 'CONTENT-TYPE:', content_type
if content_length:
environ['CONTENT_LENGTH'] = content_length
if debuglevel >= 2:
print 'CONTENT-LENGTH:', content_length
#
# handle cookies.
#
if cookies:
environ['HTTP_COOKIE'] = "; ".join(cookies)
if debuglevel:
print 'WSGI environ dictionary:', environ
return environ
#
# fake socket for WSGI intercept stuff.
#
class wsgi_fake_socket:
"""
Handle HTTP traffic and stuff into a WSGI application object instead.
Note that this class assumes:
1. 'makefile' is called (by the response class) only after all of the
data has been sent to the socket by the request class;
2. non-persistent (i.e. non-HTTP/1.1) connections.
"""
def __init__(self, app, host, port, script_name):
self.app = app # WSGI app object
self.host = host
self.port = port
self.script_name = script_name # SCRIPT_NAME (app mount point)
self.inp = StringIO() # stuff written into this "socket"
self.write_results = [] # results from the 'write_fn'
self.results = None # results from running the app
self.output = StringIO() # all output from the app, incl headers
def makefile(self, *args, **kwargs):
"""
'makefile' is called by the HTTPResponse class once all of the
data has been written. So, in this interceptor class, we need to:
1. build a start_response function that grabs all the headers
returned by the WSGI app;
2. create a wsgi.input file object 'inp', containing all of the
traffic;
3. build an environment dict out of the traffic in inp;
4. run the WSGI app & grab the result object;
5. concatenate & return the result(s) read from the result object.
@CTB: 'start_response' should return a function that writes
directly to self.result, too.
"""
# dynamically construct the start_response function for no good reason.
def start_response(status, headers, exc_info=None):
# construct the HTTP request.
self.output.write("HTTP/1.0 " + status + "\n")
for k, v in headers:
self.output.write('%s: %s\n' % (k, v,))
self.output.write('\n')
def write_fn(s):
self.write_results.append(s)
return write_fn
# construct the wsgi.input file from everything that's been
# written to this "socket".
inp = StringIO(self.inp.getvalue())
# build the environ dictionary.
environ = make_environ(inp, self.host, self.port, self.script_name)
# run the application.
app_result = self.app(environ, start_response)
self.result = iter(app_result)
###
# read all of the results. the trick here is to get the *first*
# bit of data from the app via the generator, *then* grab & return
# the data passed back from the 'write' function, and then return
# the generator data. this is because the 'write' fn doesn't
# necessarily get called until the first result is requested from
# the app function.
#
# see twill tests, 'test_wrapper_intercept' for a test that breaks
# if this is done incorrectly.
try:
generator_data = None
try:
generator_data = self.result.next()
finally:
for data in self.write_results:
self.output.write(data)
if generator_data:
self.output.write(generator_data)
while 1:
data = self.result.next()
self.output.write(data)
except StopIteration:
pass
if hasattr(app_result, 'close'):
app_result.close()
if debuglevel >= 2:
print "***", self.output.getvalue(), "***"
# return the concatenated results.
return StringIO(self.output.getvalue())
def sendall(self, str):
"""
Save all the traffic to self.inp.
"""
if debuglevel >= 2:
print ">>>", str, ">>>"
self.inp.write(str)
def close(self):
"Do nothing, for now."
pass
#
# WSGI_HTTPConnection
#
class WSGI_HTTPConnection(HTTPConnection):
"""
Intercept all traffic to certain hosts & redirect into a WSGI
application object.
"""
def get_app(self, host, port):
"""
Return the app object for the given (host, port).
"""
key = (host, int(port))
app, script_name = None, None
if _wsgi_intercept.has_key(key):
(app_fn, script_name) = _wsgi_intercept[key]
app = app_fn()
return app, script_name
def connect(self):
"""
Override the connect() function to intercept calls to certain
host/ports.
If no app at host/port has been registered for interception then
a normal HTTPConnection is made.
"""
if debuglevel:
sys.stderr.write('connect: %s, %s\n' % (self.host, self.port,))
try:
(app, script_name) = self.get_app(self.host, self.port)
if app:
if debuglevel:
sys.stderr.write('INTERCEPTING call to %s:%s\n' % \
(self.host, self.port,))
self.sock = wsgi_fake_socket(app, self.host, self.port,
script_name)
else:
HTTPConnection.connect(self)
except Exception, e:
if debuglevel: # intercept & print out tracebacks
traceback.print_exc()
raise
#
# WSGI_HTTPSConnection
#
try:
from httplib import HTTPSConnection
except ImportError:
pass
else:
class WSGI_HTTPSConnection(HTTPSConnection, WSGI_HTTPConnection):
"""
Intercept all traffic to certain hosts & redirect into a WSGI
application object.
"""
def get_app(self, host, port):
"""
Return the app object for the given (host, port).
"""
key = (host, int(port))
app, script_name = None, None
if _wsgi_intercept.has_key(key):
(app_fn, script_name) = _wsgi_intercept[key]
app = app_fn()
return app, script_name
def connect(self):
"""
Override the connect() function to intercept calls to certain
host/ports.
If no app at host/port has been registered for interception then
a normal HTTPSConnection is made.
"""
if debuglevel:
sys.stderr.write('connect: %s, %s\n' % (self.host, self.port,))
try:
(app, script_name) = self.get_app(self.host, self.port)
if app:
if debuglevel:
sys.stderr.write('INTERCEPTING call to %s:%s\n' % \
(self.host, self.port,))
self.sock = wsgi_fake_socket(app, self.host, self.port,
script_name)
else:
HTTPSConnection.connect(self)
except Exception, e:
if debuglevel: # intercept & print out tracebacks
traceback.print_exc()
raise
wsgi_intercept-0.5.1/wsgi_intercept/httplib2_intercept.py 0000644 0000765 0000024 00000003167 11660467065 024212 0 ustar cdent staff 0000000 0000000
"""intercept HTTP connections that use httplib2
(see wsgi_intercept/__init__.py for examples)
"""
import httplib2
import wsgi_intercept
from httplib2 import SCHEME_TO_CONNECTION, HTTPConnectionWithTimeout, HTTPSConnectionWithTimeout
import sys
InterceptorMixin = wsgi_intercept.WSGI_HTTPConnection
# might make more sense as a decorator
def connect(self):
"""
Override the connect() function to intercept calls to certain
host/ports.
"""
if wsgi_intercept.debuglevel:
sys.stderr.write('connect: %s, %s\n' % (self.host, self.port,))
(app, script_name) = self.get_app(self.host, self.port)
if app:
if wsgi_intercept.debuglevel:
sys.stderr.write('INTERCEPTING call to %s:%s\n' % \
(self.host, self.port,))
self.sock = wsgi_intercept.wsgi_fake_socket(app,
self.host, self.port,
script_name)
else:
self._connect()
class HTTP_WSGIInterceptorWithTimeout(HTTPConnectionWithTimeout, InterceptorMixin):
_connect = httplib2.HTTPConnectionWithTimeout.connect
connect = connect
class HTTPS_WSGIInterceptorWithTimeout(HTTPSConnectionWithTimeout, InterceptorMixin):
_connect = httplib2.HTTPSConnectionWithTimeout.connect
connect = connect
def install():
SCHEME_TO_CONNECTION['http'] = HTTP_WSGIInterceptorWithTimeout
SCHEME_TO_CONNECTION['https'] = HTTPS_WSGIInterceptorWithTimeout
def uninstall():
SCHEME_TO_CONNECTION['http'] = HTTPConnectionWithTimeout
SCHEME_TO_CONNECTION['https'] = HTTPSConnectionWithTimeout
wsgi_intercept-0.5.1/wsgi_intercept/httplib_intercept.py 0000644 0000765 0000024 00000001030 11660467065 024113 0 ustar cdent staff 0000000 0000000
"""intercept HTTP connections that use httplib
(see wsgi_intercept/__init__.py for examples)
"""
import httplib
import wsgi_intercept
import sys
from httplib import (
HTTPConnection as OriginalHTTPConnection,
HTTPSConnection as OriginalHTTPSConnection)
def install():
httplib.HTTPConnection = wsgi_intercept.WSGI_HTTPConnection
httplib.HTTPSConnection = wsgi_intercept.WSGI_HTTPSConnection
def uninstall():
httplib.HTTPConnection = OriginalHTTPConnection
httplib.HTTPSConnection = OriginalHTTPSConnection
wsgi_intercept-0.5.1/wsgi_intercept/mechanize_intercept/ 0000755 0000765 0000024 00000000000 11660472720 024036 5 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/wsgi_intercept/mechanize_intercept/__init__.py 0000644 0000765 0000024 00000000215 11660467064 026152 0 ustar cdent staff 0000000 0000000 """intercept connections made using a mechanize Browser.
(see wsgi_intercept/__init__.py for examples)
"""
from wsgi_browser import Browser wsgi_intercept-0.5.1/wsgi_intercept/mechanize_intercept/wsgi_browser.py 0000644 0000765 0000024 00000002054 11660467064 027132 0 ustar cdent staff 0000000 0000000 """
A mechanize browser that redirects specified HTTP connections to a WSGI
object.
"""
from httplib import HTTP
from mechanize import Browser as MechanizeBrowser
from wsgi_intercept.urllib2_intercept import install_opener, uninstall_opener
try:
from mechanize import HTTPHandler
except ImportError:
# pre mechanize 0.1.0 it was a separate package
# (this will break if it is combined with a newer mechanize)
from ClientCookie import HTTPHandler
import sys, os.path
from wsgi_intercept.urllib2_intercept import WSGI_HTTPHandler, WSGI_HTTPSHandler
class Browser(MechanizeBrowser):
"""
A version of the mechanize browser class that
installs the WSGI intercept handler
"""
handler_classes = MechanizeBrowser.handler_classes.copy()
handler_classes['http'] = WSGI_HTTPHandler
handler_classes['https'] = WSGI_HTTPSHandler
def __init__(self, *args, **kwargs):
# install WSGI intercept handler.
install(self)
MechanizeBrowser.__init__(self, *args, **kwargs)
def install(browser):
install_opener() wsgi_intercept-0.5.1/wsgi_intercept/mock_http.py 0000644 0000765 0000024 00000004302 11660467065 022365 0 ustar cdent staff 0000000 0000000 #
# mock_http.py
#
# Written by Marc Hedlund .
# Released under the same terms as wsgi_intercept.
#
"""
This is a dirt-simple example of using wsgi_intercept to set up a mock
object HTTP server for testing HTTP clients.
"""
import sys
sys.path.insert(0, 'urllib2')
import unittest
import urllib2
import wsgi_intercept
from wsgi_intercept import urllib2_intercept as wsgi_urllib2
test_page = """
Mock HTTP Server
Mock HTTP Server
You have successfully reached the Mock HTTP Server.
"""
class MockHttpServer:
def __init__(self, port=8000):
"""Initializes the mock server on localhost port 8000. Use
urllib2.urlopen('http://localhost:8000') to reach the test
server. The constructor takes a 'port=' argument if you
want the server to listen on a different port."""
wsgi_intercept.add_wsgi_intercept('localhost', port, self.interceptor)
wsgi_urllib2.install_opener()
def handleResponse(self, environment, start_response):
"""Processes a request to the mock server, and returns a
String object containing the response document. The mock server
will send this to the client code, which can read it as a
StringIO document. This example always returns a successful
response to any request; a more intricate mock server could
examine the request environment to determine what sort of
response to give."""
status = "200 OK"
headers = [('Content-Type', 'text/html')]
start_response(status, headers)
return test_page
def interceptor(self):
"""Sets this class as the handler for intercepted urllib2
requests."""
return self.handleResponse
class MockHttpServerTest(unittest.TestCase):
"""Demonstrates the use of the MockHttpServer from client code."""
def setUp(self):
self.server = MockHttpServer()
def test_simple_get(self):
result = urllib2.urlopen('http://localhost:8000/')
self.assertEqual(result.read(), test_page)
if __name__ == "__main__":
unittest.main()
wsgi_intercept-0.5.1/wsgi_intercept/setup_cmd/ 0000755 0000765 0000024 00000000000 11660472720 022001 5 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/wsgi_intercept/setup_cmd/__init__.py 0000644 0000765 0000024 00000000001 11660467064 024106 0 ustar cdent staff 0000000 0000000
wsgi_intercept-0.5.1/wsgi_intercept/setup_cmd/build_docs.py 0000644 0000765 0000024 00000011333 11660467064 024470 0 ustar cdent staff 0000000 0000000
from distutils.cmd import Command
from distutils.errors import *
from distutils import log
from docutils import statemachine
from docutils.parsers.rst import directives
from docutils.core import (
publish_file, publish_string, publish_doctree, publish_from_doctree)
from docutils.parsers import rst
from docutils.nodes import SparseNodeVisitor
from docutils.readers.standalone import Reader
from docutils.writers.html4css1 import HTMLTranslator, Writer
from docutils import nodes
import compiler
from compiler import visitor
from pprint import pprint
import pydoc, os, shutil
class DocInspector(object):
"""expose docstrings for objects by parsing the abstract syntax tree.
splitdocfor() is the interface around this
"""
def __init__(self, filename):
self.filename = filename
self.top_level_doc = None
self.map = {}
def __getitem__(self, path):
return self.map[path]
def __contains__(self, path):
return path in self.map
def makepath(self, node):
path = [n.name for n in node.lineage] + [node.name]
# skip first name in lineage because that's the file ...
return ".".join(path[1:])
def default(self, node):
for child in node.getChildNodes():
self.visit(child, node.lineage + [node])
def visitModule(self, node):
self.top_level_doc = node.doc
node.name = self.filename
node.lineage = []
# descend into classes and functions
self.default(node)
def visitClass(self, node, lineage=[]):
node.lineage = lineage
self.map[self.makepath(node)] = node.doc
self.default(node)
def visitFunction(self, node, lineage=[]):
node.lineage = lineage
self.map[self.makepath(node)] = node.doc
self.default(node)
def splitdocfor(path):
"""split the docstring for a path
valid paths are::
./path/to/module.py
./path/to/module.py:SomeClass.method
returns (description, long_description) from the docstring for path
or (None, None) if there isn't a docstring.
Example::
>>> splitdocfor("./wsgi_intercept/__init__.py")[0]
'installs a WSGI application in place of a real URI for testing.'
>>> splitdocfor("./wsgi_intercept/__init__.py:WSGI_HTTPConnection.get_app")[0]
'Return the app object for the given (host, port).'
>>>
"""
if ":" in path:
filename, objpath = path.split(':')
else:
filename, objpath = path, None
inspector = DocInspector(filename)
visitor.walk(compiler.parseFile(filename), inspector)
if objpath is None:
if inspector.top_level_doc is None:
return None, None
return pydoc.splitdoc(inspector.top_level_doc)
else:
if inspector[objpath] is None:
return None, None
return pydoc.splitdoc(inspector[objpath])
def include_docstring(
name, arguments, options, content, lineno,
content_offset, block_text, state, state_machine):
"""include reStructuredText from a docstring. use the directive like:
| .. include_docstring:: path/to/module.py
| .. include_docstring:: path/to/module.py:SomeClass
| .. include_docstring:: path/to/module.py:SomeClass.method
"""
rawpath = arguments[0]
summary, body = splitdocfor(rawpath)
# nabbed from docutils.parsers.rst.directives.misc.include
include_lines = statemachine.string2lines(body, convert_whitespace=1)
state_machine.insert_input(include_lines, None)
return []
# return [publish_doctree(body)]
include_docstring.arguments = (1, 0, 0)
include_docstring.options = {}
include_docstring.content = 0
directives.register_directive('include_docstring', include_docstring)
class build_docs(Command):
description = "build documentation for wsgi_intercept"
user_options = [
# ('optname=', None, ""),
]
def initialize_options(self):
pass
def finalize_options(self):
pass
def run(self):
"""build end-user documentation."""
if not os.path.exists('./build'):
os.mkdir('./build')
log.info("created build dir")
if os.path.exists('./build/docs'):
shutil.rmtree('./build/docs')
os.mkdir("./build/docs")
body = publish_file(open("./docs/index.rst", 'r'),
destination=open("./build/docs/index.html", 'w'),
writer_name='html',
# settings_overrides={'halt_level':2,
# 'report_level':5}
)
log.info("published docs to: ./build/docs/index.html")
wsgi_intercept-0.5.1/wsgi_intercept/setup_cmd/publish_docs.py 0000644 0000765 0000024 00000014621 11660467064 025042 0 ustar cdent staff 0000000 0000000
import re, pydoc
from distutils.cmd import Command
from distutils.errors import *
from distutils import log
from docutils.core import publish_string, publish_parts
from docutils import nodes
from docutils.nodes import SparseNodeVisitor
from docutils.writers import Writer
import wsgi_intercept
from mechanize import Browser
wiki_word_re = re.compile(r'^[A-Z][a-z]+(?:[A-Z][a-z]+)+')
class WikiWriter(Writer):
def translate(self):
visitor = WikiVisitor(self.document)
self.document.walkabout(visitor)
self.output = visitor.astext()
class WikiVisitor(SparseNodeVisitor):
"""visits RST nodes and transforms into Moin Moin wiki syntax.
swiped from the nose project, originally written by Jason Pellerin.
"""
def __init__(self, document):
SparseNodeVisitor.__init__(self, document)
self.list_depth = 0
self.list_item_prefix = None
self.indent = self.old_indent = ''
self.output = []
self.preformat = False
self.section_level = 0
def astext(self):
return ''.join(self.output)
def visit_Text(self, node):
#print "Text", node
data = node.astext()
if not self.preformat:
data = data.lstrip('\n\r')
data = data.replace('\r', '')
data = data.replace('\n', ' ')
self.output.append(data)
def visit_bullet_list(self, node):
self.list_depth += 1
self.list_item_prefix = (' ' * self.list_depth) + '* '
def depart_bullet_list(self, node):
self.list_depth -= 1
if self.list_depth == 0:
self.list_item_prefix = None
else:
self.list_item_prefix = (' ' * self.list_depth) + '* '
self.output.append('\n\n')
def visit_list_item(self, node):
self.old_indent = self.indent
self.indent = self.list_item_prefix
def depart_list_item(self, node):
self.indent = self.old_indent
def visit_literal_block(self, node):
self.output.extend(['{{{', '\n'])
self.preformat = True
def depart_literal_block(self, node):
self.output.extend(['\n', '}}}', '\n\n'])
self.preformat = False
def visit_doctest_block(self, node):
self.output.extend(['{{{', '\n'])
self.preformat = True
def depart_doctest_block(self, node):
self.output.extend(['\n', '}}}', '\n\n'])
self.preformat = False
def visit_paragraph(self, node):
self.output.append(self.indent)
def depart_paragraph(self, node):
self.output.append('\n')
if not isinstance(node.parent, nodes.list_item):
self.output.append('\n')
if self.indent == self.list_item_prefix:
# we're in a sub paragraph of a list item
self.indent = ' ' * self.list_depth
def visit_reference(self, node):
if node.has_key('refuri'):
href = node['refuri']
elif node.has_key('refid'):
href = '#' + node['refid']
else:
href = None
self.output.append('[' + href + ' ')
def depart_reference(self, node):
self.output.append(']')
def _find_header_level(self, node):
if isinstance(node.parent, nodes.topic):
h_level = 2 # ??
elif isinstance(node.parent, nodes.document):
h_level = 1
else:
assert isinstance(node.parent, nodes.section), (
"unexpected parent: %s" % node.parent.__class__)
h_level = self.section_level
return h_level
def _depart_header_node(self, node):
h_level = self._find_header_level(node)
self.output.append(' %s\n\n' % ('='*h_level))
self.list_depth = 0
self.indent = ''
def _visit_header_node(self, node):
h_level = self._find_header_level(node)
self.output.append('%s ' % ('='*h_level))
def visit_subtitle(self, node):
self._visit_header_node(node)
def depart_subtitle(self, node):
self._depart_header_node(node)
def visit_title(self, node):
self._visit_header_node(node)
def depart_title(self, node):
self._depart_header_node(node)
def visit_title_reference(self, node):
self.output.append("`")
def depart_title_reference(self, node):
self.output.append("`")
def visit_section(self, node):
self.section_level += 1
def depart_section(self, node):
self.section_level -= 1
def visit_emphasis(self, node):
self.output.append('*')
def depart_emphasis(self, node):
self.output.append('*')
def visit_literal(self, node):
self.output.append('`')
def depart_literal(self, node):
self.output.append('`')
class publish_docs(Command):
description = "publish documentation to front page of Google Code project"
user_options = [
('google-user=', None, "Google Code username"),
('google-password=', None, "Google Code password"),
]
def initialize_options(self):
self.google_user = None
self.google_password = None
def finalize_options(self):
if self.google_user is None and self.google_password is None:
raise DistutilsOptionError("--google-user and --google-password are required")
def run(self):
summary, doc = pydoc.splitdoc(wsgi_intercept.__doc__)
wikidoc = publish_string(doc, writer=WikiWriter())
print wikidoc
## Google html is so broken that this isn't working :/
# br = Browser()
# br.open('http://code.google.com/p/wsgi-intercept/admin')
# url = br.geturl()
# assert url.startswith('https://www.google.com/accounts/Login'), (
# "unexpected URL: %s" % url)
# log.info("logging in to Google Code...")
# forms = [f for f in br.forms()]
# assert len(forms)==1, "unexpected forms: %s for %s" % (forms, br.geturl())
# br.select_form(nr=0)
# br['Email'] = self.google_user
# br['Passwd'] = self.google_password
# admin = br.submit()
# url = admin.geturl()
# assert url=='http://code.google.com/p/wsgi-intercept/admin', (
# "unexpected URL: %s" % url)
# br.select_form(nr=0)
# br['projectdescription'] = wikidoc
# br.submit()
# print br.geturl()
wsgi_intercept-0.5.1/wsgi_intercept/test/ 0000755 0000765 0000024 00000000000 11660472720 020775 5 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/wsgi_intercept/test/__init__.py 0000644 0000765 0000024 00000000000 11660467064 023101 0 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/wsgi_intercept/test/test_httplib.py 0000644 0000765 0000024 00000003244 11660467064 024064 0 ustar cdent staff 0000000 0000000 #! /usr/bin/env python2.4
from nose.tools import with_setup, raises, eq_
from wsgi_intercept import httplib_intercept
from socket import gaierror
import wsgi_intercept
from wsgi_intercept import test_wsgi_app
import httplib
_saved_debuglevel = None
def http_install():
_saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1
httplib_intercept.install()
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80, test_wsgi_app.create_fn)
def http_uninstall():
wsgi_intercept.debuglevel = _saved_debuglevel
wsgi_intercept.remove_wsgi_intercept('some_hopefully_nonexistant_domain', 80)
httplib_intercept.uninstall()
@with_setup(http_install, http_uninstall)
def test_http_success():
http = httplib.HTTPConnection('some_hopefully_nonexistant_domain')
http.request('GET', '/')
content = http.getresponse().read()
eq_(content, 'WSGI intercept successful!\n')
assert test_wsgi_app.success()
def https_install():
_saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1
httplib_intercept.install()
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 443, test_wsgi_app.create_fn)
def https_uninstall():
wsgi_intercept.debuglevel = _saved_debuglevel
wsgi_intercept.remove_wsgi_intercept('some_hopefully_nonexistant_domain', 443)
httplib_intercept.uninstall()
@with_setup(https_install, https_uninstall)
def test_https_success():
http = httplib.HTTPSConnection('some_hopefully_nonexistant_domain')
http.request('GET', '/')
content = http.getresponse().read()
eq_(content, 'WSGI intercept successful!\n')
assert test_wsgi_app.success() wsgi_intercept-0.5.1/wsgi_intercept/test/test_httplib2.py 0000644 0000765 0000024 00000002365 11660467064 024151 0 ustar cdent staff 0000000 0000000 #! /usr/bin/env python2.4
from wsgi_intercept import httplib2_intercept
from nose.tools import with_setup, raises, eq_
from socket import gaierror
import wsgi_intercept
from wsgi_intercept import test_wsgi_app
import httplib2
_saved_debuglevel = None
def install(port=80):
_saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1
httplib2_intercept.install()
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', port, test_wsgi_app.create_fn)
def uninstall():
wsgi_intercept.debuglevel = _saved_debuglevel
httplib2_intercept.uninstall()
@with_setup(install, uninstall)
def test_success():
http = httplib2.Http()
resp, content = http.request('http://some_hopefully_nonexistant_domain:80/', 'GET')
eq_(content, "WSGI intercept successful!\n")
assert test_wsgi_app.success()
@with_setup(install, uninstall)
@raises(gaierror)
def test_bogus_domain():
wsgi_intercept.debuglevel = 1;
httplib2_intercept.HTTP_WSGIInterceptorWithTimeout("_nonexistant_domain_").connect()
@with_setup(lambda: install(443), uninstall)
def test_https_success():
http = httplib2.Http()
resp, content = http.request('https://some_hopefully_nonexistant_domain/', 'GET')
assert test_wsgi_app.success() wsgi_intercept-0.5.1/wsgi_intercept/test/test_mechanize.py 0000644 0000765 0000024 00000002763 11660467064 024366 0 ustar cdent staff 0000000 0000000
from nose.tools import with_setup, raises
from urllib2 import URLError
from wsgi_intercept.mechanize_intercept import Browser
import wsgi_intercept
from wsgi_intercept import test_wsgi_app
from mechanize import Browser as MechanizeBrowser
###
_saved_debuglevel = None
def add_intercept():
# _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80, test_wsgi_app.create_fn)
def add_https_intercept():
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 443, test_wsgi_app.create_fn)
def remove_intercept():
wsgi_intercept.remove_wsgi_intercept('some_hopefully_nonexistant_domain', 80)
# wsgi_intercept.debuglevel = _saved_debuglevel
@with_setup(add_intercept, remove_intercept)
def test_intercepted():
b = Browser()
b.open('http://some_hopefully_nonexistant_domain:80/')
assert test_wsgi_app.success()
@with_setup(add_intercept)
@raises(URLError)
def test_intercept_removed():
remove_intercept()
b = Browser()
b.open('http://some_hopefully_nonexistant_domain:80/')
@with_setup(add_https_intercept, remove_intercept)
def test_https_intercept():
b = Browser()
b.open('https://some_hopefully_nonexistant_domain:443/')
assert test_wsgi_app.success()
@with_setup(add_intercept, remove_intercept)
def test_https_intercept_default_port():
b = Browser()
b.open('https://some_hopefully_nonexistant_domain/')
assert test_wsgi_app.success() wsgi_intercept-0.5.1/wsgi_intercept/test/test_webtest.py 0000644 0000765 0000024 00000002232 11660467064 024067 0 ustar cdent staff 0000000 0000000 #! /usr/bin/env python
import sys
import wsgi_intercept
from wsgi_intercept import test_wsgi_app, webtest_intercept
class WSGI_Test(webtest_intercept.WebCase):
HTTP_CONN = wsgi_intercept.WSGI_HTTPConnection
HOST = 'some_hopefully_nonexistant_domain'
def setUp(self):
wsgi_intercept.add_wsgi_intercept(self.HOST, self.PORT,
test_wsgi_app.create_fn)
def tearDown(self):
wsgi_intercept.remove_wsgi_intercept()
def test_page(self):
self.getPage('http://%s:%s/' % (self.HOST, self.PORT))
assert test_wsgi_app.success()
class WSGI_HTTPS_Test(webtest_intercept.WebCase):
HTTP_CONN = wsgi_intercept.WSGI_HTTPConnection
HOST = 'some_hopefully_nonexistant_domain'
def setUp(self):
wsgi_intercept.add_wsgi_intercept(self.HOST, self.PORT,
test_wsgi_app.create_fn)
def tearDown(self):
wsgi_intercept.remove_wsgi_intercept()
def test_page(self):
self.getPage('https://%s:%s/' % (self.HOST, self.PORT))
assert test_wsgi_app.success()
if __name__ == '__main__':
webtest.main()
wsgi_intercept-0.5.1/wsgi_intercept/test/test_webunit.py 0000644 0000765 0000024 00000002440 11660467064 024070 0 ustar cdent staff 0000000 0000000 #! /usr/bin/env python
import sys, os.path
import wsgi_intercept
from wsgi_intercept import WSGI_HTTPConnection
from wsgi_intercept import test_wsgi_app
from httplib import HTTP
class WSGI_HTTP(HTTP):
_connection_class = WSGI_HTTPConnection
###
from wsgi_intercept.webunit_intercept import WebTestCase
import unittest
class WSGI_WebTestCase(WebTestCase):
scheme_handlers = dict(http=WSGI_HTTP)
def setUp(self):
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80,
test_wsgi_app.create_fn)
def tearDown(self):
wsgi_intercept.remove_wsgi_intercept()
def test_get(self):
r = self.page('http://some_hopefully_nonexistant_domain/')
assert test_wsgi_app.success()
class WSGI_HTTPS_WebTestCase(WebTestCase):
scheme_handlers = dict(https=WSGI_HTTP)
def setUp(self):
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 443,
test_wsgi_app.create_fn)
def tearDown(self):
wsgi_intercept.remove_wsgi_intercept()
def test_get(self):
r = self.page('https://some_hopefully_nonexistant_domain/')
assert test_wsgi_app.success()
if __name__ == '__main__':
unittest.main()
wsgi_intercept-0.5.1/wsgi_intercept/test/test_wsgi_compliance.py 0000644 0000765 0000024 00000003717 11660467064 025566 0 ustar cdent staff 0000000 0000000 #! /usr/bin/env python2.4
import warnings
from nose.tools import eq_
from wsgi_intercept.httplib2_intercept import install, uninstall
import wsgi_intercept
from wsgi_intercept import test_wsgi_app
import httplib2
from paste import lint
_saved_debuglevel = None
def prudent_wsgi_app():
return lint.middleware(test_wsgi_app.create_fn())
def setup():
warnings.simplefilter("error")
_saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1
install()
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80, prudent_wsgi_app)
def test():
http = httplib2.Http()
resp, content = http.request('http://some_hopefully_nonexistant_domain:80/', 'GET')
assert test_wsgi_app.success()
def test_quoting_issue11():
# see http://code.google.com/p/wsgi-intercept/issues/detail?id=11
http = httplib2.Http()
inspected_env = {}
def make_path_checking_app():
def path_checking_app(environ, start_response):
inspected_env ['QUERY_STRING'] = environ['QUERY_STRING']
inspected_env ['PATH_INFO'] = environ['PATH_INFO']
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
return []
return path_checking_app
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80, make_path_checking_app)
resp, content = http.request('http://some_hopefully_nonexistant_domain:80/spaced+words.html?word=something%20spaced', 'GET')
assert ('QUERY_STRING' in inspected_env and 'PATH_INFO' in inspected_env), "path_checking_app() was never called?"
eq_(inspected_env['PATH_INFO'], '/spaced+words.html')
eq_(inspected_env['QUERY_STRING'], 'word=something%20spaced')
def teardown():
warnings.resetwarnings()
wsgi_intercept.debuglevel = _saved_debuglevel
uninstall()
if __name__ == '__main__':
setup()
try:
test()
finally:
teardown()
wsgi_intercept-0.5.1/wsgi_intercept/test/test_wsgi_urllib2.py 0000644 0000765 0000024 00000003162 11660467064 025021 0 ustar cdent staff 0000000 0000000 #! /usr/bin/env python
import sys, os.path
from nose.tools import with_setup
import urllib2
from wsgi_intercept import urllib2_intercept
import wsgi_intercept
from wsgi_intercept import test_wsgi_app
_saved_debuglevel = None
def add_http_intercept():
_saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 80, test_wsgi_app.create_fn)
def add_https_intercept():
_saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', 443, test_wsgi_app.create_fn)
def remove_intercept():
wsgi_intercept.debuglevel = _saved_debuglevel
wsgi_intercept.remove_wsgi_intercept()
@with_setup(add_http_intercept, remove_intercept)
def test_http():
urllib2_intercept.install_opener()
urllib2.urlopen('http://some_hopefully_nonexistant_domain:80/')
assert test_wsgi_app.success()
@with_setup(add_http_intercept, remove_intercept)
def test_http_default_port():
urllib2_intercept.install_opener()
urllib2.urlopen('http://some_hopefully_nonexistant_domain/')
assert test_wsgi_app.success()
@with_setup(add_https_intercept, remove_intercept)
def test_https():
urllib2_intercept.install_opener()
urllib2.urlopen('https://some_hopefully_nonexistant_domain:443/')
assert test_wsgi_app.success()
@with_setup(add_https_intercept, remove_intercept)
def test_https_default_port():
urllib2_intercept.install_opener()
urllib2.urlopen('https://some_hopefully_nonexistant_domain/')
assert test_wsgi_app.success() wsgi_intercept-0.5.1/wsgi_intercept/test/test_zope_testbrowser.py 0000644 0000765 0000024 00000002534 11660467064 026037 0 ustar cdent staff 0000000 0000000
from nose.tools import with_setup, raises
from urllib2 import URLError
from wsgi_intercept.zope_testbrowser.wsgi_testbrowser import WSGI_Browser
import wsgi_intercept
from wsgi_intercept import test_wsgi_app
_saved_debuglevel = None
def add_intercept(port=80):
# _saved_debuglevel, wsgi_intercept.debuglevel = wsgi_intercept.debuglevel, 1
wsgi_intercept.add_wsgi_intercept('some_hopefully_nonexistant_domain', port, test_wsgi_app.create_fn)
def remove_intercept():
wsgi_intercept.remove_wsgi_intercept()
# wsgi_intercept.debuglevel = _saved_debuglevel
@with_setup(add_intercept, remove_intercept)
def test_intercepted():
b = WSGI_Browser()
b.open('http://some_hopefully_nonexistant_domain:80/')
assert test_wsgi_app.success()
@with_setup(add_intercept)
@raises(URLError)
def test_intercept_removed():
remove_intercept()
b = WSGI_Browser()
b.open('http://some_hopefully_nonexistant_domain:80/')
@with_setup(lambda: add_intercept(443), remove_intercept)
def test_https_intercepted():
b = WSGI_Browser()
b.open('https://some_hopefully_nonexistant_domain/')
assert test_wsgi_app.success()
@with_setup(lambda: add_intercept(443), remove_intercept)
def test_https_intercepted_443_port():
b = WSGI_Browser()
b.open('https://some_hopefully_nonexistant_domain:443/')
assert test_wsgi_app.success() wsgi_intercept-0.5.1/wsgi_intercept/test_wsgi_app.py 0000644 0000765 0000024 00000000773 11660467065 023255 0 ustar cdent staff 0000000 0000000 """
A simple WSGI application for testing.
"""
_app_was_hit = False
def success():
return _app_was_hit
def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
global _app_was_hit
_app_was_hit = True
return ['WSGI intercept successful!\n']
def create_fn():
global _app_was_hit
_app_was_hit = False
return simple_app
wsgi_intercept-0.5.1/wsgi_intercept/urllib2_intercept/ 0000755 0000765 0000024 00000000000 11660472720 023446 5 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/wsgi_intercept/urllib2_intercept/__init__.py 0000644 0000765 0000024 00000000211 11660467064 025556 0 ustar cdent staff 0000000 0000000
"""intercept http requests made using the urllib2 module.
(see wsgi_intercept/__init__.py for examples)
"""
from wsgi_urllib2 import * wsgi_intercept-0.5.1/wsgi_intercept/urllib2_intercept/wsgi_urllib2.py 0000644 0000765 0000024 00000003531 11660467064 026433 0 ustar cdent staff 0000000 0000000 import sys
from wsgi_intercept import WSGI_HTTPConnection
import urllib2, httplib
from urllib2 import HTTPHandler
from httplib import HTTP
#
# ugh, version dependence.
#
if sys.version_info[:2] == (2, 3):
class WSGI_HTTP(HTTP):
_connection_class = WSGI_HTTPConnection
class WSGI_HTTPHandler(HTTPHandler):
"""
Override the default HTTPHandler class with one that uses the
WSGI_HTTPConnection class to open HTTP URLs.
"""
def http_open(self, req):
return self.do_open(WSGI_HTTP, req)
# I'm not implementing HTTPS for 2.3 until someone complains about it! -Kumar
WSGI_HTTPSHandler = None
else:
class WSGI_HTTPHandler(HTTPHandler):
"""
Override the default HTTPHandler class with one that uses the
WSGI_HTTPConnection class to open HTTP URLs.
"""
def http_open(self, req):
return self.do_open(WSGI_HTTPConnection, req)
if hasattr(httplib, 'HTTPS'):
# urllib2 does this check as well, I assume it's to see if
# python was compiled with SSL support
from wsgi_intercept import WSGI_HTTPSConnection
from urllib2 import HTTPSHandler
class WSGI_HTTPSHandler(HTTPSHandler):
"""
Override the default HTTPSHandler class with one that uses the
WSGI_HTTPConnection class to open HTTPS URLs.
"""
def https_open(self, req):
return self.do_open(WSGI_HTTPSConnection, req)
else:
WSGI_HTTPSHandler = None
def install_opener():
handlers = [WSGI_HTTPHandler()]
if WSGI_HTTPSHandler is not None:
handlers.append(WSGI_HTTPSHandler())
opener = urllib2.build_opener(*handlers)
urllib2.install_opener(opener)
return opener
def uninstall_opener():
urllib2.install_opener(None)
wsgi_intercept-0.5.1/wsgi_intercept/webtest_intercept/ 0000755 0000765 0000024 00000000000 11660472720 023550 5 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/wsgi_intercept/webtest_intercept/__init__.py 0000644 0000765 0000024 00000000213 11660467065 025663 0 ustar cdent staff 0000000 0000000 """work with an intercepted HTTP connection in a webtest.WebCase
(see wsgi_intercept/__init__.py for examples)
"""
from webtest import * wsgi_intercept-0.5.1/wsgi_intercept/webtest_intercept/webtest.py 0000644 0000765 0000024 00000033516 11660467065 025615 0 ustar cdent staff 0000000 0000000 """Extensions to unittest for web frameworks.
Use the WebCase.getPage method to request a page from your HTTP server.
Framework Integration
=====================
If you have control over your server process, you can handle errors
in the server-side of the HTTP conversation a bit better. You must run
both the client (your WebCase tests) and the server in the same process
(but in separate threads, obviously).
When an error occurs in the framework, call server_error. It will print
the traceback to stdout, and keep any assertions you have from running
(the assumption is that, if the server errors, the page output won't be
of further significance to your tests).
"""
import os, sys, time, re
import types
import pprint
import socket
import httplib
import traceback
from unittest import *
from unittest import _TextTestResult
class TerseTestResult(_TextTestResult):
def printErrors(self):
# Overridden to avoid unnecessary empty line
if self.errors or self.failures:
if self.dots or self.showAll:
self.stream.writeln()
self.printErrorList('ERROR', self.errors)
self.printErrorList('FAIL', self.failures)
class TerseTestRunner(TextTestRunner):
"""A test runner class that displays results in textual form."""
def _makeResult(self):
return TerseTestResult(self.stream, self.descriptions, self.verbosity)
def run(self, test):
"Run the given test case or test suite."
# Overridden to remove unnecessary empty lines and separators
result = self._makeResult()
startTime = time.time()
test(result)
timeTaken = float(time.time() - startTime)
result.printErrors()
if not result.wasSuccessful():
self.stream.write("FAILED (")
failed, errored = map(len, (result.failures, result.errors))
if failed:
self.stream.write("failures=%d" % failed)
if errored:
if failed: self.stream.write(", ")
self.stream.write("errors=%d" % errored)
self.stream.writeln(")")
return result
class ReloadingTestLoader(TestLoader):
def loadTestsFromName(self, name, module=None):
"""Return a suite of all tests cases given a string specifier.
The name may resolve either to a module, a test case class, a
test method within a test case class, or a callable object which
returns a TestCase or TestSuite instance.
The method optionally resolves the names relative to a given module.
"""
parts = name.split('.')
if module is None:
if not parts:
raise ValueError("incomplete test name: %s" % name)
else:
parts_copy = parts[:]
while parts_copy:
target = ".".join(parts_copy)
if target in sys.modules:
module = reload(sys.modules[target])
break
else:
try:
module = __import__(target)
break
except ImportError:
del parts_copy[-1]
if not parts_copy:
raise
parts = parts[1:]
obj = module
for part in parts:
obj = getattr(obj, part)
if type(obj) == types.ModuleType:
return self.loadTestsFromModule(obj)
elif (isinstance(obj, (type, types.ClassType)) and
issubclass(obj, TestCase)):
return self.loadTestsFromTestCase(obj)
elif type(obj) == types.UnboundMethodType:
return obj.im_class(obj.__name__)
elif callable(obj):
test = obj()
if not isinstance(test, TestCase) and \
not isinstance(test, TestSuite):
raise ValueError("calling %s returned %s, "
"not a test" % (obj,test))
return test
else:
raise ValueError("don't know how to make test from: %s" % obj)
try:
# On Windows, msvcrt.getch reads a single char without output.
import msvcrt
def getchar():
return msvcrt.getch()
except ImportError:
# Unix getchr
import tty, termios
def getchar():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
class WebCase(TestCase):
HOST = "127.0.0.1"
PORT = 8000
HTTP_CONN=httplib.HTTPConnection
def getPage(self, url, headers=None, method="GET", body=None):
"""Open the url with debugging support. Return status, headers, body."""
ServerError.on = False
self.url = url
result = openURL(url, headers, method, body, self.HOST, self.PORT,
self.HTTP_CONN)
self.status, self.headers, self.body = result
# Build a list of request cookies from the previous response cookies.
self.cookies = [('Cookie', v) for k, v in self.headers
if k.lower() == 'set-cookie']
if ServerError.on:
raise ServerError()
return result
interactive = True
console_height = 30
def _handlewebError(self, msg):
if not self.interactive:
raise self.failureException(msg)
print
print " ERROR:", msg
p = " Show: [B]ody [H]eaders [S]tatus [U]RL; [I]gnore, [R]aise, or sys.e[X]it >> "
print p,
while True:
i = getchar().upper()
if i not in "BHSUIRX":
continue
print i.upper() # Also prints new line
if i == "B":
for x, line in enumerate(self.body.splitlines()):
if (x + 1) % self.console_height == 0:
# The \r and comma should make the next line overwrite
print "<-- More -->\r",
m = getchar().lower()
# Erase our "More" prompt
print " \r",
if m == "q":
break
print line
elif i == "H":
pprint.pprint(self.headers)
elif i == "S":
print self.status
elif i == "U":
print self.url
elif i == "I":
# return without raising the normal exception
return
elif i == "R":
raise self.failureException(msg)
elif i == "X":
self.exit()
print p,
def exit(self):
sys.exit()
def __call__(self, result=None):
if result is None:
result = self.defaultTestResult()
result.startTest(self)
if hasattr(self, '_testMethodName'):
# 2.5 + ?
testMethod = getattr(self, self._testMethodName)
elif hasattr(self, '_TestCase__testMethodName'):
# 2.4
testMethod = getattr(self, self._TestCase__testMethodName)
else:
raise AttributeError("Not sure how to get the test method in %s" % self)
try:
try:
self.setUp()
except (KeyboardInterrupt, SystemExit):
raise
except:
result.addError(self, self._TestCase__exc_info())
return
ok = 0
try:
testMethod()
ok = 1
except self.failureException:
result.addFailure(self, self._TestCase__exc_info())
except (KeyboardInterrupt, SystemExit):
raise
except:
result.addError(self, self._TestCase__exc_info())
try:
self.tearDown()
except (KeyboardInterrupt, SystemExit):
raise
except:
result.addError(self, self._TestCase__exc_info())
ok = 0
if ok:
result.addSuccess(self)
finally:
result.stopTest(self)
def assertStatus(self, status, msg=None):
"""Fail if self.status != status."""
if isinstance(status, basestring):
if not self.status == status:
if msg is None:
msg = 'Status (%s) != %s' % (`self.status`, `status`)
self._handlewebError(msg)
else:
if not self.status in status:
if msg is None:
msg = 'Status (%s) not in %s' % (`self.status`, `status`)
self._handlewebError(msg)
def assertHeader(self, key, value=None, msg=None):
"""Fail if (key, [value]) not in self.headers."""
lowkey = key.lower()
for k, v in self.headers:
if k.lower() == lowkey:
if value is None or str(value) == v:
return
if msg is None:
if value is None:
msg = '%s not in headers' % `key`
else:
msg = '%s:%s not in headers' % (`key`, `value`)
self._handlewebError(msg)
def assertNoHeader(self, key, msg=None):
"""Fail if key in self.headers."""
lowkey = key.lower()
matches = [k for k, v in self.headers if k.lower() == lowkey]
if matches:
if msg is None:
msg = '%s in headers' % `key`
self._handlewebError(msg)
def assertBody(self, value, msg=None):
"""Fail if value != self.body."""
if value != self.body:
if msg is None:
msg = 'expected body:\n%s\n\nactual body:\n%s' % (`value`, `self.body`)
self._handlewebError(msg)
def assertInBody(self, value, msg=None):
"""Fail if value not in self.body."""
if value not in self.body:
if msg is None:
msg = '%s not in body' % `value`
self._handlewebError(msg)
def assertNotInBody(self, value, msg=None):
"""Fail if value in self.body."""
if value in self.body:
if msg is None:
msg = '%s found in body' % `value`
self._handlewebError(msg)
def assertMatchesBody(self, pattern, msg=None, flags=0):
"""Fail if value (a regex pattern) is not in self.body."""
if re.search(pattern, self.body, flags) is None:
if msg is None:
msg = 'No match for %s in body' % `pattern`
self._handlewebError(msg)
def cleanHeaders(headers, method, body, host, port):
"""Return request headers, with required headers added (if missing)."""
if headers is None:
headers = []
# Add the required Host request header if not present.
# [This specifies the host:port of the server, not the client.]
found = False
for k, v in headers:
if k.lower() == 'host':
found = True
break
if not found:
headers.append(("Host", "%s:%s" % (host, port)))
if method in ("POST", "PUT"):
# Stick in default type and length headers if not present
found = False
for k, v in headers:
if k.lower() == 'content-type':
found = True
break
if not found:
headers.append(("Content-Type", "application/x-www-form-urlencoded"))
headers.append(("Content-Length", str(len(body or ""))))
return headers
def openURL(url, headers=None, method="GET", body=None,
host="127.0.0.1", port=8000, http_conn=httplib.HTTPConnection):
"""Open the given HTTP resource and return status, headers, and body."""
headers = cleanHeaders(headers, method, body, host, port)
# Trying 10 times is simply in case of socket errors.
# Normal case--it should run once.
trial = 0
while trial < 10:
try:
conn = http_conn(host, port)
conn.putrequest(method.upper(), url)
for key, value in headers:
conn.putheader(key, value)
conn.endheaders()
if body is not None:
conn.send(body)
# Handle response
response = conn.getresponse()
status = "%s %s" % (response.status, response.reason)
outheaders = []
for line in response.msg.headers:
key, value = line.split(":", 1)
outheaders.append((key.strip(), value.strip()))
outbody = response.read()
conn.close()
return status, outheaders, outbody
except socket.error:
trial += 1
if trial >= 10:
raise
else:
time.sleep(0.5)
# Add any exceptions which your web framework handles
# normally (that you don't want server_error to trap).
ignored_exceptions = []
# You'll want set this to True when you can't guarantee
# that each response will immediately follow each request;
# for example, when handling requests via multiple threads.
ignore_all = False
class ServerError(Exception):
on = False
def server_error(exc=None):
"""Server debug hook. Return True if exception handled, False if ignored.
You probably want to wrap this, so you can still handle an error using
your framework when it's ignored.
"""
if exc is None:
exc = sys.exc_info()
if ignore_all or exc[0] in ignored_exceptions:
return False
else:
ServerError.on = True
print
print "".join(traceback.format_exception(*exc))
return True
wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/ 0000755 0000765 0000024 00000000000 11660472720 023550 5 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/__init__.py 0000644 0000765 0000024 00000000270 11660467064 025665 0 ustar cdent staff 0000000 0000000
"""work intercepted HTTP connectsion in a webunit test case.
(see wsgi_intercept/__init__.py for examples)
"""
from webunittest import WebTestCase
__version__ = '1.3.8'
wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/config.py 0000644 0000765 0000024 00000001444 11660467064 025377 0 ustar cdent staff 0000000 0000000 """
This file allows you to set up configuration variables to identify the
machine and port to test.
It needs some work, but in a nutshell, put a config.cfg in your "test"
directory with the following contents::
[DEFAULT]
machine = www.dev.ekorp.com
port = 80
[dev-ekit]
# uses DEFAULT
[dev-lp]
machine = www.lonelyplanet.dev.ekorp.com
port = 80
Then set the environment var "TEST_CONFIG" to the config to use.
"""
import os
if os.path.exists('test/config.cfg'):
import ConfigParser
cfg = ConfigParser.ConfigParser()
cfg.read('test/config.cfg')
# figure the active config
active = os.environ.get('TEST_CONFIG', 'DEFAULT')
# fetch the actual config info
machine = cfg.get(active, 'machine')
port = cfg.getint(active, 'port')
wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/cookie.py 0000644 0000765 0000024 00000007120 11660467064 025400 0 ustar cdent staff 0000000 0000000 import re, urlparse, Cookie
class Error:
'''Handles a specific cookie error.
message - a specific message as to why the cookie is erroneous
'''
def __init__(self, message):
self.message = str(message)
def __str__(self):
return 'COOKIE ERROR: %s'%self.message
def parse_cookie(text, qparmre=re.compile(
r'([\0- ]*([^\0- ;,=\"]+)="([^"]*)\"([\0- ]*[;,])?[\0- ]*)'),
parmre=re.compile(
r'([\0- ]*([^\0- ;,=\"]+)=([^\0- ;,\"]*)([\0- ]*[;,])?[\0- ]*)')):
result = {}
l = 0
while 1:
if qparmre.match(text[l:]) >= 0:
# Match quoted correct cookies
name=qparmre.group(2)
value=qparmre.group(3)
l=len(qparmre.group(1))
elif parmre.match(text[l:]) >= 0:
# Match evil MSIE cookies ;)
name=parmre.group(2)
value=parmre.group(3)
l=len(parmre.group(1))
else:
# this may be an invalid cookie.
# We'll simply bail without raising an error
# if the cookie is invalid.
return result
if not result.has_key(name):
result[name]=value
return result
def decodeCookies(url, server, headers, cookies):
'''Decode cookies into the supplied cookies dictionary
http://www.ietf.org/rfc/rfc2109.txt
'''
# the path of the request URL up to, but not including, the right-most /
request_path = urlparse.urlparse(url)[2]
if len(request_path) > 1 and request_path[-1] == '/':
request_path = request_path[:-1]
hdrcookies = Cookie.SimpleCookie("\n".join(map(lambda x: x.strip(),
headers.getallmatchingheaders('set-cookie'))))
for cookie in hdrcookies.values():
# XXX: there doesn't seem to be a way to determine if the
# cookie was set or defaulted to an empty string :(
if cookie['domain']:
domain = cookie['domain']
# reject if The value for the Domain attribute contains no
# embedded dots or does not start with a dot.
if '.' not in domain:
raise Error, 'Cookie domain "%s" has no "."'%domain
if domain[0] != '.':
raise Error, 'Cookie domain "%s" doesn\'t start '\
'with "."'%domain
# reject if The value for the request-host does not
# domain-match the Domain attribute.
if not server.endswith(domain):
raise Error, 'Cookie domain "%s" doesn\'t match '\
'request host "%s"'%(domain, server)
# reject if The request-host is a FQDN (not IP address) and
# has the form HD, where D is the value of the Domain
# attribute, and H is a string that contains one or more dots.
if re.search(r'[a-zA-Z]', server):
H = server[:-len(domain)]
if '.' in H:
raise Error, 'Cookie domain "%s" too short '\
'for request host "%s"'%(domain, server)
else:
domain = server
# path check
path = cookie['path'] or request_path
# reject if Path attribute is not a prefix of the request-URI
# (noting that empty request path and '/' are often synonymous, yay)
if not (request_path.startswith(path) or (request_path == '' and
cookie['path'] == '/')):
raise Error, 'Cookie path "%s" doesn\'t match '\
'request url "%s"'%(path, request_path)
bydom = cookies.setdefault(domain, {})
bypath = bydom.setdefault(path, {})
bypath[cookie.key] = cookie
wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/HTMLParser.py 0000644 0000765 0000024 00000036554 11660467064 026065 0 ustar cdent staff 0000000 0000000 """A parser for HTML."""
# This file is derived from sgmllib.py, which is part of Python.
# XXX There should be a way to distinguish between PCDATA (parsed
# character data -- the normal case), RCDATA (replaceable character
# data -- only char and entity references and end tags are special)
# and CDATA (character data -- only end tags are special).
import re
import string
# Regular expressions used for parsing
interesting_normal = re.compile('[&<]')
interesting_cdata = re.compile(r'<(/|\Z)')
incomplete = re.compile('(&[a-zA-Z][-.a-zA-Z0-9]*|[0-9]*)')
entityref = re.compile('&([a-zA-Z][-.a-zA-Z0-9]*)[^a-zA-Z0-9]')
charref = re.compile('([0-9]+)[^0-9]')
starttagopen = re.compile('<[a-zA-Z]')
piopen = re.compile(r'<\?')
piclose = re.compile('>')
endtagopen = re.compile('')
declopen = re.compile(']*>')
commentopen = re.compile('" % data)
def handle_decl(self, data):
self.emitText("" % data)
def handle_pi(self, data):
self.emitText("%s>" % data)
def emitStartTag(self, name, attrlist, isend=0):
if isend:
if self.__debug: print '*** content'
self.content.append(SimpleDOMNode(name, attrlist, []))
else:
# generate a new scope and push the current one on the stack
if self.__debug: print '*** push'
newcontent = []
self.stack.append(self.content)
self.content.append(SimpleDOMNode(name, attrlist, newcontent))
self.content = newcontent
def emitEndTag(self, name):
if self.__debug: print '*** pop'
self.content = self.stack.pop()
def emitText(self, text):
self.content.append(text)
def emitStartElement(self, name, attrlist, isend=0):
# Handle the simple, common case
self.emitStartTag(name, attrlist, isend)
if isend:
self.emitEndElement(name, isend)
def emitEndElement(self, name, isend=0, implied=0):
if not isend or implied:
self.emitEndTag(name)
if __name__ == '__main__':
tester = SimpleDOMParser(debug=0)
tester.parseFile('/tmp/test.html')
dom = tester.getDOM()
# html = dom.getByNameFlat('html')[0]
# body = html.getByNameFlat('body')[0]
# table = body.getByNameFlat('table')[0]
# tr = table.getByNameFlat('tr')[1]
# td = tr.getByNameFlat('td')[2]
# print td
import pprint;pprint.pprint(dom)
wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/utility.py 0000644 0000765 0000024 00000005172 11660467064 025637 0 ustar cdent staff 0000000 0000000 #
# Copyright (c) 2003 Richard Jones (http://mechanicalcat.net/richard)
# Copyright (c) 2002 ekit.com Inc (http://www.ekit-inc.com/)
# Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
#
# See the README for full license details.
#
# $Id: utility.py,v 1.3 2003/08/23 02:01:59 richard Exp $
import cStringIO
import os.path
class Upload:
'''Simple "sentinel" class that lets us identify file uploads in POST
data mappings.
'''
def __init__(self, filename):
self.filename = filename
def __cmp__(self, other):
return cmp(self.filename, other.filename)
boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
sep_boundary = '\n--' + boundary
end_boundary = sep_boundary + '--'
def mimeEncode(data, sep_boundary=sep_boundary, end_boundary=end_boundary):
'''Take the mapping of data and construct the body of a
multipart/form-data message with it using the indicated boundaries.
'''
ret = cStringIO.StringIO()
for key, value in data.items():
if not key:
continue
# handle multiple entries for the same name
if type(value) != type([]): value = [value]
for value in value:
ret.write(sep_boundary)
# if key starts with a '$' then the entry is a file upload
if isinstance(value, Upload):
ret.write('\nContent-Disposition: form-data; name="%s"'%key)
ret.write('; filename="%s"\n\n'%value.filename)
if value.filename:
value = open(os.path.join(value.filename), "rb").read()
else:
value = ''
else:
ret.write('\nContent-Disposition: form-data; name="%s"'%key)
ret.write("\n\n")
ret.write(str(value))
if value and value[-1] == '\r':
ret.write('\n') # write an extra newline
ret.write(end_boundary)
return ret.getvalue()
def log(message, content, logfile='logfile'):
'''Log a single message to the indicated logfile
'''
logfile = open(logfile, 'a')
logfile.write('\n>>> %s\n'%message)
logfile.write(str(content) + '\n')
logfile.close()
#
# $Log: utility.py,v $
# Revision 1.3 2003/08/23 02:01:59 richard
# fixes to cookie sending
#
# Revision 1.2 2003/07/22 01:19:22 richard
# patches
#
# Revision 1.1.1.1 2003/07/22 01:01:44 richard
#
#
# Revision 1.4 2002/02/25 02:59:09 rjones
# *** empty log message ***
#
# Revision 1.3 2002/02/22 06:24:31 rjones
# Code cleanup
#
# Revision 1.2 2002/02/13 01:16:56 rjones
# *** empty log message ***
#
#
# vim: set filetype=python ts=4 sw=4 et si
wsgi_intercept-0.5.1/wsgi_intercept/webunit_intercept/webunittest.py 0000644 0000765 0000024 00000062474 11660467064 026521 0 ustar cdent staff 0000000 0000000 #
# Copyright (c) 2003 Richard Jones (http://mechanicalcat.net/richard)
# Copyright (c) 2002 ekit.com Inc (http://www.ekit-inc.com/)
# Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
#
# See the README for full license details.
#
# $Id: webunittest.py,v 1.12 2004/01/21 22:41:46 richard Exp $
import os, base64, urllib, urlparse, unittest, cStringIO, time, re, sys
import httplib
#try:
# from M2Crypto import httpslib
#except ImportError:
# httpslib = None
from SimpleDOM import SimpleDOMParser
from IMGSucker import IMGSucker
from utility import Upload, mimeEncode, boundary, log
import cookie
VERBOSE = os.environ.get('VERBOSE', '')
class HTTPError:
'''Wraps a HTTP response that is not 200.
url - the URL that generated the error
code, message, headers - the information returned by httplib.HTTP.getreply()
'''
def __init__(self, response):
self.response = response
def __str__(self):
return 'ERROR: %s'%str(self.response)
class WebFetcher:
'''Provide a "web client" class that handles fetching web pages.
Handles basic authentication, HTTPS, detection of error content, ...
Creates a HTTPResponse object on a valid response.
Stores cookies received from the server.
'''
scheme_handlers = dict(http = httplib.HTTP,
https = httplib.HTTPS)
def __init__(self):
'''Initialise the server, port, authinfo, images and error_content
attributes.
'''
self.protocol = 'http'
self.server = '127.0.0.1'
self.port = 80
self.authinfo = ''
self.url = None
self.images = {}
self.error_content = []
self.expect_codes = [200, 301, 302]
self.expect_content = None
self.expect_cookies = None
self.accept_cookies = 1
self.cookies = {}
result_count = 0
def clearContext(self):
self.authinfo = ''
self.cookies = {}
self.url = None
self.images = {}
def setServer(self, server, port):
'''Set the server and port number to perform the HTTP requests to.
'''
self.server = server
self.port = int(port)
#
# Authentication
#
def clearBasicAuth(self):
'''Clear the current Basic authentication information
'''
self.authinfo = ''
def setBasicAuth(self, username, password):
'''Set the Basic authentication information to the given username
and password.
'''
self.authinfo = base64.encodestring('%s:%s'%(username,
password)).strip()
#
# cookie handling
#
def clearCookies(self):
'''Clear all currently received cookies
'''
self.cookies = {}
def setAcceptCookies(self, accept=1):
'''Indicate whether to accept cookies or not
'''
self.accept_cookies = accept
def registerErrorContent(self, content):
'''Register the given string as content that should be considered a
test failure (even though the response code is 200).
'''
self.error_content.append(content)
def removeErrorContent(self, content):
'''Remove the given string from the error content list.
'''
self.error_content.remove(content)
def clearErrorContent(self):
'''Clear the current list of error content strings.
'''
self.error_content = []
def log(self, message, content):
'''Log a message to the logfile
'''
log(message, content, 'logfile.'+self.server)
#
# Register cookies we expect to send to the server
#
def registerExpectedCookie(self, cookie):
'''Register a cookie name that we expect to send to the server.
'''
if self.expect_cookies is None:
self.expect_cookies = [cookie]
return
self.expect_cookies.append(cookie)
self.expect_cookies.sort()
def removeExpectedCookie(self, cookie):
'''Remove the given cookie from the list of cookies we expect to
send to the server.
'''
self.expect_cookies.remove(cookie)
def clearExpectedCookies(self):
'''Clear the current list of cookies we expect to send to the server.
'''
self.expect_cookies = None
#
# POST
#
def post(self, url, params, code=None, **kw):
'''Perform a HTTP POST using the specified URL and form parameters.
'''
if code is None: code = self.expect_codes
WebTestCase.result_count = WebTestCase.result_count + 1
try:
response = self.fetch(url, params, ok_codes=code, **kw)
except HTTPError, error:
self.log('post'+`(url, params)`, error.response.body)
raise self.failureException, str(error.response)
return response
def postAssertCode(self, url, params, code=None, **kw):
'''Perform a HTTP POST and assert that the return code from the
server is one of the indicated codes.
'''
if code is None: code = self.expect_codes
WebTestCase.result_count = WebTestCase.result_count + 1
if type(code) != type([]):
code = [code]
try:
response = self.fetch(url, params, ok_codes = code, **kw)
except HTTPError, error:
self.log('postAssertCode'+`(url, params, code)`,
error.response.body)
raise self.failureException, str(error.response)
return response
def postAssertContent(self, url, params, content, code=None, **kw):
'''Perform a HTTP POST and assert that the data returned from the
server contains the indicated content string.
'''
if code is None: code = self.expect_codes
WebTestCase.result_count = WebTestCase.result_count + 1
if type(code) != type([]):
code = [code]
try:
response = self.fetch(url, params, ok_codes = code, **kw)
except HTTPError, error:
self.log('postAssertContent'+`(url, params, code)`,
error.response.body)
raise self.failureException, str(error)
if response.body.find(content) == -1:
self.log('postAssertContent'+`(url, params, content)`,
response.body)
raise self.failureException, 'Expected content not in response'
return response
def postAssertNotContent(self, url, params, content, code=None, **kw):
'''Perform a HTTP POST and assert that the data returned from the
server doesn't contain the indicated content string.
'''
if code is None: code = self.expect_codes
WebTestCase.result_count = WebTestCase.result_count + 1
if type(code) != type([]):
code = [code]
try:
response = self.fetch(url, params, ok_codes = code, **kw)
except HTTPError, error:
self.log('postAssertNotContent'+`(url, params, code)`,
error.response.body)
raise self.failureException, str(error)
if response.body.find(content) != -1:
self.log('postAssertNotContent'+`(url, params, content)`,
response.body)
raise self.failureException, 'Expected content was in response'
return response
def postPage(self, url, params, code=None, **kw):
'''Perform a HTTP POST using the specified URL and form parameters
and then retrieve all image and linked stylesheet components for the
resulting HTML page.
'''
if code is None: code = self.expect_codes
WebTestCase.result_count = WebTestCase.result_count + 1
try:
response = self.fetch(url, params, ok_codes=code, **kw)
except HTTPError, error:
self.log('postPage %r'%((url, params),), error.response.body)
raise self.failureException, str(error)
# Check return code for redirect
while response.code in (301, 302):
try:
# Figure the location - which may be relative
newurl = response.headers['Location']
url = urlparse.urljoin(url, newurl)
response = self.fetch(url, ok_codes=code)
except HTTPError, error:
self.log('postPage %r'%url, error.response.body)
raise self.failureException, str(error)
# read and parse the content
page = response.body
if hasattr(self, 'results') and self.results:
self.writeResult(url, page)
try:
self.pageImages(url, page)
except HTTPError, error:
raise self.failureException, str(error)
return response
#
# GET
#
def assertCode(self, url, code=None, **kw):
'''Perform a HTTP GET and assert that the return code from the
server one of the indicated codes.
'''
if code is None: code = self.expect_codes
return self.postAssertCode(url, None, code=code, **kw)
get = getAssertCode = assertCode
def assertContent(self, url, content, code=None, **kw):
'''Perform a HTTP GET and assert that the data returned from the
server contains the indicated content string.
'''
if code is None: code = self.expect_codes
return self.postAssertContent(url, None, content, code)
getAssertContent = assertContent
def assertNotContent(self, url, content, code=None, **kw):
'''Perform a HTTP GET and assert that the data returned from the
server contains the indicated content string.
'''
if code is None: code = self.expect_codes
return self.postAssertNotContent(url, None, content, code)
getAssertNotContent = assertNotContent
def page(self, url, code=None, **kw):
'''Perform a HTTP GET using the specified URL and then retrieve all
image and linked stylesheet components for the resulting HTML page.
'''
if code is None: code = self.expect_codes
WebTestCase.result_count = WebTestCase.result_count + 1
return self.postPage(url, None, code=code, **kw)
def get_base_url(self):
# try to get a tag and use that to root the URL on
if hasattr(self, 'getDOM'):
base = self.getDOM().getByName('base')
if base:
#
return base[0].href
if self.url is not None:
# join the request URL with the "current" URL
return self.url
return None
#
# The function that does it all
#
def fetch(self, url, postdata=None, server=None, port=None, protocol=None,
ok_codes=None):
'''Run a single test request to the indicated url. Use the POST data
if supplied.
Raises failureException if the returned data contains any of the
strings indicated to be Error Content.
Returns a HTTPReponse object wrapping the response from the server.
'''
# see if the url is fully-qualified (not just a path)
t_protocol, t_server, t_url, x, t_args, x = urlparse.urlparse(url)
if t_server:
protocol = t_protocol
if ':' in t_server:
server, port = t_server.split(':')
else:
server = t_server
if protocol == 'http':
port = '80'
else:
port = '443'
url = t_url
if t_args:
url = url + '?' + t_args
# ignore the machine name if the URL is for localhost
if t_server == 'localhost':
server = None
elif not server:
# no server was specified with this fetch, or in the URL, so
# see if there's a base URL to use.
base = self.get_base_url()
if base:
t_protocol, t_server, t_url, x, x, x = urlparse.urlparse(base)
if t_protocol:
protocol = t_protocol
if t_server:
server = t_server
if t_url:
url = urlparse.urljoin(t_url, url)
# TODO: allow override of the server and port from the URL!
if server is None: server = self.server
if port is None: port = self.port
if protocol is None: protocol = self.protocol
if ok_codes is None: ok_codes = self.expect_codes
if protocol == 'http':
handler = self.scheme_handlers.get('http')
h = handler(server, int(port))
if int(port) == 80:
host_header = server
else:
host_header = '%s:%s'%(server, port)
elif protocol == 'https':
#if httpslib is None:
#raise ValueError, "Can't fetch HTTPS: M2Crypto not installed"
handler = self.scheme_handlers.get('https')
h = handler(server, int(port))
if int(port) == 443:
host_header = server
else:
host_header = '%s:%s'%(server, port)
else:
raise ValueError, protocol
params = None
if postdata:
for field,value in postdata.items():
if type(value) == type({}):
postdata[field] = []
for k,selected in value.items():
if selected: postdata[field].append(k)
# Do a post with the data file
params = mimeEncode(postdata)
h.putrequest('POST', url)
h.putheader('Content-type', 'multipart/form-data; boundary=%s'%
boundary)
h.putheader('Content-length', str(len(params)))
else:
# Normal GET
h.putrequest('GET', url)
# Other Full Request headers
if self.authinfo:
h.putheader('Authorization', "Basic %s"%self.authinfo)
h.putheader('Host', host_header)
# Send cookies
# - check the domain, max-age (seconds), path and secure
# (http://www.ietf.org/rfc/rfc2109.txt)
cookies_used = []
cookie_list = []
for domain, cookies in self.cookies.items():
# check cookie domain
if not server.endswith(domain):
continue
for path, cookies in cookies.items():
# check that the path matches
urlpath = urlparse.urlparse(url)[2]
if not urlpath.startswith(path) and not (path == '/' and
urlpath == ''):
continue
for sendcookie in cookies.values():
# and that the cookie is or isn't secure
if sendcookie['secure'] and protocol != 'https':
continue
# TODO: check max-age
cookie_list.append("%s=%s;"%(sendcookie.key,
sendcookie.coded_value))
cookies_used.append(sendcookie.key)
if cookie_list:
h.putheader('Cookie', ' '.join(cookie_list))
# check that we sent the cookies we expected to
if self.expect_cookies is not None:
assert cookies_used == self.expect_cookies, \
"Didn't use all cookies (%s expected, %s used)"%(
self.expect_cookies, cookies_used)
# finish the headers
h.endheaders()
if params is not None:
h.send(params)
# handle the reply
errcode, errmsg, headers = h.getreply()
# get the body and save it
f = h.getfile()
g = cStringIO.StringIO()
d = f.read()
while d:
g.write(d)
d = f.read()
response = HTTPResponse(self.cookies, protocol, server, port, url,
errcode, errmsg, headers, g.getvalue(), self.error_content)
f.close()
if errcode not in ok_codes:
if VERBOSE:
sys.stdout.write('e')
sys.stdout.flush()
raise HTTPError(response)
# decode the cookies
if self.accept_cookies:
try:
# decode the cookies and update the cookies store
cookie.decodeCookies(url, server, headers, self.cookies)
except:
if VERBOSE:
sys.stdout.write('c')
sys.stdout.flush()
raise
# Check errors
if self.error_content:
data = response.body
for content in self.error_content:
if data.find(content) != -1:
msg = "Matched error: %s"%content
if hasattr(self, 'results') and self.results:
self.writeError(url, msg)
self.log('Matched error'+`(url, content)`, data)
if VERBOSE:
sys.stdout.write('c')
sys.stdout.flush()
raise self.failureException, msg
if VERBOSE:
sys.stdout.write('_')
sys.stdout.flush()
return response
def pageImages(self, url, page):
'''Given the HTML page that was loaded from url, grab all the images.
'''
sucker = IMGSucker(url, self)
sucker.feed(page)
sucker.close()
class WebTestCase(WebFetcher, unittest.TestCase):
'''Extend the standard unittest TestCase with some HTTP fetching and
response testing functions.
'''
def __init__(self, methodName='runTest'):
'''Initialise the server, port, authinfo, images and error_content
attributes.
'''
unittest.TestCase.__init__(self, methodName=methodName)
WebFetcher.__init__(self)
class HTTPResponse(WebFetcher, unittest.TestCase):
'''Wraps a HTTP response.
protocol, server, port, url - the request server and URL
code, message, headers - the information returned by httplib.HTTP.getreply()
body - the response body returned by httplib.HTTP.getfile()
'''
def __init__(self, cookies, protocol, server, port, url, code, message,
headers, body, error_content=[]):
WebFetcher.__init__(self)
# single cookie store per test
self.cookies = cookies
self.error_content = error_content[:]
# this is the request that generated this response
self.protocol = protocol
self.server = server
self.port = port
self.url = url
# info about the response
self.code = code
self.message = message
self.headers = headers
self.body = body
self.dom = None
def __str__(self):
return '%s\nHTTP Response %s: %s'%(self.url, self.code, self.message)
def getDOM(self):
'''Get a DOM for this page
'''
if self.dom is None:
parser = SimpleDOMParser()
try:
parser.parseString(self.body)
except:
log('HTTPResponse.getDOM'+`(self.url, self.code, self.message,
self.headers)`, self.body)
raise
self.dom = parser.getDOM()
return self.dom
def extractForm(self, path=[], include_submit=0, include_button=0):
'''Extract a form (as a dictionary) from this page.
The "path" is a list of 2-tuples ('element name', index) to follow
to find the form. So:
..
To extract the second form, any of these could be used:
[('html',0), ('body',0), ('p',1), ('form',0)]
[('form',1)]
[('p',1)]
'''
return self.getDOM().extractElements(path, include_submit,
include_button)
def getForm(self, formnum, getmethod, postargs, *args):
'''Given this page, extract the "formnum"th form from it, fill the
form with the "postargs" and post back to the server using the
"postmethod" with additional "args".
NOTE: the form submission will include any "default" values from
the form extracted from this page. To "remove" a value from the
form, just pass a value None for the elementn and it will be
removed from the form submission.
example WebTestCase:
page = self.get('/foo')
page.getForm(0, self.post, {'name': 'blahblah',
'password': 'foo'})
or the slightly more complex:
page = self.get('/foo')
page.getForm(0, self.assertContent, {'name': 'blahblah',
'password': None}, 'password incorrect')
'''
formData, url = self.getFormData(formnum, postargs)
# whack on the url params
l = []
for k, v in formData.items():
if isinstance(v, type([])):
for item in v:
l.append('%s=%s'%(urllib.quote(k),
urllib.quote_plus(item, safe='')))
else:
l.append('%s=%s'%(urllib.quote(k),
urllib.quote_plus(v, safe='')))
if l:
url = url + '?' + '&'.join(l)
# make the post
return getmethod(url, *args)
def postForm(self, formnum, postmethod, postargs, *args):
'''Given this page, extract the "formnum"th form from it, fill the
form with the "postargs" and post back to the server using the
"postmethod" with additional "args".
NOTE: the form submission will include any "default" values from
the form extracted from this page. To "remove" a value from the
form, just pass a value None for the elementn and it will be
removed from the form submission.
example WebTestCase:
page = self.get('/foo')
page.postForm(0, self.post, {'name': 'blahblah',
'password': 'foo'})
or the slightly more complex:
page = self.get('/foo')
page.postForm(0, self.postAssertContent, {'name': 'blahblah',
'password': None}, 'password incorrect')
'''
formData, url = self.getFormData(formnum, postargs)
# make the post
return postmethod(url, formData, *args)
def getFormData(self, formnum, postargs={}):
''' Postargs are in the same format as the data returned by the
SimpleDOM extractElements() method, and they are merged with
the existing form data.
'''
dom = self.getDOM()
form = dom.getByName('form')[formnum]
formData = form.extractElements()
# Make sure all the postargs are present in the form:
# TODO this test needs to be switchable, as it barfs when you explicitly
# identify a submit button in the form - the existing form data doesn't
# have submit buttons in it
# for k in postargs.keys():
# assert formData.has_key(k), (formData, k)
formData.update(postargs)
for k,v in postargs.items():
if v is None:
del formData[k]
# transmogrify select/checkbox/radio select options from dicts
# (key:'selected') to lists of values
for k,v in formData.items():
if isinstance(v, type({})):
l = []
for kk,vv in v.items():
if vv in ('selected', 'checked'):
l.append(kk)
formData[k] = l
if form.hasattr('action'):
url = form.action
base = self.get_base_url()
if not url or url == '.':
if base and base[0].hasattr('href'):
url = base[0].href
elif self.url.endswith('/'):
url = self.url
elif self.url.startswith('http') or self.url.startswith('/'):
url = '%s/' % '/'.join(self.url.split('/')[:-1])
else:
url = '/%s/' % '/'.join(self.url.split('/')[:-1])
elif not (url.startswith('/') or url.startswith('http')):
url = urlparse.urljoin(base, url)
else:
url = self.url
return formData, url
#
# $Log: webunittest.py,v $
# Revision 1.12 2004/01/21 22:41:46 richard
# *** empty log message ***
#
# Revision 1.11 2004/01/20 23:59:39 richard
# *** empty log message ***
#
# Revision 1.10 2003/11/06 06:50:29 richard
# *** empty log message ***
#
# Revision 1.9 2003/11/03 05:11:17 richard
# *** empty log message ***
#
# Revision 1.5 2003/10/08 05:37:32 richard
# fixes
#
# Revision 1.4 2003/08/23 02:01:59 richard
# fixes to cookie sending
#
# Revision 1.3 2003/08/22 00:46:29 richard
# much fixes
#
# Revision 1.2 2003/07/22 01:19:22 richard
# patches
#
# Revision 1.1.1.1 2003/07/22 01:01:44 richard
#
#
# Revision 1.11 2002/02/27 03:00:08 rjones
# more tests, bugfixes
#
# Revision 1.10 2002/02/26 03:14:41 rjones
# more tests
#
# Revision 1.9 2002/02/25 02:58:47 rjones
# *** empty log message ***
#
# Revision 1.8 2002/02/22 06:24:31 rjones
# Code cleanup
#
# Revision 1.7 2002/02/22 04:15:34 rjones
# web test goodness
#
# Revision 1.6 2002/02/13 04:32:50 rjones
# *** empty log message ***
#
# Revision 1.5 2002/02/13 04:24:42 rjones
# *** empty log message ***
#
# Revision 1.4 2002/02/13 02:21:59 rjones
# *** empty log message ***
#
# Revision 1.3 2002/02/13 01:48:23 rjones
# *** empty log message ***
#
# Revision 1.2 2002/02/13 01:16:56 rjones
# *** empty log message ***
#
#
# vim: set filetype=python ts=4 sw=4 et si
wsgi_intercept-0.5.1/wsgi_intercept/zope_testbrowser/ 0000755 0000765 0000024 00000000000 11660472720 023436 5 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/wsgi_intercept/zope_testbrowser/__init__.py 0000644 0000765 0000024 00000000233 11660467064 025552 0 ustar cdent staff 0000000 0000000 """intercepting HTTP connections made using zope.testbrowser.
(see wsgi_intercept/__init__.py for examples)
"""
from wsgi_testbrowser import WSGI_Browser wsgi_intercept-0.5.1/wsgi_intercept/zope_testbrowser/wsgi_testbrowser.py 0000644 0000765 0000024 00000001237 11660467064 027434 0 ustar cdent staff 0000000 0000000 """
A zope.testbrowser-style Web browser interface that redirects specified
connections to a WSGI application.
"""
from mechanize import Browser as MechanizeBrowser
from wsgi_intercept.mechanize_intercept import Browser as InterceptBrowser
from zope.testbrowser.browser import Browser as ZopeTestbrowser
from httplib import HTTP
import sys, os.path
class WSGI_Browser(ZopeTestbrowser):
"""
Override the zope.testbrowser.browser.Browser interface so that it
uses PatchedMechanizeBrowser
"""
def __init__(self, *args, **kwargs):
kwargs['mech_browser'] = InterceptBrowser()
ZopeTestbrowser.__init__(self, *args, **kwargs)
wsgi_intercept-0.5.1/wsgi_intercept.egg-info/ 0000755 0000765 0000024 00000000000 11660472720 021510 5 ustar cdent staff 0000000 0000000 wsgi_intercept-0.5.1/wsgi_intercept.egg-info/dependency_links.txt 0000644 0000765 0000024 00000000001 11660472720 025556 0 ustar cdent staff 0000000 0000000
wsgi_intercept-0.5.1/wsgi_intercept.egg-info/PKG-INFO 0000644 0000765 0000024 00000022127 11660472720 022611 0 ustar cdent staff 0000000 0000000 Metadata-Version: 1.0
Name: wsgi-intercept
Version: 0.5.1
Summary: installs a WSGI application in place of a real URI for testing.
Home-page: http://code.google.com/p/wsgi-intercept/
Author: Titus Brown, Kumar McMillan
Author-email: kumar.mcmillan@gmail.com
License: MIT License
Description: Introduction
============
Testing a WSGI application normally involves starting a server at a local host and port, then pointing your test code to that address. Instead, this library lets you intercept calls to any specific host/port combination and redirect them into a `WSGI application`_ importable by your test program. Thus, you can avoid spawning multiple processes or threads to test your Web app.
How Does It Work?
=================
``wsgi_intercept`` works by replacing ``httplib.HTTPConnection`` with a subclass, ``wsgi_intercept.WSGI_HTTPConnection``. This class then redirects specific server/port combinations into a WSGI application by emulating a socket. If no intercept is registered for the host and port requested, those requests are passed on to the standard handler.
The functions ``add_wsgi_intercept(host, port, app_create_fn, script_name='')`` and ``remove_wsgi_intercept(host,port)`` specify which URLs should be redirect into what applications. Note especially that ``app_create_fn`` is a *function object* returning a WSGI application; ``script_name`` becomes ``SCRIPT_NAME`` in the WSGI app's environment, if set.
Install
=======
::
easy_install wsgi_intercept
(The ``easy_install`` command is bundled with the setuptools_ module)
To use a `development version`_ of wsgi_intercept, run::
easy_install http://wsgi-intercept.googlecode.com/svn/trunk
.. _setuptools: http://cheeseshop.python.org/pypi/setuptools/
.. _development version: http://wsgi-intercept.googlecode.com/svn/trunk/#egg=wsgi_intercept-dev
Packages Intercepted
====================
Unfortunately each of the Web testing frameworks uses its own specific mechanism for making HTTP call-outs, so individual implementations are needed. Below are the packages supported and how to create an intercept.
urllib2
-------
urllib2_ is a standard Python module, and ``urllib2.urlopen`` is a pretty
normal way to open URLs.
The following code will install the WSGI intercept stuff as a default
urllib2 handler: ::
>>> from wsgi_intercept.urllib2_intercept import install_opener
>>> install_opener() #doctest: +ELLIPSIS
>>> import wsgi_intercept
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> import urllib2
>>> urllib2.urlopen('http://some_host:80/').read()
'WSGI intercept successful!\n'
The only tricky bit in there is that different handler classes need to
be constructed for Python 2.3 and Python 2.4, because the httplib
interface changed between those versions.
.. _urllib2: http://docs.python.org/lib/module-urllib2.html
httplib2
--------
httplib2_ is a 3rd party extension of the built-in ``httplib``. To intercept
requests, it is similar to urllib2::
>>> from wsgi_intercept.httplib2_intercept import install
>>> install()
>>> import wsgi_intercept
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> import httplib2
>>> resp, content = httplib2.Http().request('http://some_host:80/', 'GET')
>>> content
'WSGI intercept successful!\n'
(Contributed by `David "Whit" Morris`_.)
.. _httplib2: http://code.google.com/p/httplib2/
.. _David "Whit" Morris: http://public.xdi.org/=whit
webtest
-------
webtest_ is an extension to ``unittest`` that has some nice functions for
testing Web sites.
To install the WSGI intercept handler, do ::
>>> import wsgi_intercept.webtest_intercept
>>> class WSGI_Test(wsgi_intercept.webtest_intercept.WebCase):
... HTTP_CONN = wsgi_intercept.WSGI_HTTPConnection
... HOST='localhost'
... PORT=80
...
... def setUp(self):
... wsgi_intercept.add_wsgi_intercept(self.HOST, self.PORT, create_fn)
...
>>>
.. _webtest: http://www.cherrypy.org/file/trunk/cherrypy/test/webtest.py
webunit
-------
webunit_ is another unittest-like framework that contains nice functions
for Web testing. (funkload_ uses webunit, too.)
webunit needed to be patched to support different scheme handlers.
The patched package is in webunit/wsgi_webunit/, and the only
file that was changed was webunittest.py; the original is in
webunittest-orig.py.
To install the WSGI intercept handler, do ::
>>> from httplib import HTTP
>>> import wsgi_intercept.webunit_intercept
>>> class WSGI_HTTP(HTTP):
... _connection_class = wsgi_intercept.WSGI_HTTPConnection
...
>>> class WSGI_WebTestCase(wsgi_intercept.webunit_intercept.WebTestCase):
... scheme_handlers = dict(http=WSGI_HTTP)
...
... def setUp(self):
... wsgi_intercept.add_wsgi_intercept('127.0.0.1', 80, create_fn)
...
>>>
.. _webunit: http://mechanicalcat.net/tech/webunit/
mechanize
---------
mechanize_ is John J. Lee's port of Perl's WWW::Mechanize to Python.
It mimics a browser. (It's also what's behind twill_.)
>>> import wsgi_intercept.mechanize_intercept
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> b = wsgi_intercept.mechanize_intercept.Browser()
>>> response = b.open('http://some_host:80')
>>> response.read()
'WSGI intercept successful!\n'
.. _mechanize: http://wwwsearch.sf.net/
zope.testbrowser
----------------
zope.testbrowser_ is a prettified interface to mechanize_ that is used
primarily for testing Zope applications.
zope.testbrowser is also pretty easy ::
>>> import wsgi_intercept.zope_testbrowser
>>> from wsgi_intercept.test_wsgi_app import create_fn
>>> wsgi_intercept.add_wsgi_intercept('some_host', 80, create_fn)
>>> b = wsgi_intercept.zope_testbrowser.WSGI_Browser('http://some_host:80/')
>>> b.contents
'WSGI intercept successful!\n'
.. _zope.testbrowser: http://www.python.org/pypi/zope.testbrowser
History
=======
Pursuant to Ian Bicking's `"best Web testing framework"`_ post,
Titus Brown put together an `in-process HTTP-to-WSGI interception mechanism`_ for
his own Web testing system, twill_. Because the mechanism is pretty
generic -- it works at the httplib level -- Titus decided to try adding it into
all of the *other* Python Web testing frameworks.
This is the result.
Mocking your HTTP Server
========================
Marc Hedlund has gone one further, and written a full-blown mock HTTP
server for wsgi_intercept. Combined with wsgi_intercept itself, this
lets you entirely replace client calls to a server with a mock setup
that hits neither the network nor server code. You can see his work
in the file ``mock_http.py``. Run ``mock_http.py`` to see a test.
.. _twill: http://www.idyll.org/~t/www-tools/twill.html
.. _"best Web testing framework": http://blog.ianbicking.org/best-of-the-web-app-test-frameworks.html
.. _in-process HTTP-to-WSGI interception mechanism: http://www.advogato.org/person/titus/diary.html?start=119
.. _WSGI application: http://www.python.org/peps/pep-0333.html
.. _funkload: http://funkload.nuxeo.org/
Project Home
============
If you aren't already there, this project lives on `Google Code`_. Please submit all bugs, patches, failing tests, et cetera using the `Issue Tracker`_
.. _Google Code: http://code.google.com/p/wsgi-intercept/
.. _Issue Tracker: http://code.google.com/p/wsgi-intercept/issues/list
Platform: UNKNOWN
wsgi_intercept-0.5.1/wsgi_intercept.egg-info/SOURCES.txt 0000644 0000765 0000024 00000003135 11660472720 023376 0 ustar cdent staff 0000000 0000000 AUTHORS.txt
CHANGELOG.txt
LICENSE.txt
README.txt
run-tests
setup.cfg
setup.py
tox.ini
docs/README.txt
docs/index.rst
wsgi_intercept/__init__.py
wsgi_intercept/httplib2_intercept.py
wsgi_intercept/httplib_intercept.py
wsgi_intercept/mock_http.py
wsgi_intercept/test_wsgi_app.py
wsgi_intercept.egg-info/PKG-INFO
wsgi_intercept.egg-info/SOURCES.txt
wsgi_intercept.egg-info/dependency_links.txt
wsgi_intercept.egg-info/top_level.txt
wsgi_intercept/mechanize_intercept/__init__.py
wsgi_intercept/mechanize_intercept/wsgi_browser.py
wsgi_intercept/setup_cmd/__init__.py
wsgi_intercept/setup_cmd/build_docs.py
wsgi_intercept/setup_cmd/publish_docs.py
wsgi_intercept/test/__init__.py
wsgi_intercept/test/test_httplib.py
wsgi_intercept/test/test_httplib2.py
wsgi_intercept/test/test_mechanize.py
wsgi_intercept/test/test_webtest.py
wsgi_intercept/test/test_webunit.py
wsgi_intercept/test/test_wsgi_compliance.py
wsgi_intercept/test/test_wsgi_urllib2.py
wsgi_intercept/test/test_zope_testbrowser.py
wsgi_intercept/urllib2_intercept/__init__.py
wsgi_intercept/urllib2_intercept/wsgi_urllib2.py
wsgi_intercept/webtest_intercept/__init__.py
wsgi_intercept/webtest_intercept/webtest.py
wsgi_intercept/webunit_intercept/HTMLParser.py
wsgi_intercept/webunit_intercept/IMGSucker.py
wsgi_intercept/webunit_intercept/SimpleDOM.py
wsgi_intercept/webunit_intercept/__init__.py
wsgi_intercept/webunit_intercept/config.py
wsgi_intercept/webunit_intercept/cookie.py
wsgi_intercept/webunit_intercept/utility.py
wsgi_intercept/webunit_intercept/webunittest.py
wsgi_intercept/zope_testbrowser/__init__.py
wsgi_intercept/zope_testbrowser/wsgi_testbrowser.py wsgi_intercept-0.5.1/wsgi_intercept.egg-info/top_level.txt 0000644 0000765 0000024 00000000017 11660472720 024240 0 ustar cdent staff 0000000 0000000 wsgi_intercept