Fixed #28520 -- Added --start-at/--start-after options to runtests.py.

This commit is contained in:
Hasan Ramezani 2019-05-25 20:55:30 +02:00 committed by Mariusz Felisiak
parent ef7e0ae53b
commit e2de49ec2e
3 changed files with 66 additions and 9 deletions

View File

@ -213,6 +213,24 @@ Going beyond that, you can specify an individual test method like this:
$ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects $ ./runtests.py --settings=path.to.settings i18n.tests.TranslationTests.test_lazy_objects
You can run tests starting at a specified top-level module with ``--start-at``
option. For example:
.. console::
$ ./runtests.py --start-at=wsgi
You can also run tests starting after a specified top-level module with
``--start-after`` option. For example:
.. console::
$ ./runtests.py --start-after=wsgi
Note that the ``--reverse`` option doesn't impact on ``--start-at`` or
``--start-after`` options. Moreover these options cannot be used with test
labels.
Running the Selenium tests Running the Selenium tests
-------------------------- --------------------------

View File

@ -267,6 +267,9 @@ Tests
* Django test runner now supports headless mode for selenium tests on supported * Django test runner now supports headless mode for selenium tests on supported
browsers. Add the ``--headless`` option to enable this mode. browsers. Add the ``--headless`` option to enable this mode.
* Django test runner now supports ``--start-at`` and ``--start-after`` options
to run tests starting from a specific top-level module.
URLs URLs
~~~~ ~~~~

View File

@ -116,7 +116,7 @@ def get_installed():
return [app_config.name for app_config in apps.get_app_configs()] return [app_config.name for app_config in apps.get_app_configs()]
def setup(verbosity, test_labels, parallel): def setup(verbosity, test_labels, parallel, start_at, start_after):
# Reduce the given test labels to just the app module path. # Reduce the given test labels to just the app module path.
test_labels_set = set() test_labels_set = set()
for label in test_labels: for label in test_labels:
@ -202,12 +202,21 @@ def setup(verbosity, test_labels, parallel):
# Load all the test model apps. # Load all the test model apps.
test_modules = get_test_modules() test_modules = get_test_modules()
found_start = not (start_at or start_after)
installed_app_names = set(get_installed()) installed_app_names = set(get_installed())
for modpath, module_name in test_modules: for modpath, module_name in test_modules:
if modpath: if modpath:
module_label = modpath + '.' + module_name module_label = modpath + '.' + module_name
else: else:
module_label = module_name module_label = module_name
if not found_start:
if start_at and _module_match_label(module_label, start_at):
found_start = True
elif start_after and _module_match_label(module_label, start_after):
found_start = True
continue
else:
continue
# if the module (or an ancestor) 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 # no modules were named (i.e., run all), import
# this module and add it to INSTALLED_APPS. # this module and add it to INSTALLED_APPS.
@ -275,8 +284,8 @@ class ActionSelenium(argparse.Action):
def django_tests(verbosity, interactive, failfast, keepdb, reverse, def django_tests(verbosity, interactive, failfast, keepdb, reverse,
test_labels, debug_sql, parallel, tags, exclude_tags, test_labels, debug_sql, parallel, tags, exclude_tags,
test_name_patterns): test_name_patterns, start_at, start_after):
state = setup(verbosity, test_labels, parallel) state = setup(verbosity, test_labels, parallel, start_at, start_after)
extra_tests = [] extra_tests = []
# Run the test suite, including the extra validation tests. # Run the test suite, including the extra validation tests.
@ -321,8 +330,8 @@ def get_subprocess_args(options):
return subprocess_args return subprocess_args
def bisect_tests(bisection_label, options, test_labels, parallel): def bisect_tests(bisection_label, options, test_labels, parallel, start_at, start_after):
state = setup(options.verbosity, test_labels, parallel) state = setup(options.verbosity, test_labels, parallel, start_at, start_after)
test_labels = test_labels or get_installed() test_labels = test_labels or get_installed()
@ -372,8 +381,8 @@ def bisect_tests(bisection_label, options, test_labels, parallel):
teardown(state) teardown(state)
def paired_tests(paired_test, options, test_labels, parallel): def paired_tests(paired_test, options, test_labels, parallel, start_at, start_after):
state = setup(options.verbosity, test_labels, parallel) state = setup(options.verbosity, test_labels, parallel, start_at, start_after)
test_labels = test_labels or get_installed() test_labels = test_labels or get_installed()
@ -478,6 +487,14 @@ if __name__ == "__main__":
'--exclude-tag', dest='exclude_tags', action='append', '--exclude-tag', dest='exclude_tags', action='append',
help='Do not run tests with the specified tag. Can be used multiple times.', help='Do not run tests with the specified tag. Can be used multiple times.',
) )
parser.add_argument(
'--start-after', dest='start_after',
help='Run tests starting after the specified top-level module.',
)
parser.add_argument(
'--start-at', dest='start_at',
help='Run tests starting at the specified top-level module.',
)
if PY37: if PY37:
parser.add_argument( parser.add_argument(
'-k', dest='test_name_patterns', action='append', '-k', dest='test_name_patterns', action='append',
@ -498,6 +515,18 @@ if __name__ == "__main__":
# Allow including a trailing slash on app_labels for tab completion convenience # Allow including a trailing slash on app_labels for tab completion convenience
options.modules = [os.path.normpath(labels) for labels in options.modules] options.modules = [os.path.normpath(labels) for labels in options.modules]
mutually_exclusive_options = [options.start_at, options.start_after, options.modules]
enabled_module_options = [bool(option) for option in mutually_exclusive_options].count(True)
if enabled_module_options > 1:
print('Aborting: --start-at, --start-after, and test labels are mutually exclusive.')
sys.exit(1)
for opt_name in ['start_at', 'start_after']:
opt_val = getattr(options, opt_name)
if opt_val:
if '.' in opt_val:
print('Aborting: --%s must be a top-level module.' % opt_name.replace('_', '-'))
sys.exit(1)
setattr(options, opt_name, os.path.normpath(opt_val))
if options.settings: if options.settings:
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
else: else:
@ -516,9 +545,15 @@ if __name__ == "__main__":
SeleniumTestCaseBase.browsers = options.selenium SeleniumTestCaseBase.browsers = options.selenium
if options.bisect: if options.bisect:
bisect_tests(options.bisect, options, options.modules, options.parallel) bisect_tests(
options.bisect, options, options.modules, options.parallel,
options.start_at, options.start_after,
)
elif options.pair: elif options.pair:
paired_tests(options.pair, options, options.modules, options.parallel) paired_tests(
options.pair, options, options.modules, options.parallel,
options.start_at, options.start_after,
)
else: else:
failures = django_tests( failures = django_tests(
options.verbosity, options.interactive, options.failfast, options.verbosity, options.interactive, options.failfast,
@ -526,6 +561,7 @@ if __name__ == "__main__":
options.debug_sql, options.parallel, options.tags, options.debug_sql, options.parallel, options.tags,
options.exclude_tags, options.exclude_tags,
getattr(options, 'test_name_patterns', None), getattr(options, 'test_name_patterns', None),
options.start_at, options.start_after,
) )
if failures: if failures:
sys.exit(1) sys.exit(1)