View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.pool2;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertNotNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  import static org.junit.jupiter.api.Assertions.fail;
24  
25  import java.lang.reflect.InvocationHandler;
26  import java.lang.reflect.Method;
27  import java.lang.reflect.Proxy;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Collection;
31  import java.util.Collections;
32  import java.util.HashSet;
33  import java.util.List;
34  import java.util.Map;
35  import java.util.Set;
36  import java.util.TimerTask;
37  
38  import org.apache.commons.pool2.impl.DefaultPooledObject;
39  import org.apache.commons.pool2.impl.GenericKeyedObjectPool;
40  import org.apache.commons.pool2.impl.GenericObjectPool;
41  import org.apache.commons.pool2.impl.TestGenericKeyedObjectPool;
42  import org.junit.jupiter.api.Test;
43  import org.opentest4j.AssertionFailedError;
44  
45  /**
46   * Unit tests for {@link PoolUtils}.
47   *
48   * TODO Replace our own mocking with a mocking library like Mockito.
49   */
50  public class TestPoolUtils {
51  
52      private static class MethodCallLogger implements InvocationHandler {
53          private final List<String> calledMethods;
54  
55          MethodCallLogger(final List<String> calledMethods) {
56              this.calledMethods = calledMethods;
57          }
58  
59          @Override
60          public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
61              if (calledMethods == null) {
62                  return null;
63              }
64              calledMethods.add(method.getName());
65              if (boolean.class.equals(method.getReturnType())) {
66                  return Boolean.FALSE;
67              }
68              if (int.class.equals(method.getReturnType())) {
69                  return Integer.valueOf(0);
70              }
71              if (long.class.equals(method.getReturnType())) {
72                  return Long.valueOf(0);
73              }
74              if (Object.class.equals(method.getReturnType())) {
75                  return new Object();
76              }
77              if (PooledObject.class.equals(method.getReturnType())) {
78                  return new DefaultPooledObject<>(new Object());
79              }
80              return null;
81          }
82      }
83  
84      /** Period between checks for minIdle tests. Increase this if you happen to get too many false failures. */
85      private static final int CHECK_PERIOD = 300;
86  
87      /** Times to let the minIdle check run. */
88      private static final int CHECK_COUNT = 4;
89  
90      /** Sleep time to let the minIdle tests run CHECK_COUNT times. */
91      private static final int CHECK_SLEEP_PERIOD = CHECK_PERIOD * (CHECK_COUNT - 1) + CHECK_PERIOD / 2;
92  
93      @SuppressWarnings("unchecked")
94      private static <T> T createProxy(final Class<T> clazz, final InvocationHandler handler) {
95          return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, handler);
96      }
97  
98      private static <T> T createProxy(final Class<T> clazz, final List<String> logger) {
99          return createProxy(clazz, new MethodCallLogger(logger));
100     }
101 
102     private static List<String> invokeEveryMethod(final KeyedObjectPool<Object, Object> kop) throws Exception {
103         kop.addObject(null);
104         kop.borrowObject(null);
105         kop.clear();
106         kop.clear(null);
107         kop.close();
108         kop.getKeys();
109         kop.getNumActive();
110         kop.getNumActive(null);
111         kop.getNumIdle();
112         kop.getNumIdle(null);
113         kop.invalidateObject(null, new Object());
114         kop.returnObject(null, new Object());
115         kop.toString();
116 
117         return Arrays.asList("addObject", "borrowObject", "clear", "clear", "close", "getKeys", "getNumActive", "getNumActive", "getNumIdle", "getNumIdle",
118                 "invalidateObject", "returnObject", "toString");
119     }
120 
121     private static <K, V> List<String> invokeEveryMethod(final KeyedPooledObjectFactory<K, V> kpof) throws Exception {
122         kpof.activateObject(null, null);
123         kpof.destroyObject(null, null);
124         kpof.makeObject(null);
125         kpof.passivateObject(null, null);
126         kpof.validateObject(null, null);
127         kpof.toString();
128 
129         return Arrays.asList("activateObject", "destroyObject", "makeObject", "passivateObject", "validateObject", "toString");
130     }
131 
132     private static List<String> invokeEveryMethod(final ObjectPool<Object> op) throws Exception {
133         op.addObject();
134         op.borrowObject();
135         op.clear();
136         op.close();
137         op.getNumActive();
138         op.getNumIdle();
139         op.invalidateObject(new Object());
140         op.returnObject(new Object());
141         op.toString();
142 
143         return Arrays.asList("addObject", "borrowObject", "clear", "close", "getNumActive", "getNumIdle", "invalidateObject",
144                 "returnObject", "toString");
145     }
146 
147     private static <T> List<String> invokeEveryMethod(final PooledObjectFactory<T> pof) throws Exception {
148         pof.activateObject(null);
149         pof.destroyObject(null);
150         pof.makeObject();
151         pof.passivateObject(null);
152         pof.validateObject(null);
153         pof.toString();
154 
155         return Arrays.asList("activateObject", "destroyObject", "makeObject", "passivateObject", "validateObject", "toString");
156     }
157 
158     @Test
159     public void testCheckMinIdleKeyedObjectPool() throws InterruptedException {
160         assertThrows(IllegalArgumentException.class, () -> PoolUtils.checkMinIdle(null, new Object(), 1, 1),
161                 "PoolUtils.checkMinIdle(KeyedObjectPool,Object,int,long) must not allow null pool.");
162         try (@SuppressWarnings("unchecked")
163         final KeyedObjectPool<Object, Object> pool = createProxy(KeyedObjectPool.class, (List<String>) null)) {
164             assertThrows(IllegalArgumentException.class, () -> PoolUtils.checkMinIdle(pool, (Object) null, 1, 1),
165                     "PoolUtils.checkMinIdle(KeyedObjectPool,Object,int,long) must not accept null keys.");
166         }
167         try (@SuppressWarnings("unchecked")
168         final KeyedObjectPool<Object, Object> pool = createProxy(KeyedObjectPool.class, (List<String>) null)) {
169             assertThrows(IllegalArgumentException.class, () -> PoolUtils.checkMinIdle(pool, new Object(), -1, 1),
170                     "PoolUtils.checkMinIdle(KeyedObjectPool,Object,int,long) must not accept negative min idle values.");
171         }
172 
173         final List<String> calledMethods = new ArrayList<>();
174         final Object key = new Object();
175 
176         // Test that the minIdle check doesn't add too many idle objects
177         @SuppressWarnings("unchecked")
178         final KeyedPooledObjectFactory<Object, Object> kpof = createProxy(KeyedPooledObjectFactory.class, calledMethods);
179         try (final KeyedObjectPool<Object, Object> kop = new GenericKeyedObjectPool<>(kpof)) {
180             PoolUtils.checkMinIdle(kop, key, 2, 100);
181             Thread.sleep(400);
182             assertEquals(2, kop.getNumIdle(key));
183             assertEquals(2, kop.getNumIdle());
184         }
185         int makeObjectCount = 0;
186         for (String methodName : calledMethods) {
187             if ("makeObject".equals(methodName)) {
188                 makeObjectCount++;
189             }
190         }
191         assertEquals(2, makeObjectCount, "makeObject should have been called two time");
192 
193         // Because this isn't deterministic and you can get false failures, try more than once.
194         AssertionFailedError afe = null;
195         int triesLeft = 3;
196         do {
197             afe = null;
198             try {
199                 calledMethods.clear();
200                 try (@SuppressWarnings("unchecked")
201                 final KeyedObjectPool<Object, Object> pool = createProxy(KeyedObjectPool.class, calledMethods)) {
202                     // checks minIdle immediately
203                     final TimerTask task = PoolUtils.checkMinIdle(pool, key, 1, CHECK_PERIOD);
204 
205                     Thread.sleep(CHECK_SLEEP_PERIOD); // will check CHECK_COUNT more times.
206                     task.cancel();
207                     task.toString();
208 
209                     final List<String> expectedMethods = new ArrayList<>();
210                     for (int i = 0; i < CHECK_COUNT; i++) {
211                         expectedMethods.add("getNumIdle");
212                         expectedMethods.add("addObject");
213                     }
214                     expectedMethods.add("toString");
215                     assertEquals(expectedMethods, calledMethods); // may fail because of the thread scheduler
216                 }
217             } catch (final AssertionFailedError e) {
218                 afe = e;
219             }
220         } while (--triesLeft > 0 && afe != null);
221         if (afe != null) {
222             throw afe;
223         }
224     }
225 
226     @Test
227     public void testCheckMinIdleKeyedObjectPoolKeys() throws InterruptedException {
228         // Because this isn't deterministic and you can get false failures, try more than once.
229         AssertionFailedError afe = null;
230         int triesLeft = 3;
231         do {
232             afe = null;
233             final List<String> calledMethods = new ArrayList<>();
234             try (@SuppressWarnings("unchecked")
235             final KeyedObjectPool<String, Object> pool = createProxy(KeyedObjectPool.class, calledMethods)) {
236                 final Collection<String> keys = new ArrayList<>(2);
237                 keys.add("one");
238                 keys.add("two");
239                 // checks minIdle immediately
240                 final Map<String, TimerTask> tasks = PoolUtils.checkMinIdle(pool, keys, 1, CHECK_PERIOD);
241 
242                 Thread.sleep(CHECK_SLEEP_PERIOD); // will check CHECK_COUNT more times.
243                 for (final TimerTask task : tasks.values()) {
244                     task.cancel();
245                 }
246 
247                 final List<String> expectedMethods = new ArrayList<>();
248                 for (int i = 0; i < CHECK_COUNT * keys.size(); i++) {
249                     expectedMethods.add("getNumIdle");
250                     expectedMethods.add("addObject");
251                 }
252                 assertEquals(expectedMethods, calledMethods); // may fail because of the thread scheduler
253             } catch (final AssertionFailedError e) {
254                 afe = e;
255             }
256         } while (--triesLeft > 0 && afe != null);
257         if (afe != null) {
258             throw afe;
259         }
260     }
261 
262     @Test
263     public void testCheckMinIdleKeyedObjectPoolKeysNulls() {
264         try (@SuppressWarnings("unchecked")
265         final KeyedObjectPool<Object, Object> pool = createProxy(KeyedObjectPool.class, (List<String>) null)) {
266             assertThrows(IllegalArgumentException.class, () -> PoolUtils.checkMinIdle(pool, (Collection<?>) null, 1, 1),
267                     "PoolUtils.checkMinIdle(KeyedObjectPool,Collection,int,long) must not accept null keys.");
268         }
269 
270         try (@SuppressWarnings("unchecked")
271         final KeyedObjectPool<Object, Object> pool = createProxy(KeyedObjectPool.class, (List<String>) null)) {
272             PoolUtils.checkMinIdle(pool, (Collection<?>) Collections.emptyList(), 1, 1);
273         } catch (final IllegalArgumentException iae) {
274             fail("PoolUtils.checkMinIdle(KeyedObjectPool,Collection,int,long) must accept empty lists.");
275         }
276     }
277 
278     @Test
279     public void testCheckMinIdleObjectPool() throws InterruptedException {
280         assertThrows(IllegalArgumentException.class, () -> PoolUtils.checkMinIdle(null, 1, 1),
281                 "PoolUtils.checkMinIdle(ObjectPool,,) must not allow null pool.");
282         try (@SuppressWarnings("unchecked")
283         final ObjectPool<Object> pool = createProxy(ObjectPool.class, (List<String>) null)) {
284             assertThrows(IllegalArgumentException.class, () -> PoolUtils.checkMinIdle(pool, -1, 1),
285                     "PoolUtils.checkMinIdle(ObjectPool,,) must not accept negative min idle values.");
286         }
287 
288         final List<String> calledMethods = new ArrayList<>();
289 
290         // Test that the minIdle check doesn't add too many idle objects
291         @SuppressWarnings("unchecked")
292         final PooledObjectFactory<Object> pof = createProxy(PooledObjectFactory.class, calledMethods);
293         try (final ObjectPool<Object> op = new GenericObjectPool<>(pof)) {
294             PoolUtils.checkMinIdle(op, 2, 100);
295             Thread.sleep(1000);
296             assertEquals(2, op.getNumIdle());
297         }
298         int makeObjectCount = 0;
299         for (String methodName : calledMethods) {
300             if ("makeObject".equals(methodName)) {
301                 makeObjectCount++;
302             }
303         }
304         assertEquals(2, makeObjectCount, "makeObject should have been called two time");
305 
306         // Because this isn't deterministic and you can get false failures, try more than once.
307         AssertionFailedError afe = null;
308         int triesLeft = 3;
309         do {
310             afe = null;
311             try {
312                 calledMethods.clear();
313                 try (@SuppressWarnings("unchecked")
314                 final ObjectPool<Object> pool = createProxy(ObjectPool.class, calledMethods)) {
315                     final TimerTask task = PoolUtils.checkMinIdle(pool, 1, CHECK_PERIOD); // checks minIdle immediately
316 
317                     Thread.sleep(CHECK_SLEEP_PERIOD); // will check CHECK_COUNT more times.
318                     task.cancel();
319                     task.toString();
320 
321                     final List<String> expectedMethods = new ArrayList<>();
322                     for (int i = 0; i < CHECK_COUNT; i++) {
323                         expectedMethods.add("getNumIdle");
324                         expectedMethods.add("addObject");
325                     }
326                     expectedMethods.add("toString");
327                     assertEquals(expectedMethods, calledMethods); // may fail because of the thread scheduler
328                 }
329             } catch (final AssertionFailedError e) {
330                 afe = e;
331             }
332         } while (--triesLeft > 0 && afe != null);
333         if (afe != null) {
334             throw afe;
335         }
336     }
337 
338     @Test
339     public void testCheckRethrow() {
340         try {
341             PoolUtils.checkRethrow(new Exception());
342         } catch (final Throwable t) {
343             fail("PoolUtils.checkRethrow(Throwable) must rethrow only ThreadDeath and VirtualMachineError.");
344         }
345         try {
346             PoolUtils.checkRethrow(new ThreadDeath());
347             fail("PoolUtils.checkRethrow(Throwable) must rethrow ThreadDeath.");
348         } catch (final ThreadDeath td) {
349             // expected
350         } catch (final Throwable t) {
351             fail("PoolUtils.checkRethrow(Throwable) must rethrow only ThreadDeath and VirtualMachineError.");
352         }
353         try {
354             PoolUtils.checkRethrow(new InternalError()); // InternalError extends VirtualMachineError
355             fail("PoolUtils.checkRethrow(Throwable) must rethrow VirtualMachineError.");
356         } catch (final VirtualMachineError td) {
357             // expected
358         } catch (final Throwable t) {
359             fail("PoolUtils.checkRethrow(Throwable) must rethrow only ThreadDeath and VirtualMachineError.");
360         }
361     }
362 
363     @Test
364     public void testErodingObjectPoolDefaultFactor() {
365         try (@SuppressWarnings("unchecked")
366         final ObjectPool<Object> internalPool = createProxy(ObjectPool.class, (arg0, arg1, arg2) -> null);
367                 final ObjectPool<Object> pool = PoolUtils.erodingPool(internalPool)) {
368             final String expectedToString = "ErodingObjectPool{factor=ErodingFactor{factor=1.0, idleHighWaterMark=1}, pool=" +
369                     internalPool + "}";
370             // The factor is not exposed, but will be printed in the toString() method
371             // In this case since we didn't pass one, the default 1.0f will be printed
372             assertEquals(expectedToString, pool.toString());
373         }
374     }
375 
376     @Test
377     public void testErodingPerKeyKeyedObjectPool() throws Exception {
378         assertThrows(IllegalArgumentException.class, () -> PoolUtils.erodingPool((KeyedObjectPool<Object, Object>) null, 1f, true),
379                 "PoolUtils.erodingPool(KeyedObjectPool) must not allow a null pool.");
380 
381         assertThrows(IllegalArgumentException.class, () -> PoolUtils.erodingPool((KeyedObjectPool<Object, Object>) null, 0f, true),
382                 "PoolUtils.erodingPool(ObjectPool, float, boolean) must not allow a non-positive factor.");
383 
384         assertThrows(IllegalArgumentException.class, () -> PoolUtils.erodingPool((KeyedObjectPool<Object, Object>) null, 1f, true),
385                 "PoolUtils.erodingPool(KeyedObjectPool, float, boolean) must not allow a null pool.");
386 
387         final List<String> calledMethods = new ArrayList<>();
388         final InvocationHandler handler = new MethodCallLogger(calledMethods) {
389             @Override
390             public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
391                 Object o = super.invoke(proxy, method, args);
392                 if (o instanceof Integer) {
393                     // so getNumActive/getNumIdle are not zero.
394                     o = Integer.valueOf(1);
395                 }
396                 return o;
397             }
398         };
399 
400         // If the logic behind PoolUtils.erodingPool changes then this will need to be tweaked.
401         final float factor = 0.01f; // about ~9 seconds until first discard
402         try (@SuppressWarnings("unchecked")
403         final KeyedObjectPool<Object, Object> pool = PoolUtils.erodingPool(createProxy(KeyedObjectPool.class, handler), factor, true)) {
404 
405             final List<String> expectedMethods = new ArrayList<>();
406             assertEquals(expectedMethods, calledMethods);
407 
408             final Object key = "key";
409 
410             Object o = pool.borrowObject(key);
411             expectedMethods.add("borrowObject");
412 
413             assertEquals(expectedMethods, calledMethods);
414 
415             pool.returnObject(key, o);
416             expectedMethods.add("returnObject");
417             assertEquals(expectedMethods, calledMethods);
418 
419             for (int i = 0; i < 5; i++) {
420                 o = pool.borrowObject(key);
421                 expectedMethods.add("borrowObject");
422 
423                 Thread.sleep(50);
424 
425                 pool.returnObject(key, o);
426                 expectedMethods.add("returnObject");
427 
428                 assertEquals(expectedMethods, calledMethods);
429 
430                 expectedMethods.clear();
431                 calledMethods.clear();
432             }
433 
434             Thread.sleep(10000); // 10 seconds
435 
436             o = pool.borrowObject(key);
437             expectedMethods.add("borrowObject");
438             pool.returnObject(key, o);
439             expectedMethods.add("getNumIdle");
440             expectedMethods.add("invalidateObject");
441             assertEquals(expectedMethods, calledMethods);
442 
443             final String expectedToString = "ErodingPerKeyKeyedObjectPool{factor=" + factor + ", keyedPool=null}";
444             assertEquals(expectedToString, pool.toString());
445         }
446     }
447 
448     @Test
449     public void testErodingPoolKeyedObjectPool() throws Exception {
450         assertThrows(IllegalArgumentException.class, () -> PoolUtils.erodingPool((KeyedObjectPool<Object, Object>) null),
451                 "PoolUtils.erodingPool(KeyedObjectPool) must not allow a null pool.");
452 
453         assertThrows(IllegalArgumentException.class, () -> PoolUtils.erodingPool((KeyedObjectPool<Object, Object>) null, 1f),
454                 "PoolUtils.erodingPool(KeyedObjectPool, float) must not allow a null pool.");
455 
456         assertThrows(IllegalArgumentException.class, () -> PoolUtils.erodingPool((KeyedObjectPool<Object, Object>) null, 1f, true),
457                 "PoolUtils.erodingPool(KeyedObjectPool, float, boolean) must not allow a null pool.");
458 
459         final List<String> calledMethods = new ArrayList<>();
460         final InvocationHandler handler = new MethodCallLogger(calledMethods) {
461             @Override
462             public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
463                 Object o = super.invoke(proxy, method, args);
464                 if (o instanceof Integer) {
465                     // so getNumActive/getNumIdle are not zero.
466                     o = Integer.valueOf(1);
467                 }
468                 return o;
469             }
470         };
471 
472         assertThrows(IllegalArgumentException.class, () -> PoolUtils.erodingPool(createProxy(KeyedObjectPool.class, handler), 0f),
473                 "PoolUtils.erodingPool(ObjectPool, float) must not allow a non-positive factor.");
474 
475         assertThrows(IllegalArgumentException.class, () -> PoolUtils.erodingPool(createProxy(KeyedObjectPool.class, handler), 0f, false),
476                 "PoolUtils.erodingPool(ObjectPool, float, boolean) must not allow a non-positive factor.");
477 
478         // If the logic behind PoolUtils.erodingPool changes then this will need to be tweaked.
479         final float factor = 0.01f; // about ~9 seconds until first discard
480         final List<String> expectedMethods = new ArrayList<>();
481         try (@SuppressWarnings("unchecked")
482         final KeyedObjectPool<Object, Object> pool = PoolUtils.erodingPool(createProxy(KeyedObjectPool.class, handler), factor)) {
483 
484             assertEquals(expectedMethods, calledMethods);
485 
486             final Object key = "key";
487 
488             pool.addObject(key);
489             expectedMethods.add("addObject");
490 
491             Object o = pool.borrowObject(key);
492             expectedMethods.add("borrowObject");
493 
494             assertEquals(expectedMethods, calledMethods);
495 
496             pool.returnObject(key, o);
497             expectedMethods.add("returnObject");
498             assertEquals(expectedMethods, calledMethods);
499 
500             // the invocation handler always returns 1
501             assertEquals(1, pool.getNumActive());
502             expectedMethods.add("getNumActive");
503             assertEquals(1, pool.getNumIdle());
504             expectedMethods.add("getNumIdle");
505 
506             for (int i = 0; i < 5; i++) {
507                 o = pool.borrowObject(key);
508                 expectedMethods.add("borrowObject");
509 
510                 Thread.sleep(50);
511 
512                 pool.returnObject(key, o);
513                 expectedMethods.add("returnObject");
514 
515                 assertEquals(expectedMethods, calledMethods);
516 
517                 expectedMethods.clear();
518                 calledMethods.clear();
519             }
520 
521             Thread.sleep(10000); // 10 seconds
522 
523             o = pool.borrowObject(key);
524             expectedMethods.add("borrowObject");
525             pool.returnObject(key, o);
526             expectedMethods.add("getNumIdle");
527             expectedMethods.add("invalidateObject");
528             pool.clear();
529         }
530         expectedMethods.add("clear");
531         expectedMethods.add("close");
532         assertEquals(expectedMethods, calledMethods);
533     }
534 
535     @Test
536     public void testErodingPoolKeyedObjectPoolDefaultFactor() {
537         try (@SuppressWarnings("unchecked")
538         final KeyedObjectPool<Object, Object> internalPool = createProxy(KeyedObjectPool.class,
539                 (arg0, arg1, arg2) -> null);
540                 final KeyedObjectPool<Object, Object> pool = PoolUtils.erodingPool(internalPool)) {
541             final String expectedToString = "ErodingKeyedObjectPool{factor=ErodingFactor{factor=1.0, idleHighWaterMark=1}, keyedPool=" +
542                     internalPool + "}";
543             // The factor is not exposed, but will be printed in the toString() method
544             // In this case since we didn't pass one, the default 1.0f will be printed
545             assertEquals(expectedToString, pool.toString());
546         }
547     }
548 
549     @Test
550     public void testErodingPoolObjectPool() throws Exception {
551         assertThrows(IllegalArgumentException.class, () -> PoolUtils.erodingPool((ObjectPool<Object>) null),
552                 "PoolUtils.erodingPool(ObjectPool) must not allow a null pool.");
553 
554         assertThrows(IllegalArgumentException.class, () -> PoolUtils.erodingPool((ObjectPool<Object>) null, 1f),
555                 "PoolUtils.erodingPool(ObjectPool, float) must not allow a null pool.");
556 
557         final List<String> calledMethods = new ArrayList<>();
558         final InvocationHandler handler = new MethodCallLogger(calledMethods) {
559             @Override
560             public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
561                 Object o = super.invoke(proxy, method, args);
562                 if (o instanceof Integer) {
563                     // so getNumActive/getNumIdle are not zero.
564                     o = Integer.valueOf(1);
565                 }
566                 return o;
567             }
568         };
569 
570         assertThrows(IllegalArgumentException.class, () -> PoolUtils.erodingPool(createProxy(ObjectPool.class, handler), -1f),
571                 "PoolUtils.erodingPool(ObjectPool, float) must not allow a non-positive factor.");
572 
573         // If the logic behind PoolUtils.erodingPool changes then this will need to be tweaked.
574         final float factor = 0.01f; // about ~9 seconds until first discard
575         final List<String> expectedMethods = new ArrayList<>();
576         try (@SuppressWarnings("unchecked")
577         final ObjectPool<Object> pool = PoolUtils.erodingPool(createProxy(ObjectPool.class, handler), factor)) {
578 
579             assertEquals(expectedMethods, calledMethods);
580 
581             pool.addObject();
582             expectedMethods.add("addObject");
583 
584             Object o = pool.borrowObject();
585             expectedMethods.add("borrowObject");
586 
587             assertEquals(expectedMethods, calledMethods);
588 
589             pool.returnObject(o);
590             expectedMethods.add("returnObject");
591             assertEquals(expectedMethods, calledMethods);
592 
593             // the invocation handler always returns 1
594             assertEquals(1, pool.getNumActive());
595             expectedMethods.add("getNumActive");
596             assertEquals(1, pool.getNumIdle());
597             expectedMethods.add("getNumIdle");
598 
599             for (int i = 0; i < 5; i++) {
600                 o = pool.borrowObject();
601                 expectedMethods.add("borrowObject");
602 
603                 Thread.sleep(50);
604 
605                 pool.returnObject(o);
606                 expectedMethods.add("returnObject");
607 
608                 assertEquals(expectedMethods, calledMethods);
609 
610                 expectedMethods.clear();
611                 calledMethods.clear();
612             }
613 
614             Thread.sleep(10000); // 10 seconds
615 
616             o = pool.borrowObject();
617             expectedMethods.add("borrowObject");
618             pool.returnObject(o);
619             expectedMethods.add("getNumIdle");
620             expectedMethods.add("invalidateObject");
621             pool.clear();
622         }
623         expectedMethods.add("clear");
624         expectedMethods.add("close");
625         assertEquals(expectedMethods, calledMethods);
626     }
627 
628     @Test
629     public void testJavaBeanInstantiation() {
630         assertNotNull(new PoolUtils());
631     }
632 
633     @SuppressWarnings("deprecation")
634     @Test
635     public void testPrefillKeyedObjectPool() throws Exception {
636         assertThrows(IllegalArgumentException.class, () -> PoolUtils.prefill(null, new Object(), 1),
637                 "PoolUtils.prefill(KeyedObjectPool,Object,int) must not accept null pool.");
638 
639         try (final KeyedObjectPool<Object, String> pool = new GenericKeyedObjectPool<>(new TestGenericKeyedObjectPool.SimpleFactory<>())) {
640             assertThrows(IllegalArgumentException.class, () -> PoolUtils.prefill(pool, (Object) null, 1),
641                     "PoolUtils.prefill(KeyedObjectPool,Object,int) must not accept null key.");
642         }
643 
644         final List<String> calledMethods = new ArrayList<>();
645         try (@SuppressWarnings("unchecked")
646         final KeyedObjectPool<Object, Object> pool = createProxy(KeyedObjectPool.class, calledMethods)) {
647 
648             PoolUtils.prefill(pool, new Object(), 0);
649             final List<String> expectedMethods = new ArrayList<>();
650             expectedMethods.add("addObjects");
651             assertEquals(expectedMethods, calledMethods);
652 
653             calledMethods.clear();
654             PoolUtils.prefill(pool, new Object(), 3);
655             assertEquals(expectedMethods, calledMethods);
656         }
657     }
658 
659     @SuppressWarnings("deprecation")
660     @Test
661     public void testPrefillKeyedObjectPoolCollection() throws Exception {
662         try (@SuppressWarnings("unchecked")
663         final KeyedObjectPool<String, String> pool = createProxy(KeyedObjectPool.class, (List<String>) null)) {
664             assertThrows(IllegalArgumentException.class, () -> PoolUtils.prefill(pool, (Collection<String>) null, 1),
665                     "PoolUtils.prefill(KeyedObjectPool,Collection,int) must not accept null keys.");
666         }
667 
668         final List<String> calledMethods = new ArrayList<>();
669         try (@SuppressWarnings("unchecked")
670             final KeyedObjectPool<String, Object> pool = createProxy(KeyedObjectPool.class, calledMethods)) {
671 
672             final Set<String> keys = new HashSet<>();
673             PoolUtils.prefill(pool, keys, 0);
674             final List<String> expectedMethods = new ArrayList<>();
675             expectedMethods.add("addObjects");
676             assertEquals(expectedMethods, calledMethods);
677 
678             calledMethods.clear();
679             keys.add("one");
680             keys.add("two");
681             keys.add("three");
682             final int count = 3;
683             PoolUtils.prefill(pool, keys, count);
684             assertEquals(expectedMethods, calledMethods);
685         }
686     }
687 
688     @SuppressWarnings("deprecation")
689     @Test
690     public void testPrefillObjectPool() throws Exception {
691         assertThrows(IllegalArgumentException.class, () -> PoolUtils.prefill(null, 1), "PoolUtils.prefill(ObjectPool,int) must not allow null pool.");
692 
693         final List<String> calledMethods = new ArrayList<>();
694         try (@SuppressWarnings("unchecked")
695         final ObjectPool<Object> pool = createProxy(ObjectPool.class, calledMethods)) {
696 
697             PoolUtils.prefill(pool, 0);
698             final List<String> expectedMethods = new ArrayList<>();
699             expectedMethods.add("addObjects");
700             assertEquals(expectedMethods, calledMethods);
701 
702             calledMethods.clear();
703             PoolUtils.prefill(pool, 3);
704             assertEquals(expectedMethods, calledMethods);
705         }
706     }
707 
708     @Test
709     public void testSynchronizedPoolableFactoryKeyedPooledObjectFactory() throws Exception {
710         assertThrows(IllegalArgumentException.class,
711             () -> PoolUtils.synchronizedKeyedPooledFactory((KeyedPooledObjectFactory<Object, Object>) null),
712             "PoolUtils.synchronizedPoolableFactory(KeyedPooledObjectFactory) must not allow a null factory.");
713 
714         final List<String> calledMethods = new ArrayList<>();
715         @SuppressWarnings("unchecked")
716         final KeyedPooledObjectFactory<Object, Object> kpof = createProxy(KeyedPooledObjectFactory.class, calledMethods);
717 
718         final KeyedPooledObjectFactory<Object, Object> skpof = PoolUtils.synchronizedKeyedPooledFactory(kpof);
719         final List<String> expectedMethods = invokeEveryMethod(skpof);
720         assertEquals(expectedMethods, calledMethods);
721 
722         // TODO: Anyone feel motivated to construct a test that verifies proper synchronization?
723     }
724 
725     @Test
726     public void testSynchronizedPoolableFactoryPoolableObjectFactory() throws Exception {
727         assertThrows(IllegalArgumentException.class, () -> PoolUtils.synchronizedPooledFactory((PooledObjectFactory<Object>) null),
728                 "PoolUtils.synchronizedPoolableFactory(PoolableObjectFactory) must not allow a null factory.");
729 
730         final List<String> calledMethods = new ArrayList<>();
731         @SuppressWarnings("unchecked")
732         final PooledObjectFactory<Object> pof = createProxy(PooledObjectFactory.class, calledMethods);
733 
734         final PooledObjectFactory<Object> spof = PoolUtils.synchronizedPooledFactory(pof);
735         final List<String> expectedMethods = invokeEveryMethod(spof);
736         assertEquals(expectedMethods, calledMethods);
737 
738         // TODO: Anyone feel motivated to construct a test that verifies proper synchronization?
739     }
740 
741     @Test
742     public void testSynchronizedPoolKeyedObjectPool() throws Exception {
743         assertThrows(IllegalArgumentException.class, () -> PoolUtils.synchronizedPool((KeyedObjectPool<Object, Object>) null),
744                 "PoolUtils.synchronizedPool(KeyedObjectPool) must not allow a null pool.");
745 
746         final List<String> calledMethods = new ArrayList<>();
747         try (@SuppressWarnings("unchecked")
748             final KeyedObjectPool<Object, Object> kop = createProxy(KeyedObjectPool.class, calledMethods);
749             final KeyedObjectPool<Object, Object> skop = PoolUtils.synchronizedPool(kop)) {
750             final List<String> expectedMethods = invokeEveryMethod(skop);
751             assertEquals(expectedMethods, calledMethods);
752         }
753 
754         // TODO: Anyone feel motivated to construct a test that verifies proper synchronization?
755     }
756 
757     @Test
758     public void testSynchronizedPoolObjectPool() throws Exception {
759         assertThrows(IllegalArgumentException.class, () -> PoolUtils.synchronizedPool((ObjectPool<Object>) null),
760                 "PoolUtils.synchronizedPool(ObjectPool) must not allow a null pool.");
761 
762         final List<String> calledMethods = new ArrayList<>();
763         try (@SuppressWarnings("unchecked")
764         final ObjectPool<Object> op = createProxy(ObjectPool.class, calledMethods); final ObjectPool<Object> sop = PoolUtils.synchronizedPool(op)) {
765             final List<String> expectedMethods = invokeEveryMethod(sop);
766             assertEquals(expectedMethods, calledMethods);
767 
768             // TODO: Anyone feel motivated to construct a test that verifies proper synchronization?
769         }
770     }
771 
772     /**
773      * Tests the {@link PoolUtils} timer holder.
774      */
775     @Test
776     public void testTimerHolder() {
777         final PoolUtils.TimerHolder h = new PoolUtils.TimerHolder();
778         assertNotNull(h);
779         assertNotNull(PoolUtils.TimerHolder.MIN_IDLE_TIMER);
780     }
781 }