/*
 * Written by Doug Lea with assistance from members of JCP JSR-166
 * Expert Group and released to the public domain, as explained at
 * http://creativecommons.org/publicdomain/zero/1.0/
 * Other contributors include Andrew Wright, Jeffrey Hayes,
 * Pat Fisher, Mike Judd.
 */

import junit.framework.*;
import java.util.*;
import java.util.concurrent.*;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import java.io.*;

public class SemaphoreTest extends JSR166TestCase {
    public static void main(String[] args) {
        junit.textui.TestRunner.run(suite());
    }
    public static Test suite() {
        return new TestSuite(SemaphoreTest.class);
    }

    /**
     * Subclass to expose protected methods
     */
    static class PublicSemaphore extends Semaphore {
        PublicSemaphore(int p, boolean f) { super(p, f); }
        public Collection<Thread> getQueuedThreads() {
            return super.getQueuedThreads();
        }
        public void reducePermits(int p) {
            super.reducePermits(p);
        }
    }

    /**
     * A runnable calling acquire
     */
    class InterruptibleLockRunnable extends CheckedRunnable {
        final Semaphore lock;
        InterruptibleLockRunnable(Semaphore l) { lock = l; }
        public void realRun() {
            try {
                lock.acquire();
            }
            catch (InterruptedException ignored) {}
        }
    }


    /**
     * A runnable calling acquire that expects to be interrupted
     */
    class InterruptedLockRunnable extends CheckedInterruptedRunnable {
        final Semaphore lock;
        InterruptedLockRunnable(Semaphore l) { lock = l; }
        public void realRun() throws InterruptedException {
            lock.acquire();
        }
    }

    /**
     * Zero, negative, and positive initial values are allowed in constructor
     */
    public void testConstructor() {
        for (int permits : new int[] { -1, 0, 1 }) {
            for (boolean fair : new boolean[] { false, true }) {
                Semaphore s = new Semaphore(permits, fair);
                assertEquals(permits, s.availablePermits());
                assertEquals(fair, s.isFair());
            }
        }
    }

    /**
     * Constructor without fairness argument behaves as nonfair
     */
    public void testConstructor2() {
        for (int permits : new int[] { -1, 0, 1 }) {
            Semaphore s = new Semaphore(permits);
            assertEquals(permits, s.availablePermits());
            assertFalse(s.isFair());
        }
    }

    /**
     * tryAcquire succeeds when sufficient permits, else fails
     */
    public void testTryAcquireInSameThread() {
        Semaphore s = new Semaphore(2, false);
        assertEquals(2, s.availablePermits());
        assertTrue(s.tryAcquire());
        assertTrue(s.tryAcquire());
        assertEquals(0, s.availablePermits());
        assertFalse(s.tryAcquire());
    }

    /**
     * Acquire and release of semaphore succeed if initially available
     */
    public void testAcquireReleaseInSameThread()
        throws InterruptedException {
        Semaphore s = new Semaphore(1, false);
        s.acquire();
        s.release();
        s.acquire();
        s.release();
        s.acquire();
        s.release();
        s.acquire();
        s.release();
        s.acquire();
        s.release();
        assertEquals(1, s.availablePermits());
    }

    /**
     * Uninterruptible acquire and release of semaphore succeed if
     * initially available
     */
    public void testAcquireUninterruptiblyReleaseInSameThread()
        throws InterruptedException {
        Semaphore s = new Semaphore(1, false);
        s.acquireUninterruptibly();
        s.release();
        s.acquireUninterruptibly();
        s.release();
        s.acquireUninterruptibly();
        s.release();
        s.acquireUninterruptibly();
        s.release();
        s.acquireUninterruptibly();
        s.release();
        assertEquals(1, s.availablePermits());
    }

    /**
     * Timed Acquire and release of semaphore succeed if
     * initially available
     */
    public void testTimedAcquireReleaseInSameThread()
        throws InterruptedException {
        Semaphore s = new Semaphore(1, false);
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        assertEquals(1, s.availablePermits());
    }

    /**
     * A release in one thread enables an acquire in another thread
     */
    public void testAcquireReleaseInDifferentThreads()
        throws InterruptedException {
        final Semaphore s = new Semaphore(0, false);
        Thread t = new Thread(new CheckedRunnable() {
            public void realRun() throws InterruptedException {
                s.acquire();
                s.release();
                s.release();
                s.acquire();
            }});

        t.start();
        delay(SHORT_DELAY_MS);
        s.release();
        s.release();
        s.acquire();
        s.acquire();
        s.release();
        t.join();
    }

    /**
     * A release in one thread enables an uninterruptible acquire in another thread
     */
    public void testUninterruptibleAcquireReleaseInDifferentThreads()
        throws InterruptedException {
        final Semaphore s = new Semaphore(0, false);
        Thread t = new Thread(new CheckedRunnable() {
            public void realRun() throws InterruptedException {
                s.acquireUninterruptibly();
                s.release();
                s.release();
                s.acquireUninterruptibly();
            }});

        t.start();
        delay(SHORT_DELAY_MS);
        s.release();
        s.release();
        s.acquireUninterruptibly();
        s.acquireUninterruptibly();
        s.release();
        t.join();
    }


    /**
     * A release in one thread enables a timed acquire in another thread
     */
    public void testTimedAcquireReleaseInDifferentThreads()
        throws InterruptedException {
        final Semaphore s = new Semaphore(1, false);
        Thread t = new Thread(new CheckedRunnable() {
            public void realRun() throws InterruptedException {
                s.release();
                assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
                s.release();
                assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
            }});

        t.start();
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        s.release();
        t.join();
    }

    /**
     * A waiting acquire blocks interruptibly
     */
    public void testAcquire_InterruptedException()
        throws InterruptedException {
        final Semaphore s = new Semaphore(0, false);
        Thread t = new Thread(new CheckedInterruptedRunnable() {
            public void realRun() throws InterruptedException {
                s.acquire();
            }});

        t.start();
        delay(SHORT_DELAY_MS);
        t.interrupt();
        t.join();
    }

    /**
     * A waiting timed acquire blocks interruptibly
     */
    public void testTryAcquire_InterruptedException()
        throws InterruptedException {
        final Semaphore s = new Semaphore(0, false);
        Thread t = new Thread(new CheckedInterruptedRunnable() {
            public void realRun() throws InterruptedException {
                s.tryAcquire(MEDIUM_DELAY_MS, MILLISECONDS);
            }});

        t.start();
        delay(SHORT_DELAY_MS);
        t.interrupt();
        t.join();
    }

    /**
     * hasQueuedThreads reports whether there are waiting threads
     */
    public void testHasQueuedThreads() throws InterruptedException {
        final Semaphore lock = new Semaphore(1, false);
        Thread t1 = new Thread(new InterruptedLockRunnable(lock));
        Thread t2 = new Thread(new InterruptibleLockRunnable(lock));
        assertFalse(lock.hasQueuedThreads());
        lock.acquireUninterruptibly();
        t1.start();
        delay(SHORT_DELAY_MS);
        assertTrue(lock.hasQueuedThreads());
        t2.start();
        delay(SHORT_DELAY_MS);
        assertTrue(lock.hasQueuedThreads());
        t1.interrupt();
        delay(SHORT_DELAY_MS);
        assertTrue(lock.hasQueuedThreads());
        lock.release();
        delay(SHORT_DELAY_MS);
        assertFalse(lock.hasQueuedThreads());
        t1.join();
        t2.join();
    }

    /**
     * getQueueLength reports number of waiting threads
     */
    public void testGetQueueLength() throws InterruptedException {
        final Semaphore lock = new Semaphore(1, false);
        Thread t1 = new Thread(new InterruptedLockRunnable(lock));
        Thread t2 = new Thread(new InterruptibleLockRunnable(lock));
        assertEquals(0, lock.getQueueLength());
        lock.acquireUninterruptibly();
        t1.start();
        delay(SHORT_DELAY_MS);
        assertEquals(1, lock.getQueueLength());
        t2.start();
        delay(SHORT_DELAY_MS);
        assertEquals(2, lock.getQueueLength());
        t1.interrupt();
        delay(SHORT_DELAY_MS);
        assertEquals(1, lock.getQueueLength());
        lock.release();
        delay(SHORT_DELAY_MS);
        assertEquals(0, lock.getQueueLength());
        t1.join();
        t2.join();
    }

    /**
     * getQueuedThreads includes waiting threads
     */
    public void testGetQueuedThreads() throws InterruptedException {
        final PublicSemaphore lock = new PublicSemaphore(1, false);
        Thread t1 = new Thread(new InterruptedLockRunnable(lock));
        Thread t2 = new Thread(new InterruptibleLockRunnable(lock));
        assertTrue(lock.getQueuedThreads().isEmpty());
        lock.acquireUninterruptibly();
        assertTrue(lock.getQueuedThreads().isEmpty());
        t1.start();
        delay(SHORT_DELAY_MS);
        assertTrue(lock.getQueuedThreads().contains(t1));
        t2.start();
        delay(SHORT_DELAY_MS);
        assertTrue(lock.getQueuedThreads().contains(t1));
        assertTrue(lock.getQueuedThreads().contains(t2));
        t1.interrupt();
        delay(SHORT_DELAY_MS);
        assertFalse(lock.getQueuedThreads().contains(t1));
        assertTrue(lock.getQueuedThreads().contains(t2));
        lock.release();
        delay(SHORT_DELAY_MS);
        assertTrue(lock.getQueuedThreads().isEmpty());
        t1.join();
        t2.join();
    }

    /**
     * drainPermits reports and removes given number of permits
     */
    public void testDrainPermits() {
        Semaphore s = new Semaphore(0, false);
        assertEquals(0, s.availablePermits());
        assertEquals(0, s.drainPermits());
        s.release(10);
        assertEquals(10, s.availablePermits());
        assertEquals(10, s.drainPermits());
        assertEquals(0, s.availablePermits());
        assertEquals(0, s.drainPermits());
    }

    /**
     * reducePermits reduces number of permits
     */
    public void testReducePermits() {
        PublicSemaphore s = new PublicSemaphore(10, false);
        assertEquals(10, s.availablePermits());
        s.reducePermits(1);
        assertEquals(9, s.availablePermits());
        s.reducePermits(10);
        assertEquals(-1, s.availablePermits());
    }

    /**
     * a deserialized serialized semaphore has same number of permits
     */
    public void testSerialization() throws Exception {
        Semaphore l = new Semaphore(3, false);
        l.acquire();
        l.release();
        ByteArrayOutputStream bout = new ByteArrayOutputStream(10000);
        ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(bout));
        out.writeObject(l);
        out.close();

        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(bin));
        Semaphore r = (Semaphore) in.readObject();
        assertEquals(3, r.availablePermits());
        assertFalse(r.isFair());
        r.acquire();
        r.release();
    }


    /**
     * Zero, negative, and positive initial values are allowed in constructor
     */
    public void testConstructor_fair() {
        Semaphore s0 = new Semaphore(0, true);
        assertEquals(0, s0.availablePermits());
        assertTrue(s0.isFair());
        Semaphore s1 = new Semaphore(-1, true);
        assertEquals(-1, s1.availablePermits());
        Semaphore s2 = new Semaphore(-1, true);
        assertEquals(-1, s2.availablePermits());
    }

    /**
     * tryAcquire succeeds when sufficient permits, else fails
     */
    public void testTryAcquireInSameThread_fair() {
        Semaphore s = new Semaphore(2, true);
        assertEquals(2, s.availablePermits());
        assertTrue(s.tryAcquire());
        assertTrue(s.tryAcquire());
        assertEquals(0, s.availablePermits());
        assertFalse(s.tryAcquire());
    }

    /**
     * tryAcquire(n) succeeds when sufficient permits, else fails
     */
    public void testTryAcquireNInSameThread_fair() {
        Semaphore s = new Semaphore(2, true);
        assertEquals(2, s.availablePermits());
        assertTrue(s.tryAcquire(2));
        assertEquals(0, s.availablePermits());
        assertFalse(s.tryAcquire());
    }

    /**
     * Acquire and release of semaphore succeed if initially available
     */
    public void testAcquireReleaseInSameThread_fair()
        throws InterruptedException {
        Semaphore s = new Semaphore(1, true);
        s.acquire();
        s.release();
        s.acquire();
        s.release();
        s.acquire();
        s.release();
        s.acquire();
        s.release();
        s.acquire();
        s.release();
        assertEquals(1, s.availablePermits());
    }

    /**
     * Acquire(n) and release(n) of semaphore succeed if initially available
     */
    public void testAcquireReleaseNInSameThread_fair()
        throws InterruptedException {
        Semaphore s = new Semaphore(1, true);
        s.release(1);
        s.acquire(1);
        s.release(2);
        s.acquire(2);
        s.release(3);
        s.acquire(3);
        s.release(4);
        s.acquire(4);
        s.release(5);
        s.acquire(5);
        assertEquals(1, s.availablePermits());
    }

    /**
     * Acquire(n) and release(n) of semaphore succeed if initially available
     */
    public void testAcquireUninterruptiblyReleaseNInSameThread_fair() {
        Semaphore s = new Semaphore(1, true);
        s.release(1);
        s.acquireUninterruptibly(1);
        s.release(2);
        s.acquireUninterruptibly(2);
        s.release(3);
        s.acquireUninterruptibly(3);
        s.release(4);
        s.acquireUninterruptibly(4);
        s.release(5);
        s.acquireUninterruptibly(5);
        assertEquals(1, s.availablePermits());
    }

    /**
     * release(n) in one thread enables timed acquire(n) in another thread
     */
    public void testTimedAcquireReleaseNInSameThread_fair()
        throws InterruptedException {
        Semaphore s = new Semaphore(1, true);
        s.release(1);
        assertTrue(s.tryAcquire(1, SHORT_DELAY_MS, MILLISECONDS));
        s.release(2);
        assertTrue(s.tryAcquire(2, SHORT_DELAY_MS, MILLISECONDS));
        s.release(3);
        assertTrue(s.tryAcquire(3, SHORT_DELAY_MS, MILLISECONDS));
        s.release(4);
        assertTrue(s.tryAcquire(4, SHORT_DELAY_MS, MILLISECONDS));
        s.release(5);
        assertTrue(s.tryAcquire(5, SHORT_DELAY_MS, MILLISECONDS));
        assertEquals(1, s.availablePermits());
    }

    /**
     * release in one thread enables timed acquire in another thread
     */
    public void testTimedAcquireReleaseInSameThread_fair()
        throws InterruptedException {
        Semaphore s = new Semaphore(1, true);
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
        s.release();
        assertEquals(1, s.availablePermits());
    }

    /**
     * A release in one thread enables an acquire in another thread
     */
    public void testAcquireReleaseInDifferentThreads_fair()
        throws InterruptedException {
        final Semaphore s = new Semaphore(0, true);
        Thread t = new Thread(new CheckedRunnable() {
            public void realRun() throws InterruptedException {
                s.acquire();
                s.acquire();
                s.acquire();
                s.acquire();
            }});

        t.start();
        delay(SHORT_DELAY_MS);
        s.release();
        s.release();
        s.release();
        s.release();
        s.release();
        s.release();
        t.join();
        assertEquals(2, s.availablePermits());
    }

    /**
     * release(n) in one thread enables acquire(n) in another thread
     */
    public void testAcquireReleaseNInDifferentThreads_fair()
        throws InterruptedException {
        final Semaphore s = new Semaphore(0, true);
        Thread t = new Thread(new CheckedRunnable() {
            public void realRun() throws InterruptedException {
                s.acquire();
                s.release(2);
                s.acquire();
            }});

        t.start();
        delay(SHORT_DELAY_MS);
        s.release(2);
        s.acquire(2);
        s.release(1);
        t.join();
    }

    /**
     * release(n) in one thread enables acquire(n) in another thread
     */
    public void testAcquireReleaseNInDifferentThreads_fair2()
        throws InterruptedException {
        final Semaphore s = new Semaphore(0, true);
        Thread t = new Thread(new CheckedRunnable() {
            public void realRun() throws InterruptedException {
                s.acquire(2);
                s.acquire(2);
                s.release(4);
            }});

        t.start();
        delay(SHORT_DELAY_MS);
        s.release(6);
        s.acquire(2);
        s.acquire(2);
        s.release(2);
        t.join();
    }


    /**
     * release in one thread enables timed acquire in another thread
     */
    public void testTimedAcquireReleaseInDifferentThreads_fair()
        throws InterruptedException {
        final Semaphore s = new Semaphore(1, true);
        Thread t = new Thread(new CheckedRunnable() {
            public void realRun() throws InterruptedException {
                assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
                assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
                assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
                assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
                assertTrue(s.tryAcquire(SHORT_DELAY_MS, MILLISECONDS));
            }});

        t.start();
        s.release();
        s.release();
        s.release();
        s.release();
        s.release();
        t.join();
    }

    /**
     * release(n) in one thread enables timed acquire(n) in another thread
     */
    public void testTimedAcquireReleaseNInDifferentThreads_fair()
        throws InterruptedException {
        final Semaphore s = new Semaphore(2, true);
        Thread t = new Thread(new CheckedRunnable() {
            public void realRun() throws InterruptedException {
                assertTrue(s.tryAcquire(2, SHORT_DELAY_MS, MILLISECONDS));
                s.release(2);
                assertTrue(s.tryAcquire(2, SHORT_DELAY_MS, MILLISECONDS));
                s.release(2);
            }});

        t.start();
        assertTrue(s.tryAcquire(2, SHORT_DELAY_MS, MILLISECONDS));
        s.release(2);
        assertTrue(s.tryAcquire(2, SHORT_DELAY_MS, MILLISECONDS));
        s.release(2);
        t.join();
    }

    /**
     * A waiting acquire blocks interruptibly
     */
    public void testAcquire_InterruptedException_fair()
        throws InterruptedException {
        final Semaphore s = new Semaphore(0, true);
        Thread t = new Thread(new CheckedInterruptedRunnable() {
            public void realRun() throws InterruptedException {
                s.acquire();
            }});

        t.start();
        delay(SHORT_DELAY_MS);
        t.interrupt();
        t.join();
    }

    /**
     * A waiting acquire(n) blocks interruptibly
     */
    public void testAcquireN_InterruptedException_fair()
        throws InterruptedException {
        final Semaphore s = new Semaphore(2, true);
        Thread t = new Thread(new CheckedInterruptedRunnable() {
            public void realRun() throws InterruptedException {
                s.acquire(3);
            }});

        t.start();
        delay(SHORT_DELAY_MS);
        t.interrupt();
        t.join();
    }

    /**
     * A waiting tryAcquire blocks interruptibly
     */
    public void testTryAcquire_InterruptedException_fair()
        throws InterruptedException {
        final Semaphore s = new Semaphore(0, true);
        Thread t = new Thread(new CheckedInterruptedRunnable() {
            public void realRun() throws InterruptedException {
                s.tryAcquire(MEDIUM_DELAY_MS, MILLISECONDS);
            }});

        t.start();
        delay(SHORT_DELAY_MS);
        t.interrupt();
        t.join();
    }

    /**
     * A waiting tryAcquire(n) blocks interruptibly
     */
    public void testTryAcquireN_InterruptedException_fair()
        throws InterruptedException {
        final Semaphore s = new Semaphore(1, true);
        Thread t = new Thread(new CheckedInterruptedRunnable() {
            public void realRun() throws InterruptedException {
                s.tryAcquire(4, MEDIUM_DELAY_MS, MILLISECONDS);
            }});

        t.start();
        delay(SHORT_DELAY_MS);
        t.interrupt();
        t.join();
    }

    /**
     * getQueueLength reports number of waiting threads
     */
    public void testGetQueueLength_fair() throws InterruptedException {
        final Semaphore lock = new Semaphore(1, true);
        Thread t1 = new Thread(new InterruptedLockRunnable(lock));
        Thread t2 = new Thread(new InterruptibleLockRunnable(lock));
        assertEquals(0, lock.getQueueLength());
        lock.acquireUninterruptibly();
        t1.start();
        delay(SHORT_DELAY_MS);
        assertEquals(1, lock.getQueueLength());
        t2.start();
        delay(SHORT_DELAY_MS);
        assertEquals(2, lock.getQueueLength());
        t1.interrupt();
        delay(SHORT_DELAY_MS);
        assertEquals(1, lock.getQueueLength());
        lock.release();
        delay(SHORT_DELAY_MS);
        assertEquals(0, lock.getQueueLength());
        t1.join();
        t2.join();
    }


    /**
     * a deserialized serialized semaphore has same number of permits
     */
    public void testSerialization_fair() throws Exception {
        Semaphore l = new Semaphore(3, true);

        l.acquire();
        l.release();
        ByteArrayOutputStream bout = new ByteArrayOutputStream(10000);
        ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(bout));
        out.writeObject(l);
        out.close();

        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(bin));
        Semaphore r = (Semaphore) in.readObject();
        assertEquals(3, r.availablePermits());
        assertTrue(r.isFair());
        r.acquire();
        r.release();
    }

    /**
     * toString indicates current number of permits
     */
    public void testToString() {
        Semaphore s = new Semaphore(0);
        String us = s.toString();
        assertTrue(us.indexOf("Permits = 0") >= 0);
        s.release();
        String s1 = s.toString();
        assertTrue(s1.indexOf("Permits = 1") >= 0);
        s.release();
        String s2 = s.toString();
        assertTrue(s2.indexOf("Permits = 2") >= 0);
    }

}
