# -*- coding: utf-8 -*-

########################################################################
# pyeole.decorator - decorators with optional arguments tests
# Copyright © 2012 Pôle de Compétence EOLE <eole@ac-dijon.fr>
#
# Licence CeCILL en français http://www.cecill.info/licences/Licence_CeCILL_V2-fr.html
# License CeCILL in english http://www.cecill.info/licences/Licence_CeCILL_V2-en.html
########################################################################

import pytest
#pytestmark = pytest.mark.unitary

import warnings

from pyeole.inspect_utils import get_caller_infos
from pyeole.decorator import advice
import pyeole.decorator
import pyeole.deprecation

import sys
if sys.version_info[0] >= 3:
    unicode = str

# Advice functions

def before(function, *args, **kwargs):
    warnings.warn("before: '%s'" % function.__name__)

def before2(function, *args, **kwargs):
    warnings.warn("before2: '%s'" % function.__name__)

def around(function, *args, **kwargs):
    warnings.warn("around: '%s'" % function.__name__)
    return function(*args, **kwargs)

def around2(function, *args, **kwargs):
    warnings.warn("around2: '%s'" % function.__name__)
    return function(*args, **kwargs)

def around_modify(function, *args, **kwargs):
    warnings.warn("around modify: '%s'" % function.__name__)
    function(*args, **kwargs)
    return 1

def around_modify2(function, *args, **kwargs):
    warnings.warn("around modify2: '%s'" % function.__name__)
    function(*args, **kwargs)
    return 2

def after(ret, function, *args, **kwargs):
    warnings.warn("after: '%s'" % function.__name__)

def after2(ret, function, *args, **kwargs):
    warnings.warn("after2: '%s'" % function.__name__)

# Adviced functions

@advice(before=before)
def before_adviced_function():
    """before adviced docstring"""
    warnings.warn("function: 'before_adviced_function'")
    return get_caller_infos()

@advice(around=around)
def around_adviced_function():
    warnings.warn("function: 'around_adviced_function'")
    return get_caller_infos()

@advice(around=around_modify)
def modified_around_adviced_function():
    warnings.warn("function: 'modified_around_adviced_function'")
    return get_caller_infos()

@advice(after=after)
def after_adviced_function():
    warnings.warn("function: 'after_adviced_function'")
    return get_caller_infos()

@advice(before=[before,before2])
def multi_before_adviced_function():
    warnings.warn("function: 'multi_before_adviced_function'")
    return get_caller_infos()

@advice(around=[around,around2])
def multi_around_adviced_function():
    warnings.warn("function: 'multi_around_adviced_function'")
    return get_caller_infos()

@advice(around=[around_modify,around_modify2])
def multi_around_modify_adviced_function():
    warnings.warn("function: 'multi_around_modify_adviced_function'")
    return get_caller_infos()

@advice(after=[after,after2])
def multi_after_adviced_function():
    warnings.warn("function: 'multi_after_adviced_function'")
    return get_caller_infos()

@pyeole.decorator.deprecated
def deprecated_function():
    warnings.warn("function: 'deprecated_function'")
    return get_caller_infos()

@pyeole.decorator.deprecated("deprecated positional argument")
def deprecated_with_positional_message_function():
    warnings.warn("function: 'deprecated_with_positional_message_function'")
    return get_caller_infos()

@pyeole.decorator.deprecated(message="deprecated keyword argument")
def deprecated_with_keyword_message_function():
    warnings.warn("function: 'deprecated_with_keyword_message_function'")
    return get_caller_infos()

@pyeole.deprecation.deprecated
def old_api_deprecated_function():
    """Old api function test"""
    warnings.warn("function: 'old_api_deprecated_function'")
    return get_caller_infos()

# Tests

def test_wrapper_attributes():
    """Test if the wrapper gets the decorated function attributes
    """
    assert before_adviced_function.__name__ == 'before_adviced_function'
    assert before_adviced_function.__doc__ == 'before adviced docstring'
    assert deprecated_function.__name__ == 'deprecated_function'

def test_decorator_stacking():
    """Test if decorators can be stacked
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        @pyeole.decorator.deprecated
        @advice(before=before)
        def deprecated_before_adviced_function():
            """deprecated before adviced docstring"""
            warnings.warn("function: 'deprecated_before_adviced_function'")
            return get_caller_infos()
        ret = deprecated_before_adviced_function()
        assert isinstance(ret, dict)
        assert ret['codename'] == 'decorate'
        assert len(w) == 3
        assert issubclass(w[0].category, DeprecationWarning)
        assert issubclass(w[1].category, UserWarning)
        assert issubclass(w[2].category, UserWarning)
        assert unicode(w[0].message) == u"Deprecated function call 'deprecated_before_adviced_function' by 'test_decorators.test_decorator_stacking'"
        assert unicode(w[1].message) == u"before: 'deprecated_before_adviced_function'"
        assert unicode(w[2].message) == u"function: 'deprecated_before_adviced_function'"

def test_advice_with_uncallable_modifier():
    """Test if advice raise with uncallable modifier
    """
    with pytest.raises(ValueError):
        @advice(before="foo")
        def adviced_with_uncallable_modifier_function():
            pass

def test_advice_with_uncallable_modifier_list():
    """Test if advice raise with uncallable in modifier list
    """
    with pytest.raises(ValueError):
        @advice(before=[before, "foo"])
        def adviced_with_uncallable_modifier_list_function():
            pass

def test_advice_with_positional_argument():
    """Test if advice raise with positional argument
    """
    with pytest.raises(ValueError):
        @advice(before)
        def adviced_with_positional_function():
            pass

def test_before_advice():
    """Test advice with only before
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = before_adviced_function()
        assert isinstance(ret, dict)
        assert ret['codename'] == 'decorate'
        assert len(w) == 2
        assert issubclass(w[0].category, UserWarning)
        assert issubclass(w[1].category, UserWarning)
        assert unicode(w[0].message) == u"before: 'before_adviced_function'"
        assert unicode(w[1].message) == u"function: 'before_adviced_function'"

def test_around_advice():
    """Test advice with only around
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = around_adviced_function()
        assert isinstance(ret, dict)
        assert ret['codename'] == 'around'
        assert len(w) == 2
        assert issubclass(w[0].category, UserWarning)
        assert issubclass(w[1].category, UserWarning)
        assert unicode(w[0].message) == u"around: 'around_adviced_function'"
        assert unicode(w[1].message) == u"function: 'around_adviced_function'"

def test_modified_around_advice():
    """Test advice with only around modifying the returned value of the
    decorated function.
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = modified_around_adviced_function()
        assert isinstance(ret, int)
        assert ret == 1
        assert len(w) == 2
        assert issubclass(w[0].category, UserWarning)
        assert issubclass(w[1].category, UserWarning)
        assert unicode(w[0].message) == u"around modify: 'modified_around_adviced_function'"
        assert unicode(w[1].message) == u"function: 'modified_around_adviced_function'"

def test_after_advice():
    """Test advice with only after
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = after_adviced_function()
        assert isinstance(ret, dict)
        assert ret['codename'] == 'decorate'
        assert len(w) == 2
        assert issubclass(w[0].category, UserWarning)
        assert issubclass(w[1].category, UserWarning)
        assert unicode(w[0].message) == u"function: 'after_adviced_function'"
        assert unicode(w[1].message) == u"after: 'after_adviced_function'"

def test_multi_before_advice():
    """Test advice with multiple before advices

    The before advices are run in 'reversed' order
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = multi_before_adviced_function()
        assert isinstance(ret, dict)
        assert ret['codename'] == 'decorate'
        assert len(w) == 3
        assert issubclass(w[0].category, UserWarning)
        assert issubclass(w[1].category, UserWarning)
        assert issubclass(w[2].category, UserWarning)
        assert unicode(w[0].message) == u"before2: 'multi_before_adviced_function'"
        assert unicode(w[1].message) == u"before: 'multi_before_adviced_function'"
        assert unicode(w[2].message) == u"function: 'multi_before_adviced_function'"

def test_multi_around_advice():
    """Test advice with multiple around advices

    The around advices are run recursively in 'reversed' order.
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = multi_around_adviced_function()
        assert isinstance(ret, dict)
        assert ret['codename'] == 'around'
        assert len(w) == 3
        assert issubclass(w[0].category, UserWarning)
        assert issubclass(w[1].category, UserWarning)
        assert issubclass(w[2].category, UserWarning)
        assert unicode(w[0].message) == u"around2: '_call_around'"
        assert unicode(w[1].message) == u"around: 'multi_around_adviced_function'"
        assert unicode(w[2].message) == u"function: 'multi_around_adviced_function'"

def test_multi_around_modify_advice():
    """Test advice with multiple around advices overriding return value

    The around advices are run recursively in 'reversed' order.
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = multi_around_modify_adviced_function()
        assert isinstance(ret, int)
        assert ret == 2
        assert len(w) == 3
        assert issubclass(w[0].category, UserWarning)
        assert issubclass(w[1].category, UserWarning)
        assert issubclass(w[2].category, UserWarning)
        assert unicode(w[0].message) == u"around modify2: '_call_around'"
        assert unicode(w[1].message) == u"around modify: 'multi_around_modify_adviced_function'"
        assert unicode(w[2].message) == u"function: 'multi_around_modify_adviced_function'"

def test_multi_after_advice():
    """Test advice with multiple after advices

    The after advices are run in 'normal' order.
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = multi_after_adviced_function()
        assert isinstance(ret, dict)
        assert ret['codename'] == 'decorate'
        assert len(w) == 3
        assert issubclass(w[0].category, UserWarning)
        assert issubclass(w[1].category, UserWarning)
        assert issubclass(w[2].category, UserWarning)
        assert unicode(w[0].message) == u"function: 'multi_after_adviced_function'"
        assert unicode(w[1].message) == u"after: 'multi_after_adviced_function'"
        assert unicode(w[2].message) == u"after2: 'multi_after_adviced_function'"

def test_deprecated():
    """Test deprecated
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = deprecated_function()
        assert isinstance(ret, dict)
        assert ret['codename'] == 'decorate'
        assert len(w) == 2
        assert issubclass(w[0].category, DeprecationWarning)
        assert issubclass(w[1].category, UserWarning)
        assert unicode(w[0].message) == u"Deprecated function call 'deprecated_function' by 'test_decorators.test_deprecated'"
        assert unicode(w[1].message) == u"function: 'deprecated_function'"

def test_deprecated_with_positional_message():
    """Test deprecated with a message passed as positional argument
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = deprecated_with_positional_message_function()
        assert isinstance(ret, dict)
        assert ret['codename'] == 'decorate'
        assert len(w) == 2
        assert issubclass(w[0].category, DeprecationWarning)
        assert issubclass(w[1].category, UserWarning)
        assert unicode(w[0].message) == u"Deprecated function call 'deprecated_with_positional_message_function' by 'test_decorators.test_deprecated_with_positional_message': deprecated positional argument"
        assert unicode(w[1].message) == u"function: 'deprecated_with_positional_message_function'"

def test_deprecated_with_keyword_message():
    """Test deprecated with a message passed as keyword argument
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = deprecated_with_keyword_message_function()
        assert isinstance(ret, dict)
        assert ret['codename'] == 'decorate'
        assert len(w) == 2
        assert issubclass(w[0].category, DeprecationWarning)
        assert issubclass(w[1].category, UserWarning)
        assert unicode(w[0].message) == u"Deprecated function call 'deprecated_with_keyword_message_function' by 'test_decorators.test_deprecated_with_keyword_message': deprecated keyword argument"
        assert unicode(w[1].message) == u"function: 'deprecated_with_keyword_message_function'"

def test_old_api_deprecated():
    """Test previous deprecated function
    """
    with warnings.catch_warnings(record=True) as w:
        warnings.simplefilter("always")
        ret = old_api_deprecated_function()
        assert isinstance(ret, dict)
        assert ret['codename'] == 'deprecate'
        assert len(w) == 3
        assert issubclass(w[0].category, DeprecationWarning)
        assert issubclass(w[1].category, DeprecationWarning)
        assert issubclass(w[2].category, UserWarning)
        assert unicode(w[0].message) == u"Deprecated function call 'deprecate' by 'test_decorators.test_old_api_deprecated': use pyeole.decorator.deprecated."
        assert unicode(w[1].message) == u"Call to deprecated function old_api_deprecated_function."
        assert unicode(w[2].message) == u"function: 'old_api_deprecated_function'"
