blob: 4e2f9a495fe8dd9d8d4e9b9928ab4a76f81360f1 [file] [log] [blame]
Sudheer Shankaae53d112020-01-31 14:20:53 -08001/*
2 * Copyright 2020 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 */
16package com.android.server.blob;
17
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -070018import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod;
Sudheer Shankaae53d112020-01-31 14:20:53 -080019import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
20import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
21import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
Sudheer Shanka90ac4a62020-05-25 15:40:21 -070022import static com.android.server.blob.BlobStoreConfig.DeviceConfigProperties.SESSION_EXPIRY_TIMEOUT_MS;
Sudheer Shankaae53d112020-01-31 14:20:53 -080023
24import static com.google.common.truth.Truth.assertThat;
25
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -070026import static org.mockito.ArgumentMatchers.anyBoolean;
27import static org.mockito.ArgumentMatchers.anyLong;
Sudheer Shankaae53d112020-01-31 14:20:53 -080028import static org.mockito.Mockito.never;
29import static org.mockito.Mockito.verify;
30
31import android.app.blob.BlobHandle;
32import android.content.Context;
33import android.os.Handler;
34import android.os.Looper;
35import android.os.Message;
36import android.os.UserHandle;
37import android.platform.test.annotations.Presubmit;
38import android.util.ArrayMap;
39import android.util.LongSparseArray;
40
41import androidx.test.InstrumentationRegistry;
42import androidx.test.filters.SmallTest;
43import androidx.test.runner.AndroidJUnit4;
44
45import com.android.server.blob.BlobStoreManagerService.Injector;
46
47import org.junit.After;
48import org.junit.Before;
49import org.junit.Test;
50import org.junit.runner.RunWith;
51import org.mockito.Mock;
52import org.mockito.MockitoSession;
53import org.mockito.quality.Strictness;
54
55import java.io.File;
56
57@RunWith(AndroidJUnit4.class)
58@SmallTest
59@Presubmit
60public class BlobStoreManagerServiceTest {
61 private Context mContext;
62 private Handler mHandler;
63 private BlobStoreManagerService mService;
64
65 private MockitoSession mMockitoSession;
66
67 @Mock
68 private File mBlobsDir;
69
70 private LongSparseArray<BlobStoreSession> mUserSessions;
71 private ArrayMap<BlobHandle, BlobMetadata> mUserBlobs;
72
73 private static final String TEST_PKG1 = "com.example1";
74 private static final String TEST_PKG2 = "com.example2";
75 private static final String TEST_PKG3 = "com.example3";
76
77 private static final int TEST_UID1 = 10001;
78 private static final int TEST_UID2 = 10002;
79 private static final int TEST_UID3 = 10003;
80
81 @Before
82 public void setUp() {
83 // Share classloader to allow package private access.
84 System.setProperty("dexmaker.share_classloader", "true");
85
86 mMockitoSession = mockitoSession()
87 .initMocks(this)
88 .strictness(Strictness.LENIENT)
89 .mockStatic(BlobStoreConfig.class)
90 .startMocking();
91
92 doReturn(mBlobsDir).when(() -> BlobStoreConfig.getBlobsDir());
93 doReturn(true).when(mBlobsDir).exists();
94 doReturn(new File[0]).when(mBlobsDir).listFiles();
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -070095 doReturn(true).when(() -> BlobStoreConfig.hasLeaseWaitTimeElapsed(anyLong()));
Sudheer Shanka90ac4a62020-05-25 15:40:21 -070096 doCallRealMethod().when(() -> BlobStoreConfig.hasSessionExpired(anyLong()));
Sudheer Shankaae53d112020-01-31 14:20:53 -080097
98 mContext = InstrumentationRegistry.getTargetContext();
99 mHandler = new TestHandler(Looper.getMainLooper());
100 mService = new BlobStoreManagerService(mContext, new TestInjector());
101 mUserSessions = new LongSparseArray<>();
102 mUserBlobs = new ArrayMap<>();
103
104 mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId());
105 mService.addUserBlobsForTest(mUserBlobs, UserHandle.myUserId());
106 }
107
108 @After
109 public void tearDown() {
110 if (mMockitoSession != null) {
111 mMockitoSession.finishMocking();
112 }
113 }
114
115 @Test
116 public void testHandlePackageRemoved() throws Exception {
117 // Setup sessions
118 final File sessionFile1 = mock(File.class);
119 final long sessionId1 = 11;
120 final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1,
121 sessionId1, sessionFile1);
122 mUserSessions.append(sessionId1, session1);
123
124 final File sessionFile2 = mock(File.class);
125 final long sessionId2 = 25;
126 final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2,
127 sessionId2, sessionFile2);
128 mUserSessions.append(sessionId2, session2);
129
130 final File sessionFile3 = mock(File.class);
131 final long sessionId3 = 37;
132 final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3,
133 sessionId3, sessionFile3);
134 mUserSessions.append(sessionId3, session3);
135
136 final File sessionFile4 = mock(File.class);
137 final long sessionId4 = 48;
138 final BlobStoreSession session4 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1,
139 sessionId4, sessionFile4);
140 mUserSessions.append(sessionId4, session4);
141
142 // Setup blobs
143 final long blobId1 = 978;
144 final File blobFile1 = mock(File.class);
145 final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700146 "label1", System.currentTimeMillis() + 10000, "tag1");
147 final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1,
148 blobHandle1, true /* hasLeases */);
149 doReturn(true).when(blobMetadata1).isACommitter(TEST_PKG1, TEST_UID1);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800150 mUserBlobs.put(blobHandle1, blobMetadata1);
151
152 final long blobId2 = 347;
153 final File blobFile2 = mock(File.class);
154 final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(),
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700155 "label2", System.currentTimeMillis() + 20000, "tag2");
156 final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2,
157 blobHandle2, false /* hasLeases */);
158 doReturn(false).when(blobMetadata2).isACommitter(TEST_PKG1, TEST_UID1);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800159 mUserBlobs.put(blobHandle2, blobMetadata2);
160
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700161 final long blobId3 = 49875;
162 final File blobFile3 = mock(File.class);
163 final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(),
164 "label3", System.currentTimeMillis() - 1000, "tag3");
165 final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3,
166 blobHandle3, true /* hasLeases */);
167 doReturn(true).when(blobMetadata3).isACommitter(TEST_PKG1, TEST_UID1);
168 mUserBlobs.put(blobHandle3, blobMetadata3);
169
Sudheer Shanka60803032020-02-13 12:47:59 -0800170 mService.addActiveIdsForTest(sessionId1, sessionId2, sessionId3, sessionId4,
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700171 blobId1, blobId2, blobId3);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800172
173 // Invoke test method
174 mService.handlePackageRemoved(TEST_PKG1, TEST_UID1);
175
176 // Verify sessions are removed
Sudheer Shanka9ed72492020-06-24 13:33:18 -0700177 verify(session1).destroy();
178 verify(session2, never()).destroy();
179 verify(session3, never()).destroy();
180 verify(session4).destroy();
Sudheer Shankaae53d112020-01-31 14:20:53 -0800181
182 assertThat(mUserSessions.size()).isEqualTo(2);
183 assertThat(mUserSessions.get(sessionId1)).isNull();
184 assertThat(mUserSessions.get(sessionId2)).isNotNull();
185 assertThat(mUserSessions.get(sessionId3)).isNotNull();
186 assertThat(mUserSessions.get(sessionId4)).isNull();
187
188 // Verify blobs are removed
189 verify(blobMetadata1).removeCommitter(TEST_PKG1, TEST_UID1);
190 verify(blobMetadata1).removeLeasee(TEST_PKG1, TEST_UID1);
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700191 verify(blobMetadata2, never()).removeCommitter(TEST_PKG1, TEST_UID1);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800192 verify(blobMetadata2).removeLeasee(TEST_PKG1, TEST_UID1);
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700193 verify(blobMetadata3).removeCommitter(TEST_PKG1, TEST_UID1);
194 verify(blobMetadata3).removeLeasee(TEST_PKG1, TEST_UID1);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800195
Sudheer Shanka9ed72492020-06-24 13:33:18 -0700196 verify(blobMetadata1, never()).destroy();
197 verify(blobMetadata2).destroy();
198 verify(blobMetadata3).destroy();
Sudheer Shankaae53d112020-01-31 14:20:53 -0800199
200 assertThat(mUserBlobs.size()).isEqualTo(1);
201 assertThat(mUserBlobs.get(blobHandle1)).isNotNull();
202 assertThat(mUserBlobs.get(blobHandle2)).isNull();
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700203 assertThat(mUserBlobs.get(blobHandle3)).isNull();
Sudheer Shankaae53d112020-01-31 14:20:53 -0800204
Sudheer Shanka60803032020-02-13 12:47:59 -0800205 assertThat(mService.getActiveIdsForTest()).containsExactly(
Sudheer Shankaae53d112020-01-31 14:20:53 -0800206 sessionId2, sessionId3, blobId1);
Sudheer Shanka60803032020-02-13 12:47:59 -0800207 assertThat(mService.getKnownIdsForTest()).containsExactly(
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700208 sessionId1, sessionId2, sessionId3, sessionId4, blobId1, blobId2, blobId3);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800209 }
210
211 @Test
212 public void testHandleIdleMaintenance_deleteUnknownBlobs() throws Exception {
213 // Setup blob files
214 final long testId1 = 286;
215 final File file1 = mock(File.class);
216 doReturn(String.valueOf(testId1)).when(file1).getName();
217 final long testId2 = 349;
218 final File file2 = mock(File.class);
219 doReturn(String.valueOf(testId2)).when(file2).getName();
220 final long testId3 = 7355;
221 final File file3 = mock(File.class);
222 doReturn(String.valueOf(testId3)).when(file3).getName();
223
224 doReturn(new File[] {file1, file2, file3}).when(mBlobsDir).listFiles();
Sudheer Shanka60803032020-02-13 12:47:59 -0800225 mService.addActiveIdsForTest(testId1, testId3);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800226
227 // Invoke test method
228 mService.handleIdleMaintenanceLocked();
229
Sudheer Shanka60803032020-02-13 12:47:59 -0800230 // Verify unknown blobs are deleted
Sudheer Shankaae53d112020-01-31 14:20:53 -0800231 verify(file1, never()).delete();
232 verify(file2).delete();
233 verify(file3, never()).delete();
234 }
235
Sudheer Shankaae53d112020-01-31 14:20:53 -0800236 @Test
237 public void testHandleIdleMaintenance_deleteStaleSessions() throws Exception {
238 // Setup sessions
239 final File sessionFile1 = mock(File.class);
Sudheer Shanka90ac4a62020-05-25 15:40:21 -0700240 doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MS + 1000)
Sudheer Shankaae53d112020-01-31 14:20:53 -0800241 .when(sessionFile1).lastModified();
242 final long sessionId1 = 342;
Sudheer Shanka7c0111a2020-02-07 11:38:36 -0800243 final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
244 "label1", System.currentTimeMillis() - 1000, "tag1");
Sudheer Shankaae53d112020-01-31 14:20:53 -0800245 final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1,
246 sessionId1, sessionFile1, blobHandle1);
247 mUserSessions.append(sessionId1, session1);
248
249 final File sessionFile2 = mock(File.class);
250 doReturn(System.currentTimeMillis() - 20000)
251 .when(sessionFile2).lastModified();
252 final long sessionId2 = 4597;
Sudheer Shanka7c0111a2020-02-07 11:38:36 -0800253 final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(),
254 "label2", System.currentTimeMillis() + 20000, "tag2");
Sudheer Shankaae53d112020-01-31 14:20:53 -0800255 final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2,
256 sessionId2, sessionFile2, blobHandle2);
257 mUserSessions.append(sessionId2, session2);
258
259 final File sessionFile3 = mock(File.class);
Sudheer Shanka90ac4a62020-05-25 15:40:21 -0700260 doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MS - 2000)
Sudheer Shankaae53d112020-01-31 14:20:53 -0800261 .when(sessionFile3).lastModified();
262 final long sessionId3 = 9484;
Sudheer Shanka7c0111a2020-02-07 11:38:36 -0800263 final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(),
264 "label3", System.currentTimeMillis() + 30000, "tag3");
Sudheer Shankaae53d112020-01-31 14:20:53 -0800265 final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3,
266 sessionId3, sessionFile3, blobHandle3);
267 mUserSessions.append(sessionId3, session3);
268
Sudheer Shanka60803032020-02-13 12:47:59 -0800269 mService.addActiveIdsForTest(sessionId1, sessionId2, sessionId3);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800270
271 // Invoke test method
272 mService.handleIdleMaintenanceLocked();
273
274 // Verify stale sessions are removed
Sudheer Shanka9ed72492020-06-24 13:33:18 -0700275 verify(session1).destroy();
276 verify(session2, never()).destroy();
277 verify(session3).destroy();
Sudheer Shankaae53d112020-01-31 14:20:53 -0800278
279 assertThat(mUserSessions.size()).isEqualTo(1);
280 assertThat(mUserSessions.get(sessionId2)).isNotNull();
281
Sudheer Shanka60803032020-02-13 12:47:59 -0800282 assertThat(mService.getActiveIdsForTest()).containsExactly(sessionId2);
283 assertThat(mService.getKnownIdsForTest()).containsExactly(
284 sessionId1, sessionId2, sessionId3);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800285 }
286
287 @Test
288 public void testHandleIdleMaintenance_deleteStaleBlobs() throws Exception {
289 // Setup blobs
290 final long blobId1 = 3489;
291 final File blobFile1 = mock(File.class);
292 final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(),
293 "label1", System.currentTimeMillis() - 2000, "tag1");
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700294 final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, blobHandle1,
295 true /* hasLeases */);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800296 mUserBlobs.put(blobHandle1, blobMetadata1);
297
298 final long blobId2 = 78974;
299 final File blobFile2 = mock(File.class);
300 final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(),
301 "label2", System.currentTimeMillis() + 30000, "tag2");
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700302 final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, blobHandle2,
303 true /* hasLeases */);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800304 mUserBlobs.put(blobHandle2, blobMetadata2);
305
306 final long blobId3 = 97;
307 final File blobFile3 = mock(File.class);
308 final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(),
309 "label3", System.currentTimeMillis() + 4400000, "tag3");
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700310 final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, blobHandle3,
311 false /* hasLeases */);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800312 mUserBlobs.put(blobHandle3, blobMetadata3);
313
Sudheer Shanka60803032020-02-13 12:47:59 -0800314 mService.addActiveIdsForTest(blobId1, blobId2, blobId3);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800315
316 // Invoke test method
317 mService.handleIdleMaintenanceLocked();
318
319 // Verify stale blobs are removed
Sudheer Shanka9ed72492020-06-24 13:33:18 -0700320 verify(blobMetadata1).destroy();
321 verify(blobMetadata2, never()).destroy();
322 verify(blobMetadata3).destroy();
Sudheer Shankaae53d112020-01-31 14:20:53 -0800323
324 assertThat(mUserBlobs.size()).isEqualTo(1);
325 assertThat(mUserBlobs.get(blobHandle2)).isNotNull();
326
Sudheer Shanka60803032020-02-13 12:47:59 -0800327 assertThat(mService.getActiveIdsForTest()).containsExactly(blobId2);
328 assertThat(mService.getKnownIdsForTest()).containsExactly(blobId1, blobId2, blobId3);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800329 }
330
Sudheer Shanka364364b2020-02-19 17:56:09 -0800331 @Test
332 public void testGetTotalUsageBytes() throws Exception {
333 // Setup blobs
334 final BlobMetadata blobMetadata1 = mock(BlobMetadata.class);
335 final long size1 = 4567;
336 doReturn(size1).when(blobMetadata1).getSize();
337 doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG1, TEST_UID1);
338 doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG2, TEST_UID2);
339 mUserBlobs.put(mock(BlobHandle.class), blobMetadata1);
340
341 final BlobMetadata blobMetadata2 = mock(BlobMetadata.class);
342 final long size2 = 89475;
343 doReturn(size2).when(blobMetadata2).getSize();
344 doReturn(false).when(blobMetadata2).isALeasee(TEST_PKG1, TEST_UID1);
345 doReturn(true).when(blobMetadata2).isALeasee(TEST_PKG2, TEST_UID2);
346 mUserBlobs.put(mock(BlobHandle.class), blobMetadata2);
347
348 final BlobMetadata blobMetadata3 = mock(BlobMetadata.class);
349 final long size3 = 328732;
350 doReturn(size3).when(blobMetadata3).getSize();
351 doReturn(true).when(blobMetadata3).isALeasee(TEST_PKG1, TEST_UID1);
352 doReturn(false).when(blobMetadata3).isALeasee(TEST_PKG2, TEST_UID2);
353 mUserBlobs.put(mock(BlobHandle.class), blobMetadata3);
354
355 // Verify usage is calculated correctly
356 assertThat(mService.getTotalUsageBytesLocked(TEST_UID1, TEST_PKG1))
357 .isEqualTo(size1 + size3);
358 assertThat(mService.getTotalUsageBytesLocked(TEST_UID2, TEST_PKG2))
359 .isEqualTo(size1 + size2);
360 }
361
Sudheer Shankaae53d112020-01-31 14:20:53 -0800362 private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid,
363 long sessionId, File sessionFile) {
364 return createBlobStoreSessionMock(ownerPackageName, ownerUid, sessionId, sessionFile,
365 mock(BlobHandle.class));
366 }
367 private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid,
368 long sessionId, File sessionFile, BlobHandle blobHandle) {
369 final BlobStoreSession session = mock(BlobStoreSession.class);
370 doReturn(ownerPackageName).when(session).getOwnerPackageName();
371 doReturn(ownerUid).when(session).getOwnerUid();
372 doReturn(sessionId).when(session).getSessionId();
373 doReturn(sessionFile).when(session).getSessionFile();
374 doReturn(blobHandle).when(session).getBlobHandle();
Sudheer Shankab2a17b72020-05-28 01:14:10 -0700375 doCallRealMethod().when(session).isExpired();
Sudheer Shankaae53d112020-01-31 14:20:53 -0800376 return session;
377 }
378
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700379 private BlobMetadata createBlobMetadataMock(long blobId, File blobFile,
380 BlobHandle blobHandle, boolean hasLeases) {
Sudheer Shankaae53d112020-01-31 14:20:53 -0800381 final BlobMetadata blobMetadata = mock(BlobMetadata.class);
382 doReturn(blobId).when(blobMetadata).getBlobId();
383 doReturn(blobFile).when(blobMetadata).getBlobFile();
384 doReturn(hasLeases).when(blobMetadata).hasLeases();
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700385 doReturn(blobHandle).when(blobMetadata).getBlobHandle();
386 doCallRealMethod().when(blobMetadata).shouldBeDeleted(anyBoolean());
Sudheer Shankaaf3f8872020-03-24 11:26:23 -0700387 doReturn(true).when(blobMetadata).hasLeaseWaitTimeElapsedForAll();
Sudheer Shankaae53d112020-01-31 14:20:53 -0800388 return blobMetadata;
389 }
390
391 private class TestHandler extends Handler {
392 TestHandler(Looper looper) {
393 super(looper);
394 }
395
396 @Override
397 public void dispatchMessage(Message msg) {
398 // Ignore all messages
399 }
400 }
401
402 private class TestInjector extends Injector {
403 @Override
404 public Handler initializeMessageHandler() {
405 return mHandler;
406 }
Sudheer Shankab76f7662020-02-27 15:17:43 -0800407
408 @Override
409 public Handler getBackgroundHandler() {
410 return mHandler;
411 }
Sudheer Shankaae53d112020-01-31 14:20:53 -0800412 }
413}