diff options
44 files changed, 2748 insertions, 67 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..0d20b6487c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 99fc72e468..eb9629990d 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -379,7 +379,7 @@ PASSWORD_RESET_TIMEOUT_DAYS = 3 ########### # The name of the method to use to invoke the test suite -TEST_RUNNER = 'django.test.simple.run_tests' +TEST_RUNNER = 'django.test.simple.DefaultTestRunner' # The name of the database to use for testing purposes. # If None, a name of 'test_' + DATABASE_NAME will be assumed @@ -393,6 +393,55 @@ TEST_DATABASE_CHARSET = None TEST_DATABASE_COLLATION = None ############ +# COVERAGE # +############ + + +# Specify the coverage test runner +COVERAGE_TEST_RUNNER = 'django.test.test_coverage.ConsoleReportCoverageRunner' + +# Specify regular expressions of code blocks the coverage analyzer should +# ignore as statements (e.g. ``raise NotImplemented``). +# These statements are not figured in as part of the coverage statistics. +# This setting is optional. +COVERAGE_CODE_EXCLUDES = [ + 'def __unicode__\(self\):', 'def get_absolute_url\(self\):', + 'from .* import .*', 'import .*', + ] + +# Specify a list of regular expressions of paths to exclude from +# coverage analysis. +# Note these paths are ignored by the module introspection tool and take +# precedence over any package/module settings such as: +# TODO: THE SETTING FOR MODULES +# Use this to exclude subdirectories like ``r'.svn'``, for example. +# This setting is optional. +COVERAGE_PATH_EXCLUDES = [r'.svn'] + +# Specify a list of additional module paths to include +# in the coverage analysis. By default, only modules within installed +# apps are reported. If you have utility modules outside of the app +# structure, you can include them here. +# Note this list is *NOT* regular expression, so you have to be explicit, +# such as 'myproject.utils', and not 'utils$'. +# This setting is optional. +COVERAGE_ADDITIONAL_MODULES = [] + +# Specify a list of regular expressions of module paths to exclude +# from the coverage analysis. Examples are ``'tests$'`` and ``'urls$'``. +# This setting is optional. +COVERAGE_MODULE_EXCLUDES = ['tests$', 'settings$','urls$', 'common.views.test', + '__init__', 'django'] + +# Specify the directory where you would like the coverage report to create +# the HTML files. +# You'll need to make sure this directory exists and is writable by the +# user account running the test. +# You should probably set this one explicitly in your own settings file. +COVERAGE_REPORT_HTML_OUTPUT_DIR = 'test_html' + + +############ # FIXTURES # ############ diff --git a/django/conf/locale/sr_Latn/LC_MESSAGES/django.po b/django/conf/locale/sr_Latn/LC_MESSAGES/django.po index fd7ae5f869..06dfd356b9 100644 --- a/django/conf/locale/sr_Latn/LC_MESSAGES/django.po +++ b/django/conf/locale/sr_Latn/LC_MESSAGES/django.po @@ -314,7 +314,7 @@ msgstr "zapisi u logovima" #: contrib/admin/options.py:133 contrib/admin/options.py:147 msgid "None" -msgstr "Ništa" +msgstr "Ништа" #: contrib/admin/options.py:519 #, python-format diff --git a/django/contrib/auth/tests/views.py b/django/contrib/auth/tests/views.py index 532f92b523..d7915fd51d 100644 --- a/django/contrib/auth/tests/views.py +++ b/django/contrib/auth/tests/views.py @@ -9,6 +9,7 @@ from django.contrib.auth.models import User from django.test import TestCase from django.core import mail from django.core.urlresolvers import reverse +from django.test.decorators import views_required class AuthViewsTestCase(TestCase): """ @@ -49,22 +50,26 @@ class PasswordResetTest(AuthViewsTestCase): def test_email_not_found(self): "Error is raised if the provided email address isn't currently registered" - response = self.client.get('/password_reset/') + response = self.client.get(reverse('django.contrib.auth.views.password_reset')) self.assertEquals(response.status_code, 200) - response = self.client.post('/password_reset/', {'email': 'not_a_real_email@email.com'}) + response = self.client.post(reverse('django.contrib.auth.views.password_reset'), {'email': 'not_a_real_email@email.com'}) self.assertContains(response, "That e-mail address doesn't have an associated user account") self.assertEquals(len(mail.outbox), 0) - + + test_email_not_found = views_required(required_views=['django.contrib.auth.views.password_reset'])(test_email_not_found) + def test_email_found(self): "Email is sent if a valid email address is provided for password reset" - response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) + response = self.client.post(reverse('django.contrib.auth.views.password_reset'), {'email': 'staffmember@example.com'}) self.assertEquals(response.status_code, 302) self.assertEquals(len(mail.outbox), 1) self.assert_("http://" in mail.outbox[0].body) + + test_email_found = views_required(required_views=['django.contrib.auth.views.password_reset'])(test_email_found) def _test_confirm_start(self): # Start by creating the email - response = self.client.post('/password_reset/', {'email': 'staffmember@example.com'}) + response = self.client.post(reverse('django.contrib.auth.views.password_reset'), {'email': 'staffmember@example.com'}) self.assertEquals(response.status_code, 302) self.assertEquals(len(mail.outbox), 1) return self._read_signup_email(mail.outbox[0]) @@ -80,6 +85,8 @@ class PasswordResetTest(AuthViewsTestCase): # redirect to a 'complete' page: self.assertEquals(response.status_code, 200) self.assert_("Please enter your new password" in response.content) + test_confirm_valid = views_required(required_views=['django.contrib.auth.views.password_reset'])(test_confirm_valid) + def test_confirm_invalid(self): url, path = self._test_confirm_start() @@ -90,6 +97,7 @@ class PasswordResetTest(AuthViewsTestCase): response = self.client.get(path) self.assertEquals(response.status_code, 200) self.assert_("The password reset link was invalid" in response.content) + test_confirm_invalid = views_required(required_views=['django.contrib.auth.views.password_reset'])(test_confirm_invalid) def test_confirm_invalid_post(self): # Same as test_confirm_invalid, but trying @@ -102,6 +110,7 @@ class PasswordResetTest(AuthViewsTestCase): # Check the password has not been changed u = User.objects.get(email='staffmember@example.com') self.assert_(not u.check_password("anewpassword")) + test_confirm_invalid_post = views_required(required_views=['django.contrib.auth.views.password_reset'])(test_confirm_invalid_post) def test_confirm_complete(self): url, path = self._test_confirm_start() @@ -117,6 +126,7 @@ class PasswordResetTest(AuthViewsTestCase): response = self.client.get(path) self.assertEquals(response.status_code, 200) self.assert_("The password reset link was invalid" in response.content) + test_confirm_complete = views_required(required_views=['django.contrib.auth.views.password_reset'])(test_confirm_complete) def test_confirm_different_passwords(self): url, path = self._test_confirm_start() @@ -124,7 +134,8 @@ class PasswordResetTest(AuthViewsTestCase): 'new_password2':' x'}) self.assertEquals(response.status_code, 200) self.assert_("The two password fields didn't match" in response.content) - + + test_confirm_different_passwords = views_required(required_views=['django.contrib.auth.views.password_reset'])(test_confirm_different_passwords) class ChangePasswordTest(AuthViewsTestCase): def login(self, password='password'): diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py index fbe539274e..54203c532b 100644 --- a/django/core/management/commands/inspectdb.py +++ b/django/core/management/commands/inspectdb.py @@ -15,8 +15,9 @@ class Command(NoArgsCommand): def handle_inspection(self): from django.db import connection import keyword - - table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '') + + table2model = lambda table_name: table_name.title() + .replace('_', '').replace(' ', '').replace('-', '').replace('*','_').replace(',','_') cursor = connection.cursor() yield "# This is an auto-generated Django model module." diff --git a/django/core/management/commands/test.py b/django/core/management/commands/test.py index 8ebf3daea6..92949fc367 100644 --- a/django/core/management/commands/test.py +++ b/django/core/management/commands/test.py @@ -6,6 +6,10 @@ class Command(BaseCommand): option_list = BaseCommand.option_list + ( make_option('--noinput', action='store_false', dest='interactive', default=True, help='Tells Django to NOT prompt the user for input of any kind.'), + make_option('--coverage', action='store_true', dest='coverage', default=False, + help='Tells Django to run the coverage runner'), + make_option('--reports', action='store_true', dest='reports', default=False, + help='Tells Django to output coverage results as HTML reports'), ) help = 'Runs the test suite for the specified applications, or the entire site if no apps are specified.' args = '[appname ...]' @@ -18,8 +22,13 @@ class Command(BaseCommand): verbosity = int(options.get('verbosity', 1)) interactive = options.get('interactive', True) - test_runner = get_runner(settings) - - failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive) + cover = options.get('coverage', False) + report = options.get('reports', False) + test_runner = get_runner(settings, coverage=cover, reports=report) + if(type(test_runner) == 'function'): + failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive) + else: + tr = test_runner() + failures = tr.run_tests(test_labels, verbosity=verbosity, interactive=interactive) if failures: sys.exit(failures) diff --git a/django/core/management/commands/test_windmill.py b/django/core/management/commands/test_windmill.py new file mode 100644 index 0000000000..c3da16fe84 --- /dev/null +++ b/django/core/management/commands/test_windmill.py @@ -0,0 +1,122 @@ +from django.core.management.base import BaseCommand +#from windmill.authoring import djangotest +from django.utils import importlib +from django.test import windmill_tests as djangotest +import sys, os +from time import sleep +import types +import logging +import threading + +class ServerContainer(object): + start_test_server = djangotest.start_test_server + stop_test_server = djangotest.stop_test_server + +def attempt_import(name, suffix): + try: + mod = importlib.import_module(name+'.'+suffix) + except ImportError: + mod = None + if mod is not None: + s = name.split('.') + mod = importlib.import_module(s.pop(0)) + for x in s+[suffix]: + try: + mod = getattr(mod, x) + except Exception, e: + pass + return mod + +class Command(BaseCommand): + + help = "Run windmill tests. Specify a browser, if one is not passed Firefox will be used" + + args = '<label label ...>' + label = 'label' + + def handle(self, *labels, **options): + + from windmill.conf import global_settings + from django.test.windmill_tests import WindmillDjangoUnitTest + if 'ie' in labels: + global_settings.START_IE = True + sys.argv.remove('ie') + elif 'safari' in labels: + global_settings.START_SAFARI = True + sys.argv.remove('safari') + elif 'chrome' in labels: + global_settings.START_CHROME = True + sys.argv.remove('chrome') + else: + global_settings.START_FIREFOX = True + if 'firefox' in labels: + sys.argv.remove('firefox') + + if 'manage.py' in sys.argv: + sys.argv.remove('manage.py') + if 'test_windmill' in sys.argv: + sys.argv.remove('test_windmill') + + from django.conf import settings + tests = [] + for name in settings.INSTALLED_APPS: + for suffix in ['tests', 'wmtests', 'windmilltests']: + x = attempt_import(name, suffix) + if x is not None: tests.append((suffix,x,)); + wmfixs = [] + wmtests = [] + for (ttype, mod,) in tests: + if ttype == 'tests': + for ucls in [getattr(mod, x) for x in dir(mod) + if ( type(getattr(mod, x, None)) in (types.ClassType, + types.TypeType) ) and + issubclass(getattr(mod, x), WindmillDjangoUnitTest) + ]: + wmtests.append(ucls.test_dir) + + else: + if mod.__file__.endswith('__init__.py') or mod.__file__.endswith('__init__.pyc'): + wmtests.append(os.path.join(*os.path.split(os.path.abspath(mod.__file__))[:-1])) + else: + wmtests.append(os.path.abspath(mod.__file__)) + # Look for any attribute named fixtures and try to load it. + if hasattr(mod, 'fixtures'): + for fixture in getattr(mod, 'fixtures'): + wmfixs.append(fixture) + + # Create the threaded server. + server_container = ServerContainer() + # Set the server's 'fixtures' attribute so they can be loaded in-thread if using sqlite's memory backend. + server_container.__setattr__('fixtures', wmfixs) + # Start the server thread. + started = server_container.start_test_server() + + print 'Waiting for threaded server to come online.' + started.wait() + print 'DB Ready, Server online.' + + global_settings.TEST_URL = 'http://localhost:%d' % server_container.server_thread.port + + # import windmill + # windmill.stdout, windmill.stdin = sys.stdout, sys.stdin + from windmill.authoring import setup_module, teardown_module + + + if len(wmtests) is 0: + print 'Sorry, no windmill tests found.' + else: + testtotals = {} + x = logging.getLogger() + x.setLevel(0) + from windmill.server.proxy import logger + from functest import bin + from functest import runner + runner.CLIRunner.final = classmethod(lambda self, totals: testtotals.update(totals) ) + import windmill + setup_module(tests[0][1]) + sys.argv = sys.argv + wmtests + bin.cli() + teardown_module(tests[0][1]) + if testtotals['fail'] is not 0: + sleep(.5) + sys.exit(1) diff --git a/django/db/models/loading.py b/django/db/models/loading.py index e07aab4efe..96ebe43941 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -10,7 +10,7 @@ import os import threading __all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models', - 'load_app', 'app_cache_ready') + 'load_app', 'app_cache_ready', 'remove_model') class AppCache(object): """ @@ -178,6 +178,39 @@ class AppCache(object): continue model_dict[model_name] = model + def clear_apps(self): + """Clears cache so on next call, it will be reloaded.""" + self.loaded = False + self.write_lock.acquire() + self.handled.clear() + try: + if self.loaded: + return + for app_name in settings.INSTALLED_APPS: + if app_name in self.handled: + continue + self.load_app(app_name, True) + if not self.nesting_level: + for app_name in self.postponed: + self.load_app(app_name) + self.loaded = True + finally: + self.write_lock.release() + + def remove_model(self, model_name): + """Removes a model from the cache. Used when loading test-only models.""" + try: + try: + self.write_lock.acquire() + if model_name in self.app_models: + del self.app_models[model_name] + except Exception, e: + raise e + finally: + self.write_lock.release() + + + cache = AppCache() # These methods were always module level, so are kept that way for backwards @@ -190,3 +223,4 @@ get_model = cache.get_model register_models = cache.register_models load_app = cache.load_app app_cache_ready = cache.app_cache_ready +remove_model = cache.remove_model
\ No newline at end of file diff --git a/django/test/__init__.py b/django/test/__init__.py index 957b293e12..e5647243f2 100644 --- a/django/test/__init__.py +++ b/django/test/__init__.py @@ -4,3 +4,10 @@ Django Unit Test and Doctest framework. from django.test.client import Client from django.test.testcases import TestCase, TransactionTestCase + +class SkippedTest(Exception): + def __init__(self, reason): + self.reason = reason + + def __str__(self): + return self.reason
\ No newline at end of file diff --git a/django/test/decorators.py b/django/test/decorators.py new file mode 100644 index 0000000000..ca621894e3 --- /dev/null +++ b/django/test/decorators.py @@ -0,0 +1,44 @@ +from django.core import urlresolvers +from django.test import SkippedTest + +def views_required(required_views=[]): + def urls_found(): + try: + for view in required_views: + urlresolvers.reverse(view) + return True + except urlresolvers.NoReverseMatch: + return False + reason = 'Required view%s for this test not found: %s' % \ + (len(required_views) > 1 and 's' or '', ', '.join(required_views)) + return conditional_skip(urls_found, reason=reason) + +def modules_required(required_modules=[]): + def modules_found(): + try: + for module in required_modules: + __import__(module) + return True + except ImportError: + return False + reason = 'Required module%s for this test not found: %s' % \ + (len(required_modules) > 1 and 's' or '', ', '.join(required_modules)) + return conditional_skip(modules_found, reason=reason) + +def skip_specific_database(database_engine): + def database_check(): + from django.conf import settings + return database_engine == settings.DATABASE_ENGINE + reason = 'Test not run for database engine %s.' % database_engine + return conditional_skip(database_check, reason=reason) + +def conditional_skip(required_condition, reason=''): + if required_condition(): + return lambda x: x + else: + return skip_test(reason) + +def skip_test(reason=''): + def _skip(x): + raise SkippedTest(reason=reason) + return lambda x: _skip diff --git a/django/test/mocks.py b/django/test/mocks.py new file mode 100644 index 0000000000..94048a2a2a --- /dev/null +++ b/django/test/mocks.py @@ -0,0 +1,38 @@ +from django.test import Client +from django.core.handlers.wsgi import WSGIRequest + +class RequestFactory(Client): + """ + Class that lets you create mock Request objects for use in testing. + + Usage: + + rf = RequestFactory() + get_request = rf.get('/hello/') + post_request = rf.post('/submit/', {'foo': 'bar'}) + + This class re-uses the django.test.client.Client interface, docs here: + http://www.djangoproject.com/documentation/testing/#the-test-client + + Once you have a request object you can pass it to any view function, + just as if that view had been hooked up using a URLconf. + + """ + def request(self, **request): + """ + Similar to parent class, but returns the request object as soon as it + has created it. + """ + environ = { + 'HTTP_COOKIE': self.cookies, + 'PATH_INFO': '/', + 'QUERY_STRING': '', + 'REQUEST_METHOD': 'GET', + 'SCRIPT_NAME': '', + 'SERVER_NAME': 'testserver', + 'SERVER_PORT': 80, + 'SERVER_PROTOCOL': 'HTTP/1.1', + } + environ.update(self.defaults) + environ.update(request) + return WSGIRequest(environ) diff --git a/django/test/simple.py b/django/test/simple.py index f3c48bae33..8a60b69b36 100644 --- a/django/test/simple.py +++ b/django/test/simple.py @@ -1,4 +1,4 @@ -import unittest +import sys, time, traceback, unittest from django.conf import settings from django.db.models import get_app, get_apps from django.test import _doctest as doctest @@ -146,52 +146,151 @@ def reorder_suite(suite, classes): bins[0].addTests(bins[i+1]) return bins[0] -def run_tests(test_labels, verbosity=1, interactive=True, extra_tests=[]): +class DefaultTestRunner(object): + """ + The original test runner. No coverage reporting. """ - Run the unit tests for all the test labels in the provided list. - Labels must be of the form: - - app.TestClass.test_method - Run a single specific test method - - app.TestClass - Run all the test methods in a given class - - app - Search for doctests and unittests in the named application. - When looking for tests, the test runner will look in the models and - tests modules for the application. + def __init__(self): + """ + Placeholder constructor. Want to make it obvious that it can + be overridden. + """ + self.isloaded = True - A list of 'extra' tests may also be provided; these tests - will be added to the test suite. - Returns the number of tests that failed. - """ - setup_test_environment() + def run_tests(self, test_labels, verbosity=1, interactive=True, extra_tests=[]): + """ + Run the unit tests for all the test labels in the provided list. + Labels must be of the form: + - app.TestClass.test_method + Run a single specific test method + - app.TestClass + Run all the test methods in a given class + - app + Search for doctests and unittests in the named application. - settings.DEBUG = False - suite = unittest.TestSuite() + When looking for tests, the test runner will look in the models and + tests modules for the application. - if test_labels: - for label in test_labels: - if '.' in label: - suite.addTest(build_test(label)) - else: - app = get_app(label) + A list of 'extra' tests may also be provided; these tests + will be added to the test suite. + + Returns the number of tests that failed. + """ + setup_test_environment() + + settings.DEBUG = False + suite = unittest.TestSuite() + + if test_labels: + for label in test_labels: + if '.' in label: + suite.addTest(build_test(label)) + else: + app = get_app(label) + suite.addTest(build_suite(app)) + else: + for app in get_apps(): suite.addTest(build_suite(app)) - else: - for app in get_apps(): - suite.addTest(build_suite(app)) - for test in extra_tests: - suite.addTest(test) + for test in extra_tests: + suite.addTest(test) + + suite = reorder_suite(suite, (TestCase,)) + + old_name = settings.DATABASE_NAME + from django.db import connection + connection.creation.create_test_db(verbosity, autoclobber=not interactive) + result = SkipTestRunner(verbosity=verbosity).run(suite) + connection.creation.destroy_test_db(old_name, verbosity) + + teardown_test_environment() + + return len(result.failures) + len(result.errors) - suite = reorder_suite(suite, (TestCase,)) - old_name = settings.DATABASE_NAME - from django.db import connection - connection.creation.create_test_db(verbosity, autoclobber=not interactive) - result = unittest.TextTestRunner(verbosity=verbosity).run(suite) - connection.creation.destroy_test_db(old_name, verbosity) +class SkipTestRunner: + """ + A test runner class that adds a Skipped category in the output layer. + + Modeled after unittest.TextTestRunner. + + Similarly to unittest.TextTestRunner, prints summary of the results at the end. + (Including a count of skipped tests.) + """ + + def __init__(self, stream=sys.stderr, descriptions=1, verbosity=1): + self.stream = unittest._WritelnDecorator(stream) + self.descriptions = descriptions + self.verbosity = verbosity + self.result = _SkipTestResult(self.stream, descriptions, verbosity) + + def run(self, test): + "Run the given test case or test suite." + startTime = time.time() + test.run(self.result) + stopTime = time.time() + timeTaken = stopTime - startTime + + self.result.printErrors() + self.stream.writeln(self.result.separator2) + run = self.result.testsRun + self.stream.writeln('Ran %d test%s in %.3fs' % + (run, run != 1 and 's' or '', timeTaken)) + self.stream.writeln() + if not self.result.wasSuccessful(): + self.stream.write('FAILED (') + failed, errored, skipped = map(len, (self.result.failures, self.result.errors, self.result.skipped)) + if failed: + self.stream.write('failures=%d' % failed) + if errored: + if failed: self.stream.write(', ') + self.stream.write('errors=%d' % errored) + if skipped: + if errored or failed: self.stream.write(', ') + self.stream.write('skipped=%d' % skipped) + self.stream.writeln(')') + else: + self.stream.writeln('OK') + return self.result + +class _SkipTestResult(unittest._TextTestResult): + """ + A test result class that adds a Skipped category in the output layer. + + Modeled after unittest._TextTestResult. + + Similarly to unittest._TextTestResult, prints out the names of tests as they are + run and errors as they occur. + """ + + def __init__(self, stream, descriptions, verbosity): + unittest._TextTestResult.__init__(self, stream, descriptions, verbosity) + self.skipped = [] + + def addError(self, test, err): + # Determine if this is a skipped test + tracebacks = traceback.extract_tb(err[2]) + if tracebacks[-1][-1].startswith('raise SkippedTest'): + self.skipped.append((test, self._exc_info_to_string(err, test))) + if self.showAll: + self.stream.writeln('SKIPPED') + elif self.dots: + self.stream.write('S') + self.stream.flush() + else: + unittest.TestResult.addError(self, test, err) + if self.showAll: + self.stream.writeln('ERROR') + elif self.dots: + self.stream.write('E') + self.stream.flush() - teardown_test_environment() + def printErrors(self): + if self.dots or self.showAll: + self.stream.writeln() + self.printErrorList('SKIPPED', self.skipped) + self.printErrorList('ERROR', self.errors) + self.printErrorList('FAIL', self.failures) - return len(result.failures) + len(result.errors) diff --git a/django/test/test_coverage.py b/django/test/test_coverage.py new file mode 100644 index 0000000000..7c22edacb1 --- /dev/null +++ b/django/test/test_coverage.py @@ -0,0 +1,129 @@ +import coverage, time + +import os, sys + +from django.conf import settings +from django.db.models.loading import get_app, get_apps + +from django.test.simple import DefaultTestRunner as base_run_tests + +from django.utils.module_tools import get_all_modules +from django.utils.translation import ugettext as _ + +def _get_app_package(app_model_module): + """ + Returns the app module name from the app model module. + """ + return '.'.join(app_model_module.__name__.split('.')[:-1]) + + +class BaseCoverageRunner(object): + """ + Placeholder class for coverage runners. Intended to be easily extended. + """ + + def __init__(self): + """Placeholder (since it is overrideable)""" + 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, + extra_tests=[]): + """ + Runs the specified tests while generating code coverage statistics. Upon + the tests' completion, the results are printed to stdout. + """ + #self.cov.erase() + #Allow an on-disk cache of coverage stats. + #self.cov.use_cache(0) + #for e in getattr(settings, 'COVERAGE_CODE_EXCLUDES', []): + # self.cov.exclude(e) + + + self.cov.start() + 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: + label = label.split('.')[0] + app = get_app(label) + coverage_modules.append(_get_app_package(app)) + else: + for app in get_apps(): + coverage_modules.append(_get_app_package(app)) + + coverage_modules.extend(getattr(settings, 'COVERAGE_ADDITIONAL_MODULES', [])) + + packages, self.modules, self.excludes, self.errors = get_all_modules( + coverage_modules, getattr(settings, 'COVERAGE_MODULE_EXCLUDES', []), + getattr(settings, 'COVERAGE_PATH_EXCLUDES', [])) + + + + return results + +class ConsoleReportCoverageRunner(BaseCoverageRunner): + + def run_tests(self, *args, **kwargs): + """docstring for run_tests""" + res = super(ConsoleReportCoverageRunner, self).run_tests( *args, **kwargs) + self.cov.report(self.modules.values(), show_missing=1) + + if self.excludes: + print >> sys.stdout + print >> sys.stdout, _("The following packages or modules were excluded:"), + for e in self.excludes: + print >> sys.stdout, e, + print >>sys.stdout + if self.errors: + print >> sys.stdout + print >> sys.stderr, _("There were problems with the following packages or modules:"), + for e in self.errors: + print >> sys.stderr, e, + print >> sys.stdout + return res + +class ReportingCoverageRunner(BaseCoverageRunner): + """Runs coverage.py analysis, as well as generating detailed HTML reports.""" + + def __init__(self, outdir = None): + """ + Constructor, overrides BaseCoverageRunner. Sets output directory + for reports. Parameter or setting. + """ + super(ReportingCoverageRunner, self).__init__() + if(outdir): + self.outdir = outdir + else: + # Realistically, we aren't going to ship the entire reporting framework.. + # but for the time being I have left it in. + self.outdir = getattr(settings, 'COVERAGE_REPORT_HTML_OUTPUT_DIR', 'test_html') + self.outdir = os.path.abspath(self.outdir) + # Create directory + if( not os.path.exists(self.outdir)): + os.mkdir(self.outdir) + + + def run_tests(self, *args, **kwargs): + """ + Overrides BaseCoverageRunner.run_tests, and adds html report generation + with the results + """ + res = super(ReportingCoverageRunner, self).run_tests( *args, **kwargs) + self.cov.html_report(self.modules.values(), + directory=self.outdir, + ignore_errors=True, + omit_prefixes='modeltests') + print >>sys.stdout + print >>sys.stdout, _("HTML reports were output to '%s'") %self.outdir + + return res + diff --git a/django/test/testcases.py b/django/test/testcases.py index 8c73c63796..010827fe67 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -212,8 +212,11 @@ class TransactionTestCase(unittest.TestCase): named fixtures. * If the Test Case class has a 'urls' member, replace the ROOT_URLCONF with it. + * If the Test Case class has a 'test_models' member, load the relivent + named models. * Clearing the mail test outbox. """ + self._test_model_setup() self._fixture_setup() self._urlconf_setup() mail.outbox = [] @@ -225,6 +228,37 @@ class TransactionTestCase(unittest.TestCase): # that we're using *args and **kwargs together. call_command('loaddata', *self.fixtures, **{'verbosity': 0}) + def _test_model_setup(self): + if hasattr(self, 'test_models'): + #print self.test_models + if self.__module__.endswith('tests'): + app_label = self.__module__.split('.')[:-1][-1] + app_path = '.'.join(self.__module__.split('.')[:-1]) + else: + app_label = self.__module__.split('.')[:-2][-1] + app_path = '.'.join(self.__module__.split('.')[:-2]) + from django.db.models.loading import cache + from django.utils import importlib + from django.db import models + #importlib.import_module() + cache.write_lock.acquire() + try: + app_mods = cache.app_models[app_label] + for tm in self.test_models: + #print "importing %s " % tm + im = importlib.import_module(app_path + '.' + tm) + #cache.app_store[im] = len(cache.app_store) + #print "finding model classes" + mod_classes = [f for f in im.__dict__.values() if hasattr(f,'__bases__') and issubclass(f,models.Model)] + #print "Found models %s " % mod_classes + for mc in mod_classes: + print "Adding %s to AppCache" % mc + app_mods[mc.__name__.lower()] = mc + finally: + cache.write_lock.release() + #call_command('syncdb', **{'verbosity': 0}) + + def _urlconf_setup(self): if hasattr(self, 'urls'): self._old_root_urlconf = settings.ROOT_URLCONF @@ -261,12 +295,55 @@ class TransactionTestCase(unittest.TestCase): * Putting back the original ROOT_URLCONF if it was changed. """ + self._test_model_teardown() self._fixture_teardown() self._urlconf_teardown() def _fixture_teardown(self): pass + def _test_model_teardown(self): + if hasattr(self, 'test_models'): + #print self.test_models + if self.__module__.endswith('tests'): + app_label = self.__module__.split('.')[:-1][-1] + app_path = '.'.join(self.__module__.split('.')[:-1]) + else: + app_label = self.__module__.split('.')[:-2][-1] + app_path = '.'.join(self.__module__.split('.')[:-2]) + from django.db.models.loading import cache + from django.utils import importlib + from django.db import models + #importlib.import_module() + # cc = cache.get_app(app_label) + # del cache.app_store[cc] + # #del cache.app_models[app_label] + # cache.loaded = False + # print cache.handled + # print '.'.join(cc.__name__.split('.')[:-1]) + # print cc.__package__ + # del cache.handled[cc.__package__] + # cache._populate() + # print cache.get_app(app_label) + #cc = cache.get_app(app_label) + + #reload(cache.get_app(app_label)) + cache.write_lock.acquire() + try: + app_mods = cache.app_models[app_label] + #print app_mods + for tm in self.test_models: + #print "importing %s " % tm + im = importlib.import_module(app_path + '.' + tm) + #cache.app_store[im] = len(cache.app_store) + #print "finding model classes" + mod_classes = [f for f in im.__dict__.values() if hasattr(f,'__bases__') and issubclass(f,models.Model)] + #print "Found models %s " % mod_classes + for mc in mod_classes: + print "Deleting %s from AppCache" % mc + del app_mods[mc.__name__.lower()] + finally: + cache.write_lock.release() def _urlconf_teardown(self): if hasattr(self, '_old_root_urlconf'): settings.ROOT_URLCONF = self._old_root_urlconf 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/django/test/utils.py b/django/test/utils.py index d34dd33d15..8430ef8f10 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -5,6 +5,7 @@ from django.core import mail from django.test import signals from django.template import Template from django.utils.translation import deactivate +import inspect class ContextList(list): """A wrapper that provides direct key access to context items contained @@ -80,8 +81,18 @@ def teardown_test_environment(): del mail.outbox -def get_runner(settings): - test_path = settings.TEST_RUNNER.split('.') +def get_runner(settings, coverage = False, reports = False): + """ + Based on the settings and parameters, returns the appropriate test + runner class. + """ + if(coverage): + if(reports): + test_path = 'django.test.test_coverage.ReportingCoverageRunner'.split('.') + else: + test_path = settings.COVERAGE_TEST_RUNNER.split('.') + else: + test_path = settings.TEST_RUNNER.split('.') # Allow for Python 2.5 relative paths if len(test_path) > 1: test_module_name = '.'.join(test_path[:-1]) @@ -90,3 +101,9 @@ def get_runner(settings): test_module = __import__(test_module_name, {}, {}, test_path[-1]) test_runner = getattr(test_module, test_path[-1]) return test_runner + +def calling_func_name(): + """ + Inspect's on the stack to determine the calling functions name. + """ + return inspect.stack()[1][3]
\ No newline at end of file diff --git a/django/test/windmill_tests.py b/django/test/windmill_tests.py new file mode 100644 index 0000000000..3212e9c1e8 --- /dev/null +++ b/django/test/windmill_tests.py @@ -0,0 +1,137 @@ + +# Code from django_live_server_r8458.diff @ http://code.djangoproject.com/ticket/2879#comment:41 +# Editing to monkey patch django rather than be in trunk + +import socket +import threading +from django.core.handlers.wsgi import WSGIHandler +from django.core.servers import basehttp +from django.test.testcases import call_command + +#from django.core.management import call_command + +# support both django 1.0 and 1.1 +try: + from django.test.testcases import TransactionTestCase as TestCase +except ImportError: + from django.test.testcases import TestCase + +try: + from windmill.authoring import unit +except Exception, e: + print "You don't appear to have windmill installed, please install before trying to run windmill tests again." + unit = None + +class StoppableWSGIServer(basehttp.WSGIServer): + """WSGIServer with short timeout, so that server thread can stop this server.""" + + def server_bind(self): + """Sets timeout to 1 second.""" + basehttp.WSGIServer.server_bind(self) + self.socket.settimeout(1) + + def get_request(self): + """Checks for timeout when getting request.""" + try: + sock, address = self.socket.accept() + sock.settimeout(None) + return (sock, address) + except socket.timeout: + raise + +class TestServerThread(threading.Thread): + """Thread for running a http server while tests are running.""" + + def __init__(self, address, port): + self.address = address + self.port = port + self._stopevent = threading.Event() + self.started = threading.Event() + self.error = None + super(TestServerThread, self).__init__() + + def run(self): + """Sets up test server and database and loops over handling http requests.""" + + # Must do database stuff in this new thread if database in memory. + from django.conf import settings + #if settings.DATABASE_ENGINE == 'sqlite3' \ + # and (not settings.TEST_DATABASE_NAME or settings.TEST_DATABASE_NAME == ':memory:'): + from django.db import connection + print 'Creating test DB' + db_name = connection.creation.create_test_db(0,autoclobber=True) + #call_command('syncdb', 0, 0) + # Import the fixture data into the test database. + if hasattr(self, 'fixtures'): + print 'Loading fixtures.' + # We have to use this slightly awkward syntax due to the fact + # that we're using *args and **kwargs together. + call_command('loaddata', *self.fixtures, **{'verbosity': 0}) + + try: + print "running thread" + handler = basehttp.AdminMediaHandler(WSGIHandler()) + httpd = None + while httpd is None: + try: + server_address = (self.address, self.port) + httpd = StoppableWSGIServer(server_address, basehttp.WSGIRequestHandler) + except basehttp.WSGIServerException, e: + if "Address already in use" in str(e): + print "Address already in use" + self.port +=1 + else: + raise e + httpd.set_app(handler) + self.started.set() + except basehttp.WSGIServerException, e: + self.error = e + self.started.set() + return + + + # Loop until we get a stop event. + while not self._stopevent.isSet(): + httpd.handle_request() + httpd.server_close() + + def join(self, timeout=None): + """Stop the thread and wait for it to finish.""" + self._stopevent.set() + threading.Thread.join(self, timeout) + + +def start_test_server(self, address='localhost', port=8000): + """Creates a live test server object (instance of WSGIServer).""" + self.server_thread = TestServerThread(address, port) + if hasattr(self, 'fixtures'): + print 'loading fixtures %s' % self.fixtures + self.server_thread.__setattr__('fixtures', self.fixtures) + self.server_thread.start() + self.server_thread.started.wait() + if self.server_thread.error: + raise self.server_thread.error + return self.server_thread.started + +def stop_test_server(self): + if self.server_thread: + self.server_thread.join() + +## New Code + +TestCase.start_test_server = classmethod(start_test_server) +TestCase.stop_test_server = classmethod(stop_test_server) + + +class WindmillDjangoUnitTest(TestCase, unit.WindmillUnitTestCase): + test_port = 8000 + def setUp(self): + self.start_test_server('localhost', self.test_port) + self.test_url = 'http://localhost:%d' % self.server_thread.port + unit.WindmillUnitTestCase.setUp(self) + + def tearDown(self): + unit.WindmillUnitTestCase.tearDown(self) + self.stop_test_server() + +WindmillDjangoTransactionUnitTest = WindmillDjangoUnitTest diff --git a/django/utils/module_tools/__init__.py b/django/utils/module_tools/__init__.py new file mode 100644 index 0000000000..976d4b5e4f --- /dev/null +++ b/django/utils/module_tools/__init__.py @@ -0,0 +1,3 @@ +from module_loader import * +from module_walker import * + diff --git a/django/utils/module_tools/data_storage.py b/django/utils/module_tools/data_storage.py new file mode 100644 index 0000000000..aed5980e6b --- /dev/null +++ b/django/utils/module_tools/data_storage.py @@ -0,0 +1,42 @@ +""" +Copyright 2009 55 Minutes (http://www.55minutes.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +__all__ = ('Packages', 'Modules', 'Excluded', 'Errors') + +class SingletonType(type): + def __call__(cls, *args, **kwargs): + if getattr(cls, '__instance__', None) is None: + instance = cls.__new__(cls) + instance.__init__(*args, **kwargs) + cls.__instance__ = instance + return cls.__instance__ + +class Packages(object): + __metaclass__ = SingletonType + packages = {} + +class Modules(object): + __metaclass__ = SingletonType + modules = {} + +class Excluded(object): + __metaclass__ = SingletonType + excluded = [] + +class Errors(object): + __metaclass__ = SingletonType + errors = [] + diff --git a/django/utils/module_tools/module_loader.py b/django/utils/module_tools/module_loader.py new file mode 100644 index 0000000000..e6dd6ce4b8 --- /dev/null +++ b/django/utils/module_tools/module_loader.py @@ -0,0 +1,79 @@ +""" +Copyright 2009 55 Minutes (http://www.55minutes.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import imp, sys, types + +__all__ = ('find_or_load_module',) + +def _brute_force_find_module(module_name, module_path, module_type): + for m in [m for n, m in sys.modules.iteritems() if type(m) == types.ModuleType]: + m_path = [] + try: + if module_type in (imp.PY_COMPILED, imp.PY_SOURCE): + m_path = [m.__file__] + elif module_type==imp.PKG_DIRECTORY: + m_path = m.__path__ + except AttributeError: + pass + for p in m_path: + if p.startswith(module_path): + return m + return None + +def _load_module(module_name, fo, fp, desc): + suffix, mode, mtype = desc + if module_name in sys.modules and \ + sys.modules[module_name].__file__.startswith(fp): + module = sys.modules[module_name] + else: + module = _brute_force_find_module(module_name, fp, mtype) + if not module: + try: + module = imp.load_module(module_name, fo, fp, desc) + except: + raise ImportError + return module + +def _load_package(pkg_name, fp, desc): + suffix, mode, mtype = desc + if pkg_name in sys.modules: + if fp in sys.modules[pkg_name].__path__: + pkg = sys.modules[pkg_name] + else: + pkg = _brute_force_find_module(pkg_name, fp, mtype) + if not pkg: + pkg = imp.load_module(pkg_name, None, fp, desc) + return pkg + +def find_or_load_module(module_name, path=None): + """ + Attempts to lookup ``module_name`` in ``sys.modules``, else uses the + facilities in the ``imp`` module to load the module. + + If module_name specified is not of type ``imp.PY_SOURCE`` or + ``imp.PKG_DIRECTORY``, raise ``ImportError`` since we don't know + what to do with those. + """ + fo, fp, desc = imp.find_module(module_name.split('.')[-1], path) + suffix, mode, mtype = desc + if mtype in (imp.PY_SOURCE, imp.PY_COMPILED): + module = _load_module(module_name, fo, fp, desc) + elif mtype==imp.PKG_DIRECTORY: + module = _load_package(module_name, fp, desc) + else: + raise ImportError("Don't know how to handle this module type.") + return module + diff --git a/django/utils/module_tools/module_walker.py b/django/utils/module_tools/module_walker.py new file mode 100644 index 0000000000..442150e689 --- /dev/null +++ b/django/utils/module_tools/module_walker.py @@ -0,0 +1,135 @@ +""" +Copyright 2009 55 Minutes (http://www.55minutes.com) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +""" + +import os, re, sys +from glob import glob + +from data_storage import * +from module_loader import find_or_load_module + +try: + set +except: + from sets import Set as set + +__all__ = ('get_all_modules',) + +def _build_pkg_path(pkg_name, pkg, path): + for rp in [x for x in pkg.__path__ if path.startswith(x)]: + p = path.replace(rp, '').replace(os.path.sep, '.') + return pkg_name + p + +def _build_module_path(pkg_name, pkg, path): + return _build_pkg_path(pkg_name, pkg, os.path.splitext(path)[0]) + +def _prune_whitelist(whitelist, blacklist): + excluded = Excluded().excluded + + for wp in whitelist[:]: + for bp in blacklist: + if re.search(bp, wp): + whitelist.remove(wp) + excluded.append(wp) + break + return whitelist + +def _parse_module_list(m_list): + packages = Packages().packages + modules = Modules().modules + excluded = Excluded().excluded + errors = Errors().errors + + for m in m_list: + components = m.split('.') + m_name = '' + search_path = [] + processed=False + for i, c in enumerate(components): + m_name = '.'.join([x for x in m_name.split('.') if x] + [c]) + try: + module = find_or_load_module(m_name, search_path or None) + except ImportError: + processed=True + errors.append(m) + break + try: + search_path.extend(module.__path__) + except AttributeError: + processed = True + if i+1==len(components): + modules[m_name] = module + else: + errors.append(m) + break + if not processed: + packages[m_name] = module + +def prune_dirs(root, dirs, exclude_dirs): + _dirs = [os.path.join(root, d) for d in dirs] + for i, p in enumerate(_dirs): + for e in exclude_dirs: + if re.search(e, p): + del dirs[i] + break + +def _get_all_packages(pkg_name, pkg, blacklist, exclude_dirs): + packages = Packages().packages + errors = Errors().errors + + for path in pkg.__path__: + for root, dirs, files in os.walk(path): + prune_dirs(root, dirs, exclude_dirs or []) + m_name = _build_pkg_path(pkg_name, pkg, root) + try: + if _prune_whitelist([m_name], blacklist): + m = find_or_load_module(m_name, [os.path.split(root)[0]]) + packages[m_name] = m + else: + for d in dirs[:]: + dirs.remove(d) + except ImportError: + errors.append(m_name) + for d in dirs[:]: + dirs.remove(d) + +def _get_all_modules(pkg_name, pkg, blacklist): + modules = Modules().modules + errors = Errors().errors + + for p in pkg.__path__: + for f in glob('%s/*.py' %p): + m_name = _build_module_path(pkg_name, pkg, f) + try: + if _prune_whitelist([m_name], blacklist): + m = find_or_load_module(m_name, [p]) + modules[m_name] = m + except ImportError: + errors.append(m_name) + +def get_all_modules(whitelist, blacklist=None, exclude_dirs=None): + packages = Packages().packages + modules = Modules().modules + excluded = Excluded().excluded + errors = Errors().errors + + whitelist = _prune_whitelist(whitelist, blacklist or []) + _parse_module_list(whitelist) + for pkg_name, pkg in packages.copy().iteritems(): + _get_all_packages(pkg_name, pkg, blacklist, exclude_dirs) + for pkg_name, pkg in packages.copy().iteritems(): + _get_all_modules(pkg_name, pkg, blacklist) + return packages, modules, list(set(excluded)), list(set(errors)) + diff --git a/docs/howto/index.txt b/docs/howto/index.txt index 1a27a2ebac..214f36e47f 100644 --- a/docs/howto/index.txt +++ b/docs/howto/index.txt @@ -25,6 +25,7 @@ you quickly accomplish common tasks. outputting-csv outputting-pdf static-files + windmill-tests .. seealso:: diff --git a/docs/howto/windmill-tests.txt b/docs/howto/windmill-tests.txt new file mode 100644 index 0000000000..3321bb1869 --- /dev/null +++ b/docs/howto/windmill-tests.txt @@ -0,0 +1,58 @@ +.. _howto-windmill-tests: + +Writing a Functional Tests with Windmill +======================================== + +.. currentmodule:: django.test + +If you need to test overall behaviors of your site, ajax widgets or rendered +html, then functional tests are the solution. Django includes support for the +popular `Windmill`_ framework. Writing a windmill test +is simple, following these steps: + +.. _Windmill: http://getwindmill.com + +#. Your windmill tests must be their own module, named ``wmtests`` or ``windmilltests``. + +#. Django must be able to run any function in the module without arguments.:: + + from windmill.conf import global_settings + ADMIN_URL = "%s/test_admin/admin" % global_settings.TEST_URL + from windmill.authoring import WindmillTestClient + from django.test.utils import calling_func_name + + def test_loginAndSetup(): + '''Mostly just a proof of concept to test working order of tests.''' + client = WindmillTestClient(calling_func_name()) + + client.open(url='http://localhost:8000/admin') + client.waits.forPageLoad(timeout=u'20000') + ... + +#. Your windmill testing module must load any files other than the module loader in ``__init__.py``.:: + + from primary import * + ... + +Setup and Teardown are done differently for windmill tests, and consist of the following +functions. : + + * :meth:`setup_module` + * :meth:`teardown_module` + +A sample implementation, in ``__init__.py``.:: + + from primary import * + + def setup_module(module): + module.property = fetch_property() + + def teardown_module(module): + module.property = None + + + +These methods can be included in any file with windmill tests. See `functest`_ +for more information. + +.. _functest : http://functest.pythonesque.org/
\ No newline at end of file diff --git a/docs/index.txt b/docs/index.txt index d03f90c117..03478f06a2 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -142,7 +142,9 @@ The development process :ref:`Overview <ref-django-admin>` | :ref:`Adding custom commands <howto-custom-management-commands>` - * **Testing:** :ref:`Overview <topics-testing>` + * **Testing:** + :ref:`Overview <topics-testing>` | + :ref:`Windmill <howto-windmill-tests>` * **Deployment:** :ref:`Overview <howto-deployment-index>` | diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt index cec6002b7b..d066ca086a 100644 --- a/docs/topics/testing.txt +++ b/docs/topics/testing.txt @@ -400,7 +400,7 @@ Some of the things you can do with the test client are: a template context that contains certain values. Note that the test client is not intended to be a replacement for Twill_, -Selenium_, or other "in-browser" frameworks. Django's test client has +Windmill_, or other "in-browser" frameworks. Django's test client has a different focus. In short: * Use Django's test client to establish that the correct view is being @@ -409,10 +409,15 @@ a different focus. In short: * Use in-browser frameworks such as Twill and Selenium to test *rendered* HTML and the *behavior* of Web pages, namely JavaScript functionality. -A comprehensive test suite should use a combination of both test types. +A comprehensive test suite should use a combination of both test types. Which +is why Django makes it easy to integrate with 3rd party test runners via the +:setting:`TEST_RUNNER` setting. For convenience, Django ships a runner for +the framework used in testing the :ref:`admin interface, <ref-contrib-admin>` +Windmill_. Details on integrating Windmill tests with Django are available +:ref:`here. <howto-windmill-tests>` .. _Twill: http://twill.idyll.org/ -.. _Selenium: http://www.openqa.org/selenium/ +.. _Windmill: http://www.getwindmill.com/ Overview and a quick example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -706,6 +711,28 @@ arguments at time of construction: and session data cleared to defaults. Subsequent requests will appear to come from an AnonymousUser. + +Making mock requests +~~~~~~~~~~~~~~~~~~~~ +.. versionadded:: 1.1 + +Use the ``django.test.mocks.RequestFactory`` class to create mock requests. Usage is as follows. +:: + rf = RequestFactory() + get_request = rf.get('/hello/') + post_request = rf.post('/submit/', {'foo': 'bar'}) + +Once you have a request object you can pass it to any view function, just as if + that view had been hooked up using a URLconf. + + +.. class:: RequestFactory() + There is only one method on a ``RequestFactory``. + + .. method:: RequestFactory.request(path, **request) + This will return a request object with the proper environment. + + Testing responses ~~~~~~~~~~~~~~~~~ @@ -938,6 +965,8 @@ This means, instead of instantiating a ``Client`` in each test:: response = self.client.get('/customer/index/') self.failUnlessEqual(response.status_code, 200) + + .. _topics-testing-fixtures: Fixture loading @@ -999,6 +1028,8 @@ This flush/load procedure is repeated for each test in the test case, so you can be certain that the outcome of a test will not be affected by another test, or by the order of test execution. +.. _topics-testing-urlconf: + URLconf configuration ~~~~~~~~~~~~~~~~~~~~~ @@ -1032,6 +1063,87 @@ For example:: This test case will use the contents of ``myapp.test_urls`` as the URLconf for the duration of the test case. +.. _topics-testing-testmodels: + +Test-Only Models configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.1 + +.. attribute:: TransactionTestCase.test_models + +If you want to test your application with models that are only available during specific +test cases, ``django.test.TransactionTestCase`` provides the ability to customize the models +configuration for the duration of the execution of a test suite. If your +``TransactionTestCase`` instance defines an ``test_models`` attribute, the ``TransactionTestCase`` will load +the value of that attribute as a module, and load the models contained within for the +duration of that test. + +For example:: + + from django.test import TransactionTestCase + + class TestMyViews(TransactionTestCase): + test_models = ['test_models'] + + def testIndexPageView(self): + # Here you'd test your view using ``Client``. + +This test case will load the contents of ``myapp.test_models`` and add +any subclass of ``django.db.models.Model`` to ``myapp.models``. + +Skipping tests bound to fail +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded: 1.1 + +Occasionally it's helpful to specify tests that are skipped under certain +circumstances. To accomplish this, the Django test framework offers decorators +that you can apply to your test methods for them to be conditionally skipped. + +You can supply your own condition function as follows:: + + from django.tests.decorators import * + + class TestUnderCondition(TestCase): + + def _my_condition(): + # Condition returning True if test should be run and False if it + # should be skipped. + + @conditional_skip(_my_condition, reason='This test should be skipped sometimes') + def testOnlyOnTuesday(self): + # Test to run if _my_condition evaluates to True + +In addition, the Django test framework supplies a handful of skip conditions that +handle commonly used conditions for skipping tests. + +``views_required(required_views=[])`` + Does a ``urlresolver.Reverse`` on the required views supplied. Runs test only if + all views in ``required_views`` are in use. + +``modules_required(required_modules=[])`` + Runs tests only if all modules in ``required_modules`` can be imported. + +``skip_specific_database(database_engine)`` + Skips test if ``settings.DATABASE_ENGINE`` is equal to database_engine. + +If a test is skipped, it is added to a skipped category in the test runner and +the test results are reported as such:: + + ====================================================================== + SKIPPED: test_email_found (django.contrib.auth.tests.basic.PasswordResetTest) + ---------------------------------------------------------------------- + Traceback (most recent call last): + File "/Users/dnaquin/Dropbox/Sandbox/django/django/test/decorators.py", line 43, in _skip + raise SkippedTest(reason=reason) + SkippedTest: Required view for this test not found: django.contrib.auth.views.password_reset + + ---------------------------------------------------------------------- + Ran 408 tests in 339.663s + + FAILED (failures=1, skipped=2) + .. _emptying-test-outbox: Emptying the test outbox diff --git a/tests/regressiontests/admin_views/test_models.py b/tests/regressiontests/admin_views/test_models.py new file mode 100644 index 0000000000..5ef173d716 --- /dev/null +++ b/tests/regressiontests/admin_views/test_models.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +import tempfile +import os +# from django.core.files.storage import FileSystemStorage +from django.db import models +# from django.contrib import admin +# from django.core.mail import EmailMessage + +class SectionTest(models.Model): + """ + A simple section that links to articles, to test linking to related items + in admin views. + """ + name = models.CharField(max_length=100) + +#admin.site.register(SectionTest, save_as=True)
\ No newline at end of file diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py index aafa303cec..9d24c017b6 100644 --- a/tests/regressiontests/admin_views/tests.py +++ b/tests/regressiontests/admin_views/tests.py @@ -20,6 +20,8 @@ from models import Article, BarAccount, CustomArticle, EmptyModel, \ Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit, \ Category + + try: set except NameError: @@ -27,16 +29,30 @@ except NameError: class AdminViewBasicTest(TestCase): fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml'] - + test_models = ['test_models'] # Store the bit of the URL where the admin is registered as a class # variable. That way we can test a second AdminSite just by subclassing # this test case and changing urlbit. urlbit = 'admin' def setUp(self): + # from django.contrib import admin + # admin.register(TestSection) + from test_models import SectionTest + from django.contrib import admin + admin.site.register(SectionTest) + import regressiontests.admin_views.urls + reload(regressiontests.admin_views.urls) self.client.login(username='super', password='secret') def tearDown(self): + # from django.contrib import admin + # admin.unregister(TestSection) + from test_models import SectionTest + from django.contrib import admin + admin.site.unregister(SectionTest) + import regressiontests.admin_views.urls + reload(regressiontests.admin_views.urls) self.client.logout() def testTrailingSlashRequired(self): @@ -55,6 +71,13 @@ class AdminViewBasicTest(TestCase): response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit) self.failUnlessEqual(response.status_code, 200) + def testBasicAddGetTest(self): + """ + A smoke test to ensure GET on the add_view works. + """ + response = self.client.get('/test_admin/%s/admin_views/sectiontest/add/' % self.urlbit) + self.failUnlessEqual(response.status_code, 200) + def testAddWithGETArgs(self): response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit, {'name': 'My Section'}) self.failUnlessEqual(response.status_code, 200) @@ -63,6 +86,14 @@ class AdminViewBasicTest(TestCase): "Couldn't find an input with the right value in the response." ) + def testAddWithGETArgsTestModel(self): + response = self.client.get('/test_admin/%s/admin_views/sectiontest/add/' % self.urlbit, {'name': 'My TestSection'}) + self.failUnlessEqual(response.status_code, 200) + self.failUnless( + 'value="My TestSection"' in response.content, + "Couldn't find an input with the right value in the response." + ) + def testBasicEditGet(self): """ A smoke test to ensureGET on the change_view works. @@ -287,6 +318,18 @@ class CustomModelAdminTest(AdminViewBasicTest): self.client.login(username='super', password='secret') response = self.client.get('/test_admin/%s/my_view/' % self.urlbit) self.assert_(response.content == "Django is a magical pony!", response.content) + + def testBasicAddGetTest(self): + """ + A smoke test to ensure GET on the add_view works. + """ + response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit) + self.failUnlessEqual(response.status_code, 200) + + def testAddWithGETArgsTestModel(self): + response = self.client.get('/test_admin/%s/admin_views/sectiontest/add/' % self.urlbit, {'name': 'My TestSection'}) + self.failUnlessEqual(response.status_code, 404) + def get_perm(Model, perm): """Return the permission object, for the Model""" @@ -598,7 +641,7 @@ class AdminViewStringPrimaryKeyTest(TestCase): def tearDown(self): self.client.logout() - + def test_get_change_view(self): "Retrieving the object using urlencoded form of primary key should work" response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(self.pk)) @@ -1479,6 +1522,26 @@ class AdminInlineTests(TestCase): self.failUnlessEqual(FancyDoodad.objects.count(), 1) self.failUnlessEqual(FancyDoodad.objects.all()[0].name, "Fancy Doodad 1 Updated") +# import os +# from django.test import windmill_tests as djangotest +# #from windmill.authoring import djangotest +# #from windmill.conf import global_settings +# +# class TestProjectWindmillTest(djangotest.WindmillDjangoUnitTest): +# fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml', 'admin-views-unicode.xml', +# 'multiple-child-classes', 'admin-views-actions.xml', 'string-primary-key.xml', 'admin-views-person.xml'] +# test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'windmilltests') +# #test_dir = os.path.dirname(os.path.abspath(__file__)) +# #test_dir = os.path.dirname(os.path.abspath(__file__)) +# browser = 'firefox' +# test_url = 'http://localhost:8000/test_admin/admin/' +# #global_settings.TEST_URL = test_url +# +# # def test_tryout(self): +# # pass + + + def test_ordered_inline(self): """Check that an inline with an editable ordering fields is updated correctly. Regression for #10922""" diff --git a/tests/regressiontests/admin_views/urls.py b/tests/regressiontests/admin_views/urls.py index f3f1fbd43a..afce456af7 100644 --- a/tests/regressiontests/admin_views/urls.py +++ b/tests/regressiontests/admin_views/urls.py @@ -2,7 +2,10 @@ from django.conf.urls.defaults import * from django.contrib import admin import views import customadmin - +try: + admin.autodiscover() +except Exception, e: + print 'Autodiscover Error' urlpatterns = patterns('', (r'^admin/doc/', include('django.contrib.admindocs.urls')), (r'^admin/secure-view/$', views.secure_view), diff --git a/tests/regressiontests/admin_views/windmilltests/__init__.py b/tests/regressiontests/admin_views/windmilltests/__init__.py new file mode 100644 index 0000000000..6a0abd005a --- /dev/null +++ b/tests/regressiontests/admin_views/windmilltests/__init__.py @@ -0,0 +1,39 @@ +fixtures = ['admin-views-users.xml', + 'admin-views-colors.xml', + 'admin-views-fabrics.xml', + 'admin-views-unicode.xml', + 'multiple-child-classes', + 'admin-views-actions.xml', + 'string-primary-key.xml', + 'admin-views-person.xml'] + +# import os +# from django.test import windmill_tests as djangotest +# #from windmill.authoring import djangotest + +from windmill.conf import global_settings +ADMIN_URL = "%s/test_admin/admin" % global_settings.TEST_URL +#ADMIN_URL = 'http://localhost:8000/test_admin/admin/' +#['regressiontests/admin_views/fixtures/%s' % fix for fix in ] + +# +# class TestProjectWindmillTest(djangotest.WindmillDjangoUnitTest): +# fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml', 'admin-views-unicode.xml', +# 'multiple-child-classes', 'admin-views-actions.xml', 'string-primary-key.xml', 'admin-views-person.xml'] +# #test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'windmilltests') +# test_dir = os.path.dirname(os.path.abspath(__file__)) +# #test_dir = os.path.dirname(os.path.abspath(__file__)) +# browser = 'firefox' +# test_url = 'http://localhost:8000/test_admin/admin/' +# global_settings.TEST_URL = test_url +# +# # def test_tryout(self): +# # pass +# +from windmill.authoring import WindmillTestClient +from django.test.utils import calling_func_name + +# import functest +# functest.modules_passed = [] +# functest.modules_failed = [] +from primary import * diff --git a/tests/regressiontests/admin_views/windmilltests/primary.py b/tests/regressiontests/admin_views/windmilltests/primary.py new file mode 100644 index 0000000000..3b074239bc --- /dev/null +++ b/tests/regressiontests/admin_views/windmilltests/primary.py @@ -0,0 +1,493 @@ +from windmill.authoring import WindmillTestClient +from django.test.utils import calling_func_name +from windmill.conf import global_settings +ADMIN_URL = "%s/test_admin/admin" % global_settings.TEST_URL + + +def test_loginAndSetup(): + '''Mostly just a proof of concept to test working order of tests.''' + client = WindmillTestClient(calling_func_name()) + + # print dir(client) + # print dir(client.open) + # print dir(client.commands) + # print client.commands() + + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + client.type(text=u'super', id=u'id_username') + client.type(text=u'secret', id=u'id_password') + client.click(value=u'Log in') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertNode(xpath=u"//div[@id='content-main']/div/table/tbody/tr[1]/th") + client.asserts.assertNode(link=u'Articles') + client.asserts.assertNode(link=u'Add') + 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[23]/td/a") + client.waits.forPageLoad(timeout=u'20000') + client.type(text=u'Test Section', id=u'id_name') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertNode(link=u'Section object') + client.click(link=u' Admin_views ') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Add', timeout=u'8000') + client.click(link=u'Add') + client.waits.forPageLoad(timeout=u'20000') + client.type(text=u'Test 1', id=u'id_title') + client.type(text=u'This is test content.', id=u'id_content') + client.click(link=u'Today') + client.click(link=u'Now') + client.click(id=u'id_section') + client.select(option=u'Section object', id=u'id_section') + client.click(value=u'1') + #client.asserts.assertValue(validator=u'2009-06-16', id=u'id_date_0') + #client.asserts.assertValue(validator=u'13:31:21', id=u'id_date_1') + client.asserts.assertNode(id=u'id_section') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertNode(link=u'This is test content.') + client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/td[2]") + client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/td[3]") + client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/td[4]") + client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/td[5]") + client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/th") + client.click(link=u'Today') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/th") + client.click(link=u' Home ') + client.waits.forPageLoad(timeout=u'20000') + +def test_changeListNamingLinkingHistory(): + '''Creating a Model with strings for pk, and checking history.''' + client = WindmillTestClient(calling_func_name()) + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + # client.open(url=ADMIN_URL) + # client.type(text=u'super', id=u'id_username') + # client.type(text=u'secret', id=u'id_password') + # client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Model with string primary keys', timeout=u'8000') + client.click(link=u'Model with string primary keys') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u' Add model with string primary key ') + client.waits.forPageLoad(timeout=u'20000') + client.type(text=u'hello', id=u'id_id') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + #client.asserts.assertNode(link=u'hello') + client.click(link=u'hello') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'History') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertNode(xpath=u"//table[@id='change-history']/tbody/tr/td") + client.click(link=u'hello') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertValue(validator=u'hello', id=u'id_id') + client.click(link=u'Delete') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertNode(xpath=u"//div[@id='content']/ul/li") + client.asserts.assertNode(link=u'hello') + client.click(value=u"Yes, I'm sure") + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertText(xpath=u"//div[@id='changelist']/form/p", validator=u'\n\n1 model with string primary key\n\n\n') + client.click(link=u' Home ') + +def test_filtersSearchOnChangeList(): + '''Testing Updates and Filters/Search on Person Models''' + client = WindmillTestClient(calling_func_name()) + + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'Persons') + client.asserts.assertNode(link=u'John Mauchly') + client.asserts.assertNode(link=u'Grace Hooper') + client.asserts.assertNode(link=u'Guido van Rossum') + client.asserts.assertSelected(validator=u'Male', id=u'id_form-0-gender') + client.asserts.assertValue(validator=u'1', id=u'id_form-1-gender') + client.asserts.assertSelected(validator=u'Male', id=u'id_form-2-gender') + client.asserts.assertValue(validator=u'on', id=u'id_form-0-alive') + client.asserts.assertValue(validator=u'on', id=u'id_form-1-alive') + client.asserts.assertValue(validator=u'on', id=u'id_form-2-alive') + client.click(link=u'John Mauchly') + client.asserts.assertValue(validator=u'John Mauchly', id=u'id_name') + client.asserts.assertSelected(validator=u'Male', id=u'id_gender') + client.asserts.assertValue(validator=u'on', id=u'id_alive') + client.check(id=u'id_alive') + client.click(xpath=u"//form[@id='person_form']/div/fieldset/div[2]") + client.click(id=u'id_gender') + client.select(option=u'Female', id=u'id_gender') + client.click(value=u'2') + client.click(id=u'id_name') + client.type(text=u'John Mauchly Updated', id=u'id_name') + client.click(name=u'_save') + client.asserts.assertSelected(validator=u'Female', id=u'id_form-0-gender') + client.asserts.assertValue(validator=u'on', id=u'id_form-0-alive') + client.asserts.assertNode(link=u'John Mauchly Updated') + client.click(id=u'searchbar') + client.type(text=u'John', id=u'searchbar') + client.click(value=u'Search') + client.asserts.assertNode(link=u'John Mauchly Updated') + client.click(link=u'3 total') + client.type(text=u'Grace', id=u'searchbar') + client.click(value=u'Search') + client.asserts.assertNode(link=u'Grace Hooper') + client.click(link=u'3 total') + client.asserts.assertNode(link=u'Guido van Rossum') + client.click(link=u'Female') + client.asserts.assertNode(link=u'John Mauchly Updated') + client.click(link=u'All') + client.asserts.assertNode(link=u'Guido van Rossum') + client.click(link=u' Home ') + +def test_defaultDeleteAdminAction(): + '''Admin Actions test. Test the default delete action.''' + client = WindmillTestClient(calling_func_name()) + + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'Fabrics') + client.check(name=u'_selected_action') + client.click(name=u'action') + client.select(option=u'Delete selected fabrics', name=u'action') + client.click(value=u'delete_selected') + client.click(name=u'index') + client.click(value=u"Yes, I'm sure") + client.asserts.assertNode(link=u'Vertical') + client.asserts.assertNode(link=u'Horizontal') + +def test_dateTimeModelsandWidgets(): + client = WindmillTestClient(calling_func_name()) + + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'Articles') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Date', timeout=u'8000') + client.click(link=u'Date') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertText(xpath=u"//div[@id='changelist']/form/table/tbody/tr/td[2]", validator=u'March 18, 2000, 11:54 a.m.') + client.asserts.assertText(xpath=u"//div[@id='changelist']/form/table/tbody/tr[2]/td[2]", validator=u'March 18, 2008, 11:54 a.m.') + client.asserts.assertText(xpath=u"//div[@id='changelist']/form/table/tbody/tr/td[4]", validator=u'2000') + client.asserts.assertText(xpath=u"//div[@id='changelist']/form/table/tbody/tr[2]/td[4]", validator=u'2008') + client.asserts.assertText(xpath=u"//div[@id='changelist']/form/table/tbody/tr[3]/td[4]", validator=u'2009') + client.click(link=u'Modeladmin year') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Content', timeout=u'8000') + client.click(link=u'Content') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertText(xpath=u"//div[@id='changelist']/form/table/tbody/tr/td[4]", validator=u'2008') + client.click(xpath=u"//div[@id='changelist']/form/table/tbody/tr/th/a") + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(xpath=u"//a[@id='calendarlink0']/img", timeout=u'8000') + client.click(xpath=u"//a[@id='calendarlink0']/img") + client.click(link=u'Cancel') + client.click(xpath=u"//a[@id='clocklink0']/img") + client.click(link=u'Midnight') + client.click(id=u'id_date_1') + client.asserts.assertValue(validator=u'00:00:00', id=u'id_date_1') + client.click(xpath=u"//a[@id='clocklink0']/img") + client.click(link=u'6 a.m.') + client.asserts.assertValue(validator=u'06:00:00', id=u'id_date_1') + client.click(xpath=u"//a[@id='clocklink0']/img") + client.click(link=u'Noon') + client.click(id=u'id_date_1') + client.asserts.assertValue(validator=u'12:00:00', id=u'id_date_1') + client.click(link=u'Articles') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u' Add article ', timeout=u'8000') + client.click(link=u' Add article ') + client.waits.forPageLoad(timeout=u'20000') + client.type(text=u'Test Art', id=u'id_title') + client.type(text=u'<p> Test </p>', id=u'id_content') + client.click(xpath=u"//a[@id='calendarlink0']/img") + client.click(link=u'17') + client.click(xpath=u"//a[@id='clocklink0']/img") + client.click(link=u'6 a.m.') + client.click(id=u'id_section') + client.select(option=u'Section object', id=u'id_section') + client.click(value=u'1') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(xpath=u"//div[@id='changelist']/form/table/tbody/tr/th/a", timeout=u'8000') + client.click(xpath=u"//div[@id='changelist']/form/table/tbody/tr/th/a") + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(timeout=u'8000', id=u'id_content') + client.click(id=u'id_content') + client.type(text=u'<p> Test This </p>', id=u'id_content') + client.click(name=u'_continue') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertValue(validator=u'<p> Test This </p>', id=u'id_content') + client.click(link=u'Articles') + client.waits.forPageLoad(timeout=u'20000') + client.check(name=u'_selected_action') + client.click(name=u'action') + client.check(name=u'_selected_action') + client.click(link=u' Home ') + client.waits.forPageLoad(timeout=u'20000') + +def test_inlineEditandCreate(): + client = WindmillTestClient(calling_func_name()) + + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'Parents') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u' Add parent ', timeout=u'8000') + client.click(link=u' Add parent ') + client.waits.forPageLoad(timeout=u'20000') + client.type(text=u'Papa', id=u'id_name') + client.type(text=u'Billy', id=u'id_child_set-0-name') + client.type(text=u'Bobby', id=u'id_child_set-1-name') + client.type(text=u'Betty', id=u'id_child_set-2-name') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Parent object', timeout=u'8000') + client.click(link=u'Parent object') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertValue(validator=u'Billy', id=u'id_child_set-0-name') + client.asserts.assertValue(validator=u'Bobby', id=u'id_child_set-1-name') + client.asserts.assertValue(validator=u'Betty', id=u'id_child_set-2-name') + client.click(link=u'Home') + client.waits.forPageLoad(timeout=u'20000') + + +def test_adminActionEmptyModels(): + client = WindmillTestClient(calling_func_name()) + + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'Empty models') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u' Add empty model ', timeout=u'8000') + client.click(link=u' Add empty model ') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(timeout=u'8000', name=u'_save') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u' Add empty model ', timeout=u'8000') + client.click(link=u' Add empty model ') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(timeout=u'8000', name=u'_continue') + client.click(name=u'_continue') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Empty models', timeout=u'8000') + client.click(link=u'Empty models') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Primary key = 2', timeout=u'8000') + client.click(link=u'Primary key = 2') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(timeout=u'8000', name=u'_addanother') + client.click(name=u'_addanother') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(timeout=u'8000', name=u'_save') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Primary key = 3', timeout=u'8000') + client.click(link=u'Primary key = 3') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(timeout=u'8000', name=u'_addanother') + client.click(name=u'_addanother') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(timeout=u'8000', name=u'_save') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.check(xpath=u"//div[@id='changelist']/form/table/tbody/tr[2]/td[1]/input") + client.check(name=u'_selected_action') + client.click(name=u'action') + client.select(option=u'Delete selected empty models', name=u'action') + client.click(value=u'delete_selected') + client.click(name=u'index') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(timeout=u'8000', value=u"Yes, I'm sure") + client.click(value=u"Yes, I'm sure") + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Primary key = 2', timeout=u'8000') + client.click(link=u'Primary key = 2') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Delete', timeout=u'8000') + client.click(link=u'Delete') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(timeout=u'8000', value=u"Yes, I'm sure") + client.click(value=u"Yes, I'm sure") + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u' Home ', timeout=u'8000') + client.click(link=u' Home ') + client.waits.forPageLoad(timeout=u'20000') + +def test_parentChildRelationship(): + client = WindmillTestClient(calling_func_name()) + + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + 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[21]/td/a") + client.click(id=u'id_recommender') + client.select(option=u'Recommender object', id=u'id_recommender') + client.click(value=u'1') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'Languages') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u' Add language ') + client.waits.forPageLoad(timeout=u'20000') + client.type(text=u'en', id=u'id_iso') + client.type(text=u'testEnglish', id=u'id_name') + client.type(text=u'test', id=u'id_english_name') + client.click(xpath=u"//form[@id='language_form']/div/fieldset/div[4]/div/label") + client.check(id=u'id_shortlist') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u' Home ') + client.waits.forPageLoad(timeout=u'20000') + +def test_AdminAuthContrib(): + client = WindmillTestClient(calling_func_name()) + + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Users', timeout=u'8000') + client.click(link=u'Users') + client.waits.forPageLoad(timeout=u'20000') + #print client.commands.getPageText() + #client.asserts.assertNode(link=u'adduser') + client.asserts.assertNode(link=u'changeuser') + client.asserts.assertNode(link=u'deleteuser') + client.asserts.assertNode(link=u'joepublic') + client.asserts.assertNode(link=u'super') + client.click(link=u'Yes') + client.waits.forPageLoad(timeout=u'20000') + #client.asserts.assertNode(link=u'adduser') + client.asserts.assertNode(link=u'changeuser') + client.asserts.assertNode(link=u'deleteuser') + # client.asserts.assertNode(link=u'super') + # client.click(link=u'6 total') + # client.waits.forPageLoad(timeout=u'20000') + # client.waits.forElement(link=u'super', timeout=u'8000') + client.click(link=u'super') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Clear all', timeout=u'8000') + client.click(link=u'Clear all') + client.click(link=u'Choose all') + client.click(name=u'_continue') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertValue(validator=u'2007-05-30', id=u'id_date_joined_0') + client.asserts.assertValue(validator=u'13:20:10', id=u'id_date_joined_1') + client.asserts.assertValue(validator=u'Super', id=u'id_first_name') + client.asserts.assertValue(validator=u'User', id=u'id_last_name') + client.asserts.assertValue(validator=u'super@example.com', id=u'id_email') + client.asserts.assertValue(validator=u'on', id=u'id_is_staff') + client.asserts.assertValue(validator=u'on', id=u'id_is_active') + client.asserts.assertValue(validator=u'on', id=u'id_is_superuser') + client.asserts.assertValue(validator=u'super', id=u'id_username') + client.click(link=u'Users') + client.waits.forPageLoad(timeout=u'20000') + client.type(text=u'super', id=u'searchbar') + client.click(value=u'Search') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertNode(link=u'super') + client.click(link=u' Home ') + client.waits.forPageLoad(timeout=u'20000') + + +def test_contribFlatSitesRedirect(): + client = WindmillTestClient(calling_func_name()) + + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + #print client.commands.getPageText() + client.click(xpath=u"//div[@id='content-main']/div[table/caption/a/text()='Flatpages']/table/tbody/tr[1]/td/a") + client.waits.forPageLoad(timeout=u'20000') + #print client.commands.getPageText() + client.click(id=u'id_url') + client.type(text=u'/testflat/test/', id=u'id_url') + client.type(text=u'Test Flat', id=u'id_title') + client.type(text=u'This is some unique test content.', id=u'id_content') + client.select(id='id_sites', val=u'1') + client.click(id=u'fieldsetcollapser1') + client.check(id=u'id_enable_comments') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'/testflat/test/') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'Home') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'Flat pages') + 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/caption/a/text()='Sites']/table/tbody/tr[1]/th/a") + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'example.com') + client.waits.forPageLoad(timeout=u'20000') + client.click(id=u'id_domain') + client.doubleClick(id=u'id_domain') + client.type(text=u'localhost:8000', id=u'id_domain') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u' Home ') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'Flat pages') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'/testflat/test/') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'View on site') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertText(xpath=u'/html/body', validator=u'\nThis is some unique test content.\n') + client.goBack() + 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/caption/a/text()='Redirects']/table/tbody/tr[1]/th/a") + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u' Add redirect ') + client.waits.forPageLoad(timeout=u'20000') + client.click(xpath=u"//ul[@id='id_site']/li/label") + client.waits.forPageLoad(timeout=u'20000') + client.click(id=u'id_site_0') + client.radio(id=u'id_site_0') + client.click(id=u'id_old_path') + client.type(text=u'/events/test', id=u'id_old_path') + client.type(text=u'/', id=u'id_new_path') + client.type(text=u'/test_admin/', id=u'id_new_path') + client.click(id=u'id_new_path') + client.doubleClick(id=u'id_new_path') + client.type(text=u'/testflat/test/', id=u'id_new_path') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'/events/test') + client.waits.forPageLoad(timeout=u'20000') + client.click(link=u'Home') + client.waits.forPageLoad(timeout=u'20000') + client.open(url=u'http://localhost:8000/events/test') + client.waits.forPageLoad(timeout=u'8000') + client.asserts.assertText(xpath=u'/html/body', validator=u'\nThis is some unique test content.\n') + client.open(url=u'http://localhost:8000/test_admin/admin/') + client.waits.forPageLoad(timeout=u'8000') + client.click(link=u'Log out') + client.waits.forPageLoad(timeout=u'20000') + + +def test_ensureLogout(): + client = WindmillTestClient(calling_func_name()) + + client.open(url=ADMIN_URL) + client.waits.forPageLoad(timeout=u'20000') + client.open(url="%s/accounts/logout"% global_settings.TEST_URL) + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertText(xpath=u"//div[@id='content']/h1", validator=u'Logged out') + client.asserts.assertText(xpath=u"//div[@id='content']/p", validator=u'Thanks for spending some quality time with the Web site today.') + client.asserts.assertNode(link=u'Log in again')
\ No newline at end of file diff --git a/tests/regressiontests/admin_widgets/wmtests/__init__.py b/tests/regressiontests/admin_widgets/wmtests/__init__.py new file mode 100644 index 0000000000..572975ffa3 --- /dev/null +++ b/tests/regressiontests/admin_widgets/wmtests/__init__.py @@ -0,0 +1,128 @@ + +from windmill.conf import global_settings +ADMIN_WIDGET_URL = "%s/widget_admin/" % global_settings.TEST_URL +from windmill.authoring import WindmillTestClient +from django.test.utils import calling_func_name + + +def test_baseWidgetTestCars(): + client = WindmillTestClient("A second module") + + client.open(url=ADMIN_WIDGET_URL) + client.waits.forPageLoad(timeout=u'20000') + client.type(text=u'super', id=u'id_username') + client.type(text=u'secret', id=u'id_password') + client.click(value=u'Log in') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Car tires', timeout=u'8000') + client.click(link=u'Car tires') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u' Add car tire ', timeout=u'8000') + client.click(link=u' Add car tire ') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(timeout=u'8000', id=u'id_car') + client.click(id=u'id_car') + client.click(xpath=u"//a[@id='add_id_car']/img") + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(xpath=u"//form[@id='car_form']/div/fieldset/div[2]/div", timeout=u'8000') + client.click(xpath=u"//form[@id='car_form']/div/fieldset/div[2]/div") + client.click(id=u'id_owner') + client.select(option=u'super', id=u'id_owner') + client.click(value=u'100') + client.click(id=u'id_make') + client.type(text=u'Ferrari', id=u'id_make') + client.type(text=u'F-xx', id=u'id_model') + client.click(xpath=u"//form[@id='car_form']/div/fieldset/div[2]") + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.closeWindow() + client.click(link=u'Home') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Car tires', timeout=u'8000') + client.click(link=u'Car tires') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u' Add car tire ', timeout=u'8000') + client.click(link=u' Add car tire ') + client.waits.forPageLoad(timeout=u'20000') + client.select(option=u'Ferrari F-xx', id=u'id_car') + client.click(value=u'1') + client.click(name=u'_save') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'CarTire object', timeout=u'8000') + client.click(link=u'CarTire object') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertImageLoaded(xpath=u"//a[@id='add_id_car']/img") + client.asserts.assertNode(id=u'id_car') + client.asserts.assertNode(link=u'Delete') + client.click(xpath=u"//form[@id='cartire_form']/div/fieldset/div/div") + client.click(link=u'Home') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(link=u'Cars', timeout=u'8000') + client.click(link=u'Cars') + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertImageLoaded(xpath=u"//a[@id='add_id_form-0-owner']/img") + client.asserts.assertNode(link=u'Ferrari') + client.asserts.assertNode(id=u'id_form-0-owner') + #client.asserts.assertSelected(xpath=u"//select[@id='id_form-0-owner']/option[3]", validator=u'') + client.click(link=u' Home ') + client.waits.forPageLoad(timeout=u'20000') + client.waits.forElement(timeout=u'8000', id=u'user-tools') + client.click(id=u'user-tools') + client.click(link=u'Log out') + client.waits.forPageLoad(timeout=u'20000') + +def test_loginWidgetAdmin(): + '''Mostly just a proof of concept to test working order of tests.''' + client = WindmillTestClient("freebie test") + + # print dir(client) + # print dir(client.open) + # print dir(client.commands) + # print client.commands() + + # client.open(url=ADMIN_URL) + # client.waits.forPageLoad(timeout=u'20000') + # client.type(text=u'super', id=u'id_username') + # client.type(text=u'secret', id=u'id_password') + # client.click(value=u'Log in') + # client.waits.forPageLoad(timeout=u'20000') + # client.asserts.assertNode(xpath=u"//div[@id='content-main']/div/table/tbody/tr[1]/th") + # client.asserts.assertNode(link=u'Articles') + # client.asserts.assertNode(link=u'Add') + # 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.waits.forPageLoad(timeout=u'20000') + # client.type(text=u'Test Section', id=u'id_name') + # client.click(name=u'_save') + # client.waits.forPageLoad(timeout=u'20000') + # client.asserts.assertNode(link=u'Section object') + # client.click(link=u' Admin_views ') + # client.waits.forPageLoad(timeout=u'20000') + # client.waits.forElement(link=u'Add', timeout=u'8000') + # client.click(link=u'Add') + # client.waits.forPageLoad(timeout=u'20000') + # client.type(text=u'Test 1', id=u'id_title') + # client.type(text=u'This is test content.', id=u'id_content') + # client.click(link=u'Today') + # client.click(link=u'Now') + # client.click(id=u'id_section') + # client.select(option=u'Section object', id=u'id_section') + # client.click(value=u'1') + # #client.asserts.assertValue(validator=u'2009-06-16', id=u'id_date_0') + # #client.asserts.assertValue(validator=u'13:31:21', id=u'id_date_1') + # client.asserts.assertNode(id=u'id_section') + # client.click(name=u'_save') + # client.waits.forPageLoad(timeout=u'20000') + # client.asserts.assertNode(link=u'This is test content.') + # client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/td[2]") + # client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/td[3]") + # client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/td[4]") + # client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/td[5]") + # client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/th") + # client.click(link=u'Today') + # client.waits.forPageLoad(timeout=u'20000') + # client.asserts.assertNode(xpath=u"//div[@id='changelist']/form/table/tbody/tr/th") + # client.click(link=u' Home ') + # client.waits.forPageLoad(timeout=u'20000')
\ No newline at end of file diff --git a/tests/regressiontests/test_decorators/tests.py b/tests/regressiontests/test_decorators/tests.py new file mode 100644 index 0000000000..561319bf71 --- /dev/null +++ b/tests/regressiontests/test_decorators/tests.py @@ -0,0 +1,23 @@ +""" +>>> from django.test import SkippedTest +>>> from django.test.decorators import * + +>>> skip_test()(None)(None) +Traceback (most recent call last): + ... +SkippedTest + +>>> skip_test(reason='testing')(None)(None) +Traceback (most recent call last): + ... +SkippedTest: testing + +>>> conditional_skip(lambda: False)(None)(None) +Traceback (most recent call last): + ... +SkippedTest + +>>> conditional_skip(lambda: True)(lambda: True)() +True + +""" diff --git a/tests/runtests.py b/tests/runtests.py index f556246c90..d9332ff66d 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -1,9 +1,20 @@ #!/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 import django.contrib as contrib +from django.core.servers import basehttp +from time import sleep try: set @@ -11,6 +22,7 @@ except NameError: from sets import Set as set # For Python 2.3 + CONTRIB_DIR_NAME = 'django.contrib' MODEL_TESTS_DIR_NAME = 'modeltests' REGRESSION_TESTS_DIR_NAME = 'regressiontests' @@ -32,6 +44,16 @@ ALWAYS_INSTALLED_APPS = [ 'django.contrib.admin', ] +# WINDMILL_FIXTURES = ['regressiontests/admin_views/fixtures/%s' % fix for fix in ['admin-views-users.xml', +# 'admin-views-colors.xml', +# 'admin-views-fabrics.xml', +# 'admin-views-unicode.xml', +# 'multiple-child-classes', +# 'admin-views-actions.xml', +# 'string-primary-key.xml', +# 'admin-views-person.xml']] + + def get_test_models(): models = [] for loc, dirpath in (MODEL_TESTS_DIR_NAME, MODEL_TEST_DIR), (REGRESSION_TESTS_DIR_NAME, REGRESSION_TEST_DIR), (CONTRIB_DIR_NAME, CONTRIB_DIR): @@ -99,8 +121,11 @@ def django_tests(verbosity, interactive, test_labels): # Redirect some settings for the duration of these tests. settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS + # Try and include windmill application if specified on command line. + if do_windmill: + settings.INSTALLED_APPS.append('windmill') settings.ROOT_URLCONF = 'urls' - settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),) + settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR), ) settings.USE_I18N = True settings.LANGUAGE_CODE = 'en' settings.LOGIN_URL = '/accounts/login/' @@ -108,12 +133,15 @@ def django_tests(verbosity, interactive, test_labels): 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.common.CommonMiddleware', + #Add the following 2 middleware so we can test them in Windmill. + 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', + 'django.contrib.redirects.middleware.RedirectFallbackMiddleware', ) settings.SITE_ID = 1 # For testing comment-utils, we require the MANAGERS attribute # to be set, so that a test email is sent out which we catch # in our tests. - settings.MANAGERS = ("admin@djangoproject.com",) + settings.MANAGERS = ("admin@djangoproject.com", ) # Load all the ALWAYS_INSTALLED_APPS. # (This import statement is intentionally delayed until after we @@ -156,10 +184,173 @@ def django_tests(verbosity, interactive, test_labels): from django.test.utils import get_runner if not hasattr(settings, 'TEST_RUNNER'): settings.TEST_RUNNER = 'django.test.simple.run_tests' - test_runner = get_runner(settings) + #establish coverage settings for the regression suite + settings.COVERAGE_MODULE_EXCLUDES = ['modeltests*', 'regressiontests*'] + settings.COVERAGE_CODE_EXCLUDES = ['def __unicode__\(self\):', + 'def get_absolute_url\(self\):', + 'from .* import .*', + 'import .*', + 'from *'] + # depending on how this is run, we might need to tell the coverage libraries to consider django.* + settings.COVERAGE_ADDITIONAL_MODULES = ['django'] + + # Default number of failures is 0 + failures = 0 + if do_windmill: + # Our bank of windmill-specific imports. Only loaded when used. + import types + import logging + import threading + from windmill.conf import global_settings + from windmill.authoring import setup_module, teardown_module + from django.core.management.commands.test_windmill import ServerContainer, attempt_import + from django.test.windmill_tests import WindmillDjangoUnitTest + from django.db.models.loading import remove_model, get_app + #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) + #Check if this is an old-style testrunner, and behave accordingly. + if(type(test_runner) == 'function'): + failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests) + else: + tr = test_runner() + failures = tr.run_tests(test_labels, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests) + + #Run windmill tests if --windmill parameter was passed. + if do_windmill: + # We don't want to try and parse models that we know are invalid. + remove_model('invalid_models') + + # Determine which browser to run the tests in. + if 'ie' == wm_browser: + global_settings.START_IE = True + elif 'safari' == wm_browser: + global_settings.START_SAFARI = True + elif 'chrome' == wm_browser: + global_settings.START_CHROME = True + else: + global_settings.START_FIREFOX = True + + # + # Find which of our INSTALLED_APPS have tests. + tests = [] + for name in [app for app in settings.INSTALLED_APPS if not('invalid' in app)]: + for suffix in ['tests', 'wmtests', 'windmilltests']: + x = attempt_import(name, suffix) + if x is not None: + print "Adding %s %s to tests" % (suffix, x, ) + tests.append((suffix, x, )) - failures = test_runner(test_labels, verbosity=verbosity, interactive=interactive, extra_tests=extra_tests) + # Collect the WindmillDjangoUnitTest from tests.py and any 'wmtests' or 'windmilltests' modules. + wmfixs = [] + wmtests = [] + for (ttype, mod, ) in tests: + if ttype == 'tests': + for ucls in [getattr(mod, x) for x in dir(mod) + if (type(getattr(mod, x, None)) in (types.ClassType, + types.TypeType) ) and + issubclass(getattr(mod, x), WindmillDjangoUnitTest) + ]: + wmtests.append(ucls.test_dir) + + else: + if mod.__file__.endswith('__init__.py') or mod.__file__.endswith('__init__.pyc'): + wmtests.append(os.path.join(*os.path.split(os.path.abspath(mod.__file__))[:-1])) + else: + wmtests.append(os.path.abspath(mod.__file__)) + # Look for any attribute named fixtures and try to load it. + if hasattr(mod, 'fixtures'): + for fixture in getattr(mod, 'fixtures'): + wmfixs.append(fixture) + + # Create the threaded server. + server_container = ServerContainer() + # Set the server's 'fixtures' attribute so they can be loaded in-thread if using sqlite's memory backend. + server_container.__setattr__('fixtures', wmfixs) + # Start the server thread. + started = server_container.start_test_server() + # These 2 unit tests can't be used while running our admin. Explicitly remove. + if 'regressiontests.bug8245' in settings.INSTALLED_APPS: + settings.INSTALLED_APPS.remove('regressiontests.bug8245') + if 'django.contrib.gis' in settings.INSTALLED_APPS: + settings.INSTALLED_APPS.remove('django.contrib.gis') + + + print 'Waiting for threaded server to come online.' + started.wait() + print 'DB Ready, Server online.' + + + + # Set the testing URL based on what available port we get. + global_settings.TEST_URL = 'http://localhost:%d' % server_container.server_thread.port + + + + # Make sure we even need to run tests. + if len(wmtests) is 0: + print 'Sorry, no windmill tests found.' + else: + # Setup and run unittests. + testtotals = {} + x = logging.getLogger() + x.setLevel(logging.DEBUG) + from windmill.server.proxy import logger + #from functest import bin + #from functest import runner + #runner.CLIRunner.final = classmethod(lambda self, totals: testtotals.update(totals)) + #import windmill + #count = 0 + #print wmtests + #for wmt in wmtests: + #print wmt + #print tests[count][1] + #count = count + 1 + #count = 0 + bin = None + runner = None + setup_module(tests[0][1]) + for wmt in wmtests: + print sys.argv + sys.argv = [wmt,] + print sys.argv + for k in (k for k in sys.modules.keys() if k.startswith('functest')): + del(sys.modules[k]) + #dbin) + #del(runner) + import functest + from functest import bin + from functest import runner + runner.CLIRunner.final = classmethod(lambda self, totals: testtotals.update(totals) ) + bin.cli() + bin = None + runner = None + print sys.argv + # import functest + # functest.modules_passed = [] + # functest.modules_failed = [] + # from functest import frame + # frame.totals = {'pass':0, 'fail':0, 'skip':0} + #teardown_module(tests[count][1]) + #sleep(.5) + #count = count + 1 + #setup_module(tests[count][1]) + teardown_module(tests[0][1]) + server_container.stop_test_server() + # sys.argv = [wmtests[0],] + # bin.cli() + + if testtotals['fail'] is not 0: + sleep(.5) + sys.exit(1) + # If there where failures then report them, but give the server thread .5 seconds. if failures: + sleep(.5) sys.exit(failures) # Restore the old settings. @@ -171,21 +362,37 @@ def django_tests(verbosity, interactive, test_labels): settings.LOGIN_URL = old_login_url settings.MIDDLEWARE_CLASSES = old_middleware_classes + if __name__ == "__main__": from optparse import OptionParser usage = "%prog [options] [model model model ...]" parser = OptionParser(usage=usage) - parser.add_option('-v','--verbosity', action='store', dest='verbosity', default='0', + parser.add_option('-v', '--verbosity', action='store', dest='verbosity', default='0', type='choice', choices=['0', '1', '2'], help='Verbosity level; 0=minimal output, 1=normal output, 2=all output') parser.add_option('--noinput', action='store_false', dest='interactive', default=True, help='Tells Django to NOT prompt the user for input of any kind.') + parser.add_option('--windmill', action='store_true', dest='windmill', default=False, + help='Tells Django to run the Windmill functional tests as well.') + parser.add_option('--coverage', action='store_true', dest='coverage', default=False, + help='Tells Django to run the tests with code coverage as well.') + parser.add_option('--nostd', action='store_false', dest='standard', default=True, + help='Tells Django to not run the standard regression suite.') parser.add_option('--settings', help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.') + parser.add_option('--wmbrowser', + help='The browser for windmill to run its tests in.', + default='firefox', action='store', dest='wmbrowser', type='choice', + choices=['firefox', 'chrome', 'safari', 'ie']) options, args = parser.parse_args() if options.settings: os.environ['DJANGO_SETTINGS_MODULE'] = options.settings elif "DJANGO_SETTINGS_MODULE" not in os.environ: parser.error("DJANGO_SETTINGS_MODULE is not set in the environment. " "Set it or use --settings.") + do_windmill = options.windmill + do_coverage = options.coverage + do_std = options.standard + wm_browser = options.wmbrowser django_tests(int(options.verbosity), options.interactive, args) + diff --git a/tests/templates/flatpages/default.html b/tests/templates/flatpages/default.html new file mode 100644 index 0000000000..3a32a20ec8 --- /dev/null +++ b/tests/templates/flatpages/default.html @@ -0,0 +1,10 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" + "http://www.w3.org/TR/REC-html40/loose.dtd"> +<html> +<head> +<title>{{ flatpage.title }}</title> +</head> +<body> +{{ flatpage.content }} +</body> +</html> diff --git a/tests/urls.py b/tests/urls.py index 6704829231..b06863dfb8 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -11,6 +11,7 @@ urlpatterns = patterns('', # Always provide the auth system login and logout views (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}), (r'^accounts/logout/$', 'django.contrib.auth.views.logout'), + (r'^accounts2/', include('django.contrib.auth.urls')), # test urlconf for {% url %} template tag (r'^url_tag/', include('regressiontests.templates.urls')), diff --git a/tests/windmill_dev/__init__.py b/tests/windmill_dev/__init__.py new file mode 100755 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/windmill_dev/__init__.py diff --git a/tests/windmill_dev/manage.py b/tests/windmill_dev/manage.py new file mode 100755 index 0000000000..5e78ea979e --- /dev/null +++ b/tests/windmill_dev/manage.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/tests/windmill_dev/settings.py b/tests/windmill_dev/settings.py new file mode 100755 index 0000000000..ba75978ae6 --- /dev/null +++ b/tests/windmill_dev/settings.py @@ -0,0 +1,83 @@ +# Django settings for windmill_dev project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + # ('Your Name', 'your_email@domain.com'), +) + +MANAGERS = ADMINS + +DATABASE_ENGINE = 'sqlite3' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. +DATABASE_NAME = 'test.db' # Or path to database file if using sqlite3. +DATABASE_USER = '' # Not used with sqlite3. +DATABASE_PASSWORD = '' # Not used with sqlite3. +DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3. +DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. + +# Local time zone for this installation. Choices can be found here: +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be available on all operating systems. +# If running in a Windows environment this must be set to the same as your +# system time zone. +TIME_ZONE = 'America/Chicago' + +# Language code for this installation. All choices can be found here: +# http://www.i18nguy.com/unicode/language-identifiers.html +LANGUAGE_CODE = 'en-us' + +SITE_ID = 1 + +# If you set this to False, Django will make some optimizations so as not +# to load the internationalization machinery. +USE_I18N = True + +# Absolute path to the directory that holds media. +# Example: "/home/media/media.lawrence.com/" +MEDIA_ROOT = '' + +# URL that handles the media served from MEDIA_ROOT. Make sure to use a +# trailing slash if there is a path component (optional in other cases). +# Examples: "http://media.lawrence.com", "http://example.com/media/" +MEDIA_URL = '' + +# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a +# trailing slash. +# Examples: "http://foo.com/media/", "/media/". +ADMIN_MEDIA_PREFIX = '/media/' + +# Make this unique, and don't share it with anybody. +SECRET_KEY = '+w3^9f0n-e+08t&z@&#^47z(qd@c$a#mr^$!(ab+xdoovu%_9h' + +# List of callables that know how to import templates from various sources. +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', +# 'django.template.loaders.eggs.load_template_source', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', +) + +ROOT_URLCONF = 'windmill_dev.urls' + +TEMPLATE_DIRS = ( + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.admin', + 'django.contrib.admindocs', + 'windmill', + 'testapp', +) diff --git a/tests/windmill_dev/testapp/__init__.py b/tests/windmill_dev/testapp/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/windmill_dev/testapp/__init__.py diff --git a/tests/windmill_dev/testapp/models.py b/tests/windmill_dev/testapp/models.py new file mode 100644 index 0000000000..71a8362390 --- /dev/null +++ b/tests/windmill_dev/testapp/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/tests/windmill_dev/testapp/tests.py b/tests/windmill_dev/testapp/tests.py new file mode 100644 index 0000000000..63fe9ec782 --- /dev/null +++ b/tests/windmill_dev/testapp/tests.py @@ -0,0 +1,32 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + +import os +from windmill.authoring import djangotest + +class TestProjectWindmillTest(djangotest.WindmillDjangoUnitTest): + test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'windmilltests') + #test_dir = os.path.dirname(os.path.abspath(__file__)) + browser = 'firefox' + + diff --git a/tests/windmill_dev/testapp/views.py b/tests/windmill_dev/testapp/views.py new file mode 100644 index 0000000000..60f00ef0ef --- /dev/null +++ b/tests/windmill_dev/testapp/views.py @@ -0,0 +1 @@ +# Create your views here. diff --git a/tests/windmill_dev/testapp/windmilltests/__init__.py b/tests/windmill_dev/testapp/windmilltests/__init__.py new file mode 100644 index 0000000000..b942b75115 --- /dev/null +++ b/tests/windmill_dev/testapp/windmilltests/__init__.py @@ -0,0 +1,12 @@ +# Generated by the windmill services transformer +from windmill.authoring import WindmillTestClient + +def test_recordingSuite0(): + client = WindmillTestClient(__name__) + client.type(text="Hello", name='q') + client.click(xpath=u"//span[@id='body']/center/form/table[2]/tbody/tr[8]/td") + client.waits.forPageLoad(timeout=u'20000') + client.asserts.assertNode(xpath=u"//div[@id='res']/div/ol/li/h3[1]/a/em") + client.asserts.assertNode(link=u'How do you say hello in Japanese ? - Yahoo! Answers') + client.asserts.assertNode(link=u'hello') + client.asserts.assertNode(link=u'japanese')
\ No newline at end of file diff --git a/tests/windmill_dev/urls.py b/tests/windmill_dev/urls.py new file mode 100755 index 0000000000..123cf0cbe0 --- /dev/null +++ b/tests/windmill_dev/urls.py @@ -0,0 +1,17 @@ +from django.conf.urls.defaults import * + +# Uncomment the next two lines to enable the admin: +from django.contrib import admin +admin.autodiscover() + +urlpatterns = patterns('', + # Example: + # (r'^windmill_dev/', include('windmill_dev.foo.urls')), + + # Uncomment the admin/doc line below and add 'django.contrib.admindocs' + # to INSTALLED_APPS to enable admin documentation: + (r'^admin/doc/', include('django.contrib.admindocs.urls')), + + # Uncomment the next line to enable the admin: + (r'^admin/(.*)', admin.site.root), +) |
