/*
 * @(#)SynchQueueUTest.java
 *
 * Copyright (C) 2001-2004 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.util.datastruct.v1;

import net.sourceforge.groboutils.junit.v1.MultiThreadedTestRunner;
import net.sourceforge.groboutils.junit.v1.TestMonitorRunnable;
import net.sourceforge.groboutils.junit.v1.TestRunnable;

import net.sourceforge.groboutils.autodoc.v1.AutoDoc;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;


/**
 * 
 * <H3>Changes made for 0.9.1:</H3>
 * <UL>
 *      <LI>Added a new test to check the size of the queue during the
 *          threaded part.  The size should never go below 0.
 * </UL>
 *
 * @author    Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
 * @since     March 14, 2001 (Alpha 0.9.0)
 * @version   $Date: 2004/06/08 20:55:40 $
 */
public class SynchQueueUTest extends TestCase
{
    private static final Class THIS_CLASS = SynchQueueUTest.class;
    private static final AutoDoc DOC = new AutoDoc( THIS_CLASS );
    
    public SynchQueueUTest( String name )
    {
        super( name );
    }
    
    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 );
    }
    
    protected void setUp() throws Exception
    {
        super.setUp();
        
        // set ourself up
    }
    
    
    protected void tearDown() throws Exception
    {
        // tear ourself down
        
        
        super.tearDown();
    }
    
    
    public void testInstantiate1()
    {
        Object o = new SynchQueue();
        assertNotNull( "New should never return null.", o );
        assertTrue( "Instantiation must be of the correct class.",
            o instanceof SynchQueue );
        assertTrue( "Queue needs to be empty immediately after instantiation.",
            ((SynchQueue)o).isEmpty() );
    }
    
    
    public void testSimpleAdd1() throws InterruptedException
    {
        SynchQueue sq = new SynchQueue();
        Object o1 = new Object();
        sq.enqueue( o1 );
        assertTrue( "Queue should not be empty.", !sq.isEmpty() );
        Object o2 = sq.dequeue();
        assertEquals( "What gets put in, needs to equal what gets put out.",
            o1, o2 );
        assertTrue( "Queue should be empty after removing.", sq.isEmpty() );
    }
    
    public void testAddSize1()
    {
        SynchQueue sq = new SynchQueue();
        assertEquals( "Size must be 0 immediately after instantiation.",
            0, sq.size() );
        for (int i = 1; i <= 20; i++)
        {
            Object o = new Object();
            sq.enqueue( o );
            assertEquals( "Size must be "+i+" after enqueue call.",
                i, sq.size() );
        }
    }
    
    public void testRemoveSize1() throws InterruptedException
    {
        SynchQueue sq = new SynchQueue();
        int count = 0;
        for (int i = 1; i <= 20; i++)
        {
            Object o = new Object();
            sq.enqueue( o );
            count++;
//System.out.println("Added element: size now "+count);
        }
        while( count > 0 )
        {
            assertTrue( "Queue should not be empty.", !sq.isEmpty() );
            assertEquals( "Queue size must be "+count+".", count, sq.size() );
//System.out.println("Removing element: size is now "+sq.size()+" (should be "+count+")" );
            sq.dequeue();
            count--;
        }
        assertTrue( "Queue should be empty after removing.", sq.isEmpty() );
        assertEquals( "Size must be 0 after dequeue all.", 0, sq.size() );
    }
    
    public void testUniqueRetrieval1() throws InterruptedException
    {
        SynchQueue sq = new SynchQueue();
        int maxSize = 20;
        Object objs[] = new Object[ maxSize ];
        for (int i = 0; i < maxSize; i++)
        {
            objs[i] = "element "+i;
            sq.enqueue( objs[i] );
        }
        // parse forwards through the list for checking the return values
        // (its a queue, not a stack)
//System.err.println("Starting dequeue loop:");
        for (int i = 0; i < maxSize; i++)
        {
//System.err.println("dequeueing element index "+i);
            assertTrue( "Queue cannot be empty at this point: should have "+(i+1)+
                " elements left.", !sq.isEmpty() );
            Object o = sq.dequeue();
            assertNotNull( "Queue must never return null from dequeue.", o );
            assertEquals( "Queue did not return the right object in the right "+
                "order (element "+i+", size = "+sq.size()+")", objs[i], o );
        }
        // yeah, we already have a test for this - but just to be careful.
        assertTrue( "Queue must be empty.", sq.isEmpty() );
    }
    
    public void testIdenticalRetrieval1() throws InterruptedException
    {
        SynchQueue sq = new SynchQueue();
        int maxSize = 20;
        Object obj = new Object();
        for (int i = 0; i < maxSize; i++)
        {
            sq.enqueue( obj );
        }
        // parse forwards through the list for checking the return values
        // (its a queue, not a stack)
//System.err.println("Starting dequeue loop:");
        for (int i = 0; i < maxSize; i++)
        {
//System.err.println("dequeueing element index "+i);
            assertTrue( "Queue cannot be empty at this point: should have "+(i+1)+
                " elements left.", !sq.isEmpty() );
            Object o = sq.dequeue();
            assertNotNull( "Queue must never return null from dequeue.", o );
            assertEquals( "Queue did not return the right object in the right "+
                "order (element "+i+", size = "+sq.size()+")", obj, o );
        }
        // yeah, we already have a test for this - but just to be careful.
        assertTrue( "Queue must be empty.", sq.isEmpty() );
    }
    

    public void testPeek1() throws InterruptedException
    {
        SynchQueue sq = new SynchQueue();
        assertNull( "Peek must return null on an empty queue", sq.peek() );
        
        int maxSize = 20;
        Object objs[] = new Object[ maxSize ];
        for (int i = 0; i < maxSize; i++)
        {
            objs[i] = "element "+i;
            sq.enqueue( objs[i] );
        }
        // parse forwards through the list for checking the return values
        // (its a queue, not a stack)
//System.err.println("Starting dequeue loop:");
        for (int i = 0; i < maxSize; i++)
        {
//System.err.println("dequeueing element index "+i);
            assertTrue( "Queue cannot be empty at this point: should have "+(i+1)+
                " elements left.", !sq.isEmpty() );
            Object o1 = sq.peek();
            assertNotNull( "Peek must not return null on a non-null queue",o1 );
            Object o2 = sq.dequeue();
            assertNotNull( "Queue must never return null from dequeue.", o2 );
            assertEquals( "Peek did not return the right element in the right "+
                "order (element "+i+", size = "+sq.size()+")", o2, o1 );
        }
        // yeah, we already have a test for this - but just to be careful.
        assertNull( "Peek must return null on an empty queue", sq.peek() );
    }
    
    
    public void testRemoveAll1() throws InterruptedException
    {
        SynchQueue sq = new SynchQueue();
        assertNull( "Peek must return null on an empty queue", sq.peek() );
        
        int maxSize = 20;
        Object objs[] = new Object[ maxSize ];
        for (int i = 0; i < maxSize; i++)
        {
            objs[i] = "element "+i;
            sq.enqueue( objs[i] );
        }
        assertEquals("Queue must have "+maxSize+" elements.", maxSize,
            sq.size() );
        sq.removeAll();
        assertEquals("Queue must be empty.", 0, sq.size() );
        assertTrue( "Queue must be empty.", sq.isEmpty() );
        
        // now verify that the queue still works correctly
        testUniqueRetrieval1();
    }
    

    
    private class PutObjects extends TestRunnable
    {
        private SynchQueue m_sq;
        private Object[] m_objs;
        private long m_delay;
        public PutObjects( SynchQueue sq, Object[] list, long delayMillis )
        {
            this.m_sq = sq;
            this.m_objs = list;
            this.m_delay = delayMillis;
        }
        
        public void runTest() throws Throwable
        {
            try
            {
                DOC.getLog().debug("Entering PutObjects.runTest()");
                for (int i = 0; i < this.m_objs.length; i++)
                {
                    // for putting into the list, we delay before inserting
                    DOC.getLog().debug( "Enqueue delay "+this.m_delay+
                        " millis." );
                    delay( this.m_delay );
                    DOC.getLog().debug( new Object[] {
                        "Enqueue object '", this.m_objs[i], "'" } );
                    this.m_sq.enqueue( this.m_objs[i] );
                }
            }
            finally
            {
                DOC.getLog().debug("Leaving PutObjects.runTest()");
            }
        }
    }
    
    

    private class CheckSize extends TestMonitorRunnable
    {
        private SynchQueue m_sq;
        private int m_maxSize;
        public CheckSize( SynchQueue sq, int maxSize )
        {
            this.m_sq = sq;
            this.m_maxSize = maxSize;
        }
        
        public void runMonitor() throws Throwable
        {
            int size = this.m_sq.size();
            assertTrue(
                "Invalid queue size "+size+
                ": must be within the set [0, "+this.m_maxSize+"].",
                (size >= 0) && (size <= this.m_maxSize) );
        }
    }
    
    
    private class GetObjects extends TestRunnable
    {
        private SynchQueue m_sq;
        private Object[] m_objs;
        private long m_delay;
        private long m_waitMillis;
        private int m_waitNanos;
        public GetObjects( SynchQueue sq, Object[] expectedlist,
                long delayMillis )
        {
            this( sq, expectedlist, delayMillis, -1, -1 );
        }
        public GetObjects( SynchQueue sq, Object[] expectedlist,
                long delayMillis, long waitTime, int nanos )
        {
            this.m_sq = sq;
            this.m_objs = expectedlist;
            this.m_delay = delayMillis;
            this.m_waitMillis = waitTime;
            this.m_waitNanos = nanos;
        }
        
        public void runTest() throws Throwable
        {
            DOC.getLog().debug("Entering GetObjects.runTest()");
            for (int i = 0; i < this.m_objs.length; i++)
            {
                DOC.getLog().debug("Dequeue delay "+this.m_delay+
                    " millis.");
                delay( this.m_delay );
                Object o = dequeue();
                assertEquals( "Retrieved element "+i+" doesn't match",
                    this.m_objs[i], o );
            }
            DOC.getLog().debug("Leaving GetObjects.runTest()");
        }
        
        protected Object dequeue() throws Throwable
        {
            long startTime = System.currentTimeMillis();
            Object ret;
            if (this.m_waitMillis > 0)
            {
                if (this.m_waitNanos > 0)
                {
                    DOC.getLog().debug("dequeue w/ nano delay");
                    ret = this.m_sq.dequeue(
                        this.m_waitMillis, this.m_waitNanos );
                }
                else
                {
                    DOC.getLog().debug("dequeue w/ millis dely ["+
                        this.m_waitMillis+"]");
                    ret = this.m_sq.dequeue( this.m_waitMillis );
                }
            }
            else
            {
                DOC.getLog().debug("dequeue w/ no time-out");
                ret = this.m_sq.dequeue();
            }
            long endTime = System.currentTimeMillis();
            DOC.getLog().debug( new Object[] {
                "returning dequeued object '", ret, "' after "+
                (endTime - startTime)+" milliseconds." } );
            return ret;
        }
    }
    
    
    
    
    public void testSimpleThreaded()
            throws Throwable
    {
        DOC.getLog().info( "Entering testSimpleThreaded" );
        int numElements = 20;
        SynchQueue sq = new SynchQueue();
        Object list[] = new Object[ numElements ];
        for (int i = 0; i < numElements; i++)
        {
            list[i] = "element "+i;
        }
        
        // MCA: 26-Oct-2003: changed GetObjects delay to 20 ms due to
        // coverage number delays and file I/O on slower machines.
        TestRunnable tcs[] = { new PutObjects( sq, list, 10 ),
            new GetObjects( sq, list, 20 ) };
        // allow for a maximum of 60 seconds for this test to run
        DOC.getLog().debug("Starting SimpleThreaded()");
        runTestRunnables( tcs, new CheckSize( sq, numElements ), 1000 * 60 );
        DOC.getLog().debug("Finished SimpleThreaded()");
    }
    
    public void testSyncapatedThreaded()
            throws Throwable
    {
        DOC.getLog().info( "Entering testSyncapatedThreaded" );
        int numElements = 20;
        SynchQueue sq = new SynchQueue();
        Object list[] = new Object[ numElements ];
        for (int i = 0; i < numElements; i++)
        {
            list[i] = "element "+i;
        }
        TestRunnable tcs[] = { new PutObjects( sq, list, 1000 ),
            new GetObjects( sq, list, 10 ) };
        DOC.getLog().debug("Starting SyncapatedThreaded()");
        runTestRunnables( tcs, new CheckSize( sq, numElements ), 1000 * 60 );
        DOC.getLog().debug("Finished SyncapatedThreaded()");
    }
    
    public void testDelayMillisThreaded()
            throws Throwable
    {
        DOC.getLog().info( "Entering testDelayMillisThreaded" );
        int numElements = 1;
        Object list[] = new Object[ numElements ];
        SynchQueue sq = new SynchQueue();
        // don't wait - detect the null immediately
        TestRunnable tcs[] = { new GetObjects( sq, list, 0, 10, -1 ) };
        DOC.getLog().debug("Starting DelayMillisThreaded()");
        runTestRunnables( tcs, new CheckSize( sq, numElements ), 2000 );
        DOC.getLog().debug("Finished DelayMillisThreaded()");
    }
    
    public void testDelayNanosThreaded()
            throws Throwable
    {
        DOC.getLog().info( "Entering testDelayNanosThreaded" );
        int numElements = 1;
        Object list[] = new Object[ numElements ];
        SynchQueue sq = new SynchQueue();
        // wait for 10 millis, 10 nanos, for null.
        CheckSize cs = new CheckSize( sq, numElements );
        TestRunnable tcs[] = { new GetObjects( sq, list, 0, 10, 10 ) };
        DOC.getLog().debug("Starting DelayNanosThreaded()");
        runTestRunnables( tcs, new CheckSize( sq, numElements ), 2000 );
        DOC.getLog().debug("Finished DelayNanosThreaded()");
    }
    
    public void testPutDelayMillisThreaded()
            throws Throwable
    {
        DOC.getLog().info( "Entering testPutDelayMillisThreaded" );
        int numElements = 20;
        SynchQueue sq = new SynchQueue();
        Object list[] = new Object[ numElements ];
        for (int i = 0; i < numElements; i++)
        {
            list[i] = "element "+i;
        }
        TestRunnable tcs[] = { new PutObjects( sq, list, 10 ),
            new GetObjects( sq, list, 0, 500, -1 ) };
        DOC.getLog().debug("Starting PutDelayMillisThreaded()");
        runTestRunnables( tcs, new CheckSize( sq, numElements ), 1000 * 60 );
        DOC.getLog().debug("Finished PutDelayMillisThreaded()");
    }
    
    public void testPutDelayMillisThreaded2()
            throws Throwable
    {
        DOC.getLog().info( "Entering testPutDelayMillisThreaded2" );
        int numElements = 5;
        SynchQueue sq = new SynchQueue();
        Object list[] = new Object[ numElements ];
        Object nulllist[] = new Object[ numElements ];
        for (int i = 0; i < numElements; i++)
        {
            list[i] = "element "+i;
        }
        // timing issues cause these numbers not to match up perfectly.
        TestRunnable tcs[] = { new PutObjects( sq, list, numElements * 500 ),
            new GetObjects( sq, nulllist, 0, 100, -1 ) };
        DOC.getLog().debug("Starting PutDelayMillisThreaded2()");
        runTestRunnables( tcs, new CheckSize( sq, numElements ), 1000 * 60 );
        DOC.getLog().debug("Finished PutDelayMillisThreaded2()");
    }
    
    
    protected void runTestRunnables( TestRunnable tr[], TestRunnable tm,
            long maxtime )
            throws Throwable
    {
        MultiThreadedTestRunner mttr = new MultiThreadedTestRunner( tr,
            new TestRunnable[] { tm } );
        mttr.runTestRunnables( maxtime );
    }
}

