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.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.assertTrue;
23
24 import java.util.concurrent.CountDownLatch;
25
26 import org.apache.commons.lang3.AbstractLangTest;
27 import org.junit.jupiter.api.Test;
28
29 /**
30 * <p>
31 * An abstract base class for tests of concrete {@code ConcurrentInitializer} implementations.
32 * </p>
33 * <p>
34 * This class provides some basic tests for initializer implementations. Derived class have to create a {@link ConcurrentInitializer} object on which the tests
35 * are executed.
36 * </p>
37 *
38 * @param <T> Domain type.
39 */
40 public abstract class AbstractConcurrentInitializerTest<T> extends AbstractLangTest {
41
42 static final class GetThread extends Thread {
43
44 private Object object;
45 private final CountDownLatch startLatch;
46 private final ConcurrentInitializer<?> initializer;
47
48 GetThread(final CountDownLatch startLatch, final ConcurrentInitializer<?> initializer) {
49 this.startLatch = startLatch;
50 this.initializer = initializer;
51 }
52
53 @Override
54 public void run() {
55 try {
56 // wait until all threads are ready for maximum parallelism
57 startLatch.await();
58 // access the initializer
59 object = initializer.get();
60 } catch (final InterruptedException iex) {
61 // ignore
62 } catch (final ConcurrentException cex) {
63 object = cex;
64 }
65 }
66 }
67
68 /**
69 * Creates the {@link ConcurrentInitializer} object to be tested. This method is called whenever the test fixture needs to be obtained.
70 *
71 * @return the initializer object to be tested
72 */
73 protected abstract ConcurrentInitializer<T> createInitializer();
74
75 /**
76 * Tests a simple invocation of the get() method.
77 *
78 * @throws org.apache.commons.lang3.concurrent.ConcurrentException because the object under test may throw it.
79 */
80 @Test
81 void testGet() throws ConcurrentException {
82 assertNotNull(createInitializer().get(), "No managed object");
83 }
84
85 /**
86 * Tests whether get() can be invoked from multiple threads concurrently. Always the same object should be returned.
87 *
88 * @throws org.apache.commons.lang3.concurrent.ConcurrentException because the object under test may throw it.
89 * @throws InterruptedException because the threading API my throw it.
90 */
91 @Test
92 void testGetConcurrent() throws ConcurrentException, InterruptedException {
93 final ConcurrentInitializer<T> initializer = createInitializer();
94 final int threadCount = 20;
95 final CountDownLatch startLatch = new CountDownLatch(1);
96 final GetThread[] threads = new GetThread[threadCount];
97 for (int i = 0; i < threadCount; i++) {
98 threads[i] = new GetThread(startLatch, initializer);
99 threads[i].start();
100 }
101
102 // fire all threads and wait until they are ready
103 startLatch.countDown();
104 for (final Thread t : threads) {
105 t.join();
106 }
107
108 // check results
109 final Object managedObject = initializer.get();
110 for (final GetThread t : threads) {
111 assertEquals(managedObject, t.object, "Wrong object");
112 }
113 }
114
115 /**
116 * Tests whether sequential get() invocations always return the same instance.
117 *
118 * @throws org.apache.commons.lang3.concurrent.ConcurrentException because the object under test may throw it.
119 */
120 @Test
121 void testGetMultipleTimes() throws ConcurrentException {
122 final ConcurrentInitializer<T> initializer = createInitializer();
123 final Object obj = initializer.get();
124 for (int i = 0; i < 10; i++) {
125 assertEquals(obj, initializer.get(), "Got different object at " + i);
126 }
127 }
128
129 /**
130 * Tests a simple invocation of the isInitialized() method.
131 *
132 * @throws Throwable on test failure.
133 */
134 @Test
135 void testisInitialized() throws Throwable {
136 final ConcurrentInitializer<T> initializer = createInitializer();
137 if (initializer instanceof AbstractConcurrentInitializer) {
138 @SuppressWarnings("unchecked")
139 final AbstractConcurrentInitializer<T, Exception> castedInitializer = (AbstractConcurrentInitializer<T, Exception>) initializer;
140 assertFalse(castedInitializer.isInitialized(), "was initialized before get()");
141 assertNotNull(castedInitializer.get(), "No managed object");
142 assertTrue(castedInitializer.isInitialized(), "was not initialized after get()");
143 }
144 }
145 }