package org.apache.maven.surefire.junitcore;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.Map;
import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
import org.apache.maven.surefire.report.ConsoleStream;
import org.apache.maven.surefire.report.DefaultDirectConsoleReporter;
import org.apache.maven.surefire.report.ReporterFactory;
import org.apache.maven.surefire.report.RunListener;
import org.apache.maven.surefire.report.RunStatistics;
import org.apache.maven.surefire.testset.TestSetFailedException;

import junit.framework.Assert;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.Computer;
import org.junit.runner.JUnitCore;

/*
 * @author Kristian Rosenvold
 */
public class ConcurrentRunListenerTest
    extends TestCase
{
    // Tests are in order of increasing complexity
    public void testNoErrorsCounting()
        throws Exception
    {
        runClasses( 3, 0, 0, DummyAllOk.class );
    }

    public void testNoErrorsCounting2()
        throws Exception
    {
        runClasses( 2, 0, 0, Dummy3.class );
    }

    public void testOneIgnoreCounting()
        throws Exception
    {
        runClasses( 3, 1, 0, DummyWithOneIgnore.class );
    }

    public void testOneFailureCounting()
        throws Exception
    {
        runClasses( 3, 0, 1, DummyWithFailure.class );
    }

    public void testWithErrorsCountingDemultiplexed()
        throws Exception
    {
        runClasses( 6, 1, 1, DummyWithOneIgnore.class, DummyWithFailure.class );
    }

    public void testJunitResultCountingDemultiplexed()
        throws Exception
    {
        runClasses( 8, 1, 1, DummyWithOneIgnore.class, DummyWithFailure.class, Dummy3.class );
    }

    public void testJunitResultCountingJUnit3Demultiplexed()
        throws Exception
    {
        runClasses( 3, 0, 0, Junit3Tc1.class, Junit3Tc2.class );
    }

    public void testJunitResultCountingJUnit3OddTest()
        throws Exception
    {
        runClasses( 2, 0, 0, Junit3OddTest1.class );
    }

    public void testJunit3WithNestedSuite()
        throws TestSetFailedException
    {
        runClasses( 4, 0, 0, Junit3WithNestedSuite.class );
    }

    public void testJunit3NestedSuite()
        throws Exception
    {
        runClasses( 2, 0, 0, Junit3OddTest1.class );
    }

    public void testSimpleOutput()
        throws Exception
    {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        PrintStream collector = new PrintStream( byteArrayOutputStream );
        PrintStream orgOur = System.out;
        System.setOut( collector );

        RunStatistics result = runClasses( Dummy3.class );
        assertReporter( result, 2, 0, 0, "msgs" );

        String foo = new String( byteArrayOutputStream.toByteArray() );
        assertNotNull( foo );

        System.setOut( orgOur );
    }

    public void testOutputOrdering()
        throws Exception
    {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        PrintStream collector = new PrintStream( byteArrayOutputStream );
        PrintStream orgOur = System.out;
        System.setOut( collector );

        RunStatistics result = runClasses( DummyWithOneIgnore.class, DummyWithFailure.class, Dummy3.class );
        assertReporter( result, 8, 1, 1, "msgs" );

        String foo = new String( byteArrayOutputStream.toByteArray() );
        assertNotNull( foo );

        System.setOut( orgOur );

//        final List<String> stringList = result.getEvents();
//        assertEquals( 23, stringList.size() );
    }

    private void runClasses( int success, int ignored, int failure, Class<?>... classes )
        throws TestSetFailedException
    {
        DefaultReporterFactory reporterFactory = createReporterFactory();
        HashMap<String, TestSet> classMethodCounts = new HashMap<String, TestSet>();
        final ConsoleStream defaultConsoleReporter = new DefaultDirectConsoleReporter( System.out );
        RunListener reporter =
            new ClassesParallelRunListener( classMethodCounts, reporterFactory, defaultConsoleReporter );
        JUnitCoreRunListener runListener = new JUnitCoreRunListener( reporter, classMethodCounts );
        RunStatistics result = runClasses( reporterFactory, runListener, classes );
        assertReporter( result, success, ignored, failure, "classes" );
        classMethodCounts.clear();

        reporterFactory = createReporterFactory();
        reporter = new MethodsParallelRunListener( classMethodCounts, reporterFactory, true, defaultConsoleReporter );
        runListener = new JUnitCoreRunListener( reporter, classMethodCounts );
        result = runClasses( reporterFactory, runListener, classes );
        assertReporter( result, success, ignored, failure, "methods" );
    }

    private RunStatistics runClasses( Class<?>... classes )
        throws TestSetFailedException
    {
        HashMap<String, TestSet> classMethodCounts = new HashMap<String, TestSet>();
        final DefaultReporterFactory reporterManagerFactory = createReporterFactory();
        org.junit.runner.notification.RunListener demultiplexingRunListener =
            createRunListener( reporterManagerFactory, classMethodCounts );

        JUnitCore jUnitCore = new JUnitCore();

        jUnitCore.addListener( demultiplexingRunListener );
        Computer computer = new Computer();

        jUnitCore.run( computer, classes );
        reporterManagerFactory.close();
        return reporterManagerFactory.getGlobalRunStatistics();
    }

    private RunStatistics runClasses( DefaultReporterFactory reporterManagerFactory,
                                      org.junit.runner.notification.RunListener demultiplexingRunListener,
                                      Class<?>... classes )
        throws TestSetFailedException
    {

        JUnitCore jUnitCore = new JUnitCore();

        jUnitCore.addListener( demultiplexingRunListener );
        Computer computer = new Computer();

        jUnitCore.run( computer, classes );
        return reporterManagerFactory.getGlobalRunStatistics();
    }

    private org.junit.runner.notification.RunListener createRunListener( ReporterFactory reporterFactory,
                                                                         Map<String, TestSet> testSetMap )
        throws TestSetFailedException
    {
        return new JUnitCoreRunListener(
            new ClassesParallelRunListener( testSetMap, reporterFactory,
                                                  new DefaultDirectConsoleReporter( System.out ) ), testSetMap );
    }


    public static class DummyWithOneIgnore
    {
        @Test
        public void testNotMuch()
        {
        }

        @Ignore
        @Test
        public void testStub1()
        {
        }

        @Test
        public void testStub2()
        {
        }
    }

    public static class DummyWithFailure
    {
        @Test
        public void testBeforeFail()
        {
        }

        @Test
        public void testWillFail()
        {
            Assert.fail( "We will fail" );
        }

        @Test
        public void testAfterFail()
        {
        }
    }

    public static class DummyAllOk
    {

        @Test
        public void testNotMuchA()
        {
        }

        @Test
        public void testStub1A()
        {
        }

        @Test
        public void testStub2A()
        {
        }
    }

    public static class Dummy3
    {

        @Test
        public void testNotMuchA()
        {
            System.out.println( "tNMA1" );
            System.err.println( "tNMA1err" );
        }

        @Test
        public void testStub2A()
        {
            System.out.println( "tS2A" );
            System.err.println( "tS2AErr" );
        }
    }

    public static class Junit3Tc1
        extends TestCase
    {

        public Junit3Tc1()
        {
            super( "testNotMuchJunit3TC1" );
        }

        public void testNotMuchJunit3TC1()
        {
            System.out.println( "Junit3TC1" );
        }


        public static junit.framework.Test suite()
        {
            TestSuite suite = new TestSuite();
            suite.addTest( new Junit3Tc1() );
            return suite;
        }
    }

    public static class Junit3Tc2
        extends TestCase
    {
        public Junit3Tc2( String testMethod )
        {
            super( testMethod );
        }

        public void testNotMuchJunit3TC2()
        {
            System.out.println( "Junit3TC2" );
        }

        public void testStubJ3TC2A()
        {
            System.out.println( "testStubJ3TC2A" );
        }

        public static junit.framework.Test suite()
        {
            TestSuite suite = new TestSuite();
            suite.addTest( new Junit3Tc2( "testNotMuchJunit3TC2" ) );
            suite.addTest( new Junit3Tc2( "testStubJ3TC2A" ) );
            return suite;
        }
    }

    public static class Junit3OddTest1
        extends TestCase
    {
        public static junit.framework.Test suite()
        {
            TestSuite suite = new TestSuite();

            suite.addTest( new Junit3OddTest1( "testMe" ) );
            suite.addTest( new Junit3OddTest1( "testMe2" ) );

            return suite;
        }

        public Junit3OddTest1( String name )
        {
            super( name );
        }

        public void testMe()
        {
            assertTrue( true );
        }
    }

    public static class Junit3WithNestedSuite
        extends TestCase
    {
        public static junit.framework.Test suite()
        {
            TestSuite suite = new TestSuite();

            suite.addTest( new Junit3WithNestedSuite( "testMe" ) );
            suite.addTest( new Junit3WithNestedSuite( "testMe2" ) );
            suite.addTestSuite( Junit3Tc2.class );
            return suite;
        }

        public Junit3WithNestedSuite( String name )
        {
            super( name );
        }

        public void testMe2()
        {
            assertTrue( true );
        }
    }


    private DefaultReporterFactory createReporterFactory()
    {
        return JUnitCoreTester.defaultNoXml();
    }


    private void assertReporter( RunStatistics result, int success, int ignored, int failure, String message )
    {
        assertEquals( message, success, result.getCompletedCount() );
        assertEquals( message, ignored, result.getSkipped() );
    }

}
