"""Test correct setup/teardowns at module, class, and instance level."""
from typing import List

import pytest
from _pytest.pytester import Pytester


def test_module_and_function_setup(pytester: Pytester) -> None:
    reprec = pytester.inline_runsource(
        """
        modlevel = []
        def setup_module(module):
            assert not modlevel
            module.modlevel.append(42)

        def teardown_module(module):
            modlevel.pop()

        def setup_function(function):
            function.answer = 17

        def teardown_function(function):
            del function.answer

        def test_modlevel():
            assert modlevel[0] == 42
            assert test_modlevel.answer == 17

        class TestFromClass(object):
            def test_module(self):
                assert modlevel[0] == 42
                assert not hasattr(test_modlevel, 'answer')
    """
    )
    rep = reprec.matchreport("test_modlevel")
    assert rep.passed
    rep = reprec.matchreport("test_module")
    assert rep.passed


def test_module_setup_failure_no_teardown(pytester: Pytester) -> None:
    reprec = pytester.inline_runsource(
        """
        values = []
        def setup_module(module):
            values.append(1)
            0/0

        def test_nothing():
            pass

        def teardown_module(module):
            values.append(2)
    """
    )
    reprec.assertoutcome(failed=1)
    calls = reprec.getcalls("pytest_runtest_setup")
    assert calls[0].item.module.values == [1]


def test_setup_function_failure_no_teardown(pytester: Pytester) -> None:
    reprec = pytester.inline_runsource(
        """
        modlevel = []
        def setup_function(function):
            modlevel.append(1)
            0/0

        def teardown_function(module):
            modlevel.append(2)

        def test_func():
            pass
    """
    )
    calls = reprec.getcalls("pytest_runtest_setup")
    assert calls[0].item.module.modlevel == [1]


def test_class_setup(pytester: Pytester) -> None:
    reprec = pytester.inline_runsource(
        """
        class TestSimpleClassSetup(object):
            clslevel = []
            def setup_class(cls):
                cls.clslevel.append(23)

            def teardown_class(cls):
                cls.clslevel.pop()

            def test_classlevel(self):
                assert self.clslevel[0] == 23

        class TestInheritedClassSetupStillWorks(TestSimpleClassSetup):
            def test_classlevel_anothertime(self):
                assert self.clslevel == [23]

        def test_cleanup():
            assert not TestSimpleClassSetup.clslevel
            assert not TestInheritedClassSetupStillWorks.clslevel
    """
    )
    reprec.assertoutcome(passed=1 + 2 + 1)


def test_class_setup_failure_no_teardown(pytester: Pytester) -> None:
    reprec = pytester.inline_runsource(
        """
        class TestSimpleClassSetup(object):
            clslevel = []
            def setup_class(cls):
                0/0

            def teardown_class(cls):
                cls.clslevel.append(1)

            def test_classlevel(self):
                pass

        def test_cleanup():
            assert not TestSimpleClassSetup.clslevel
    """
    )
    reprec.assertoutcome(failed=1, passed=1)


def test_method_setup(pytester: Pytester) -> None:
    reprec = pytester.inline_runsource(
        """
        class TestSetupMethod(object):
            def setup_method(self, meth):
                self.methsetup = meth
            def teardown_method(self, meth):
                del self.methsetup

            def test_some(self):
                assert self.methsetup == self.test_some

            def test_other(self):
                assert self.methsetup == self.test_other
    """
    )
    reprec.assertoutcome(passed=2)


def test_method_setup_failure_no_teardown(pytester: Pytester) -> None:
    reprec = pytester.inline_runsource(
        """
        class TestMethodSetup(object):
            clslevel = []
            def setup_method(self, method):
                self.clslevel.append(1)
                0/0

            def teardown_method(self, method):
                self.clslevel.append(2)

            def test_method(self):
                pass

        def test_cleanup():
            assert TestMethodSetup.clslevel == [1]
    """
    )
    reprec.assertoutcome(failed=1, passed=1)


def test_method_setup_uses_fresh_instances(pytester: Pytester) -> None:
    reprec = pytester.inline_runsource(
        """
        class TestSelfState1(object):
            memory = []
            def test_hello(self):
                self.memory.append(self)

            def test_afterhello(self):
                assert self != self.memory[0]
    """
    )
    reprec.assertoutcome(passed=2, failed=0)


def test_setup_that_skips_calledagain(pytester: Pytester) -> None:
    p = pytester.makepyfile(
        """
        import pytest
        def setup_module(mod):
            pytest.skip("x")
        def test_function1():
            pass
        def test_function2():
            pass
    """
    )
    reprec = pytester.inline_run(p)
    reprec.assertoutcome(skipped=2)


def test_setup_fails_again_on_all_tests(pytester: Pytester) -> None:
    p = pytester.makepyfile(
        """
        import pytest
        def setup_module(mod):
            raise ValueError(42)
        def test_function1():
            pass
        def test_function2():
            pass
    """
    )
    reprec = pytester.inline_run(p)
    reprec.assertoutcome(failed=2)


def test_setup_funcarg_setup_when_outer_scope_fails(pytester: Pytester) -> None:
    p = pytester.makepyfile(
        """
        import pytest
        def setup_module(mod):
            raise ValueError(42)
        @pytest.fixture
        def hello(request):
            raise ValueError("xyz43")
        def test_function1(hello):
            pass
        def test_function2(hello):
            pass
    """
    )
    result = pytester.runpytest(p)
    result.stdout.fnmatch_lines(
        [
            "*function1*",
            "*ValueError*42*",
            "*function2*",
            "*ValueError*42*",
            "*2 errors*",
        ]
    )
    result.stdout.no_fnmatch_line("*xyz43*")


@pytest.mark.parametrize("arg", ["", "arg"])
def test_setup_teardown_function_level_with_optional_argument(
    pytester: Pytester,
    monkeypatch,
    arg: str,
) -> None:
    """Parameter to setup/teardown xunit-style functions parameter is now optional (#1728)."""
    import sys

    trace_setups_teardowns: List[str] = []
    monkeypatch.setattr(
        sys, "trace_setups_teardowns", trace_setups_teardowns, raising=False
    )
    p = pytester.makepyfile(
        """
        import pytest
        import sys

        trace = sys.trace_setups_teardowns.append

        def setup_module({arg}): trace('setup_module')
        def teardown_module({arg}): trace('teardown_module')

        def setup_function({arg}): trace('setup_function')
        def teardown_function({arg}): trace('teardown_function')

        def test_function_1(): pass
        def test_function_2(): pass

        class Test(object):
            def setup_method(self, {arg}): trace('setup_method')
            def teardown_method(self, {arg}): trace('teardown_method')

            def test_method_1(self): pass
            def test_method_2(self): pass
    """.format(
            arg=arg
        )
    )
    result = pytester.inline_run(p)
    result.assertoutcome(passed=4)

    expected = [
        "setup_module",
        "setup_function",
        "teardown_function",
        "setup_function",
        "teardown_function",
        "setup_method",
        "teardown_method",
        "setup_method",
        "teardown_method",
        "teardown_module",
    ]
    assert trace_setups_teardowns == expected
