diff --git a/django/test/simple.py b/django/test/simple.py
a
|
b
|
|
1 | | import sys |
2 | | import signal |
3 | | |
4 | 1 | from django.conf import settings |
5 | 2 | from django.db.models import get_app, get_apps |
6 | 3 | from django.test import _doctest as doctest |
… |
… |
|
26 | 23 | try: |
27 | 24 | app_path = app_module.__name__.split('.')[:-1] |
28 | 25 | test_module = __import__('.'.join(app_path + [TEST_MODULE]), {}, {}, TEST_MODULE) |
29 | | except ImportError, e: |
| 26 | except ImportError: |
30 | 27 | # Couldn't import tests.py. Was it due to a missing file, or |
31 | 28 | # due to an import error in a tests.py that actually exists? |
32 | 29 | import os.path |
… |
… |
|
198 | 195 | def build_suite(self, test_labels, extra_tests=None, **kwargs): |
199 | 196 | suite = unittest.TestSuite() |
200 | 197 | |
| 198 | exclude_labels = kwargs.get('exclude_labels') |
| 199 | if exclude_labels is None: |
| 200 | exclude_labels = [] |
| 201 | |
201 | 202 | if test_labels: |
202 | 203 | for label in test_labels: |
| 204 | if label in exclude_labels: |
| 205 | if self.verbosity >= 1: |
| 206 | if '.' in label: |
| 207 | print 'Skipping %s test' % label |
| 208 | else: |
| 209 | print 'Skipping tests from app %s' % label |
| 210 | continue |
203 | 211 | if '.' in label: |
204 | 212 | suite.addTest(build_test(label)) |
205 | 213 | else: |
… |
… |
|
269 | 277 | A list of 'extra' tests may also be provided; these tests |
270 | 278 | will be added to the test suite. |
271 | 279 | |
| 280 | It's also possible to specify a list of labels to exclude from the |
| 281 | test suite by using the exclude_labels parameter. |
| 282 | |
272 | 283 | Returns the number of tests that failed. |
273 | 284 | """ |
| 285 | exclude_labels = kwargs.get('exclude_labels') |
274 | 286 | self.setup_test_environment() |
275 | | suite = self.build_suite(test_labels, extra_tests) |
| 287 | suite = self.build_suite(test_labels, extra_tests, exclude_labels=exclude_labels) |
276 | 288 | old_config = self.setup_databases() |
277 | 289 | result = self.run_suite(suite) |
278 | 290 | self.teardown_databases(old_config) |
diff --git a/tests/runtests.py b/tests/runtests.py
a
|
b
|
|
1 | 1 | #!/usr/bin/env python |
2 | | import os, subprocess, sys, traceback |
| 2 | import os |
| 3 | import subprocess |
| 4 | import sys |
3 | 5 | |
4 | 6 | import django.contrib as contrib |
5 | 7 | from django.utils import unittest |
… |
… |
|
63 | 65 | |
64 | 66 | try: |
65 | 67 | module = load_app(self.model_label) |
66 | | except Exception, e: |
| 68 | except Exception: |
67 | 69 | self.fail('Unable to load invalid model module') |
68 | 70 | |
69 | 71 | # Make sure sys.stdout is not a tty so that we get errors without |
… |
… |
|
72 | 74 | orig_stdout = sys.stdout |
73 | 75 | s = StringIO() |
74 | 76 | sys.stdout = s |
75 | | count = get_validation_errors(s, module) |
| 77 | get_validation_errors(s, module) |
76 | 78 | sys.stdout = orig_stdout |
77 | 79 | s.seek(0) |
78 | 80 | error_log = s.read() |
… |
… |
|
85 | 87 | self.assert_(not unexpected, "Unexpected Errors: " + '\n'.join(unexpected)) |
86 | 88 | self.assert_(not missing, "Missing Errors: " + '\n'.join(missing)) |
87 | 89 | |
88 | | def setup(verbosity, test_labels): |
| 90 | def setup(verbosity, test_labels, exclude_labels): |
89 | 91 | from django.conf import settings |
90 | 92 | state = { |
91 | 93 | 'INSTALLED_APPS': settings.INSTALLED_APPS, |
… |
… |
|
116 | 118 | # in our tests. |
117 | 119 | settings.MANAGERS = ("admin@djangoproject.com",) |
118 | 120 | |
| 121 | if exclude_labels is None: |
| 122 | exclude_labels = [] |
| 123 | |
119 | 124 | # Load all the ALWAYS_INSTALLED_APPS. |
120 | 125 | # (This import statement is intentionally delayed until after we |
121 | 126 | # access settings because of the USE_I18N dependency.) |
122 | 127 | from django.db.models.loading import get_apps, load_app |
123 | 128 | get_apps() |
124 | 129 | |
| 130 | # Only avoid loading an app if it is fully excluded, if testcases or test |
| 131 | # methods names were excluded then load its apps normally |
| 132 | exclude_apps = [label for label in exclude_labels if '.' not in label] |
| 133 | |
125 | 134 | # Load all the test model apps. |
126 | | test_labels_set = set([label.split('.')[0] for label in test_labels]) |
| 135 | test_labels_set = set([label.split('.')[0] for label in test_labels if label not in exclude_apps]) |
127 | 136 | for model_dir, model_name in get_test_models(): |
128 | 137 | model_label = '.'.join([model_dir, model_name]) |
129 | | # if the model was named on the command line, or |
130 | | # no models were named (i.e., run all), import |
131 | | # this model and add it to the list to test. |
| 138 | # if the model was named on the command line, or no models were named |
| 139 | # (i.e., run all), import this model and add it to the list to test. |
| 140 | # Also, skip importing the test apps explicitly excluded by the user. |
132 | 141 | if not test_labels or model_name in test_labels_set: |
| 142 | if model_name in exclude_apps: |
| 143 | if verbosity >= 2: |
| 144 | print "Skipping import of app %s" % model_name |
| 145 | continue |
133 | 146 | if verbosity >= 2: |
134 | 147 | print "Importing model %s" % model_name |
135 | 148 | mod = load_app(model_label) |
… |
… |
|
145 | 158 | for key, value in state.items(): |
146 | 159 | setattr(settings, key, value) |
147 | 160 | |
148 | | def django_tests(verbosity, interactive, failfast, test_labels): |
| 161 | def django_tests(verbosity, interactive, failfast, test_labels, exclude_labels=None): |
149 | 162 | from django.conf import settings |
150 | | state = setup(verbosity, test_labels) |
| 163 | state = setup(verbosity, test_labels, exclude_labels) |
151 | 164 | |
152 | 165 | # Add tests for invalid models. |
153 | 166 | extra_tests = [] |
… |
… |
|
162 | 175 | except ValueError: |
163 | 176 | pass |
164 | 177 | |
165 | | # Run the test suite, including the extra validation tests. |
| 178 | # Run the test suite, including the extra validation tests and skipping |
| 179 | # the test explicitely excluded. |
166 | 180 | from django.test.utils import get_runner |
167 | 181 | if not hasattr(settings, 'TEST_RUNNER'): |
168 | 182 | settings.TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner' |
… |
… |
|
180 | 194 | extra_tests=extra_tests) |
181 | 195 | else: |
182 | 196 | test_runner = TestRunner(verbosity=verbosity, interactive=interactive, failfast=failfast) |
183 | | failures = test_runner.run_tests(test_labels, extra_tests=extra_tests) |
| 197 | failures = test_runner.run_tests(test_labels, extra_tests=extra_tests, exclude_labels=exclude_labels) |
184 | 198 | |
185 | 199 | teardown(state) |
186 | 200 | return failures |
… |
… |
|
299 | 313 | help="Bisect the test suite to discover a test that causes a test failure when combined with the named test.") |
300 | 314 | parser.add_option('--pair', action='store', dest='pair', default=None, |
301 | 315 | help="Run the test suite in pairs with the named test to find problem pairs.") |
| 316 | parser.add_option('-e', '--exclude', action='append', dest='exclude', default=None, |
| 317 | help='Test to exclude (use multiple times to exclude multiple tests).') |
302 | 318 | options, args = parser.parse_args() |
303 | 319 | if options.settings: |
304 | 320 | os.environ['DJANGO_SETTINGS_MODULE'] = options.settings |
… |
… |
|
311 | 327 | elif options.pair: |
312 | 328 | paired_tests(options.pair, options, args) |
313 | 329 | else: |
314 | | failures = django_tests(int(options.verbosity), options.interactive, options.failfast, args) |
| 330 | failures = django_tests( |
| 331 | int(options.verbosity), |
| 332 | options.interactive, |
| 333 | options.failfast, |
| 334 | args, |
| 335 | options.exclude |
| 336 | ) |
315 | 337 | if failures: |
316 | 338 | sys.exit(bool(failures)) |