#!/usr/bin/python

import time
import unittest

try:
    import autotest.common as common  # pylint: disable=W0611
except ImportError:
    import common  # pylint: disable=W0611
from autotest.client.shared import settings
from autotest.client.shared.test_utils import mock
from autotest.database_legacy import database_connection

_CONFIG_SECTION = 'AUTOTEST_WEB'
_HOST = 'myhost'
_USER = 'myuser'
_PASS = 'mypass'
_DB_NAME = 'mydb'
_DB_TYPE = 'mydbtype'

_CONNECT_KWARGS = dict(host=_HOST, username=_USER, password=_PASS,
                       db_name=_DB_NAME)
_RECONNECT_DELAY = 10


class FakeDatabaseError(Exception):
    pass


class DatabaseConnectionTest(unittest.TestCase):

    def setUp(self):
        self.god = mock.mock_god()
        self.god.stub_function(time, 'sleep')

    def tearDown(self):
        settings.settings.reset_values()
        self.god.unstub_all()

    def _get_database_connection(self, config_section=_CONFIG_SECTION):
        if config_section == _CONFIG_SECTION:
            self._override_config()
        db = database_connection.DatabaseConnection(config_section)

        self._fake_backend = self.god.create_mock_class(
            database_connection._GenericBackend, 'fake_backend')
        for exception in database_connection._DB_EXCEPTIONS:
            setattr(self._fake_backend, exception, FakeDatabaseError)
        self._fake_backend.rowcount = 0

        def get_fake_backend(db_type):
            self._db_type = db_type
            return self._fake_backend
        self.god.stub_with(db, '_get_backend', get_fake_backend)

        db.reconnect_delay_sec = _RECONNECT_DELAY
        return db

    def _override_config(self):
        c = settings.settings
        c.override_value(_CONFIG_SECTION, 'host', _HOST)
        c.override_value(_CONFIG_SECTION, 'user', _USER)
        c.override_value(_CONFIG_SECTION, 'password', _PASS)
        c.override_value(_CONFIG_SECTION, 'database', _DB_NAME)
        c.override_value(_CONFIG_SECTION, 'db_type', _DB_TYPE)

    def test_connect(self):
        db = self._get_database_connection(config_section=None)
        self._fake_backend.connect.expect_call(**_CONNECT_KWARGS)

        db.connect(db_type=_DB_TYPE, host=_HOST, username=_USER,
                   password=_PASS, db_name=_DB_NAME)

        self.assertEquals(self._db_type, _DB_TYPE)
        self.god.check_playback()

    def test_settings(self):
        db = self._get_database_connection()
        self._fake_backend.connect.expect_call(**_CONNECT_KWARGS)

        db.connect()

        self.assertEquals(self._db_type, _DB_TYPE)
        self.god.check_playback()

    def _expect_reconnect(self, fail=False):
        self._fake_backend.disconnect.expect_call()
        call = self._fake_backend.connect.expect_call(**_CONNECT_KWARGS)
        if fail:
            call.and_raises(FakeDatabaseError())

    def _expect_fail_and_reconnect(self, num_reconnects, fail_last=False):
        self._fake_backend.connect.expect_call(**_CONNECT_KWARGS).and_raises(
            FakeDatabaseError())
        for i in xrange(num_reconnects):
            time.sleep.expect_call(_RECONNECT_DELAY)
            if i < num_reconnects - 1:
                self._expect_reconnect(fail=True)
            else:
                self._expect_reconnect(fail=fail_last)

    def test_connect_retry(self):
        db = self._get_database_connection()
        self._expect_fail_and_reconnect(1)

        db.connect()
        self.god.check_playback()

        self._fake_backend.disconnect.expect_call()
        self._expect_fail_and_reconnect(0)
        self.assertRaises(FakeDatabaseError, db.connect,
                          try_reconnecting=False)
        self.god.check_playback()

        db.reconnect_enabled = False
        self._fake_backend.disconnect.expect_call()
        self._expect_fail_and_reconnect(0)
        self.assertRaises(FakeDatabaseError, db.connect)
        self.god.check_playback()

    def test_max_reconnect(self):
        db = self._get_database_connection()
        db.max_reconnect_attempts = 5
        self._expect_fail_and_reconnect(5, fail_last=True)

        self.assertRaises(FakeDatabaseError, db.connect)
        self.god.check_playback()

    def test_reconnect_forever(self):
        db = self._get_database_connection()
        db.max_reconnect_attempts = database_connection.RECONNECT_FOREVER
        self._expect_fail_and_reconnect(30)

        db.connect()
        self.god.check_playback()

    def _simple_connect(self, db):
        self._fake_backend.connect.expect_call(**_CONNECT_KWARGS)
        db.connect()
        self.god.check_playback()

    def test_disconnect(self):
        db = self._get_database_connection()
        self._simple_connect(db)
        self._fake_backend.disconnect.expect_call()

        db.disconnect()
        self.god.check_playback()

    def test_execute(self):
        db = self._get_database_connection()
        self._simple_connect(db)
        params = object()
        self._fake_backend.execute.expect_call('query', params)

        db.execute('query', params)
        self.god.check_playback()

    def test_execute_retry(self):
        db = self._get_database_connection()
        self._simple_connect(db)
        self._fake_backend.execute.expect_call('query', None).and_raises(
            FakeDatabaseError())
        self._expect_reconnect()
        self._fake_backend.execute.expect_call('query', None)

        db.execute('query')
        self.god.check_playback()

        self._fake_backend.execute.expect_call('query', None).and_raises(
            FakeDatabaseError())
        self.assertRaises(FakeDatabaseError, db.execute, 'query',
                          try_reconnecting=False)


if __name__ == '__main__':
    unittest.main()
