(no commit message)
[utils] / support / general / src / test / java / org / wamblee / concurrency / ReadWriteLockTest.java
diff --git a/support/general/src/test/java/org/wamblee/concurrency/ReadWriteLockTest.java b/support/general/src/test/java/org/wamblee/concurrency/ReadWriteLockTest.java
new file mode 100644 (file)
index 0000000..2c159e9
--- /dev/null
@@ -0,0 +1,507 @@
+/*
+ * Copyright 2005 the original author or authors.
+ * 
+ * Licensed 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.
+ */ 
+package org.wamblee.concurrency;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+
+/**
+ * Testing the read-write lock class. Note: in case of problems, test cases
+ * could hang.
+ *
+ * @see ReadWriteLock
+ */
+public class ReadWriteLockTest extends TestCase {
+    /**
+     * 
+     */
+    private static final int HALF_SECOND = 500;
+    /**
+     * 
+     */
+    private static final int ONE_SECOND = 1000;
+    /**
+     * 
+     */
+    private static final int TWO_SECONDS = 2000;
+    private ReadWriteLock _lock;
+    private int                   _nReaders;
+    private int                   _nWriters;
+
+    /**
+     * Constructor for ReadWriteLockTest.
+     *
+     * @param aName
+     */
+    public ReadWriteLockTest(String aName) {
+        super(aName);
+    }
+
+    private synchronized int getReaderCount() {
+        return _nReaders;
+    }
+
+    private synchronized int getWriterCount() {
+        return _nWriters;
+    }
+
+    synchronized void incrementReaderCount() {
+        _nReaders++;
+    }
+
+    synchronized void incrementWriterCount() {
+        _nWriters++;
+    }
+
+    synchronized void decrementReaderCount() {
+        _nReaders--;
+    }
+
+    synchronized void decrementWriterCount() {
+        _nWriters--;
+    }
+
+    /*
+     * @see TestCase#setUp()
+     */
+    protected void setUp() throws Exception {
+        super.setUp();
+        _lock = new ReadWriteLock();
+    }
+
+    /*
+     * @see TestCase#tearDown()
+     */
+    protected void tearDown() throws Exception {
+        _lock = null;
+        super.tearDown();
+    }
+
+    /**
+     * Acquire and release a read lock.
+     */
+    public void testRead() {
+        _lock.acquireRead();
+        _lock.releaseRead();
+    }
+
+    /**
+     * Acquire and release a write lock.
+     */
+    public void testWrite() {
+        _lock.acquireWrite();
+        _lock.releaseWrite();
+    }
+
+    /**
+     * Verify concurrent access by multiple readers is possible.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testMultipleReaders() throws InterruptedException {
+        Runnable runnable = new ReadLocker(_lock, this, TWO_SECONDS);
+
+        Thread   t1 = new Thread(runnable);
+        t1.start();
+
+        Thread t2 = new Thread(runnable);
+        t2.start();
+        Thread.sleep(ONE_SECOND);
+        assertTrue("Not enough readers!", getReaderCount() == 2);
+        t1.join();
+        t2.join();
+    }
+
+    /**
+     * Verify that only one writer at a time can acquire the write lock.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testSingleWriter() throws InterruptedException {
+        WriteLocker writer = new WriteLocker(_lock, this, ONE_SECOND);
+        Thread      t1 = new Thread(writer);
+        Thread      t2 = new Thread(writer);
+
+        t1.start();
+        t2.start();
+        Thread.sleep(HALF_SECOND);
+        assertTrue("Wrong writer count: " + getWriterCount(),
+            getWriterCount() == 1);
+        t1.join();
+        t2.join();
+    }
+
+    /**
+     * Verify that multiple writers cannot acquire the write lock concurrently.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testMultipleWriters() throws InterruptedException {
+        WriteLocker writer1 = new WriteLocker(_lock, this, HALF_SECOND + ONE_SECOND);
+        WriteLocker writer2 = new WriteLocker(_lock, this, ONE_SECOND);
+        Thread      t1      = new Thread(writer1);
+        Thread      t2      = new Thread(writer2);
+
+        t1.start();
+        Thread.sleep(HALF_SECOND);
+        assertTrue(getWriterCount() == 1);
+        t2.start();
+        Thread.sleep(HALF_SECOND);
+        assertTrue(getWriterCount() == 1); // first writer still
+
+        // has the lock.
+        Thread.sleep(ONE_SECOND);
+
+        // at t = 2, the second writer still must have
+        // a lock. 
+        assertTrue(getWriterCount() == 1);
+        t1.join();
+        t2.join();
+    }
+
+    /**
+     * Verify that after the first reader acquires a lock,  a subsequent writer
+     * can only acquire the lock after the reader has released it.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testReadWrite1() throws InterruptedException {
+        ReadLocker  readLocker  = new ReadLocker(_lock, this, TWO_SECONDS);
+        Thread      t1          = new Thread(readLocker);
+        WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+        Thread      t2          = new Thread(writeLocker);
+
+        t1.start(); // acquire read lock
+        Thread.sleep(HALF_SECOND);
+        assertTrue(getReaderCount() == 1);
+        t2.start();
+        Thread.sleep(HALF_SECOND);
+
+        // 1 second underway, reader still holding the
+        //   lock so write lock cannot be acquired.
+        assertTrue(getReaderCount() == 1);
+        assertTrue(getWriterCount() == 0);
+        Thread.sleep(ONE_SECOND + HALF_SECOND);
+
+        // 2.5 seconds underway, read lock released and 
+        // write lock must be acquired. 
+        assertTrue("Wrong no. of readers: " + getReaderCount(),
+            getReaderCount() == 0);
+        assertTrue(getWriterCount() == 1);
+        t1.join();
+        t2.join();
+    }
+
+    /**
+     * Verify that when multiple readers have acquired a read lock, the writer
+     * can only acquire the lock after all readers have released it.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testReadWrite2() throws InterruptedException {
+        ReadLocker  readLocker1 = new ReadLocker(_lock, this, TWO_SECONDS + HALF_SECOND);
+        ReadLocker  readLocker2 = new ReadLocker(_lock, this, TWO_SECONDS + HALF_SECOND);
+        Thread      t1          = new Thread(readLocker1);
+        Thread      t2          = new Thread(readLocker2);
+        WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+        Thread      t3          = new Thread(writeLocker);
+
+        t1.start(); // acquire read lock
+        Thread.sleep(ONE_SECOND);
+        assertTrue(getReaderCount() == 1);
+        t2.start();
+        Thread.sleep(HALF_SECOND);
+        assertTrue(getReaderCount() == 2);
+        t3.start();
+        Thread.sleep(HALF_SECOND);
+
+        // 2 seconds, 
+        assertTrue(getReaderCount() == 2);
+        assertTrue(getWriterCount() == 0);
+        Thread.sleep(ONE_SECOND);
+
+        // 3 seconds underway, first read lock must
+        // have been released.
+        assertTrue(getReaderCount() == 1);
+        assertTrue(getWriterCount() == 0);
+        Thread.sleep(HALF_SECOND);
+
+        // 4 seconds underway, write lock must have 
+        // been acquired. 
+        assertTrue(getReaderCount() == 0);
+        assertTrue(getWriterCount() == 1);
+
+        t1.join();
+        t2.join();
+        t3.join();
+    }
+
+    /**
+     * Verify that after a writer acquires a lock,  a subsequent reader can
+     * only acquire the lock after the writer has released it.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testReadWrite3() throws InterruptedException {
+        ReadLocker  readLocker  = new ReadLocker(_lock, this, TWO_SECONDS);
+        Thread      t1          = new Thread(readLocker);
+        WriteLocker writeLocker = new WriteLocker(_lock, this, TWO_SECONDS);
+        Thread      t2          = new Thread(writeLocker);
+
+        t2.start(); // acquire write lock
+        Thread.sleep(HALF_SECOND);
+        assertTrue(getWriterCount() == 1);
+        t1.start();
+        Thread.sleep(HALF_SECOND);
+
+        // 1 second underway, writer still holding the
+        //   lock so read lock cannot be acquired.
+        assertTrue(getWriterCount() == 1);
+        assertTrue(getReaderCount() == 0);
+        Thread.sleep(ONE_SECOND + HALF_SECOND);
+
+        // 2.5 seconds underway, write lock released and 
+        // read lock must be acquired. 
+        assertTrue("Wrong no. of writers: " + getReaderCount(),
+            getWriterCount() == 0);
+        assertTrue(getReaderCount() == 1);
+        t1.join();
+        t2.join();
+    }
+
+    /*
+     * The following test cases are for testing whether or not
+     * the read write lock checks the locking correctly.
+     * Strictly speaking, these checks wouldn't be necessary
+     * because it involves the contract of the ReadWriteLock which
+     * must be obeyed by users of the ReadWriteLock. Nevertheless,
+     * this is tested anyway to be absolutely sure.
+     */
+
+    /**
+     * Acquire a read lock from one thread, release it from another. Verify
+     * that a RuntimeException is thrown.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testReleaseReadFromWrongThread() throws InterruptedException {
+        Thread t1 = null;
+
+        try {
+            t1 = new Thread(new Runnable() {
+                        public void run() {
+                            ReadWriteLockTest.this._lock.acquireRead();
+                        }
+                    });
+            t1.start();
+            Thread.sleep(ONE_SECOND); // wait until thread is started
+            _lock.releaseRead(); // release lock from wrong thread.
+        } catch (RuntimeException e) {
+            return; // ok
+        } finally {
+            t1.join();
+        }
+
+        fail();
+    }
+
+    /**
+     * Acquire a write lock from one thread, release it from another. Verify
+     * that a RuntimeException is thrown.
+     *
+     * @throws InterruptedException May not occur.
+     */
+    public void testReleaseWriteFromWrongThread() throws InterruptedException {
+        Thread t1 = null;
+
+        try {
+            t1 = new Thread(new Runnable() {
+                        public void run() {
+                            ReadWriteLockTest.this._lock.acquireWrite();
+                        }
+                    });
+            t1.start();
+            Thread.sleep(ONE_SECOND); // wait until thread is started
+            _lock.releaseWrite(); // release lock from wrong thread.
+        } catch (RuntimeException e) {
+            return; // ok
+        } finally {
+            t1.join();
+        }
+
+        fail();
+    }
+
+    /**
+     * Try to acquire a read lock multiple times. Verify that a
+     * RuntimeException is thrown.
+     */
+    public void testAcquireReadTwice() {
+        try {
+            _lock.acquireRead();
+            _lock.acquireRead();
+        } catch (RuntimeException e) {
+            // ok
+            return;
+        }
+
+        fail();
+    }
+
+    /**
+     * Try to acquire a write lock multiple times. Verify that a
+     * RuntimeException is thrown.
+     */
+    public void testAcquireWriteTwice() {
+        try {
+            _lock.acquireWrite();
+            _lock.acquireWrite();
+        } catch (RuntimeException e) {
+            return; // ok
+        }
+
+        fail();
+    }
+
+    /**
+     * Acquire the lock for reading and directly afterwards acquire it for
+     * writing. Verify that a RuntimeException is thrown.
+     */
+    public void testAcquireReadFollowedByWrite() {
+        try {
+            _lock.acquireRead();
+            _lock.acquireWrite();
+        } catch (RuntimeException e) {
+            return; // ok
+        }
+
+        fail();
+    }
+
+    /**
+     * Acquire the lock for writing and directly afterwards acquire it for
+     * reading. Verify that a RuntimeException is thrown.
+     */
+    public void testAcquireWriteFollowedByRead() {
+        try {
+            _lock.acquireWrite();
+            _lock.acquireRead();
+        } catch (RuntimeException e) {
+            return; // ok
+        }
+
+        fail();
+    }
+
+    /**
+     * Acquire a read lock and release it as a write lock. Verify that a
+     * RuntimeException is thrown.
+     */
+    public void testAcquireReadFollowedByReleaseaWrite() {
+        try {
+            _lock.acquireRead();
+            _lock.releaseWrite();
+        } catch (RuntimeException e) {
+            return; // ok
+        }
+
+        fail();
+    }
+
+    /**
+     * Acquire a write lock and release it as a read lock. Verify that a
+     * RuntimeException is thrown.
+     */
+    public void testAcquireWriteFollowedByReleaseRead() {
+        try {
+            _lock.acquireWrite();
+            _lock.releaseRead();
+        } catch (RuntimeException e) {
+            return; // ok
+        }
+
+        fail();
+    }
+}
+
+
+/**
+ * ReadLocker acquires a read lock and performs a callback when  the lock as
+ * been acquired, sleeps for a designated amount of time, releases the read
+ * lock, and performs a callback after the lock has been released.
+ */
+class ReadLocker implements Runnable {
+    private ReadWriteLock     _lock;
+    private ReadWriteLockTest _lockTest;
+    private int               _sleepTime;
+
+    public ReadLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
+        int aSleepTime) {
+        _lock          = aLock;
+        _lockTest      = aLockTest;
+        _sleepTime     = aSleepTime;
+    }
+
+    public void run() {
+        _lock.acquireRead();
+        _lockTest.incrementReaderCount();
+
+        try {
+            Thread.sleep(_sleepTime);
+        } catch (InterruptedException e) {
+            Assert.fail("ReadLocker thread was interrupted."
+                + Thread.currentThread());
+        }
+
+        _lock.releaseRead();
+        _lockTest.decrementReaderCount();
+    }
+}
+
+
+/**
+ * WriteLocker acquires a write lock and performs a callback when  the lock as
+ * been acquired, sleeps for a designated amount of time, releases the write
+ * lock, and performs a callback after the lock has been released.
+ */
+class WriteLocker implements Runnable {
+    private ReadWriteLock     _lock;
+    private ReadWriteLockTest _lockTest;
+    private int               _sleepTime;
+
+    public WriteLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest,
+        int aSleepTime) {
+        _lock          = aLock;
+        _lockTest      = aLockTest;
+        _sleepTime     = aSleepTime;
+    }
+
+    public void run() {
+        _lock.acquireWrite();
+        _lockTest.incrementWriterCount();
+
+        try {
+            Thread.sleep(_sleepTime);
+        } catch (InterruptedException e) {
+            Assert.fail("WriteLocker thread was interrupted: "
+                + Thread.currentThread());
+        }
+
+        _lock.releaseWrite();
+        _lockTest.decrementWriterCount();
+    }
+}