"""Tests for the decorators we've created for IPython."""

# Module imports
# Std lib
import inspect
import sys

# Our own
from IPython.testing import decorators as dec
from IPython.testing.skipdoctest import skip_doctest
from IPython.utils.text import dedent

# -----------------------------------------------------------------------------
# Utilities


# Note: copied from OInspect, kept here so the testing stuff doesn't create
# circular dependencies and is easier to reuse.
def getargspec(obj):
    """Get the names and default values of a function's arguments.

    A tuple of four things is returned: (args, varargs, varkw, defaults).
    'args' is a list of the argument names (it may contain nested lists).
    'varargs' and 'varkw' are the names of the * and ** arguments or None.
    'defaults' is an n-tuple of the default values of the last n arguments.

    Modified version of inspect.getargspec from the Python Standard
    Library."""

    if inspect.isfunction(obj):
        func_obj = obj
    elif inspect.ismethod(obj):
        func_obj = obj.__func__
    else:
        raise TypeError("arg is not a Python function")
    args, varargs, varkw = inspect.getargs(func_obj.__code__)
    return args, varargs, varkw, func_obj.__defaults__


# -----------------------------------------------------------------------------
# Testing functions


def test_trivial():
    """A trivial test"""
    pass


@dec.skip()
def test_deliberately_broken():
    """A deliberately broken test - we want to skip this one."""
    1 / 0


@dec.skip("Testing the skip decorator")
def test_deliberately_broken2():
    """Another deliberately broken test - we want to skip this one."""
    1 / 0


# Verify that we can correctly skip the doctest for a function at will, but
# that the docstring itself is NOT destroyed by the decorator.
@skip_doctest
def doctest_bad(x, y=1, **k):
    """A function whose doctest we need to skip.

    >>> 1+1
    3
    """
    print("x:", x)
    print("y:", y)
    print("k:", k)


def call_doctest_bad():
    """Check that we can still call the decorated functions.

    >>> doctest_bad(3,y=4)
    x: 3
    y: 4
    k: {}
    """
    pass


def test_skip_dt_decorator():
    """Doctest-skipping decorator should preserve the docstring."""
    # Careful: 'check' must be a *verbatim* copy of the doctest_bad docstring!
    check = """A function whose doctest we need to skip.

    >>> 1+1
    3
    """

    # Fetch the docstring from doctest_bad after decoration.
    val = doctest_bad.__doc__

    assert dedent(check) == dedent(val), "doctest_bad docstrings don't match"


# Doctest skipping should work for class methods too
class FooClass(object):
    """FooClass

    Example:

    >>> 1+1
    2
    """

    @skip_doctest
    def __init__(self, x):
        """Make a FooClass.

        Example:

        >>> f = FooClass(3)
        junk
        """
        print("Making a FooClass.")
        self.x = x

    @skip_doctest
    def bar(self, y):
        """Example:

        >>> ff = FooClass(3)
        >>> ff.bar(0)
        boom!
        >>> 1/0
        bam!
        """
        return 1 / y

    def baz(self, y):
        """Example:

        >>> ff2 = FooClass(3)
        Making a FooClass.
        >>> ff2.baz(3)
        True
        """
        return self.x == y


def test_skip_dt_decorator2():
    """Doctest-skipping decorator should preserve function signature."""
    # Hardcoded correct answer
    dtargs = (["x", "y"], None, "k", (1,))
    # Introspect out the value
    dtargsr = getargspec(doctest_bad)
    assert dtargsr == dtargs, "Incorrectly reconstructed args for doctest_bad: %s" % (
        dtargsr,
    )
