• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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