blob: 09c82c89783b8e8cb87f63ad438cc9460e665b26 [file] [log] [blame]
/*
* 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.
*/
package jsr166;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
import junit.framework.AssertionFailedError;
import junit.framework.Test;
import junit.framework.TestSuite;
public class SemaphoreTest extends JSR166TestCase {
// android-note: Removed because the CTS runner does a bad job of
// retrying tests that have suite() declarations.
//
// public static void main(String[] args) {
// main(suite(), args);
// }
// public static Test suite() {
// return new TestSuite(SemaphoreTest.class);
// }
/**
* Subclass to expose protected methods
*/
static class PublicSemaphore extends Semaphore {
PublicSemaphore(int permits) { super(permits); }
PublicSemaphore(int permits, boolean fair) { super(permits, fair); }
public Collection<Thread> getQueuedThreads() {
return super.getQueuedThreads();
}
public boolean hasQueuedThread(Thread t) {
return super.getQueuedThreads().contains(t);
}
public void reducePermits(int reduction) {
super.reducePermits(reduction);
}
}
/**
* A runnable calling acquire
*/
class InterruptibleLockRunnable extends CheckedRunnable {
final Semaphore lock;
InterruptibleLockRunnable(Semaphore s) { lock = s; }
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 s) { lock = s; }
public void realRun() throws InterruptedException {
lock.acquire();
}
}
/**
* Spin-waits until s.hasQueuedThread(t) becomes true.
*/
void waitForQueuedThread(PublicSemaphore s, Thread t) {
long startTime = System.nanoTime();
while (!s.hasQueuedThread(t)) {
if (millisElapsedSince(startTime) > LONG_DELAY_MS)
throw new AssertionFailedError("timed out");
Thread.yield();
}
assertTrue(s.hasQueuedThreads());
assertTrue(t.isAlive());
}
/**
* Spin-waits until s.hasQueuedThreads() becomes true.
*/
void waitForQueuedThreads(Semaphore s) {
long startTime = System.nanoTime();
while (!s.hasQueuedThreads()) {
if (millisElapsedSince(startTime) > LONG_DELAY_MS)
throw new AssertionFailedError("timed out");
Thread.yield();
}
}
enum AcquireMethod {
acquire() {
void acquire(Semaphore s) throws InterruptedException {
s.acquire();
}
},
acquireN() {
void acquire(Semaphore s, int permits) throws InterruptedException {
s.acquire(permits);
}
},
acquireUninterruptibly() {
void acquire(Semaphore s) {
s.acquireUninterruptibly();
}
},
acquireUninterruptiblyN() {
void acquire(Semaphore s, int permits) {
s.acquireUninterruptibly(permits);
}
},
tryAcquire() {
void acquire(Semaphore s) {
assertTrue(s.tryAcquire());
}
},
tryAcquireN() {
void acquire(Semaphore s, int permits) {
assertTrue(s.tryAcquire(permits));
}
},
tryAcquireTimed() {
void acquire(Semaphore s) throws InterruptedException {
assertTrue(s.tryAcquire(2 * LONG_DELAY_MS, MILLISECONDS));
}
},
tryAcquireTimedN {
void acquire(Semaphore s, int permits) throws InterruptedException {
assertTrue(s.tryAcquire(permits, 2 * LONG_DELAY_MS, MILLISECONDS));
}
};
// Intentionally meta-circular
/** Acquires 1 permit. */
void acquire(Semaphore s) throws InterruptedException {
acquire(s, 1);
}
/** Acquires the given number of permits. */
void acquire(Semaphore s, int permits) throws InterruptedException {
for (int i = 0; i < permits; i++)
acquire(s);
}
}
/**
* Zero, negative, and positive initial values are allowed in constructor
*/
public void testConstructor() { testConstructor(false); }
public void testConstructor_fair() { testConstructor(true); }
public void testConstructor(boolean fair) {
for (int permits : new int[] { -42, -1, 0, 1, 42 }) {
Semaphore s = new Semaphore(permits, fair);
assertEquals(permits, s.availablePermits());
assertEquals(fair, s.isFair());
}
}
/**
* Constructor without fairness argument behaves as nonfair
*/
public void testConstructorDefaultsToNonFair() {
for (int permits : new int[] { -42, -1, 0, 1, 42 }) {
Semaphore s = new Semaphore(permits);
assertEquals(permits, s.availablePermits());
assertFalse(s.isFair());
}
}
/**
* tryAcquire succeeds when sufficient permits, else fails
*/
public void testTryAcquireInSameThread() { testTryAcquireInSameThread(false); }
public void testTryAcquireInSameThread_fair() { testTryAcquireInSameThread(true); }
public void testTryAcquireInSameThread(boolean fair) {
Semaphore s = new Semaphore(2, fair);
assertEquals(2, s.availablePermits());
assertTrue(s.tryAcquire());
assertTrue(s.tryAcquire());
assertEquals(0, s.availablePermits());
assertFalse(s.tryAcquire());
assertFalse(s.tryAcquire());
assertEquals(0, s.availablePermits());
}
/**
* timed tryAcquire times out
*/
public void testTryAcquire_timeout() { testTryAcquire_timeout(false); }
public void testTryAcquire_timeout_fair() { testTryAcquire_timeout(true); }
public void testTryAcquire_timeout(boolean fair) {
Semaphore s = new Semaphore(0, fair);
long startTime = System.nanoTime();
try { assertFalse(s.tryAcquire(timeoutMillis(), MILLISECONDS)); }
catch (InterruptedException e) { threadUnexpectedException(e); }
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
}
/**
* timed tryAcquire(N) times out
*/
public void testTryAcquireN_timeout() { testTryAcquireN_timeout(false); }
public void testTryAcquireN_timeout_fair() { testTryAcquireN_timeout(true); }
public void testTryAcquireN_timeout(boolean fair) {
Semaphore s = new Semaphore(2, fair);
long startTime = System.nanoTime();
try { assertFalse(s.tryAcquire(3, timeoutMillis(), MILLISECONDS)); }
catch (InterruptedException e) { threadUnexpectedException(e); }
assertTrue(millisElapsedSince(startTime) >= timeoutMillis());
}
/**
* acquire(), acquire(N), timed tryAcquired, timed tryAcquire(N)
* are interruptible
*/
public void testInterruptible_acquire() { testInterruptible(false, AcquireMethod.acquire); }
public void testInterruptible_acquire_fair() { testInterruptible(true, AcquireMethod.acquire); }
public void testInterruptible_acquireN() { testInterruptible(false, AcquireMethod.acquireN); }
public void testInterruptible_acquireN_fair() { testInterruptible(true, AcquireMethod.acquireN); }
public void testInterruptible_tryAcquireTimed() { testInterruptible(false, AcquireMethod.tryAcquireTimed); }
public void testInterruptible_tryAcquireTimed_fair() { testInterruptible(true, AcquireMethod.tryAcquireTimed); }
public void testInterruptible_tryAcquireTimedN() { testInterruptible(false, AcquireMethod.tryAcquireTimedN); }
public void testInterruptible_tryAcquireTimedN_fair() { testInterruptible(true, AcquireMethod.tryAcquireTimedN); }
public void testInterruptible(boolean fair, final AcquireMethod acquirer) {
final PublicSemaphore s = new PublicSemaphore(0, fair);
final Semaphore pleaseInterrupt = new Semaphore(0, fair);
Thread t = newStartedThread(new CheckedRunnable() {
public void realRun() {
// Interrupt before acquire
Thread.currentThread().interrupt();
try {
acquirer.acquire(s);
shouldThrow();
} catch (InterruptedException success) {}
// Interrupt during acquire
try {
acquirer.acquire(s);
shouldThrow();
} catch (InterruptedException success) {}
// Interrupt before acquire(N)
Thread.currentThread().interrupt();
try {
acquirer.acquire(s, 3);
shouldThrow();
} catch (InterruptedException success) {}
pleaseInterrupt.release();
// Interrupt during acquire(N)
try {
acquirer.acquire(s, 3);
shouldThrow();
} catch (InterruptedException success) {}
}});
waitForQueuedThread(s, t);
t.interrupt();
await(pleaseInterrupt);
waitForQueuedThread(s, t);
t.interrupt();
awaitTermination(t);
}
/**
* acquireUninterruptibly(), acquireUninterruptibly(N) are
* uninterruptible
*/
public void testUninterruptible_acquireUninterruptibly() { testUninterruptible(false, AcquireMethod.acquireUninterruptibly); }
public void testUninterruptible_acquireUninterruptibly_fair() { testUninterruptible(true, AcquireMethod.acquireUninterruptibly); }
public void testUninterruptible_acquireUninterruptiblyN() { testUninterruptible(false, AcquireMethod.acquireUninterruptiblyN); }
public void testUninterruptible_acquireUninterruptiblyN_fair() { testUninterruptible(true, AcquireMethod.acquireUninterruptiblyN); }
public void testUninterruptible(boolean fair, final AcquireMethod acquirer) {
final PublicSemaphore s = new PublicSemaphore(0, fair);
final Semaphore pleaseInterrupt = new Semaphore(-1, fair);
Thread t1 = newStartedThread(new CheckedRunnable() {
public void realRun() throws InterruptedException {
// Interrupt before acquire
pleaseInterrupt.release();
Thread.currentThread().interrupt();
acquirer.acquire(s);
assertTrue(Thread.interrupted());
}});
Thread t2 = newStartedThread(new CheckedRunnable() {
public void realRun() throws InterruptedException {
// Interrupt during acquire
pleaseInterrupt.release();
acquirer.acquire(s);
assertTrue(Thread.interrupted());
}});
await(pleaseInterrupt);
waitForQueuedThread(s, t1);
waitForQueuedThread(s, t2);
t2.interrupt();
assertThreadStaysAlive(t1);
assertTrue(t2.isAlive());
s.release(2);
awaitTermination(t1);
awaitTermination(t2);
}
/**
* hasQueuedThreads reports whether there are waiting threads
*/
public void testHasQueuedThreads() { testHasQueuedThreads(false); }
public void testHasQueuedThreads_fair() { testHasQueuedThreads(true); }
public void testHasQueuedThreads(boolean fair) {
final PublicSemaphore lock = new PublicSemaphore(1, fair);
assertFalse(lock.hasQueuedThreads());
lock.acquireUninterruptibly();
Thread t1 = newStartedThread(new InterruptedLockRunnable(lock));
waitForQueuedThread(lock, t1);
assertTrue(lock.hasQueuedThreads());
Thread t2 = newStartedThread(new InterruptibleLockRunnable(lock));
waitForQueuedThread(lock, t2);
assertTrue(lock.hasQueuedThreads());
t1.interrupt();
awaitTermination(t1);
assertTrue(lock.hasQueuedThreads());
lock.release();
awaitTermination(t2);
assertFalse(lock.hasQueuedThreads());
}
/**
* getQueueLength reports number of waiting threads
*/
public void testGetQueueLength() { testGetQueueLength(false); }
public void testGetQueueLength_fair() { testGetQueueLength(true); }
public void testGetQueueLength(boolean fair) {
final PublicSemaphore lock = new PublicSemaphore(1, fair);
assertEquals(0, lock.getQueueLength());
lock.acquireUninterruptibly();
Thread t1 = newStartedThread(new InterruptedLockRunnable(lock));
waitForQueuedThread(lock, t1);
assertEquals(1, lock.getQueueLength());
Thread t2 = newStartedThread(new InterruptibleLockRunnable(lock));
waitForQueuedThread(lock, t2);
assertEquals(2, lock.getQueueLength());
t1.interrupt();
awaitTermination(t1);
assertEquals(1, lock.getQueueLength());
lock.release();
awaitTermination(t2);
assertEquals(0, lock.getQueueLength());
}
/**
* getQueuedThreads includes waiting threads
*/
public void testGetQueuedThreads() { testGetQueuedThreads(false); }
public void testGetQueuedThreads_fair() { testGetQueuedThreads(true); }
public void testGetQueuedThreads(boolean fair) {
final PublicSemaphore lock = new PublicSemaphore(1, fair);
assertTrue(lock.getQueuedThreads().isEmpty());
lock.acquireUninterruptibly();
assertTrue(lock.getQueuedThreads().isEmpty());
Thread t1 = newStartedThread(new InterruptedLockRunnable(lock));
waitForQueuedThread(lock, t1);
assertTrue(lock.getQueuedThreads().contains(t1));
Thread t2 = newStartedThread(new InterruptibleLockRunnable(lock));
waitForQueuedThread(lock, t2);
assertTrue(lock.getQueuedThreads().contains(t1));
assertTrue(lock.getQueuedThreads().contains(t2));
t1.interrupt();
awaitTermination(t1);
assertFalse(lock.getQueuedThreads().contains(t1));
assertTrue(lock.getQueuedThreads().contains(t2));
lock.release();
awaitTermination(t2);
assertTrue(lock.getQueuedThreads().isEmpty());
}
/**
* drainPermits reports and removes given number of permits
*/
public void testDrainPermits() { testDrainPermits(false); }
public void testDrainPermits_fair() { testDrainPermits(true); }
public void testDrainPermits(boolean fair) {
Semaphore s = new Semaphore(0, fair);
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());
}
/**
* release(-N) throws IllegalArgumentException
*/
public void testReleaseIAE() { testReleaseIAE(false); }
public void testReleaseIAE_fair() { testReleaseIAE(true); }
public void testReleaseIAE(boolean fair) {
Semaphore s = new Semaphore(10, fair);
try {
s.release(-1);
shouldThrow();
} catch (IllegalArgumentException success) {}
}
/**
* reducePermits(-N) throws IllegalArgumentException
*/
public void testReducePermitsIAE() { testReducePermitsIAE(false); }
public void testReducePermitsIAE_fair() { testReducePermitsIAE(true); }
public void testReducePermitsIAE(boolean fair) {
PublicSemaphore s = new PublicSemaphore(10, fair);
try {
s.reducePermits(-1);
shouldThrow();
} catch (IllegalArgumentException success) {}
}
/**
* reducePermits reduces number of permits
*/
public void testReducePermits() { testReducePermits(false); }
public void testReducePermits_fair() { testReducePermits(true); }
public void testReducePermits(boolean fair) {
PublicSemaphore s = new PublicSemaphore(10, fair);
assertEquals(10, s.availablePermits());
s.reducePermits(0);
assertEquals(10, s.availablePermits());
s.reducePermits(1);
assertEquals(9, s.availablePermits());
s.reducePermits(10);
assertEquals(-1, s.availablePermits());
s.reducePermits(10);
assertEquals(-11, s.availablePermits());
s.reducePermits(0);
assertEquals(-11, s.availablePermits());
}
/**
* a reserialized semaphore has same number of permits and
* fairness, but no queued threads
*/
public void testSerialization() { testSerialization(false); }
public void testSerialization_fair() { testSerialization(true); }
public void testSerialization(boolean fair) {
try {
Semaphore s = new Semaphore(3, fair);
s.acquire();
s.acquire();
s.release();
Semaphore clone = serialClone(s);
assertEquals(fair, s.isFair());
assertEquals(fair, clone.isFair());
assertEquals(2, s.availablePermits());
assertEquals(2, clone.availablePermits());
clone.acquire();
clone.acquire();
clone.release();
assertEquals(2, s.availablePermits());
assertEquals(1, clone.availablePermits());
assertFalse(s.hasQueuedThreads());
assertFalse(clone.hasQueuedThreads());
} catch (InterruptedException e) { threadUnexpectedException(e); }
{
PublicSemaphore s = new PublicSemaphore(0, fair);
Thread t = newStartedThread(new InterruptibleLockRunnable(s));
// waitForQueuedThreads(s); // suffers from "flicker", so ...
waitForQueuedThread(s, t); // ... we use this instead
PublicSemaphore clone = serialClone(s);
assertEquals(fair, s.isFair());
assertEquals(fair, clone.isFair());
assertEquals(0, s.availablePermits());
assertEquals(0, clone.availablePermits());
assertTrue(s.hasQueuedThreads());
assertFalse(clone.hasQueuedThreads());
s.release();
awaitTermination(t);
assertFalse(s.hasQueuedThreads());
assertFalse(clone.hasQueuedThreads());
}
}
/**
* tryAcquire(n) succeeds when sufficient permits, else fails
*/
public void testTryAcquireNInSameThread() { testTryAcquireNInSameThread(false); }
public void testTryAcquireNInSameThread_fair() { testTryAcquireNInSameThread(true); }
public void testTryAcquireNInSameThread(boolean fair) {
Semaphore s = new Semaphore(2, fair);
assertEquals(2, s.availablePermits());
assertFalse(s.tryAcquire(3));
assertEquals(2, s.availablePermits());
assertTrue(s.tryAcquire(2));
assertEquals(0, s.availablePermits());
assertFalse(s.tryAcquire(1));
assertFalse(s.tryAcquire(2));
assertEquals(0, s.availablePermits());
}
/**
* acquire succeeds if permits available
*/
public void testReleaseAcquireSameThread_acquire() { testReleaseAcquireSameThread(false, AcquireMethod.acquire); }
public void testReleaseAcquireSameThread_acquire_fair() { testReleaseAcquireSameThread(true, AcquireMethod.acquire); }
public void testReleaseAcquireSameThread_acquireN() { testReleaseAcquireSameThread(false, AcquireMethod.acquireN); }
public void testReleaseAcquireSameThread_acquireN_fair() { testReleaseAcquireSameThread(true, AcquireMethod.acquireN); }
public void testReleaseAcquireSameThread_acquireUninterruptibly() { testReleaseAcquireSameThread(false, AcquireMethod.acquireUninterruptibly); }
public void testReleaseAcquireSameThread_acquireUninterruptibly_fair() { testReleaseAcquireSameThread(true, AcquireMethod.acquireUninterruptibly); }
public void testReleaseAcquireSameThread_acquireUninterruptiblyN() { testReleaseAcquireSameThread(false, AcquireMethod.acquireUninterruptibly); }
public void testReleaseAcquireSameThread_acquireUninterruptiblyN_fair() { testReleaseAcquireSameThread(true, AcquireMethod.acquireUninterruptibly); }
public void testReleaseAcquireSameThread_tryAcquire() { testReleaseAcquireSameThread(false, AcquireMethod.tryAcquire); }
public void testReleaseAcquireSameThread_tryAcquire_fair() { testReleaseAcquireSameThread(true, AcquireMethod.tryAcquire); }
public void testReleaseAcquireSameThread_tryAcquireN() { testReleaseAcquireSameThread(false, AcquireMethod.tryAcquireN); }
public void testReleaseAcquireSameThread_tryAcquireN_fair() { testReleaseAcquireSameThread(true, AcquireMethod.tryAcquireN); }
public void testReleaseAcquireSameThread_tryAcquireTimed() { testReleaseAcquireSameThread(false, AcquireMethod.tryAcquireTimed); }
public void testReleaseAcquireSameThread_tryAcquireTimed_fair() { testReleaseAcquireSameThread(true, AcquireMethod.tryAcquireTimed); }
public void testReleaseAcquireSameThread_tryAcquireTimedN() { testReleaseAcquireSameThread(false, AcquireMethod.tryAcquireTimedN); }
public void testReleaseAcquireSameThread_tryAcquireTimedN_fair() { testReleaseAcquireSameThread(true, AcquireMethod.tryAcquireTimedN); }
public void testReleaseAcquireSameThread(boolean fair,
final AcquireMethod acquirer) {
Semaphore s = new Semaphore(1, fair);
for (int i = 1; i < 6; i++) {
s.release(i);
assertEquals(1 + i, s.availablePermits());
try {
acquirer.acquire(s, i);
} catch (InterruptedException e) { threadUnexpectedException(e); }
assertEquals(1, s.availablePermits());
}
}
/**
* release in one thread enables acquire in another thread
*/
public void testReleaseAcquireDifferentThreads_acquire() { testReleaseAcquireDifferentThreads(false, AcquireMethod.acquire); }
public void testReleaseAcquireDifferentThreads_acquire_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.acquire); }
public void testReleaseAcquireDifferentThreads_acquireN() { testReleaseAcquireDifferentThreads(false, AcquireMethod.acquireN); }
public void testReleaseAcquireDifferentThreads_acquireN_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.acquireN); }
public void testReleaseAcquireDifferentThreads_acquireUninterruptibly() { testReleaseAcquireDifferentThreads(false, AcquireMethod.acquireUninterruptibly); }
public void testReleaseAcquireDifferentThreads_acquireUninterruptibly_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.acquireUninterruptibly); }
public void testReleaseAcquireDifferentThreads_acquireUninterruptiblyN() { testReleaseAcquireDifferentThreads(false, AcquireMethod.acquireUninterruptibly); }
public void testReleaseAcquireDifferentThreads_acquireUninterruptiblyN_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.acquireUninterruptibly); }
public void testReleaseAcquireDifferentThreads_tryAcquireTimed() { testReleaseAcquireDifferentThreads(false, AcquireMethod.tryAcquireTimed); }
public void testReleaseAcquireDifferentThreads_tryAcquireTimed_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.tryAcquireTimed); }
public void testReleaseAcquireDifferentThreads_tryAcquireTimedN() { testReleaseAcquireDifferentThreads(false, AcquireMethod.tryAcquireTimedN); }
public void testReleaseAcquireDifferentThreads_tryAcquireTimedN_fair() { testReleaseAcquireDifferentThreads(true, AcquireMethod.tryAcquireTimedN); }
public void testReleaseAcquireDifferentThreads(boolean fair,
final AcquireMethod acquirer) {
final Semaphore s = new Semaphore(0, fair);
final int rounds = 4;
long startTime = System.nanoTime();
Thread t = newStartedThread(new CheckedRunnable() {
public void realRun() throws InterruptedException {
for (int i = 0; i < rounds; i++) {
assertFalse(s.hasQueuedThreads());
if (i % 2 == 0)
acquirer.acquire(s);
else
acquirer.acquire(s, 3);
}}});
for (int i = 0; i < rounds; i++) {
while (! (s.availablePermits() == 0 && s.hasQueuedThreads()))
Thread.yield();
assertTrue(t.isAlive());
if (i % 2 == 0)
s.release();
else
s.release(3);
}
awaitTermination(t);
assertEquals(0, s.availablePermits());
assertTrue(millisElapsedSince(startTime) < LONG_DELAY_MS);
}
/**
* fair locks are strictly FIFO
*/
public void testFairLocksFifo() {
final PublicSemaphore s = new PublicSemaphore(1, true);
final CountDownLatch pleaseRelease = new CountDownLatch(1);
Thread t1 = newStartedThread(new CheckedRunnable() {
public void realRun() throws InterruptedException {
// Will block; permits are available, but not three
s.acquire(3);
}});
waitForQueuedThread(s, t1);
Thread t2 = newStartedThread(new CheckedRunnable() {
public void realRun() throws InterruptedException {
// Will fail, even though 1 permit is available
assertFalse(s.tryAcquire(0L, MILLISECONDS));
assertFalse(s.tryAcquire(1, 0L, MILLISECONDS));
// untimed tryAcquire will barge and succeed
assertTrue(s.tryAcquire());
s.release(2);
assertTrue(s.tryAcquire(2));
s.release();
pleaseRelease.countDown();
// Will queue up behind t1, even though 1 permit is available
s.acquire();
}});
await(pleaseRelease);
waitForQueuedThread(s, t2);
s.release(2);
awaitTermination(t1);
assertTrue(t2.isAlive());
s.release();
awaitTermination(t2);
}
/**
* toString indicates current number of permits
*/
public void testToString() { testToString(false); }
public void testToString_fair() { testToString(true); }
public void testToString(boolean fair) {
PublicSemaphore s = new PublicSemaphore(0, fair);
assertTrue(s.toString().contains("Permits = 0"));
s.release();
assertTrue(s.toString().contains("Permits = 1"));
s.release(2);
assertTrue(s.toString().contains("Permits = 3"));
s.reducePermits(5);
assertTrue(s.toString().contains("Permits = -2"));
}
}