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