package org.junit.tests.experimental.max;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import junit.framework.TestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.max.MaxCore;
import org.junit.internal.runners.JUnit38ClassRunner;
import org.junit.runner.Computer;
import org.junit.runner.Description;
import org.junit.runner.JUnitCore;
import org.junit.runner.Request;
import org.junit.runner.Result;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunListener;
import org.junit.tests.AllTests;

public class MaxStarterTest {
    private MaxCore fMax;

    private File fMaxFile;

    @Before
    public void createMax() {
        fMaxFile = new File("MaxCore.ser");
        if (fMaxFile.exists()) {
            fMaxFile.delete();
        }
        fMax = MaxCore.storedLocally(fMaxFile);
    }

    @After
    public void forgetMax() {
        fMaxFile.delete();
    }

    public static class TwoTests {
        @Test
        public void succeed() {
        }

        @Test
        public void dontSucceed() {
            fail();
        }
    }

    @Test
    public void twoTestsNotRunComeBackInRandomOrder() {
        Request request = Request.aClass(TwoTests.class);
        List<Description> things = fMax.sortedLeavesForTest(request);
        Description succeed = Description.createTestDescription(TwoTests.class,
                "succeed");
        Description dontSucceed = Description.createTestDescription(
                TwoTests.class, "dontSucceed");
        assertTrue(things.contains(succeed));
        assertTrue(things.contains(dontSucceed));
        assertEquals(2, things.size());
    }

    @Test
    public void preferNewTests() {
        Request one = Request.method(TwoTests.class, "succeed");
        fMax.run(one);
        Request two = Request.aClass(TwoTests.class);
        List<Description> things = fMax.sortedLeavesForTest(two);
        Description dontSucceed = Description.createTestDescription(
                TwoTests.class, "dontSucceed");
        assertEquals(dontSucceed, things.get(0));
        assertEquals(2, things.size());
    }

    // This covers a seemingly-unlikely case, where you had a test that failed
    // on the
    // last run and you also introduced new tests. In such a case it pretty much
    // doesn't matter
    // which order they run, you just want them both to be early in the sequence
    @Test
    public void preferNewTestsOverTestsThatFailed() {
        Request one = Request.method(TwoTests.class, "dontSucceed");
        fMax.run(one);
        Request two = Request.aClass(TwoTests.class);
        List<Description> things = fMax.sortedLeavesForTest(two);
        Description succeed = Description.createTestDescription(TwoTests.class,
                "succeed");
        assertEquals(succeed, things.get(0));
        assertEquals(2, things.size());
    }

    @Test
    public void preferRecentlyFailed() {
        Request request = Request.aClass(TwoTests.class);
        fMax.run(request);
        List<Description> tests = fMax.sortedLeavesForTest(request);
        Description dontSucceed = Description.createTestDescription(
                TwoTests.class, "dontSucceed");
        assertEquals(dontSucceed, tests.get(0));
    }

    @Test
    public void sortTestsInMultipleClasses() {
        Request request = Request.classes(Computer.serial(), TwoTests.class,
                TwoTests.class);
        fMax.run(request);
        List<Description> tests = fMax.sortedLeavesForTest(request);
        Description dontSucceed = Description.createTestDescription(
                TwoTests.class, "dontSucceed");
        assertEquals(dontSucceed, tests.get(0));
        assertEquals(dontSucceed, tests.get(1));
    }

    public static class TwoUnEqualTests {
        @Test
        public void slow() throws InterruptedException {
            Thread.sleep(100);
            fail();
        }

        @Test
        public void fast() {
            fail();
        }

    }

    @Test
    public void rememberOldRuns() {
        fMax.run(TwoUnEqualTests.class);

        MaxCore reincarnation = MaxCore.storedLocally(fMaxFile);
        List<Failure> failures = reincarnation.run(TwoUnEqualTests.class)
                .getFailures();
        assertEquals("fast", failures.get(0).getDescription().getMethodName());
        assertEquals("slow", failures.get(1).getDescription().getMethodName());
    }

    @Test
    public void preferFast() {
        Request request = Request.aClass(TwoUnEqualTests.class);
        fMax.run(request);
        Description thing = fMax.sortedLeavesForTest(request).get(1);
        assertEquals(Description.createTestDescription(TwoUnEqualTests.class,
                "slow"), thing);
    }

    @Test
    public void listenersAreCalledCorrectlyInTheFaceOfFailures()
            throws Exception {
        JUnitCore core = new JUnitCore();
        final List<Failure> failures = new ArrayList<Failure>();
        core.addListener(new RunListener() {
            @Override
            public void testRunFinished(Result result) throws Exception {
                failures.addAll(result.getFailures());
            }
        });
        fMax.run(Request.aClass(TwoTests.class), core);
        assertEquals(1, failures.size());
    }

    @Test
    public void testsAreOnlyIncludedOnceWhenExpandingForSorting()
            throws Exception {
        Result result = fMax.run(Request.aClass(TwoTests.class));
        assertEquals(2, result.getRunCount());
    }

    public static class TwoOldTests extends TestCase {
        public void testOne() {
        }

        public void testTwo() {
        }
    }

    @Test
    public void junit3TestsAreRunOnce() throws Exception {
        Result result = fMax.run(Request.aClass(TwoOldTests.class),
                new JUnitCore());
        assertEquals(2, result.getRunCount());
    }

    @Test
    public void filterSingleMethodFromOldTestClass() throws Exception {
        final Description method = Description.createTestDescription(
                TwoOldTests.class, "testOne");
        Filter filter = Filter.matchMethodDescription(method);
        JUnit38ClassRunner child = new JUnit38ClassRunner(TwoOldTests.class);
        child.filter(filter);
        assertEquals(1, child.testCount());
    }

    @Test
    public void testCountsStandUpToFiltration() {
        assertFilterLeavesTestUnscathed(AllTests.class);
    }

    private void assertFilterLeavesTestUnscathed(Class<?> testClass) {
        Request oneClass = Request.aClass(testClass);
        Request filtered = oneClass.filterWith(new Filter() {
            @Override
            public boolean shouldRun(Description description) {
                return true;
            }

            @Override
            public String describe() {
                return "Everything";
            }
        });

        int filterCount = filtered.getRunner().testCount();
        int coreCount = oneClass.getRunner().testCount();
        assertEquals("Counts match up in " + testClass, coreCount, filterCount);
    }

    private static class MalformedJUnit38Test {
        private MalformedJUnit38Test() {
        }

        @SuppressWarnings("unused")
        public void testSucceeds() {
        }
    }

    @Test
    public void maxShouldSkipMalformedJUnit38Classes() {
        Request request = Request.aClass(MalformedJUnit38Test.class);
        fMax.run(request);
    }

    public static class MalformedJUnit38TestMethod extends TestCase {
        @SuppressWarnings("unused")
        private void testNothing() {
        }
    }

    String fMessage = null;

    @Test
    public void correctErrorFromMalformedTest() {
        Request request = Request.aClass(MalformedJUnit38TestMethod.class);
        JUnitCore core = new JUnitCore();
        Request sorted = fMax.sortRequest(request);
        Runner runner = sorted.getRunner();
        Result result = core.run(runner);
        Failure failure = result.getFailures().get(0);
        assertThat(failure.toString(), containsString("MalformedJUnit38TestMethod"));
        assertThat(failure.toString(), containsString("testNothing"));
        assertThat(failure.toString(), containsString("isn't public"));
    }

    public static class HalfMalformedJUnit38TestMethod extends TestCase {
        public void testSomething() {
        }

        @SuppressWarnings("unused")
        private void testNothing() {
        }
    }

    @Test
    public void halfMalformed() {
        assertThat(JUnitCore.runClasses(HalfMalformedJUnit38TestMethod.class)
                .getFailureCount(), is(1));
    }


    @Test
    public void correctErrorFromHalfMalformedTest() {
        Request request = Request.aClass(HalfMalformedJUnit38TestMethod.class);
        JUnitCore core = new JUnitCore();
        Request sorted = fMax.sortRequest(request);
        Runner runner = sorted.getRunner();
        Result result = core.run(runner);
        Failure failure = result.getFailures().get(0);
        assertThat(failure.toString(), containsString("MalformedJUnit38TestMethod"));
        assertThat(failure.toString(), containsString("testNothing"));
        assertThat(failure.toString(), containsString("isn't public"));
    }
}
