001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.pool;
018
019 import junit.framework.TestCase;
020
021 import java.util.List;
022 import java.util.ArrayList;
023 import java.util.NoSuchElementException;
024
025 import org.apache.commons.pool.impl.GenericKeyedObjectPool;
026 import org.apache.commons.pool.impl.StackKeyedObjectPool;
027
028 /**
029 * Abstract {@link TestCase} for {@link ObjectPool} implementations.
030 * @author Rodney Waldhoff
031 * @author Sandy McArthur
032 * @version $Revision: 774099 $ $Date: 2009-05-12 17:29:02 -0400 (Tue, 12 May 2009) $
033 */
034 public abstract class TestKeyedObjectPool extends TestCase {
035 public TestKeyedObjectPool(String testName) {
036 super(testName);
037 }
038
039 /**
040 * Create an <code>KeyedObjectPool</code> with the specified factory.
041 * The pool should be in a default configuration and conform to the expected
042 * behaviors described in {@link KeyedObjectPool}.
043 * Generally speaking there should be no limits on the various object counts.
044 */
045 protected abstract KeyedObjectPool makeEmptyPool(KeyedPoolableObjectFactory factory);
046
047 protected final String KEY = "key";
048
049 public void testClosedPoolBehavior() throws Exception {
050 final KeyedObjectPool pool;
051 try {
052 pool = makeEmptyPool(new BaseKeyedPoolableObjectFactory() {
053 public Object makeObject(final Object key) throws Exception {
054 return new Object();
055 }
056 });
057 } catch(UnsupportedOperationException uoe) {
058 return; // test not supported
059 }
060
061 Object o1 = pool.borrowObject(KEY);
062 Object o2 = pool.borrowObject(KEY);
063
064 pool.close();
065
066 try {
067 pool.addObject(KEY);
068 fail("A closed pool must throw an IllegalStateException when addObject is called.");
069 } catch (IllegalStateException ise) {
070 // expected
071 }
072
073 try {
074 pool.borrowObject(KEY);
075 fail("A closed pool must throw an IllegalStateException when borrowObject is called.");
076 } catch (IllegalStateException ise) {
077 // expected
078 }
079
080 // The following should not throw exceptions just because the pool is closed.
081 assertEquals("A closed pool shouldn't have any idle objects.", 0, pool.getNumIdle(KEY));
082 assertEquals("A closed pool shouldn't have any idle objects.", 0, pool.getNumIdle());
083 pool.getNumActive();
084 pool.getNumActive(KEY);
085 pool.returnObject(KEY, o1);
086 assertEquals("returnObject should not add items back into the idle object pool for a closed pool.", 0, pool.getNumIdle(KEY));
087 assertEquals("returnObject should not add items back into the idle object pool for a closed pool.", 0, pool.getNumIdle());
088 pool.invalidateObject(KEY, o2);
089 pool.clear(KEY);
090 pool.clear();
091 pool.close();
092 }
093
094 private final Integer ZERO = new Integer(0);
095 private final Integer ONE = new Integer(1);
096
097 public void testKPOFAddObjectUsage() throws Exception {
098 final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
099 final KeyedObjectPool pool;
100 try {
101 pool = makeEmptyPool(factory);
102 } catch(UnsupportedOperationException uoe) {
103 return; // test not supported
104 }
105 final List expectedMethods = new ArrayList();
106
107 // addObject should make a new object, pasivate it and put it in the pool
108 pool.addObject(KEY);
109 expectedMethods.add(new MethodCall("makeObject", KEY).returned(ZERO));
110 if (pool instanceof StackKeyedObjectPool) {
111 expectedMethods.add(new MethodCall(
112 "validateObject", KEY, ZERO).returned(Boolean.TRUE));
113 }
114 expectedMethods.add(new MethodCall("passivateObject", KEY, ZERO));
115 assertEquals(expectedMethods, factory.getMethodCalls());
116
117 //// Test exception handling of addObject
118 reset(pool, factory, expectedMethods);
119
120 // makeObject Exceptions should be propagated to client code from addObject
121 factory.setMakeObjectFail(true);
122 try {
123 pool.addObject(KEY);
124 fail("Expected addObject to propagate makeObject exception.");
125 } catch (PrivateException pe) {
126 // expected
127 }
128 expectedMethods.add(new MethodCall("makeObject", KEY));
129 assertEquals(expectedMethods, factory.getMethodCalls());
130
131 clear(factory, expectedMethods);
132
133 // passivateObject Exceptions should be propagated to client code from addObject
134 factory.setMakeObjectFail(false);
135 factory.setPassivateObjectFail(true);
136 try {
137 pool.addObject(KEY);
138 fail("Expected addObject to propagate passivateObject exception.");
139 } catch (PrivateException pe) {
140 // expected
141 }
142 expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
143 if (pool instanceof StackKeyedObjectPool) {
144 expectedMethods.add(new MethodCall(
145 "validateObject", KEY, ONE).returned(Boolean.TRUE));
146 }
147 expectedMethods.add(new MethodCall("passivateObject", KEY, ONE));
148 assertEquals(expectedMethods, factory.getMethodCalls());
149 }
150
151 public void testKPOFBorrowObjectUsages() throws Exception {
152 final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
153 final KeyedObjectPool pool;
154 try {
155 pool = makeEmptyPool(factory);
156 } catch(UnsupportedOperationException uoe) {
157 return; // test not supported
158 }
159 final List expectedMethods = new ArrayList();
160 Object obj;
161
162 if (pool instanceof GenericKeyedObjectPool) {
163 ((GenericKeyedObjectPool) pool).setTestOnBorrow(true);
164 }
165
166 /// Test correct behavior code paths
167
168 // existing idle object should be activated and validated
169 pool.addObject(KEY);
170 clear(factory, expectedMethods);
171 obj = pool.borrowObject(KEY);
172 expectedMethods.add(new MethodCall("activateObject", KEY, ZERO));
173 expectedMethods.add(new MethodCall("validateObject", KEY, ZERO).returned(Boolean.TRUE));
174 assertEquals(expectedMethods, factory.getMethodCalls());
175 pool.returnObject(KEY, obj);
176
177 //// Test exception handling of borrowObject
178 reset(pool, factory, expectedMethods);
179
180 // makeObject Exceptions should be propagated to client code from borrowObject
181 factory.setMakeObjectFail(true);
182 try {
183 obj = pool.borrowObject(KEY);
184 fail("Expected borrowObject to propagate makeObject exception.");
185 } catch (PrivateException pe) {
186 // expected
187 }
188 expectedMethods.add(new MethodCall("makeObject", KEY));
189 assertEquals(expectedMethods, factory.getMethodCalls());
190
191
192 // when activateObject fails in borrowObject, a new object should be borrowed/created
193 reset(pool, factory, expectedMethods);
194 pool.addObject(KEY);
195 clear(factory, expectedMethods);
196
197 factory.setActivateObjectFail(true);
198 expectedMethods.add(new MethodCall("activateObject", KEY, obj));
199 try {
200 obj = pool.borrowObject(KEY);
201 fail("Expecting NoSuchElementException");
202 } catch (NoSuchElementException e) {
203 //Activate should fail
204 }
205 // After idle object fails validation, new on is created and activation
206 // fails again for the new one.
207 expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
208 expectedMethods.add(new MethodCall("activateObject", KEY, ONE));
209 TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
210 assertEquals(expectedMethods, factory.getMethodCalls());
211
212 // when validateObject fails in borrowObject, a new object should be borrowed/created
213 reset(pool, factory, expectedMethods);
214 pool.addObject(KEY);
215 clear(factory, expectedMethods);
216
217 factory.setValidateObjectFail(true);
218 // testOnBorrow is on, so this will throw when the newly created instance
219 // fails validation
220 try {
221 obj = pool.borrowObject(KEY);
222 fail("Expecting NoSuchElementException");
223 } catch (NoSuchElementException ex) {
224 // expected
225 }
226 // Activate, then validate for idle instance
227 expectedMethods.add(new MethodCall("activateObject", KEY, ZERO));
228 expectedMethods.add(new MethodCall("validateObject", KEY, ZERO));
229 // Make new instance, activate succeeds, validate fails
230 expectedMethods.add(new MethodCall("makeObject", KEY).returned(ONE));
231 expectedMethods.add(new MethodCall("activateObject", KEY, ONE));
232 expectedMethods.add(new MethodCall("validateObject", KEY, ONE));
233 TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls());
234 assertEquals(expectedMethods, factory.getMethodCalls());
235 }
236
237 public void testKPOFReturnObjectUsages() throws Exception {
238 final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
239 final KeyedObjectPool pool;
240 try {
241 pool = makeEmptyPool(factory);
242 } catch(UnsupportedOperationException uoe) {
243 return; // test not supported
244 }
245 final List expectedMethods = new ArrayList();
246 Object obj;
247
248 /// Test correct behavior code paths
249 obj = pool.borrowObject(KEY);
250 clear(factory, expectedMethods);
251
252 // returned object should be passivated
253 pool.returnObject(KEY, obj);
254 if (pool instanceof StackKeyedObjectPool) {
255 expectedMethods.add(new MethodCall(
256 "validateObject", KEY, obj).returned(Boolean.TRUE));
257 }
258 expectedMethods.add(new MethodCall("passivateObject", KEY, obj));
259 assertEquals(expectedMethods, factory.getMethodCalls());
260
261 //// Test exception handling of returnObject
262 reset(pool, factory, expectedMethods);
263
264 // passivateObject should swallow exceptions and not add the object to the pool
265 pool.addObject(KEY);
266 pool.addObject(KEY);
267 pool.addObject(KEY);
268 assertEquals(3, pool.getNumIdle(KEY));
269 obj = pool.borrowObject(KEY);
270 obj = pool.borrowObject(KEY);
271 assertEquals(1, pool.getNumIdle(KEY));
272 assertEquals(2, pool.getNumActive(KEY));
273 clear(factory, expectedMethods);
274 factory.setPassivateObjectFail(true);
275 pool.returnObject(KEY, obj);
276 if (pool instanceof StackKeyedObjectPool) {
277 expectedMethods.add(new MethodCall(
278 "validateObject", KEY, obj).returned(Boolean.TRUE));
279 }
280 expectedMethods.add(new MethodCall("passivateObject", KEY, obj));
281 TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
282 assertEquals(expectedMethods, factory.getMethodCalls());
283 assertEquals(1, pool.getNumIdle(KEY)); // Not added
284 assertEquals(1, pool.getNumActive(KEY)); // But not active
285
286 reset(pool, factory, expectedMethods);
287 obj = pool.borrowObject(KEY);
288 clear(factory, expectedMethods);
289 factory.setPassivateObjectFail(true);
290 factory.setDestroyObjectFail(true);
291 try {
292 pool.returnObject(KEY, obj);
293 if (!(pool instanceof GenericKeyedObjectPool)) { // ugh, 1.3-compat
294 fail("Expecting destroyObject exception to be propagated");
295 }
296 } catch (PrivateException ex) {
297 // Expected
298 }
299 }
300
301 public void testKPOFInvalidateObjectUsages() throws Exception {
302 final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
303 final KeyedObjectPool pool;
304 try {
305 pool = makeEmptyPool(factory);
306 } catch(UnsupportedOperationException uoe) {
307 return; // test not supported
308 }
309 final List expectedMethods = new ArrayList();
310 Object obj;
311
312 /// Test correct behavior code paths
313
314 obj = pool.borrowObject(KEY);
315 clear(factory, expectedMethods);
316
317 // invalidated object should be destroyed
318 pool.invalidateObject(KEY, obj);
319 expectedMethods.add(new MethodCall("destroyObject", KEY, obj));
320 assertEquals(expectedMethods, factory.getMethodCalls());
321
322 //// Test exception handling of invalidateObject
323 reset(pool, factory, expectedMethods);
324 obj = pool.borrowObject(KEY);
325 clear(factory, expectedMethods);
326 factory.setDestroyObjectFail(true);
327 try {
328 pool.invalidateObject(KEY, obj);
329 fail("Expecting destroy exception to propagate");
330 } catch (PrivateException ex) {
331 // Expected
332 }
333 Thread.sleep(250); // could be defered
334 TestObjectPool.removeDestroyObjectCall(factory.getMethodCalls());
335 assertEquals(expectedMethods, factory.getMethodCalls());
336 }
337
338 public void testKPOFClearUsages() throws Exception {
339 final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
340 final KeyedObjectPool pool;
341 try {
342 pool = makeEmptyPool(factory);
343 } catch(UnsupportedOperationException uoe) {
344 return; // test not supported
345 }
346 final List expectedMethods = new ArrayList();
347
348 /// Test correct behavior code paths
349 PoolUtils.prefill(pool, KEY, 5);
350 pool.clear();
351
352 //// Test exception handling clear should swallow destory object failures
353 reset(pool, factory, expectedMethods);
354 factory.setDestroyObjectFail(true);
355 PoolUtils.prefill(pool, KEY, 5);
356 pool.clear();
357 }
358
359 public void testKPOFCloseUsages() throws Exception {
360 final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
361 KeyedObjectPool pool;
362 try {
363 pool = makeEmptyPool(factory);
364 } catch(UnsupportedOperationException uoe) {
365 return; // test not supported
366 }
367 final List expectedMethods = new ArrayList();
368
369 /// Test correct behavior code paths
370 PoolUtils.prefill(pool, KEY, 5);
371 pool.close();
372
373
374 //// Test exception handling close should swallow failures
375 pool = makeEmptyPool(factory);
376 reset(pool, factory, expectedMethods);
377 factory.setDestroyObjectFail(true);
378 PoolUtils.prefill(pool, KEY, 5);
379 pool.close();
380 }
381
382 public void testToString() throws Exception {
383 final FailingKeyedPoolableObjectFactory factory = new FailingKeyedPoolableObjectFactory();
384 try {
385 makeEmptyPool(factory).toString();
386 } catch(UnsupportedOperationException uoe) {
387 return; // test not supported
388 }
389 }
390
391 private void reset(final KeyedObjectPool pool, final FailingKeyedPoolableObjectFactory factory, final List expectedMethods) throws Exception {
392 pool.clear();
393 clear(factory, expectedMethods);
394 factory.reset();
395 }
396
397 private void clear(final FailingKeyedPoolableObjectFactory factory, final List expectedMethods) {
398 factory.getMethodCalls().clear();
399 expectedMethods.clear();
400 }
401
402 protected static class FailingKeyedPoolableObjectFactory implements KeyedPoolableObjectFactory {
403 private final List methodCalls = new ArrayList();
404 private int count = 0;
405 private boolean makeObjectFail;
406 private boolean activateObjectFail;
407 private boolean validateObjectFail;
408 private boolean passivateObjectFail;
409 private boolean destroyObjectFail;
410
411 public FailingKeyedPoolableObjectFactory() {
412 }
413
414 public void reset() {
415 count = 0;
416 getMethodCalls().clear();
417 setMakeObjectFail(false);
418 setActivateObjectFail(false);
419 setValidateObjectFail(false);
420 setPassivateObjectFail(false);
421 setDestroyObjectFail(false);
422 }
423
424 public List getMethodCalls() {
425 return methodCalls;
426 }
427
428 public int getCurrentCount() {
429 return count;
430 }
431
432 public void setCurrentCount(final int count) {
433 this.count = count;
434 }
435
436 public boolean isMakeObjectFail() {
437 return makeObjectFail;
438 }
439
440 public void setMakeObjectFail(boolean makeObjectFail) {
441 this.makeObjectFail = makeObjectFail;
442 }
443
444 public boolean isDestroyObjectFail() {
445 return destroyObjectFail;
446 }
447
448 public void setDestroyObjectFail(boolean destroyObjectFail) {
449 this.destroyObjectFail = destroyObjectFail;
450 }
451
452 public boolean isValidateObjectFail() {
453 return validateObjectFail;
454 }
455
456 public void setValidateObjectFail(boolean validateObjectFail) {
457 this.validateObjectFail = validateObjectFail;
458 }
459
460 public boolean isActivateObjectFail() {
461 return activateObjectFail;
462 }
463
464 public void setActivateObjectFail(boolean activateObjectFail) {
465 this.activateObjectFail = activateObjectFail;
466 }
467
468 public boolean isPassivateObjectFail() {
469 return passivateObjectFail;
470 }
471
472 public void setPassivateObjectFail(boolean passivateObjectFail) {
473 this.passivateObjectFail = passivateObjectFail;
474 }
475
476 public Object makeObject(final Object key) throws Exception {
477 final MethodCall call = new MethodCall("makeObject", key);
478 methodCalls.add(call);
479 int count = this.count++;
480 if (makeObjectFail) {
481 throw new PrivateException("makeObject");
482 }
483 final Integer obj = new Integer(count);
484 call.setReturned(obj);
485 return obj;
486 }
487
488 public void activateObject(final Object key, final Object obj) throws Exception {
489 methodCalls.add(new MethodCall("activateObject", key, obj));
490 if (activateObjectFail) {
491 throw new PrivateException("activateObject");
492 }
493 }
494
495 public boolean validateObject(final Object key, final Object obj) {
496 final MethodCall call = new MethodCall("validateObject", key, obj);
497 methodCalls.add(call);
498 if (validateObjectFail) {
499 throw new PrivateException("validateObject");
500 }
501 final boolean r = true;
502 call.returned(new Boolean(r));
503 return r;
504 }
505
506 public void passivateObject(final Object key, final Object obj) throws Exception {
507 methodCalls.add(new MethodCall("passivateObject", key, obj));
508 if (passivateObjectFail) {
509 throw new PrivateException("passivateObject");
510 }
511 }
512
513 public void destroyObject(final Object key, final Object obj) throws Exception {
514 methodCalls.add(new MethodCall("destroyObject", key, obj));
515 if (destroyObjectFail) {
516 throw new PrivateException("destroyObject");
517 }
518 }
519 }
520 }