package org.junit.tests.running.methods;

import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertThat;

import java.util.Collection;
import java.util.HashSet;

import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.Description;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

public class AnnotationTest extends TestCase {
    static boolean run;

    @Override
    public void setUp() {
        run = false;
    }

    static public class SimpleTest {
        @Test
        public void success() {
            run = true;
        }
    }

    public void testAnnotatedMethod() throws Exception {
        JUnitCore runner = new JUnitCore();
        runner.run(SimpleTest.class);
        assertTrue(run);
    }

    @RunWith(JUnit4.class)
    static public class SimpleTestWithFutureProofExplicitRunner {
        @Test
        public void success() {
            run = true;
        }
    }

    public void testAnnotatedMethodWithFutureProofExplicitRunner() throws Exception {
        JUnitCore runner = new JUnitCore();
        runner.run(SimpleTestWithFutureProofExplicitRunner.class);
        assertTrue(run);
    }

    static public class SetupTest {
        @Before
        public void before() {
            run = true;
        }

        @Test
        public void success() {
        }
    }

    public void testSetup() throws Exception {
        JUnitCore runner = new JUnitCore();
        runner.run(SetupTest.class);
        assertTrue(run);
    }

    static public class TeardownTest {
        @After
        public void after() {
            run = true;
        }

        @Test
        public void success() {
        }
    }

    public void testTeardown() throws Exception {
        JUnitCore runner = new JUnitCore();
        runner.run(TeardownTest.class);
        assertTrue(run);
    }

    static public class FailureTest {
        @Test
        public void error() throws Exception {
            org.junit.Assert.fail();
        }
    }

    public void testRunFailure() throws Exception {
        JUnitCore runner = new JUnitCore();
        Result result = runner.run(FailureTest.class);
        assertEquals(1, result.getRunCount());
        assertEquals(1, result.getFailureCount());
        assertEquals(AssertionError.class, result.getFailures().get(0).getException().getClass());
    }

    static public class SetupFailureTest {
        @Before
        public void before() {
            throw new Error();
        }

        @Test
        public void test() {
            run = true;
        }
    }

    public void testSetupFailure() throws Exception {
        JUnitCore core = new JUnitCore();
        Result runner = core.run(SetupFailureTest.class);
        assertEquals(1, runner.getRunCount());
        assertEquals(1, runner.getFailureCount());
        assertEquals(Error.class, runner.getFailures().get(0).getException().getClass());
        assertFalse(run);
    }

    static public class TeardownFailureTest {
        @After
        public void after() {
            throw new Error();
        }

        @Test
        public void test() {
        }
    }

    public void testTeardownFailure() throws Exception {
        JUnitCore core = new JUnitCore();
        Result runner = core.run(TeardownFailureTest.class);
        assertEquals(1, runner.getRunCount());
        assertEquals(1, runner.getFailureCount());
        assertEquals(Error.class, runner.getFailures().get(0).getException().getClass());
    }

    static public class TestAndTeardownFailureTest {
        @After
        public void after() {
            throw new Error("hereAfter");
        }

        @Test
        public void test() throws Exception {
            throw new Exception("inTest");
        }
    }

    public void testTestAndTeardownFailure() throws Exception {
        JUnitCore core = new JUnitCore();
        Result runner = core.run(TestAndTeardownFailureTest.class);
        assertEquals(1, runner.getRunCount());
        assertEquals(2, runner.getFailureCount());
        assertThat(runner.getFailures().toString(), allOf(containsString("hereAfter"), containsString("inTest")));
    }

    static public class TeardownAfterFailureTest {
        @After
        public void after() {
            run = true;
        }

        @Test
        public void test() throws Exception {
            throw new Exception();
        }
    }

    public void testTeardownAfterFailure() throws Exception {
        JUnitCore runner = new JUnitCore();
        runner.run(TeardownAfterFailureTest.class);
        assertTrue(run);
    }

    static int count;
    static Collection<Object> tests;

    static public class TwoTests {
        @Test
        public void one() {
            count++;
            tests.add(this);
        }

        @Test
        public void two() {
            count++;
            tests.add(this);
        }
    }

    public void testTwoTests() throws Exception {
        count = 0;
        tests = new HashSet<Object>();
        JUnitCore runner = new JUnitCore();
        runner.run(TwoTests.class);
        assertEquals(2, count);
        assertEquals(2, tests.size());
    }

    static public class OldTest extends TestCase {
        public void test() {
            run = true;
        }
    }

    public void testOldTest() throws Exception {
        JUnitCore runner = new JUnitCore();
        runner.run(OldTest.class);
        assertTrue(run);
    }

    static public class OldSuiteTest extends TestCase {
        public void testOne() {
            run = true;
        }
    }

    public void testOldSuiteTest() throws Exception {
        TestSuite suite = new TestSuite(OldSuiteTest.class);
        JUnitCore runner = new JUnitCore();
        runner.run(suite);
        assertTrue(run);
    }

    static public class ExceptionTest {
        @Test(expected = Error.class)
        public void expectedException() {
            throw new Error();
        }
    }

    public void testException() throws Exception {
        JUnitCore core = new JUnitCore();
        Result result = core.run(ExceptionTest.class);
        assertEquals(0, result.getFailureCount());
    }

    static public class NoExceptionTest {
        @Test(expected = Error.class)
        public void expectedException() {
        }
    }

    public void testExceptionNotThrown() throws Exception {
        JUnitCore core = new JUnitCore();
        Result result = core.run(NoExceptionTest.class);
        assertEquals(1, result.getFailureCount());
        assertEquals("Expected exception: java.lang.Error", result.getFailures().get(0).getMessage());
    }

    static public class OneTimeSetup {
        @BeforeClass
        public static void once() {
            count++;
        }

        @Test
        public void one() {
        }

        @Test
        public void two() {
        }
    }

    public void testOneTimeSetup() throws Exception {
        count = 0;
        JUnitCore core = new JUnitCore();
        core.run(OneTimeSetup.class);
        assertEquals(1, count);
    }

    static public class OneTimeTeardown {
        @AfterClass
        public static void once() {
            count++;
        }

        @Test
        public void one() {
        }

        @Test
        public void two() {
        }
    }

    public void testOneTimeTeardown() throws Exception {
        count = 0;
        JUnitCore core = new JUnitCore();
        core.run(OneTimeTeardown.class);
        assertEquals(1, count);
    }

    static String log;

    public static class OrderTest {
        @BeforeClass
        public static void onceBefore() {
            log += "beforeClass ";
        }

        @Before
        public void before() {
            log += "before ";
        }

        @Test
        public void test() {
            log += "test ";
        }

        @After
        public void after() {
            log += "after ";
        }

        @AfterClass
        public static void onceAfter() {
            log += "afterClass ";
        }
    }

    public void testOrder() throws Exception {
        log = "";
        JUnitCore core = new JUnitCore();
        core.run(OrderTest.class);
        assertEquals("beforeClass before test after afterClass ", log);
    }

    static public class NonStaticOneTimeSetup {
        @BeforeClass
        public void once() {
        }

        @Test
        public void aTest() {
        }
    }

    public void testNonStaticOneTimeSetup() throws Exception {
        JUnitCore core = new JUnitCore();
        Result result = core.run(NonStaticOneTimeSetup.class);
        assertEquals(1, result.getFailureCount());
    }

    static public class ErrorInBeforeClass {
        @BeforeClass
        public static void before() throws Exception {
            throw new Exception();
        }

        @Test
        public void test() {
            run = true;
        }
    }

    public void testErrorInBeforeClass() throws Exception {
        run = false;
        JUnitCore core = new JUnitCore();
        Result result = core.run(ErrorInBeforeClass.class);
        assertFalse(run);
        assertEquals(1, result.getFailureCount());
        Description description = result.getFailures().get(0).getDescription();
        assertEquals(ErrorInBeforeClass.class.getName(), description.getDisplayName());
    }

    static public class ErrorInAfterClass {
        @Test
        public void test() {
            run = true;
        }

        @AfterClass
        public static void after() throws Exception {
            throw new Exception();
        }
    }

    public void testErrorInAfterClass() throws Exception {
        run = false;
        JUnitCore core = new JUnitCore();
        Result result = core.run(ErrorInAfterClass.class);
        assertTrue(run);
        assertEquals(1, result.getFailureCount());
    }

    static public class SuperInheritance {
        @BeforeClass
        static public void beforeClassSuper() {
            log += "Before class super ";
        }

        @AfterClass
        static public void afterClassSuper() {
            log += "After class super ";
        }

        @Before
        public void beforeSuper() {
            log += "Before super ";
        }

        @After
        public void afterSuper() {
            log += "After super ";
        }
    }

    static public class SubInheritance extends SuperInheritance {
        @BeforeClass
        static public void beforeClassSub() {
            log += "Before class sub ";
        }

        @AfterClass
        static public void afterClassSub() {
            log += "After class sub ";
        }

        @Before
        public void beforeSub() {
            log += "Before sub ";
        }

        @After
        public void afterSub() {
            log += "After sub ";
        }

        @Test
        public void test() {
            log += "Test ";
        }
    }

    public void testOrderingOfInheritance() throws Exception {
        log = "";
        JUnitCore core = new JUnitCore();
        core.run(SubInheritance.class);
        assertEquals("Before class super Before class sub Before super Before sub Test After sub After super After class sub After class super ", log);
    }

    static public class SuperShadowing {
        @Before
        public void before() {
            log += "Before super ";
        }

        @After
        public void after() {
            log += "After super ";
        }
    }

    static public class SubShadowing extends SuperShadowing {
        @Override
        @Before
        public void before() {
            log += "Before sub ";
        }

        @Override
        @After
        public void after() {
            log += "After sub ";
        }

        @Test
        public void test() {
            log += "Test ";
        }
    }

    public void testShadowing() throws Exception {
        log = "";
        JUnitCore core = new JUnitCore();
        core.run(SubShadowing.class);
        assertEquals("Before sub Test After sub ", log);
    }

    static public class SuperTest {
        @Test
        public void one() {
            log += "Super";
        }

        @Test
        public void two() {
            log += "Two";
        }
    }

    static public class SubTest extends SuperTest {
        @Override
        @Test
        public void one() {
            log += "Sub";
        }
    }

    public void testTestInheritance() throws Exception {
        log = "";
        JUnitCore core = new JUnitCore();
        core.run(SubTest.class);
        // The order in which the test methods are called is unspecified
        assertTrue(log.contains("Sub"));
        assertTrue(log.contains("Two"));
        assertFalse(log.contains("Super"));
    }

    static public class RunAllAfters {
        @Before
        public void good() {
        }

        @Before
        public void bad() {
            throw new Error();
        }

        @Test
        public void empty() {
        }

        @After
        public void one() {
            log += "one";
        }

        @After
        public void two() {
            log += "two";
        }
    }

    public void testRunAllAfters() {
        log = "";
        JUnitCore core = new JUnitCore();
        core.run(RunAllAfters.class);
        assertTrue(log.contains("one"));
        assertTrue(log.contains("two"));
    }

    static public class RunAllAftersRegardless {
        @Test
        public void empty() {
        }

        @After
        public void one() {
            log += "one";
            throw new Error();
        }

        @After
        public void two() {
            log += "two";
            throw new Error();
        }
    }

    public void testRunAllAftersRegardless() {
        log = "";
        JUnitCore core = new JUnitCore();
        Result result = core.run(RunAllAftersRegardless.class);
        assertTrue(log.contains("one"));
        assertTrue(log.contains("two"));
        assertEquals(2, result.getFailureCount());
    }

    static public class RunAllAfterClasses {
        @Before
        public void good() {
        }

        @BeforeClass
        public static void bad() {
            throw new Error();
        }

        @Test
        public void empty() {
        }

        @AfterClass
        public static void one() {
            log += "one";
        }

        @AfterClass
        public static void two() {
            log += "two";
        }
    }

    public void testRunAllAfterClasses() {
        log = "";
        JUnitCore core = new JUnitCore();
        core.run(RunAllAfterClasses.class);
        assertTrue(log.contains("one"));
        assertTrue(log.contains("two"));
    }

    static public class RunAllAfterClassesRegardless {
        @Test
        public void empty() {
        }

        @AfterClass
        static public void one() {
            log += "one";
            throw new Error();
        }

        @AfterClass
        static public void two() {
            log += "two";
            throw new Error();
        }
    }

    public void testRunAllAfterClassesRegardless() {
        log = "";
        JUnitCore core = new JUnitCore();
        Result result = core.run(RunAllAfterClassesRegardless.class);
        assertTrue(log.contains("one"));
        assertTrue(log.contains("two"));
        assertEquals(2, result.getFailureCount());
    }
}
