Fixed #17365, #17366, #18727 -- Switched to discovery test runner.

Thanks to Preston Timmons for the bulk of the work on the patch, especially
updating Django's own test suite to comply with the requirements of the new
runner. Thanks also to Jannis Leidel and Mahdi Yusuf for earlier work on the
patch and the discovery runner.

Refs #11077, #17032, and #18670.
This commit is contained in:
Carl Meyer 2013-05-10 23:08:45 -04:00
parent c0d8932a6d
commit 9012833af8
79 changed files with 959 additions and 1019 deletions

View file

@ -10,16 +10,23 @@ from django import contrib
from django.utils._os import upath
from django.utils import six
CONTRIB_DIR_NAME = 'django.contrib'
CONTRIB_MODULE_PATH = 'django.contrib'
TEST_TEMPLATE_DIR = 'templates'
RUNTESTS_DIR = os.path.abspath(os.path.dirname(upath(__file__)))
CONTRIB_DIR = os.path.dirname(upath(contrib.__file__))
TEMP_DIR = tempfile.mkdtemp(prefix='django_')
os.environ['DJANGO_TEST_TEMP_DIR'] = TEMP_DIR
SUBDIRS_TO_SKIP = ['templates']
SUBDIRS_TO_SKIP = [
'templates',
'test_discovery_sample',
'test_discovery_sample2',
'test_runner_deprecation_app',
'test_runner_invalid_app',
]
ALWAYS_INSTALLED_APPS = [
'shared_models',
@ -40,17 +47,12 @@ ALWAYS_INSTALLED_APPS = [
'staticfiles_tests.apps.no_label',
]
def geodjango(settings):
# All databases must have spatial backends to run GeoDjango tests.
spatial_dbs = [name for name, db_dict in settings.DATABASES.items()
if db_dict['ENGINE'].startswith('django.contrib.gis')]
return len(spatial_dbs) == len(settings.DATABASES)
def get_test_modules():
modules = []
for loc, dirpath in (
for modpath, dirpath in (
(None, RUNTESTS_DIR),
(CONTRIB_DIR_NAME, CONTRIB_DIR)):
(CONTRIB_MODULE_PATH, CONTRIB_DIR)):
for f in os.listdir(dirpath):
if ('.' in f or
# Python 3 byte code dirs (PEP 3147)
@ -59,9 +61,14 @@ def get_test_modules():
os.path.basename(f) in SUBDIRS_TO_SKIP or
os.path.isfile(f)):
continue
modules.append((loc, f))
modules.append((modpath, f))
return modules
def get_installed():
from django.db.models.loading import get_apps
return [app.__name__.rsplit('.', 1)[0] for app in get_apps()]
def setup(verbosity, test_labels):
from django.conf import settings
from django.db.models.loading import get_apps, load_app
@ -95,25 +102,45 @@ def setup(verbosity, test_labels):
get_apps()
# Load all the test model apps.
test_labels_set = set([label.split('.')[0] for label in test_labels])
test_modules = get_test_modules()
# Reduce given test labels to just the app module path
test_labels_set = set()
for label in test_labels:
bits = label.split('.')
if bits[:2] == ['django', 'contrib']:
bits = bits[:3]
else:
bits = bits[:1]
test_labels_set.add('.'.join(bits))
# If GeoDjango, then we'll want to add in the test applications
# that are a part of its test suite.
if geodjango(settings):
from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
if HAS_SPATIAL_DB:
from django.contrib.gis.tests import geo_apps
test_modules.extend(geo_apps(runtests=True))
test_modules.extend(geo_apps())
settings.INSTALLED_APPS.extend(['django.contrib.gis', 'django.contrib.sitemaps'])
for module_dir, module_name in test_modules:
if module_dir:
module_label = '.'.join([module_dir, module_name])
for modpath, module_name in test_modules:
if modpath:
module_label = '.'.join([modpath, module_name])
else:
module_label = module_name
# if the module was named on the command line, or
# if the module (or an ancestor) was named on the command line, or
# no modules were named (i.e., run all), import
# this module and add it to the list to test.
if not test_labels or module_name in test_labels_set:
# this module and add it to INSTALLED_APPS.
if not test_labels:
module_found_in_labels = True
else:
match = lambda label: (
module_label == label or # exact match
module_label.startswith(label + '.') # ancestor match
)
module_found_in_labels = any(match(l) for l in test_labels_set)
if module_found_in_labels:
if verbosity >= 2:
print("Importing application %s" % module_name)
mod = load_app(module_label)
@ -139,21 +166,16 @@ def django_tests(verbosity, interactive, failfast, test_labels):
state = setup(verbosity, test_labels)
extra_tests = []
# If GeoDjango is used, add it's tests that aren't a part of
# an application (e.g., GEOS, GDAL, Distance objects).
if geodjango(settings) and (not test_labels or 'gis' in test_labels):
from django.contrib.gis.tests import geodjango_suite
extra_tests.append(geodjango_suite(apps=False))
# Run the test suite, including the extra validation tests.
from django.test.utils import get_runner
if not hasattr(settings, 'TEST_RUNNER'):
settings.TEST_RUNNER = 'django.test.simple.DjangoTestSuiteRunner'
TestRunner = get_runner(settings)
from django.test.runner import DiscoverRunner
test_runner = TestRunner(verbosity=verbosity, interactive=interactive,
failfast=failfast)
failures = test_runner.run_tests(test_labels, extra_tests=extra_tests)
test_runner = DiscoverRunner(
verbosity=verbosity,
interactive=interactive,
failfast=failfast,
)
failures = test_runner.run_tests(
test_labels or get_installed(), extra_tests=extra_tests)
teardown(state)
return failures
@ -162,10 +184,7 @@ def django_tests(verbosity, interactive, failfast, test_labels):
def bisect_tests(bisection_label, options, test_labels):
state = setup(int(options.verbosity), test_labels)
if not test_labels:
# Get the full list of test labels to use for bisection
from django.db.models.loading import get_apps
test_labels = [app.__name__.split('.')[-2] for app in get_apps()]
test_labels = test_labels or get_installed()
print('***** Bisecting test suite: %s' % ' '.join(test_labels))
@ -222,11 +241,7 @@ def bisect_tests(bisection_label, options, test_labels):
def paired_tests(paired_test, options, test_labels):
state = setup(int(options.verbosity), test_labels)
if not test_labels:
print("")
# Get the full list of test labels to use for bisection
from django.db.models.loading import get_apps
test_labels = [app.__name__.split('.')[-2] for app in get_apps()]
test_labels = test_labels or get_installed()
print('***** Trying paired execution')