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.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertSame;
23  import static org.junit.jupiter.api.Assertions.assertTrue;
24  import static org.junit.jupiter.api.Assertions.fail;
25  
26  import java.io.IOException;
27  import java.lang.reflect.Field;
28  
29  import org.apache.commons.lang3.function.FailableConsumer;
30  import org.apache.commons.lang3.function.FailableSupplier;
31  import org.junit.jupiter.api.BeforeEach;
32  import org.junit.jupiter.api.Test;
33  
34  /**
35   * Test class for {@link MultiBackgroundInitializer}.
36   */
37  public class MultiBackgroundInitializerSupplierTest extends MultiBackgroundInitializerTest {
38  
39      /**
40       * A concrete implementation of {@code BackgroundInitializer} used for
41       * defining background tasks for {@code MultiBackgroundInitializer}.
42       */
43      private static final class SupplierChildBackgroundInitializer extends AbstractChildBackgroundInitializer {
44  
45          SupplierChildBackgroundInitializer() {
46              this((CloseableCounter cc) -> cc.close());
47          }
48  
49          SupplierChildBackgroundInitializer(FailableConsumer<?, ?> consumer) {
50              try {
51                  // Use reflection here because the constructors we need are private
52                  final FailableSupplier<?, ?> supplier = () -> initializeInternal();
53                  final Field initializer = AbstractConcurrentInitializer.class.getDeclaredField("initializer");
54                  initializer.setAccessible(true);
55                  initializer.set(this, supplier);
56  
57                  final Field closer = AbstractConcurrentInitializer.class.getDeclaredField("closer");
58                  closer.setAccessible(true);
59                  closer.set(this, consumer);
60              } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
61                  fail();
62              }
63          }
64      }
65      private NullPointerException npe;
66      private IOException ioException;
67      private FailableConsumer<?, ?> ioExceptionConsumer;
68  
69      private FailableConsumer<?, ?> nullPointerExceptionConsumer;
70  
71      /**
72       * {@inheritDoc}
73       */
74      @Override
75      protected AbstractChildBackgroundInitializer createChildBackgroundInitializer() {
76          return new SupplierChildBackgroundInitializer();
77      }
78  
79      @BeforeEach
80      public void setUpException() throws Exception {
81          npe = new NullPointerException();
82          ioException = new IOException();
83          ioExceptionConsumer = (CloseableCounter cc) -> {
84              throw ioException;
85          };
86          nullPointerExceptionConsumer = (CloseableCounter cc) -> {
87              throw npe;
88          };
89      }
90  
91      /**
92       * Tests that close() method closes the wrapped object
93       *
94       * @throws Exception
95       */
96      @Test
97      public void testClose()
98              throws ConcurrentException, InterruptedException {
99          final AbstractChildBackgroundInitializer childOne = createChildBackgroundInitializer();
100         final AbstractChildBackgroundInitializer childTwo = createChildBackgroundInitializer();
101 
102         assertFalse(initializer.isInitialized(), "Initalized without having anything to initalize");
103 
104         initializer.addInitializer("child one", childOne);
105         initializer.addInitializer("child two", childTwo);
106 
107         assertFalse(childOne.getCloseableCounter().isClosed(), "child one closed() succeeded before start()");
108         assertFalse(childTwo.getCloseableCounter().isClosed(), "child two closed() succeeded before start()");
109 
110         initializer.start();
111 
112         long startTime = System.currentTimeMillis();
113         long waitTime = 3000;
114         long endTime = startTime + waitTime;
115         //wait for the children to start
116         while (!childOne.isStarted() || !childTwo.isStarted()) {
117             if (System.currentTimeMillis() > endTime) {
118                 fail("children never started");
119                 Thread.sleep(PERIOD_MILLIS);
120             }
121         }
122 
123         assertFalse(childOne.getCloseableCounter().isClosed(), "child one close() succeeded after start() but before close()");
124         assertFalse(childTwo.getCloseableCounter().isClosed(), "child two close() succeeded after start() but before close()");
125 
126         childOne.get(); // ensure this child finishes initializing
127         childTwo.get(); // ensure this child finishes initializing
128 
129         assertFalse(childOne.getCloseableCounter().isClosed(), "child one initializing succeeded after start() but before close()");
130         assertFalse(childTwo.getCloseableCounter().isClosed(), "child two initializing succeeded after start() but before close()");
131 
132         try {
133             initializer.close();
134         } catch (Exception e) {
135             fail();
136         }
137 
138         assertTrue(childOne.getCloseableCounter().isClosed(), "child one close() did not succeed");
139         assertTrue(childOne.getCloseableCounter().isClosed(), "child two close() did not succeed");
140     }
141 
142     /**
143      * Tests that close() wraps a checked exception from a child initializer in an ConcurrentException as the first suppressed under in an ConcurrentException
144      *
145      * @throws Exception
146      */
147     @Test
148     public void testCloseWithCheckedException() throws Exception {
149         final AbstractChildBackgroundInitializer childOne = new SupplierChildBackgroundInitializer(ioExceptionConsumer);
150 
151         initializer.addInitializer("child one", childOne);
152         initializer.start();
153 
154         long startTime = System.currentTimeMillis();
155         long waitTime = 3000;
156         long endTime = startTime + waitTime;
157         //wait for the children to start
158         while (! childOne.isStarted()) {
159             if (System.currentTimeMillis() > endTime) {
160                 fail("children never started");
161                 Thread.sleep(PERIOD_MILLIS);
162             }
163         }
164 
165         childOne.get(); // ensure the Future has completed.
166         try {
167             initializer.close();
168             fail();
169         } catch (Exception e) {
170             assertThat(e, instanceOf(ConcurrentException.class));
171             assertSame(ioException, e.getSuppressed()[0]);
172         }
173     }
174 
175     /**
176      * Tests that close() wraps a runtime exception from a child initializer as the first suppressed under in an ConcurrentException
177      *
178      * @throws Exception
179      */
180     @Test
181     public void testCloseWithRuntimeException() throws Exception {
182         final AbstractChildBackgroundInitializer childOne = new SupplierChildBackgroundInitializer(nullPointerExceptionConsumer);
183 
184         initializer.addInitializer("child one", childOne);
185         initializer.start();
186 
187         long startTime = System.currentTimeMillis();
188         long waitTime = 3000;
189         long endTime = startTime + waitTime;
190         //wait for the children to start
191         while (! childOne.isStarted()) {
192             if (System.currentTimeMillis() > endTime) {
193                 fail("children never started");
194                 Thread.sleep(PERIOD_MILLIS);
195             }
196         }
197 
198         childOne.get(); // ensure the Future has completed.
199         try {
200             initializer.close();
201             fail();
202         } catch (Exception e) {
203             assertThat(e, instanceOf(ConcurrentException.class));
204             assertSame(npe, e.getSuppressed()[0]);
205         }
206     }
207 
208     /**
209      * Tests that calling close() on a MultiBackgroundInitializer with two children that both throw exceptions throws
210      * an ConcurrentException and both the child exceptions are present
211      *
212      * @throws Exception
213      */
214     @Test
215     public void testCloseWithTwoExceptions()
216             throws ConcurrentException, InterruptedException {
217 
218         final AbstractChildBackgroundInitializer childOne = new SupplierChildBackgroundInitializer(ioExceptionConsumer);
219         final AbstractChildBackgroundInitializer childTwo = new SupplierChildBackgroundInitializer(nullPointerExceptionConsumer);
220 
221         initializer.addInitializer("child one", childOne);
222         initializer.addInitializer("child two", childTwo);
223 
224         initializer.start();
225 
226         final long startTime = System.currentTimeMillis();
227         final long waitTime = 3000;
228         final long endTime = startTime + waitTime;
229         //wait for the children to start
230         while (! childOne.isStarted() || ! childTwo.isStarted()) {
231             if (System.currentTimeMillis() > endTime) {
232                 fail("children never started");
233                 Thread.sleep(PERIOD_MILLIS);
234             }
235         }
236 
237         childOne.get(); // ensure this child finishes initializing
238         childTwo.get(); // ensure this child finishes initializing
239 
240         try {
241             initializer.close();
242             fail();
243         } catch (Exception e) {
244             // We don't actually know which order the children will be closed in
245             boolean foundChildOneException = false;
246             boolean foundChildTwoException = false;
247 
248             for (Throwable t : e.getSuppressed()) {
249                 if (t.equals(ioException)) {
250                     foundChildOneException = true;
251                 }
252                 if (t.equals(npe)) {
253                     foundChildTwoException = true;
254                 }
255             }
256 
257             assertTrue(foundChildOneException);
258             assertTrue(foundChildTwoException);
259         }
260     }
261 }