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