mirror of
https://github.com/django/django.git
synced 2025-07-05 10:19:20 +00:00
[gsoc2009-testing] Finally fixed the coverage issue by tracing all imports to a file, then loading on test run. Also, this has a copy of the python twill runner, still working on the DSL version
git-svn-id: http://code.djangoproject.com/svn/django/branches/soc2009/test-improvements@11294 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
2511eb2759
commit
afa7b39d6f
@ -24,8 +24,11 @@ class BaseCoverageRunner(object):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Placeholder (since it is overrideable)"""
|
"""Placeholder (since it is overrideable)"""
|
||||||
self.cov = coverage.coverage(cover_pylib=True)
|
self.cov = coverage.coverage(cover_pylib=True, auto_data=True)
|
||||||
self.cov.erase()
|
self.cov.use_cache(True)
|
||||||
|
self.cov.load()
|
||||||
|
#self.cov.combine()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run_tests(self, test_labels, verbosity=1, interactive=True,
|
def run_tests(self, test_labels, verbosity=1, interactive=True,
|
||||||
@ -36,7 +39,7 @@ class BaseCoverageRunner(object):
|
|||||||
"""
|
"""
|
||||||
#self.cov.erase()
|
#self.cov.erase()
|
||||||
#Allow an on-disk cache of coverage stats.
|
#Allow an on-disk cache of coverage stats.
|
||||||
self.cov.use_cache(0)
|
#self.cov.use_cache(0)
|
||||||
#for e in getattr(settings, 'COVERAGE_CODE_EXCLUDES', []):
|
#for e in getattr(settings, 'COVERAGE_CODE_EXCLUDES', []):
|
||||||
# self.cov.exclude(e)
|
# self.cov.exclude(e)
|
||||||
|
|
||||||
@ -45,7 +48,7 @@ class BaseCoverageRunner(object):
|
|||||||
brt = base_run_tests()
|
brt = base_run_tests()
|
||||||
results = brt.run_tests(test_labels, verbosity, interactive, extra_tests)
|
results = brt.run_tests(test_labels, verbosity, interactive, extra_tests)
|
||||||
self.cov.stop()
|
self.cov.stop()
|
||||||
|
#self.cov.erase()
|
||||||
|
|
||||||
coverage_modules = []
|
coverage_modules = []
|
||||||
if test_labels:
|
if test_labels:
|
||||||
|
332
django/test/twill_tests.py
Normal file
332
django/test/twill_tests.py
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
This code is originally by miracle2k:
|
||||||
|
http://bitbucket.org/miracle2k/djutils/src/97f92c32c621/djutils/test/twill.py
|
||||||
|
|
||||||
|
|
||||||
|
Integrates the twill web browsing scripting language with Django.
|
||||||
|
|
||||||
|
Provides too main functions, ``setup()`` and ``teardown``, that hook
|
||||||
|
(and unhook) a certain host name to the WSGI interface of your Django
|
||||||
|
app, making it possible to test your site using twill without actually
|
||||||
|
going through TCP/IP.
|
||||||
|
|
||||||
|
It also changes the twill browsing behaviour, so that relative urls
|
||||||
|
per default point to the intercept (e.g. your Django app), so long
|
||||||
|
as you don't browse away from that host. Further, you are allowed to
|
||||||
|
specify the target url as arguments to Django's ``reverse()``.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
|
||||||
|
from test_utils.utils import twill_runner as twill
|
||||||
|
twill.setup()
|
||||||
|
try:
|
||||||
|
twill.go('/') # --> Django WSGI
|
||||||
|
twill.code(200)
|
||||||
|
|
||||||
|
twill.go('http://google.com')
|
||||||
|
twill.go('/services') # --> http://google.com/services
|
||||||
|
|
||||||
|
twill.go('/list', default=True) # --> back to Django WSGI
|
||||||
|
|
||||||
|
twill.go('proj.app.views.func',
|
||||||
|
args=[1,2,3])
|
||||||
|
finally:
|
||||||
|
twill.teardown()
|
||||||
|
|
||||||
|
For more information about twill, see:
|
||||||
|
http://twill.idyll.org/
|
||||||
|
"""
|
||||||
|
|
||||||
|
# allows us to import global twill as opposed to this module
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
# TODO: import all names with a _-prefix to keep the namespace clean with the twill stuff?
|
||||||
|
import urlparse
|
||||||
|
import cookielib
|
||||||
|
|
||||||
|
import twill
|
||||||
|
import twill.commands
|
||||||
|
import twill.browser
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.servers.basehttp import AdminMediaHandler
|
||||||
|
from django.core.handlers.wsgi import WSGIHandler
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from django.utils.datastructures import SortedDict
|
||||||
|
from django.contrib import auth
|
||||||
|
|
||||||
|
from django.core.management.commands.test_windmill import ServerContainer, attempt_import
|
||||||
|
|
||||||
|
# make available through this module
|
||||||
|
from twill.commands import *
|
||||||
|
|
||||||
|
__all__ = ('INSTALLED', 'setup', 'teardown', 'reverse',) + tuple(twill.commands.__all__)
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_HOST = '127.0.0.1'
|
||||||
|
DEFAULT_PORT = 9090
|
||||||
|
INSTALLED = SortedDict() # keep track of the installed hooks
|
||||||
|
|
||||||
|
|
||||||
|
def setup(host=None, port=None, allow_xhtml=True, propagate=True):
|
||||||
|
"""Install the WSGI hook for ``host`` and ``port``.
|
||||||
|
|
||||||
|
The default values will be used if host or port are not specified.
|
||||||
|
|
||||||
|
``allow_xhtml`` enables a workaround for the "not viewer HTML"
|
||||||
|
error when browsing sites that are determined to be XHTML, e.g.
|
||||||
|
featuring xhtml-ish mimetypes.
|
||||||
|
|
||||||
|
Unless ``propagate specifies otherwise``, the
|
||||||
|
``DEBUG_PROPAGATE_EXCEPTIONS`` will be enabled for better debugging:
|
||||||
|
when using twill, we don't really want to see 500 error pages,
|
||||||
|
but rather directly the exceptions that occured on the view side.
|
||||||
|
|
||||||
|
Multiple calls to this function will only result in one handler
|
||||||
|
for each host/port combination being installed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
host = host or DEFAULT_HOST
|
||||||
|
port = port or DEFAULT_PORT
|
||||||
|
key = (host, port)
|
||||||
|
|
||||||
|
if not key in INSTALLED:
|
||||||
|
# installer wsgi handler
|
||||||
|
app = AdminMediaHandler(WSGIHandler())
|
||||||
|
twill.add_wsgi_intercept(host, port, lambda: app)
|
||||||
|
|
||||||
|
# start browser fresh
|
||||||
|
browser = get_browser()
|
||||||
|
browser.diverged = False
|
||||||
|
|
||||||
|
# enable xhtml mode if requested
|
||||||
|
_enable_xhtml(browser, allow_xhtml)
|
||||||
|
|
||||||
|
# init debug propagate setting, and remember old value
|
||||||
|
if propagate:
|
||||||
|
old_propgate_setting = settings.DEBUG_PROPAGATE_EXCEPTIONS
|
||||||
|
settings.DEBUG_PROPAGATE_EXCEPTIONS = True
|
||||||
|
else:
|
||||||
|
old_propgate_setting = None
|
||||||
|
|
||||||
|
INSTALLED[key] = (app, old_propgate_setting)
|
||||||
|
return browser
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def teardown(host=None, port=None):
|
||||||
|
"""Remove an installed WSGI hook for ``host`` and ```port``.
|
||||||
|
|
||||||
|
If no host or port is passed, the default values will be assumed.
|
||||||
|
If no hook is installed for the defaults, and both the host and
|
||||||
|
port are missing, the last hook installed will be removed.
|
||||||
|
|
||||||
|
Returns True if a hook was removed, otherwise False.
|
||||||
|
"""
|
||||||
|
|
||||||
|
both_missing = not host and not port
|
||||||
|
host = host or DEFAULT_HOST
|
||||||
|
port = port or DEFAULT_PORT
|
||||||
|
key = (host, port)
|
||||||
|
|
||||||
|
key_to_delete = None
|
||||||
|
if key in INSTALLED:
|
||||||
|
key_to_delete = key
|
||||||
|
if not key in INSTALLED and both_missing and len(INSTALLED) > 0:
|
||||||
|
host, port = key_to_delete = INSTALLED.keys()[-1]
|
||||||
|
|
||||||
|
if key_to_delete:
|
||||||
|
_, old_propagate = INSTALLED[key_to_delete]
|
||||||
|
del INSTALLED[key_to_delete]
|
||||||
|
result = True
|
||||||
|
if old_propagate is not None:
|
||||||
|
settings.DEBUG_PROPAGATE_EXCEPTIONS = old_propagate
|
||||||
|
else:
|
||||||
|
result = False
|
||||||
|
|
||||||
|
# note that our return value is just a guess according to our
|
||||||
|
# own records, we pass the request on to twill in any case
|
||||||
|
twill.remove_wsgi_intercept(host, port)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _enable_xhtml(browser, enable):
|
||||||
|
"""Twill (darcs from 19-09-2008) does not work with documents
|
||||||
|
identifying themselves as XHTML.
|
||||||
|
|
||||||
|
This is a workaround.
|
||||||
|
"""
|
||||||
|
factory = browser._browser._factory
|
||||||
|
factory.basic_factory._response_type_finder._allow_xhtml = \
|
||||||
|
factory.soup_factory._response_type_finder._allow_xhtml = \
|
||||||
|
enable
|
||||||
|
|
||||||
|
|
||||||
|
class _EasyTwillBrowser(twill.browser.TwillBrowser):
|
||||||
|
"""Custom version of twill's browser class that defaults relative
|
||||||
|
URLs to the last installed hook, if available.
|
||||||
|
|
||||||
|
It also supports reverse resolving, and some additional commands.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.diverged = False
|
||||||
|
self._testing_ = False
|
||||||
|
super(_EasyTwillBrowser, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def go(self, url, args=None, kwargs=None, default=None):
|
||||||
|
assert not ((args or kwargs) and default==False)
|
||||||
|
|
||||||
|
if args or kwargs:
|
||||||
|
url = reverse(url, args=args, kwargs=kwargs)
|
||||||
|
default = True # default is implied
|
||||||
|
|
||||||
|
if INSTALLED:
|
||||||
|
netloc = '%s:%s' % INSTALLED.keys()[-1]
|
||||||
|
urlbits = urlparse.urlsplit(url)
|
||||||
|
if not urlbits[0]:
|
||||||
|
if default:
|
||||||
|
# force "undiverge"
|
||||||
|
self.diverged = False
|
||||||
|
if not self.diverged:
|
||||||
|
url = urlparse.urlunsplit(('http', netloc)+urlbits[2:])
|
||||||
|
else:
|
||||||
|
self.diverged = True
|
||||||
|
|
||||||
|
if self._testing_: # hack that makes it simple for us to test this
|
||||||
|
return url
|
||||||
|
return super(_EasyTwillBrowser, self).go(url)
|
||||||
|
|
||||||
|
def login(self, **credentials):
|
||||||
|
"""Log the user with the given credentials into your Django
|
||||||
|
site.
|
||||||
|
|
||||||
|
To further simplify things, rather than giving the credentials,
|
||||||
|
you may pass a ``user`` parameter with the ``User`` instance you
|
||||||
|
want to login. Note that in this case the user will not be
|
||||||
|
further validated, i.e. it is possible to login an inactive user
|
||||||
|
this way.
|
||||||
|
|
||||||
|
This works regardless of the url currently browsed, but does
|
||||||
|
require the WSGI intercept to be setup.
|
||||||
|
|
||||||
|
Returns ``True`` if login was possible; ``False`` if the
|
||||||
|
provided credentials are incorrect, or the user is inactive,
|
||||||
|
or if the sessions framework is not available.
|
||||||
|
|
||||||
|
Based on ``django.test.client.Client.logout``.
|
||||||
|
|
||||||
|
Note: A ``twill.reload()`` will not refresh the cookies sent
|
||||||
|
with the request, so your login will not have any effect there.
|
||||||
|
This is different for ``logout``, since it actually invalidates
|
||||||
|
the session server-side, thus making the current key invalid.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not 'django.contrib.sessions' in settings.INSTALLED_APPS:
|
||||||
|
return False
|
||||||
|
|
||||||
|
host, port = INSTALLED.keys()[-1]
|
||||||
|
|
||||||
|
# determine the user we want to login
|
||||||
|
user = credentials.pop('user', None)
|
||||||
|
if user:
|
||||||
|
# Login expects the user object to reference it's backend.
|
||||||
|
# Since we're not going through ``authenticate``, we'll
|
||||||
|
# have to do this ourselves.
|
||||||
|
backend = auth.get_backends()[0]
|
||||||
|
user.backend = user.backend = "%s.%s" % (
|
||||||
|
backend.__module__, backend.__class__.__name__)
|
||||||
|
else:
|
||||||
|
user = auth.authenticate(**credentials)
|
||||||
|
if not user or not user.is_active:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# create a fake request to use with ``auth.login``
|
||||||
|
request = HttpRequest()
|
||||||
|
request.session = __import__(settings.SESSION_ENGINE, {}, {}, ['']).SessionStore()
|
||||||
|
auth.login(request, user)
|
||||||
|
request.session.save()
|
||||||
|
|
||||||
|
# set the cookie to represent the session
|
||||||
|
self.cj.set_cookie(cookielib.Cookie(
|
||||||
|
version=None,
|
||||||
|
name=settings.SESSION_COOKIE_NAME,
|
||||||
|
value=request.session.session_key,
|
||||||
|
port=str(port), # must be a string
|
||||||
|
port_specified = False,
|
||||||
|
domain=host, #settings.SESSION_COOKIE_DOMAIN,
|
||||||
|
domain_specified=True,
|
||||||
|
domain_initial_dot=False,
|
||||||
|
path='/',
|
||||||
|
path_specified=True,
|
||||||
|
secure=settings.SESSION_COOKIE_SECURE or None,
|
||||||
|
expires=None,
|
||||||
|
discard=None,
|
||||||
|
comment=None,
|
||||||
|
comment_url=None,
|
||||||
|
rest=None
|
||||||
|
))
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def logout(self):
|
||||||
|
"""Log the current user out of your Django site.
|
||||||
|
|
||||||
|
This works regardless of the url currently browsed, but does
|
||||||
|
require the WSGI intercept to be setup.
|
||||||
|
|
||||||
|
Based on ``django.test.client.Client.logout``.
|
||||||
|
"""
|
||||||
|
host, port = INSTALLED.keys()[-1]
|
||||||
|
for cookie in self.cj:
|
||||||
|
if cookie.name == settings.SESSION_COOKIE_NAME \
|
||||||
|
and cookie.domain==host \
|
||||||
|
and (not cookie.port or str(cookie.port)==str(port)):
|
||||||
|
session = __import__(settings.SESSION_ENGINE, {}, {}, ['']).SessionStore()
|
||||||
|
session.delete(session_key=cookie.value)
|
||||||
|
self.cj.clear(cookie.domain, cookie.path, cookie.name)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def go(*args, **kwargs):
|
||||||
|
# replace the default ``go`` to make the additional
|
||||||
|
# arguments that our custom browser provides available.
|
||||||
|
browser = get_browser()
|
||||||
|
browser.go(*args, **kwargs)
|
||||||
|
return browser.get_url()
|
||||||
|
|
||||||
|
def login(*args, **kwargs):
|
||||||
|
return get_browser().login(*args, **kwargs)
|
||||||
|
|
||||||
|
def logout(*args, **kwargs):
|
||||||
|
return get_browser().logout(*args, **kwargs)
|
||||||
|
|
||||||
|
def reset_browser(*args, **kwargs):
|
||||||
|
# replace the default ``reset_browser`` to ensure
|
||||||
|
# that our custom browser class is used
|
||||||
|
result = twill.commands.reset_browser(*args, **kwargs)
|
||||||
|
twill.commands.browser = _EasyTwillBrowser()
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Monkey-patch our custom browser into twill; this will be global, but
|
||||||
|
# will only have an actual effect when intercepts are installed through
|
||||||
|
# our module (via ``setup``).
|
||||||
|
# Unfortunately, twill pretty much forces us to use the same global
|
||||||
|
# state it does itself, lest us reimplement everything from
|
||||||
|
# ``twill.commands``. It's a bit of a shame, we could provide dedicated
|
||||||
|
# browser instances for each call to ``setup()``.
|
||||||
|
reset_browser()
|
||||||
|
|
||||||
|
|
||||||
|
def url(should_be=None):
|
||||||
|
"""Like the default ``url()``, but can be called without arguments,
|
||||||
|
in which case it returns the current url.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if should_be is None:
|
||||||
|
return get_browser().get_url()
|
||||||
|
else:
|
||||||
|
return twill.commands.url(should_be)
|
@ -27,7 +27,7 @@ def test_loginAndSetup():
|
|||||||
client.asserts.assertNode(link=u'Change')
|
client.asserts.assertNode(link=u'Change')
|
||||||
client.asserts.assertNode(link=u'Admin_Views')
|
client.asserts.assertNode(link=u'Admin_Views')
|
||||||
client.asserts.assertNode(xpath=u"//div[@id='user-tools']/strong")
|
client.asserts.assertNode(xpath=u"//div[@id='user-tools']/strong")
|
||||||
client.click(xpath=u"//div[@id='content-main']/div/table/tbody/tr[22]/td/a")
|
client.click(xpath=u"//div[@id='content-main']/div/table/tbody/tr[23]/td/a")
|
||||||
client.waits.forPageLoad(timeout=u'20000')
|
client.waits.forPageLoad(timeout=u'20000')
|
||||||
client.type(text=u'Test Section', id=u'id_name')
|
client.type(text=u'Test Section', id=u'id_name')
|
||||||
client.click(name=u'_save')
|
client.click(name=u'_save')
|
||||||
@ -324,15 +324,15 @@ def test_parentChildRelationship():
|
|||||||
|
|
||||||
client.open(url=ADMIN_URL)
|
client.open(url=ADMIN_URL)
|
||||||
client.waits.forPageLoad(timeout=u'20000')
|
client.waits.forPageLoad(timeout=u'20000')
|
||||||
client.waits.forElement(xpath=u"//div[@id='content-main']/div/table/tbody/tr[21]/td/a", timeout=u'8000')
|
client.waits.forElement(xpath=u"//div[@id='content-main']/div/table/tbody/tr[22]/td/a", timeout=u'8000')
|
||||||
client.click(xpath=u"//div[@id='content-main']/div/table/tbody/tr[21]/td/a")
|
client.click(xpath=u"//div[@id='content-main']/div/table/tbody/tr[22]/td/a")
|
||||||
client.click(name=u'_save')
|
client.click(name=u'_save')
|
||||||
client.waits.forPageLoad(timeout=u'20000')
|
client.waits.forPageLoad(timeout=u'20000')
|
||||||
client.click(link=u'Recommender object')
|
client.click(link=u'Recommender object')
|
||||||
client.waits.forPageLoad(timeout=u'20000')
|
client.waits.forPageLoad(timeout=u'20000')
|
||||||
client.click(link=u'Home')
|
client.click(link=u'Home')
|
||||||
client.waits.forPageLoad(timeout=u'20000')
|
client.waits.forPageLoad(timeout=u'20000')
|
||||||
client.click(xpath=u"//div[@id='content-main']/div/table/tbody/tr[20]/td/a")
|
client.click(xpath=u"//div[@id='content-main']/div/table/tbody/tr[21]/td/a")
|
||||||
client.click(id=u'id_recommender')
|
client.click(id=u'id_recommender')
|
||||||
client.select(option=u'Recommender object', id=u'id_recommender')
|
client.select(option=u'Recommender object', id=u'id_recommender')
|
||||||
client.click(value=u'1')
|
client.click(value=u'1')
|
||||||
|
@ -1,4 +1,14 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
try:
|
||||||
|
import coverage
|
||||||
|
global _dj_cover
|
||||||
|
_dj_cover = coverage.coverage(cover_pylib=True, auto_data=True)
|
||||||
|
_dj_cover.erase()
|
||||||
|
_dj_cover.use_cache(True)
|
||||||
|
_dj_cover.start()
|
||||||
|
except Exception, e:
|
||||||
|
print "coverage.py module not available"
|
||||||
|
|
||||||
import os, sys, traceback
|
import os, sys, traceback
|
||||||
import unittest
|
import unittest
|
||||||
import django
|
import django
|
||||||
@ -11,10 +21,7 @@ try:
|
|||||||
except NameError:
|
except NameError:
|
||||||
from sets import Set as set # For Python 2.3
|
from sets import Set as set # For Python 2.3
|
||||||
|
|
||||||
try:
|
|
||||||
import coverage
|
|
||||||
except Exception, e:
|
|
||||||
print "coverage.py module not available"
|
|
||||||
|
|
||||||
CONTRIB_DIR_NAME = 'django.contrib'
|
CONTRIB_DIR_NAME = 'django.contrib'
|
||||||
MODEL_TESTS_DIR_NAME = 'modeltests'
|
MODEL_TESTS_DIR_NAME = 'modeltests'
|
||||||
@ -202,6 +209,8 @@ def django_tests(verbosity, interactive, test_labels):
|
|||||||
#Run the appropriate test runner based on command line params.
|
#Run the appropriate test runner based on command line params.
|
||||||
if do_std:
|
if do_std:
|
||||||
if do_coverage:
|
if do_coverage:
|
||||||
|
_dj_cover.save()
|
||||||
|
_dj_cover.stop()
|
||||||
test_runner = get_runner(settings, coverage=True, reports=True)
|
test_runner = get_runner(settings, coverage=True, reports=True)
|
||||||
else:
|
else:
|
||||||
test_runner = get_runner(settings, coverage=False, reports=False)
|
test_runner = get_runner(settings, coverage=False, reports=False)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user