usrbinenv python ignore informational warnings from queryset explain make deprecation warnings errors to ensure no usage of deprecated features make resource and runtime warning errors to ensure no usage of error prone patterns reduce garbage collection frequency to improve performance since cpython uses refcounting garbage collection only collects objects with cyclic references which are a minority so the garbage collection threshold can be larger than the default threshold of 700 allocations deallocations without much increase in memory usage create a specific subdirectory for the duration of the test suite set the tmpdir environment variable in addition to tempfile tempdir so that children processes inherit it removing the temporary tmpdir add variables enabling coverage to trace code in subprocesses this is a dict mapping runtestsdir subdirectory to subdirectories of that directory to skip when searching for test modules need to add the associated contrib app to installedapps in some cases to avoid runtimeerror model class x doesn t declare an explicit applabel and isn t in an application in installedapps scan the tests directory and yield the names of all test modules the yielded names have either one dotted part like testrunner or in the case of gis tests two dotted parts like gistests gdaltests gis tests are in nested apps return the toplevel module part for a test label path pathlabel if lenpath parts 1 interpret the label as a dotted module name return label split 0 otherwise interpret the label as a path check existence first to provide a better error message than relativeto if it doesn t exist if not path exists raise runtimeerrorftest label path label does not exist path path resolve relpath path relativetoruntestsdir return relpath parts0 def getfilteredtestmodulesstartat startafter gisenabled testlabelsnone if testlabels is none testlabels reduce each test label to just the toplevel module part labelmodules set for label in testlabels testmodule getlabelmodulelabel labelmodules addtestmodule it would be nice to put this validation earlier but it must come after django setup so that connection features gisenabled can be accessed if gistests in labelmodules and not gisenabled printaborting a gis database backend is required to run gistests sys exit1 def modulematchlabelmodulename label exact or ancestor match return modulename label or modulename startswithlabel startlabel startat or startafter for testmodule in gettestmodulesgisenabled if startlabel if not modulematchlabeltestmodule startlabel continue startlabel if not startat assert startafter skip the current one before starting continue if the module or an ancestor was named on the command line or no modules were named i e run all include the test module if not testlabels or any modulematchlabeltestmodule labelmodule for labelmodule in labelmodules yield testmodule def setupcollecttestsstartat startafter testlabelsnone state installedapps settings installedapps rooturlconf getattrsettings rooturlconf templates settings templates languagecode settings languagecode staticurl settings staticurl staticroot settings staticroot middleware settings middleware redirect some settings for the duration of these tests settings installedapps alwaysinstalledapps settings rooturlconf urls settings staticurl static settings staticroot os path jointmpdir static settings templates backend django template backends django djangotemplates dirs templatedir appdirs true options contextprocessors django template contextprocessors debug django template contextprocessors request django contrib auth contextprocessors auth django contrib messages contextprocessors messages settings languagecode en settings siteid 1 settings middleware alwaysmiddleware settings migrationmodules this lets us skip creating migrations for the test models as many of them depend on one of the following contrib applications auth none contenttypes none sessions none logconfig copy deepcopydefaultlogging filter out nonerror logging so we don t have to capture it in lots of tests logconfigloggersdjangolevel error settings logging logconfig settings silencedsystemchecks fields w342 foreignkeyuniquetrue onetoonefield load all the alwaysinstalledapps django setup this flag must be evaluated after django setup because otherwise it can raise appregistrynotready when running gistests in isolation on some backends e g postgis gisenabled connection features gisenabled testmodules list getfilteredtestmodules startat startafter gisenabled testlabelstestlabels return testmodules state def teardowncollecttestsstate restore the old settings for key value in state items setattrsettings key value def getinstalled return appconfig name for appconfig in apps getappconfigs this function should be called only after calling django setup since it calls connection features gisenabled def getappstoinstalltestmodules for testmodule in testmodules if testmodule in contribteststoapps yield from contribteststoappstestmodule yield testmodule add contrib gis to installedapps if needed rather than requiring overridesettingsinstalledapps on all test cases if connection features gisenabled yield django contrib gis def setupruntestsverbosity startat startafter testlabelsnone testmodules state setupcollecttests startat startafter testlabelstestlabels installedapps setgetinstalled for app in getappstoinstalltestmodules if app in installedapps continue if verbosity 2 printfimporting application app settings installedapps appendapp installedapps addapp apps setinstalledappssettings installedapps force declaring availableapps in transactiontestcase for faster tests def noavailableappsself raise exception please define availableapps in transactiontestcase and its subclasses transactiontestcase availableapps propertynoavailableapps testcase availableapps none set an environment variable that other code may consult to see if django s own test suite is running os environrunningdjangostestsuite true testlabels testlabels or testmodules return testlabels state def teardownruntestsstate teardowncollecttestsstate discard the multiprocessing util finalizer that tries to remove a temporary directory that s already removed by this script s atexit registershutil rmtree tmpdir handler prevents filenotfounderror at the end of a test run 27890 from multiprocessing util import finalizerregistry finalizerregistry pop100 0 none del os environrunningdjangostestsuite class actionseleniumargparse action def callself parser namespace values optionstringnone try import selenium noqa except importerror as e raise improperlyconfiguredferror loading selenium module e browsers values split for browser in browsers try seleniumtestcasebase importwebdriverbrowser except importerror raise argparse argumenterror self selenium browser specification s is not valid browser setattrnamespace self dest browsers def djangotests verbosity interactive failfast keepdb reverse testlabels debugsql parallel tags excludetags testnamepatterns startat startafter pdb buffer timing shuffle durationsnone if parallel in 0 auto maxparallel getmaxtestprocesses else maxparallel parallel if verbosity 1 msg testing against django installed in s os path dirname django file if maxparallel 1 msg with up to d processes maxparallel printmsg processsetupargs verbosity startat startafter testlabels testlabels state setupruntestsprocesssetupargs run the test suite including the extra validation tests if not hasattrsettings testrunner settings testrunner django test runner discoverrunner if parallel in 0 auto this doesn t work before django setup on some databases if allconn features canclonedatabases for conn in connections all parallel maxparallel else parallel 1 testrunner getrunnersettings testrunner paralleltestsuite processsetup setupruntests testrunner paralleltestsuite processsetupargs processsetupargs testrunner testrunner verbosityverbosity interactiveinteractive failfastfailfast keepdbkeepdb reversereverse debugsqldebugsql parallelparallel tagstags excludetagsexcludetags testnamepatternstestnamepatterns pdbpdb bufferbuffer timingtiming shuffleshuffle durationsdurations failures testrunner runteststestlabels teardownruntestsstate return failures def collecttestmodulesstartat startafter testmodules state setupcollecttestsstartat startafter teardowncollecttestsstate return testmodules def getsubprocessargsoptions subprocessargs sys executable file settingss options settings if options failfast subprocessargs appendfailfast if options verbosity subprocessargs appendverbositys options verbosity if not options interactive subprocessargs appendnoinput if options tags subprocessargs appendtags options tags if options excludetags subprocessargs appendexcludetags options excludetags if options shuffle is not false if options shuffle is none subprocessargs appendshuffle else subprocessargs appendshuffles options shuffle return subprocessargs def bisecttestsbisectionlabel options testlabels startat startafter if not testlabels testlabels collecttestmodulesstartat startafter print bisecting test suite s jointestlabels 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 bisectionlabel modelinheritancesamemodelname try testlabels removelabel except valueerror pass subprocessargs getsubprocessargsoptions iteration 1 while lentestlabels 1 midpoint lentestlabels 2 testlabelsa testlabels midpoint bisectionlabel testlabelsb testlabelsmidpoint bisectionlabel print pass da running the first half of the test suite iteration print test labels s jointestlabelsa failuresa subprocess runsubprocessargs testlabelsa print pass db running the second half of the test suite iteration print test labels s jointestlabelsb print failuresb subprocess runsubprocessargs testlabelsb if failuresa returncode and not failuresb returncode print problem found in first half bisecting again iteration 1 testlabels testlabelsa 1 elif failuresb returncode and not failuresa returncode print problem found in second half bisecting again iteration 1 testlabels testlabelsb 1 elif failuresa returncode and failuresb returncode print multiple sources of failure found break else print no source of failure found try pair execution pair break if lentestlabels 1 print source of error s testlabels0 def pairedtestspairedtest options testlabels startat startafter if not testlabels testlabels collecttestmodulesstartat startafter print trying paired execution make sure the constant member of the pair isn t in the test list also remove tests that need to be run in specific combinations for label in pairedtest modelinheritancesamemodelname try testlabels removelabel except valueerror pass subprocessargs getsubprocessargsoptions for i label in enumeratetestlabels print d of d check test pairing with s i 1 lentestlabels label failures subprocess callsubprocessargs label pairedtest if failures print found problem pair with s label return print no problem pair found if name main parser argparse argumentparserdescriptionrun the django test suite parser addargument modules nargs metavarmodule help optional paths to test modules e g i18n or i18n tests translationtests testlazyobjects parser addargument v verbosity default1 typeint choices0 1 2 3 helpverbosity level 0minimal output 1normal output 2all output parser addargument noinput actionstorefalse destinteractive helptells django to not prompt the user for input of any kind parser addargument failfast actionstoretrue helptells django to stop running the test suite after first failed test parser addargument keepdb actionstoretrue helptells django to preserve the test database between runs parser addargument settings help python path to settings module e g myproject settings if this isn t provided either the djangosettingsmodule environment variable or testsqlite will be used parser addargument bisect helpbisect the test suite to discover a test that causes a test failure when combined with the named test parser addargument pair helprun the test suite in pairs with the named test to find problem pairs parser addargument shuffle nargs defaultfalse typeint metavarseed help shuffle the order of test cases to help check that tests are properly isolated parser addargument reverse actionstoretrue helpsort test suites and test cases in opposite order to debug test side effects not apparent with normal execution lineup parser addargument selenium actionactionselenium metavarbrowsers helpa commaseparated list of browsers to run the selenium tests against parser addargument screenshots actionstoretrue helptake screenshots during selenium tests to capture the user interface parser addargument headless actionstoretrue helprun selenium tests in headless mode if the browser supports the option parser addargument seleniumhub helpa url for a selenium hub instance to use in combination with selenium parser addargument externalhost defaultsocket gethostname help the external host that can be reached by the selenium hub instance when running selenium tests via selenium hub parser addargument debugsql actionstoretrue helpturn on the sql query logger within tests 0 is converted to auto or 1 later on depending on a method used by multiprocessing to start subprocesses and on the backend support for cloning databases parser addargument parallel nargs constauto default0 typeparalleltype metavarn help run tests using up to n parallel processes use the value auto to run one test process for each processor core parser addargument tag desttags actionappend helprun only tests with the specified tags can be used multiple times parser addargument excludetag destexcludetags actionappend helpdo not run tests with the specified tag can be used multiple times parser addargument startafter deststartafter helprun tests starting after the specified toplevel module parser addargument startat deststartat helprun tests starting at the specified toplevel module parser addargument pdb actionstoretrue helpruns the pdb debugger on error or failure parser addargument b buffer actionstoretrue helpdiscard output of passing tests parser addargument timing actionstoretrue helpoutput timings including database set up and total run time parser addargument k desttestnamepatterns actionappend help only run test methods and classes matching test name pattern same as unittest k option can be used multiple times if py312 parser addargument durations destdurations typeint defaultnone metavarn helpshow the n slowest test cases n0 for all options parser parseargs usingseleniumhub options selenium and options seleniumhub if options seleniumhub and not options selenium parser error seleniumhub and externalhost require selenium to be used if usingseleniumhub and not options externalhost parser errorseleniumhub and externalhost must be used together if options screenshots and not options selenium parser errorscreenshots require selenium to be used if options screenshots and options tags parser errorscreenshots and tag are mutually exclusive allow including a trailing slash on applabels for tab completion convenience options modules os path normpathlabels for labels in options modules mutuallyexclusiveoptions options startat options startafter options modules enabledmoduleoptions booloption for option in mutuallyexclusiveoptions counttrue if enabledmoduleoptions 1 print aborting startat startafter and test labels are mutually exclusive sys exit1 for optname in startat startafter optval getattroptions optname if optval if in optval print aborting s must be a toplevel module optname replace sys exit1 setattroptions optname os path normpathoptval if options settings os environdjangosettingsmodule options settings else os environ setdefaultdjangosettingsmodule testsqlite options settings os environdjangosettingsmodule if options selenium if multiprocessing getstartmethod spawn and options parallel 1 parser error you cannot use selenium with parallel tests on this system pass parallel1 to use selenium if not options tags options tags selenium elif selenium not in options tags options tags appendselenium if options seleniumhub seleniumtestcasebase seleniumhub options seleniumhub seleniumtestcasebase externalhost options externalhost seleniumtestcasebase headless options headless seleniumtestcasebase browsers options selenium if options screenshots options tags screenshot seleniumtestcase screenshots options screenshots if options bisect bisecttests options bisect options options modules options startat options startafter elif options pair pairedtests options pair options options modules options startat options startafter else timekeeper timekeeper if options timing else nulltimekeeper with timekeeper timedtotal run failures djangotests options verbosity options interactive options failfast options keepdb options reverse options modules options debugsql options parallel options tags options excludetags options testnamepatterns options startat options startafter options pdb options buffer options timing options shuffle getattroptions durations none timekeeper printresults if failures sys exit1 usr bin env python ignore informational warnings from queryset explain make deprecation warnings errors to ensure no usage of deprecated features make resource and runtime warning errors to ensure no usage of error prone patterns reduce garbage collection frequency to improve performance since cpython uses refcounting garbage collection only collects objects with cyclic references which are a minority so the garbage collection threshold can be larger than the default threshold of 700 allocations deallocations without much increase in memory usage create a specific subdirectory for the duration of the test suite set the tmpdir environment variable in addition to tempfile tempdir so that children processes inherit it removing the temporary tmpdir add variables enabling coverage to trace code in subprocesses this is a dict mapping runtests_dir subdirectory to subdirectories of that directory to skip when searching for test modules need to add the associated contrib app to installed_apps in some cases to avoid runtimeerror model class x doesn t declare an explicit app_label and isn t in an application in installed_apps scan the tests directory and yield the names of all test modules the yielded names have either one dotted part like test_runner or in the case of gis tests two dotted parts like gis_tests gdal_tests gis tests are in nested apps return the top level module part for a test label interpret the label as a dotted module name otherwise interpret the label as a path check existence first to provide a better error message than relative_to if it doesn t exist reduce each test label to just the top level module part it would be nice to put this validation earlier but it must come after django setup so that connection features gis_enabled can be accessed exact or ancestor match skip the current one before starting if the module or an ancestor was named on the command line or no modules were named i e run all include the test module redirect some settings for the duration of these tests this lets us skip creating migrations for the test models as many of them depend on one of the following contrib applications filter out non error logging so we don t have to capture it in lots of tests foreignkey unique true onetoonefield load all the always_installed_apps this flag must be evaluated after django setup because otherwise it can raise appregistrynotready when running gis_tests in isolation on some backends e g postgis restore the old settings this function should be called only after calling django setup since it calls connection features gis_enabled add contrib gis to installed_apps if needed rather than requiring override_settings installed_apps on all test cases force declaring available_apps in transactiontestcase for faster tests set an environment variable that other code may consult to see if django s own test suite is running discard the multiprocessing util finalizer that tries to remove a temporary directory that s already removed by this script s atexit register shutil rmtree tmpdir handler prevents filenotfounderror at the end of a test run 27890 validate the comma separated list of requested browsers noqa run the test suite including the extra validation tests this doesn t work before django setup on some databases make sure the bisection point isn t in the test list also remove tests that need to be run in specific combinations make sure the constant member of the pair isn t in the test list also remove tests that need to be run in specific combinations 0 is converted to auto or 1 later on depending on a method used by multiprocessing to start subprocesses and on the backend support for cloning databases allow including a trailing slash on app_labels for tab completion convenience
import argparse
import atexit
import copy
import gc
import multiprocessing
import os
import shutil
import socket
import subprocess
import sys
import tempfile
import warnings
from pathlib import Path

try:
    import django
except ImportError as e:
    raise RuntimeError(
        "Django module not found, reference tests/README.rst for instructions."
    ) from e
else:
    from django.apps import apps
    from django.conf import settings
    from django.core.exceptions import ImproperlyConfigured
    from django.db import connection, connections
    from django.test import TestCase, TransactionTestCase
    from django.test.runner import get_max_test_processes, parallel_type
    from django.test.selenium import SeleniumTestCase, SeleniumTestCaseBase
    from django.test.utils import NullTimeKeeper, TimeKeeper, get_runner
    from django.utils.deprecation import RemovedInDjango60Warning
    from django.utils.log import DEFAULT_LOGGING
    from django.utils.version import PY312, PYPY

try:
    import MySQLdb
except ImportError:
    pass
else:
    
    warnings.filterwarnings("ignore", r"\(1003, *", category=MySQLdb.Warning)


warnings.simplefilter("error", RemovedInDjango60Warning)


warnings.simplefilter("error", ResourceWarning)
warnings.simplefilter("error", RuntimeWarning)






if not PYPY:
    gc.set_threshold(100_000)

RUNTESTS_DIR = os.path.abspath(os.path.dirname(__file__))

TEMPLATE_DIR = os.path.join(RUNTESTS_DIR, "templates")


TMPDIR = tempfile.mkdtemp(prefix="django_")


tempfile.tempdir = os.environ["TMPDIR"] = TMPDIR


atexit.register(shutil.rmtree, TMPDIR)


os.environ["RUNTESTS_DIR"] = RUNTESTS_DIR
os.environ["COVERAGE_PROCESS_START"] = os.path.join(RUNTESTS_DIR, ".coveragerc")




SUBDIRS_TO_SKIP = {
    "": {"import_error_package", "test_runner_apps"},
    "gis_tests": {"data"},
}

ALWAYS_INSTALLED_APPS = [
    "django.contrib.contenttypes",
    "django.contrib.auth",
    "django.contrib.sites",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.admin.apps.SimpleAdminConfig",
    "django.contrib.staticfiles",
]

ALWAYS_MIDDLEWARE = [
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
]




CONTRIB_TESTS_TO_APPS = {
    "deprecation": ["django.contrib.flatpages", "django.contrib.redirects"],
    "flatpages_tests": ["django.contrib.flatpages"],
    "redirects_tests": ["django.contrib.redirects"],
}


def get_test_modules(gis_enabled):
    
    discovery_dirs = [""]
    if gis_enabled:
        
        discovery_dirs.append("gis_tests")
    else:
        SUBDIRS_TO_SKIP[""].add("gis_tests")

    for dirname in discovery_dirs:
        dirpath = os.path.join(RUNTESTS_DIR, dirname)
        subdirs_to_skip = SUBDIRS_TO_SKIP[dirname]
        with os.scandir(dirpath) as entries:
            for f in entries:
                if (
                    "." in f.name
                    or os.path.basename(f.name) in subdirs_to_skip
                    or f.is_file()
                    or not os.path.exists(os.path.join(f.path, "__init__.py"))
                ):
                    continue
                test_module = f.name
                if dirname:
                    test_module = dirname + "." + test_module
                yield test_module


def get_label_module(label):
    
    path = Path(label)
    if len(path.parts) == 1:
        
        return label.split(".")[0]

    
    
    if not path.exists():
        raise RuntimeError(f"Test label path {label} does not exist")
    path = path.resolve()
    rel_path = path.relative_to(RUNTESTS_DIR)
    return rel_path.parts[0]


def get_filtered_test_modules(start_at, start_after, gis_enabled, test_labels=None):
    if test_labels is None:
        test_labels = []
    
    label_modules = set()
    for label in test_labels:
        test_module = get_label_module(label)
        label_modules.add(test_module)

    
    
    if "gis_tests" in label_modules and not gis_enabled:
        print("Aborting: A GIS database backend is required to run gis_tests.")
        sys.exit(1)

    def _module_match_label(module_name, label):
        
        return module_name == label or module_name.startswith(label + ".")

    start_label = start_at or start_after
    for test_module in get_test_modules(gis_enabled):
        if start_label:
            if not _module_match_label(test_module, start_label):
                continue
            start_label = ""
            if not start_at:
                assert start_after
                
                continue
        
        
        if not test_labels or any(
            _module_match_label(test_module, label_module)
            for label_module in label_modules
        ):
            yield test_module


def setup_collect_tests(start_at, start_after, test_labels=None):
    state = {
        "INSTALLED_APPS": settings.INSTALLED_APPS,
        "ROOT_URLCONF": getattr(settings, "ROOT_URLCONF", ""),
        "TEMPLATES": settings.TEMPLATES,
        "LANGUAGE_CODE": settings.LANGUAGE_CODE,
        "STATIC_URL": settings.STATIC_URL,
        "STATIC_ROOT": settings.STATIC_ROOT,
        "MIDDLEWARE": settings.MIDDLEWARE,
    }

    
    settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
    settings.ROOT_URLCONF = "urls"
    settings.STATIC_URL = "static/"
    settings.STATIC_ROOT = os.path.join(TMPDIR, "static")
    settings.TEMPLATES = [
        {
            "BACKEND": "django.template.backends.django.DjangoTemplates",
            "DIRS": [TEMPLATE_DIR],
            "APP_DIRS": True,
            "OPTIONS": {
                "context_processors": [
                    "django.template.context_processors.debug",
                    "django.template.context_processors.request",
                    "django.contrib.auth.context_processors.auth",
                    "django.contrib.messages.context_processors.messages",
                ],
            },
        }
    ]
    settings.LANGUAGE_CODE = "en"
    settings.SITE_ID = 1
    settings.MIDDLEWARE = ALWAYS_MIDDLEWARE
    settings.MIGRATION_MODULES = {
        
        
        "auth": None,
        "contenttypes": None,
        "sessions": None,
    }
    log_config = copy.deepcopy(DEFAULT_LOGGING)
    
    
    log_config["loggers"]["django"]["level"] = "ERROR"
    settings.LOGGING = log_config
    settings.SILENCED_SYSTEM_CHECKS = [
        "fields.W342",  
    ]

    
    django.setup()

    
    
    
    gis_enabled = connection.features.gis_enabled

    test_modules = list(
        get_filtered_test_modules(
            start_at,
            start_after,
            gis_enabled,
            test_labels=test_labels,
        )
    )
    return test_modules, state


def teardown_collect_tests(state):
    
    for key, value in state.items():
        setattr(settings, key, value)


def get_installed():
    return [app_config.name for app_config in apps.get_app_configs()]




def get_apps_to_install(test_modules):
    for test_module in test_modules:
        if test_module in CONTRIB_TESTS_TO_APPS:
            yield from CONTRIB_TESTS_TO_APPS[test_module]
        yield test_module

    
    
    if connection.features.gis_enabled:
        yield "django.contrib.gis"


def setup_run_tests(verbosity, start_at, start_after, test_labels=None):
    test_modules, state = setup_collect_tests(
        start_at, start_after, test_labels=test_labels
    )

    installed_apps = set(get_installed())
    for app in get_apps_to_install(test_modules):
        if app in installed_apps:
            continue
        if verbosity >= 2:
            print(f"Importing application {app}")
        settings.INSTALLED_APPS.append(app)
        installed_apps.add(app)

    apps.set_installed_apps(settings.INSTALLED_APPS)

    
    def no_available_apps(self):
        raise Exception(
            "Please define available_apps in TransactionTestCase and its subclasses."
        )

    TransactionTestCase.available_apps = property(no_available_apps)
    TestCase.available_apps = None

    
    
    os.environ["RUNNING_DJANGOS_TEST_SUITE"] = "true"

    test_labels = test_labels or test_modules
    return test_labels, state


def teardown_run_tests(state):
    teardown_collect_tests(state)
    
    
    
    
    from multiprocessing.util import _finalizer_registry

    _finalizer_registry.pop((-100, 0), None)
    del os.environ["RUNNING_DJANGOS_TEST_SUITE"]


class ActionSelenium(argparse.Action):
    

    def __call__(self, parser, namespace, values, option_string=None):
        try:
            import selenium  
        except ImportError as e:
            raise ImproperlyConfigured(f"Error loading selenium module: {e}")
        browsers = values.split(",")
        for browser in browsers:
            try:
                SeleniumTestCaseBase.import_webdriver(browser)
            except ImportError:
                raise argparse.ArgumentError(
                    self, "Selenium browser specification '%s' is not valid." % browser
                )
        setattr(namespace, self.dest, browsers)


def django_tests(
    verbosity,
    interactive,
    failfast,
    keepdb,
    reverse,
    test_labels,
    debug_sql,
    parallel,
    tags,
    exclude_tags,
    test_name_patterns,
    start_at,
    start_after,
    pdb,
    buffer,
    timing,
    shuffle,
    durations=None,
):
    if parallel in {0, "auto"}:
        max_parallel = get_max_test_processes()
    else:
        max_parallel = parallel

    if verbosity >= 1:
        msg = "Testing against Django installed in '%s'" % os.path.dirname(
            django.__file__
        )
        if max_parallel > 1:
            msg += " with up to %d processes" % max_parallel
        print(msg)

    process_setup_args = (verbosity, start_at, start_after, test_labels)
    test_labels, state = setup_run_tests(*process_setup_args)
    
    if not hasattr(settings, "TEST_RUNNER"):
        settings.TEST_RUNNER = "django.test.runner.DiscoverRunner"

    if parallel in {0, "auto"}:
        
        if all(conn.features.can_clone_databases for conn in connections.all()):
            parallel = max_parallel
        else:
            parallel = 1

    TestRunner = get_runner(settings)
    TestRunner.parallel_test_suite.process_setup = setup_run_tests
    TestRunner.parallel_test_suite.process_setup_args = process_setup_args
    test_runner = TestRunner(
        verbosity=verbosity,
        interactive=interactive,
        failfast=failfast,
        keepdb=keepdb,
        reverse=reverse,
        debug_sql=debug_sql,
        parallel=parallel,
        tags=tags,
        exclude_tags=exclude_tags,
        test_name_patterns=test_name_patterns,
        pdb=pdb,
        buffer=buffer,
        timing=timing,
        shuffle=shuffle,
        durations=durations,
    )
    failures = test_runner.run_tests(test_labels)
    teardown_run_tests(state)
    return failures


def collect_test_modules(start_at, start_after):
    test_modules, state = setup_collect_tests(start_at, start_after)
    teardown_collect_tests(state)
    return test_modules


def get_subprocess_args(options):
    subprocess_args = [sys.executable, __file__, "--settings=%s" % options.settings]
    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")
    if options.tags:
        subprocess_args.append("--tag=%s" % options.tags)
    if options.exclude_tags:
        subprocess_args.append("--exclude_tag=%s" % options.exclude_tags)
    if options.shuffle is not False:
        if options.shuffle is None:
            subprocess_args.append("--shuffle")
        else:
            subprocess_args.append("--shuffle=%s" % options.shuffle)
    return subprocess_args


def bisect_tests(bisection_label, options, test_labels, start_at, start_after):
    if not test_labels:
        test_labels = collect_test_modules(start_at, start_after)

    print("***** Bisecting test suite: %s" % " ".join(test_labels))

    
    
    for label in [bisection_label, "model_inheritance_same_model_name"]:
        try:
            test_labels.remove(label)
        except ValueError:
            pass

    subprocess_args = get_subprocess_args(options)

    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 %da: Running the first half of the test suite" % iteration)
        print("***** Test labels: %s" % " ".join(test_labels_a))
        failures_a = subprocess.run(subprocess_args + test_labels_a)

        print("***** Pass %db: Running the second half of the test suite" % iteration)
        print("***** Test labels: %s" % " ".join(test_labels_b))
        print("")
        failures_b = subprocess.run(subprocess_args + test_labels_b)

        if failures_a.returncode and not failures_b.returncode:
            print("***** Problem found in first half. Bisecting again...")
            iteration += 1
            test_labels = test_labels_a[:-1]
        elif failures_b.returncode and not failures_a.returncode:
            print("***** Problem found in second half. Bisecting again...")
            iteration += 1
            test_labels = test_labels_b[:-1]
        elif failures_a.returncode and failures_b.returncode:
            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: %s" % test_labels[0])


def paired_tests(paired_test, options, test_labels, start_at, start_after):
    if not test_labels:
        test_labels = collect_test_modules(start_at, start_after)

    print("***** Trying paired execution")

    
    
    for label in [paired_test, "model_inheritance_same_model_name"]:
        try:
            test_labels.remove(label)
        except ValueError:
            pass

    subprocess_args = get_subprocess_args(options)

    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 %s" % label)
            return

    print("***** No problem pair found")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Run the Django test suite.")
    parser.add_argument(
        "modules",
        nargs="*",
        metavar="module",
        help='Optional path(s) to test modules; e.g. "i18n" or '
        '"i18n.tests.TranslationTests.test_lazy_objects".',
    )
    parser.add_argument(
        "-v",
        "--verbosity",
        default=1,
        type=int,
        choices=[0, 1, 2, 3],
        help="Verbosity level; 0=minimal output, 1=normal output, 2=all output",
    )
    parser.add_argument(
        "--noinput",
        action="store_false",
        dest="interactive",
        help="Tells Django to NOT prompt the user for input of any kind.",
    )
    parser.add_argument(
        "--failfast",
        action="store_true",
        help="Tells Django to stop running the test suite after first failed test.",
    )
    parser.add_argument(
        "--keepdb",
        action="store_true",
        help="Tells Django to preserve the test database between runs.",
    )
    parser.add_argument(
        "--settings",
        help='Python path to settings module, e.g. "myproject.settings". If '
        "this isn't provided, either the DJANGO_SETTINGS_MODULE "
        'environment variable or "test_sqlite" will be used.',
    )
    parser.add_argument(
        "--bisect",
        help="Bisect the test suite to discover a test that causes a test "
        "failure when combined with the named test.",
    )
    parser.add_argument(
        "--pair",
        help="Run the test suite in pairs with the named test to find problem pairs.",
    )
    parser.add_argument(
        "--shuffle",
        nargs="?",
        default=False,
        type=int,
        metavar="SEED",
        help=(
            "Shuffle the order of test cases to help check that tests are "
            "properly isolated."
        ),
    )
    parser.add_argument(
        "--reverse",
        action="store_true",
        help="Sort test suites and test cases in opposite order to debug "
        "test side effects not apparent with normal execution lineup.",
    )
    parser.add_argument(
        "--selenium",
        action=ActionSelenium,
        metavar="BROWSERS",
        help="A comma-separated list of browsers to run the Selenium tests against.",
    )
    parser.add_argument(
        "--screenshots",
        action="store_true",
        help="Take screenshots during selenium tests to capture the user interface.",
    )
    parser.add_argument(
        "--headless",
        action="store_true",
        help="Run selenium tests in headless mode, if the browser supports the option.",
    )
    parser.add_argument(
        "--selenium-hub",
        help="A URL for a selenium hub instance to use in combination with --selenium.",
    )
    parser.add_argument(
        "--external-host",
        default=socket.gethostname(),
        help=(
            "The external host that can be reached by the selenium hub instance when "
            "running Selenium tests via Selenium Hub."
        ),
    )
    parser.add_argument(
        "--debug-sql",
        action="store_true",
        help="Turn on the SQL query logger within tests.",
    )
    
    
    
    parser.add_argument(
        "--parallel",
        nargs="?",
        const="auto",
        default=0,
        type=parallel_type,
        metavar="N",
        help=(
            'Run tests using up to N parallel processes. Use the value "auto" '
            "to run one test process for each processor core."
        ),
    )
    parser.add_argument(
        "--tag",
        dest="tags",
        action="append",
        help="Run only tests with the specified tags. Can be used multiple times.",
    )
    parser.add_argument(
        "--exclude-tag",
        dest="exclude_tags",
        action="append",
        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.",
    )
    parser.add_argument(
        "--pdb", action="store_true", help="Runs the PDB debugger on error or failure."
    )
    parser.add_argument(
        "-b",
        "--buffer",
        action="store_true",
        help="Discard output of passing tests.",
    )
    parser.add_argument(
        "--timing",
        action="store_true",
        help="Output timings, including database set up and total run time.",
    )
    parser.add_argument(
        "-k",
        dest="test_name_patterns",
        action="append",
        help=(
            "Only run test methods and classes matching test name pattern. "
            "Same as unittest -k option. Can be used multiple times."
        ),
    )
    if PY312:
        parser.add_argument(
            "--durations",
            dest="durations",
            type=int,
            default=None,
            metavar="N",
            help="Show the N slowest test cases (N=0 for all).",
        )

    options = parser.parse_args()

    using_selenium_hub = options.selenium and options.selenium_hub
    if options.selenium_hub and not options.selenium:
        parser.error(
            "--selenium-hub and --external-host require --selenium to be used."
        )
    if using_selenium_hub and not options.external_host:
        parser.error("--selenium-hub and --external-host must be used together.")
    if options.screenshots and not options.selenium:
        parser.error("--screenshots require --selenium to be used.")
    if options.screenshots and options.tags:
        parser.error("--screenshots and --tag are mutually exclusive.")

    
    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:
        os.environ["DJANGO_SETTINGS_MODULE"] = options.settings
    else:
        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_sqlite")
        options.settings = os.environ["DJANGO_SETTINGS_MODULE"]

    if options.selenium:
        if multiprocessing.get_start_method() == "spawn" and options.parallel != 1:
            parser.error(
                "You cannot use --selenium with parallel tests on this system. "
                "Pass --parallel=1 to use --selenium."
            )
        if not options.tags:
            options.tags = ["selenium"]
        elif "selenium" not in options.tags:
            options.tags.append("selenium")
        if options.selenium_hub:
            SeleniumTestCaseBase.selenium_hub = options.selenium_hub
            SeleniumTestCaseBase.external_host = options.external_host
        SeleniumTestCaseBase.headless = options.headless
        SeleniumTestCaseBase.browsers = options.selenium
        if options.screenshots:
            options.tags = ["screenshot"]
            SeleniumTestCase.screenshots = options.screenshots

    if options.bisect:
        bisect_tests(
            options.bisect,
            options,
            options.modules,
            options.start_at,
            options.start_after,
        )
    elif options.pair:
        paired_tests(
            options.pair,
            options,
            options.modules,
            options.start_at,
            options.start_after,
        )
    else:
        time_keeper = TimeKeeper() if options.timing else NullTimeKeeper()
        with time_keeper.timed("Total run"):
            failures = django_tests(
                options.verbosity,
                options.interactive,
                options.failfast,
                options.keepdb,
                options.reverse,
                options.modules,
                options.debug_sql,
                options.parallel,
                options.tags,
                options.exclude_tags,
                options.test_name_patterns,
                options.start_at,
                options.start_after,
                options.pdb,
                options.buffer,
                options.timing,
                options.shuffle,
                getattr(options, "durations", None),
            )
        time_keeper.print_results()
        if failures:
            sys.exit(1)