2005-07-29 23:15:40 +08:00
#!/usr/bin/env python
2011-01-21 23:55:27 +08:00
import os , subprocess , sys
2005-07-29 23:15:40 +08:00
2007-05-20 11:51:21 +08:00
import django . contrib as contrib
2010-10-11 20:55:17 +08:00
from django . utils import unittest
2007-09-14 17:55:17 +08:00
2007-05-20 11:51:21 +08:00
CONTRIB_DIR_NAME = ' django.contrib '
2006-05-02 09:31:56 +08:00
MODEL_TESTS_DIR_NAME = ' modeltests '
2006-06-20 13:29:19 +08:00
REGRESSION_TESTS_DIR_NAME = ' regressiontests '
2007-05-20 11:51:21 +08:00
2006-09-02 17:34:40 +08:00
TEST_TEMPLATE_DIR = ' templates '
2005-07-29 23:15:40 +08:00
2007-05-20 11:51:21 +08:00
CONTRIB_DIR = os . path . dirname ( contrib . __file__ )
2006-05-02 09:31:56 +08:00
MODEL_TEST_DIR = os . path . join ( os . path . dirname ( __file__ ) , MODEL_TESTS_DIR_NAME )
2006-06-20 13:29:19 +08:00
REGRESSION_TEST_DIR = os . path . join ( os . path . dirname ( __file__ ) , REGRESSION_TESTS_DIR_NAME )
2005-07-30 06:35:54 +08:00
2010-04-15 02:17:44 +08:00
REGRESSION_SUBDIRS_TO_SKIP = [ ' locale ' ]
2006-05-27 05:28:12 +08:00
ALWAYS_INSTALLED_APPS = [
' django.contrib.contenttypes ' ,
2006-06-20 22:27:44 +08:00
' django.contrib.auth ' ,
2007-08-15 19:25:22 +08:00
' django.contrib.sites ' ,
2006-05-27 05:28:12 +08:00
' django.contrib.flatpages ' ,
' django.contrib.redirects ' ,
' django.contrib.sessions ' ,
2009-12-10 00:57:23 +08:00
' django.contrib.messages ' ,
2006-06-17 02:58:45 +08:00
' django.contrib.comments ' ,
' django.contrib.admin ' ,
2010-08-14 21:41:56 +08:00
' django.contrib.admindocs ' ,
2010-10-20 09:33:24 +08:00
' django.contrib.staticfiles ' ,
2006-05-27 05:28:12 +08:00
]
2010-12-22 07:42:12 +08:00
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 )
2011-01-21 23:55:27 +08:00
def get_test_modules ( ) :
modules = [ ]
2007-05-20 11:51:21 +08:00
for loc , dirpath in ( MODEL_TESTS_DIR_NAME , MODEL_TEST_DIR ) , ( REGRESSION_TESTS_DIR_NAME , REGRESSION_TEST_DIR ) , ( CONTRIB_DIR_NAME , CONTRIB_DIR ) :
2006-06-20 22:27:44 +08:00
for f in os . listdir ( dirpath ) :
2010-04-15 02:17:44 +08:00
if f . startswith ( ' __init__ ' ) or f . startswith ( ' . ' ) or \
f . startswith ( ' sql ' ) or f . startswith ( ' invalid ' ) or \
os . path . basename ( f ) in REGRESSION_SUBDIRS_TO_SKIP :
2006-06-20 15:12:45 +08:00
continue
2011-01-21 23:55:27 +08:00
modules . append ( ( loc , f ) )
return modules
2005-07-30 06:35:54 +08:00
2011-01-21 23:55:27 +08:00
def get_invalid_modules ( ) :
modules = [ ]
2007-05-20 11:51:21 +08:00
for loc , dirpath in ( MODEL_TESTS_DIR_NAME , MODEL_TEST_DIR ) , ( REGRESSION_TESTS_DIR_NAME , REGRESSION_TEST_DIR ) , ( CONTRIB_DIR_NAME , CONTRIB_DIR ) :
2006-08-27 21:59:47 +08:00
for f in os . listdir ( dirpath ) :
if f . startswith ( ' __init__ ' ) or f . startswith ( ' . ' ) or f . startswith ( ' sql ' ) :
continue
if f . startswith ( ' invalid ' ) :
2011-01-21 23:55:27 +08:00
modules . append ( ( loc , f ) )
return modules
2006-12-15 14:06:52 +08:00
2006-08-27 21:59:47 +08:00
class InvalidModelTestCase ( unittest . TestCase ) :
2011-01-21 23:55:27 +08:00
def __init__ ( self , module_label ) :
2006-08-27 21:59:47 +08:00
unittest . TestCase . __init__ ( self )
2011-01-21 23:55:27 +08:00
self . module_label = module_label
2006-12-15 14:06:52 +08:00
2006-08-27 21:59:47 +08:00
def runTest ( self ) :
2007-08-16 14:06:55 +08:00
from django . core . management . validation import get_validation_errors
2006-06-23 12:37:00 +08:00
from django . db . models . loading import load_app
2006-08-27 21:59:47 +08:00
from cStringIO import StringIO
try :
2011-01-21 23:55:27 +08:00
module = load_app ( self . module_label )
2006-08-27 21:59:47 +08:00
except Exception , e :
self . fail ( ' Unable to load invalid model module ' )
2006-12-15 14:06:52 +08:00
2007-08-17 20:29:08 +08:00
# Make sure sys.stdout is not a tty so that we get errors without
# coloring attached (makes matching the results easier). We restore
# sys.stderr afterwards.
orig_stdout = sys . stdout
2006-08-27 21:59:47 +08:00
s = StringIO ( )
2007-08-17 20:29:08 +08:00
sys . stdout = s
2007-08-16 14:06:55 +08:00
count = get_validation_errors ( s , module )
2007-08-17 20:29:08 +08:00
sys . stdout = orig_stdout
2006-08-27 21:59:47 +08:00
s . seek ( 0 )
error_log = s . read ( )
actual = error_log . split ( ' \n ' )
expected = module . model_errors . split ( ' \n ' )
unexpected = [ err for err in actual if err not in expected ]
missing = [ err for err in expected if err not in actual ]
2011-03-03 23:04:39 +08:00
self . assertTrue ( not unexpected , " Unexpected Errors: " + ' \n ' . join ( unexpected ) )
self . assertTrue ( not missing , " Missing Errors: " + ' \n ' . join ( missing ) )
2006-08-27 21:59:47 +08:00
2010-10-09 22:44:54 +08:00
def setup ( verbosity , test_labels ) :
2006-08-27 21:59:47 +08:00
from django . conf import settings
2010-10-09 22:44:54 +08:00
state = {
' INSTALLED_APPS ' : settings . INSTALLED_APPS ,
' ROOT_URLCONF ' : getattr ( settings , " ROOT_URLCONF " , " " ) ,
' TEMPLATE_DIRS ' : settings . TEMPLATE_DIRS ,
' USE_I18N ' : settings . USE_I18N ,
' LOGIN_URL ' : settings . LOGIN_URL ,
' LANGUAGE_CODE ' : settings . LANGUAGE_CODE ,
' MIDDLEWARE_CLASSES ' : settings . MIDDLEWARE_CLASSES ,
}
2006-12-15 14:06:52 +08:00
2007-02-10 12:01:19 +08:00
# Redirect some settings for the duration of these tests.
2006-08-27 21:59:47 +08:00
settings . INSTALLED_APPS = ALWAYS_INSTALLED_APPS
2006-09-02 17:34:40 +08:00
settings . ROOT_URLCONF = ' urls '
settings . TEMPLATE_DIRS = ( os . path . join ( os . path . dirname ( __file__ ) , TEST_TEMPLATE_DIR ) , )
2006-12-15 14:06:52 +08:00
settings . USE_I18N = True
2007-10-22 01:26:32 +08:00
settings . LANGUAGE_CODE = ' en '
2007-12-17 18:31:20 +08:00
settings . LOGIN_URL = ' /accounts/login/ '
2007-02-10 12:01:19 +08:00
settings . MIDDLEWARE_CLASSES = (
' django.contrib.sessions.middleware.SessionMiddleware ' ,
' django.contrib.auth.middleware.AuthenticationMiddleware ' ,
2009-12-10 00:57:23 +08:00
' django.contrib.messages.middleware.MessageMiddleware ' ,
2007-02-10 12:01:19 +08:00
' django.middleware.common.CommonMiddleware ' ,
)
2007-12-02 05:58:51 +08:00
settings . SITE_ID = 1
2009-03-24 05:07:02 +08:00
# 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 " , )
2006-12-15 14:06:52 +08:00
# Load all the ALWAYS_INSTALLED_APPS.
# (This import statement is intentionally delayed until after we
# access settings because of the USE_I18N dependency.)
from django . db . models . loading import get_apps , load_app
2006-08-27 21:59:47 +08:00
get_apps ( )
2006-12-15 14:06:52 +08:00
2007-02-10 12:01:19 +08:00
# Load all the test model apps.
2010-08-20 23:03:11 +08:00
test_labels_set = set ( [ label . split ( ' . ' ) [ 0 ] for label in test_labels ] )
2011-01-21 23:55:27 +08:00
test_modules = get_test_modules ( )
2010-12-22 07:42:12 +08:00
# 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 import geo_apps
2011-01-21 23:55:27 +08:00
test_modules . extend ( geo_apps ( runtests = True ) )
for module_dir , module_name in test_modules :
module_label = ' . ' . join ( [ module_dir , module_name ] )
# if the module 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 :
2010-08-20 23:03:11 +08:00
if verbosity > = 2 :
2011-01-21 23:55:27 +08:00
print " Importing application %s " % module_name
mod = load_app ( module_label )
2010-08-20 23:03:11 +08:00
if mod :
2011-01-21 23:55:27 +08:00
if module_label not in settings . INSTALLED_APPS :
settings . INSTALLED_APPS . append ( module_label )
2006-08-27 21:59:47 +08:00
2010-10-09 22:44:54 +08:00
return state
def teardown ( state ) :
from django . conf import settings
# Restore the old settings.
for key , value in state . items ( ) :
setattr ( settings , key , value )
def django_tests ( verbosity , interactive , failfast , test_labels ) :
from django . conf import settings
state = setup ( verbosity , test_labels )
2011-01-21 23:55:27 +08:00
# Add tests for invalid models apps.
2006-08-27 21:59:47 +08:00
extra_tests = [ ]
2011-01-21 23:55:27 +08:00
for module_dir , module_name in get_invalid_modules ( ) :
module_label = ' . ' . join ( [ module_dir , module_name ] )
if not test_labels or module_name in test_labels :
extra_tests . append ( InvalidModelTestCase ( module_label ) )
2008-06-06 20:55:38 +08:00
try :
2008-10-02 20:57:13 +08:00
# Invalid models are not working apps, so we cannot pass them into
2008-06-06 20:55:38 +08:00
# the test runner with the other test_labels
2011-01-21 23:55:27 +08:00
test_labels . remove ( module_name )
2008-06-06 20:55:38 +08:00
except ValueError :
pass
2008-10-02 20:57:13 +08:00
2010-12-22 07:42:12 +08:00
# 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 ) :
from django . contrib . gis . tests import geodjango_suite
extra_tests . append ( geodjango_suite ( apps = False ) )
2006-08-27 21:59:47 +08:00
# Run the test suite, including the extra validation tests.
2009-02-28 12:46:38 +08:00
from django . test . utils import get_runner
if not hasattr ( settings , ' TEST_RUNNER ' ) :
2010-01-18 23:11:01 +08:00
settings . TEST_RUNNER = ' django.test.simple.DjangoTestSuiteRunner '
TestRunner = get_runner ( settings )
if hasattr ( TestRunner , ' func_name ' ) :
# Pre 1.2 test runners were just functions,
# and did not support the 'failfast' option.
import warnings
warnings . warn (
' Function-based test runners are deprecated. Test runners should be classes with a run_tests() method. ' ,
2010-10-11 20:20:07 +08:00
DeprecationWarning
2010-01-18 23:11:01 +08:00
)
failures = TestRunner ( test_labels , verbosity = verbosity , interactive = interactive ,
extra_tests = extra_tests )
else :
test_runner = TestRunner ( verbosity = verbosity , interactive = interactive , failfast = failfast )
failures = test_runner . run_tests ( test_labels , extra_tests = extra_tests )
2009-02-28 12:46:38 +08:00
2010-10-09 22:44:54 +08:00
teardown ( state )
return failures
2006-12-15 14:06:52 +08:00
2010-10-09 22:44:54 +08:00
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 ( ) ]
print ' ***** Bisecting test suite: ' , ' ' . join ( test_labels )
# Make sure the bisection point isn't in the test list
# Also remove tests that need to be run in specific combinations
for label in [ bisection_label , ' model_inheritance_same_model_name ' ] :
try :
test_labels . remove ( label )
except ValueError :
pass
2011-01-27 08:00:20 +08:00
subprocess_args = [ sys . executable , __file__ , ' --settings= %s ' % options . settings ]
2010-10-09 22:44:54 +08:00
if options . failfast :
subprocess_args . append ( ' --failfast ' )
if options . verbosity :
subprocess_args . append ( ' --verbosity= %s ' % options . verbosity )
if not options . interactive :
subprocess_args . append ( ' --noinput ' )
iteration = 1
while len ( test_labels ) > 1 :
midpoint = len ( test_labels ) / 2
test_labels_a = test_labels [ : midpoint ] + [ bisection_label ]
test_labels_b = test_labels [ midpoint : ] + [ bisection_label ]
print ' ***** Pass %d a: Running the first half of the test suite ' % iteration
print ' ***** Test labels: ' , ' ' . join ( test_labels_a )
failures_a = subprocess . call ( subprocess_args + test_labels_a )
print ' ***** Pass %d b: Running the second half of the test suite ' % iteration
print ' ***** Test labels: ' , ' ' . join ( test_labels_b )
print
failures_b = subprocess . call ( subprocess_args + test_labels_b )
if failures_a and not failures_b :
print " ***** Problem found in first half. Bisecting again... "
iteration = iteration + 1
test_labels = test_labels_a [ : - 1 ]
elif failures_b and not failures_a :
print " ***** Problem found in second half. Bisecting again... "
iteration = iteration + 1
test_labels = test_labels_b [ : - 1 ]
elif failures_a and failures_b :
print " ***** Multiple sources of failure found "
break
else :
print " ***** No source of failure found... try pair execution (--pair) "
break
if len ( test_labels ) == 1 :
print " ***** Source of error: " , test_labels [ 0 ]
teardown ( state )
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 ( ) ]
print ' ***** Trying paired execution '
2010-10-11 20:55:17 +08:00
# Make sure the constant member of the pair isn't in the test list
2010-10-09 22:44:54 +08:00
# Also remove tests that need to be run in specific combinations
for label in [ paired_test , ' model_inheritance_same_model_name ' ] :
try :
test_labels . remove ( label )
except ValueError :
pass
2011-01-27 08:00:20 +08:00
subprocess_args = [ sys . executable , __file__ , ' --settings= %s ' % options . settings ]
2010-10-09 22:44:54 +08:00
if options . failfast :
subprocess_args . append ( ' --failfast ' )
if options . verbosity :
subprocess_args . append ( ' --verbosity= %s ' % options . verbosity )
if not options . interactive :
subprocess_args . append ( ' --noinput ' )
for i , label in enumerate ( test_labels ) :
print ' ***** %d of %d : Check test pairing with %s ' % ( i + 1 , len ( test_labels ) , label )
failures = subprocess . call ( subprocess_args + [ label , paired_test ] )
if failures :
print ' ***** Found problem pair with ' , label
return
print ' ***** No problem pair found '
teardown ( state )
2006-12-15 14:06:52 +08:00
2005-07-29 23:15:40 +08:00
if __name__ == " __main__ " :
from optparse import OptionParser
2011-01-21 23:55:27 +08:00
usage = " % prog [options] [module module module ...] "
2006-02-19 04:08:20 +08:00
parser = OptionParser ( usage = usage )
2010-08-07 14:58:14 +08:00
parser . add_option ( ' -v ' , ' --verbosity ' , action = ' store ' , dest = ' verbosity ' , default = ' 1 ' ,
type = ' choice ' , choices = [ ' 0 ' , ' 1 ' , ' 2 ' , ' 3 ' ] ,
2006-12-15 14:06:52 +08:00
help = ' Verbosity level; 0=minimal output, 1=normal output, 2=all output ' )
2007-07-23 20:14:32 +08:00
parser . add_option ( ' --noinput ' , action = ' store_false ' , dest = ' interactive ' , default = True ,
help = ' Tells Django to NOT prompt the user for input of any kind. ' )
2009-12-14 00:24:36 +08:00
parser . add_option ( ' --failfast ' , action = ' store_true ' , dest = ' failfast ' , default = False ,
help = ' Tells Django to stop running the test suite after first failed test. ' )
2005-08-10 23:36:16 +08:00
parser . add_option ( ' --settings ' ,
2006-01-02 02:37:33 +08:00
help = ' Python path to settings module, e.g. " myproject.settings " . If this isn \' t provided, the DJANGO_SETTINGS_MODULE environment variable will be used. ' )
2010-10-09 22:44:54 +08:00
parser . add_option ( ' --bisect ' , action = ' store ' , dest = ' bisect ' , default = None ,
help = " Bisect the test suite to discover a test that causes a test failure when combined with the named test. " )
parser . add_option ( ' --pair ' , action = ' store ' , dest = ' pair ' , default = None ,
help = " Run the test suite in pairs with the named test to find problem pairs. " )
2005-07-29 23:15:40 +08:00
options , args = parser . parse_args ( )
2005-08-10 23:36:16 +08:00
if options . settings :
os . environ [ ' DJANGO_SETTINGS_MODULE ' ] = options . settings
2007-02-27 06:34:56 +08:00
elif " DJANGO_SETTINGS_MODULE " not in os . environ :
parser . error ( " DJANGO_SETTINGS_MODULE is not set in the environment. "
" Set it or use --settings. " )
2011-01-27 08:00:20 +08:00
else :
options . settings = os . environ [ ' DJANGO_SETTINGS_MODULE ' ]
2010-10-09 22:44:54 +08:00
if options . bisect :
bisect_tests ( options . bisect , options , args )
elif options . pair :
paired_tests ( options . pair , options , args )
else :
failures = django_tests ( int ( options . verbosity ) , options . interactive , options . failfast , args )
if failures :
sys . exit ( bool ( failures ) )