Author: kkubasik Date: 2009-07-22 08:12:26 -0500 (Wed, 22 Jul 2009) New Revision: 11294
Added: django/branches/soc2009/test-improvements/django/test/twill_tests.py Modified: django/branches/soc2009/test-improvements/ django/branches/soc2009/test-improvements/django/test/test_coverage.py django/branches/soc2009/test-improvements/tests/regressiontests/admin_views/windmilltests/primary.py django/branches/soc2009/test-improvements/tests/runtests.py Log: [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 Property changes on: django/branches/soc2009/test-improvements ___________________________________________________________________ Name: svk:merge - 23ef3597-c209-482b-90c0-ea6045f15f7f:/local/django-gsoc:11116 23ef3597-c209-482b-90c0-ea6045f15f7f:/local/django/trunk:10927 bcc190cf-cafb-0310-a4f2-bffc1f526a37:/django/trunk:11277 + 23ef3597-c209-482b-90c0-ea6045f15f7f:/local/django-gsoc:11117 23ef3597-c209-482b-90c0-ea6045f15f7f:/local/django/trunk:10927 bcc190cf-cafb-0310-a4f2-bffc1f526a37:/django/trunk:11277 Modified: django/branches/soc2009/test-improvements/django/test/test_coverage.py =================================================================== --- django/branches/soc2009/test-improvements/django/test/test_coverage.py 2009-07-22 13:11:55 UTC (rev 11293) +++ django/branches/soc2009/test-improvements/django/test/test_coverage.py 2009-07-22 13:12:26 UTC (rev 11294) @@ -24,8 +24,11 @@ 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 @@ """ #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,8 +48,8 @@ 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: for label in test_labels: Added: django/branches/soc2009/test-improvements/django/test/twill_tests.py =================================================================== --- django/branches/soc2009/test-improvements/django/test/twill_tests.py (rev 0) +++ django/branches/soc2009/test-improvements/django/test/twill_tests.py 2009-07-22 13:12:26 UTC (rev 11294) @@ -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) Modified: django/branches/soc2009/test-improvements/tests/regressiontests/admin_views/windmilltests/primary.py =================================================================== --- django/branches/soc2009/test-improvements/tests/regressiontests/admin_views/windmilltests/primary.py 2009-07-22 13:11:55 UTC (rev 11293) +++ django/branches/soc2009/test-improvements/tests/regressiontests/admin_views/windmilltests/primary.py 2009-07-22 13:12:26 UTC (rev 11294) @@ -27,7 +27,7 @@ client.asserts.assertNode(link=u'Change') client.asserts.assertNode(link=u'Admin_Views') client.asserts.assertNode(xpath=u"//d...@id='user-tools']/strong") - client.click(xpath=u"//d...@id='content-main']/div/table/tbody/tr[22]/td/a") + client.click(xpath=u"//d...@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 @@ client.open(url=ADMIN_URL) client.waits.forPageLoad(timeout=u'20000') - client.waits.forElement(xpath=u"//d...@id='content-main']/div/table/tbody/tr[21]/td/a", timeout=u'8000') - client.click(xpath=u"//d...@id='content-main']/div/table/tbody/tr[21]/td/a") + client.waits.forElement(xpath=u"//d...@id='content-main']/div/table/tbody/tr[22]/td/a", timeout=u'8000') + client.click(xpath=u"//d...@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"//d...@id='content-main']/div/table/tbody/tr[20]/td/a") + client.click(xpath=u"//d...@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') Modified: django/branches/soc2009/test-improvements/tests/runtests.py =================================================================== --- django/branches/soc2009/test-improvements/tests/runtests.py 2009-07-22 13:11:55 UTC (rev 11293) +++ django/branches/soc2009/test-improvements/tests/runtests.py 2009-07-22 13:12:26 UTC (rev 11294) @@ -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,11 +21,8 @@ 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' REGRESSION_TESTS_DIR_NAME = 'regressiontests' @@ -202,6 +209,8 @@ #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) --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Django updates" group. To post to this group, send email to django-updates@googlegroups.com To unsubscribe from this group, send email to django-updates+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/django-updates?hl=en -~----------~----~----~----~------~----~------~--~---