# -*- coding: utf-8 -*-
import sys
import warnings

import pytest

import deprecated.classic


class MyDeprecationWarning(DeprecationWarning):
    pass


_PARAMS = [
    None,
    ((), {}),
    (('Good reason',), {}),
    ((), {'reason': 'Good reason'}),
    ((), {'version': '1.2.3'}),
    ((), {'action': 'once'}),
    ((), {'category': MyDeprecationWarning}),
]


@pytest.fixture(scope="module", params=_PARAMS)
def classic_deprecated_function(request):
    if request.param is None:

        @deprecated.classic.deprecated
        def foo1():
            pass

        return foo1
    else:
        args, kwargs = request.param

        @deprecated.classic.deprecated(*args, **kwargs)
        def foo1():
            pass

        return foo1


@pytest.fixture(scope="module", params=_PARAMS)
def classic_deprecated_class(request):
    if request.param is None:

        @deprecated.classic.deprecated
        class Foo2(object):
            pass

        return Foo2
    else:
        args, kwargs = request.param

        @deprecated.classic.deprecated(*args, **kwargs)
        class Foo2(object):
            pass

        return Foo2


@pytest.fixture(scope="module", params=_PARAMS)
def classic_deprecated_method(request):
    if request.param is None:

        class Foo3(object):
            @deprecated.classic.deprecated
            def foo3(self):
                pass

        return Foo3
    else:
        args, kwargs = request.param

        class Foo3(object):
            @deprecated.classic.deprecated(*args, **kwargs)
            def foo3(self):
                pass

        return Foo3


@pytest.fixture(scope="module", params=_PARAMS)
def classic_deprecated_static_method(request):
    if request.param is None:

        class Foo4(object):
            @staticmethod
            @deprecated.classic.deprecated
            def foo4():
                pass

        return Foo4.foo4
    else:
        args, kwargs = request.param

        class Foo4(object):
            @staticmethod
            @deprecated.classic.deprecated(*args, **kwargs)
            def foo4():
                pass

        return Foo4.foo4


@pytest.fixture(scope="module", params=_PARAMS)
def classic_deprecated_class_method(request):
    if request.param is None:

        class Foo5(object):
            @classmethod
            @deprecated.classic.deprecated
            def foo5(cls):
                pass

        return Foo5
    else:
        args, kwargs = request.param

        class Foo5(object):
            @classmethod
            @deprecated.classic.deprecated(*args, **kwargs)
            def foo5(cls):
                pass

        return Foo5


# noinspection PyShadowingNames
def test_classic_deprecated_function__warns(classic_deprecated_function):
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        classic_deprecated_function()
    assert len(warns) == 1
    warn = warns[0]
    assert issubclass(warn.category, DeprecationWarning)
    assert "deprecated function (or staticmethod)" in str(warn.message)
    assert warn.filename == __file__, 'Incorrect warning stackLevel'


# noinspection PyShadowingNames
def test_classic_deprecated_class__warns(classic_deprecated_class):
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        classic_deprecated_class()
    assert len(warns) == 1
    warn = warns[0]
    assert issubclass(warn.category, DeprecationWarning)
    assert "deprecated class" in str(warn.message)
    assert warn.filename == __file__, 'Incorrect warning stackLevel'


# noinspection PyShadowingNames
def test_classic_deprecated_method__warns(classic_deprecated_method):
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        obj = classic_deprecated_method()
        obj.foo3()
    assert len(warns) == 1
    warn = warns[0]
    assert issubclass(warn.category, DeprecationWarning)
    assert "deprecated method" in str(warn.message)
    assert warn.filename == __file__, 'Incorrect warning stackLevel'


# noinspection PyShadowingNames
def test_classic_deprecated_static_method__warns(classic_deprecated_static_method):
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        classic_deprecated_static_method()
    assert len(warns) == 1
    warn = warns[0]
    assert issubclass(warn.category, DeprecationWarning)
    assert "deprecated function (or staticmethod)" in str(warn.message)
    assert warn.filename == __file__, 'Incorrect warning stackLevel'


# noinspection PyShadowingNames
def test_classic_deprecated_class_method__warns(classic_deprecated_class_method):
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        cls = classic_deprecated_class_method()
        cls.foo5()
    assert len(warns) == 1
    warn = warns[0]
    assert issubclass(warn.category, DeprecationWarning)
    if sys.version_info >= (3, 9):
        assert "deprecated class method" in str(warn.message)
    else:
        assert "deprecated function (or staticmethod)" in str(warn.message)
    assert warn.filename == __file__, 'Incorrect warning stackLevel'


def test_should_raise_type_error():
    try:
        deprecated.classic.deprecated(5)
        assert False, "TypeError not raised"
    except TypeError:
        pass


def test_warning_msg_has_reason():
    reason = "Good reason"

    @deprecated.classic.deprecated(reason=reason)
    def foo():
        pass

    with warnings.catch_warnings(record=True) as warns:
        foo()
    warn = warns[0]
    assert reason in str(warn.message)


def test_warning_msg_has_version():
    version = "1.2.3"

    @deprecated.classic.deprecated(version=version)
    def foo():
        pass

    with warnings.catch_warnings(record=True) as warns:
        foo()
    warn = warns[0]
    assert version in str(warn.message)


def test_warning_is_ignored():
    @deprecated.classic.deprecated(action='ignore')
    def foo():
        pass

    with warnings.catch_warnings(record=True) as warns:
        foo()
    assert len(warns) == 0


def test_specific_warning_cls_is_used():
    @deprecated.classic.deprecated(category=MyDeprecationWarning)
    def foo():
        pass

    with warnings.catch_warnings(record=True) as warns:
        foo()
    warn = warns[0]
    assert issubclass(warn.category, MyDeprecationWarning)


def test_respect_global_filter():
    @deprecated.classic.deprecated(version='1.2.1', reason="deprecated function")
    def fun():
        print("fun")

    warnings.simplefilter("once", category=DeprecationWarning)

    with warnings.catch_warnings(record=True) as warns:
        fun()
        fun()
    assert len(warns) == 1

--- class ---
# coding: utf-8
from __future__ import print_function

import inspect
import io
import warnings

import deprecated.classic


def test_simple_class_deprecation():
    # stream is used to store the deprecation message for testing
    stream = io.StringIO()

    # To deprecate a class, it is better to emit a message when ``__new__`` is called.
    # The simplest way is to override the ``__new__``method.
    class MyBaseClass(object):
        def __new__(cls, *args, **kwargs):
            print(u"I am deprecated!", file=stream)
            return super(MyBaseClass, cls).__new__(cls, *args, **kwargs)

    # Of course, the subclass will be deprecated too
    class MySubClass(MyBaseClass):
        pass

    obj = MySubClass()
    assert isinstance(obj, MyBaseClass)
    assert inspect.isclass(MyBaseClass)
    assert stream.getvalue().strip() == u"I am deprecated!"


def test_class_deprecation_using_wrapper():
    # stream is used to store the deprecation message for testing
    stream = io.StringIO()

    class MyBaseClass(object):
        pass

    # To deprecated the class, we use a wrapper function which emits
    # the deprecation message and calls ``__new__```.

    original_new = MyBaseClass.__new__

    def wrapped_new(unused, *args, **kwargs):
        print(u"I am deprecated!", file=stream)
        return original_new(*args, **kwargs)

    # Like ``__new__``, this wrapper is a class method.
    # It is used to patch the original ``__new__``method.
    MyBaseClass.__new__ = classmethod(wrapped_new)

    class MySubClass(MyBaseClass):
        pass

    obj = MySubClass()
    assert isinstance(obj, MyBaseClass)
    assert inspect.isclass(MyBaseClass)
    assert stream.getvalue().strip() == u"I am deprecated!"


def test_class_deprecation_using_a_simple_decorator():
    # stream is used to store the deprecation message for testing
    stream = io.StringIO()

    # To deprecated the class, we use a simple decorator
    # which patches the original ``__new__`` method.

    def simple_decorator(wrapped_cls):
        old_new = wrapped_cls.__new__

        def wrapped_new(unused, *args, **kwargs):
            print(u"I am deprecated!", file=stream)
            return old_new(*args, **kwargs)

        wrapped_cls.__new__ = classmethod(wrapped_new)
        return wrapped_cls

    @simple_decorator
    class MyBaseClass(object):
        pass

    class MySubClass(MyBaseClass):
        pass

    obj = MySubClass()
    assert isinstance(obj, MyBaseClass)
    assert inspect.isclass(MyBaseClass)
    assert stream.getvalue().strip() == u"I am deprecated!"


def test_class_deprecation_using_deprecated_decorator():
    @deprecated.classic.deprecated
    class MyBaseClass(object):
        pass

    class MySubClass(MyBaseClass):
        pass

    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        obj = MySubClass()

    assert len(warns) == 1
    assert isinstance(obj, MyBaseClass)
    assert inspect.isclass(MyBaseClass)
    assert issubclass(MySubClass, MyBaseClass)


def test_class_respect_global_filter():
    @deprecated.classic.deprecated
    class MyBaseClass(object):
        pass

    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("once")
        obj = MyBaseClass()
        obj = MyBaseClass()

    assert len(warns) == 1


def test_subclass_deprecation_using_deprecated_decorator():
    @deprecated.classic.deprecated
    class MyBaseClass(object):
        pass

    @deprecated.classic.deprecated
    class MySubClass(MyBaseClass):
        pass

    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        obj = MySubClass()

    assert len(warns) == 2
    assert isinstance(obj, MyBaseClass)
    assert inspect.isclass(MyBaseClass)
    assert issubclass(MySubClass, MyBaseClass)


def test_simple_class_deprecation_with_args():
    @deprecated.classic.deprecated('kwargs class')
    class MyClass(object):
        def __init__(self, arg):
            super(MyClass, self).__init__()
            self.args = arg

    MyClass(5)
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        obj = MyClass(5)

    assert len(warns) == 1
    assert isinstance(obj, MyClass)
    assert inspect.isclass(MyClass)

--- meta ---
# coding: utf-8
import warnings

import deprecated.classic


def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""

    # This requires a bit of explanation: the basic idea is to make a dummy
    # metaclass for one level of class instantiation that replaces itself with
    # the actual metaclass.
    class metaclass(type):
        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)

        @classmethod
        def __prepare__(cls, name, this_bases):
            return meta.__prepare__(name, bases)

    return type.__new__(metaclass, 'temporary_class', (), {})


def test_with_init():
    @deprecated.classic.deprecated
    class MyClass(object):
        def __init__(self, a, b=5):
            self.a = a
            self.b = b

    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        obj = MyClass("five")

    assert len(warns) == 1

    assert obj.a == "five"
    assert obj.b == 5


def test_with_new():
    @deprecated.classic.deprecated
    class MyClass(object):
        def __new__(cls, a, b=5):
            obj = super(MyClass, cls).__new__(cls)
            obj.c = 3.14
            return obj

        def __init__(self, a, b=5):
            self.a = a
            self.b = b

    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        obj = MyClass("five")

    assert len(warns) == 1

    assert obj.a == "five"
    assert obj.b == 5
    assert obj.c == 3.14


def test_with_metaclass():
    class Meta(type):
        def __call__(cls, *args, **kwargs):
            obj = super(Meta, cls).__call__(*args, **kwargs)
            obj.c = 3.14
            return obj

    @deprecated.classic.deprecated
    class MyClass(with_metaclass(Meta)):
        def __init__(self, a, b=5):
            self.a = a
            self.b = b

    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        obj = MyClass("five")

    assert len(warns) == 1

    assert obj.a == "five"
    assert obj.b == 5
    assert obj.c == 3.14


def test_with_singleton_metaclass():
    class Singleton(type):
        _instances = {}

        def __call__(cls, *args, **kwargs):
            if cls not in cls._instances:
                cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
            return cls._instances[cls]

    @deprecated.classic.deprecated
    class MyClass(with_metaclass(Singleton)):
        def __init__(self, a, b=5):
            self.a = a
            self.b = b

    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        obj1 = MyClass("five")
        obj2 = MyClass("six", b=6)

    # __new__ is called only once:
    # the instance is constructed only once,
    # so we have only one warning.
    assert len(warns) == 1

    assert obj1.a == "five"
    assert obj1.b == 5
    assert obj2 is obj1

--- sphinx ---
# coding: utf-8
from __future__ import print_function

import re
import sys
import textwrap
import warnings

import pytest

import deprecated.sphinx


@pytest.fixture(
    scope="module",
    params=[
        None,
        """This function adds *x* and *y*.""",
        """
        This function adds *x* and *y*.

        :param x: number *x*
        :param y: number *y*
        :return: sum = *x* + *y*
        """,
        """This function adds *x* and *y*.

        :param x: number *x*
        :param y: number *y*
        :return: sum = *x* + *y*
        """,
    ],
    ids=["no_docstring", "short_docstring", "D213_long_docstring", "D212_long_docstring"],
)
def docstring(request):
    return request.param


@pytest.fixture(scope="module", params=['versionadded', 'versionchanged', 'deprecated'])
def directive(request):
    ret<response clipped><NOTE>Due to the max output limit, only part of the full response has been shown to you.</NOTE>eprecated_function(request):
    kwargs = request.param

    @deprecated.sphinx.deprecated(**kwargs)
    def foo1():
        pass

    return foo1


@pytest.fixture(scope="module", params=_PARAMS)
def sphinx_deprecated_class(request):
    kwargs = request.param

    @deprecated.sphinx.deprecated(**kwargs)
    class Foo2(object):
        pass

    return Foo2


@pytest.fixture(scope="module", params=_PARAMS)
def sphinx_deprecated_method(request):
    kwargs = request.param

    class Foo3(object):
        @deprecated.sphinx.deprecated(**kwargs)
        def foo3(self):
            pass

    return Foo3


@pytest.fixture(scope="module", params=_PARAMS)
def sphinx_deprecated_static_method(request):
    kwargs = request.param

    class Foo4(object):
        @staticmethod
        @deprecated.sphinx.deprecated(**kwargs)
        def foo4():
            pass

    return Foo4.foo4


@pytest.fixture(scope="module", params=_PARAMS)
def sphinx_deprecated_class_method(request):
    kwargs = request.param

    class Foo5(object):
        @classmethod
        @deprecated.sphinx.deprecated(**kwargs)
        def foo5(cls):
            pass

    return Foo5


# noinspection PyShadowingNames
def test_sphinx_deprecated_function__warns(sphinx_deprecated_function):
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        sphinx_deprecated_function()
    assert len(warns) == 1
    warn = warns[0]
    assert issubclass(warn.category, DeprecationWarning)
    assert "deprecated function (or staticmethod)" in str(warn.message)


# noinspection PyShadowingNames
@pytest.mark.skipif(
    sys.version_info < (3, 3), reason="Classes should have mutable docstrings -- resolved in python 3.3"
)
def test_sphinx_deprecated_class__warns(sphinx_deprecated_class):
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        sphinx_deprecated_class()
    assert len(warns) == 1
    warn = warns[0]
    assert issubclass(warn.category, DeprecationWarning)
    assert "deprecated class" in str(warn.message)


# noinspection PyShadowingNames
def test_sphinx_deprecated_method__warns(sphinx_deprecated_method):
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        obj = sphinx_deprecated_method()
        obj.foo3()
    assert len(warns) == 1
    warn = warns[0]
    assert issubclass(warn.category, DeprecationWarning)
    assert "deprecated method" in str(warn.message)


# noinspection PyShadowingNames
def test_sphinx_deprecated_static_method__warns(sphinx_deprecated_static_method):
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        sphinx_deprecated_static_method()
    assert len(warns) == 1
    warn = warns[0]
    assert issubclass(warn.category, DeprecationWarning)
    assert "deprecated function (or staticmethod)" in str(warn.message)


# noinspection PyShadowingNames
def test_sphinx_deprecated_class_method__warns(sphinx_deprecated_class_method):
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        cls = sphinx_deprecated_class_method()
        cls.foo5()
    assert len(warns) == 1
    warn = warns[0]
    assert issubclass(warn.category, DeprecationWarning)
    if sys.version_info >= (3, 9):
        assert "deprecated class method" in str(warn.message)
    else:
        assert "deprecated function (or staticmethod)" in str(warn.message)


def test_should_raise_type_error():
    try:
        @deprecated.sphinx.deprecated(version="4.5.6", reason=5)
        def foo():
            pass

        assert False, "TypeError not raised"
    except TypeError:
        pass


def test_warning_msg_has_reason():
    reason = "Good reason"

    @deprecated.sphinx.deprecated(version="4.5.6", reason=reason)
    def foo():
        pass

    with warnings.catch_warnings(record=True) as warns:
        foo()
    warn = warns[0]
    assert reason in str(warn.message)


def test_warning_msg_has_version():
    version = "1.2.3"

    @deprecated.sphinx.deprecated(version=version)
    def foo():
        pass

    with warnings.catch_warnings(record=True) as warns:
        foo()
    warn = warns[0]
    assert version in str(warn.message)


def test_warning_is_ignored():
    @deprecated.sphinx.deprecated(version="4.5.6", action='ignore')
    def foo():
        pass

    with warnings.catch_warnings(record=True) as warns:
        foo()
    assert len(warns) == 0


def test_specific_warning_cls_is_used():
    @deprecated.sphinx.deprecated(version="4.5.6", category=MyDeprecationWarning)
    def foo():
        pass

    with warnings.catch_warnings(record=True) as warns:
        foo()
    warn = warns[0]
    assert issubclass(warn.category, MyDeprecationWarning)


def test_can_catch_warnings():
    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        warnings.warn("A message in a bottle", category=DeprecationWarning, stacklevel=2)
    assert len(warns) == 1


@pytest.mark.parametrize(
    ["reason", "expected"],
    [
        ("Use :function:`bar` instead", "Use `bar` instead"),
        ("Use :py:func:`bar` instead", "Use `bar` instead"),
    ],
)
def test_sphinx_syntax_trimming(reason, expected):
    @deprecated.sphinx.deprecated(version="4.5.6", reason=reason)
    def foo():
        pass

    with warnings.catch_warnings(record=True) as warns:
        foo()
    warn = warns[0]
    assert expected in str(warn.message)


# noinspection SpellCheckingInspection
@pytest.mark.parametrize(
    "reason, expected",
    [
        # classic examples using the default domain (Python)
        ("Use :func:`bar` instead", "Use `bar` instead"),
        ("Use :function:`bar` instead", "Use `bar` instead"),
        ("Use :class:`Baz` instead", "Use `Baz` instead"),
        ("Use :exc:`Baz` instead", "Use `Baz` instead"),
        ("Use :exception:`Baz` instead", "Use `Baz` instead"),
        ("Use :meth:`Baz.bar` instead", "Use `Baz.bar` instead"),
        ("Use :method:`Baz.bar` instead", "Use `Baz.bar` instead"),
        # other examples using a domain :
        ("Use :py:func:`bar` instead", "Use `bar` instead"),
        ("Use :cpp:func:`bar` instead", "Use `bar` instead"),
        ("Use :js:func:`bar` instead", "Use `bar` instead"),
        # the reference can have special characters:
        ("Use :func:`~pkg.mod.bar` instead", "Use `~pkg.mod.bar` instead"),
        # edge cases:
        ("Use :r:`` instead", "Use `` instead"),
        ("Use :d:r:`` instead", "Use `` instead"),
        ("Use :r:`foo` instead", "Use `foo` instead"),
        ("Use :d:r:`foo` instead", "Use `foo` instead"),
        ("Use r:`bad` instead", "Use r:`bad` instead"),
        ("Use ::`bad` instead", "Use ::`bad` instead"),
        ("Use :::`bad` instead", "Use :::`bad` instead"),
    ],
)
def test_get_deprecated_msg(reason, expected):
    adapter = deprecated.sphinx.SphinxAdapter("deprecated", reason=reason, version="1")
    actual = adapter.get_deprecated_msg(lambda: None, None)
    assert expected in actual

--- sphinx class ---
# coding: utf-8
from __future__ import print_function

import inspect
import io
import sys
import warnings

import pytest

import deprecated.sphinx


def test_class_deprecation_using_a_simple_decorator():
    # stream is used to store the deprecation message for testing
    stream = io.StringIO()

    # To deprecated the class, we use a simple decorator
    # which patches the original ``__new__`` method.

    def simple_decorator(wrapped_cls):
        old_new = wrapped_cls.__new__

        def wrapped_new(unused, *args, **kwargs):
            print(u"I am deprecated!", file=stream)
            return old_new(*args, **kwargs)

        wrapped_cls.__new__ = classmethod(wrapped_new)
        return wrapped_cls

    @simple_decorator
    class MyBaseClass(object):
        pass

    class MySubClass(MyBaseClass):
        pass

    obj = MySubClass()
    assert isinstance(obj, MyBaseClass)
    assert inspect.isclass(MyBaseClass)
    assert stream.getvalue().strip() == u"I am deprecated!"


@pytest.mark.skipif(
    sys.version_info < (3, 3), reason="Classes should have mutable docstrings -- resolved in python 3.3"
)
def test_class_deprecation_using_deprecated_decorator():
    @deprecated.sphinx.deprecated(version="7.8.9")
    class MyBaseClass(object):
        pass

    class MySubClass(MyBaseClass):
        pass

    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        obj = MySubClass()

    assert len(warns) == 1
    assert isinstance(obj, MyBaseClass)
    assert inspect.isclass(MyBaseClass)
    assert issubclass(MySubClass, MyBaseClass)


@pytest.mark.skipif(
    sys.version_info < (3, 3), reason="Classes should have mutable docstrings -- resolved in python 3.3"
)
def test_subclass_deprecation_using_deprecated_decorator():
    @deprecated.sphinx.deprecated(version="7.8.9")
    class MyBaseClass(object):
        pass

    @deprecated.sphinx.deprecated(version="7.8.9")
    class MySubClass(MyBaseClass):
        pass

    with warnings.catch_warnings(record=True) as warns:
        warnings.simplefilter("always")
        obj = MySubClass()

    assert len(warns) == 2
    assert isinstance(obj, MyBaseClass)
    assert inspect.isclass(MyBaseClass)
    assert issubclass(MySubClass, MyBaseClass)


@pytest.mark.skipif(
    sys.version_info < (3, 3), reason="Classes should have mutable docstrings -- resolved in python 3.3"
)
def test_isinstance_versionadded():
    # https://github.com/tantale/deprecated/issues/48
    @deprecated.sphinx.versionadded(version="X.Y", reason="some reason")
    class VersionAddedCls:
        pass

    @deprecated.sphinx.versionadded(version="X.Y", reason="some reason")
    class VersionAddedChildCls(VersionAddedCls):
        pass

    instance = VersionAddedChildCls()
    assert isinstance(instance, VersionAddedChildCls)
    assert isinstance(instance, VersionAddedCls)


@pytest.mark.skipif(
    sys.version_info < (3, 3), reason="Classes should have mutable docstrings -- resolved in python 3.3"
)
def test_isinstance_versionchanged():
    @deprecated.sphinx.versionchanged(version="X.Y", reason="some reason")
    class VersionChangedCls:
        pass

    @deprecated.sphinx.versionchanged(version="X.Y", reason="some reason")
    class VersionChangedChildCls(VersionChangedCls):
        pass

    instance = VersionChangedChildCls()
    assert isinstance(instance, VersionChangedChildCls)
    assert isinstance(instance, VersionChangedCls)


@pytest.mark.skipif(
    sys.version_info < (3, 3), reason="Classes should have mutable docstrings -- resolved in python 3.3"
)
def test_isinstance_deprecated():
    @deprecated.sphinx.deprecated(version="X.Y", reason="some reason")
    class DeprecatedCls:
        pass

    @deprecated.sphinx.deprecated(version="Y.Z", reason="some reason")
    class DeprecatedChildCls(DeprecatedCls):
        pass

    instance = DeprecatedChildCls()
    assert isinstance(instance, DeprecatedChildCls)
    assert isinstance(instance, DeprecatedCls)


@pytest.mark.skipif(
    sys.version_info < (3, 3), reason="Classes should have mutable docstrings -- resolved in python 3.3"
)
def test_isinstance_versionadded_versionchanged():
    @deprecated.sphinx.versionadded(version="X.Y")
    @deprecated.sphinx.versionchanged(version="X.Y.Z")
    class AddedChangedCls:
        pass

    instance = AddedChangedCls()
    assert isinstance(instance, AddedChangedCls)

--- adapter ---
# coding: utf-8
import textwrap

import pytest

from deprecated.sphinx import SphinxAdapter
from deprecated.sphinx import deprecated
from deprecated.sphinx import versionadded
from deprecated.sphinx import versionchanged


@pytest.mark.parametrize(
    "line_length, expected",
    [
        (
            50,
            textwrap.dedent(
                """
                Description of foo

                :return: nothing

                .. {directive}:: 1.2.3
                   foo has changed in this version

                   bar bar bar bar bar bar bar bar bar bar bar
                   bar bar bar bar bar bar bar bar bar bar bar
                   bar
                """
            ),
        ),
        (
            0,
            textwrap.dedent(
                """
                Description of foo

                :return: nothing

                .. {directive}:: 1.2.3
                   foo has changed in this version

                   bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar bar
                """
            ),
        ),
    ],
    ids=["wrapped", "long"],
)
@pytest.mark.parametrize("directive", ["versionchanged", "versionadded", "deprecated"])
def test_sphinx_adapter(directive, line_length, expected):
    lines = [
        "foo has changed in this version",
        "",  # newline
        "bar " * 23,  # long line
        "",  # trailing newline
    ]
    reason = "\n".join(lines)
    adapter = SphinxAdapter(directive, reason=reason, version="1.2.3", line_length=line_length)

    def foo():
        """
        Description of foo

        :return: nothing
        """

    wrapped = adapter.__call__(foo)
    expected = expected.format(directive=directive)
    assert wrapped.__doc__ == expected


@pytest.mark.parametrize("directive", ["versionchanged", "versionadded", "deprecated"])
def test_sphinx_adapter__empty_docstring(directive):
    lines = [
        "foo has changed in this version",
        "",  # newline
        "bar " * 23,  # long line
        "",  # trailing newline
    ]
    reason = "\n".join(lines)
    adapter = SphinxAdapter(directive, reason=reason, version="1.2.3", line_length=50)

    def foo():
        pass

    wrapped = adapter.__call__(foo)
    expected = textwrap.dedent(
        """
        .. {directive}:: 1.2.3
           foo has changed in this version

           bar bar bar bar bar bar bar bar bar bar bar
           bar bar bar bar bar bar bar bar bar bar bar
           bar
        """
    )
    expected = expected.format(directive=directive)
    assert wrapped.__doc__ == expected


@pytest.mark.parametrize(
    "decorator_factory, directive",
    [
        (versionadded, "versionadded"),
        (versionchanged, "versionchanged"),
        (deprecated, "deprecated"),
    ],
)
def test_decorator_accept_line_length(decorator_factory, directive):
    reason = "bar " * 30
    decorator = decorator_factory(reason=reason, version="1.2.3", line_length=50)

    def foo():
        pass

    foo = decorator(foo)

    expected = textwrap.dedent(
        """
        .. {directive}:: 1.2.3
           bar bar bar bar bar bar bar bar bar bar bar
           bar bar bar bar bar bar bar bar bar bar bar
           bar bar bar bar bar bar bar bar
        """
    )
    expected = expected.format(directive=directive)
    assert foo.__doc__ == expected
[The command completed with exit code 0.]
[Current working directory: /workspace/deprecated]
[Python interpreter: /usr/bin/python]
[Command finished with exit code 0]