• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
20 import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
21 import static com.android.server.art.testing.TestingUtils.deepEq;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.mockito.Mockito.any;
26 import static org.mockito.Mockito.anyInt;
27 import static org.mockito.Mockito.argThat;
28 import static org.mockito.Mockito.doAnswer;
29 import static org.mockito.Mockito.doReturn;
30 import static org.mockito.Mockito.eq;
31 import static org.mockito.Mockito.inOrder;
32 import static org.mockito.Mockito.isNull;
33 import static org.mockito.Mockito.lenient;
34 import static org.mockito.Mockito.mock;
35 import static org.mockito.Mockito.never;
36 import static org.mockito.Mockito.same;
37 import static org.mockito.Mockito.times;
38 import static org.mockito.Mockito.verify;
39 import static org.mockito.Mockito.when;
40 
41 import android.os.Process;
42 import android.os.ServiceSpecificException;
43 import android.os.UserHandle;
44 
45 import androidx.test.filters.SmallTest;
46 import androidx.test.runner.AndroidJUnit4;
47 
48 import com.android.server.art.model.ArtFlags;
49 import com.android.server.art.model.DexoptParams;
50 import com.android.server.art.model.DexoptResult;
51 import com.android.server.art.testing.TestingUtils;
52 
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.mockito.InOrder;
57 
58 import java.util.ArrayList;
59 import java.util.List;
60 import java.util.concurrent.ForkJoinPool;
61 import java.util.concurrent.Future;
62 import java.util.concurrent.Semaphore;
63 import java.util.concurrent.TimeUnit;
64 import java.util.stream.Collectors;
65 
66 @SmallTest
67 @RunWith(AndroidJUnit4.class)
68 public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
69     private final String mDexPath = "/data/app/foo/base.apk";
70     private final ProfilePath mRefProfile =
71             AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "primary");
72     private final ProfilePath mPrebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(mDexPath);
73     private final ProfilePath mDmProfile = AidlUtils.buildProfilePathForDm(mDexPath);
74     private final DexMetadataPath mDmFile = AidlUtils.buildDexMetadataPath(mDexPath);
75     private final OutputProfile mPublicOutputProfile = AidlUtils.buildOutputProfileForPrimary(
76             PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, true /* isOtherReadable */);
77     private final OutputProfile mPrivateOutputProfile = AidlUtils.buildOutputProfileForPrimary(
78             PKG_NAME, "primary", Process.SYSTEM_UID, SHARED_GID, false /* isOtherReadable */);
79 
80     private final String mSplit0DexPath = "/data/app/foo/split_0.apk";
81     private final ProfilePath mSplit0RefProfile =
82             AidlUtils.buildProfilePathForPrimaryRef(PKG_NAME, "split_0.split");
83 
84     private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
85             | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
86     private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
87             | DexoptTrigger.COMPILER_FILTER_IS_SAME
88             | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
89     private final int mForceDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
90             | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE
91             | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE
92             | DexoptTrigger.NEED_EXTRACTION;
93 
94     private final MergeProfileOptions mMergeProfileOptions = new MergeProfileOptions();
95 
96     private final ArtdDexoptResult mArtdDexoptResult =
97             createArtdDexoptResult(false /* cancelled */);
98 
99     private DexoptParams mDexoptParams =
100             new DexoptParams.Builder("install").setCompilerFilter("speed-profile").build();
101 
102     private PrimaryDexopter mPrimaryDexopter;
103 
104     private List<ProfilePath> mUsedProfiles;
105 
106     @Before
setUp()107     public void setUp() throws Exception {
108         super.setUp();
109 
110         // By default, none of the profiles are usable.
111         lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(false);
112         lenient().when(mArtd.copyAndRewriteProfile(any(), any(), any())).thenReturn(false);
113 
114         // By default, no DM file exists.
115         lenient().when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
116 
117         // Dexopt is by default needed and successful.
118         lenient()
119                 .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
120                 .thenReturn(dexoptIsNeeded());
121         lenient()
122                 .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(),
123                         any(), any()))
124                 .thenReturn(mArtdDexoptResult);
125 
126         lenient()
127                 .when(mArtd.createCancellationSignal())
128                 .thenReturn(mock(IArtdCancellationSignal.class));
129 
130         mPrimaryDexopter =
131                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
132 
133         mUsedProfiles = new ArrayList<>();
134     }
135 
136     @Test
testDexoptInputVdex()137     public void testDexoptInputVdex() throws Exception {
138         // null.
139         doReturn(dexoptIsNeeded(ArtifactsLocation.NONE_OR_ERROR))
140                 .when(mArtd)
141                 .getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), any(), anyInt());
142         doReturn(mArtdDexoptResult)
143                 .when(mArtd)
144                 .dexopt(any(), eq(mDexPath), eq("arm64"), any(), any(), any(), isNull(), any(),
145                         anyInt(), any(), any());
146 
147         // ArtifactsPath, isInDalvikCache=true.
148         doReturn(dexoptIsNeeded(ArtifactsLocation.DALVIK_CACHE))
149                 .when(mArtd)
150                 .getDexoptNeeded(eq(mDexPath), eq("arm"), any(), any(), anyInt());
151         doReturn(mArtdDexoptResult)
152                 .when(mArtd)
153                 .dexopt(any(), eq(mDexPath), eq("arm"), any(), any(), any(),
154                         deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
155                                 mDexPath, "arm", true /* isInDalvikCache */))),
156                         any(), anyInt(), any(), any());
157 
158         // ArtifactsPath, isInDalvikCache=false.
159         doReturn(dexoptIsNeeded(ArtifactsLocation.NEXT_TO_DEX))
160                 .when(mArtd)
161                 .getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), any(), anyInt());
162         doReturn(mArtdDexoptResult)
163                 .when(mArtd)
164                 .dexopt(any(), eq(mSplit0DexPath), eq("arm64"), any(), any(), any(),
165                         deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPath(
166                                 mSplit0DexPath, "arm64", false /* isInDalvikCache */))),
167                         any(), anyInt(), any(), any());
168 
169         // DexMetadataPath.
170         doReturn(dexoptIsNeeded(ArtifactsLocation.DM))
171                 .when(mArtd)
172                 .getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), any(), anyInt());
173         doReturn(mArtdDexoptResult)
174                 .when(mArtd)
175                 .dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(), isNull(), any(),
176                         anyInt(), any(), any());
177 
178         mPrimaryDexopter.dexopt();
179     }
180 
181     @Test
testDexoptDm()182     public void testDexoptDm() throws Exception {
183         lenient()
184                 .when(mArtd.getDmFileVisibility(deepEq(mDmFile)))
185                 .thenReturn(FileVisibility.OTHER_READABLE);
186 
187         mPrimaryDexopter.dexopt();
188 
189         verify(mArtd, times(2))
190                 .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), deepEq(mDmFile),
191                         anyInt(),
192                         argThat(dexoptOptions
193                                 -> dexoptOptions.compilationReason.equals("install-dm")),
194                         any());
195         verify(mArtd, times(2))
196                 .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), isNull(),
197                         anyInt(),
198                         argThat(dexoptOptions -> dexoptOptions.compilationReason.equals("install")),
199                         any());
200     }
201 
202     @Test
testDexoptUsesRefProfile()203     public void testDexoptUsesRefProfile() throws Exception {
204         makeProfileUsable(mRefProfile);
205         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
206                 .thenReturn(FileVisibility.NOT_OTHER_READABLE);
207 
208         // Other profiles are also usable, but they shouldn't be used.
209         makeProfileUsable(mPrebuiltProfile);
210         makeProfileUsable(mDmProfile);
211 
212         mPrimaryDexopter.dexopt();
213 
214         verify(mArtd).getDexoptNeeded(
215                 eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
216         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile,
217                 false /* isOtherReadable */, true /* generateAppImage */);
218 
219         verify(mArtd).getDexoptNeeded(
220                 eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
221         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile,
222                 false /* isOtherReadable */, true /* generateAppImage */);
223 
224         // There is no profile for split 0, so it should fall back to "verify".
225         verify(mArtd).getDexoptNeeded(
226                 eq(mSplit0DexPath), eq("arm64"), any(), eq("verify"), eq(mDefaultDexoptTrigger));
227         checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "verify");
228 
229         verify(mArtd).getDexoptNeeded(
230                 eq(mSplit0DexPath), eq("arm"), any(), eq("verify"), eq(mDefaultDexoptTrigger));
231         checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "verify");
232 
233         verifyProfileNotUsed(mPrebuiltProfile);
234         verifyProfileNotUsed(mDmProfile);
235     }
236 
237     @Test
testDexoptUsesPublicRefProfile()238     public void testDexoptUsesPublicRefProfile() throws Exception {
239         // The ref profile is usable and public.
240         makeProfileUsable(mRefProfile);
241         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
242                 .thenReturn(FileVisibility.OTHER_READABLE);
243 
244         // Other profiles are also usable, but they shouldn't be used.
245         makeProfileUsable(mPrebuiltProfile);
246         makeProfileUsable(mDmProfile);
247 
248         mPrimaryDexopter.dexopt();
249 
250         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile,
251                 true /* isOtherReadable */, true /* generateAppImage */);
252         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile,
253                 true /* isOtherReadable */, true /* generateAppImage */);
254 
255         verifyProfileNotUsed(mPrebuiltProfile);
256         verifyProfileNotUsed(mDmProfile);
257     }
258 
259     @Test
testDexoptUsesPrebuiltProfile()260     public void testDexoptUsesPrebuiltProfile() throws Exception {
261         makeProfileNotUsable(mRefProfile);
262         makeProfileUsable(mPrebuiltProfile);
263         makeProfileUsable(mDmProfile);
264 
265         mPrimaryDexopter.dexopt();
266 
267         InOrder inOrder = inOrder(mArtd);
268 
269         inOrder.verify(mArtd).copyAndRewriteProfile(
270                 deepEq(mPrebuiltProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
271 
272         checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm64",
273                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
274                 true /* isOtherReadable */, true /* generateAppImage */);
275         checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm",
276                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
277                 true /* isOtherReadable */, true /* generateAppImage */);
278 
279         inOrder.verify(mArtd).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath));
280 
281         verifyProfileNotUsed(mRefProfile);
282         verifyProfileNotUsed(mDmProfile);
283     }
284 
285     @Test
testDexoptMergesProfiles()286     public void testDexoptMergesProfiles() throws Exception {
287         when(mPkgState.getStateForUser(eq(UserHandle.of(0)))).thenReturn(mPkgUserStateInstalled);
288         when(mPkgState.getStateForUser(eq(UserHandle.of(2)))).thenReturn(mPkgUserStateInstalled);
289 
290         when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(true);
291 
292         makeProfileUsable(mRefProfile);
293         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
294                 .thenReturn(FileVisibility.OTHER_READABLE);
295 
296         mPrimaryDexopter.dexopt();
297 
298         InOrder inOrder = inOrder(mArtd);
299 
300         inOrder.verify(mArtd).mergeProfiles(
301                 deepEq(List.of(AidlUtils.buildProfilePathForPrimaryCur(
302                                        0 /* userId */, PKG_NAME, "primary"),
303                         AidlUtils.buildProfilePathForPrimaryCur(
304                                 2 /* userId */, PKG_NAME, "primary"))),
305                 deepEq(mRefProfile), deepEq(mPrivateOutputProfile), deepEq(List.of(mDexPath)),
306                 deepEq(mMergeProfileOptions));
307 
308         // It should use `mBetterOrSameDexoptTrigger` and the merged profile for both ISAs.
309         inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), eq("speed-profile"),
310                 eq(mBetterOrSameDexoptTrigger));
311         checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm64",
312                 ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath),
313                 false /* isOtherReadable */, true /* generateAppImage */);
314 
315         inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm"), any(), eq("speed-profile"),
316                 eq(mBetterOrSameDexoptTrigger));
317         checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm",
318                 ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath),
319                 false /* isOtherReadable */, true /* generateAppImage */);
320 
321         inOrder.verify(mArtd).commitTmpProfile(deepEq(mPrivateOutputProfile.profilePath));
322 
323         inOrder.verify(mArtd).deleteProfile(deepEq(
324                 AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME, "primary")));
325         inOrder.verify(mArtd).deleteProfile(deepEq(
326                 AidlUtils.buildProfilePathForPrimaryCur(2 /* userId */, PKG_NAME, "primary")));
327     }
328 
329     @Test
testDexoptMergesProfilesMergeFailed()330     public void testDexoptMergesProfilesMergeFailed() throws Exception {
331         when(mPkgState.getStateForUser(eq(UserHandle.of(0)))).thenReturn(mPkgUserStateInstalled);
332         when(mPkgState.getStateForUser(eq(UserHandle.of(2)))).thenReturn(mPkgUserStateInstalled);
333 
334         when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
335 
336         makeProfileUsable(mRefProfile);
337         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
338                 .thenReturn(FileVisibility.OTHER_READABLE);
339 
340         mPrimaryDexopter.dexopt();
341 
342         // It should still use "speed-profile", but with the existing reference profile only.
343         verify(mArtd).getDexoptNeeded(
344                 eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
345         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64", mRefProfile,
346                 true /* isOtherReadable */, true /* generateAppImage */);
347 
348         verify(mArtd).getDexoptNeeded(
349                 eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
350         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm", mRefProfile,
351                 true /* isOtherReadable */, true /* generateAppImage */);
352 
353         verify(mArtd, never()).deleteProfile(any());
354         verify(mArtd, never()).commitTmpProfile(any());
355     }
356 
357     @Test
testDexoptUsesDmProfile()358     public void testDexoptUsesDmProfile() throws Exception {
359         makeProfileNotUsable(mRefProfile);
360         makeProfileNotUsable(mPrebuiltProfile);
361         makeProfileUsable(mDmProfile);
362 
363         mPrimaryDexopter.dexopt();
364 
365         verify(mArtd).copyAndRewriteProfile(
366                 deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
367 
368         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64",
369                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
370                 true /* isOtherReadable */, true /* generateAppImage */);
371         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm",
372                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
373                 true /* isOtherReadable */, true /* generateAppImage */);
374 
375         verifyProfileNotUsed(mRefProfile);
376         verifyProfileNotUsed(mPrebuiltProfile);
377     }
378 
379     @Test
testDexoptDeletesProfileOnFailure()380     public void testDexoptDeletesProfileOnFailure() throws Exception {
381         makeProfileNotUsable(mRefProfile);
382         makeProfileNotUsable(mPrebuiltProfile);
383         makeProfileUsable(mDmProfile);
384 
385         when(mArtd.dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
386                      any(), any()))
387                 .thenThrow(ServiceSpecificException.class);
388 
389         mPrimaryDexopter.dexopt();
390 
391         verify(mArtd).deleteProfile(
392                 deepEq(ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath)));
393         verify(mArtd, never()).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath));
394     }
395 
396     @Test
testDexoptNeedsToBeShared()397     public void testDexoptNeedsToBeShared() throws Exception {
398         when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mDexPath)))
399                 .thenReturn(true);
400         when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mSplit0DexPath)))
401                 .thenReturn(true);
402 
403         // The ref profile is usable but shouldn't be used.
404         makeProfileUsable(mRefProfile);
405 
406         makeProfileNotUsable(mPrebuiltProfile);
407         makeProfileUsable(mDmProfile);
408 
409         // The existing artifacts are private.
410         when(mArtd.getArtifactsVisibility(
411                      argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
412                 .thenReturn(FileVisibility.NOT_OTHER_READABLE);
413 
414         mPrimaryDexopter.dexopt();
415 
416         verify(mArtd).copyAndRewriteProfile(
417                 deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
418 
419         // It should re-compile anyway.
420         verify(mArtd).getDexoptNeeded(
421                 eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
422         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64",
423                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
424                 true /* isOtherReadable */, true /* generateAppImage */);
425 
426         verify(mArtd).getDexoptNeeded(
427                 eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
428         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm",
429                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
430                 true /* isOtherReadable */, true /* generateAppImage */);
431 
432         checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "speed");
433         checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "speed");
434 
435         verifyProfileNotUsed(mRefProfile);
436         verifyProfileNotUsed(mPrebuiltProfile);
437     }
438 
439     @Test
testDexoptNeedsToBeSharedArtifactsArePublic()440     public void testDexoptNeedsToBeSharedArtifactsArePublic() throws Exception {
441         // Same setup as above, but the existing artifacts are public.
442         when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mDexPath)))
443                 .thenReturn(true);
444         when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mSplit0DexPath)))
445                 .thenReturn(true);
446 
447         makeProfileUsable(mRefProfile);
448         makeProfileNotUsable(mPrebuiltProfile);
449         makeProfileUsable(mDmProfile);
450         when(mArtd.getArtifactsVisibility(
451                      argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
452                 .thenReturn(FileVisibility.OTHER_READABLE);
453 
454         mPrimaryDexopter.dexopt();
455 
456         // It should use the default dexopt trigger.
457         verify(mArtd).getDexoptNeeded(
458                 eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
459         verify(mArtd).getDexoptNeeded(
460                 eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
461     }
462 
463     @Test
testDexoptUsesProfileForSplit()464     public void testDexoptUsesProfileForSplit() throws Exception {
465         makeProfileUsable(mSplit0RefProfile);
466         when(mArtd.getProfileVisibility(deepEq(mSplit0RefProfile)))
467                 .thenReturn(FileVisibility.NOT_OTHER_READABLE);
468 
469         mPrimaryDexopter.dexopt();
470 
471         verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), eq("speed-profile"),
472                 eq(mDefaultDexoptTrigger));
473         checkDexoptWithProfile(verify(mArtd), mSplit0DexPath, "arm64", mSplit0RefProfile,
474                 false /* isOtherReadable */, false /* generateAppImage */);
475 
476         verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), eq("speed-profile"),
477                 eq(mDefaultDexoptTrigger));
478         checkDexoptWithProfile(verify(mArtd), mSplit0DexPath, "arm", mSplit0RefProfile,
479                 false /* isOtherReadable */, false /* generateAppImage */);
480     }
481 
482     @Test
testDexoptCancelledBeforeDexopt()483     public void testDexoptCancelledBeforeDexopt() throws Exception {
484         mCancellationSignal.cancel();
485 
486         var artdCancellationSignal = mock(IArtdCancellationSignal.class);
487         when(mArtd.createCancellationSignal()).thenReturn(artdCancellationSignal);
488 
489         doAnswer(invocation -> {
490             verify(artdCancellationSignal).cancel();
491             return createArtdDexoptResult(true /* cancelled */);
492         })
493                 .when(mArtd)
494                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
495                         same(artdCancellationSignal));
496 
497         // The result should only contain one element: the result of the first file with
498         // DEXOPT_CANCELLED.
499         assertThat(mPrimaryDexopter.dexopt()
500                            .stream()
501                            .map(DexContainerFileDexoptResult::getStatus)
502                            .collect(Collectors.toList()))
503                 .containsExactly(DexoptResult.DEXOPT_CANCELLED);
504 
505         // It shouldn't continue after being cancelled on the first file.
506         verify(mArtd, times(1)).createCancellationSignal();
507         verify(mArtd, times(1))
508                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
509                         any());
510     }
511 
512     @Test
testDexoptCancelledDuringDexopt()513     public void testDexoptCancelledDuringDexopt() throws Exception {
514         Semaphore dexoptStarted = new Semaphore(0);
515         Semaphore dexoptCancelled = new Semaphore(0);
516         final long TIMEOUT_SEC = 10;
517 
518         var artdCancellationSignal = mock(IArtdCancellationSignal.class);
519         when(mArtd.createCancellationSignal()).thenReturn(artdCancellationSignal);
520 
521         doAnswer(invocation -> {
522             dexoptStarted.release();
523             assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
524             return createArtdDexoptResult(true /* cancelled */);
525         })
526                 .when(mArtd)
527                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
528                         same(artdCancellationSignal));
529         doAnswer(invocation -> {
530             dexoptCancelled.release();
531             return null;
532         })
533                 .when(artdCancellationSignal)
534                 .cancel();
535 
536         Future<List<DexContainerFileDexoptResult>> results =
537                 ForkJoinPool.commonPool().submit(() -> { return mPrimaryDexopter.dexopt(); });
538 
539         assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
540 
541         mCancellationSignal.cancel();
542 
543         assertThat(results.get()
544                            .stream()
545                            .map(DexContainerFileDexoptResult::getStatus)
546                            .collect(Collectors.toList()))
547                 .containsExactly(DexoptResult.DEXOPT_CANCELLED);
548 
549         // It shouldn't continue after being cancelled on the first file.
550         verify(mArtd, times(1)).createCancellationSignal();
551         verify(mArtd, times(1))
552                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
553                         any());
554     }
555 
556     @Test
testDexoptBaseApk()557     public void testDexoptBaseApk() throws Exception {
558         mDexoptParams =
559                 new DexoptParams.Builder("install")
560                         .setCompilerFilter("speed-profile")
561                         .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
562                         .setSplitName(null)
563                         .build();
564         mPrimaryDexopter =
565                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
566 
567         mPrimaryDexopter.dexopt();
568 
569         verify(mArtd, times(2))
570                 .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
571                         any(), any());
572         verify(mArtd, never())
573                 .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), any(),
574                         anyInt(), any(), any());
575     }
576 
577     @Test
testDexoptSplitApk()578     public void testDexoptSplitApk() throws Exception {
579         mDexoptParams =
580                 new DexoptParams.Builder("install")
581                         .setCompilerFilter("speed-profile")
582                         .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
583                         .setSplitName("split_0")
584                         .build();
585         mPrimaryDexopter =
586                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
587 
588         mPrimaryDexopter.dexopt();
589 
590         verify(mArtd, never())
591                 .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
592                         any(), any());
593         verify(mArtd, times(2))
594                 .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), any(),
595                         anyInt(), any(), any());
596     }
597 
598     @Test
testDexoptStorageLow()599     public void testDexoptStorageLow() throws Exception {
600         when(mStorageManager.getAllocatableBytes(any())).thenReturn(1l, 0l, 0l, 1l);
601 
602         mDexoptParams =
603                 new DexoptParams.Builder("install")
604                         .setCompilerFilter("speed-profile")
605                         .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SKIP_IF_STORAGE_LOW)
606                         .build();
607         mPrimaryDexopter =
608                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
609 
610         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
611         assertThat(results.get(0).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
612         assertThat(results.get(0).isSkippedDueToStorageLow()).isFalse();
613         assertThat(results.get(1).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
614         assertThat(results.get(1).isSkippedDueToStorageLow()).isTrue();
615         assertThat(results.get(2).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
616         assertThat(results.get(2).isSkippedDueToStorageLow()).isTrue();
617         assertThat(results.get(3).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
618         assertThat(results.get(3).isSkippedDueToStorageLow()).isFalse();
619 
620         verify(mArtd, times(2))
621                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
622                         any());
623     }
624 
checkDexoptWithProfile(IArtd artd, String dexPath, String isa, ProfilePath profile, boolean isOtherReadable, boolean generateAppImage)625     private void checkDexoptWithProfile(IArtd artd, String dexPath, String isa, ProfilePath profile,
626             boolean isOtherReadable, boolean generateAppImage) throws Exception {
627         artd.dexopt(argThat(artifacts
628                             -> artifacts.permissionSettings.fileFsPermission.isOtherReadable
629                                     == isOtherReadable),
630                 eq(dexPath), eq(isa), any(), eq("speed-profile"), deepEq(profile), any(), any(),
631                 anyInt(),
632                 argThat(dexoptOptions -> dexoptOptions.generateAppImage == generateAppImage),
633                 any());
634     }
635 
checkDexoptWithNoProfile( IArtd artd, String dexPath, String isa, String compilerFilter)636     private void checkDexoptWithNoProfile(
637             IArtd artd, String dexPath, String isa, String compilerFilter) throws Exception {
638         artd.dexopt(
639                 argThat(artifacts
640                         -> artifacts.permissionSettings.fileFsPermission.isOtherReadable == true),
641                 eq(dexPath), eq(isa), any(), eq(compilerFilter), isNull(), any(), any(), anyInt(),
642                 argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
643     }
644 
verifyProfileNotUsed(ProfilePath profile)645     private void verifyProfileNotUsed(ProfilePath profile) throws Exception {
646         assertThat(mUsedProfiles)
647                 .comparingElementsUsing(TestingUtils.<ProfilePath>deepEquality())
648                 .doesNotContain(profile);
649     }
650 
makeProfileUsable(ProfilePath profile)651     private void makeProfileUsable(ProfilePath profile) throws Exception {
652         lenient().when(mArtd.isProfileUsable(deepEq(profile), any())).thenAnswer(invocation -> {
653             mUsedProfiles.add(invocation.<ProfilePath>getArgument(0));
654             return true;
655         });
656         lenient()
657                 .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any()))
658                 .thenAnswer(invocation -> {
659                     mUsedProfiles.add(invocation.<ProfilePath>getArgument(0));
660                     return true;
661                 });
662     }
663 
makeProfileNotUsable(ProfilePath profile)664     private void makeProfileNotUsable(ProfilePath profile) throws Exception {
665         lenient().when(mArtd.isProfileUsable(deepEq(profile), any())).thenReturn(false);
666         lenient()
667                 .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any()))
668                 .thenReturn(false);
669     }
670 }
671