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    *      https://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  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     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             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             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 (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 (final 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                     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     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                 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     void testCheckMinIdleKeyedObjectPoolKeysNulls() {
264         try (@SuppressWarnings("unchecked")
265             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             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     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             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 (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 (final 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                     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     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     void testErodingObjectPoolDefaultFactor() {
365         try (@SuppressWarnings("unchecked")
366             ObjectPool<Object> internalPool = createProxy(ObjectPool.class, (arg0, arg1, arg2) -> null);
367                 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     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             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     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             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     void testErodingPoolKeyedObjectPoolDefaultFactor() {
537         try (@SuppressWarnings("unchecked")
538             KeyedObjectPool<Object, Object> internalPool = createProxy(KeyedObjectPool.class,
539                 (arg0, arg1, arg2) -> null);
540                 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     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             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     void testJavaBeanInstantiation() {
630         assertNotNull(new PoolUtils());
631     }
632 
633     @SuppressWarnings("deprecation")
634     @Test
635     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         try (KeyedObjectPool<Object, String> pool = new GenericKeyedObjectPool<>(new TestGenericKeyedObjectPool.SimpleFactory<>())) {
639             assertThrows(IllegalArgumentException.class, () -> PoolUtils.prefill(pool, (Object) null, 1),
640                     "PoolUtils.prefill(KeyedObjectPool,Object,int) must not accept null key.");
641         }
642         final List<String> calledMethods = new ArrayList<>();
643         try (@SuppressWarnings("unchecked")
644         KeyedObjectPool<Object, Object> pool = createProxy(KeyedObjectPool.class, calledMethods)) {
645             PoolUtils.prefill(pool, new Object(), 0);
646             final List<String> expectedMethods = new ArrayList<>();
647             expectedMethods.add("addObjects");
648             assertEquals(expectedMethods, calledMethods);
649             calledMethods.clear();
650             PoolUtils.prefill(pool, new Object(), 3);
651             assertEquals(expectedMethods, calledMethods);
652         }
653     }
654 
655     @SuppressWarnings("deprecation")
656     @Test
657     void testPrefillKeyedObjectPoolCollection() throws Exception {
658         try (@SuppressWarnings("unchecked")
659         KeyedObjectPool<String, String> pool = createProxy(KeyedObjectPool.class, (List<String>) null)) {
660             assertThrows(IllegalArgumentException.class, () -> PoolUtils.prefill(pool, (Collection<String>) null, 1),
661                     "PoolUtils.prefill(KeyedObjectPool,Collection,int) must not accept null keys.");
662         }
663         final List<String> calledMethods = new ArrayList<>();
664         try (@SuppressWarnings("unchecked")
665         KeyedObjectPool<String, Object> pool = createProxy(KeyedObjectPool.class, calledMethods)) {
666             final Set<String> keys = new HashSet<>();
667             PoolUtils.prefill(pool, keys, 0);
668             final List<String> expectedMethods = new ArrayList<>();
669             expectedMethods.add("addObjects");
670             assertEquals(expectedMethods, calledMethods);
671             calledMethods.clear();
672             keys.add("one");
673             keys.add("two");
674             keys.add("three");
675             final int count = 3;
676             PoolUtils.prefill(pool, keys, count);
677             assertEquals(expectedMethods, calledMethods);
678         }
679     }
680 
681     @SuppressWarnings("deprecation")
682     @Test
683     void testPrefillObjectPool() throws Exception {
684         assertThrows(IllegalArgumentException.class, () -> PoolUtils.prefill(null, 1), "PoolUtils.prefill(ObjectPool,int) must not allow null pool.");
685         final List<String> calledMethods = new ArrayList<>();
686         try (@SuppressWarnings("unchecked")
687         ObjectPool<Object> pool = createProxy(ObjectPool.class, calledMethods)) {
688             PoolUtils.prefill(pool, 0);
689             final List<String> expectedMethods = new ArrayList<>();
690             expectedMethods.add("addObjects");
691             assertEquals(expectedMethods, calledMethods);
692             calledMethods.clear();
693             PoolUtils.prefill(pool, 3);
694             assertEquals(expectedMethods, calledMethods);
695         }
696     }
697 
698     @Test
699     void testSynchronizedPoolableFactoryKeyedPooledObjectFactory() throws Exception {
700         assertThrows(IllegalArgumentException.class,
701             () -> PoolUtils.synchronizedKeyedPooledFactory((KeyedPooledObjectFactory<Object, Object>) null),
702             "PoolUtils.synchronizedPoolableFactory(KeyedPooledObjectFactory) must not allow a null factory.");
703 
704         final List<String> calledMethods = new ArrayList<>();
705         @SuppressWarnings("unchecked")
706         final KeyedPooledObjectFactory<Object, Object> kpof = createProxy(KeyedPooledObjectFactory.class, calledMethods);
707 
708         final KeyedPooledObjectFactory<Object, Object> skpof = PoolUtils.synchronizedKeyedPooledFactory(kpof);
709         final List<String> expectedMethods = invokeEveryMethod(skpof);
710         assertEquals(expectedMethods, calledMethods);
711 
712         // TODO: Anyone feel motivated to construct a test that verifies proper synchronization?
713     }
714 
715     @Test
716     void testSynchronizedPoolableFactoryPoolableObjectFactory() throws Exception {
717         assertThrows(IllegalArgumentException.class, () -> PoolUtils.synchronizedPooledFactory((PooledObjectFactory<Object>) null),
718                 "PoolUtils.synchronizedPoolableFactory(PoolableObjectFactory) must not allow a null factory.");
719 
720         final List<String> calledMethods = new ArrayList<>();
721         @SuppressWarnings("unchecked")
722         final PooledObjectFactory<Object> pof = createProxy(PooledObjectFactory.class, calledMethods);
723 
724         final PooledObjectFactory<Object> spof = PoolUtils.synchronizedPooledFactory(pof);
725         final List<String> expectedMethods = invokeEveryMethod(spof);
726         assertEquals(expectedMethods, calledMethods);
727 
728         // TODO: Anyone feel motivated to construct a test that verifies proper synchronization?
729     }
730 
731     @Test
732     void testSynchronizedPoolKeyedObjectPool() throws Exception {
733         assertThrows(IllegalArgumentException.class, () -> PoolUtils.synchronizedPool((KeyedObjectPool<Object, Object>) null),
734                 "PoolUtils.synchronizedPool(KeyedObjectPool) must not allow a null pool.");
735 
736         final List<String> calledMethods = new ArrayList<>();
737         try (@SuppressWarnings("unchecked")
738             KeyedObjectPool<Object, Object> kop = createProxy(KeyedObjectPool.class, calledMethods);
739             KeyedObjectPool<Object, Object> skop = PoolUtils.synchronizedPool(kop)) {
740             final List<String> expectedMethods = invokeEveryMethod(skop);
741             assertEquals(expectedMethods, calledMethods);
742         }
743 
744         // TODO: Anyone feel motivated to construct a test that verifies proper synchronization?
745     }
746 
747     @Test
748     void testSynchronizedPoolObjectPool() throws Exception {
749         assertThrows(IllegalArgumentException.class, () -> PoolUtils.synchronizedPool((ObjectPool<Object>) null),
750                 "PoolUtils.synchronizedPool(ObjectPool) must not allow a null pool.");
751         final List<String> calledMethods = new ArrayList<>();
752         try (@SuppressWarnings("unchecked")
753         ObjectPool<Object> op = createProxy(ObjectPool.class, calledMethods); ObjectPool<Object> sop = PoolUtils.synchronizedPool(op)) {
754             final List<String> expectedMethods = invokeEveryMethod(sop);
755             assertEquals(expectedMethods, calledMethods);
756             // TODO: Anyone feel motivated to construct a test that verifies proper synchronization?
757         }
758     }
759 
760     /**
761      * Tests the {@link PoolUtils} timer holder.
762      */
763     @Test
764     void testTimerHolder() {
765         final PoolUtils.TimerHolder h = new PoolUtils.TimerHolder();
766         assertNotNull(h);
767         assertNotNull(PoolUtils.TimerHolder.MIN_IDLE_TIMER);
768     }
769 }