/*
 * @(#)MultiThreadedTestRunnerUTest.java
 *
 * Copyright (C) 2002-2003 Matt Albrecht
 * groboclown@users.sourceforge.net
 * http://groboutils.sourceforge.net
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a
 *  copy of this software and associated documentation files (the "Software"),
 *  to deal in the Software without restriction, including without limitation
 *  the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *  and/or sell copies of the Software, and to permit persons to whom the
 *  Software is furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 *  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 *  DEALINGS IN THE SOFTWARE.
 */

package net.sourceforge.groboutils.junit.v1;

import net.sourceforge.groboutils.autodoc.v1.AutoDoc;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

import java.io.IOException;


/**
 * Tests the MultiThreadedTestRunner class.
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @since     March 1, 2002
 * @version   $Date: 2004/04/09 19:26:52 $
 */
public class MultiThreadedTestRunnerUTest extends TestCase
{
    //-------------------------------------------------------------------------
    // Standard JUnit Class-specific declarations
    
    private static final Class THIS_CLASS = MultiThreadedTestRunnerUTest.class;
    private static final AutoDoc DOC = new AutoDoc( THIS_CLASS );
    
    public MultiThreadedTestRunnerUTest( String name )
    {
        super( name );
    }

    


    //-------------------------------------------------------------------------
    // Tests
    
    
    public void testConstructor1()
    {
        DOC.getLog().debug("testConstructor1");
        try
        {
            new MultiThreadedTestRunner( null );
            fail("did not throw an IllegalArgumentException.");
        }
        catch (IllegalArgumentException e)
        {
            // check exception text?
        }
    }
    
    
    public void testConstructor2()
    {
        DOC.getLog().debug("testConstructor2");
        try
        {
            new MultiThreadedTestRunner( new TestRunnable[0] );
            fail("did not throw an IllegalArgumentException.");
        }
        catch (IllegalArgumentException e)
        {
            // check exception text?
        }
    }
    
    
    static class RunValue1
    {
        public int value;
    }
    
    public synchronized void testRun1()
            throws Throwable
    {
        DOC.getIT().testsIssue( 526710 );
        DOC.getLog().debug("testRun1");
        
        final RunValue1 runCount = new RunValue1();
        TestRunnable tr1 = new TestRunnable() {
                public void runTest() throws Throwable
                {
                    DOC.getLog().debug("Running test testRun1");
                    ++runCount.value;
                }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1 } );
        mttr.runTestRunnables();
        
        assertEquals(
            "Did not run the runTest method enough times.",
            1,
            runCount.value );
    }
    
    
    public synchronized void testRun2()
            throws Throwable
    {
        DOC.getIT().testsIssue( 526710 );
        DOC.getLog().debug("testRun2");
        
        final RunValue1 runCount = new RunValue1();
        TestRunnable tr1 = new TestRunnable() {
                public void runTest() throws Throwable
                {
                    ++runCount.value;
                }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1 } );
        mttr.runTestRunnables( 1000 );
        
        assertEquals(
            "Did not run the runTest method enough times.",
            1,
            runCount.value );
    }
    
    
    public synchronized void testRun3a()
            throws Throwable
    {
        DOC.getIT().testsIssue( 526710 );
        DOC.getLog().debug("testRun3a");
        DOC.getLog().warn(
        "****************\n"+
        "This test may expose timing issues, where only one test is run.\n"+
        "I've only seen this with JDK 1.1, but that may expose an underlying\n"+
        "problem.  This will require further investigation.\n"+
        "****************" );
        
        final RunValue1 runCount = new RunValue1();
        TestRunnable tr1 = new TestRunnable() {
                public synchronized void runTest() throws Throwable
                {
                    ++runCount.value;
                }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1, tr1 } );
        mttr.runTestRunnables();
        
        assertEquals(
            "Did not run the runTest method enough times.",
            2,
            runCount.value );
    }
    
    
    public synchronized void testRun3b()
            throws Throwable
    {
        DOC.getLog().debug("testRun3b");
        final RunValue1 runCount = new RunValue1();
        TestRunnable tr1 = new TestRunnable() {
                public void runTest() throws Throwable
                {
                    ++runCount.value;
                }
        };
        int totalCount = 15;
        TestRunnable trList[] = new TestRunnable[ totalCount ];
        for (int i = 0; i < totalCount; ++i)
        {
            trList[ i ] = tr1;
        }
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner( trList );
        mttr.runTestRunnables();
        
        assertEquals(
            "Did not run the runTest method enough times.",
            totalCount,
            runCount.value );
    }
    
    
    public void testRun4()
            throws Throwable
    {
        DOC.getLog().debug("testRun4");
        TestRunnable tr1 = new TestRunnable() {
                public void runTest() throws Throwable
                {
                    throw new IOException("My exception");
                }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1 } );
        try
        {
            mttr.runTestRunnables();
            fail("did not throw IOException.");
        }
        catch (IOException ioe)
        {
            // check message
            assertEquals(
                "IOException not the one we threw.",
                "My exception",
                ioe.getMessage() );
        }
    }
    
    
    public void testRun5()
            throws Throwable
    {
        DOC.getLog().debug("testRun5");
        TestRunnable tr1 = new TestRunnable() {
                public void runTest() throws Throwable
                {
                    Object o = new Object();
                    synchronized( o )
                    {
                        o.wait();
                    }
                }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1 } );
        boolean fail = false;
        try
        {
            // wait for 10 ms to ensure the join loop is not entered
            mttr.runTestRunnables( 10 );
            fail = true;
        }
        catch (junit.framework.AssertionFailedError e)
        {
            // test failure?
        }
        if (fail)
        {
            fail("Did not throw an assertion failed error.");
        }
    }
    
    
    public void testRun5a()
            throws Throwable
    {
        DOC.getLog().debug("testRun5a");
        TestRunnable tr1 = new TestRunnable() {
                public void runTest() throws Throwable
                {
                    Object o = new Object();
                    synchronized( o )
                    {
                        o.wait();
                    }
                }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1 } );
        boolean fail = false;
        try
        {
            // wait for 1 second to ensure the join section is entered
            mttr.runTestRunnables( 1000 );
            fail = true;
        }
        catch (junit.framework.AssertionFailedError e)
        {
            // test failure?
        }
        if (fail)
        {
            fail("Did not throw an assertion failed error.");
        }
    }
    
    
    private static final int MAX_LOOP_COUNT = 20000;
    
    static abstract class TestRunnable6 extends TestRunnable
    {
        public Thread runningThread = null;
        public boolean wasInterrupted = false;
        public void runTest() throws Throwable
        {
            this.runningThread = Thread.currentThread();
            runMyTest();
        }
        
        
        public abstract void runMyTest() throws Throwable;
    }
    
    
    public void testRun6()
            throws Throwable
    {
        DOC.getIT().testsIssue( 771000 );
        DOC.getLog().debug("testRun6");
        TestRunnable6 tr1 = new TestRunnable6() {
                public void runMyTest() throws Throwable
                {
                    DOC.getLog().warn("runMyTest 6 running forever");
                    // never exit!
                    for (int i = 0; i < MAX_LOOP_COUNT; ++i)
                    {
                        //Thread.yield();
                        try {
                            delay( 100 );
                        } catch (InterruptedException ie) {
                            this.wasInterrupted = true;
                            DOC.getLog().warn("runMyTest 6: interrupted!");
                        }
                        DOC.getLog().warn("runMyTest 6: Loop!");
                        if (Thread.interrupted())
                        {
                            this.wasInterrupted = true;
                            DOC.getLog().warn("runMyTest 6: inerrupted!");
                        }
                    }
                    fail( "This line should NEVER be reached!" );
                }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1 } );
        boolean fail = false;
        try
        {
            mttr.runTestRunnables( 10 );
            fail = true;
        }
        catch (junit.framework.AssertionFailedError e)
        {
            // test failure
            if (e.getMessage() != null &&
                e.getMessage().indexOf( "This line should NEVER be reached!" )
                >= 0)
            {
                fail("Did not kill the runnable." );
            }
        }
        if (fail)
        {
            fail("Did not throw an assertion failed error.");
        }
        
        assertTrue(
            "Test was not interrupted.",
            tr1.wasInterrupted );
        assertFalse(
            "Did not stop the test runnable.",
            tr1.runningThread.isAlive() );
    }
    
    
    public void testRun7()
            throws Throwable
    {
        DOC.getIT().testsIssue( 771001 );
        DOC.getLog().debug("testRun7");
        TestRunnable6 tr1 = new TestRunnable6() {
                public void runMyTest() throws Throwable
                {
                    assertTrue(
                        "Did not create the thread as daemon.",
                        this.runningThread.isDaemon() );
                }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1 } );
        mttr.runTestRunnables();
    }
    
    
    
    static class TesterClass
    {
        public int value;
    }
    
    public void testRun8()
            throws Throwable
    {
        DOC.getIT().testsIssue( 771008 );
        
        // ensure the monitors catch an error
        
        DOC.getLog().debug("testRun8");
        final TesterClass tc = new TesterClass();
        TestRunnable tr1 = new TestRunnable() {
            public void runTest() throws Throwable
            {
                for (int i = 0; i < 30; ++i)
                {
                    tc.value = i;
                    delay( 100 );
                }
                tc.value = -1;
            }
        };
        TestMonitorRunnable tm1 = new TestMonitorRunnable() {
            public void runMonitor() throws Throwable
            {
                assertTrue(
                    "Value is < 0.",
                    tc.value >= 0 );
            }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1 }, new TestRunnable[] { tm1 } );
        boolean fail = false;
        try
        {
            mttr.runTestRunnables();
            fail = true;
        }
        catch (junit.framework.AssertionFailedError e)
        {
            assertTrue(
                "Did not throw correct exception from monitor.",
                e.getMessage().indexOf( "Value is < 0." ) >= 0 );
        }
        if (fail)
        {
            fail("Did not throw an assertion failed error.");
        }
    }
    
    
    
    static abstract class TestMonitorRunnable9 extends TestMonitorRunnable
    {
        public Thread runningThread = null;
        public void runTest() throws Throwable
        {
            this.runningThread = Thread.currentThread();
            super.runTest();
        }
    }
    
    
    public void testRun9()
            throws Throwable
    {
        DOC.getIT().testsIssue( 771008 );
        
        // ensure the monitors quit
        
        DOC.getLog().debug("testRun9");
        TestRunnable tr1 = new TestRunnable() {
            public void runTest() throws Throwable
            {
                delay( 100 );
            }
        };
        TestMonitorRunnable9 tm1 = new TestMonitorRunnable9() {
            public void runMonitor() throws Throwable
            {
                // do nothing
            }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1 }, new TestRunnable[] { tm1 } );
        mttr.runTestRunnables();
        assertFalse(
            "Did not stop the test monitor.",
            tm1.runningThread.isAlive() );
    }
    
    
    public void testRun10()
            throws Throwable
    {
        DOC.getIT().testsIssue( 771008 );
        
        // ensure the monitors quit
        
        DOC.getLog().debug("testRun10");
        TestRunnable tr1 = new TestRunnable() {
            public void runTest() throws Throwable
            {
                delay( 100 );
            }
        };
        TestRunnable6 tm1 = new TestRunnable6() {
            public void runMyTest() throws Throwable
            {
                DOC.getLog().warn("runMyTest 10 running forever");
                for (int i = 0; i < MAX_LOOP_COUNT; ++i)
                {
                    //Thread.yield();
                    try {
                        delay( 100 );
                    } catch (InterruptedException ie) {
                        this.wasInterrupted = true;
                        DOC.getLog().warn("runMyTest 10: interrupted!");
                    }
                    DOC.getLog().warn("runMyTest 10: Loop!");
                    if (Thread.interrupted())
                    {
                        this.wasInterrupted = true;
                        DOC.getLog().warn("runMyTest 10: interrupted!");
                    }
                }
                fail( "This line should NEVER be reached!" );
            }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1 }, new TestRunnable[] { tm1 } );
        
        // monitor timeout should never cause an error.
        mttr.runTestRunnables();
        
        assertTrue(
            "monitor was not interrupted.",
            tm1.wasInterrupted );
        Thread.sleep( 100 );
        assertFalse(
            "Did not stop the test monitor.",
            tm1.runningThread.isAlive() );
    }
    
    public void testRun11()
            throws Throwable
    {
        DOC.getIT().testsIssue( 771008 );
        
        // ensure the monitors catch an error (generated right away)
        
        DOC.getLog().debug("testRun11");
        final TesterClass tc = new TesterClass();
        TestRunnable tr1 = new TestRunnable() {
            public void runTest() throws Throwable
            {
                tc.value = -1;
                for (int i = 0; i < 30; ++i)
                {
                    delay( 100 );
                    tc.value = i;
                }
            }
        };
        TestMonitorRunnable tm1 = new TestMonitorRunnable() {
            public void runMonitor() throws Throwable
            {
                assertTrue(
                    "Value is < 0.",
                    tc.value >= 0 );
            }
        };
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(
            new TestRunnable[] { tr1 }, new TestRunnable[] { tm1 } );
        boolean fail = false;
        try
        {
            mttr.runTestRunnables();
            fail = true;
        }
        catch (junit.framework.AssertionFailedError e)
        {
            assertTrue(
                "Did not throw correct exception from monitor.",
                e.getMessage().indexOf( "Value is < 0." ) >= 0 );
        }
        if (fail)
        {
            fail("Did not throw an assertion failed error.");
        }
    }
    
    
    //-------------------------------------------------------------------------
    // Standard JUnit declarations
    
    
    public static Test suite()
    {
        TestSuite suite = new TestSuite( THIS_CLASS );
        
        return suite;
    }
    
    public static void main( String[] args )
    {
        String[] name = { THIS_CLASS.getName() };
        
        // junit.textui.TestRunner.main( name );
        // junit.swingui.TestRunner.main( name );
        
        junit.textui.TestRunner.main( name );
    }
    
    
    /**
     * 
     * @exception Exception thrown under any exceptional condition.
     */
    protected void setUp() throws Exception
    {
        super.setUp();
        
        // set ourself up
    }
    
    
    /**
     * 
     * @exception Exception thrown under any exceptional condition.
     */
    protected void tearDown() throws Exception
    {
        // tear ourself down
        
        
        super.tearDown();
    }
}

