Repository: commons-pool
Updated Branches:
  refs/heads/master 04d1f0d84 -> 65f5d9204


http://git-wip-us.apache.org/repos/asf/commons-pool/blob/0a542546/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java 
b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java
index fd5a871..adc8158 100644
--- a/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java
+++ b/src/test/java/org/apache/commons/pool2/impl/TestGenericObjectPool.java
@@ -60,1220 +60,1137 @@ import org.junit.Test;
  */
 public class TestGenericObjectPool extends TestBaseObjectPool {
 
+    protected static class AtomicIntegerFactory
+    extends BasePooledObjectFactory<AtomicInteger> {
+
+    private long activateLatency = 0;
+    private long passivateLatency = 0;
+    private long createLatency = 0;
+    private long destroyLatency = 0;
+    private long validateLatency = 0;
+
     @Override
-    protected ObjectPool<String> makeEmptyPool(final int mincap) {
-       final GenericObjectPool<String> mtPool =
-               new GenericObjectPool<>(new SimpleFactory());
-       mtPool.setMaxTotal(mincap);
-       mtPool.setMaxIdle(mincap);
-       return mtPool;
+    public void activateObject(final PooledObject<AtomicInteger> p) {
+        p.getObject().incrementAndGet();
+        try {
+            Thread.sleep(activateLatency);
+        } catch (final InterruptedException ex) {}
     }
 
     @Override
-    protected ObjectPool<Object> makeEmptyPool(
-            final PooledObjectFactory<Object> fac) {
-        return new GenericObjectPool<>(fac);
+    public AtomicInteger create() {
+        try {
+            Thread.sleep(createLatency);
+        } catch (final InterruptedException ex) {}
+        return new AtomicInteger(0);
     }
 
     @Override
-    protected Object getNthObject(final int n) {
-        return String.valueOf(n);
+    public void destroyObject(final PooledObject<AtomicInteger> p) {
+        try {
+            Thread.sleep(destroyLatency);
+        } catch (final InterruptedException ex) {}
     }
 
-    @Before
-    public void setUp() throws Exception {
-        simpleFactory = new SimpleFactory();
-        genericObjectPool = new GenericObjectPool<>(simpleFactory);
+    @Override
+    public void passivateObject(final PooledObject<AtomicInteger> p) {
+        p.getObject().decrementAndGet();
+        try {
+            Thread.sleep(passivateLatency);
+        } catch (final InterruptedException ex) {}
     }
 
-    @After
-    public void tearDown() throws Exception {
-        final String poolName = genericObjectPool.getJmxName().toString();
-        genericObjectPool.clear();
-        genericObjectPool.close();
-        genericObjectPool = null;
-        simpleFactory = null;
-
-        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
-        final Set<ObjectName> result = mbs.queryNames(new ObjectName(
-                "org.apache.commoms.pool2:type=GenericObjectPool,*"), null);
-        // There should be no registered pools at this point
-        final int registeredPoolCount = result.size();
-        final StringBuilder msg = new StringBuilder("Current pool is: ");
-        msg.append(poolName);
-        msg.append("  Still open pools are: ");
-        for (final ObjectName name : result) {
-            // Clean these up ready for the next test
-            msg.append(name.toString());
-            msg.append(" created via\n");
-            msg.append(mbs.getAttribute(name, "CreationStackTrace"));
-            msg.append('\n');
-            mbs.unregisterMBean(name);
-        }
-        Assert.assertEquals(msg.toString(), 0, registeredPoolCount);
+    /**
+     * @param activateLatency the activateLatency to set
+     */
+    public void setActivateLatency(final long activateLatency) {
+        this.activateLatency = activateLatency;
     }
 
-    @Test(expected=IllegalArgumentException.class)
-    public void testConstructorNullFactory() {
-        // add dummy assert (won't be invoked because of IAE) to avoid 
"unused" warning
-        assertNotNull(new GenericObjectPool<String>(null));
-        // TODO this currently causes tearDown to report an error
-        // Looks like GOP needs to call close() or jmxUnregister() before 
throwing IAE
+    /**
+     * @param createLatency the createLatency to set
+     */
+    public void setCreateLatency(final long createLatency) {
+        this.createLatency = createLatency;
     }
 
-    @Test(timeout=60000)
-    public void testConstructors() throws Exception {
-
-        // Make constructor arguments all different from defaults
-        final int minIdle = 2;
-        final long maxWait = 3;
-        final int maxIdle = 4;
-        final int maxTotal = 5;
-        final long minEvictableIdleTimeMillis = 6;
-        final int numTestsPerEvictionRun = 7;
-        final boolean testOnBorrow = true;
-        final boolean testOnReturn = true;
-        final boolean testWhileIdle = true;
-        final long timeBetweenEvictionRunsMillis = 8;
-        final boolean blockWhenExhausted = false;
-        final boolean lifo = false;
-        final PooledObjectFactory<Object> dummyFactory = new DummyFactory();
-        try (GenericObjectPool<Object> dummyPool = new 
GenericObjectPool<>(dummyFactory)) {
-            assertEquals(GenericObjectPoolConfig.DEFAULT_MAX_IDLE, 
dummyPool.getMaxIdle());
-            assertEquals(BaseObjectPoolConfig.DEFAULT_MAX_WAIT_MILLIS, 
dummyPool.getMaxWaitMillis());
-            assertEquals(GenericObjectPoolConfig.DEFAULT_MIN_IDLE, 
dummyPool.getMinIdle());
-            assertEquals(GenericObjectPoolConfig.DEFAULT_MAX_TOTAL, 
dummyPool.getMaxTotal());
-            
assertEquals(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS,
-                    dummyPool.getMinEvictableIdleTimeMillis());
-            
assertEquals(BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN,
-                    dummyPool.getNumTestsPerEvictionRun());
-            
assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_ON_BORROW),
-                    Boolean.valueOf(dummyPool.getTestOnBorrow()));
-            
assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_ON_RETURN),
-                    Boolean.valueOf(dummyPool.getTestOnReturn()));
-            
assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_TEST_WHILE_IDLE),
-                    Boolean.valueOf(dummyPool.getTestWhileIdle()));
-            
assertEquals(BaseObjectPoolConfig.DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS,
-                    dummyPool.getTimeBetweenEvictionRunsMillis());
-            
assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_BLOCK_WHEN_EXHAUSTED),
-                    Boolean.valueOf(dummyPool.getBlockWhenExhausted()));
-            assertEquals(Boolean.valueOf(BaseObjectPoolConfig.DEFAULT_LIFO), 
Boolean.valueOf(dummyPool.getLifo()));
-        }
 
-        final GenericObjectPoolConfig config = new GenericObjectPoolConfig();
-        config.setLifo(lifo);
-        config.setMaxIdle(maxIdle);
-        config.setMinIdle(minIdle);
-        config.setMaxTotal(maxTotal);
-        config.setMaxWaitMillis(maxWait);
-        config.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
-        config.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
-        config.setTestOnBorrow(testOnBorrow);
-        config.setTestOnReturn(testOnReturn);
-        config.setTestWhileIdle(testWhileIdle);
-        config.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
-        config.setBlockWhenExhausted(blockWhenExhausted);
-        try (GenericObjectPool<Object> dummyPool = new 
GenericObjectPool<>(dummyFactory, config)) {
-            assertEquals(maxIdle, dummyPool.getMaxIdle());
-            assertEquals(maxWait, dummyPool.getMaxWaitMillis());
-            assertEquals(minIdle, dummyPool.getMinIdle());
-            assertEquals(maxTotal, dummyPool.getMaxTotal());
-            assertEquals(minEvictableIdleTimeMillis, 
dummyPool.getMinEvictableIdleTimeMillis());
-            assertEquals(numTestsPerEvictionRun, 
dummyPool.getNumTestsPerEvictionRun());
-            assertEquals(Boolean.valueOf(testOnBorrow), 
Boolean.valueOf(dummyPool.getTestOnBorrow()));
-            assertEquals(Boolean.valueOf(testOnReturn), 
Boolean.valueOf(dummyPool.getTestOnReturn()));
-            assertEquals(Boolean.valueOf(testWhileIdle), 
Boolean.valueOf(dummyPool.getTestWhileIdle()));
-            assertEquals(timeBetweenEvictionRunsMillis, 
dummyPool.getTimeBetweenEvictionRunsMillis());
-            assertEquals(Boolean.valueOf(blockWhenExhausted), 
Boolean.valueOf(dummyPool.getBlockWhenExhausted()));
-            assertEquals(Boolean.valueOf(lifo), 
Boolean.valueOf(dummyPool.getLifo()));
-        }
+    /**
+     * @param destroyLatency the destroyLatency to set
+     */
+    public void setDestroyLatency(final long destroyLatency) {
+        this.destroyLatency = destroyLatency;
     }
 
-    @Test(timeout=60000)
-    public void testWhenExhaustedFail() throws Exception {
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.setBlockWhenExhausted(false);
-        final String obj1 = genericObjectPool.borrowObject();
-        assertNotNull(obj1);
-        try {
-            genericObjectPool.borrowObject();
-            fail("Expected NoSuchElementException");
-        } catch(final NoSuchElementException e) {
-            // expected
-        }
-        genericObjectPool.returnObject(obj1);
-        assertEquals(1, genericObjectPool.getNumIdle());
-        genericObjectPool.close();
-    }
 
-    @Test(timeout=60000)
-    public void testWhenExhaustedBlock() throws Exception {
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.setBlockWhenExhausted(true);
-        genericObjectPool.setMaxWaitMillis(10L);
-        final String obj1 = genericObjectPool.borrowObject();
-        assertNotNull(obj1);
-        try {
-            genericObjectPool.borrowObject();
-            fail("Expected NoSuchElementException");
-        } catch(final NoSuchElementException e) {
-            // expected
-        }
-        genericObjectPool.returnObject(obj1);
-        genericObjectPool.close();
+    /**
+     * @param passivateLatency the passivateLatency to set
+     */
+    public void setPassivateLatency(final long passivateLatency) {
+        this.passivateLatency = passivateLatency;
     }
 
-    @Test(timeout=60000)
-    public void testWhenExhaustedBlockInterupt() throws Exception {
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.setBlockWhenExhausted(true);
-        genericObjectPool.setMaxWaitMillis(-1);
-        final String obj1 = genericObjectPool.borrowObject();
 
-        // Make sure on object was obtained
-        assertNotNull(obj1);
+    /**
+     * @param validateLatency the validateLatency to set
+     */
+    public void setValidateLatency(final long validateLatency) {
+        this.validateLatency = validateLatency;
+    }
 
-        // Create a separate thread to try and borrow another object
-        final WaitingTestThread wtt = new WaitingTestThread(genericObjectPool, 
200000);
-        wtt.start();
-        // Give wtt time to start
-        Thread.sleep(200);
-        wtt.interrupt();
 
-        // Give interrupt time to take effect
-        Thread.sleep(200);
+    @Override
+    public boolean validateObject(final PooledObject<AtomicInteger> instance) {
+        try {
+            Thread.sleep(validateLatency);
+        } catch (final InterruptedException ex) {}
+        return instance.getObject().intValue() == 1;
+    }
 
-        // Check thread was interrupted
-        assertTrue(wtt._thrown instanceof InterruptedException);
 
-        // Return object to the pool
-        genericObjectPool.returnObject(obj1);
+    @Override
+    public PooledObject<AtomicInteger> wrap(final AtomicInteger integer) {
+        return new DefaultPooledObject<>(integer);
+    }
+}
 
-        // Bug POOL-162 - check there is now an object in the pool
-        genericObjectPool.setMaxWaitMillis(10L);
-        String obj2 = null;
-        try {
-             obj2 = genericObjectPool.borrowObject();
-            assertNotNull(obj2);
-        } catch(final NoSuchElementException e) {
-            // Not expected
-            fail("NoSuchElementException not expected");
+    private class ConcurrentBorrowAndEvictThread extends Thread {
+        private final boolean borrow;
+        public String obj;
+
+        public ConcurrentBorrowAndEvictThread(final boolean borrow) {
+            this.borrow = borrow;
         }
-        genericObjectPool.returnObject(obj2);
-        genericObjectPool.close();
 
+        @Override
+        public void run() {
+            try {
+                if (borrow) {
+                    obj = genericObjectPool.borrowObject();
+                } else {
+                    genericObjectPool.evict();
+                }
+            } catch (final Exception e) { /* Ignore */}
+        }
     }
 
-    @Test(timeout=60000)
-    public void testEvictWhileEmpty() throws Exception {
-        genericObjectPool.evict();
-        genericObjectPool.evict();
-        genericObjectPool.close();
-    }
+    private static class CreateErrorFactory extends 
BasePooledObjectFactory<String> {
 
-    /**
-     * Tests addObject contention between ensureMinIdle triggered by
-     * the Evictor with minIdle &gt; 0 and borrowObject.
-     *
-     * @throws Exception May occur in some failure modes
-     */
-    @Test(timeout=60000)
-    public void testEvictAddObjects() throws Exception {
-        simpleFactory.setMakeLatency(300);
-        simpleFactory.setMaxTotal(2);
-        genericObjectPool.setMaxTotal(2);
-        genericObjectPool.setMinIdle(1);
-        genericObjectPool.borrowObject(); // numActive = 1, numIdle = 0
-        // Create a test thread that will run once and try a borrow after
-        // 150ms fixed delay
-        final TestThread<String> borrower = new 
TestThread<>(genericObjectPool, 1, 150, false);
-        final Thread borrowerThread = new Thread(borrower);
-        // Set evictor to run in 100 ms - will create idle instance
-        genericObjectPool.setTimeBetweenEvictionRunsMillis(100);
-        borrowerThread.start();  // Off to the races
-        borrowerThread.join();
-        assertTrue(!borrower.failed());
-    }
+        private final Semaphore semaphore = new Semaphore(0);
 
-    @Test(timeout=60000)
-    public void testEvictLIFO() throws Exception {
-        checkEvict(true);
-    }
+        @Override
+        public String create() throws Exception {
+            semaphore.acquire();
+            throw new UnknownError("wiggle");
+        }
 
-    @Test(timeout=60000)
-    public void testEvictFIFO() throws Exception {
-        checkEvict(false);
-    }
+        public boolean hasQueuedThreads() {
+            return semaphore.hasQueuedThreads();
+        }
 
-    private void checkEvict(final boolean lifo) throws Exception {
-        // yea this is hairy but it tests all the code paths in GOP.evict()
-        genericObjectPool.setSoftMinEvictableIdleTimeMillis(10);
-        genericObjectPool.setMinIdle(2);
-        genericObjectPool.setTestWhileIdle(true);
-        genericObjectPool.setLifo(lifo);
-        PoolUtils.prefill(genericObjectPool, 5);
-        genericObjectPool.evict();
-        simpleFactory.setEvenValid(false);
-        simpleFactory.setOddValid(false);
-        simpleFactory.setThrowExceptionOnActivate(true);
-        genericObjectPool.evict();
-        PoolUtils.prefill(genericObjectPool, 5);
-        simpleFactory.setThrowExceptionOnActivate(false);
-        simpleFactory.setThrowExceptionOnPassivate(true);
-        genericObjectPool.evict();
-        simpleFactory.setThrowExceptionOnPassivate(false);
-        simpleFactory.setEvenValid(true);
-        simpleFactory.setOddValid(true);
-        Thread.sleep(125);
-        genericObjectPool.evict();
-        assertEquals(2, genericObjectPool.getNumIdle());
-    }
+        public void release() {
+            semaphore.release();
+        }
 
-    /**
-     * Test to make sure evictor visits least recently used objects first,
-     * regardless of FIFO/LIFO.
-     *
-     * JIRA: POOL-86
-     *
-     * @throws Exception May occur in some failure modes
-     */
-    @Test(timeout=60000)
-    public void testEvictionOrder() throws Exception {
-        checkEvictionOrder(false);
-        tearDown();
-        setUp();
-        checkEvictionOrder(true);
+        @Override
+        public PooledObject<String> wrap(final String obj) {
+            return new DefaultPooledObject<>(obj);
+        }
     }
 
-    private void checkEvictionOrder(final boolean lifo) throws Exception {
-        checkEvictionOrderPart1(lifo);
-        tearDown();
-        setUp();
-        checkEvictionOrderPart2(lifo);
+    private static class CreateFailFactory extends 
BasePooledObjectFactory<String> {
+
+        private final Semaphore semaphore = new Semaphore(0);
+
+        @Override
+        public String create() throws Exception {
+            semaphore.acquire();
+            throw new UnsupportedCharsetException("wibble");
+        }
+
+        public boolean hasQueuedThreads() {
+            return semaphore.hasQueuedThreads();
+        }
+
+        public void release() {
+            semaphore.release();
+        }
+
+        @Override
+        public PooledObject<String> wrap(final String obj) {
+            return new DefaultPooledObject<>(obj);
+        }
     }
 
-    private void checkEvictionOrderPart1(final boolean lifo) throws Exception {
-        genericObjectPool.setNumTestsPerEvictionRun(2);
-        genericObjectPool.setMinEvictableIdleTimeMillis(100);
-        genericObjectPool.setLifo(lifo);
-        for (int i = 0; i < 5; i++) {
-            genericObjectPool.addObject();
-            Thread.sleep(100);
+    private static final class DummyFactory
+            extends BasePooledObjectFactory<Object> {
+        @Override
+        public Object create() throws Exception {
+            return null;
+        }
+        @Override
+        public PooledObject<Object> wrap(final Object value) {
+            return new DefaultPooledObject<>(value);
         }
-        // Order, oldest to youngest, is "0", "1", ...,"4"
-        genericObjectPool.evict(); // Should evict "0" and "1"
-        final Object obj = genericObjectPool.borrowObject();
-        assertTrue("oldest not evicted", !obj.equals("0"));
-        assertTrue("second oldest not evicted", !obj.equals("1"));
-        // 2 should be next out for FIFO, 4 for LIFO
-        assertEquals("Wrong instance returned", lifo ? "4" : "2" , obj);
     }
 
-    private void checkEvictionOrderPart2(final boolean lifo) throws Exception {
-        // Two eviction runs in sequence
-        genericObjectPool.setNumTestsPerEvictionRun(2);
-        genericObjectPool.setMinEvictableIdleTimeMillis(100);
-        genericObjectPool.setLifo(lifo);
-        for (int i = 0; i < 5; i++) {
-            genericObjectPool.addObject();
-            Thread.sleep(100);
+    private static class EvictionThread<T> extends Thread {
+
+        private final GenericObjectPool<T> pool;
+
+        public EvictionThread(final GenericObjectPool<T> pool) {
+            this.pool = pool;
+        }
+
+        @Override
+        public void run() {
+            try {
+                pool.evict();
+            } catch (final Exception e) {
+                // Ignore
+            }
         }
-        genericObjectPool.evict(); // Should evict "0" and "1"
-        genericObjectPool.evict(); // Should evict "2" and "3"
-        final Object obj = genericObjectPool.borrowObject();
-        assertEquals("Wrong instance remaining in pool", "4", obj);
     }
 
     /**
-     * Verifies that the evictor visits objects in expected order
-     * and frequency.
-     *
-     * @throws Exception May occur in some failure modes
+     * Factory that creates HashSets.  Note that this means
+     *  0) All instances are initially equal (not discernible by equals)
+     *  1) Instances are mutable and mutation can cause change in identity / 
hashcode.
      */
-    @Test
-    public void testEvictorVisiting() throws Exception {
-        checkEvictorVisiting(true);
-        checkEvictorVisiting(false);
+    private static final class HashSetFactory
+            extends BasePooledObjectFactory<HashSet<String>> {
+        @Override
+        public HashSet<String> create() throws Exception {
+            return new HashSet<>();
+        }
+        @Override
+        public PooledObject<HashSet<String>> wrap(final HashSet<String> value) 
{
+            return new DefaultPooledObject<>(value);
+        }
     }
 
-    private void checkEvictorVisiting(final boolean lifo) throws Exception {
-        VisitTracker<Object> obj;
-        VisitTrackerFactory<Object> trackerFactory = new 
VisitTrackerFactory<>();
-        try (GenericObjectPool<VisitTracker<Object>> trackerPool = new 
GenericObjectPool<>(trackerFactory)) {
-            trackerPool.setNumTestsPerEvictionRun(2);
-            trackerPool.setMinEvictableIdleTimeMillis(-1);
-            trackerPool.setTestWhileIdle(true);
-            trackerPool.setLifo(lifo);
-            trackerPool.setTestOnReturn(false);
-            trackerPool.setTestOnBorrow(false);
-            for (int i = 0; i < 8; i++) {
-                trackerPool.addObject();
-            }
-            trackerPool.evict(); // Visit oldest 2 - 0 and 1
-            obj = trackerPool.borrowObject();
-            trackerPool.returnObject(obj);
-            obj = trackerPool.borrowObject();
-            trackerPool.returnObject(obj);
-            // borrow, return, borrow, return
-            // FIFO will move 0 and 1 to end
-            // LIFO, 7 out, then in, then out, then in
-            trackerPool.evict(); // Should visit 2 and 3 in either case
-            for (int i = 0; i < 8; i++) {
-                final VisitTracker<Object> tracker = 
trackerPool.borrowObject();
-                if (tracker.getId() >= 4) {
-                    assertEquals("Unexpected instance visited " + 
tracker.getId(), 0, tracker.getValidateCount());
-                } else {
-                    assertEquals("Instance " + tracker.getId() + " visited 
wrong number of times.", 1,
-                            tracker.getValidateCount());
-                }
+    /**
+     * Attempts to invalidate an object, swallowing IllegalStateException.
+     */
+    static class InvalidateThread implements Runnable {
+        private final String obj;
+        private final ObjectPool<String> pool;
+        private boolean done = false;
+        public InvalidateThread(final ObjectPool<String> pool, final String 
obj) {
+            this.obj = obj;
+            this.pool = pool;
+        }
+        public boolean complete() {
+            return done;
+        }
+        @Override
+        public void run() {
+            try {
+                pool.invalidateObject(obj);
+            } catch (final IllegalStateException ex) {
+                // Ignore
+            } catch (final Exception ex) {
+                Assert.fail("Unexpected exception " + ex.toString());
+            } finally {
+                done = true;
             }
         }
+    }
 
-        trackerFactory = new VisitTrackerFactory<>();
-        try (GenericObjectPool<VisitTracker<Object>> trackerPool = new 
GenericObjectPool<>(trackerFactory)) {
-            trackerPool.setNumTestsPerEvictionRun(3);
-            trackerPool.setMinEvictableIdleTimeMillis(-1);
-            trackerPool.setTestWhileIdle(true);
-            trackerPool.setLifo(lifo);
-            trackerPool.setTestOnReturn(false);
-            trackerPool.setTestOnBorrow(false);
-            for (int i = 0; i < 8; i++) {
-                trackerPool.addObject();
-            }
-            trackerPool.evict(); // 0, 1, 2
-            trackerPool.evict(); // 3, 4, 5
-            obj = trackerPool.borrowObject();
-            trackerPool.returnObject(obj);
-            obj = trackerPool.borrowObject();
-            trackerPool.returnObject(obj);
-            obj = trackerPool.borrowObject();
-            trackerPool.returnObject(obj);
-            // borrow, return, borrow, return
-            // FIFO 3,4,5,6,7,0,1,2
-            // LIFO 7,6,5,4,3,2,1,0
-            // In either case, pointer should be at 6
-            trackerPool.evict();
-            // Should hit 6,7,0 - 0 for second time
-            for (int i = 0; i < 8; i++) {
-                final VisitTracker<Object> tracker = 
trackerPool.borrowObject();
-                if (tracker.getId() != 0) {
-                    assertEquals("Instance " + tracker.getId() + " visited 
wrong number of times.", 1,
-                            tracker.getValidateCount());
-                } else {
-                    assertEquals("Instance " + tracker.getId() + " visited 
wrong number of times.", 2,
-                            tracker.getValidateCount());
-                }
-            }
-        }
-
-        // Randomly generate a pools with random numTests
-        // and make sure evictor cycles through elements appropriately
-        final int[] smallPrimes = { 2, 3, 5, 7 };
-        final Random random = new Random();
-        random.setSeed(System.currentTimeMillis());
-        for (int i = 0; i < 4; i++) {
-            for (int j = 0; j < 5; j++) {
-                try (GenericObjectPool<VisitTracker<Object>> trackerPool = new 
GenericObjectPool<>(trackerFactory)) {
-                    trackerPool.setNumTestsPerEvictionRun(smallPrimes[i]);
-                    trackerPool.setMinEvictableIdleTimeMillis(-1);
-                    trackerPool.setTestWhileIdle(true);
-                    trackerPool.setLifo(lifo);
-                    trackerPool.setTestOnReturn(false);
-                    trackerPool.setTestOnBorrow(false);
-                    trackerPool.setMaxIdle(-1);
-                    final int instanceCount = 10 + random.nextInt(20);
-                    trackerPool.setMaxTotal(instanceCount);
-                    for (int k = 0; k < instanceCount; k++) {
-                        trackerPool.addObject();
-                    }
-
-                    // Execute a random number of evictor runs
-                    final int runs = 10 + random.nextInt(50);
-                    for (int k = 0; k < runs; k++) {
-                        trackerPool.evict();
-                    }
-
-                    // Number of times evictor should have cycled through the 
pool
-                    final int cycleCount = (runs * 
trackerPool.getNumTestsPerEvictionRun()) / instanceCount;
+    private static class InvalidFactory
+            extends BasePooledObjectFactory<Object> {
 
-                    // Look at elements and make sure they are visited 
cycleCount
-                    // or cycleCount + 1 times
-                    VisitTracker<Object> tracker = null;
-                    int visitCount = 0;
-                    for (int k = 0; k < instanceCount; k++) {
-                        tracker = trackerPool.borrowObject();
-                        assertTrue(trackerPool.getNumActive() <= 
trackerPool.getMaxTotal());
-                        visitCount = tracker.getValidateCount();
-                        assertTrue(visitCount >= cycleCount && visitCount <= 
cycleCount + 1);
-                    }
-                }
+        @Override
+        public Object create() throws Exception {
+            return new Object();
+        }
+        @Override
+        public boolean validateObject(final PooledObject<Object> obj) {
+            try {
+                Thread.sleep(1000);
+            } catch (final InterruptedException e) {
+                // Ignore
             }
+            return false;
         }
-    }
-
-    @Test(timeout=60000)
-    public void testExceptionOnPassivateDuringReturn() throws Exception {
-        final String obj = genericObjectPool.borrowObject();
-        simpleFactory.setThrowExceptionOnPassivate(true);
-        genericObjectPool.returnObject(obj);
-        assertEquals(0,genericObjectPool.getNumIdle());
-    }
 
-    @Test(timeout=60000)
-    public void testExceptionOnDestroyDuringBorrow() throws Exception {
-        simpleFactory.setThrowExceptionOnDestroy(true);
-        genericObjectPool.setTestOnBorrow(true);
-        genericObjectPool.borrowObject();
-        simpleFactory.setValid(false); // Make validation fail on next borrow 
attempt
-        try {
-            genericObjectPool.borrowObject();
-            fail("Expecting NoSuchElementException");
-        } catch (final NoSuchElementException ex) {
-            // expected
+        @Override
+        public PooledObject<Object> wrap(final Object value) {
+            return new DefaultPooledObject<>(value);
         }
-        assertEquals(1, genericObjectPool.getNumActive());
-        assertEquals(0, genericObjectPool.getNumIdle());
     }
 
-    @Test(timeout=60000)
-    public void testExceptionOnDestroyDuringReturn() throws Exception {
-        simpleFactory.setThrowExceptionOnDestroy(true);
-        genericObjectPool.setTestOnReturn(true);
-        final String obj1 = genericObjectPool.borrowObject();
-        genericObjectPool.borrowObject();
-        simpleFactory.setValid(false); // Make validation fail
-        genericObjectPool.returnObject(obj1);
-        assertEquals(1, genericObjectPool.getNumActive());
-        assertEquals(0, genericObjectPool.getNumIdle());
-    }
+    public static class SimpleFactory implements PooledObjectFactory<String> {
+        int makeCounter = 0;
 
-    @Test(timeout=60000)
-    public void testExceptionOnActivateDuringBorrow() throws Exception {
-        final String obj1 = genericObjectPool.borrowObject();
-        final String obj2 = genericObjectPool.borrowObject();
-        genericObjectPool.returnObject(obj1);
-        genericObjectPool.returnObject(obj2);
-        simpleFactory.setThrowExceptionOnActivate(true);
-        simpleFactory.setEvenValid(false);
-        // Activation will now throw every other time
-        // First attempt throws, but loop continues and second succeeds
-        final String obj = genericObjectPool.borrowObject();
-        assertEquals(1, genericObjectPool.getNumActive());
-        assertEquals(0, genericObjectPool.getNumIdle());
+        int activationCounter = 0;
 
-        genericObjectPool.returnObject(obj);
-        simpleFactory.setValid(false);
-        // Validation will now fail on activation when borrowObject returns
-        // an idle instance, and then when attempting to create a new instance
-        try {
-            genericObjectPool.borrowObject();
-            fail("Expecting NoSuchElementException");
-        } catch (final NoSuchElementException ex) {
-            // expected
-        }
-        assertEquals(0, genericObjectPool.getNumActive());
-        assertEquals(0, genericObjectPool.getNumIdle());
-    }
+        int validateCounter = 0;
 
-    @Test(timeout=60000)
-    public void testNegativeMaxTotal() throws Exception {
-        genericObjectPool.setMaxTotal(-1);
-        genericObjectPool.setBlockWhenExhausted(false);
-        final String obj = genericObjectPool.borrowObject();
-        assertEquals(getNthObject(0),obj);
-        genericObjectPool.returnObject(obj);
-    }
+        int activeCount = 0;
 
-    @Test(timeout=60000)
-    public void testMaxIdle() throws Exception {
-        genericObjectPool.setMaxTotal(100);
-        genericObjectPool.setMaxIdle(8);
-        final String[] active = new String[100];
-        for(int i=0;i<100;i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        assertEquals(100,genericObjectPool.getNumActive());
-        assertEquals(0,genericObjectPool.getNumIdle());
-        for(int i=0;i<100;i++) {
-            genericObjectPool.returnObject(active[i]);
-            assertEquals(99 - i,genericObjectPool.getNumActive());
-            assertEquals((i < 8 ? i+1 : 8),genericObjectPool.getNumIdle());
-        }
-    }
+        boolean evenValid = true;
 
-    @Test(timeout=60000)
-    public void testMaxIdleZero() throws Exception {
-        genericObjectPool.setMaxTotal(100);
-        genericObjectPool.setMaxIdle(0);
-        final String[] active = new String[100];
-        for(int i=0;i<100;i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        assertEquals(100,genericObjectPool.getNumActive());
-        assertEquals(0,genericObjectPool.getNumIdle());
-        for(int i=0;i<100;i++) {
-            genericObjectPool.returnObject(active[i]);
-            assertEquals(99 - i,genericObjectPool.getNumActive());
-            assertEquals(0, genericObjectPool.getNumIdle());
-        }
-    }
+        boolean oddValid = true;
 
-    @Test(timeout=60000)
-    public void testMaxTotal() throws Exception {
-        genericObjectPool.setMaxTotal(3);
-        genericObjectPool.setBlockWhenExhausted(false);
+        boolean exceptionOnPassivate = false;
 
-        genericObjectPool.borrowObject();
-        genericObjectPool.borrowObject();
-        genericObjectPool.borrowObject();
-        try {
-            genericObjectPool.borrowObject();
-            fail("Expected NoSuchElementException");
-        } catch(final NoSuchElementException e) {
-            // expected
-        }
-    }
+        boolean exceptionOnActivate = false;
 
-    @Test(timeout=60000)
-    public void testTimeoutNoLeak() throws Exception {
-        genericObjectPool.setMaxTotal(2);
-        genericObjectPool.setMaxWaitMillis(10);
-        genericObjectPool.setBlockWhenExhausted(true);
-        final String obj = genericObjectPool.borrowObject();
-        final String obj2 = genericObjectPool.borrowObject();
-        try {
-            genericObjectPool.borrowObject();
-            fail("Expecting NoSuchElementException");
-        } catch (final NoSuchElementException ex) {
-            // expected
-        }
-        genericObjectPool.returnObject(obj2);
-        genericObjectPool.returnObject(obj);
+        boolean exceptionOnDestroy = false;
 
-        genericObjectPool.borrowObject();
-        genericObjectPool.borrowObject();
-    }
+        boolean enableValidation = true;
 
-    @Test(timeout=60000)
-    public void testMaxTotalZero() throws Exception {
-        genericObjectPool.setMaxTotal(0);
-        genericObjectPool.setBlockWhenExhausted(false);
+        long destroyLatency = 0;
 
-        try {
-            genericObjectPool.borrowObject();
-            fail("Expected NoSuchElementException");
-        } catch(final NoSuchElementException e) {
-            // expected
-        }
-    }
+        long makeLatency = 0;
 
-    @Test(timeout=60000)
-    @SuppressWarnings("rawtypes")
-    public void testMaxTotalUnderLoad() {
-        // Config
-        final int numThreads = 199; // And main thread makes a round 200.
-        final int numIter = 20;
-        final int delay = 25;
-        final int maxTotal = 10;
+        long validateLatency = 0;
 
-        simpleFactory.setMaxTotal(maxTotal);
-        genericObjectPool.setMaxTotal(maxTotal);
-        genericObjectPool.setBlockWhenExhausted(true);
-        genericObjectPool.setTimeBetweenEvictionRunsMillis(-1);
+        int maxTotal = Integer.MAX_VALUE;
 
-        // Start threads to borrow objects
-        final TestThread[] threads = new TestThread[numThreads];
-        for(int i=0;i<numThreads;i++) {
-            // Factor of 2 on iterations so main thread does work whilst other
-            // threads are running. Factor of 2 on delay so average delay for
-            // other threads == actual delay for main thread
-            threads[i] = new TestThread<>(genericObjectPool, numIter * 2, 
delay * 2);
-            final Thread t = new Thread(threads[i]);
-            t.start();
-        }
-        // Give the threads a chance to start doing some work
-        try {
-            Thread.sleep(5000);
-        } catch(final InterruptedException e) {
-            // ignored
+        public SimpleFactory() {
+            this(true);
         }
 
-        for (int i = 0; i < numIter; i++) {
-            String obj = null;
-            try {
-                try {
-                    Thread.sleep(delay);
-                } catch(final InterruptedException e) {
-                    // ignored
-                }
-                obj = genericObjectPool.borrowObject();
-                // Under load, observed _numActive > _maxTotal
-                if (genericObjectPool.getNumActive() > 
genericObjectPool.getMaxTotal()) {
-                    throw new IllegalStateException("Too many active objects");
-                }
-                try {
-                    Thread.sleep(delay);
-                } catch(final InterruptedException e) {
-                    // ignored
-                }
-            } catch (final Exception e) {
-                // Shouldn't happen
-                e.printStackTrace();
-                fail("Exception on borrow");
-            } finally {
-                if (obj != null) {
-                    try {
-                        genericObjectPool.returnObject(obj);
-                    } catch (final Exception e) {
-                        // Ignore
-                    }
+        public SimpleFactory(final boolean valid) {
+            this(valid,valid);
+        }
+        public SimpleFactory(final boolean evalid, final boolean ovalid) {
+            evenValid = evalid;
+            oddValid = ovalid;
+        }
+        @Override
+        public void activateObject(final PooledObject<String> obj) throws 
Exception {
+            final boolean hurl;
+            final boolean evenTest;
+            final boolean oddTest;
+            final int counter;
+            synchronized(this) {
+                hurl = exceptionOnActivate;
+                evenTest = evenValid;
+                oddTest = oddValid;
+                counter = activationCounter++;
+            }
+            if (hurl) {
+                if (!(counter%2 == 0 ? evenTest : oddTest)) {
+                    throw new Exception();
                 }
             }
         }
-
-        for(int i=0;i<numThreads;i++) {
-            while(!(threads[i]).complete()) {
-                try {
-                    Thread.sleep(500L);
-                } catch(final InterruptedException e) {
-                    // ignored
+        @Override
+        public void destroyObject(final PooledObject<String> obj) throws 
Exception {
+            final long waitLatency;
+            final boolean hurl;
+            synchronized(this) {
+                waitLatency = destroyLatency;
+                hurl = exceptionOnDestroy;
+            }
+            if (waitLatency > 0) {
+                doWait(waitLatency);
+            }
+            synchronized(this) {
+                activeCount--;
+            }
+            if (hurl) {
+                throw new Exception();
+            }
+        }
+        private void doWait(final long latency) {
+            try {
+                Thread.sleep(latency);
+            } catch (final InterruptedException ex) {
+                // ignore
+            }
+        }
+        public synchronized int getMakeCounter() {
+            return makeCounter;
+        }
+        public synchronized boolean isThrowExceptionOnActivate() {
+            return exceptionOnActivate;
+        }
+        public synchronized boolean isValidationEnabled() {
+            return enableValidation;
+        }
+        @Override
+        public PooledObject<String> makeObject() {
+            final long waitLatency;
+            synchronized(this) {
+                activeCount++;
+                if (activeCount > maxTotal) {
+                    throw new IllegalStateException(
+                        "Too many active instances: " + activeCount);
                 }
+                waitLatency = makeLatency;
             }
-            if(threads[i].failed()) {
-                fail("Thread "+i+" failed: "+threads[i]._error.toString());
+            if (waitLatency > 0) {
+                doWait(waitLatency);
+            }
+            final int counter;
+            synchronized(this) {
+                counter = makeCounter++;
             }
+            return new DefaultPooledObject<>(String.valueOf(counter));
+        }
+        @Override
+        public void passivateObject(final PooledObject<String> obj) throws 
Exception {
+            final boolean hurl;
+            synchronized(this) {
+                hurl = exceptionOnPassivate;
+            }
+            if (hurl) {
+                throw new Exception();
+            }
+        }
+        public synchronized void setDestroyLatency(final long destroyLatency) {
+            this.destroyLatency = destroyLatency;
+        }
+        public synchronized void setEvenValid(final boolean valid) {
+            evenValid = valid;
+        }
+        public synchronized void setMakeLatency(final long makeLatency) {
+            this.makeLatency = makeLatency;
+        }
+        public synchronized void setMaxTotal(final int maxTotal) {
+            this.maxTotal = maxTotal;
+        }
+        public synchronized void setOddValid(final boolean valid) {
+            oddValid = valid;
         }
-    }
 
-    /**
-     * Showcasing a possible deadlock situation as reported in POOL-356
-     */
-    @Test(timeout=60000)
-    @SuppressWarnings("rawtypes")
-    public void testMaxIdleZeroUnderLoad() {
-        // Config
-        final int numThreads = 199; // And main thread makes a round 200.
-        final int numIter = 20;
-        final int delay = 25;
-        final int maxTotal = 10;
+        public synchronized void setThrowExceptionOnActivate(final boolean b) {
+            exceptionOnActivate = b;
+        }
 
-        simpleFactory.setMaxTotal(maxTotal);
-        genericObjectPool.setMaxTotal(maxTotal);
-        genericObjectPool.setBlockWhenExhausted(true);
-        genericObjectPool.setTimeBetweenEvictionRunsMillis(-1);
+        public synchronized void setThrowExceptionOnDestroy(final boolean b) {
+            exceptionOnDestroy = b;
+        }
 
-        // this is important to trigger POOL-356
-        genericObjectPool.setMaxIdle(0);
+        public synchronized void setThrowExceptionOnPassivate(final boolean 
bool) {
+            exceptionOnPassivate = bool;
+        }
 
-        // Start threads to borrow objects
-        final TestThread[] threads = new TestThread[numThreads];
-        for(int i=0;i<numThreads;i++) {
-            // Factor of 2 on iterations so main thread does work whilst other
-            // threads are running. Factor of 2 on delay so average delay for
-            // other threads == actual delay for main thread
-            threads[i] = new TestThread<>(genericObjectPool, numIter * 2, 
delay * 2);
-            final Thread t = new Thread(threads[i]);
-            t.start();
+        public synchronized void setValid(final boolean valid) {
+            setEvenValid(valid);
+            setOddValid(valid);
         }
-        // Give the threads a chance to start doing some work
-        try {
-            Thread.sleep(100);
-        } catch(final InterruptedException e) {
-            // ignored
+
+        public synchronized void setValidateLatency(final long 
validateLatency) {
+            this.validateLatency = validateLatency;
         }
 
-        for (int i = 0; i < numIter; i++) {
-            String obj = null;
-            try {
-                try {
-                    Thread.sleep(delay);
-                } catch(final InterruptedException e) {
-                    // ignored
-                }
-                obj = genericObjectPool.borrowObject();
-                // Under load, observed _numActive > _maxTotal
-                if (genericObjectPool.getNumActive() > 
genericObjectPool.getMaxTotal()) {
-                    throw new IllegalStateException("Too many active objects");
-                }
-                try {
-                    Thread.sleep(delay);
-                } catch(final InterruptedException e) {
-                    // ignored
-                }
-            } catch (final Exception e) {
-                // Shouldn't happen
-                e.printStackTrace();
-                fail("Exception on borrow");
-            } finally {
-                if (obj != null) {
-                    try {
-                        genericObjectPool.returnObject(obj);
-                    } catch (final Exception e) {
-                        // Ignore
-                    }
-                }
-            }
+        public synchronized void setValidationEnabled(final boolean b) {
+            enableValidation = b;
         }
 
-        for(int i=0;i<numThreads;i++) {
-            while(!(threads[i]).complete()) {
-                try {
-                    Thread.sleep(500L);
-                } catch(final InterruptedException e) {
-                    // ignored
-                }
+        @Override
+        public boolean validateObject(final PooledObject<String> obj) {
+            final boolean validate;
+            final boolean evenTest;
+            final boolean oddTest;
+            final long waitLatency;
+            final int counter;
+            synchronized(this) {
+                validate = enableValidation;
+                evenTest = evenValid;
+                oddTest = oddValid;
+                counter = validateCounter++;
+                waitLatency = validateLatency;
             }
-            if(threads[i].failed()) {
-                threads[i]._error.printStackTrace();
-                fail("Thread "+i+" failed: "+threads[i]._error.toString());
+            if (waitLatency > 0) {
+                doWait(waitLatency);
+            }
+            if (validate) {
+                return counter%2 == 0 ? evenTest : oddTest;
             }
+            return true;
         }
     }
 
-    /**
-     * This is the test case for POOL-263. It is disabled since it will always
-     * pass without artificial delay being injected into GOP.returnObject() and
-     * a way to this hasn't currently been found that doesn't involve
-     * polluting the GOP implementation. The artificial delay needs to be
-     * inserted just before the final call to isLifo() in the returnObject()
-     * method.
-     */
-    //@Test(timeout=60000)
-    public void testReturnObject() throws Exception {
+    public static class TestEvictionPolicy<T> implements EvictionPolicy<T> {
 
-        genericObjectPool.setMaxTotal(1);
-        genericObjectPool.setMaxIdle(-1);
-        final String active = genericObjectPool.borrowObject();
+        private final AtomicInteger callCount = new AtomicInteger(0);
 
-        assertEquals(1, genericObjectPool.getNumActive());
-        assertEquals(0, genericObjectPool.getNumIdle());
+        @Override
+        public boolean evict(final EvictionConfig config, final 
PooledObject<T> underTest,
+                final int idleCount) {
+            if (callCount.incrementAndGet() > 1500) {
+                return true;
+            }
+            return false;
+        }
+    }
 
-        final Thread t = new Thread() {
+    static class TestThread<T> implements Runnable {
 
-            @Override
-            public void run() {
-                genericObjectPool.close();
-            }
+        /** source of random delay times */
+        private final java.util.Random _random;
 
-        };
-        t.start();
+        /** pool to borrow from */
+        private final ObjectPool<T> _pool;
 
-        genericObjectPool.returnObject(active);
+        /** number of borrow attempts */
+        private final int _iter;
 
-        // Wait for the close() thread to complete
-        while (t.isAlive()) {
-            Thread.sleep(50);
-        }
+        /** delay before each borrow attempt */
+        private final int _startDelay;
 
-        assertEquals(0, genericObjectPool.getNumIdle());
-    }
+        /** time to hold each borrowed object before returning it */
+        private final int _holdTime;
+
+        /** whether or not start and hold time are randomly generated */
+        private final boolean _randomDelay;
 
+        /** object expected to be borrowed (fail otherwise) */
+        private final Object _expectedObject;
 
-    @Test(timeout=60000)
-    public void testSettersAndGetters() throws Exception {
-        {
-            // The object receives an Exception during its creation to prevent
-            // memory leaks. See BaseGenericObjectPool constructor for more 
details.
-            assertTrue(false == 
"".equals(genericObjectPool.getCreationStackTrace()));
-        }
-        {
-            assertEquals(0, genericObjectPool.getBorrowedCount());
-        }
-        {
-            assertEquals(0, genericObjectPool.getReturnedCount());
-        }
-        {
-            assertEquals(0, genericObjectPool.getCreatedCount());
-        }
-        {
-            assertEquals(0, genericObjectPool.getDestroyedCount());
-        }
-        {
-            assertEquals(0, genericObjectPool.getDestroyedByEvictorCount());
-        }
-        {
-            assertEquals(0, 
genericObjectPool.getDestroyedByBorrowValidationCount());
-        }
-        {
-            assertEquals(0, genericObjectPool.getMeanActiveTimeMillis());
-        }
-        {
-            assertEquals(0, genericObjectPool.getMeanIdleTimeMillis());
-        }
-        {
-            assertEquals(0, genericObjectPool.getMeanBorrowWaitTimeMillis());
-        }
-        {
-            assertEquals(0, genericObjectPool.getMaxBorrowWaitTimeMillis());
-        }
-        {
-            assertEquals(0, genericObjectPool.getNumIdle());
-        }
-        {
-            genericObjectPool.setMaxTotal(123);
-            assertEquals(123,genericObjectPool.getMaxTotal());
-        }
-        {
-            genericObjectPool.setMaxIdle(12);
-            assertEquals(12,genericObjectPool.getMaxIdle());
+        private volatile boolean _complete = false;
+        private volatile boolean _failed = false;
+        private volatile Throwable _error;
+
+        public TestThread(final ObjectPool<T> pool) {
+            this(pool, 100, 50, true, null);
         }
-        {
-            genericObjectPool.setMaxWaitMillis(1234L);
-            assertEquals(1234L,genericObjectPool.getMaxWaitMillis());
+
+        public TestThread(final ObjectPool<T> pool, final int iter) {
+            this(pool, iter, 50, true, null);
         }
-        {
-            genericObjectPool.setMinEvictableIdleTimeMillis(12345L);
-            
assertEquals(12345L,genericObjectPool.getMinEvictableIdleTimeMillis());
+
+        public TestThread(final ObjectPool<T> pool, final int iter, final int 
delay) {
+            this(pool, iter, delay, true, null);
         }
-        {
-            genericObjectPool.setNumTestsPerEvictionRun(11);
-            assertEquals(11,genericObjectPool.getNumTestsPerEvictionRun());
+
+        public TestThread(final ObjectPool<T> pool, final int iter, final int 
delay,
+                final boolean randomDelay) {
+            this(pool, iter, delay, randomDelay, null);
         }
-        {
-            genericObjectPool.setTestOnBorrow(true);
-            assertTrue(genericObjectPool.getTestOnBorrow());
-            genericObjectPool.setTestOnBorrow(false);
-            assertTrue(!genericObjectPool.getTestOnBorrow());
+
+        public TestThread(final ObjectPool<T> pool, final int iter, final int 
delay,
+                final boolean randomDelay, final Object obj) {
+            this(pool, iter, delay, delay, randomDelay, obj);
         }
-        {
-            genericObjectPool.setTestOnReturn(true);
-            assertTrue(genericObjectPool.getTestOnReturn());
-            genericObjectPool.setTestOnReturn(false);
-            assertTrue(!genericObjectPool.getTestOnReturn());
+
+        public TestThread(final ObjectPool<T> pool, final int iter, final int 
startDelay,
+            final int holdTime, final boolean randomDelay, final Object obj) {
+        _pool = pool;
+        _iter = iter;
+        _startDelay = startDelay;
+        _holdTime = holdTime;
+        _randomDelay = randomDelay;
+        _random = _randomDelay ? new Random() : null;
+        _expectedObject = obj;
+    }
+
+        public boolean complete() {
+            return _complete;
         }
-        {
-            genericObjectPool.setTestWhileIdle(true);
-            assertTrue(genericObjectPool.getTestWhileIdle());
-            genericObjectPool.setTestWhileIdle(false);
-            assertTrue(!genericObjectPool.getTestWhileIdle());
+
+        public boolean failed() {
+            return _failed;
         }
-        {
-            genericObjectPool.setTimeBetweenEvictionRunsMillis(11235L);
-            
assertEquals(11235L,genericObjectPool.getTimeBetweenEvictionRunsMillis());
+
+        @Override
+        public void run() {
+            for(int i=0;i<_iter;i++) {
+                final long startDelay =
+                    _randomDelay ? (long)_random.nextInt(_startDelay) : 
_startDelay;
+                final long holdTime =
+                    _randomDelay ? (long)_random.nextInt(_holdTime) : 
_holdTime;
+                try {
+                    Thread.sleep(startDelay);
+                } catch(final InterruptedException e) {
+                    // ignored
+                }
+                T obj = null;
+                try {
+                    obj = _pool.borrowObject();
+                } catch(final Exception e) {
+                    _error = e;
+                    _failed = true;
+                    _complete = true;
+                    break;
+                }
+
+                if (_expectedObject != null && !_expectedObject.equals(obj)) {
+                    _error = new Throwable("Expected: "+_expectedObject+ " 
found: "+obj);
+                    _failed = true;
+                    _complete = true;
+                    break;
+                }
+
+                try {
+                    Thread.sleep(holdTime);
+                } catch(final InterruptedException e) {
+                    // ignored
+                }
+                try {
+                    _pool.returnObject(obj);
+                } catch(final Exception e) {
+                    _error = e;
+                    _failed = true;
+                    _complete = true;
+                    break;
+                }
+            }
+            _complete = true;
         }
-        {
-            genericObjectPool.setSoftMinEvictableIdleTimeMillis(12135L);
-            
assertEquals(12135L,genericObjectPool.getSoftMinEvictableIdleTimeMillis());
+    }
+
+    /*
+     * Very simple test thread that just tries to borrow an object from
+     * the provided pool returns it after a wait
+     */
+    static class WaitingTestThread extends Thread {
+        private final GenericObjectPool<String> _pool;
+        private final long _pause;
+        private Throwable _thrown;
+
+        private long preborrow; // just before borrow
+        private long postborrow; //  borrow returned
+        private long postreturn; // after object was returned
+        private long ended;
+        private String objectId;
+
+        public WaitingTestThread(final GenericObjectPool<String> pool, final 
long pause) {
+            _pool = pool;
+            _pause = pause;
+            _thrown = null;
         }
-        {
-            genericObjectPool.setBlockWhenExhausted(true);
-            assertTrue(genericObjectPool.getBlockWhenExhausted());
-            genericObjectPool.setBlockWhenExhausted(false);
-            assertFalse(genericObjectPool.getBlockWhenExhausted());
+
+        @Override
+        public void run() {
+            try {
+                preborrow = System.currentTimeMillis();
+                final String obj = _pool.borrowObject();
+                objectId = obj;
+                postborrow = System.currentTimeMillis();
+                Thread.sleep(_pause);
+                _pool.returnObject(obj);
+                postreturn = System.currentTimeMillis();
+            } catch (final Throwable e) {
+                _thrown = e;
+            } finally{
+                ended = System.currentTimeMillis();
+            }
         }
     }
 
-    @Test(timeout=60000)
-    public void testDefaultConfiguration() throws Exception {
-        assertConfiguration(new GenericObjectPoolConfig(),genericObjectPool);
+    private static final boolean DISPLAY_THREAD_DETAILS=
+        
Boolean.valueOf(System.getProperty("TestGenericObjectPool.display.thread.details",
 "false")).booleanValue();
+    // To pass this to a Maven test, use:
+    // mvn test -DargLine="-DTestGenericObjectPool.display.thread.details=true"
+    // @see http://jira.codehaus.org/browse/SUREFIRE-121
+
+    protected GenericObjectPool<String> genericObjectPool = null;
+
+    private SimpleFactory simpleFactory = null;
+
+    private void assertConfiguration(final GenericObjectPoolConfig expected, 
final GenericObjectPool<?> actual) throws Exception {
+        
assertEquals("testOnCreate",Boolean.valueOf(expected.getTestOnCreate()),
+                Boolean.valueOf(actual.getTestOnCreate()));
+        
assertEquals("testOnBorrow",Boolean.valueOf(expected.getTestOnBorrow()),
+                Boolean.valueOf(actual.getTestOnBorrow()));
+        
assertEquals("testOnReturn",Boolean.valueOf(expected.getTestOnReturn()),
+                Boolean.valueOf(actual.getTestOnReturn()));
+        
assertEquals("testWhileIdle",Boolean.valueOf(expected.getTestWhileIdle()),
+                Boolean.valueOf(actual.getTestWhileIdle()));
+        assertEquals("whenExhaustedAction",
+                Boolean.valueOf(expected.getBlockWhenExhausted()),
+                Boolean.valueOf(actual.getBlockWhenExhausted()));
+        assertEquals("maxTotal",expected.getMaxTotal(),actual.getMaxTotal());
+        assertEquals("maxIdle",expected.getMaxIdle(),actual.getMaxIdle());
+        
assertEquals("maxWait",expected.getMaxWaitMillis(),actual.getMaxWaitMillis());
+        
assertEquals("minEvictableIdleTimeMillis",expected.getMinEvictableIdleTimeMillis(),actual.getMinEvictableIdleTimeMillis());
+        
assertEquals("numTestsPerEvictionRun",expected.getNumTestsPerEvictionRun(),actual.getNumTestsPerEvictionRun());
+        
assertEquals("evictorShutdownTimeoutMillis",expected.getEvictorShutdownTimeoutMillis(),actual.getEvictorShutdownTimeoutMillis());
+        
assertEquals("timeBetweenEvictionRunsMillis",expected.getTimeBetweenEvictionRunsMillis(),actual.getTimeBetweenEvictionRunsMillis());
     }
 
-    @Test(timeout=60000)
-    public void testSetConfig() throws Exception {
-        final GenericObjectPoolConfig expected = new GenericObjectPoolConfig();
-        assertConfiguration(expected,genericObjectPool);
-        expected.setMaxTotal(2);
-        expected.setMaxIdle(3);
-        expected.setMaxWaitMillis(5L);
-        expected.setMinEvictableIdleTimeMillis(7L);
-        expected.setNumTestsPerEvictionRun(9);
-        expected.setTestOnCreate(true);
-        expected.setTestOnBorrow(true);
-        expected.setTestOnReturn(true);
-        expected.setTestWhileIdle(true);
-        expected.setTimeBetweenEvictionRunsMillis(11L);
-        expected.setBlockWhenExhausted(false);
-        genericObjectPool.setConfig(expected);
-        assertConfiguration(expected,genericObjectPool);
+    private void checkEvict(final boolean lifo) throws Exception {
+        // yea this is hairy but it tests all the code paths in GOP.evict()
+        genericObjectPool.setSoftMinEvictableIdleTimeMillis(10);
+        genericObjectPool.setMinIdle(2);
+        genericObjectPool.setTestWhileIdle(true);
+        genericObjectPool.setLifo(lifo);
+        PoolUtils.prefill(genericObjectPool, 5);
+        genericObjectPool.evict();
+        simpleFactory.setEvenValid(false);
+        simpleFactory.setOddValid(false);
+        simpleFactory.setThrowExceptionOnActivate(true);
+        genericObjectPool.evict();
+        PoolUtils.prefill(genericObjectPool, 5);
+        simpleFactory.setThrowExceptionOnActivate(false);
+        simpleFactory.setThrowExceptionOnPassivate(true);
+        genericObjectPool.evict();
+        simpleFactory.setThrowExceptionOnPassivate(false);
+        simpleFactory.setEvenValid(true);
+        simpleFactory.setOddValid(true);
+        Thread.sleep(125);
+        genericObjectPool.evict();
+        assertEquals(2, genericObjectPool.getNumIdle());
     }
 
-    @Test(timeout=60000)
-    public void testStartAndStopEvictor() throws Exception {
-        // set up pool without evictor
-        genericObjectPool.setMaxIdle(6);
-        genericObjectPool.setMaxTotal(6);
-        genericObjectPool.setNumTestsPerEvictionRun(6);
-        genericObjectPool.setMinEvictableIdleTimeMillis(100L);
+    private void checkEvictionOrder(final boolean lifo) throws Exception {
+        checkEvictionOrderPart1(lifo);
+        tearDown();
+        setUp();
+        checkEvictionOrderPart2(lifo);
+    }
 
-        for(int j=0;j<2;j++) {
-            // populate the pool
-            {
-                final String[] active = new String[6];
-                for(int i=0;i<6;i++) {
-                    active[i] = genericObjectPool.borrowObject();
+    private void checkEvictionOrderPart1(final boolean lifo) throws Exception {
+        genericObjectPool.setNumTestsPerEvictionRun(2);
+        genericObjectPool.setMinEvictableIdleTimeMillis(100);
+        genericObjectPool.setLifo(lifo);
+        for (int i = 0; i < 5; i++) {
+            genericObjectPool.addObject();
+            Thread.sleep(100);
+        }
+        // Order, oldest to youngest, is "0", "1", ...,"4"
+        genericObjectPool.evict(); // Should evict "0" and "1"
+        final Object obj = genericObjectPool.borrowObject();
+        assertTrue("oldest not evicted", !obj.equals("0"));
+        assertTrue("second oldest not evicted", !obj.equals("1"));
+        // 2 should be next out for FIFO, 4 for LIFO
+        assertEquals("Wrong instance returned", lifo ? "4" : "2" , obj);
+    }
+
+    private void checkEvictionOrderPart2(final boolean lifo) throws Exception {
+        // Two eviction runs in sequence
+        genericObjectPool.setNumTestsPerEvictionRun(2);
+        genericObjectPool.setMinEvictableIdleTimeMillis(100);
+        genericObjectPool.setLifo(lifo);
+        for (int i = 0; i < 5; i++) {
+            genericObjectPool.addObject();
+            Thread.sleep(100);
+        }
+        genericObjectPool.evict(); // Should evict "0" and "1"
+        genericObjectPool.evict(); // Should evict "2" and "3"
+        final Object obj = genericObjectPool.borrowObject();
+        assertEquals("Wrong instance remaining in pool", "4", obj);
+    }
+
+    private void checkEvictorVisiting(final boolean lifo) throws Exception {
+        VisitTracker<Object> obj;
+        VisitTrackerFactory<Object> trackerFactory = new 
VisitTrackerFactory<>();
+        try (GenericObjectPool<VisitTracker<Object>> trackerPool = new 
GenericObjectPool<>(trackerFactory)) {
+            trackerPool.setNumTestsPerEvictionRun(2);
+            trackerPool.setMinEvictableIdleTimeMillis(-1);
+            trackerPool.setTestWhileIdle(true);
+            trackerPool.setLifo(lifo);
+            trackerPool.setTestOnReturn(false);
+            trackerPool.setTestOnBorrow(false);
+            for (int i = 0; i < 8; i++) {
+                trackerPool.addObject();
+            }
+            trackerPool.evict(); // Visit oldest 2 - 0 and 1
+            obj = trackerPool.borrowObject();
+            trackerPool.returnObject(obj);
+            obj = trackerPool.borrowObject();
+            trackerPool.returnObject(obj);
+            // borrow, return, borrow, return
+            // FIFO will move 0 and 1 to end
+            // LIFO, 7 out, then in, then out, then in
+            trackerPool.evict(); // Should visit 2 and 3 in either case
+            for (int i = 0; i < 8; i++) {
+                final VisitTracker<Object> tracker = 
trackerPool.borrowObject();
+                if (tracker.getId() >= 4) {
+                    assertEquals("Unexpected instance visited " + 
tracker.getId(), 0, tracker.getValidateCount());
+                } else {
+                    assertEquals("Instance " + tracker.getId() + " visited 
wrong number of times.", 1,
+                            tracker.getValidateCount());
                 }
-                for(int i=0;i<6;i++) {
-                    genericObjectPool.returnObject(active[i]);
+            }
+        }
+
+        trackerFactory = new VisitTrackerFactory<>();
+        try (GenericObjectPool<VisitTracker<Object>> trackerPool = new 
GenericObjectPool<>(trackerFactory)) {
+            trackerPool.setNumTestsPerEvictionRun(3);
+            trackerPool.setMinEvictableIdleTimeMillis(-1);
+            trackerPool.setTestWhileIdle(true);
+            trackerPool.setLifo(lifo);
+            trackerPool.setTestOnReturn(false);
+            trackerPool.setTestOnBorrow(false);
+            for (int i = 0; i < 8; i++) {
+                trackerPool.addObject();
+            }
+            trackerPool.evict(); // 0, 1, 2
+            trackerPool.evict(); // 3, 4, 5
+            obj = trackerPool.borrowObject();
+            trackerPool.returnObject(obj);
+            obj = trackerPool.borrowObject();
+            trackerPool.returnObject(obj);
+            obj = trackerPool.borrowObject();
+            trackerPool.returnObject(obj);
+            // borrow, return, borrow, return
+            // FIFO 3,4,5,6,7,0,1,2
+            // LIFO 7,6,5,4,3,2,1,0
+            // In either case, pointer should be at 6
+            trackerPool.evict();
+            // Should hit 6,7,0 - 0 for second time
+            for (int i = 0; i < 8; i++) {
+                final VisitTracker<Object> tracker = 
trackerPool.borrowObject();
+                if (tracker.getId() != 0) {
+                    assertEquals("Instance " + tracker.getId() + " visited 
wrong number of times.", 1,
+                            tracker.getValidateCount());
+                } else {
+                    assertEquals("Instance " + tracker.getId() + " visited 
wrong number of times.", 2,
+                            tracker.getValidateCount());
                 }
             }
+        }
 
-            // note that it stays populated
-            assertEquals("Should have 6 
idle",6,genericObjectPool.getNumIdle());
-
-            // start the evictor
-            genericObjectPool.setTimeBetweenEvictionRunsMillis(50L);
+        // Randomly generate a pools with random numTests
+        // and make sure evictor cycles through elements appropriately
+        final int[] smallPrimes = { 2, 3, 5, 7 };
+        final Random random = new Random();
+        random.setSeed(System.currentTimeMillis());
+        for (int i = 0; i < 4; i++) {
+            for (int j = 0; j < 5; j++) {
+                try (GenericObjectPool<VisitTracker<Object>> trackerPool = new 
GenericObjectPool<>(trackerFactory)) {
+                    trackerPool.setNumTestsPerEvictionRun(smallPrimes[i]);
+                    trackerPool.setMinEvictableIdleTimeMillis(-1);
+                    trackerPool.setTestWhileIdle(true);
+                    trackerPool.setLifo(lifo);
+                    trackerPool.setTestOnReturn(false);
+                    trackerPool.setTestOnBorrow(false);
+                    trackerPool.setMaxIdle(-1);
+                    final int instanceCount = 10 + random.nextInt(20);
+                    trackerPool.setMaxTotal(instanceCount);
+                    for (int k = 0; k < instanceCount; k++) {
+                        trackerPool.addObject();
+                    }
 
-            // wait a second (well, .2 seconds)
-            try { Thread.sleep(200L); } catch(final InterruptedException e) { }
+                    // Execute a random number of evictor runs
+                    final int runs = 10 + random.nextInt(50);
+                    for (int k = 0; k < runs; k++) {
+                        trackerPool.evict();
+                    }
 
-            // assert that the evictor has cleared out the pool
-            assertEquals("Should have 0 
idle",0,genericObjectPool.getNumIdle());
+                    // Number of times evictor should have cycled through the 
pool
+                    final int cycleCount = (runs * 
trackerPool.getNumTestsPerEvictionRun()) / instanceCount;
 
-            // stop the evictor
-            genericObjectPool.startEvictor(0L);
+                    // Look at elements and make sure they are visited 
cycleCount
+                    // or cycleCount + 1 times
+                    VisitTracker<Object> tracker = null;
+                    int visitCount = 0;
+                    for (int k = 0; k < instanceCount; k++) {
+                        tracker = trackerPool.borrowObject();
+                        assertTrue(trackerPool.getNumActive() <= 
trackerPool.getMaxTotal());
+                        visitCount = tracker.getValidateCount();
+                        assertTrue(visitCount >= cycleCount && visitCount <= 
cycleCount + 1);
+                    }
+                }
+            }
         }
     }
 
-    @Test(timeout=60000)
-    public void testEvictionWithNegativeNumTests() throws Exception {
-        // when numTestsPerEvictionRun is negative, it represents a fraction 
of the idle objects to test
-        genericObjectPool.setMaxIdle(6);
-        genericObjectPool.setMaxTotal(6);
-        genericObjectPool.setNumTestsPerEvictionRun(-2);
-        genericObjectPool.setMinEvictableIdleTimeMillis(50L);
-        genericObjectPool.setTimeBetweenEvictionRunsMillis(100L);
-
-        final String[] active = new String[6];
-        for(int i=0;i<6;i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        for(int i=0;i<6;i++) {
-            genericObjectPool.returnObject(active[i]);
-        }
+    private BasePooledObjectFactory<String> createDefaultPooledObjectFactory() 
{
+        return new BasePooledObjectFactory<String>() {
+            @Override
+            public String create() {
+                // fake
+                return null;
+            }
 
-        try { Thread.sleep(100L); } catch(final InterruptedException e) { }
-        assertTrue("Should at most 6 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() <= 6);
-        try { Thread.sleep(100L); } catch(final InterruptedException e) { }
-        assertTrue("Should at most 3 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() <= 3);
-        try { Thread.sleep(100L); } catch(final InterruptedException e) { }
-        assertTrue("Should be at most 2 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() <= 2);
-        try { Thread.sleep(100L); } catch(final InterruptedException e) { }
-        assertEquals("Should be zero idle, found " + 
genericObjectPool.getNumIdle(),0,genericObjectPool.getNumIdle());
+            @Override
+            public PooledObject<String> wrap(final String obj) {
+                // fake
+                return new DefaultPooledObject<>(obj);
+            }
+        };
     }
 
-    @Test(timeout=60000)
-    public void testEviction() throws Exception {
-        genericObjectPool.setMaxIdle(500);
-        genericObjectPool.setMaxTotal(500);
-        genericObjectPool.setNumTestsPerEvictionRun(100);
-        genericObjectPool.setMinEvictableIdleTimeMillis(250L);
-        genericObjectPool.setTimeBetweenEvictionRunsMillis(500L);
-        genericObjectPool.setTestWhileIdle(true);
-
-        final String[] active = new String[500];
-        for(int i=0;i<500;i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        for(int i=0;i<500;i++) {
-            genericObjectPool.returnObject(active[i]);
-        }
+    private BasePooledObjectFactory<String> createNullPooledObjectFactory() {
+        return new BasePooledObjectFactory<String>() {
+            @Override
+            public String create() {
+                // fake
+                return null;
+            }
 
-        try { Thread.sleep(1000L); } catch(final InterruptedException e) { }
-        assertTrue("Should be less than 500 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() < 500);
-        try { Thread.sleep(600L); } catch(final InterruptedException e) { }
-        assertTrue("Should be less than 400 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() < 400);
-        try { Thread.sleep(600L); } catch(final InterruptedException e) { }
-        assertTrue("Should be less than 300 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() < 300);
-        try { Thread.sleep(600L); } catch(final InterruptedException e) { }
-        assertTrue("Should be less than 200 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() < 200);
-        try { Thread.sleep(600L); } catch(final InterruptedException e) { }
-        assertTrue("Should be less than 100 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() < 100);
-        try { Thread.sleep(600L); } catch(final InterruptedException e) { }
-        assertEquals("Should be zero idle, found " + 
genericObjectPool.getNumIdle(),0,genericObjectPool.getNumIdle());
+            @Override
+            public PooledObject<String> wrap(final String obj) {
+                // fake
+                return null;
+            }
+        };
+    }
 
-        for(int i=0;i<500;i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        for(int i=0;i<500;i++) {
-            genericObjectPool.returnObject(active[i]);
-        }
+    private BasePooledObjectFactory<String> createSlowObjectFactory(final long 
elapsedTimeMillis) {
+        return new BasePooledObjectFactory<String>() {
+            @Override
+            public String create() throws Exception {
+                Thread.sleep(elapsedTimeMillis);
+                return "created";
+            }
 
-        try { Thread.sleep(1000L); } catch(final InterruptedException e) { }
-        assertTrue("Should be less than 500 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() < 500);
-        try { Thread.sleep(600L); } catch(final InterruptedException e) { }
-        assertTrue("Should be less than 400 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() < 400);
-        try { Thread.sleep(600L); } catch(final InterruptedException e) { }
-        assertTrue("Should be less than 300 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() < 300);
-        try { Thread.sleep(600L); } catch(final InterruptedException e) { }
-        assertTrue("Should be less than 200 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() < 200);
-        try { Thread.sleep(600L); } catch(final InterruptedException e) { }
-        assertTrue("Should be less than 100 idle, found " + 
genericObjectPool.getNumIdle(),genericObjectPool.getNumIdle() < 100);
-        try { Thread.sleep(600L); } catch(final InterruptedException e) { }
-        assertEquals("Should be zero idle, found " + 
genericObjectPool.getNumIdle(),0,genericObjectPool.getNumIdle());
+            @Override
+            public PooledObject<String> wrap(final String obj) {
+                // fake
+                return new DefaultPooledObject<>(obj);
+            }
+        };
     }
 
-    public static class TestEvictionPolicy<T> implements EvictionPolicy<T> {
-
-        private final AtomicInteger callCount = new AtomicInteger(0);
+    @Override
+    protected Object getNthObject(final int n) {
+        return String.valueOf(n);
+    }
 
-        @Override
-        public boolean evict(final EvictionConfig config, final 
PooledObject<T> underTest,
-                final int idleCount) {
-            if (callCount.incrementAndGet() > 1500) {
-                return true;
-            }
-            return false;
-        }
+    @Override
+    protected boolean isFifo() {
+        return false;
     }
 
-    @Test(timeout=60000)
-    public void testEvictionPolicy() throws Exception {
-        genericObjectPool.setMaxIdle(500);
-        genericObjectPool.setMaxTotal(500);
-        genericObjectPool.setNumTestsPerEvictionRun(500);
-        genericObjectPool.setMinEvictableIdleTimeMillis(250L);
-        genericObjectPool.setTimeBetweenEvictionRunsMillis(500L);
-        genericObjectPool.setTestWhileIdle(true);
+    @Override
+    protected boolean isLifo() {
+        return true;
+    }
 
-        // ClassNotFoundException
-        try {
-            
genericObjectPool.setEvictionPolicyClassName(Long.toString(System.currentTimeMillis()));
-            fail("setEvictionPolicyClassName must throw an error if the class 
name is invalid.");
-        } catch (final IllegalArgumentException e) {
-            // expected
-        }
+    @Override
+    protected ObjectPool<String> makeEmptyPool(final int mincap) {
+       final GenericObjectPool<String> mtPool =
+               new GenericObjectPool<>(new SimpleFactory());
+       mtPool.setMaxTotal(mincap);
+       mtPool.setMaxIdle(mincap);
+       return mtPool;
+    }
 
-        // InstantiationException
-        try {
-            
genericObjectPool.setEvictionPolicyClassName(java.io.Serializable.class.getName());
-            fail("setEvictionPolicyClassName must throw an error if the class 
name is invalid.");
-        } catch (final IllegalArgumentException e) {
-            // expected
-        }
+    @Override
+    protected ObjectPool<Object> makeEmptyPool(
+            final PooledObjectFactory<Object> fac) {
+        return new GenericObjectPool<>(fac);
+    }
 
-        // IllegalAccessException
-        try {
-            
genericObjectPool.setEvictionPolicyClassName(java.util.Collections.class.getName());
-            fail("setEvictionPolicyClassName must throw an error if the class 
name is invalid.");
-        } catch (final IllegalArgumentException e) {
-            // expected
+    /**
+     * Kicks off <numThreads> test threads, each of which will go through
+     * <iterations> borrow-return cycles with random delay times <= delay
+     * in between.
+     */
+    @SuppressWarnings({
+        "rawtypes", "unchecked"
+    })
+    private void runTestThreads(final int numThreads, final int iterations, 
final int delay, final GenericObjectPool testPool) {
+        final TestThread[] threads = new TestThread[numThreads];
+        for(int i=0;i<numThreads;i++) {
+            threads[i] = new TestThread<String>(testPool,iterations,delay);
+            final Thread t = new Thread(threads[i]);
+            t.start();
         }
-
-        try {
-            
genericObjectPool.setEvictionPolicyClassName(java.lang.String.class.getName());
-            fail("setEvictionPolicyClassName must throw an error if a class 
that does not "
-                    + "implement EvictionPolicy is specified.");
-        } catch (final IllegalArgumentException e) {
-            // expected
+        for(int i=0;i<numThreads;i++) {
+            while(!(threads[i]).complete()) {
+                try {
+                    Thread.sleep(500L);
+                } catch(final InterruptedException e) {
+                    // ignored
+                }
+            }
+            if(threads[i].failed()) {
+                fail("Thread "+i+" failed: "+threads[i]._error.toString());
+            }
         }
+    }
 
-        genericObjectPool.setEvictionPolicy(new TestEvictionPolicy<String>());
-        assertEquals(TestEvictionPolicy.class.getName(), 
genericObjectPool.getEvictionPolicyClassName());
+    @Before
+    public void setUp() throws Exception {
+        simpleFactory = new SimpleFactory();
+        genericObjectPool = new GenericObjectPool<>(simpleFactory);
+    }
 
-        
genericObjectPool.setEvictionPolicyClassName(TestEvictionPolicy.class.getName());
-        assertEquals(TestEvictionPolicy.class.getName(), 
genericObjectPool.getEvictionPolicyClassName());
+    @After
+    public void tearDown() throws Exception {
+        final String poolName = genericObjectPool.getJmxName().toString();
+        genericObjectPool.clear();
+        genericObjectPool.close();
+        genericObjectPool = null;
+        simpleFactory = null;
 
-        final String[] active = new String[500];
-        for(int i=0;i<500;i++) {
-            active[i] = genericObjectPool.borrowObject();
-        }
-        for(int i=0;i<500;i++) {
-            genericObjectPool.returnObject(active[i]);
+        final MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
+        final Set<ObjectName> result = mbs.queryNames(new ObjectName(
+                "org.apache.commoms.pool2:type=GenericObjectPool,*"), null);
+        // There should be no registered pools at this point
+        final int registeredPoolCount = result.size();
+        final StringBuilder msg = new StringBuilder("Current pool is: ");
+        msg.append(poolName);
+        msg.append("  Still open pools are: ");
+        for (final ObjectName name : result) {
+            // Clean these up ready for the next test
+            msg.append(name.toString());
+            msg.append(" created via\n");
+            msg.append(mbs.getAttribute(name, "CreationStackTrace"));
+            msg.append('\n');
+            mbs.unregisterMBean(name);
         }
+        Assert.assertEquals(msg.toString(), 0, registeredPoolCount);
+    }
 
-        // Eviction policy ignores first 1500 attempts to evict and then always
-        // evicts. After 1s, there should have been two runs of 500 tests so no
-        // evictions
-        try { Thread.sleep(1000L); } catch(final InterruptedException e) { }
-        assertEquals("Should be 500 idle", 500, 
genericObjectPool.getNumIdle());
-        // A further 1s wasn't enough so allow 2s for the evictor to clear out
-        // all of the idle objects.
-        try { Thread.sleep(2000L); } catch(final InterruptedException e) { }
-        assertEquals("Should be 0 idle", 0, genericObjectPool.getNumIdle());
+    @Test(timeout=60000)
+    public void testAddObject() throws Exception {
+        assertEquals("should be zero idle", 0, genericObjectPool.getNumIdle());
+        genericObjectPool.addObject();
+        assertEquals("should be one idle", 1, genericObjectPool.getNumIdle());
+        assertEquals("should be zero active", 0, 
genericObjectPool.getNumActive());
+        final String obj = genericObjectPool.borrowObject();
+        assertEquals("should be zero idle", 0, genericObjectPool.getNumIdle());
+        assertEquals("should be one active", 1, 
genericObjectPool.getNumActive());
+        genericObjectPool.returnObject(obj);
+        assertEquals("should be one idle", 1, genericObjectPool.getNumIdle());
+        assertEquals("should be zero active", 0, 
genericObjectPool.getNumActive());
     }
 
 
+    /*
+     * Note: This test relies on timing for correct execution. There *should* 
be
+     * enough margin for this to work correctly on most (all?) systems but be
+     * aware of this if you see a failure of this test.
+     */
+    @SuppressWarnings({
+        "rawtypes", "unchecked"
+    })
     @Test(timeout=60000)
-    public void testEvictionSoftMinIdle() throws Exception {
-        class TimeTest extends BasePooledObjectFactory<TimeTest> {
-            private final long createTime;
+    public void testBorrowObjectFairness() throws Exception {
 
-            public TimeTest() {
-                createTime = System.currentTimeMillis();
-            }
+        final int numThreads = 40;
+        final int maxTotal = 40;
 
-            @Override
-            public TimeTest create() throws Exception {
-                return new TimeTest();
-            }
+        final GenericObjectPoolConfig config = new GenericObjectPoolConfig();
+        config.setMaxTotal(maxTotal);
+        config.setMaxIdle(maxTotal);
+        config.setFairness(true);
+        config.setLifo(false);
 
-            @Override
-            public PooledObject<TimeTest> wrap(final TimeTest value) {
-                return new DefaultPooledObject<>(value);
-            }
+        genericObjectPool = new GenericObjectPool(simpleFactory, config);
 
-            public long getCreateTime() {
-                return createTime;
-            }
+        // Exhaust the pool
+        final String[] objects = new String[maxTotal];
+        for (int i = 0; i < maxTotal; i++) {
+            objects[i] = genericObjectPool.borrowObject();
         }
 
-        try (final GenericObjectPool<TimeTest> timePool = new 
GenericObjectPool<>(new TimeTest())) {
+        // Start and park threads waiting to borrow objects
+        final TestThread[] threads = new TestThread[numThreads];
+        for(int i=0;i<numThreads;i++) {
+            threads[i] = new TestThread(genericObjectPool, 1, 0, 2000, false, 
String.valueOf(i % maxTotal));
+            final Thread t = new Thread(threads[i]);
+            t.start();
+            // Short delay to ensure threads start in correct order
+            try {
+                Thread.sleep(10);
+            } catch (final InterruptedException e) {
+                fail(e.toString());
+            }
+        }
 
-            timePool.setMaxIdle(5);
-            timePool.setMaxTotal(5);
-            timePool.setNumTestsPerEvictionRun(5);
-            timePool.setMinEvictableIdleTimeMillis(3000L);
-            timePool.setSoftMinEvictableIdleTimeMillis(1000L);
-            timePool.setMinIdle(2);
+        // Return objects, other threads should get served in order
+        for (int i = 0; i < maxTotal; i++) {
+            genericObjectPool.returnObject(objects[i]);
+        }
 
-            final TimeTest[] active = new TimeTest[5];
-            final Long[] creationTime = new Long[5];
-            for (int i = 0; i < 5; i++) {
-                active[i] = timePool.borrowObject();
-                creationTime[i] = Long.valueOf((active[i]).getCreateTime());
+        // Wait for threads to finish
+        for(int i=0;i<numThreads;i++) {
+            while(!(threads[i]).complete()) {
+                try {
+                    Thread.sleep(500L);
+                } catch(final InterruptedException e) {
+                    // ignored
+                }
             }
-
-            for (int i = 0; i < 5; i++) {
-                timePool.returnObject(active[i]);
+            if(threads[i].failed()) {
+                fail("Thread "+i+" failed: "+threads[i]._error.toString());
             }
-
-            // Soft evict all but minIdle(2)
-            Thread.sleep(1500L);
-            timePool.evict();
-            assertEquals("Idle count different than expected.", 2, 
timePool.getNumIdle());
-
-            // Hard evict the rest.
-            Thread.sleep(2000L);
-            timePool.evict();
-            assertEquals("Idle count different than expected.", 0, 
timePool.getNumIdle());
         }
     }
 
+    /**
+     * On first borrow, first object fails validation, second object is OK.
+     * Subsequent borrows are OK. This was POOL-152.
+     */
     @Test(timeout=60000)
-    public void testEvictionInvalid() throws Exception {
+    public void testBrokenFactoryShouldNotBlockPool() {
+        final int maxTotal = 1;
 
-        try (final GenericObjectPool<Object> invalidFactoryPool = new 
GenericObjectPool<>(new InvalidFactory())) {
+        simpleFactory.setMaxTotal(maxTotal);
+        genericObjectPool.setMaxTotal(maxTotal);
+        genericObjectPool.setBlockWhenExhausted(true);
+        genericObjectPool.setTestOnBorrow(true);
 
-            invalidFactoryPool.setMaxIdle(1);
-            invalidFactoryPool.setMaxTotal(1);
-            invalidFactoryPool.setTestOnBorrow(false);
-            invalidFactoryPool.setTestOnReturn(false);
-            invalidFactoryPool.setTestWhileIdle(true);
-            invalidFactoryPool.setMinEvictableIdleTimeMillis(100000);
-            invalidFactoryPool.setNumTestsPerEvictionRun(1);
+        // First borrow object will need to create a new object which will fail
+        // validation.
+        String obj = null;
+        Exception ex = null;
+        simpleFactory.setValid(false);
+        try {
+            obj = genericObjectPool.borrowObject();
+        } catch (final Exception e) {
+            ex = e;
+        }
+        // Failure expected
+        assertNotNull(ex);
+        assertTrue(ex instanceof NoSuchElementException);
+        assertNull(obj);
 
-            final Object p = invalidFactoryPool.borrowObject();
-            invalidFactoryPool.returnObject(p);
+        // Configure factory to create valid objects so subsequent borrows work
+        simpleFactory.setValid(true);
 
-            // Run eviction in a separate thread
-            final Thread t = new EvictionThread<>(invalidFactoryPool);
-            t.start();
+        // Subsequent borrows should be OK
+        try {
+            obj = genericObjectPool.borrowObject();
+        } catch (final Exception e1) {
+            fail();
+        }
+        assertNotNull(obj);
+        try {
+            genericObjectPool.returnObject(obj);
+        

<TRUNCATED>

Reply via email to