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