Django

Code

root/django/branches/0.95-bugfixes/tests/runtests.py

Revision 3319, 13.0 kB (checked in by adrian, 2 years ago)

Changed runtests.py to set USE_I18N=True for testing

  • Property svn:executable set to *
Line 
1 #!/usr/bin/env python
2
3 import os, re, sys, time, traceback
4
5 # doctest is included in the same package as this module, because this testing
6 # framework uses features only available in the Python 2.4 version of doctest,
7 # and Django aims to work with Python 2.3+.
8 import doctest
9
10 MODEL_TESTS_DIR_NAME = 'modeltests'
11 OTHER_TESTS_DIR = "othertests"
12 REGRESSION_TESTS_DIR_NAME = 'regressiontests'
13 TEST_DATABASE_NAME = 'django_test_db'
14
15 error_list = []
16 def log_error(model_name, title, description):
17     error_list.append({
18         'title': "%r module: %s" % (model_name, title),
19         'description': description,
20     })
21
22 MODEL_TEST_DIR = os.path.join(os.path.dirname(__file__), MODEL_TESTS_DIR_NAME)
23 REGRESSION_TEST_DIR = os.path.join(os.path.dirname(__file__), REGRESSION_TESTS_DIR_NAME)
24
25 ALWAYS_INSTALLED_APPS = [
26     'django.contrib.contenttypes',
27     'django.contrib.auth',
28     'django.contrib.sites',
29     'django.contrib.flatpages',
30     'django.contrib.redirects',
31     'django.contrib.sessions',
32     'django.contrib.comments',
33     'django.contrib.admin',
34 ]
35
36 def get_test_models():
37     models = []
38     for loc, dirpath in (MODEL_TESTS_DIR_NAME, MODEL_TEST_DIR), (REGRESSION_TESTS_DIR_NAME, REGRESSION_TEST_DIR):
39         for f in os.listdir(dirpath):
40             if f.startswith('__init__') or f.startswith('.') or f.startswith('sql'):
41                 continue
42             models.append((loc, f))
43     return models
44
45 class DjangoDoctestRunner(doctest.DocTestRunner):
46     def __init__(self, verbosity_level, *args, **kwargs):
47         self.verbosity_level = verbosity_level
48         doctest.DocTestRunner.__init__(self, *args, **kwargs)
49         self._checker = DjangoDoctestOutputChecker()
50         self.optionflags = doctest.ELLIPSIS
51
52     def report_start(self, out, test, example):
53         if self.verbosity_level > 1:
54             out("  >>> %s\n" % example.source.strip())
55
56     def report_failure(self, out, test, example, got):
57         log_error(test.name, "API test failed",
58             "Code: %r\nLine: %s\nExpected: %r\nGot: %r" % (example.source.strip(), example.lineno, example.want, got))
59
60     def report_unexpected_exception(self, out, test, example, exc_info):
61         from django.db import transaction
62         tb = ''.join(traceback.format_exception(*exc_info)[1:])
63         log_error(test.name, "API test raised an exception",
64             "Code: %r\nLine: %s\nException: %s" % (example.source.strip(), example.lineno, tb))
65         # Rollback, in case of database errors. Otherwise they'd have
66         # side effects on other tests.
67         transaction.rollback_unless_managed()
68
69 normalize_long_ints = lambda s: re.sub(r'(?<![\w])(\d+)L(?![\w])', '\\1', s)
70
71 class DjangoDoctestOutputChecker(doctest.OutputChecker):
72     def check_output(self, want, got, optionflags):
73         ok = doctest.OutputChecker.check_output(self, want, got, optionflags)
74
75         # Doctest does an exact string comparison of output, which means long
76         # integers aren't equal to normal integers ("22L" vs. "22"). The
77         # following code normalizes long integers so that they equal normal
78         # integers.
79         if not ok:
80             return normalize_long_ints(want) == normalize_long_ints(got)
81         return ok
82
83 class TestRunner:
84     def __init__(self, verbosity_level=0, which_tests=None):
85         self.verbosity_level = verbosity_level
86         self.which_tests = which_tests
87
88     def output(self, required_level, message):
89         if self.verbosity_level > required_level - 1:
90             print message
91
92     def run_tests(self):
93         from django.conf import settings
94
95         # An empty access of the settings to force the default options to be
96         # installed prior to assigning to them.
97         settings.INSTALLED_APPS
98
99         # Manually set INSTALLED_APPS to point to the test models.
100         settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS + ['.'.join(a) for a in get_test_models()]
101
102         # Manually set DEBUG and USE_I18N.
103         settings.DEBUG = False
104         settings.USE_I18N = True
105
106         from django.db import connection
107         from django.core import management
108         import django.db.models
109
110         # Determine which models we're going to test.
111         test_models = get_test_models()
112         if 'othertests' in self.which_tests:
113             self.which_tests.remove('othertests')
114             run_othertests = True
115             if not self.which_tests:
116                 test_models = []
117         else:
118             run_othertests = not self.which_tests
119
120         if self.which_tests:
121             # Only run the specified tests.
122             bad_models = [m for m in self.which_tests if (MODEL_TESTS_DIR_NAME, m) not in test_models and (REGRESSION_TESTS_DIR_NAME, m) not in test_models]
123             if bad_models:
124                 sys.stderr.write("Models not found: %s\n" % bad_models)
125                 sys.exit(1)
126             else:
127                 all_tests = []
128                 for test in self.which_tests:
129                     for loc in MODEL_TESTS_DIR_NAME, REGRESSION_TESTS_DIR_NAME:
130                         if (loc, test) in test_models:
131                             all_tests.append((loc, test))
132                 test_models = all_tests
133
134         self.output(0, "Running tests with database %r" % settings.DATABASE_ENGINE)
135
136         # If we're using SQLite, it's more convenient to test against an
137         # in-memory database.
138         if settings.DATABASE_ENGINE == "sqlite3":
139             global TEST_DATABASE_NAME
140             TEST_DATABASE_NAME = ":memory:"
141         else:
142             # Create the test database and connect to it. We need to autocommit
143             # if the database supports it because PostgreSQL doesn't allow
144             # CREATE/DROP DATABASE statements within transactions.
145             cursor = connection.cursor()
146             self._set_autocommit(connection)
147             self.output(1, "Creating test database")
148             try:
149                 cursor.execute("CREATE DATABASE %s" % TEST_DATABASE_NAME)
150             except Exception, e:
151                 sys.stderr.write("Got an error creating the test database: %s\n" % e)
152                 confirm = raw_input("It appears the test database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % TEST_DATABASE_NAME)
153                 if confirm == 'yes':
154                     cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME)
155                     cursor.execute("CREATE DATABASE %s" % TEST_DATABASE_NAME)
156                 else:
157                     print "Tests cancelled."
158                     return
159         connection.close()
160         old_database_name = settings.DATABASE_NAME
161         settings.DATABASE_NAME = TEST_DATABASE_NAME
162
163         # Initialize the test database.
164         cursor = connection.cursor()
165
166         from django.db.models.loading import load_app
167         # Install the core always installed apps
168         for app in ALWAYS_INSTALLED_APPS:
169             self.output(1, "Installing contrib app %s" % app)
170             mod = load_app(app)
171             management.install(mod)
172
173         # Run the tests for each test model.
174         self.output(1, "Running app tests")
175         for model_dir, model_name in test_models:
176             self.output(1, "%s model: Importing" % model_name)
177             try:
178                 mod = load_app(model_dir + '.' + model_name)
179             except Exception, e:
180                 log_error(model_name, "Error while importing", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
181                 continue
182
183             if not getattr(mod, 'error_log', None):
184                 # Model is not marked as an invalid model
185                 self.output(1, "%s.%s model: Installing" % (model_dir, model_name))
186                 management.install(mod)
187
188                 # Run the API tests.
189                 p = doctest.DocTestParser()
190                 test_namespace = dict([(m._meta.object_name, m) \
191                                         for m in django.db.models.get_models(mod)])
192                 dtest = p.get_doctest(mod.API_TESTS, test_namespace, model_name, None, None)
193                 # Manually set verbose=False, because "-v" command-line parameter
194                 # has side effects on doctest TestRunner class.
195                 runner = DjangoDoctestRunner(verbosity_level=verbosity_level, verbose=False)
196                 self.output(1, "%s.%s model: Running tests" % (model_dir, model_name))
197                 runner.run(dtest, clear_globs=True, out=sys.stdout.write)
198             else:
199                 # Check that model known to be invalid is invalid for the right reasons.
200                 self.output(1, "%s.%s model: Validating" % (model_dir, model_name))
201
202                 from cStringIO import StringIO
203                 s = StringIO()
204                 count = management.get_validation_errors(s, mod)
205                 s.seek(0)
206                 error_log = s.read()
207                 actual = error_log.split('\n')
208                 expected = mod.error_log.split('\n')
209
210                 unexpected = [err for err in actual if err not in expected]
211                 missing = [err for err in expected if err not in actual]
212
213                 if unexpected or missing:
214                     unexpected_log = '\n'.join(unexpected)
215                     missing_log = '\n'.join(missing)
216                     log_error(model_name,
217                         "Validator found %d validation errors, %d expected" % (count, len(expected) - 1),
218                         "Missing errors:\n%s\n\nUnexpected errors:\n%s" % (missing_log, unexpected_log))
219
220         if run_othertests:
221             # Run the non-model tests in the other tests dir
222             self.output(1, "Running other tests")
223             other_tests_dir = os.path.join(os.path.dirname(__file__), OTHER_TESTS_DIR)
224             test_modules = [f[:-3] for f in os.listdir(other_tests_dir) if f.endswith('.py') and not f.startswith('__init__')]
225             for module in test_modules:
226                 self.output(1, "%s module: Importing" % module)
227                 try:
228                     mod = __import__("othertests." + module, '', '', [''])
229                 except Exception, e:
230                     log_error(module, "Error while importing", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
231                     continue
232                 if mod.__doc__:
233                     p = doctest.DocTestParser()
234                     dtest = p.get_doctest(mod.__doc__, mod.__dict__, module, None, None)
235                     runner = DjangoDoctestRunner(verbosity_level=verbosity_level, verbose=False)
236                     self.output(1, "%s module: running tests" % module)
237                     runner.run(dtest, clear_globs=True, out=sys.stdout.write)
238                 if hasattr(mod, "run_tests") and callable(mod.run_tests):
239                     self.output(1, "%s module: running tests" % module)
240                     try:
241                         mod.run_tests(verbosity_level)
242                     except Exception, e:
243                         log_error(module, "Exception running tests", ''.join(traceback.format_exception(*sys.exc_info())[1:]))
244                         continue
245
246         # Unless we're using SQLite, remove the test database to clean up after
247         # ourselves. Connect to the previous database (not the test database)
248         # to do so, because it's not allowed to delete a database while being
249         # connected to it.
250         if settings.DATABASE_ENGINE != "sqlite3":
251             connection.close()
252             settings.DATABASE_NAME = old_database_name
253             cursor = connection.cursor()
254             self.output(1, "Deleting test database")
255             self._set_autocommit(connection)
256             time.sleep(1) # To avoid "database is being accessed by other users" errors.
257             cursor.execute("DROP DATABASE %s" % TEST_DATABASE_NAME)
258
259         # Display output.
260         if error_list:
261             for d in error_list:
262                 print
263                 print d['title']
264                 print "=" * len(d['title'])
265                 print d['description']
266             print "%s error%s:" % (len(error_list), len(error_list) != 1 and 's' or '')
267         else:
268             print "All tests passed."
269
270     def _set_autocommit(self, connection):
271         """
272         Make sure a connection is in autocommit mode.
273         """
274         if hasattr(connection.connection, "autocommit"):
275             connection.connection.autocommit(True)
276         elif hasattr(connection.connection, "set_isolation_level"):
277             connection.connection.set_isolation_level(0)
278
279 if __name__ == "__main__":
280     from optparse import OptionParser
281     usage = "%prog [options] [model model model ...]"
282     parser = OptionParser(usage=usage)
283     parser.add_option('-v', help='How verbose should the output be? Choices are 0, 1 and 2, where 2 is most verbose. Default is 0.',
284         type='choice', choices=['0', '1', '2'])
285     parser.add_option('--settings',
286         help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
287     options, args = parser.parse_args()
288     verbosity_level = 0
289     if options.v:
290         verbosity_level = int(options.v)
291     if options.settings:
292         os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
293     t = TestRunner(verbosity_level, args)
294     t.run_tests()
Note: See TracBrowser for help on using the browser.