1 /*
2  * Copyright (C) 2024 The Android Open Source Project
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 package com.google.jetpackcamera.core.common
17 
18 import com.google.common.truth.Truth.assertThat
19 import kotlinx.atomicfu.atomic
20 import kotlinx.coroutines.Dispatchers
21 import kotlinx.coroutines.coroutineScope
22 import kotlinx.coroutines.delay
23 import kotlinx.coroutines.launch
24 import kotlinx.coroutines.runBlocking
25 import org.junit.Assert.assertThrows
26 import org.junit.Before
27 import org.junit.Test
28 import org.junit.runner.RunWith
29 import org.robolectric.RobolectricTestRunner
30 import org.robolectric.shadows.ShadowLog
31 
32 @RunWith(RobolectricTestRunner::class)
33 class RefCountedTest {
34 
35     @Before
setUpnull36     fun setUp() {
37         ShadowLog.stream = System.out
38     }
39 
40     @Test
onRelease_calledAfterReleasenull41     fun onRelease_calledAfterRelease() {
42         var onReleaseCalled = false
43         val refCounted = RefCounted<Unit> {
44             onReleaseCalled = true
45         }.also {
46             it.initialize(Unit)
47         }
48 
49         refCounted.release()
50 
51         assertThat(onReleaseCalled).isTrue()
52     }
53 
54     @Test
acquireBeforeInitialize_throwsExceptionnull55     fun acquireBeforeInitialize_throwsException() {
56         val refCounted = RefCounted<Unit> {}
57         assertThrows(IllegalStateException::class.java) {
58             refCounted.acquire()
59         }
60     }
61 
62     @Test
releaseBeforeInitialize_throwsExceptionnull63     fun releaseBeforeInitialize_throwsException() {
64         val refCounted = RefCounted<Unit> {}
65         assertThrows(IllegalStateException::class.java) {
66             refCounted.release()
67         }
68     }
69 
70     @Test
releaseCalledMoreTimesThanAcquire_throwsExceptionnull71     fun releaseCalledMoreTimesThanAcquire_throwsException() {
72         val refCounted = RefCounted<Unit> {}
73         refCounted.initialize(Unit)
74         refCounted.release()
75 
76         assertThrows(IllegalStateException::class.java) {
77             refCounted.release()
78         }
79     }
80 
81     @Test
acquireAfterRelease_returnsNullnull82     fun acquireAfterRelease_returnsNull() {
83         val refCounted = RefCounted<Unit> {}
84         refCounted.initialize(Unit)
85         refCounted.release()
86 
87         assertThat(refCounted.acquire()).isNull()
88     }
89 
90     @Test
acquireAfterInitialize_returnsValuenull91     fun acquireAfterInitialize_returnsValue() {
92         val value = Object()
93         val refCounted = RefCounted<Any> {}
94         refCounted.initialize(value)
95 
96         assertThat(refCounted.acquire()).isEqualTo(value)
97     }
98 
99     @Test
<lambda>null100     fun acquiresWithMatchedRelease_callsOnRelease() = runBlocking {
101         val onReleaseCalled = atomic(false)
102         val refCounted = RefCounted<Unit> {
103             onReleaseCalled.value = true
104         }.also {
105             it.initialize(Unit)
106         }
107 
108         // Run many acquire/release pairs in parallel
109         // Wrap in `coroutineScope` to ensure all children coroutines
110         // have finished before continuing
111         coroutineScope {
112             for (i in 1..1000) {
113                 launch(Dispatchers.IO) {
114                     refCounted.acquire()
115                     delay(5)
116                     refCounted.release()
117                 }
118             }
119         }
120 
121         val onReleaseCalledBeforeFinalRelease = onReleaseCalled.value
122 
123         // Call final release to match initialize()
124         refCounted.release()
125 
126         assertThat(onReleaseCalledBeforeFinalRelease).isFalse()
127         assertThat(onReleaseCalled.value).isTrue()
128     }
129 }
130