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  
18  package org.apache.commons.lang3.concurrent;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertNull;
22  import static org.junit.jupiter.api.Assertions.assertThrows;
23  
24  import java.io.IOException;
25  import java.nio.file.FileSystemException;
26  import java.util.concurrent.TimeUnit;
27  import java.util.concurrent.atomic.AtomicBoolean;
28  import java.util.concurrent.atomic.AtomicInteger;
29  
30  import org.apache.commons.lang3.exception.ExceptionUtils;
31  import org.junit.jupiter.api.BeforeEach;
32  import org.junit.jupiter.api.Test;
33  import org.junit.jupiter.api.Timeout;
34  import org.junit.jupiter.api.Timeout.ThreadMode;
35  import org.junit.jupiter.params.ParameterizedTest;
36  import org.junit.jupiter.params.provider.ValueSource;
37  
38  /**
39   * Test class for {@code AtomicSafeInitializer} which also serves as a simple example.
40   */
41  class AtomicSafeInitializerTest extends AbstractConcurrentInitializerTest<Object> {
42  
43      /**
44       * A concrete test implementation of {@code AtomicSafeInitializer} which also serves as a simple example.
45       * <p>
46       * This implementation also counts the number of invocations of the initialize() method.
47       * </p>
48       */
49      private static final class AtomicSafeInitializerTestImpl extends AtomicSafeInitializer<Object> {
50  
51          /** A counter for initialize() invocations. */
52          final AtomicInteger initCounter = new AtomicInteger();
53  
54          @Override
55          protected Object initialize() {
56              initCounter.incrementAndGet();
57              return new Object();
58          }
59      }
60  
61      /** The instance to be tested. */
62      private AtomicSafeInitializerTestImpl initializer;
63  
64      /**
65       * Returns the initializer to be tested.
66       *
67       * @return the {@code AtomicSafeInitializer} under test.
68       */
69      @Override
70      protected ConcurrentInitializer<Object> createInitializer() {
71          return initializer;
72      }
73  
74      @BeforeEach
75      public void setUp() {
76          initializer = new AtomicSafeInitializerTestImpl();
77      }
78  
79      @Test
80      void testGetThatReturnsNullFirstTime() throws ConcurrentException {
81          final AtomicSafeInitializer<Object> initializer = new AtomicSafeInitializer<Object>() {
82  
83              final AtomicBoolean firstRun = new AtomicBoolean(true);
84  
85              @Override
86              protected Object initialize() {
87                  if (firstRun.getAndSet(false)) {
88                      return null;
89                  }
90                  return new Object();
91              }
92          };
93          assertNull(initializer.get());
94          assertNull(initializer.get());
95      }
96  
97      @ParameterizedTest
98      @ValueSource(classes = { IOException.class, Exception.class, FileSystemException.class, ReflectiveOperationException.class, ConcurrentException.class })
99      @Timeout(value = 5, unit = TimeUnit.SECONDS, threadMode = ThreadMode.SAME_THREAD)
100     void testInitializerThrowsChecked(final Class<Exception> throwableClass) throws ConcurrentException {
101         final String message = "Initializing";
102         final AtomicSafeInitializer<Object> asi = AtomicSafeInitializer.builder().setInitializer(() -> {
103             throw throwableClass.getConstructor(String.class).newInstance(message);
104         }).get();
105         final String expected = throwableClass.getSimpleName() + ": " + message;
106         assertEquals(expected, ExceptionUtils.getRootCauseMessage(assertThrows(ConcurrentException.class, asi::get)));
107         assertEquals(expected, ExceptionUtils.getRootCauseMessage(assertThrows(ConcurrentException.class, asi::get)));
108     }
109 
110     @ParameterizedTest
111     @ValueSource(classes = { IllegalStateException.class, IllegalArgumentException.class, NullPointerException.class, RuntimeException.class })
112     @Timeout(value = 5, unit = TimeUnit.SECONDS, threadMode = ThreadMode.SAME_THREAD)
113     void testInitializerThrowsUnchecked(final Class<Exception> throwableClass) throws ConcurrentException {
114         final String message = "Initializing";
115         final AtomicSafeInitializer<Object> asi = AtomicSafeInitializer.builder().setInitializer(() -> {
116             throw throwableClass.getConstructor(String.class).newInstance(message);
117         }).get();
118         assertEquals(message, assertThrows(throwableClass, asi::get).getMessage());
119         assertEquals(message, assertThrows(throwableClass, asi::get).getMessage());
120     }
121 
122     /**
123      * Tests that initialize() is called only once.
124      *
125      * @throws org.apache.commons.lang3.concurrent.ConcurrentException because {@link #testGetConcurrent()} may throw it.
126      * @throws InterruptedException                                    because {@link #testGetConcurrent()} may throw it.
127      */
128     @Test
129     void testNumberOfInitializeInvocations() throws ConcurrentException, InterruptedException {
130         testGetConcurrent();
131         assertEquals(1, initializer.initCounter.get(), "Wrong number of invocations");
132     }
133 }