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.lang3.concurrent;
18  
19  import static org.hamcrest.CoreMatchers.instanceOf;
20  import static org.hamcrest.MatcherAssert.assertThat;
21  import static org.junit.jupiter.api.Assertions.assertEquals;
22  import static org.junit.jupiter.api.Assertions.assertFalse;
23  import static org.junit.jupiter.api.Assertions.assertThrows;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  import static org.junit.jupiter.api.Assertions.fail;
26  
27  import java.io.IOException;
28  import java.sql.SQLException;
29  
30  import org.apache.commons.lang3.function.FailableConsumer;
31  import org.apache.commons.lang3.function.FailableSupplier;
32  import org.junit.jupiter.api.Test;
33  
34  /**
35   * An abstract base class for tests of exceptions thrown during initialize and close methods
36   * on concrete {@code ConcurrentInitializer} implementations.
37   *
38   * This class provides some basic tests for initializer implementations. Derived
39   * class have to create a {@link ConcurrentInitializer} object on which the
40   * tests are executed.
41   */
42  public abstract class AbstractConcurrentInitializerCloseAndExceptionsTest extends AbstractConcurrentInitializerTest {
43  
44      protected static final class CloseableObject {
45          boolean closed;
46  
47          public void close() {
48              closed = true;
49          }
50  
51          public boolean isClosed() {
52              return closed;
53          }
54      }
55  
56      protected enum ExceptionToThrow {
57          IOException,
58          SQLException,
59          NullPointerException
60      }
61  
62      // The use of enums rather than accepting an Exception as the input means we can have
63      // multiple exception types on the method signature.
64      protected static CloseableObject methodThatThrowsException(ExceptionToThrow input) throws IOException, SQLException, ConcurrentException {
65          switch (input) {
66          case IOException:
67              throw new IOException();
68          case SQLException:
69              throw new SQLException();
70          case NullPointerException:
71              throw new NullPointerException();
72          default:
73              fail();
74              return new CloseableObject();
75          }
76      }
77  
78      protected abstract ConcurrentInitializer<CloseableObject> createInitializerThatThrowsException(
79              FailableSupplier<CloseableObject, ? extends Exception> supplier, FailableConsumer<CloseableObject, ? extends Exception> closer);
80  
81      /**
82       * This method tests that if AbstractConcurrentInitializer.close catches a
83       * ConcurrentException it will rethrow it wrapped in a ConcurrentException
84       */
85      @SuppressWarnings("rawtypes")
86      @Test
87      public void testCloserThrowsCheckedException() throws ConcurrentException {
88          final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
89                  CloseableObject::new,
90                  (CloseableObject) -> methodThatThrowsException(ExceptionToThrow.IOException));
91          try {
92              initializer.get();
93              ((AbstractConcurrentInitializer) initializer).close();
94              fail();
95          } catch (Exception e) {
96              assertThat(e, instanceOf(ConcurrentException.class));
97              assertThat(e.getCause(), instanceOf(IOException.class));
98          }
99      }
100 
101     /**
102      * This method tests that if AbstractConcurrentInitializer.close catches a
103      * RuntimeException it will throw it withuot wrapping it in a ConcurrentException
104      */
105     @SuppressWarnings("rawtypes")
106     @Test
107     public void testCloserThrowsRuntimeException() throws ConcurrentException {
108         final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
109                 CloseableObject::new,
110                 (CloseableObject) -> methodThatThrowsException(ExceptionToThrow.NullPointerException));
111 
112         initializer.get();
113         assertThrows(NullPointerException.class, () -> {
114             ((AbstractConcurrentInitializer) initializer).close();
115             });
116     }
117 
118     /**
119      * This method tests that if AbstractConcurrentInitializer.initialize catches a checked
120      * exception it will rethrow it wrapped in a ConcurrentException
121      */
122     @SuppressWarnings("unchecked") //for NOP
123     @Test
124     public void testSupplierThrowsCheckedException() {
125         final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
126                 () -> methodThatThrowsException(ExceptionToThrow.IOException),
127                 FailableConsumer.NOP);
128         assertThrows(ConcurrentException.class, () -> initializer.get());
129     }
130 
131     /**
132      * This method tests that if a AbstractConcurrentInitializer.initialize method catches a
133      * ConcurrentException it will rethrow it without wrapping it.
134      */
135     @Test
136     public void testSupplierThrowsConcurrentException() {
137         final ConcurrentException concurrentException = new ConcurrentException();
138 
139         @SuppressWarnings("unchecked")
140         final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
141                 () -> {
142                     if ("test".equals("test")) {
143                         throw concurrentException;
144                     }
145                     return new CloseableObject();
146                 },
147                 FailableConsumer.NOP);
148         try {
149             initializer.get();
150             fail();
151         } catch (ConcurrentException e) {
152             assertEquals(concurrentException, e);
153         }
154     }
155 
156     /**
157      * This method tests that if AbstractConcurrentInitializer.initialize catches a runtime exception
158      * it will not be wrapped in a ConcurrentException
159      */
160     @SuppressWarnings("unchecked")
161     @Test
162     public void testSupplierThrowsRuntimeException() {
163         final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
164                 () -> methodThatThrowsException(ExceptionToThrow.NullPointerException),
165                 FailableConsumer.NOP);
166         assertThrows(NullPointerException.class, () -> initializer.get());
167     }
168 
169     /**
170      * This method tests that if AbstractConcurrentInitializer.close actually closes the wrapped object
171      */
172     @SuppressWarnings("rawtypes")
173     @Test
174     public void testWorkingCloser() throws Exception {
175         final ConcurrentInitializer<CloseableObject> initializer = createInitializerThatThrowsException(
176                 CloseableObject::new,
177                 CloseableObject::close);
178 
179         CloseableObject cloesableObject = initializer.get();
180         assertFalse(cloesableObject.isClosed());
181         ((AbstractConcurrentInitializer) initializer).close();
182         assertTrue(cloesableObject.isClosed());
183     }
184 }