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  package org.apache.commons.pool2;
18  
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  import static org.junit.jupiter.api.Assertions.assertTrue;
23  
24  import java.util.ArrayList;
25  import java.util.List;
26  import java.util.NoSuchElementException;
27  
28  import org.apache.commons.pool2.impl.GenericObjectPool;
29  import org.apache.commons.pool2.impl.SoftReferenceObjectPool;
30  import org.junit.jupiter.api.Test;
31  
32  /**
33   * Abstract test case for {@link ObjectPool} implementations.
34   */
35  public abstract class AbstractTestObjectPool {
36  
37      private static void clear(final MethodCallPoolableObjectFactory factory, final List<MethodCall> expectedMethods) {
38          factory.getMethodCalls().clear();
39          expectedMethods.clear();
40      }
41  
42      static void removeDestroyObjectCall(final List<MethodCall> calls) {
43          calls.removeIf(call -> "destroyObject".equals(call.getName()));
44      }
45  
46      private static void reset(final ObjectPool<Object> pool, final MethodCallPoolableObjectFactory factory, final List<MethodCall> expectedMethods) throws Exception {
47          pool.clear();
48          clear(factory, expectedMethods);
49          factory.reset();
50      }
51      // Deliberate choice to create a new object in case future unit tests check
52      // for a specific object.
53      private final Integer ZERO = Integer.valueOf(0);
54  
55      private final Integer ONE = Integer.valueOf(1);
56  
57      /**
58       * Create an {@code ObjectPool} with the specified factory.
59       * The pool should be in a default configuration and conform to the expected
60       * behaviors described in {@link ObjectPool}.
61       * Generally speaking there should be no limits on the various object counts.
62       *
63       * @param <E> The exception type throws by the pool
64       * @param factory The factory to be used by the object pool
65       *
66       * @return the newly created empty pool
67       *
68       * @throws UnsupportedOperationException if the pool being tested does not
69       *                                       follow pool contracts.
70       */
71      protected abstract <E extends Exception> ObjectPool<Object> makeEmptyPool(PooledObjectFactory<Object> factory) throws UnsupportedOperationException;
72  
73      @Test
74      public void testClosedPoolBehavior() throws Exception {
75          final ObjectPool<Object> pool;
76          try {
77              pool = makeEmptyPool(new MethodCallPoolableObjectFactory());
78          } catch (final UnsupportedOperationException uoe) {
79              return; // test not supported
80          }
81          final Object o1 = pool.borrowObject();
82          final Object o2 = pool.borrowObject();
83  
84          pool.close();
85  
86          assertThrows(IllegalStateException.class, pool::addObject, "A closed pool must throw an IllegalStateException when addObject is called.");
87  
88          assertThrows(IllegalStateException.class, pool::borrowObject, "A closed pool must throw an IllegalStateException when borrowObject is called.");
89  
90          // The following should not throw exceptions just because the pool is closed.
91          if (pool.getNumIdle() >= 0) {
92              assertEquals(0, pool.getNumIdle(), "A closed pool shouldn't have any idle objects.");
93          }
94          if (pool.getNumActive() >= 0) {
95              assertEquals(2, pool.getNumActive(), "A closed pool should still keep count of active objects.");
96          }
97          pool.returnObject(o1);
98          if (pool.getNumIdle() >= 0) {
99              assertEquals(0, pool.getNumIdle(), "returnObject should not add items back into the idle object pool for a closed pool.");
100         }
101         if (pool.getNumActive() >= 0) {
102             assertEquals(1, pool.getNumActive(), "A closed pool should still keep count of active objects.");
103         }
104         pool.invalidateObject(o2);
105         if (pool.getNumIdle() >= 0) {
106             assertEquals(0, pool.getNumIdle(), "invalidateObject must not add items back into the idle object pool.");
107         }
108         if (pool.getNumActive() >= 0) {
109             assertEquals(0, pool.getNumActive(), "A closed pool should still keep count of active objects.");
110         }
111         pool.clear();
112         pool.close();
113     }
114 
115     @Test
116     public void testPOFAddObjectUsage() throws Exception {
117         final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
118         final ObjectPool<Object> pool;
119         try {
120             pool = makeEmptyPool(factory);
121         } catch (final UnsupportedOperationException uoe) {
122             return; // test not supported
123         }
124         final List<MethodCall> expectedMethods = new ArrayList<>();
125 
126         assertEquals(0, pool.getNumActive());
127         assertEquals(0, pool.getNumIdle());
128         // addObject should make a new object, passivate it and put it in the pool
129         pool.addObject();
130         assertEquals(0, pool.getNumActive());
131         assertEquals(1, pool.getNumIdle());
132         expectedMethods.add(new MethodCall("makeObject").returned(ZERO));
133         // StackObjectPool, SoftReferenceObjectPool also validate on add
134         if (pool instanceof SoftReferenceObjectPool) {
135             expectedMethods.add(new MethodCall(
136                     "validateObject", ZERO).returned(Boolean.TRUE));
137         }
138         expectedMethods.add(new MethodCall("passivateObject", ZERO));
139         assertEquals(expectedMethods, factory.getMethodCalls());
140 
141         // Test exception handling of addObject
142         reset(pool, factory, expectedMethods);
143 
144         // makeObject Exceptions should be propagated to client code from addObject
145         factory.setMakeObjectFail(true);
146         assertThrows(PrivateException.class, pool::addObject, "Expected addObject to propagate makeObject exception.");
147         expectedMethods.add(new MethodCall("makeObject"));
148         assertEquals(expectedMethods, factory.getMethodCalls());
149 
150         clear(factory, expectedMethods);
151 
152         // passivateObject Exceptions should be propagated to client code from addObject
153         factory.setMakeObjectFail(false);
154         factory.setPassivateObjectFail(true);
155         assertThrows(PrivateException.class, pool::addObject, "Expected addObject to propagate passivateObject exception.");
156         expectedMethods.add(new MethodCall("makeObject").returned(ONE));
157         // StackObjectPool, SofReferenceObjectPool also validate on add
158         if (pool instanceof SoftReferenceObjectPool) {
159             expectedMethods.add(new MethodCall("validateObject", ONE).returned(Boolean.TRUE));
160         }
161         expectedMethods.add(new MethodCall("passivateObject", ONE));
162         assertEquals(expectedMethods, factory.getMethodCalls());
163         pool.close();
164     }
165 
166     @Test
167     public void testPOFBorrowObjectUsages() throws Exception {
168         final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
169         final ObjectPool<Object> pool;
170         try {
171             pool = makeEmptyPool(factory);
172         } catch (final UnsupportedOperationException uoe) {
173             return; // test not supported
174         }
175         if (pool instanceof GenericObjectPool) {
176             ((GenericObjectPool<Object>) pool).setTestOnBorrow(true);
177         }
178         final List<MethodCall> expectedMethods = new ArrayList<>();
179         Object obj;
180 
181         // Test correct behavior code paths
182 
183         // existing idle object should be activated and validated
184         pool.addObject();
185         clear(factory, expectedMethods);
186         obj = pool.borrowObject();
187         expectedMethods.add(new MethodCall("activateObject", ZERO));
188         expectedMethods.add(new MethodCall("validateObject", ZERO).returned(Boolean.TRUE));
189         assertEquals(expectedMethods, factory.getMethodCalls());
190         pool.returnObject(obj);
191 
192         // Test exception handling of borrowObject
193         reset(pool, factory, expectedMethods);
194 
195         // makeObject Exceptions should be propagated to client code from borrowObject
196         factory.setMakeObjectFail(true);
197         assertThrows(PrivateException.class, pool::borrowObject, "Expected borrowObject to propagate makeObject exception.");
198         expectedMethods.add(new MethodCall("makeObject"));
199         assertEquals(expectedMethods, factory.getMethodCalls());
200 
201 
202         // when activateObject fails in borrowObject, a new object should be borrowed/created
203         reset(pool, factory, expectedMethods);
204         pool.addObject();
205         clear(factory, expectedMethods);
206 
207         factory.setActivateObjectFail(true);
208         expectedMethods.add(new MethodCall("activateObject", obj));
209         // Expected NoSuchElementException - newly created object will also fail to activate
210         assertThrows(NoSuchElementException.class, pool::borrowObject, "Expecting NoSuchElementException");
211         // Idle object fails activation, new one created, also fails
212         expectedMethods.add(new MethodCall("makeObject").returned(ONE));
213         expectedMethods.add(new MethodCall("activateObject", ONE));
214         removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
215         assertEquals(expectedMethods, factory.getMethodCalls());
216 
217         // when validateObject fails in borrowObject, a new object should be borrowed/created
218         reset(pool, factory, expectedMethods);
219         pool.addObject();
220         clear(factory, expectedMethods);
221 
222         factory.setValidateObjectFail(true);
223         expectedMethods.add(new MethodCall("activateObject", ZERO));
224         expectedMethods.add(new MethodCall("validateObject", ZERO));
225         // Expected NoSuchElementException - newly created object will also fail to validate
226         assertThrows(NoSuchElementException.class, pool::borrowObject, "Expecting NoSuchElementException");
227         // Idle object is activated, but fails validation.
228         // New instance is created, activated and then fails validation
229         expectedMethods.add(new MethodCall("makeObject").returned(ONE));
230         expectedMethods.add(new MethodCall("activateObject", ONE));
231         expectedMethods.add(new MethodCall("validateObject", ONE));
232         removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
233         // Second activate and validate are missing from expectedMethods
234         assertTrue(factory.getMethodCalls().containsAll(expectedMethods));
235         pool.close();
236     }
237 
238     @Test
239     public void testPOFClearUsages() throws Exception {
240         final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
241         final ObjectPool<Object> pool;
242         try {
243             pool = makeEmptyPool(factory);
244         } catch (final UnsupportedOperationException uoe) {
245             return; // test not supported
246         }
247         final List<MethodCall> expectedMethods = new ArrayList<>();
248 
249         // Test correct behavior code paths
250         pool.addObjects(5);
251         pool.clear();
252 
253         // Test exception handling clear should swallow destroy object failures
254         reset(pool, factory, expectedMethods);
255         factory.setDestroyObjectFail(true);
256         pool.addObjects(5);
257         pool.clear();
258         pool.close();
259     }
260 
261     @Test
262     public void testPOFCloseUsages() throws Exception {
263         final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
264         ObjectPool<Object> pool;
265         try {
266             pool = makeEmptyPool(factory);
267         } catch (final UnsupportedOperationException uoe) {
268             return; // test not supported
269         }
270         final List<MethodCall> expectedMethods = new ArrayList<>();
271 
272         // Test correct behavior code paths
273         pool.addObjects(5);
274         pool.close();
275 
276 
277         // Test exception handling close should swallow failures
278         try {
279             pool = makeEmptyPool(factory);
280         } catch (final UnsupportedOperationException uoe) {
281             return; // test not supported
282         }
283         reset(pool, factory, expectedMethods);
284         factory.setDestroyObjectFail(true);
285         pool.addObjects(5);
286         pool.close();
287     }
288 
289     @Test
290     public void testPOFInvalidateObjectUsages() throws Exception {
291         final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
292         final ObjectPool<Object> pool;
293         try {
294             pool = makeEmptyPool(factory);
295         } catch (final UnsupportedOperationException uoe) {
296             return; // test not supported
297         }
298         final List<MethodCall> expectedMethods = new ArrayList<>();
299         Object obj;
300 
301         // Test correct behavior code paths
302 
303         obj = pool.borrowObject();
304         clear(factory, expectedMethods);
305 
306         // invalidated object should be destroyed
307         pool.invalidateObject(obj);
308         expectedMethods.add(new MethodCall("destroyObject", obj));
309         assertEquals(expectedMethods, factory.getMethodCalls());
310 
311         // Test exception handling of invalidateObject
312         reset(pool, factory, expectedMethods);
313         final Object obj2 = pool.borrowObject();
314         clear(factory, expectedMethods);
315         factory.setDestroyObjectFail(true);
316         assertThrows(PrivateException.class, () -> pool.invalidateObject(obj2));
317         Thread.sleep(250); // could be deferred
318         removeDestroyObjectCall(factory.getMethodCalls());
319         assertEquals(expectedMethods, factory.getMethodCalls());
320         pool.close();
321     }
322 
323     @Test
324     public void testPOFReturnObjectUsages() throws Exception {
325         final MethodCallPoolableObjectFactory factory = new MethodCallPoolableObjectFactory();
326         final ObjectPool<Object> pool;
327         try {
328             pool = makeEmptyPool(factory);
329         } catch (final UnsupportedOperationException uoe) {
330             return; // test not supported
331         }
332         final List<MethodCall> expectedMethods = new ArrayList<>();
333         Object obj;
334 
335         // Test correct behavior code paths
336         obj = pool.borrowObject();
337         clear(factory, expectedMethods);
338 
339         // returned object should be passivated
340         pool.returnObject(obj);
341         // StackObjectPool, SoftReferenceObjectPool also validate on return
342         if (pool instanceof SoftReferenceObjectPool) {
343             expectedMethods.add(new MethodCall(
344                     "validateObject", obj).returned(Boolean.TRUE));
345         }
346         expectedMethods.add(new MethodCall("passivateObject", obj));
347         assertEquals(expectedMethods, factory.getMethodCalls());
348 
349         // Test exception handling of returnObject
350         reset(pool, factory, expectedMethods);
351         pool.addObject();
352         pool.addObject();
353         pool.addObject();
354         assertEquals(3, pool.getNumIdle());
355         // passivateObject should swallow exceptions and not add the object to the pool
356         obj = pool.borrowObject();
357         pool.borrowObject();
358         assertEquals(1, pool.getNumIdle());
359         assertEquals(2, pool.getNumActive());
360         clear(factory, expectedMethods);
361         factory.setPassivateObjectFail(true);
362         pool.returnObject(obj);
363         // StackObjectPool, SoftReferenceObjectPool also validate on return
364         if (pool instanceof SoftReferenceObjectPool) {
365             expectedMethods.add(new MethodCall("validateObject", obj).returned(Boolean.TRUE));
366         }
367         expectedMethods.add(new MethodCall("passivateObject", obj));
368         removeDestroyObjectCall(factory.getMethodCalls()); // The exact timing of destroyObject is flexible here.
369         assertEquals(expectedMethods, factory.getMethodCalls());
370         assertEquals(1, pool.getNumIdle());   // Not returned
371         assertEquals(1, pool.getNumActive()); // But not in active count
372 
373         // destroyObject should swallow exceptions too
374         reset(pool, factory, expectedMethods);
375         obj = pool.borrowObject();
376         clear(factory, expectedMethods);
377         factory.setPassivateObjectFail(true);
378         factory.setDestroyObjectFail(true);
379         pool.returnObject(obj);
380         pool.close();
381     }
382 
383     @Test
384     public void testToString() {
385         final ObjectPool<Object> pool;
386         try {
387             pool = makeEmptyPool(new MethodCallPoolableObjectFactory());
388         } catch (final UnsupportedOperationException uoe) {
389             return; // test not supported
390         }
391         pool.toString();
392         pool.close();
393     }
394 }