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