/* * 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.pool; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import junit.framework.TestCase; import org.apache.commons.pool.impl.GenericKeyedObjectPool; import org.apache.commons.pool.impl.StackKeyedObjectPool; /** * Abstract {@link TestCase} for {@link ObjectPool} implementations. * @author Rodney Waldhoff * @author Sandy McArthur * @version $Revision: 1222710 $ $Date: 2011-12-23 10:58:12 -0500 (Fri, 23 Dec 2011) $ */ public abstract class TestKeyedObjectPool extends TestCase { public TestKeyedObjectPool(String testName) { super(testName); } /** * Create an <code>KeyedObjectPool</code> with the specified factory. * The pool should be in a default configuration and conform to the expected * behaviors described in {@link KeyedObjectPool}. * Generally speaking there should be no limits on the various object counts. */ protected abstract KeyedObjectPool<Object, Integer> makeEmptyPool(KeyedPoolableObjectFactory<Object, Integer> factory); protected final String KEY = "key"; public void testClosedPoolBehavior() throws Exception { final KeyedObjectPool<Object, Integer> pool; try { pool = makeEmptyPool(new BaseKeyedPoolableObjectFactory<Object, Integer>() { @Override public Integer makeObject(final Object key) throws Exception { return new Integer(1234567890); } }); } catch(UnsupportedOperationException uoe) { return; // test not supported } Integer o1 = pool.borrowObject(KEY); Integer o2 = pool.borrowObject(KEY); pool.close(); try { pool.addObject(KEY); fail("A closed pool must throw an IllegalStateException when addObject is called."); } catch (IllegalStateException ise) { // expected } try { pool.borrowObject(KEY); fail("A closed pool must throw an IllegalStateException when borrowObject is called."); } catch (IllegalStateException ise) { // expected } // The following should not throw exceptions just because the pool is closed. assertEquals("A closed pool shouldn't have any idle objects.", 0, pool.getNumIdle(KEY)); assertEquals("A closed pool shouldn't have any idle objects.", 0, pool.getNumIdle()); pool.getNumActive(); pool.getNumActive(KEY); pool.returnObject(KEY, o1); assertEquals("returnObject should not add items back into the idle object pool for a closed pool.", 0, pool.getNumIdle(KEY)); assertEquals("returnObject should not add items back into the idle object pool for a closed pool.", 0, pool.getNumIdle()); pool.invalidateObject(KEY, o2); pool.clear(KEY); pool.clear(); pool.close(); } private final Integer ZERO = new Integer(0); private final Integer ONE = new Integer(1); public void testKPOFAddObjectUsage() throws Exception { final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory(); final KeyedObjectPool<Object, Integer> pool; try { pool = makeEmptyPool(factory); } catch(UnsupportedOperationException uoe) { return; // test not supported } final List<MethodCall> expectedMethods = new ArrayList<MethodCall>(); // addObject should make a new object, pasivate it and put it in the pool pool.addObject(KEY); expectedMethods.add(new MethodCall("makeObject", KEY).returned(ZERO)); if (pool instanceof StackKeyedObjectPool) { expectedMethods.add(new MethodCall( "validateObject", KEY, ZERO).returned(Boolean.TRUE)); } expectedMethods.add(new MethodCall("passivateObject", KEY, 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(KEY); fail("Expected addObject to propagate makeObject exception."); } catch (PrivateException pe) { // expected } expectedMethods.add(new MethodCall("makeObject", KEY)); 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(KEY); fail("Expected addObject to propagate passivateObject exception."); } catch (PrivateException pe) { // expected } expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE)); if (pool instanceof StackKeyedObjectPool) { expectedMethods.add(new MethodCall( "validateObject", KEY, ONE).returned(Boolean.TRUE)); } expectedMethods.add(new MethodCall("passivateObject", KEY, ONE)); assertEquals(expectedMethods, factory.getMethodCalls()); } public void testKPOFBorrowObjectUsages() throws Exception { final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory(); final KeyedObjectPool<Object, Integer> pool; try { pool = makeEmptyPool(factory); } catch(UnsupportedOperationException uoe) { return; // test not supported } final List<MethodCall> expectedMethods = new ArrayList<MethodCall>(); Integer obj; if (pool instanceof GenericKeyedObjectPool) { ((GenericKeyedObjectPool<Object, Integer>) pool).setTestOnBorrow(true); } /// Test correct behavior code paths // existing idle object should be activated and validated pool.addObject(KEY); clear(factory, expectedMethods); obj = pool.borrowObject(KEY); expectedMethods.add(new MethodCall("activateObject", KEY, ZERO)); expectedMethods.add(new MethodCall("validateObject", KEY, ZERO).returned(Boolean.TRUE)); assertEquals(expectedMethods, factory.getMethodCalls()); pool.returnObject(KEY, 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(KEY); fail("Expected borrowObject to propagate makeObject exception."); } catch (PrivateException pe) { // expected } expectedMethods.add(new MethodCall("makeObject", KEY)); assertEquals(expectedMethods, factory.getMethodCalls()); // when activateObject fails in borrowObject, a new object should be borrowed/created reset(pool, factory, expectedMethods); pool.addObject(KEY); clear(factory, expectedMethods); factory.setActivateObjectFail(true); expectedMethods.add(new MethodCall("activateObject", KEY, obj)); try { obj = pool.borrowObject(KEY); fail("Expecting NoSuchElementException"); } catch (NoSuchElementException e) { //Activate should fail } // After idle object fails validation, new on is created and activation // fails again for the new one. expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE)); expectedMethods.add(new MethodCall("activateObject", KEY, ONE)); TestObjectPool.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(KEY); clear(factory, expectedMethods); factory.setValidateObjectFail(true); // testOnBorrow is on, so this will throw when the newly created instance // fails validation try { obj = pool.borrowObject(KEY); fail("Expecting NoSuchElementException"); } catch (NoSuchElementException ex) { // expected } // Activate, then validate for idle instance expectedMethods.add(new MethodCall("activateObject", KEY, ZERO)); expectedMethods.add(new MethodCall("validateObject", KEY, ZERO)); // Make new instance, activate succeeds, validate fails expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE)); expectedMethods.add(new MethodCall("activateObject", KEY, ONE)); expectedMethods.add(new MethodCall("validateObject", KEY, ONE)); TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); assertEquals(expectedMethods, factory.getMethodCalls()); } public void testKPOFReturnObjectUsages() throws Exception { final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory(); final KeyedObjectPool<Object, Integer> pool; try { pool = makeEmptyPool(factory); } catch(UnsupportedOperationException uoe) { return; // test not supported } final List<MethodCall> expectedMethods = new ArrayList<MethodCall>(); Integer obj; /// Test correct behavior code paths obj = pool.borrowObject(KEY); clear(factory, expectedMethods); // returned object should be passivated pool.returnObject(KEY, obj); if (pool instanceof StackKeyedObjectPool) { expectedMethods.add(new MethodCall( "validateObject", KEY, obj).returned(Boolean.TRUE)); } expectedMethods.add(new MethodCall("passivateObject", KEY, obj)); assertEquals(expectedMethods, factory.getMethodCalls()); //// Test exception handling of returnObject reset(pool, factory, expectedMethods); // passivateObject should swallow exceptions and not add the object to the pool pool.addObject(KEY); pool.addObject(KEY); pool.addObject(KEY); assertEquals(3, pool.getNumIdle(KEY)); obj = pool.borrowObject(KEY); obj = pool.borrowObject(KEY); assertEquals(1, pool.getNumIdle(KEY)); assertEquals(2, pool.getNumActive(KEY)); clear(factory, expectedMethods); factory.setPassivateObjectFail(true); pool.returnObject(KEY, obj); if (pool instanceof StackKeyedObjectPool) { expectedMethods.add(new MethodCall( "validateObject", KEY, obj).returned(Boolean.TRUE)); } expectedMethods.add(new MethodCall("passivateObject", KEY, obj)); TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here. assertEquals(expectedMethods, factory.getMethodCalls()); assertEquals(1, pool.getNumIdle(KEY)); // Not added assertEquals(1, pool.getNumActive(KEY)); // But not active reset(pool, factory, expectedMethods); obj = pool.borrowObject(KEY); clear(factory, expectedMethods); factory.setPassivateObjectFail(true); factory.setDestroyObjectFail(true); try { pool.returnObject(KEY, obj); if (!(pool instanceof GenericKeyedObjectPool)) { // ugh, 1.3-compat fail("Expecting destroyObject exception to be propagated"); } } catch (PrivateException ex) { // Expected } } public void testKPOFInvalidateObjectUsages() throws Exception { final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory(); final KeyedObjectPool<Object, Integer> pool; try { pool = makeEmptyPool(factory); } catch(UnsupportedOperationException uoe) { return; // test not supported } final List<MethodCall> expectedMethods = new ArrayList<MethodCall>(); Integer obj; /// Test correct behavior code paths obj = pool.borrowObject(KEY); clear(factory, expectedMethods); // invalidated object should be destroyed pool.invalidateObject(KEY, obj); expectedMethods.add(new MethodCall("destroyObject", KEY, obj)); assertEquals(expectedMethods, factory.getMethodCalls()); //// Test exception handling of invalidateObject reset(pool, factory, expectedMethods); obj = pool.borrowObject(KEY); clear(factory, expectedMethods); factory.setDestroyObjectFail(true); try { pool.invalidateObject(KEY, obj); fail("Expecting destroy exception to propagate"); } catch (PrivateException ex) { // Expected } Thread.sleep(250); // could be defered TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); assertEquals(expectedMethods, factory.getMethodCalls()); } public void testKPOFClearUsages() throws Exception { final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory(); final KeyedObjectPool<Object, Integer> pool; try { pool = makeEmptyPool(factory); } catch(UnsupportedOperationException uoe) { return; // test not supported } final List<MethodCall> expectedMethods = new ArrayList<MethodCall>(); /// Test correct behavior code paths PoolUtils.prefill(pool, KEY, 5); pool.clear(); //// Test exception handling clear should swallow destory object failures reset(pool, factory, expectedMethods); factory.setDestroyObjectFail(true); PoolUtils.prefill(pool, KEY, 5); pool.clear(); } public void testKPOFCloseUsages() throws Exception { final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory(); KeyedObjectPool<Object, Integer> pool; try { pool = makeEmptyPool(factory); } catch(UnsupportedOperationException uoe) { return; // test not supported } final List<MethodCall> expectedMethods = new ArrayList<MethodCall>(); /// Test correct behavior code paths PoolUtils.prefill(pool, KEY, 5); pool.close(); //// Test exception handling close should swallow failures pool = makeEmptyPool(factory); reset(pool, factory, expectedMethods); factory.setDestroyObjectFail(true); PoolUtils.prefill(pool, KEY, 5); pool.close(); } public void testToString() throws Exception { final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory(); try { makeEmptyPool(factory).toString(); } catch(UnsupportedOperationException uoe) { return; // test not supported } } private void reset(final KeyedObjectPool<? ,?> pool, final FailingKeyedPoolableObjectFactory factory, final List<MethodCall> expectedMethods) throws Exception { pool.clear(); clear(factory, expectedMethods); factory.reset(); } private void clear(final FailingKeyedPoolableObjectFactory factory, final List<MethodCall> expectedMethods) { factory.getMethodCalls().clear(); expectedMethods.clear(); } protected static class FailingKeyedPoolableObjectFactory implements KeyedPoolableObjectFactory<Object, Integer> { private final List<MethodCall> methodCalls = new ArrayList<MethodCall>(); private int count = 0; private boolean makeObjectFail; private boolean activateObjectFail; private boolean validateObjectFail; private boolean passivateObjectFail; private boolean destroyObjectFail; public FailingKeyedPoolableObjectFactory() { } public void reset() { count = 0; getMethodCalls().clear(); setMakeObjectFail(false); setActivateObjectFail(false); setValidateObjectFail(false); setPassivateObjectFail(false); setDestroyObjectFail(false); } public List<MethodCall> getMethodCalls() { return methodCalls; } public int getCurrentCount() { return count; } public void setCurrentCount(final int count) { this.count = count; } public boolean isMakeObjectFail() { return makeObjectFail; } public void setMakeObjectFail(boolean makeObjectFail) { this.makeObjectFail = makeObjectFail; } public boolean isDestroyObjectFail() { return destroyObjectFail; } public void setDestroyObjectFail(boolean destroyObjectFail) { this.destroyObjectFail = destroyObjectFail; } public boolean isValidateObjectFail() { return validateObjectFail; } public void setValidateObjectFail(boolean validateObjectFail) { this.validateObjectFail = validateObjectFail; } public boolean isActivateObjectFail() { return activateObjectFail; } public void setActivateObjectFail(boolean activateObjectFail) { this.activateObjectFail = activateObjectFail; } public boolean isPassivateObjectFail() { return passivateObjectFail; } public void setPassivateObjectFail(boolean passivateObjectFail) { this.passivateObjectFail = passivateObjectFail; } public Integer makeObject(final Object key) throws Exception { final MethodCall call = new MethodCall("makeObject", key); methodCalls.add(call); int count = this.count++; if (makeObjectFail) { throw new PrivateException("makeObject"); } final Integer obj = new Integer(count); call.setReturned(obj); return obj; } public void activateObject(final Object key, final Integer obj) throws Exception { methodCalls.add(new MethodCall("activateObject", key, obj)); if (activateObjectFail) { throw new PrivateException("activateObject"); } } public boolean validateObject(final Object key, final Integer obj) { final MethodCall call = new MethodCall("validateObject", key, obj); methodCalls.add(call); if (validateObjectFail) { throw new PrivateException("validateObject"); } final boolean r = true; call.returned(new Boolean(r)); return r; } public void passivateObject(final Object key, final Integer obj) throws Exception { methodCalls.add(new MethodCall("passivateObject", key, obj)); if (passivateObjectFail) { throw new PrivateException("passivateObject"); } } public void destroyObject(final Object key, final Integer obj) throws Exception { methodCalls.add(new MethodCall("destroyObject", key, obj)); if (destroyObjectFail) { throw new PrivateException("destroyObject"); } } } }