#!/usr/bin/env python

"""
Tests for `util` module.

Credit of test for the @retry decorator:
http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/
"""

import logging
import unittest

from plumbery.util import retry, PlumberyParameters


class RetryableError(Exception):
    pass


class AnotherRetryableError(Exception):
    pass


class UnexpectedError(Exception):
    pass


class TestPlumberyUtil(unittest.TestCase):

    def test_no_retry_required(self):
        self.counter = 0

        @retry(RetryableError, tries=4, delay=0.1)
        def succeeds():
            self.counter += 1
            return 'success'

        r = succeeds()

        self.assertEqual(r, 'success')
        self.assertEqual(self.counter, 1)

    def test_retries_once(self):
        self.counter = 0

        @retry(RetryableError, tries=4, delay=0.1)
        def fails_once():
            self.counter += 1
            if self.counter < 2:
                raise RetryableError('failed')
            else:
                return 'success'

        r = fails_once()
        self.assertEqual(r, 'success')
        self.assertEqual(self.counter, 2)

    def test_limit_is_reached(self):
        self.counter = 0

        @retry(RetryableError, tries=4, delay=0.1)
        def always_fails():
            self.counter += 1
            raise RetryableError('failed')

        with self.assertRaises(RetryableError):
            always_fails()
        self.assertEqual(self.counter, 4)

    def test_multiple_exception_types(self):
        self.counter = 0

        @retry((RetryableError, AnotherRetryableError), tries=4, delay=0.1)
        def raise_multiple_exceptions():
            self.counter += 1
            if self.counter == 1:
                raise RetryableError('a retryable error')
            elif self.counter == 2:
                raise AnotherRetryableError('another retryable error')
            else:
                return 'success'

        r = raise_multiple_exceptions()
        self.assertEqual(r, 'success')
        self.assertEqual(self.counter, 3)

    def test_unexpected_exception_does_not_retry(self):

        @retry(RetryableError, tries=4, delay=0.1)
        def raise_unexpected_error():
            raise UnexpectedError('unexpected error')

        with self.assertRaises(UnexpectedError):
            raise_unexpected_error()

    def test_using_a_logger(self):
        self.counter = 0

        sh = logging.StreamHandler()
        logger = logging.getLogger(__name__)
        logger.addHandler(sh)

        @retry(RetryableError, tries=4, delay=0.1, logger=logger)
        def fails_once():
            self.counter += 1
            if self.counter < 2:
                raise RetryableError('failed')
            else:
                return 'success'

        fails_once()

    def test_parameters(self):
        params = PlumberyParameters()
        self.assertEqual(params.get('dummy'), None)
        self.assertEqual(params.get('dummy', 'ko'), 'ko')
        params.set('dummy', 1)
        self.assertEqual(params.get('dummy'), 1)
        self.assertEqual(params.get('dummy', 'whatever'), 1)
        params.set('dummy', 2)
        self.assertEqual(params.get('dummy'), 2)
        self.assertEqual(params.get('dummy', 'whatever'), 2)

        params = PlumberyParameters({'dummy': 'ok'})
        self.assertEqual(params.get('dummy'), 'ok')
        self.assertEqual(params.get('dummy', 'ko'), 'ok')
        params.set('dummy', 1)
        self.assertEqual(params.get('dummy'), 1)
        self.assertEqual(params.get('dummy', 'whatever'), 1)
        params.set('dummy', 2)
        self.assertEqual(params.get('dummy'), 2)
        self.assertEqual(params.get('dummy', 'whatever'), 2)
        self.assertEqual(params.get('more_dummy'), None)
        self.assertEqual(params.get('more_dummy', 'ok'), 'ok')
        self.assertEqual(params.get('more_dummy', 'ko'), 'ko')
        params.set('more_dummy', 3)
        self.assertEqual(params.get('more_dummy'), 3)
        self.assertEqual(params.get('more_dummy', 'ok'), 3)
        self.assertEqual(params.get('more_dummy', 'ko'), 3)

        with self.assertRaises(TypeError):
            params2 = PlumberyParameters('should raise an error')
        with self.assertRaises(TypeError):
            params2 = PlumberyParameters(params)

if __name__ == '__main__':
    import sys
    sys.exit(unittest.main())
