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 17 package com.android.server.art; 18 19 import static android.os.IBinder.DeathRecipient; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.mockito.Mockito.doAnswer; 24 import static org.mockito.Mockito.eq; 25 import static org.mockito.Mockito.lenient; 26 import static org.mockito.Mockito.mock; 27 import static org.mockito.Mockito.never; 28 import static org.mockito.Mockito.reset; 29 import static org.mockito.Mockito.times; 30 import static org.mockito.Mockito.verify; 31 32 import android.os.IBinder; 33 34 import androidx.test.filters.SmallTest; 35 36 import com.android.server.art.testing.MockClock; 37 38 import org.junit.Before; 39 import org.junit.Test; 40 import org.junit.runner.RunWith; 41 import org.mockito.ArgumentCaptor; 42 import org.mockito.Mock; 43 import org.mockito.junit.MockitoJUnitRunner; 44 45 import java.lang.ref.PhantomReference; 46 import java.lang.ref.ReferenceQueue; 47 48 @SmallTest 49 @RunWith(MockitoJUnitRunner.StrictStubs.class) 50 public class ArtdRefCacheTest { 51 @Mock private ArtdRefCache.Injector mInjector; 52 @Mock private IArtd mArtd; 53 @Mock private IBinder mBinder; 54 private MockClock mMockClock; 55 private ArtdRefCache mArtdRefCache; 56 57 @Before setUp()58 public void setUp() throws Exception { 59 mMockClock = new MockClock(); 60 61 lenient() 62 .when(mInjector.createScheduledExecutor()) 63 .thenAnswer(invocation -> mMockClock.createScheduledExecutor()); 64 lenient().when(mInjector.getArtd()).thenReturn(mArtd); 65 66 lenient().when(mArtd.asBinder()).thenReturn(mBinder); 67 68 mArtdRefCache = new ArtdRefCache(mInjector); 69 } 70 71 @Test testNoGetArtd()72 public void testNoGetArtd() throws Exception { 73 try (var pin = mArtdRefCache.new Pin()) { 74 } 75 76 verify(mInjector, never()).getArtd(); 77 } 78 79 @Test testNoPin()80 public void testNoPin() throws Exception { 81 // Cache miss. 82 mArtdRefCache.getArtd(); 83 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS - 1); 84 mArtdRefCache.getArtd(); 85 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS - 1); 86 mArtdRefCache.getArtd(); 87 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS); 88 // Cache miss. 89 mArtdRefCache.getArtd(); 90 91 verify(mInjector, times(2)).getArtd(); 92 } 93 94 @Test testSingleScope()95 public void testSingleScope() throws Exception { 96 try (var pin = mArtdRefCache.new Pin()) { 97 mArtdRefCache.getArtd(); 98 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS); 99 mArtdRefCache.getArtd(); 100 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS); 101 mArtdRefCache.getArtd(); 102 } 103 104 verify(mInjector, times(1)).getArtd(); 105 } 106 107 @Test testMultipleScopesCacheTimeout()108 public void testMultipleScopesCacheTimeout() throws Exception { 109 try (var pin = mArtdRefCache.new Pin()) { 110 mArtdRefCache.getArtd(); 111 } 112 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS); 113 try (var pin = mArtdRefCache.new Pin()) { 114 mArtdRefCache.getArtd(); 115 } 116 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS); 117 try (var pin = mArtdRefCache.new Pin()) { 118 mArtdRefCache.getArtd(); 119 } 120 121 verify(mInjector, times(3)).getArtd(); 122 } 123 124 @Test testMultipleScopesCacheHit()125 public void testMultipleScopesCacheHit() throws Exception { 126 try (var pin = mArtdRefCache.new Pin()) { 127 mArtdRefCache.getArtd(); 128 } 129 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS - 1); 130 try (var pin = mArtdRefCache.new Pin()) { 131 mArtdRefCache.getArtd(); 132 } 133 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS - 1); 134 try (var pin = mArtdRefCache.new Pin()) { 135 mArtdRefCache.getArtd(); 136 } 137 138 verify(mInjector, times(1)).getArtd(); 139 } 140 141 @Test testMultipleScopesNoUnpinAfterTimeout()142 public void testMultipleScopesNoUnpinAfterTimeout() throws Exception { 143 try (var pin = mArtdRefCache.new Pin()) { 144 mArtdRefCache.getArtd(); 145 } 146 try (var pin = mArtdRefCache.new Pin()) { 147 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS); 148 mArtdRefCache.getArtd(); 149 } 150 try (var pin = mArtdRefCache.new Pin()) { 151 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS); 152 mArtdRefCache.getArtd(); 153 } 154 155 verify(mInjector, times(1)).getArtd(); 156 } 157 158 @Test testBinderDied()159 public void testBinderDied() throws Exception { 160 var deathRecipient = ArgumentCaptor.forClass(DeathRecipient.class); 161 doAnswer(invocation -> null) 162 .when(mBinder) 163 .linkToDeath(deathRecipient.capture(), eq(0) /* flags */); 164 165 try (var pin = mArtdRefCache.new Pin()) { 166 mArtdRefCache.getArtd(); 167 deathRecipient.getValue().binderDied(mBinder); 168 mArtdRefCache.getArtd(); 169 deathRecipient.getValue().binderDied(mBinder); 170 mArtdRefCache.getArtd(); 171 172 // It should not clear the cache when called with a different binder instance. 173 var differentBinder = mock(IBinder.class); 174 deathRecipient.getValue().binderDied(differentBinder); 175 mArtdRefCache.getArtd(); 176 } 177 178 verify(mInjector, times(3)).getArtd(); 179 } 180 181 @Test testComplex()182 public void testComplex() throws Exception { 183 var deathRecipient = ArgumentCaptor.forClass(DeathRecipient.class); 184 doAnswer(invocation -> null) 185 .when(mBinder) 186 .linkToDeath(deathRecipient.capture(), eq(0) /* flags */); 187 188 try (var pin = mArtdRefCache.new Pin()) { 189 // Cache miss. 190 mArtdRefCache.getArtd(); 191 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS); 192 try (var pin2 = mArtdRefCache.new Pin()) { 193 mArtdRefCache.getArtd(); 194 try (var pin3 = mArtdRefCache.new Pin()) { 195 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS); 196 mArtdRefCache.getArtd(); 197 } 198 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS); 199 } 200 mArtdRefCache.getArtd(); 201 deathRecipient.getValue().binderDied(mBinder); 202 // Cache miss. 203 mArtdRefCache.getArtd(); 204 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS - 1); 205 } 206 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS - 1); 207 try (var pin = mArtdRefCache.new Pin()) { 208 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS - 1); 209 mArtdRefCache.getArtd(); 210 } 211 mMockClock.advanceTime(ArtdRefCache.CACHE_TIMEOUT_MS); 212 try (var pin = mArtdRefCache.new Pin()) { 213 // Cache miss. 214 mArtdRefCache.getArtd(); 215 } 216 217 verify(mInjector, times(3)).getArtd(); 218 } 219 220 @Test testReset()221 public void testReset() throws Exception { 222 var queue = new ReferenceQueue<ArtdRefCache>(); 223 var phantomRef = new PhantomReference(mArtdRefCache, queue); 224 225 try (var pin = mArtdRefCache.new Pin()) { 226 mArtdRefCache.getArtd(); 227 } 228 229 // Mockito mocks hold the arguments of historical calls. `reset` removes them. 230 reset(mBinder); 231 232 mArtdRefCache.reset(); 233 mArtdRefCache = null; 234 mMockClock.advanceTime(0); // Flush the task queue. 235 Runtime.getRuntime().gc(); 236 Runtime.getRuntime().runFinalization(); 237 238 // The reference is enqueued if it's GC-able. 239 assertThat(phantomRef.isEnqueued()).isTrue(); 240 } 241 } 242