summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin Kubasik <kevin@kubasik.net>2009-07-22 13:12:26 +0000
committerKevin Kubasik <kevin@kubasik.net>2009-07-22 13:12:26 +0000
commitafa7b39d6ffa7389585cdbeec0c161a061b44961 (patch)
treeb4edd5cebcd250681d857a144283c4aac05de0a4
parent2511eb2759097be51a3cf255762cbc1dca2f1c08 (diff)
[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
-rw-r--r--django/test/test_coverage.py11
-rw-r--r--django/test/twill_tests.py332
-rw-r--r--tests/regressiontests/admin_views/windmilltests/primary.py8
-rwxr-xr-xtests/runtests.py17
4 files changed, 356 insertions, 12 deletions
diff --git a/django/test/test_coverage.py b/django/test/test_coverage.py
index fa78cf66bc..7c22edacb1 100644
--- a/django/test/test_coverage.py
+++ b/django/test/test_coverage.py
@@ -24,8 +24,11 @@ class BaseCoverageRunner(object):
def __init__(self):
"""Placeholder (since it is overrideable)"""
- self.cov = coverage.coverage(cover_pylib=True)
- self.cov.erase()
+ self.cov = coverage.coverage(cover_pylib=True, auto_data=True)
+ self.cov.use_cache(True)
+ self.cov.load()
+ #self.cov.combine()
+
def run_tests(self, test_labels, verbosity=1, interactive=True,
@@ -36,7 +39,7 @@ class BaseCoverageRunner(object):
"""
#self.cov.erase()
#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', []):
# self.cov.exclude(e)
@@ -45,7 +48,7 @@ class BaseCoverageRunner(object):
brt = base_run_tests()
results = brt.run_tests(test_labels, verbosity, interactive, extra_tests)
self.cov.stop()
-
+ #self.cov.erase()
coverage_modules = []
if test_labels:
diff --git a/django/test/twill_tests.py b/django/test/twill_tests.py
new file mode 100644
index 0000000000..e14dd6e14c
--- /dev/null
+++ b/django/test/twill_tests.py
@@ -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)
diff --git a/tests/regressiontests/admin_views/windmilltests/primary.py b/tests/regressiontests/admin_views/windmilltests/primary.py
index 9c2ff67d64..3b074239bc 100644
--- a/tests/regressiontests/admin_views/windmilltests/primary.py
+++ b/tests/regressiontests/admin_views/windmilltests/primary.py
@@ -27,7 +27,7 @@ def test_loginAndSetup():
client.asserts.assertNode(link=u'Change')
client.asserts.assertNode(link=u'Admin_Views')
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.type(text=u'Test Section', id=u'id_name')
client.click(name=u'_save')
@@ -324,15 +324,15 @@ def test_parentChildRelationship():
client.open(url=ADMIN_URL)
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.click(xpath=u"//div[@id='content-main']/div/table/tbody/tr[21]/td/a")
+ 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[22]/td/a")
client.click(name=u'_save')
client.waits.forPageLoad(timeout=u'20000')
client.click(link=u'Recommender object')
client.waits.forPageLoad(timeout=u'20000')
client.click(link=u'Home')
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.select(option=u'Recommender object', id=u'id_recommender')
client.click(value=u'1')
diff --git a/tests/runtests.py b/tests/runtests.py
index 47a4847a7f..d9332ff66d 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -1,4 +1,14 @@
#!/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 unittest
import django
@@ -11,10 +21,7 @@ try:
except NameError:
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'
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.
if do_std:
if do_coverage:
+ _dj_cover.save()
+ _dj_cover.stop()
test_runner = get_runner(settings, coverage=True, reports=True)
else:
test_runner = get_runner(settings, coverage=False, reports=False)