1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.lang3.concurrent;
18
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertFalse;
21 import static org.junit.jupiter.api.Assertions.assertNotNull;
22 import static org.junit.jupiter.api.Assertions.assertNull;
23 import static org.junit.jupiter.api.Assertions.assertSame;
24 import static org.junit.jupiter.api.Assertions.assertThrows;
25 import static org.junit.jupiter.api.Assertions.assertTrue;
26
27 import java.time.Duration;
28 import java.util.concurrent.CountDownLatch;
29 import java.util.concurrent.ExecutorService;
30 import java.util.concurrent.Executors;
31 import java.util.concurrent.TimeUnit;
32 import java.util.concurrent.atomic.AtomicBoolean;
33 import java.util.concurrent.atomic.AtomicInteger;
34 import java.util.concurrent.atomic.AtomicReference;
35
36 import org.apache.commons.lang3.AbstractLangTest;
37 import org.apache.commons.lang3.ThreadUtils;
38 import org.junit.jupiter.api.Test;
39
40 class BackgroundInitializerTest extends AbstractLangTest {
41
42
43
44
45
46 protected static class AbstractBackgroundInitializerTestImpl extends
47 BackgroundInitializer<CloseableCounter> {
48
49
50 Exception ex;
51
52
53 boolean shouldSleep;
54
55
56 final CountDownLatch latch = new CountDownLatch(1);
57 boolean waitForLatch;
58
59
60 CloseableCounter counter = new CloseableCounter();
61
62 AbstractBackgroundInitializerTestImpl() {
63 }
64
65 AbstractBackgroundInitializerTestImpl(final ExecutorService exec) {
66 super(exec);
67 }
68
69 public void enableLatch() {
70 waitForLatch = true;
71 }
72
73 public CloseableCounter getCloseableCounter() {
74 return counter;
75 }
76
77
78
79
80
81
82
83 protected CloseableCounter initializeInternal() throws Exception {
84 if (ex != null) {
85 throw ex;
86 }
87 if (shouldSleep) {
88 ThreadUtils.sleep(Duration.ofMinutes(1));
89 }
90 if (waitForLatch) {
91 latch.await();
92 }
93 return counter.increment();
94 }
95
96 public void releaseLatch() {
97 latch.countDown();
98 }
99 }
100
101 protected static class CloseableCounter {
102
103
104 AtomicInteger initializeCalls = new AtomicInteger();
105
106
107 AtomicBoolean closed = new AtomicBoolean();
108
109 public void close() {
110 closed.set(true);
111 }
112
113 public int getInitializeCalls() {
114 return initializeCalls.get();
115 }
116
117 public CloseableCounter increment() {
118 initializeCalls.incrementAndGet();
119 return this;
120 }
121
122 public boolean isClosed() {
123 return closed.get();
124 }
125 }
126
127 protected static class MethodBackgroundInitializerTestImpl extends AbstractBackgroundInitializerTestImpl {
128
129 MethodBackgroundInitializerTestImpl() {
130 }
131
132 MethodBackgroundInitializerTestImpl(final ExecutorService exec) {
133 super(exec);
134 }
135
136 @Override
137 protected CloseableCounter initialize() throws Exception {
138 return initializeInternal();
139 }
140 }
141
142
143
144
145
146
147
148 private void checkInitialize(final AbstractBackgroundInitializerTestImpl init) throws ConcurrentException {
149 final Integer result = init.get().getInitializeCalls();
150 assertEquals(1, result.intValue(), "Wrong result");
151 assertEquals(1, init.getCloseableCounter().getInitializeCalls(), "Wrong number of invocations");
152 assertNotNull(init.getFuture(), "No future");
153 }
154
155 protected AbstractBackgroundInitializerTestImpl getBackgroundInitializerTestImpl() {
156 return new MethodBackgroundInitializerTestImpl();
157 }
158
159 protected AbstractBackgroundInitializerTestImpl getBackgroundInitializerTestImpl(final ExecutorService exec) {
160 return new MethodBackgroundInitializerTestImpl(exec);
161 }
162
163 @Test
164 void testBuilder() throws ConcurrentException {
165
166 final BackgroundInitializer<Object> backgroundInitializer = BackgroundInitializer.builder()
167 .setCloser(null)
168 .setExternalExecutor(null)
169 .setInitializer(null)
170 .get();
171
172 assertNull(backgroundInitializer.getExternalExecutor());
173 assertFalse(backgroundInitializer.isInitialized());
174 assertFalse(backgroundInitializer.isStarted());
175 assertThrows(IllegalStateException.class, backgroundInitializer::getFuture);
176 }
177
178 @Test
179 void testBuilderThenGetFailures() throws ConcurrentException {
180
181 final BackgroundInitializer<Object> backgroundInitializer = BackgroundInitializer.builder()
182 .setCloser(null)
183 .setExternalExecutor(null)
184 .setInitializer(() -> {
185 throw new IllegalStateException("test");
186 })
187 .get();
188
189 assertNull(backgroundInitializer.getExternalExecutor());
190 assertFalse(backgroundInitializer.isInitialized());
191 assertFalse(backgroundInitializer.isStarted());
192 assertThrows(IllegalStateException.class, backgroundInitializer::getFuture);
193
194 backgroundInitializer.start();
195 assertEquals("test", assertThrows(IllegalStateException.class, backgroundInitializer::get).getMessage());
196 }
197
198
199
200
201
202 @Test
203 void testGetActiveExecutorBeforeStart() {
204 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
205 assertNull(init.getActiveExecutor(), "Got an executor");
206 }
207
208
209
210
211 @Test
212 void testGetActiveExecutorExternal() throws InterruptedException, ConcurrentException {
213 final ExecutorService exec = Executors.newSingleThreadExecutor();
214 try {
215 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl(exec);
216 init.start();
217 assertSame(exec, init.getActiveExecutor(), "Wrong executor");
218 checkInitialize(init);
219 } finally {
220 exec.shutdown();
221 exec.awaitTermination(1, TimeUnit.SECONDS);
222 }
223 }
224
225
226
227
228 @Test
229 void testGetActiveExecutorTemp() throws ConcurrentException {
230 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
231 init.start();
232 assertNotNull(init.getActiveExecutor(), "No active executor");
233 checkInitialize(init);
234 }
235
236
237
238
239 @Test
240 void testGetBeforeStart() {
241 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
242 assertThrows(IllegalStateException.class, init::get);
243 }
244
245
246
247
248
249 @Test
250 void testGetCheckedException() {
251 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
252 final Exception ex = new Exception();
253 init.ex = ex;
254 init.start();
255 final ConcurrentException cex = assertThrows(ConcurrentException.class, init::get);
256 assertEquals(ex, cex.getCause(), "Exception not thrown");
257 }
258
259
260
261
262
263
264 @Test
265 void testGetInterruptedException() throws InterruptedException {
266 final ExecutorService exec = Executors.newSingleThreadExecutor();
267 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl(
268 exec);
269 final CountDownLatch latch1 = new CountDownLatch(1);
270 init.shouldSleep = true;
271 init.start();
272 final AtomicReference<InterruptedException> iex = new AtomicReference<>();
273 final Thread getThread = new Thread() {
274 @Override
275 public void run() {
276 try {
277 init.get();
278 } catch (final ConcurrentException cex) {
279 if (cex.getCause() instanceof InterruptedException) {
280 iex.set((InterruptedException) cex.getCause());
281 }
282 } finally {
283 assertTrue(isInterrupted(), "Thread not interrupted");
284 latch1.countDown();
285 }
286 }
287 };
288 getThread.start();
289 getThread.interrupt();
290 latch1.await();
291 exec.shutdownNow();
292 exec.awaitTermination(1, TimeUnit.SECONDS);
293 assertNotNull(iex.get(), "No interrupted exception");
294 }
295
296
297
298
299
300 @Test
301 void testGetRuntimeException() {
302 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
303 final RuntimeException rex = new RuntimeException();
304 init.ex = rex;
305 init.start();
306 final Exception ex = assertThrows(Exception.class, init::get);
307 assertEquals(rex, ex, "Runtime exception not thrown");
308 }
309
310
311
312
313 @Test
314 void testInitialize() throws ConcurrentException {
315 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
316 init.start();
317 checkInitialize(init);
318 }
319
320
321
322
323
324 @Test
325 void testInitializeTempExecutor() throws ConcurrentException {
326 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
327 assertTrue(init.start(), "Wrong result of start()");
328 checkInitialize(init);
329 assertTrue(init.getActiveExecutor().isShutdown(), "Executor not shutdown");
330 }
331
332
333
334
335 @Test
336 void testIsInitialized() throws ConcurrentException {
337 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
338 init.enableLatch();
339 init.start();
340 assertTrue(init.isStarted(), "Not started");
341 assertFalse(init.isInitialized(), "Initialized before releasing latch");
342 init.releaseLatch();
343 init.get();
344 assertTrue(init.isInitialized(), "Not initialized after releasing latch");
345 }
346
347
348
349
350 @Test
351 void testIsStartedAfterGet() throws ConcurrentException {
352 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
353 init.start();
354 checkInitialize(init);
355 assertTrue(init.isStarted(), "Not started");
356 }
357
358
359
360
361 @Test
362 void testIsStartedFalse() {
363 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
364 assertFalse(init.isStarted(), "Already started");
365 }
366
367
368
369
370 @Test
371 void testIsStartedTrue() {
372 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
373 init.start();
374 assertTrue(init.isStarted(), "Not started");
375 }
376
377
378
379
380
381 @Test
382 void testSetExternalExecutor() throws ConcurrentException {
383 final ExecutorService exec = Executors.newCachedThreadPool();
384 try {
385 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
386 init.setExternalExecutor(exec);
387 assertEquals(exec, init.getExternalExecutor(), "Wrong executor service");
388 assertTrue(init.start(), "Wrong result of start()");
389 assertSame(exec, init.getActiveExecutor(), "Wrong active executor");
390 checkInitialize(init);
391 assertFalse(exec.isShutdown(), "Executor was shutdown");
392 } finally {
393 exec.shutdown();
394 }
395 }
396
397
398
399
400
401
402 @Test
403 void testSetExternalExecutorAfterStart() throws ConcurrentException, InterruptedException {
404 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
405 init.start();
406 final ExecutorService exec = Executors.newSingleThreadExecutor();
407 try {
408 assertThrows(IllegalStateException.class, () -> init.setExternalExecutor(exec));
409 init.get();
410 } finally {
411 exec.shutdown();
412 exec.awaitTermination(1, TimeUnit.SECONDS);
413 }
414 }
415
416
417
418
419
420 @Test
421 void testStartMultipleTimes() throws ConcurrentException {
422 final AbstractBackgroundInitializerTestImpl init = getBackgroundInitializerTestImpl();
423 assertTrue(init.start(), "Wrong result for start()");
424 for (int i = 0; i < 10; i++) {
425 assertFalse(init.start(), "Could start again");
426 }
427 checkInitialize(init);
428 }
429 }