# Copyright (c) 2010-2011 testtools developers. See LICENSE for details.

import unittest

from testtools import (
    TestCase,
    content,
    content_type,
)
from testtools.compat import _b
from testtools.matchers import (
    Contains,
    Equals,
)
from testtools.testresult.doubles import (
    ExtendedTestResult,
)

try:
    import fixtures
except ImportError:
    fixtures = None

try:
    from fixtures.tests.helpers import LoggingFixture
except ImportError:
    LoggingFixture = None


class TestFixtureSupport(TestCase):
    def setUp(self):
        super().setUp()
        if fixtures is None or LoggingFixture is None:
            self.skipTest("Need fixtures")

    def test_useFixture(self):
        fixture = LoggingFixture()

        class SimpleTest(TestCase):
            def test_foo(self):
                self.useFixture(fixture)

        result = unittest.TestResult()
        SimpleTest("test_foo").run(result)
        self.assertTrue(result.wasSuccessful())
        self.assertEqual(["setUp", "cleanUp"], fixture.calls)

    def test_useFixture_cleanups_raise_caught(self):
        calls = []

        def raiser(ignored):
            calls.append("called")
            raise Exception("foo")

        fixture = fixtures.FunctionFixture(lambda: None, raiser)

        class SimpleTest(TestCase):
            def test_foo(self):
                self.useFixture(fixture)

        result = unittest.TestResult()
        SimpleTest("test_foo").run(result)
        self.assertFalse(result.wasSuccessful())
        self.assertEqual(["called"], calls)

    def test_useFixture_details_captured(self):
        class DetailsFixture(fixtures.Fixture):
            def setUp(self):
                fixtures.Fixture.setUp(self)
                self.addCleanup(delattr, self, "content")
                self.content = [_b("content available until cleanUp")]
                self.addDetail(
                    "content", content.Content(content_type.UTF8_TEXT, self.get_content)
                )

            def get_content(self):
                return self.content

        fixture = DetailsFixture()

        class SimpleTest(TestCase):
            def test_foo(self):
                self.useFixture(fixture)
                # Add a colliding detail (both should show up)
                self.addDetail(
                    "content",
                    content.Content(content_type.UTF8_TEXT, lambda: [_b("foo")]),
                )

        result = ExtendedTestResult()
        SimpleTest("test_foo").run(result)
        self.assertEqual("addSuccess", result._events[-2][0])
        details = result._events[-2][2]
        self.assertEqual(["content", "content-1"], sorted(details.keys()))
        self.assertEqual("foo", details["content"].as_text())
        self.assertEqual(
            "content available until cleanUp", details["content-1"].as_text()
        )

    def test_useFixture_multiple_details_captured(self):
        class DetailsFixture(fixtures.Fixture):
            def setUp(self):
                fixtures.Fixture.setUp(self)
                self.addDetail("aaa", content.text_content("foo"))
                self.addDetail("bbb", content.text_content("bar"))

        fixture = DetailsFixture()

        class SimpleTest(TestCase):
            def test_foo(self):
                self.useFixture(fixture)

        result = ExtendedTestResult()
        SimpleTest("test_foo").run(result)
        self.assertEqual("addSuccess", result._events[-2][0])
        details = result._events[-2][2]
        self.assertEqual(["aaa", "bbb"], sorted(details))
        self.assertEqual("foo", details["aaa"].as_text())
        self.assertEqual("bar", details["bbb"].as_text())

    def test_useFixture_details_captured_from_setUp(self):
        # Details added during fixture set-up are gathered even if setUp()
        # fails with an exception.
        class BrokenFixture(fixtures.Fixture):
            def setUp(self):
                fixtures.Fixture.setUp(self)
                self.addDetail("content", content.text_content("foobar"))
                raise Exception

        fixture = BrokenFixture()

        class SimpleTest(TestCase):
            def test_foo(self):
                self.useFixture(fixture)

        result = ExtendedTestResult()
        SimpleTest("test_foo").run(result)
        self.assertEqual("addError", result._events[-2][0])
        details = result._events[-2][2]
        self.assertEqual(["content", "traceback"], sorted(details))
        self.assertEqual("foobar", "".join(details["content"].iter_text()))

    def test_useFixture_details_captured_from__setUp(self):
        # Newer Fixtures deprecates setUp() in favour of _setUp().
        # https://bugs.launchpad.net/testtools/+bug/1469759 reports that
        # this is broken when gathering details from a broken _setUp().
        class BrokenFixture(fixtures.Fixture):
            def _setUp(self):
                fixtures.Fixture._setUp(self)
                self.addDetail("broken", content.text_content("foobar"))
                raise Exception("_setUp broke")

        fixture = BrokenFixture()

        class SimpleTest(TestCase):
            def test_foo(self):
                self.addDetail("foo_content", content.text_content("foo ok"))
                self.useFixture(fixture)

        result = ExtendedTestResult()
        SimpleTest("test_foo").run(result)
        self.assertEqual("addError", result._events[-2][0])
        details = result._events[-2][2]
        self.assertEqual(
            ["broken", "foo_content", "traceback", "traceback-1"], sorted(details)
        )
        self.expectThat("".join(details["broken"].iter_text()), Equals("foobar"))
        self.expectThat("".join(details["foo_content"].iter_text()), Equals("foo ok"))
        self.expectThat(
            "".join(details["traceback"].iter_text()), Contains("_setUp broke")
        )
        self.expectThat("".join(details["traceback-1"].iter_text()), Contains("foobar"))

    def test_useFixture_original_exception_raised_if_gather_details_fails(self):
        # In bug #1368440 it was reported that when a fixture fails setUp
        # and gather_details errors on it, then the original exception that
        # failed is not reported.
        class BrokenFixture(fixtures.Fixture):
            def getDetails(self):
                raise AttributeError("getDetails broke")

            def setUp(self):
                fixtures.Fixture.setUp(self)
                raise Exception("setUp broke")

        fixture = BrokenFixture()

        class SimpleTest(TestCase):
            def test_foo(self):
                self.useFixture(fixture)

        result = ExtendedTestResult()
        SimpleTest("test_foo").run(result)
        self.assertEqual("addError", result._events[-2][0])
        details = result._events[-2][2]
        self.assertEqual(["traceback", "traceback-1"], sorted(details))
        self.assertThat(
            "".join(details["traceback"].iter_text()), Contains("setUp broke")
        )
        self.assertThat(
            "".join(details["traceback-1"].iter_text()), Contains("getDetails broke")
        )


def test_suite():
    from unittest import TestLoader

    return TestLoader().loadTestsFromName(__name__)
