blob: d46bc40dae2f2b88149cdb5c7eb9224cee785e61 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.commons.pool2;
import static org.junit.Assert.*;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.SoftReferenceObjectPool;
import org.junit.Test;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* Abstract test case for {@link ObjectPool} implementations.
*/
public abstract class TestObjectPool {
/**
* Create an {@code ObjectPool} with the specified factory.
* The pool should be in a default configuration and conform to the expected
* behaviors described in {@link ObjectPool}.
* Generally speaking there should be no limits on the various object counts.
*
* @param factory The factory to be used by the object pool
*
* @return the newly created empty pool
*
* @throws UnsupportedOperationException if the pool being tested does not
* follow pool contracts.
*/
protected abstract ObjectPool<Object> makeEmptyPool(PooledObjectFactory<Object> factory) throws UnsupportedOperationException;
@Test
public void testClosedPoolBehavior() throws Exception {
final ObjectPool<Object> pool;
try {
pool = makeEmptyPool(new MethodCallPoolableObjectFactory());
} catch (final UnsupportedOperationException uoe) {
return; // test not supported
}
final Object o1 = pool.borrowObject();
final Object o2 = pool.borrowObject();
pool.close();
try {
pool.addObject();
fail("A closed pool must throw an IllegalStateException when addObject is called.");
} catch (final IllegalStateException ise) {
// expected
}
try {
pool.borrowObject();
fail("A closed pool must throw an IllegalStateException when borrowObject is called.");
} catch (final IllegalStateException ise) {
// expected
}
// The following should not throw exceptions just because the pool is closed.
if (pool.getNumIdle() >= 0) {
assertEquals("A closed pool shouldn't have any idle objects.", 0, pool.getNumIdle());
}
if (pool.getNumActive() >= 0) {
assertEquals("A closed pool should still keep count of active objects.", 2, pool.getNumActive());
}
pool.returnObject(o1);
if (pool.getNumIdle() >= 0) {
assertEquals("returnObject should not add items back into the idle object pool for a closed pool.", 0, pool.getNumIdle());
}
if (pool.getNumActive() >= 0) {
assertEquals("A closed pool should still keep count of active objects.", 1, pool.getNumActive());
}
pool.invalidateObject(o2);
if (pool.getNumIdle() >= 0) {
assertEquals("invalidateObject must not add items back into the idle object pool.", 0, pool.getNumIdle());
}
if (pool.getNumActive() >= 0) {
assertEquals("A closed pool should still keep count of active objects.", 0, pool.getNumActive());
}
pool.clear();
pool.close();
}
// Deliberate choice to create a new object in case future unit tests check
// for a specific object.
private final Integer ZERO = new Integer(0);
private final Integer ONE = new Integer(1);
@Test
public void testPOFAddObjectUsage() throws Exception {
final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
final ObjectPool<Object> pool;
try {
pool = makeEmptyPool(factory);
} catch(final UnsupportedOperationException uoe) {
return; // test not supported
}
final List<MethodCall> expectedMethods = new ArrayList<>();
assertEquals(0, pool.getNumActive());
assertEquals(0, pool.getNumIdle());
// addObject should make a new object, passivate it and put it in the pool
pool.addObject();
assertEquals(0, pool.getNumActive());
assertEquals(1, pool.getNumIdle());
expectedMethods.add(new MethodCall("makeObject").returned(ZERO));
// StackObjectPool, SoftReferenceObjectPool also validate on add
if (pool instanceof SoftReferenceObjectPool) {
expectedMethods.add(new MethodCall(
"validateObject", ZERO).returned(Boolean.TRUE));
}
expectedMethods.add(new MethodCall("passivateObject", ZERO));
assertEquals(expectedMethods, factory.getMethodCalls());
//// Test exception handling of addObject
reset(pool, factory, expectedMethods);
// makeObject Exceptions should be propagated to client code from addObject
factory.setMakeObjectFail(true);
try {
pool.addObject();
fail("Expected addObject to propagate makeObject exception.");
} catch (final PrivateException pe) {
// expected
}
expectedMethods.add(new MethodCall("makeObject"));
assertEquals(expectedMethods, factory.getMethodCalls());
clear(factory, expectedMethods);
// passivateObject Exceptions should be propagated to client code from addObject
factory.setMakeObjectFail(false);
factory.setPassivateObjectFail(true);
try {
pool.addObject();
fail("Expected addObject to propagate passivateObject exception.");
} catch (final PrivateException pe) {
// expected
}
expectedMethods.add(new MethodCall("makeObject").returned(ONE));
// StackObjectPool, SofReferenceObjectPool also validate on add
if (pool instanceof SoftReferenceObjectPool) {
expectedMethods.add(new MethodCall(
"validateObject", ONE).returned(Boolean.TRUE));
}
expectedMethods.add(new MethodCall("passivateObject", ONE));
assertEquals(expectedMethods, factory.getMethodCalls());
pool.close();
}
@Test
public void testPOFBorrowObjectUsages() throws Exception {
final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
final ObjectPool<Object> pool;
try {
pool = makeEmptyPool(factory);
} catch (final UnsupportedOperationException uoe) {
return; // test not supported
}
if (pool instanceof GenericObjectPool) {
((GenericObjectPool<Object>) pool).setTestOnBorrow(true);
}
final List<MethodCall> expectedMethods = new ArrayList<>();
Object obj;
/// Test correct behavior code paths
// existing idle object should be activated and validated
pool.addObject();
clear(factory, expectedMethods);
obj = pool.borrowObject();
expectedMethods.add(new MethodCall("activateObject", ZERO));
expectedMethods.add(new MethodCall("validateObject", ZERO).returned(Boolean.TRUE));
assertEquals(expectedMethods, factory.getMethodCalls());
pool.returnObject(obj);
//// Test exception handling of borrowObject
reset(pool, factory, expectedMethods);
// makeObject Exceptions should be propagated to client code from borrowObject
factory.setMakeObjectFail(true);
try {
obj = pool.borrowObject();
fail("Expected borrowObject to propagate makeObject exception.");
} catch (final PrivateException pe) {
// expected
}
expectedMethods.add(new MethodCall("makeObject"));
assertEquals(expectedMethods, factory.getMethodCalls());
// when activateObject fails in borrowObject, a new object should be borrowed/created
reset(pool, factory, expectedMethods);
pool.addObject();
clear(factory, expectedMethods);
factory.setActivateObjectFail(true);
expectedMethods.add(new MethodCall("activateObject", obj));
try {
pool.borrowObject();
fail("Expecting NoSuchElementException");
} catch (final NoSuchElementException ex) {
// Expected - newly created object will also fail to activate
}
// Idle object fails activation, new one created, also fails
expectedMethods.add(new MethodCall("makeObject").returned(ONE));
expectedMethods.add(new MethodCall("activateObject", ONE));
removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
assertEquals(expectedMethods, factory.getMethodCalls());
// when validateObject fails in borrowObject, a new object should be borrowed/created
reset(pool, factory, expectedMethods);
pool.addObject();
clear(factory, expectedMethods);
factory.setValidateObjectFail(true);
expectedMethods.add(new MethodCall("activateObject", ZERO));
expectedMethods.add(new MethodCall("validateObject", ZERO));
try {
pool.borrowObject();
} catch (final NoSuchElementException ex) {
// Expected - newly created object will also fail to validate
}
// Idle object is activated, but fails validation.
// New instance is created, activated and then fails validation
expectedMethods.add(new MethodCall("makeObject").returned(ONE));
expectedMethods.add(new MethodCall("activateObject", ONE));
expectedMethods.add(new MethodCall("validateObject", ONE));
removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
// Second activate and validate are missing from expectedMethods
assertTrue(factory.getMethodCalls().containsAll(expectedMethods));
pool.close();
}
@Test
public void testPOFReturnObjectUsages() throws Exception {
final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
final ObjectPool<Object> pool;
try {
pool = makeEmptyPool(factory);
} catch (final UnsupportedOperationException uoe) {
return; // test not supported
}
final List<MethodCall> expectedMethods = new ArrayList<>();
Object obj;
/// Test correct behavior code paths
obj = pool.borrowObject();
clear(factory, expectedMethods);
// returned object should be passivated
pool.returnObject(obj);
// StackObjectPool, SoftReferenceObjectPool also validate on return
if (pool instanceof SoftReferenceObjectPool) {
expectedMethods.add(new MethodCall(
"validateObject", obj).returned(Boolean.TRUE));
}
expectedMethods.add(new MethodCall("passivateObject", obj));
assertEquals(expectedMethods, factory.getMethodCalls());
//// Test exception handling of returnObject
reset(pool, factory, expectedMethods);
pool.addObject();
pool.addObject();
pool.addObject();
assertEquals(3, pool.getNumIdle());
// passivateObject should swallow exceptions and not add the object to the pool
obj = pool.borrowObject();
pool.borrowObject();
assertEquals(1, pool.getNumIdle());
assertEquals(2, pool.getNumActive());
clear(factory, expectedMethods);
factory.setPassivateObjectFail(true);
pool.returnObject(obj);
// StackObjectPool, SoftReferenceObjectPool also validate on return
if (pool instanceof SoftReferenceObjectPool) {
expectedMethods.add(new MethodCall(
"validateObject", obj).returned(Boolean.TRUE));
}
expectedMethods.add(new MethodCall("passivateObject", obj));
removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
assertEquals(expectedMethods, factory.getMethodCalls());
assertEquals(1, pool.getNumIdle()); // Not returned
assertEquals(1, pool.getNumActive()); // But not in active count
// destroyObject should swallow exceptions too
reset(pool, factory, expectedMethods);
obj = pool.borrowObject();
clear(factory, expectedMethods);
factory.setPassivateObjectFail(true);
factory.setDestroyObjectFail(true);
pool.returnObject(obj);
pool.close();
}
@Test
public void testPOFInvalidateObjectUsages() throws Exception {
final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
final ObjectPool<Object> pool;
try {
pool = makeEmptyPool(factory);
} catch (final UnsupportedOperationException uoe) {
return; // test not supported
}
final List<MethodCall> expectedMethods = new ArrayList<>();
Object obj;
/// Test correct behavior code paths
obj = pool.borrowObject();
clear(factory, expectedMethods);
// invalidated object should be destroyed
pool.invalidateObject(obj);
expectedMethods.add(new MethodCall("destroyObject", obj));
assertEquals(expectedMethods, factory.getMethodCalls());
//// Test exception handling of invalidateObject
reset(pool, factory, expectedMethods);
obj = pool.borrowObject();
clear(factory, expectedMethods);
factory.setDestroyObjectFail(true);
try {
pool.invalidateObject(obj);
fail("Expecting destroy exception to propagate");
} catch (final PrivateException ex) {
// Expected
}
Thread.sleep(250); // could be deferred
removeDestroyObjectCall(factory.getMethodCalls());
assertEquals(expectedMethods, factory.getMethodCalls());
pool.close();
}
@Test
public void testPOFClearUsages() throws Exception {
final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
final ObjectPool<Object> pool;
try {
pool = makeEmptyPool(factory);
} catch (final UnsupportedOperationException uoe) {
return; // test not supported
}
final List<MethodCall> expectedMethods = new ArrayList<>();
/// Test correct behavior code paths
pool.addObjects(5);
pool.clear();
//// Test exception handling clear should swallow destroy object failures
reset(pool, factory, expectedMethods);
factory.setDestroyObjectFail(true);
pool.addObjects(5);
pool.clear();
pool.close();
}
@Test
public void testPOFCloseUsages() throws Exception {
final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
ObjectPool<Object> pool;
try {
pool = makeEmptyPool(factory);
} catch (final UnsupportedOperationException uoe) {
return; // test not supported
}
final List<MethodCall> expectedMethods = new ArrayList<>();
/// Test correct behavior code paths
pool.addObjects(5);
pool.close();
//// Test exception handling close should swallow failures
try {
pool = makeEmptyPool(factory);
} catch (final UnsupportedOperationException uoe) {
return; // test not supported
}
reset(pool, factory, expectedMethods);
factory.setDestroyObjectFail(true);
pool.addObjects(5);
pool.close();
}
@Test
public void testToString() throws Exception {
ObjectPool<Object> pool;
try {
pool = makeEmptyPool(new MethodCallPoolableObjectFactory());
} catch (final UnsupportedOperationException uoe) {
return; // test not supported
}
pool.toString();
pool.close();
}
static void removeDestroyObjectCall(final List<MethodCall> calls) {
final Iterator<MethodCall> iter = calls.iterator();
while (iter.hasNext()) {
final MethodCall call = iter.next();
if ("destroyObject".equals(call.getName())) {
iter.remove();
}
}
}
private static void reset(final ObjectPool<Object> pool, final MethodCallPoolableObjectFactory factory, final List<MethodCall> expectedMethods) throws Exception {
pool.clear();
clear(factory, expectedMethods);
factory.reset();
}
private static void clear(final MethodCallPoolableObjectFactory factory, final List<MethodCall> expectedMethods) {
factory.getMethodCalls().clear();
expectedMethods.clear();
}
}