# -*- coding: utf-8 -*-
"""
    test_util_inspect
    ~~~~~~~~~~~~~~~

    Tests util.inspect functions.

    :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""
from unittest import TestCase

import sys
from six import PY3
import functools
from textwrap import dedent
import pytest

from sphinx.util import inspect


class TestGetArgSpec(TestCase):
    def test_getargspec_builtin_type(self):
        with pytest.raises(TypeError):
            inspect.getargspec(int)

    def test_getargspec_partial(self):
        def fun(a, b, c=1, d=2):
            pass
        p = functools.partial(fun, 10, c=11)

        if PY3:
            # Python 3's partial is rather cleverer than Python 2's, and we
            # have to jump through some hoops to define an equivalent function
            # in a way that won't confuse Python 2's parser:
            ns = {}
            exec(dedent("""
                def f_expected(b, *, c=11, d=2):
                        pass
            """), ns)
            f_expected = ns["f_expected"]
        else:
            def f_expected(b, d=2):
                pass
        expected = inspect.getargspec(f_expected)

        assert expected == inspect.getargspec(p)

    def test_getargspec_bound_methods(self):
        def f_expected_unbound(self, arg1, **kwargs):
            pass
        expected_unbound = inspect.getargspec(f_expected_unbound)

        def f_expected_bound(arg1, **kwargs):
            pass
        expected_bound = inspect.getargspec(f_expected_bound)

        class Foo:
            def method(self, arg1, **kwargs):
                pass

        bound_method = Foo().method

        @functools.wraps(bound_method)
        def wrapped_bound_method(*args, **kwargs):
            pass

        assert expected_unbound == inspect.getargspec(Foo.method)
        if PY3 and sys.version_info >= (3, 4, 4):
            # On py2, the inspect functions don't properly handle bound
            # methods (they include a spurious 'self' argument)
            assert expected_bound == inspect.getargspec(bound_method)
            # On py2, the inspect functions can't properly handle wrapped
            # functions (no __wrapped__ support)
            assert expected_bound == inspect.getargspec(wrapped_bound_method)


class TestSafeGetAttr(TestCase):
    def test_safe_getattr_with_default(self):
        class Foo(object):
            def __getattr__(self, item):
                raise Exception

        obj = Foo()

        result = inspect.safe_getattr(obj, 'bar', 'baz')

        assert result == 'baz'

    def test_safe_getattr_with_exception(self):
        class Foo(object):
            def __getattr__(self, item):
                raise Exception

        obj = Foo()

        try:
            inspect.safe_getattr(obj, 'bar')
        except AttributeError as exc:
            self.assertEqual(exc.args[0], 'bar')
        else:
            self.fail('AttributeError not raised')

    def test_safe_getattr_with_property_exception(self):
        class Foo(object):
            @property
            def bar(self):
                raise Exception

        obj = Foo()

        try:
            inspect.safe_getattr(obj, 'bar')
        except AttributeError as exc:
            self.assertEqual(exc.args[0], 'bar')
        else:
            self.fail('AttributeError not raised')

    def test_safe_getattr_with___dict___override(self):
        class Foo(object):
            @property
            def __dict__(self):
                raise Exception

        obj = Foo()

        try:
            inspect.safe_getattr(obj, 'bar')
        except AttributeError as exc:
            self.assertEqual(exc.args[0], 'bar')
        else:
            self.fail('AttributeError not raised')


class TestObjectDescription(TestCase):
    def test_dictionary_sorting(self):
        dictionary = {"c": 3, "a": 1, "d": 2, "b": 4}
        description = inspect.object_description(dictionary)
        assert description == "{'a': 1, 'b': 4, 'c': 3, 'd': 2}"

    def test_dict_customtype(self):
        class CustomType(object):
            def __init__(self, value):
                self._value = value

            def __repr__(self):
                return "<CustomType(%r)>" % self._value

        dictionary = {CustomType(2): 2, CustomType(1): 1}
        description = inspect.object_description(dictionary)
        # Type is unsortable, just check that it does not crash
        assert "<CustomType(2)>: 2" in description
