1 /*
2  * Copyright (C) 2016 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.junit.Assert.fail;
21 
22 import com.google.common.collect.Lists;
23 import com.google.common.collect.Sets;
24 import com.google.common.util.concurrent.Uninterruptibles;
25 import dagger.Lazy;
26 import java.util.List;
27 import java.util.Set;
28 import java.util.concurrent.Callable;
29 import java.util.concurrent.CountDownLatch;
30 import java.util.concurrent.ExecutorService;
31 import java.util.concurrent.Executors;
32 import java.util.concurrent.Future;
33 import java.util.concurrent.atomic.AtomicInteger;
34 import java.util.concurrent.atomic.AtomicReference;
35 import javax.inject.Provider;
36 import org.junit.Test;
37 import org.junit.runner.RunWith;
38 import org.junit.runners.JUnit4;
39 
40 @RunWith(JUnit4.class)
41 public class DoubleCheckTest {
42   @Test
provider_nullPointerException()43   public void provider_nullPointerException() {
44     try {
45       DoubleCheck.provider(null);
46       fail();
47     } catch (NullPointerException expected) {
48     }
49   }
50 
51   @Test
lazy_nullPointerException()52   public void lazy_nullPointerException() {
53     try {
54       DoubleCheck.lazy(null);
55       fail();
56     } catch (NullPointerException expected) {
57     }
58   }
59 
60   private static final Provider<Object> DOUBLE_CHECK_OBJECT_PROVIDER =
61       DoubleCheck.provider(Object::new);
62 
63   @Test
doubleWrapping_provider()64   public void doubleWrapping_provider() {
65     assertThat(DoubleCheck.provider(DOUBLE_CHECK_OBJECT_PROVIDER))
66         .isSameInstanceAs(DOUBLE_CHECK_OBJECT_PROVIDER);
67   }
68 
69   @Test
doubleWrapping_lazy()70   public void doubleWrapping_lazy() {
71     assertThat(DoubleCheck.lazy(DOUBLE_CHECK_OBJECT_PROVIDER))
72         .isSameInstanceAs(DOUBLE_CHECK_OBJECT_PROVIDER);
73   }
74 
75   @Test
get()76   public void get() throws Exception {
77     int numThreads = 10;
78     ExecutorService executor = Executors.newFixedThreadPool(numThreads);
79 
80     final CountDownLatch latch = new CountDownLatch(numThreads);
81     LatchedProvider provider = new LatchedProvider(latch);
82     final Lazy<Object> lazy = DoubleCheck.lazy(provider);
83 
84     List<Callable<Object>> tasks = Lists.newArrayListWithCapacity(numThreads);
85     for (int i = 0; i < numThreads; i++) {
86       tasks.add(
87           () -> {
88             latch.countDown();
89             return lazy.get();
90           });
91     }
92 
93     List<Future<Object>> futures = executor.invokeAll(tasks);
94 
95     assertThat(provider.provisions.get()).isEqualTo(1);
96     Set<Object> results = Sets.newIdentityHashSet();
97     for (Future<Object> future : futures) {
98       results.add(future.get());
99     }
100     assertThat(results).hasSize(1);
101   }
102 
103   private static class LatchedProvider implements Provider<Object> {
104     final AtomicInteger provisions;
105     final CountDownLatch latch;
106 
LatchedProvider(CountDownLatch latch)107     LatchedProvider(CountDownLatch latch) {
108       this.latch = latch;
109       this.provisions = new AtomicInteger();
110     }
111 
112     @Override
get()113     public Object get() {
114       if (latch != null) {
115         Uninterruptibles.awaitUninterruptibly(latch);
116       }
117       provisions.incrementAndGet();
118       return new Object();
119     }
120   }
121 
reentranceWithoutCondition_throwsStackOverflow()122   @Test public void reentranceWithoutCondition_throwsStackOverflow() {
123     final AtomicReference<Provider<Object>> doubleCheckReference =
124         new AtomicReference<>();
125     Provider<Object> doubleCheck = DoubleCheck.provider(() -> doubleCheckReference.get().get());
126     doubleCheckReference.set(doubleCheck);
127     try {
128       doubleCheck.get();
129       fail();
130     } catch (StackOverflowError expected) {}
131   }
132 
reentranceReturningSameInstance()133   @Test public void reentranceReturningSameInstance() {
134     final AtomicReference<Provider<Object>> doubleCheckReference =
135         new AtomicReference<>();
136     final AtomicInteger invocationCount = new AtomicInteger();
137     final Object object = new Object();
138     Provider<Object> doubleCheck = DoubleCheck.provider(() -> {
139         if (invocationCount.incrementAndGet() == 1) {
140          doubleCheckReference.get().get();
141        }
142        return object;
143      });
144     doubleCheckReference.set(doubleCheck);
145     assertThat(doubleCheck.get()).isSameInstanceAs(object);
146   }
147 
reentranceReturningDifferentInstances_throwsIllegalStateException()148   @Test public void reentranceReturningDifferentInstances_throwsIllegalStateException() {
149     final AtomicReference<Provider<Object>> doubleCheckReference =
150         new AtomicReference<>();
151     final AtomicInteger invocationCount = new AtomicInteger();
152     Provider<Object> doubleCheck = DoubleCheck.provider(() -> {
153        if (invocationCount.incrementAndGet() == 1) {
154          doubleCheckReference.get().get();
155        }
156        return new Object();
157      });
158     doubleCheckReference.set(doubleCheck);
159     try {
160       doubleCheck.get();
161       fail();
162     } catch (IllegalStateException expected) {}
163   }
164 
165   @Test
instanceFactoryAsLazyDoesNotWrap()166   public void instanceFactoryAsLazyDoesNotWrap() {
167     Factory<Object> factory = InstanceFactory.create(new Object());
168     assertThat(DoubleCheck.lazy(factory)).isSameInstanceAs(factory);
169   }
170 }
171