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