xref: /aosp_15_r20/external/dagger2/javatests/dagger/functional/kotlinsrc/cycle/DoubleCheckCycleTest.kt (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
1 /*
<lambda>null2  * Copyright (C) 2022 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.functional.kotlinsrc.cycle
18 
19 import com.google.common.truth.Truth.assertThat
20 import com.google.common.util.concurrent.SettableFuture
21 import com.google.common.util.concurrent.Uninterruptibles
22 import dagger.Component
23 import dagger.Module
24 import dagger.Provides
25 import java.lang.Exception
26 import java.lang.RuntimeException
27 import java.lang.Thread.State.BLOCKED
28 import java.lang.Thread.State.WAITING
29 import java.util.ArrayList
30 import java.util.Collections
31 import java.util.concurrent.CountDownLatch
32 import java.util.concurrent.ExecutionException
33 import java.util.concurrent.atomic.AtomicInteger
34 import java.util.function.Consumer
35 import javax.inject.Provider
36 import javax.inject.Qualifier
37 import javax.inject.Singleton
38 import org.junit.Assert.fail
39 import org.junit.Test
40 import org.junit.runner.RunWith
41 import org.junit.runners.JUnit4
42 
43 @RunWith(JUnit4::class)
44 class DoubleCheckCycleTest {
45   // TODO(b/77916397): Migrate remaining tests in DoubleCheckTest to functional tests in this class.
46   /** A qualifier for a reentrant scoped binding. */
47   @Retention(AnnotationRetention.RUNTIME)
48   @Qualifier
49   internal annotation class Reentrant
50 
51   /** A module to be overridden in each test. */
52   @Module
53   internal open class OverrideModule {
54     @Provides
55     @Singleton
56     open fun provideObject(): Any {
57       throw IllegalStateException("This method should be overridden in tests")
58     }
59 
60     @Provides
61     @Singleton
62     @Reentrant
63     open fun provideReentrantObject(@Reentrant provider: Provider<Any>): Any {
64       throw IllegalStateException("This method should be overridden in tests")
65     }
66   }
67 
68   @Singleton
69   @Component(modules = [OverrideModule::class])
70   internal interface TestComponent {
71     fun getObject(): Any
72 
73     @Reentrant
74     fun getReentrantObject(): Any
75   }
76 
77   @Test
78   fun testNonReentrant() {
79     val callCount = AtomicInteger(0)
80 
81     // Provides a non-reentrant binding. The provides method should only be called once.
82     val component =
83       DaggerDoubleCheckCycleTest_TestComponent.builder()
84         .overrideModule(
85           object : OverrideModule() {
86             override fun provideObject(): Any {
87               callCount.getAndIncrement()
88               return Any()
89             }
90           }
91         )
92         .build()
93     assertThat(callCount.get()).isEqualTo(0)
94     val first = component.getObject()
95     assertThat(callCount.get()).isEqualTo(1)
96     val second = component.getObject()
97     assertThat(callCount.get()).isEqualTo(1)
98     assertThat(first).isSameInstanceAs(second)
99   }
100 
101   @Test
102   fun testReentrant() {
103     val callCount = AtomicInteger(0)
104 
105     // Provides a reentrant binding. Even though it's scoped, the provides method is called twice.
106     // In this case, we allow it since the same instance is returned on the second call.
107     val component =
108       DaggerDoubleCheckCycleTest_TestComponent.builder()
109         .overrideModule(
110           object : OverrideModule() {
111             override fun provideReentrantObject(provider: Provider<Any>): Any {
112               return if (callCount.incrementAndGet() == 1) {
113                 provider.get()
114               } else {
115                 Any()
116               }
117             }
118           }
119         )
120         .build()
121     assertThat(callCount.get()).isEqualTo(0)
122     val first = component.getReentrantObject()
123     assertThat(callCount.get()).isEqualTo(2)
124     val second = component.getReentrantObject()
125     assertThat(callCount.get()).isEqualTo(2)
126     assertThat(first).isSameInstanceAs(second)
127   }
128 
129   @Test
130   fun testFailingReentrant() {
131     val callCount = AtomicInteger(0)
132 
133     // Provides a failing reentrant binding. Even though it's scoped, the provides method is called
134     // twice. In this case we throw an exception since a different instance is provided on the
135     // second call.
136     val component =
137       DaggerDoubleCheckCycleTest_TestComponent.builder()
138         .overrideModule(
139           object : OverrideModule() {
140             override fun provideReentrantObject(provider: Provider<Any>): Any {
141               if (callCount.incrementAndGet() == 1) {
142                 provider.get()
143                 return Any()
144               }
145               return Any()
146             }
147           }
148         )
149         .build()
150     assertThat(callCount.get()).isEqualTo(0)
151     try {
152       component.getReentrantObject()
153       fail("Expected IllegalStateException")
154     } catch (e: IllegalStateException) {
155       assertThat(e).hasMessageThat().contains("Scoped provider was invoked recursively")
156     }
157     assertThat(callCount.get()).isEqualTo(2)
158   }
159 
160   @Test(timeout = 5000)
161   @Throws(Exception::class)
162   fun testGetFromMultipleThreads() {
163     val callCount = AtomicInteger(0)
164     val requestCount = AtomicInteger(0)
165     val future: SettableFuture<Any> = SettableFuture.create()
166 
167     // Provides a non-reentrant binding. In this case, we return a SettableFuture so that we can
168     // control when the provides method returns.
169     val component =
170       DaggerDoubleCheckCycleTest_TestComponent.builder()
171         .overrideModule(
172           object : OverrideModule() {
173             override fun provideObject(): Any {
174               callCount.incrementAndGet()
175               return try {
176                 Uninterruptibles.getUninterruptibly(future)
177               } catch (e: ExecutionException) {
178                 throw RuntimeException(e)
179               }
180             }
181           }
182         )
183         .build()
184     val numThreads = 10
185     val remainingTasks = CountDownLatch(numThreads)
186     val tasks: MutableList<Thread> = ArrayList(numThreads)
187     val values: MutableList<Any> = Collections.synchronizedList<Any>(ArrayList<Any>(numThreads))
188 
189     // Set up multiple threads that call component.getObject().
190     for (i in 0 until numThreads) {
191       tasks.add(
192         Thread {
193           requestCount.incrementAndGet()
194           values.add(component.getObject())
195           remainingTasks.countDown()
196         }
197       )
198     }
199 
200     // Check initial conditions
201     assertThat(remainingTasks.getCount()).isEqualTo(10)
202     assertThat(requestCount.get()).isEqualTo(0)
203     assertThat(callCount.get()).isEqualTo(0)
204     assertThat(values).isEmpty()
205 
206     // Start all threads
207     tasks.forEach(Thread::start)
208 
209     // Wait for all threads to wait/block.
210     var waiting: Long = 0L
211     while (waiting != numThreads.toLong()) {
212       waiting =
213         tasks.stream()
214           .map(Thread::getState)
215           .filter { state -> state == WAITING || state == BLOCKED }
216           .count()
217     }
218 
219     // Check the intermediate state conditions.
220     // * All 10 threads should have requested the binding, but none should have finished.
221     // * Only 1 thread should have reached the provides method.
222     // * None of the threads should have set a value (since they are waiting for future to be set).
223     assertThat(remainingTasks.getCount()).isEqualTo(10)
224     assertThat(requestCount.get()).isEqualTo(10)
225     assertThat(callCount.get()).isEqualTo(1)
226     assertThat(values).isEmpty()
227 
228     // Set the future and wait on all remaining threads to finish.
229     val futureValue = Any()
230     future.set(futureValue)
231     remainingTasks.await()
232 
233     // Check the final state conditions.
234     // All values should be set now, and they should all be equal to the same instance.
235     assertThat(remainingTasks.getCount()).isEqualTo(0)
236     assertThat(requestCount.get()).isEqualTo(10)
237     assertThat(callCount.get()).isEqualTo(1)
238     assertThat(values).isEqualTo(Collections.nCopies(numThreads, futureValue))
239   }
240 }
241