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