<reponame>Mischback/django-auth_enhanced<gh_stars>0
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Includes an app-specific test runner.

This test runner heavily relies on Django's DiscoverRunner. In fact, it is very
close to Django's own test runner.

It accepts less options and looks for the tests to be run in the current
directory instead of app's test-directories.

    - available command line options:
        * --disable-optimisation    disables all test-specific optimisations
        * --enable-migrations       enables migrations during testing
        * --settings                specify a settings-module for the test run
        * --tag, -t                 specify one (or multiple) tags to be tested
        * --time                    enables time measurement during tests
        * --verbosity, -v           verbosity level (0, 1, 2, 3)

It can't be run on certain modules only, but on given tags."""

# Python imports
import argparse
import os
import sys

# Django imports
import django
from django.conf import settings
from django.test.utils import get_runner


def setup(disable_optimisation, enable_migrations, enable_timing, verbosity):
    """Prepares the test environment.

    Basically, this function is used to inject test-specific settings. It should
    be ensured, that these settings are only relevant to testing. A minimal
    configuration to actually run the app has to be specified using Django's
    setting-mechanism."""

    class DisableMigrations:
        """A generic class to disable all migrations during tests.

        See setup()-function on how this is applied."""

        def __contains__(self, item):
            return True

        def __getitem__(self, item):
            # return 'thesearenotthemigrationsyouarelookingfor'
            return None

    if not disable_optimisation:
        # don't test with debugging enabled
        settings.DEBUG = False
        settings.ALLOWED_HOSTS = []

        # only use one password hasher (and the fastest one)
        settings.PASSWORD_HASHERS = ['django.contrib.auth.hashers.MD5PasswordHasher', ]

        # turn off logging
        import logging
        logging.disable(logging.CRITICAL)

        # disable migrations during tests
        if not enable_migrations:
            # see https://simpleisbetterthancomplex.com/tips/2016/08/19/django-tip-12-disabling-migrations-to-speed-up-unit-tests.html  # noqa
            settings.MIGRATION_MODULES = DisableMigrations()
            if verbosity >= 2:
                print('Testing without applied migrations.')
        else:
            if verbosity >= 2:
                print('Testing with applied migrations.')
    else:
        if verbosity >= 2:
            print('Testing without any test-specific optimisations.')

    if enable_timing:
        from django import test
        import time

        def setUp(self):
            self.start_time = time.time()

        def tearDown(self):
            total = time.time() - self.start_time
            if total > 0.5:
                print("\n\t\033[91m{:.3f}s\t{}\033[0m".format(total, self._testMethodName))

        test.TestCase.setUp = setUp
        test.TestCase.tearDown = tearDown

    # actually build the Django configuration
    django.setup()


def app_tests(disable_optimisation, enable_migrations, enable_timing, tags, verbosity):
    """Gets the TestRunner and runs the tests"""

    # prepare the actual test environment
    setup(disable_optimisation, enable_migrations, enable_timing, verbosity)

    # reuse Django's DiscoverRunner
    if not hasattr(settings, 'TEST_RUNNER'):
        settings.TEST_RUNNER = 'django.test.runner.DiscoverRunner'
    TestRunner = get_runner(settings)

    test_runner = TestRunner(
        verbosity=verbosity,
        tags=tags,
    )

    failures = test_runner.run_tests(['.'])

    return failures


if __name__ == '__main__':
    # set up the argument parser
    parser = argparse.ArgumentParser(description='Run the django-auth_enhanced test suite')
    parser.add_argument(
        '--disable-optimisation', action='store_true', dest='disable_optimisation',
        help="Disables the test specific optimisations."
    )
    parser.add_argument(
        '--enable-migrations', action='store_true', dest='enable_migrations',
        help="Enables the usage of migrations during tests."
    )
    parser.add_argument(
        '--settings',
        help="Python path to settings module, e.g. 'myproject.settings'. If "
             "this is not provided, 'tests.utils.settings_dev' will be used."
    )
    parser.add_argument(
        '-t', '--tag', dest='tags', action='append',
        help="Run only tests with the specified tags. Can be used multiple times."
    )
    parser.add_argument(
        '--time', action='store_true', dest='enable_timing',
        help="Enables time measurements for all tests."
    )
    parser.add_argument(
        '-v', '--verbosity', default=1, type=int, choices=[0, 1, 2, 3],
        help="Verbosity level; 0=minimal, 3=maximal; default=1"
    )

    # actually get the options
    options = parser.parse_args()

    # run tests according to the options
    if options.settings:
        os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
    else:
        os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'utils.settings_dev')
        options.settings = os.environ['DJANGO_SETTINGS_MODULE']

    failures = app_tests(
        options.disable_optimisation,
        options.enable_migrations,
        options.enable_timing,
        options.tags,
        options.verbosity,
    )

    if failures:
        sys.exit(1)
