"""
Tests for django test runner
"""
from __future__ import absolute_import

import StringIO
from optparse import make_option
import warnings

from django.core.exceptions import ImproperlyConfigured
from django.core.management import call_command
from django import db
from django.test import simple, skipIfDBFeature
from django.test.simple import DjangoTestSuiteRunner, get_tests
from django.test.testcases import connections_support_transactions
from django.test.utils import get_warnings_state, restore_warnings_state
from django.utils import unittest
from django.utils.importlib import import_module

from ..admin_scripts.tests import AdminScriptTestCase


TEST_APP_OK = 'regressiontests.test_runner.valid_app.models'
TEST_APP_ERROR = 'regressiontests.test_runner.invalid_app.models'


class DjangoTestRunnerTests(unittest.TestCase):
    def setUp(self):
        self._warnings_state = get_warnings_state()
        warnings.filterwarnings('ignore', category=DeprecationWarning,
                                module='django.test.simple')

    def tearDown(self):
        restore_warnings_state(self._warnings_state)

    def test_failfast(self):
        class MockTestOne(unittest.TestCase):
            def runTest(self):
                assert False
        class MockTestTwo(unittest.TestCase):
            def runTest(self):
                assert False

        suite = unittest.TestSuite([MockTestOne(), MockTestTwo()])
        mock_stream = StringIO.StringIO()
        dtr = simple.DjangoTestRunner(verbosity=0, failfast=False, stream=mock_stream)
        result = dtr.run(suite)
        self.assertEqual(2, result.testsRun)
        self.assertEqual(2, len(result.failures))

        dtr = simple.DjangoTestRunner(verbosity=0, failfast=True, stream=mock_stream)
        result = dtr.run(suite)
        self.assertEqual(1, result.testsRun)
        self.assertEqual(1, len(result.failures))

class DependencyOrderingTests(unittest.TestCase):

    def test_simple_dependencies(self):
        raw = [
            ('s1', ('s1_db', ['alpha'])),
            ('s2', ('s2_db', ['bravo'])),
            ('s3', ('s3_db', ['charlie'])),
        ]
        dependencies = {
            'alpha': ['charlie'],
            'bravo': ['charlie'],
        }

        ordered = simple.dependency_ordered(raw, dependencies=dependencies)
        ordered_sigs = [sig for sig,value in ordered]

        self.assertIn('s1', ordered_sigs)
        self.assertIn('s2', ordered_sigs)
        self.assertIn('s3', ordered_sigs)
        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s1'))
        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s2'))

    def test_chained_dependencies(self):
        raw = [
            ('s1', ('s1_db', ['alpha'])),
            ('s2', ('s2_db', ['bravo'])),
            ('s3', ('s3_db', ['charlie'])),
        ]
        dependencies = {
            'alpha': ['bravo'],
            'bravo': ['charlie'],
        }

        ordered = simple.dependency_ordered(raw, dependencies=dependencies)
        ordered_sigs = [sig for sig,value in ordered]

        self.assertIn('s1', ordered_sigs)
        self.assertIn('s2', ordered_sigs)
        self.assertIn('s3', ordered_sigs)

        # Explicit dependencies
        self.assertLess(ordered_sigs.index('s2'), ordered_sigs.index('s1'))
        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s2'))

        # Implied dependencies
        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s1'))

    def test_multiple_dependencies(self):
        raw = [
            ('s1', ('s1_db', ['alpha'])),
            ('s2', ('s2_db', ['bravo'])),
            ('s3', ('s3_db', ['charlie'])),
            ('s4', ('s4_db', ['delta'])),
        ]
        dependencies = {
            'alpha': ['bravo','delta'],
            'bravo': ['charlie'],
            'delta': ['charlie'],
        }

        ordered = simple.dependency_ordered(raw, dependencies=dependencies)
        ordered_sigs = [sig for sig,aliases in ordered]

        self.assertIn('s1', ordered_sigs)
        self.assertIn('s2', ordered_sigs)
        self.assertIn('s3', ordered_sigs)
        self.assertIn('s4', ordered_sigs)

        # Explicit dependencies
        self.assertLess(ordered_sigs.index('s2'), ordered_sigs.index('s1'))
        self.assertLess(ordered_sigs.index('s4'), ordered_sigs.index('s1'))
        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s2'))
        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s4'))

        # Implicit dependencies
        self.assertLess(ordered_sigs.index('s3'), ordered_sigs.index('s1'))

    def test_circular_dependencies(self):
        raw = [
            ('s1', ('s1_db', ['alpha'])),
            ('s2', ('s2_db', ['bravo'])),
        ]
        dependencies = {
            'bravo': ['alpha'],
            'alpha': ['bravo'],
        }

        self.assertRaises(ImproperlyConfigured, simple.dependency_ordered, raw, dependencies=dependencies)


class MockTestRunner(object):
    invoked = False

    def __init__(self, *args, **kwargs):
        pass

    def run_tests(self, test_labels, extra_tests=None, **kwargs):
        MockTestRunner.invoked = True


class ManageCommandTests(unittest.TestCase):

    def test_custom_test_runner(self):
        call_command('test', 'sites',
                     testrunner='regressiontests.test_runner.tests.MockTestRunner')
        self.assertTrue(MockTestRunner.invoked,
                        "The custom test runner has not been invoked")


class CustomOptionsTestRunner(simple.DjangoTestSuiteRunner):
    option_list = (
        make_option('--option_a','-a', action='store', dest='option_a', default='1'),
        make_option('--option_b','-b', action='store', dest='option_b', default='2'),
        make_option('--option_c','-c', action='store', dest='option_c', default='3'),
    )

    def __init__(self, verbosity=1, interactive=True, failfast=True, option_a=None, option_b=None, option_c=None, **kwargs):
        super(CustomOptionsTestRunner, self).__init__(verbosity=verbosity, interactive=interactive,
                                                      failfast=failfast)
        self.option_a = option_a
        self.option_b = option_b
        self.option_c = option_c

    def run_tests(self, test_labels, extra_tests=None, **kwargs):
        print "%s:%s:%s" % (self.option_a, self.option_b, self.option_c)


class CustomTestRunnerOptionsTests(AdminScriptTestCase):

    def setUp(self):
        settings = {
            'TEST_RUNNER': '\'regressiontests.test_runner.tests.CustomOptionsTestRunner\'',
        }
        self.write_settings('settings.py', sdict=settings)

    def tearDown(self):
        self.remove_settings('settings.py')

    def test_default_options(self):
        args = ['test', '--settings=regressiontests.settings']
        out, err = self.run_django_admin(args)
        self.assertNoOutput(err)
        self.assertOutput(out, '1:2:3')

    def test_default_and_given_options(self):
        args = ['test', '--settings=regressiontests.settings', '--option_b=foo']
        out, err = self.run_django_admin(args)
        self.assertNoOutput(err)
        self.assertOutput(out, '1:foo:3')

    def test_option_name_and_value_separated(self):
        args = ['test', '--settings=regressiontests.settings', '--option_b', 'foo']
        out, err = self.run_django_admin(args)
        self.assertNoOutput(err)
        self.assertOutput(out, '1:foo:3')

    def test_all_options_given(self):
        args = ['test', '--settings=regressiontests.settings', '--option_a=bar', '--option_b=foo', '--option_c=31337']
        out, err = self.run_django_admin(args)
        self.assertNoOutput(err)
        self.assertOutput(out, 'bar:foo:31337')


class Ticket16885RegressionTests(unittest.TestCase):
    
    # Skipped if empty strings are nulls because this feature causes
    # database setup to fail on model validation for models defined
    # with string PKs (such models are already in the AppCache), while
    # the test cares neither about models nor about the database backend
    # from settings.
    @skipIfDBFeature('interprets_empty_strings_as_nulls')
    def test_ticket_16885(self):
        """Features are also confirmed on mirrored databases."""
        old_db_connections = db.connections
        try:
            db.connections = db.ConnectionHandler({
                'default': {
                    'ENGINE': 'django.db.backends.sqlite3',
                },
                'slave': {
                    'ENGINE': 'django.db.backends.sqlite3',
                    'TEST_MIRROR': 'default',
                },
            })
            slave = db.connections['slave']
            self.assertEqual(slave.features.supports_transactions, None)
            DjangoTestSuiteRunner(verbosity=0).setup_databases()
            self.assertNotEqual(slave.features.supports_transactions, None)
        finally:
            db.connections = old_db_connections


class Ticket17477RegressionTests(AdminScriptTestCase):
    def setUp(self):
        self.write_settings('settings.py')

    def tearDown(self):
        self.remove_settings('settings.py')

    def test_ticket_17477(self):
        """'manage.py help test' works after r16352."""
        args = ['help', 'test']
        out, err = self.run_manage(args)
        self.assertNoOutput(err)


class ModulesTestsPackages(unittest.TestCase):
    def test_get_tests(self):
        "Check that the get_tests helper function can find tests in a directory"
        module = import_module(TEST_APP_OK)
        tests = get_tests(module)
        self.assertIsInstance(tests, type(module))

    def test_import_error(self):
        "Test for #12658 - Tests with ImportError's shouldn't fail silently"
        module = import_module(TEST_APP_ERROR)
        self.assertRaises(ImportError, get_tests, module)


class Sqlite3InMemoryTestDbs(unittest.TestCase):

    @unittest.skipUnless(all(db.connections[conn].vendor == 'sqlite' for conn in db.connections),
                         "This is a sqlite-specific issue")
    def test_transaction_support(self):
        """Ticket #16329: sqlite3 in-memory test databases"""
        old_db_connections = db.connections
        for option in ('NAME', 'TEST_NAME'):
            try:
                db.connections = db.ConnectionHandler({
                    'default': {
                        'ENGINE': 'django.db.backends.sqlite3',
                        option: ':memory:',
                    },
                    'other': {
                        'ENGINE': 'django.db.backends.sqlite3',
                        option: ':memory:',
                    },
                })
                other = db.connections['other']
                self.assertIsNone(other.features.supports_transactions)
                DjangoTestSuiteRunner(verbosity=0).setup_databases()
                msg = "DATABASES setting '%s' option set to sqlite3's ':memory:' value shouldn't interfere with transaction support detection." % option
                # Transaction support should be properly initialised for the 'other' DB
                self.assertIsNotNone(other.features.supports_transactions, msg)
                # And all the DBs should report that they support transactions
                self.assertTrue(connections_support_transactions(), msg)
            finally:
                db.connections = old_db_connections
