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.model.DexoptResult.DexContainerFileDexoptResult;
20 import static com.android.server.art.testing.TestingUtils.deepEq;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 
24 import static org.mockito.Mockito.any;
25 import static org.mockito.Mockito.anyInt;
26 import static org.mockito.Mockito.argThat;
27 import static org.mockito.Mockito.doAnswer;
28 import static org.mockito.Mockito.doReturn;
29 import static org.mockito.Mockito.eq;
30 import static org.mockito.Mockito.inOrder;
31 import static org.mockito.Mockito.isNull;
32 import static org.mockito.Mockito.lenient;
33 import static org.mockito.Mockito.mock;
34 import static org.mockito.Mockito.never;
35 import static org.mockito.Mockito.same;
36 import static org.mockito.Mockito.times;
37 import static org.mockito.Mockito.verify;
38 import static org.mockito.Mockito.when;
39 
40 import android.os.Process;
41 import android.os.ServiceSpecificException;
42 import android.os.UserHandle;
43 
44 import androidx.test.filters.SmallTest;
45 import androidx.test.runner.AndroidJUnit4;
46 
47 import com.android.server.art.model.ArtFlags;
48 import com.android.server.art.model.DexoptParams;
49 import com.android.server.art.model.DexoptResult;
50 import com.android.server.art.proto.DexMetadataConfig;
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.nio.file.NoSuchFileException;
59 import java.util.ArrayList;
60 import java.util.List;
61 import java.util.concurrent.ForkJoinPool;
62 import java.util.concurrent.Future;
63 import java.util.concurrent.Semaphore;
64 import java.util.concurrent.TimeUnit;
65 import java.util.stream.Collectors;
66 import java.util.zip.ZipFile;
67 
68 @SmallTest
69 @RunWith(AndroidJUnit4.class)
70 public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
71     private final String mDexPath = "/somewhere/app/foo/base.apk";
72     private final String mDmPath = "/somewhere/app/foo/base.dm";
73     private final ProfilePath mRefProfile =
74             AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME, "primary");
75     private final ProfilePath mPrebuiltProfile = AidlUtils.buildProfilePathForPrebuilt(mDexPath);
76     private final ProfilePath mDmProfile = AidlUtils.buildProfilePathForDm(mDexPath);
77     private final DexMetadataPath mDmFile = AidlUtils.buildDexMetadataPath(mDexPath);
78     private final OutputProfile mPublicOutputProfile =
79             AidlUtils.buildOutputProfileForPrimary(PKG_NAME, "primary", Process.SYSTEM_UID,
80                     SHARED_GID, true /* isOtherReadable */, false /* isPreReboot */);
81     private final OutputProfile mPrivateOutputProfile =
82             AidlUtils.buildOutputProfileForPrimary(PKG_NAME, "primary", Process.SYSTEM_UID,
83                     SHARED_GID, false /* isOtherReadable */, false /* isPreReboot */);
84 
85     private final String mSplit0DexPath = "/somewhere/app/foo/split_0.apk";
86     private final ProfilePath mSplit0RefProfile =
87             AidlUtils.buildProfilePathForPrimaryRefAsInput(PKG_NAME, "split_0.split");
88 
89     private final int mDefaultDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
90             | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
91     private final int mBetterOrSameDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
92             | DexoptTrigger.COMPILER_FILTER_IS_SAME
93             | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
94     private final int mForceDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
95             | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE
96             | DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE
97             | DexoptTrigger.NEED_EXTRACTION;
98 
99     private final MergeProfileOptions mMergeProfileOptions = new MergeProfileOptions();
100 
101     private final ArtdDexoptResult mArtdDexoptResult =
102             createArtdDexoptResult(false /* cancelled */);
103 
104     private DexoptParams mDexoptParams =
105             new DexoptParams.Builder("install").setCompilerFilter("speed-profile").build();
106 
107     private PrimaryDexopter mPrimaryDexopter;
108 
109     private List<ProfilePath> mUsedProfiles;
110     private List<String> mUsedEmbeddedProfiles;
111 
112     @Before
setUp()113     public void setUp() throws Exception {
114         super.setUp();
115 
116         // By default, none of the profiles are usable.
117         lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(false);
118         lenient()
119                 .when(mArtd.copyAndRewriteProfile(any(), any(), any()))
120                 .thenReturn(TestingUtils.createCopyAndRewriteProfileNoProfile());
121         lenient()
122                 .when(mArtd.copyAndRewriteEmbeddedProfile(any(), any()))
123                 .thenReturn(TestingUtils.createCopyAndRewriteProfileNoProfile());
124 
125         // By default, no DM file exists.
126         lenient()
127                 .when(mDexMetadataHelperInjector.openZipFile(any()))
128                 .thenThrow(NoSuchFileException.class);
129 
130         // By default, no artifacts exist.
131         lenient().when(mArtd.getArtifactsVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
132 
133         // Dexopt is by default needed and successful.
134         lenient()
135                 .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
136                 .thenReturn(dexoptIsNeeded());
137         lenient()
138                 .when(mArtd.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(),
139                         any(), any()))
140                 .thenReturn(mArtdDexoptResult);
141 
142         lenient()
143                 .when(mArtd.createCancellationSignal())
144                 .thenReturn(mock(IArtdCancellationSignal.class));
145 
146         lenient()
147                 .when(mArtd.getDexFileVisibility(mDexPath))
148                 .thenReturn(FileVisibility.OTHER_READABLE);
149         lenient()
150                 .when(mArtd.getDexFileVisibility(mSplit0DexPath))
151                 .thenReturn(FileVisibility.OTHER_READABLE);
152 
153         mPrimaryDexopter =
154                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
155 
156         mUsedProfiles = new ArrayList<>();
157         mUsedEmbeddedProfiles = new ArrayList<>();
158     }
159 
160     @Test
testDexoptInputVdex()161     public void testDexoptInputVdex() throws Exception {
162         // null.
163         doReturn(dexoptIsNeeded(ArtifactsLocation.NONE_OR_ERROR))
164                 .when(mArtd)
165                 .getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), any(), anyInt());
166         doReturn(mArtdDexoptResult)
167                 .when(mArtd)
168                 .dexopt(any(), eq(mDexPath), eq("arm64"), any(), any(), any(), isNull(), any(),
169                         anyInt(), any(), any());
170 
171         // ArtifactsPath, isInDalvikCache=true.
172         doReturn(dexoptIsNeeded(ArtifactsLocation.DALVIK_CACHE))
173                 .when(mArtd)
174                 .getDexoptNeeded(eq(mDexPath), eq("arm"), any(), any(), anyInt());
175         doReturn(mArtdDexoptResult)
176                 .when(mArtd)
177                 .dexopt(any(), eq(mDexPath), eq("arm"), any(), any(), any(),
178                         deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput(
179                                 mDexPath, "arm", true /* isInDalvikCache */))),
180                         any(), anyInt(), any(), any());
181 
182         // ArtifactsPath, isInDalvikCache=false.
183         doReturn(dexoptIsNeeded(ArtifactsLocation.NEXT_TO_DEX))
184                 .when(mArtd)
185                 .getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), any(), anyInt());
186         doReturn(mArtdDexoptResult)
187                 .when(mArtd)
188                 .dexopt(any(), eq(mSplit0DexPath), eq("arm64"), any(), any(), any(),
189                         deepEq(VdexPath.artifactsPath(AidlUtils.buildArtifactsPathAsInput(
190                                 mSplit0DexPath, "arm64", false /* isInDalvikCache */))),
191                         any(), anyInt(), any(), any());
192 
193         // DexMetadataPath.
194         doReturn(dexoptIsNeeded(ArtifactsLocation.DM))
195                 .when(mArtd)
196                 .getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), any(), anyInt());
197         doReturn(mArtdDexoptResult)
198                 .when(mArtd)
199                 .dexopt(any(), eq(mSplit0DexPath), eq("arm"), any(), any(), any(), isNull(), any(),
200                         anyInt(), any(), any());
201 
202         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
203         verifyStatusAllOk(results);
204     }
205 
206     @Test
testDexoptDm()207     public void testDexoptDm() throws Exception {
208         String dmPath = TestingUtils.createTempZipWithEntry("primary.vdex", new byte[0] /* data */);
209         doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
210 
211         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
212         verifyStatusAllOk(results);
213 
214         verify(mArtd, times(2))
215                 .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), deepEq(mDmFile),
216                         anyInt(),
217                         argThat(dexoptOptions
218                                 -> dexoptOptions.compilationReason.equals("install-dm")),
219                         any());
220         verify(mArtd, times(2))
221                 .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), isNull(),
222                         anyInt(),
223                         argThat(dexoptOptions -> dexoptOptions.compilationReason.equals("install")),
224                         any());
225     }
226 
227     @Test
testDexoptUsesRefProfile()228     public void testDexoptUsesRefProfile() throws Exception {
229         makeProfileUsable(mRefProfile);
230         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
231                 .thenReturn(FileVisibility.NOT_OTHER_READABLE);
232 
233         // Other profiles are also usable, but they shouldn't be used.
234         makeProfileUsable(mPrebuiltProfile);
235         makeProfileUsable(mDmProfile);
236         makeEmbeddedProfileUsable(mDexPath);
237 
238         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
239         verifyStatusAllOk(results);
240 
241         verify(mArtd).getDexoptNeeded(
242                 eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
243         checkDexoptWithProfile(
244                 verify(mArtd), mDexPath, "arm64", mRefProfile, false /* isOtherReadable */);
245 
246         verify(mArtd).getDexoptNeeded(
247                 eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
248         checkDexoptWithProfile(
249                 verify(mArtd), mDexPath, "arm", mRefProfile, false /* isOtherReadable */);
250 
251         // There is no profile for split 0, so it should fall back to "verify".
252         verify(mArtd).getDexoptNeeded(
253                 eq(mSplit0DexPath), eq("arm64"), any(), eq("verify"), eq(mDefaultDexoptTrigger));
254         checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "verify");
255 
256         verify(mArtd).getDexoptNeeded(
257                 eq(mSplit0DexPath), eq("arm"), any(), eq("verify"), eq(mDefaultDexoptTrigger));
258         checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "verify");
259 
260         verifyProfileNotUsed(mPrebuiltProfile);
261         verifyProfileNotUsed(mDmProfile);
262         verifyEmbeddedProfileNotUsed(mDexPath);
263     }
264 
265     @Test
testDexoptUsesPublicRefProfile()266     public void testDexoptUsesPublicRefProfile() throws Exception {
267         // The ref profile is usable and public.
268         makeProfileUsable(mRefProfile);
269         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
270                 .thenReturn(FileVisibility.OTHER_READABLE);
271 
272         // Other profiles are also usable, but they shouldn't be used.
273         makeProfileUsable(mPrebuiltProfile);
274         makeProfileUsable(mDmProfile);
275         makeEmbeddedProfileUsable(mDexPath);
276 
277         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
278         verifyStatusAllOk(results);
279 
280         checkDexoptWithProfile(
281                 verify(mArtd), mDexPath, "arm64", mRefProfile, true /* isOtherReadable */);
282         checkDexoptWithProfile(
283                 verify(mArtd), mDexPath, "arm", mRefProfile, true /* isOtherReadable */);
284 
285         verifyProfileNotUsed(mPrebuiltProfile);
286         verifyProfileNotUsed(mDmProfile);
287         verifyEmbeddedProfileNotUsed(mDexPath);
288     }
289 
290     @Test
testDexoptUsesPrebuiltProfile()291     public void testDexoptUsesPrebuiltProfile() throws Exception {
292         makeProfileUsable(mPrebuiltProfile);
293         makeProfileUsable(mDmProfile);
294         makeEmbeddedProfileUsable(mDexPath);
295 
296         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
297         verifyStatusAllOk(results);
298 
299         InOrder inOrder = inOrder(mArtd);
300 
301         inOrder.verify(mArtd).copyAndRewriteProfile(
302                 deepEq(mPrebuiltProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
303 
304         checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm64",
305                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
306                 true /* isOtherReadable */);
307         checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm",
308                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
309                 true /* isOtherReadable */);
310 
311         inOrder.verify(mArtd).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath));
312 
313         verifyProfileNotUsed(mRefProfile);
314         verifyProfileNotUsed(mDmProfile);
315         verifyEmbeddedProfileNotUsed(mDexPath);
316     }
317 
checkDexoptMergesProfiles()318     private void checkDexoptMergesProfiles() throws Exception {
319         setPackageInstalledForUserIds(0, 2);
320 
321         when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(true);
322 
323         makeProfileUsable(mRefProfile);
324         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
325                 .thenReturn(FileVisibility.OTHER_READABLE);
326 
327         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
328         verifyStatusAllOk(results);
329 
330         InOrder inOrder = inOrder(mArtd);
331 
332         inOrder.verify(mArtd).mergeProfiles(
333                 deepEq(List.of(AidlUtils.buildProfilePathForPrimaryCur(
334                                        0 /* userId */, PKG_NAME, "primary"),
335                         AidlUtils.buildProfilePathForPrimaryCur(
336                                 2 /* userId */, PKG_NAME, "primary"))),
337                 deepEq(mRefProfile), deepEq(mPrivateOutputProfile), deepEq(List.of(mDexPath)),
338                 deepEq(mMergeProfileOptions));
339 
340         // It should use `mBetterOrSameDexoptTrigger` and the merged profile for both ISAs.
341         inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm64"), any(), eq("speed-profile"),
342                 eq(mBetterOrSameDexoptTrigger));
343         checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm64",
344                 ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath),
345                 false /* isOtherReadable */);
346 
347         inOrder.verify(mArtd).getDexoptNeeded(eq(mDexPath), eq("arm"), any(), eq("speed-profile"),
348                 eq(mBetterOrSameDexoptTrigger));
349         checkDexoptWithProfile(inOrder.verify(mArtd), mDexPath, "arm",
350                 ProfilePath.tmpProfilePath(mPrivateOutputProfile.profilePath),
351                 false /* isOtherReadable */);
352 
353         inOrder.verify(mArtd).commitTmpProfile(deepEq(mPrivateOutputProfile.profilePath));
354     }
355 
356     @Test
testDexoptMergesProfiles()357     public void testDexoptMergesProfiles() throws Exception {
358         checkDexoptMergesProfiles();
359 
360         verify(mArtd).deleteProfile(deepEq(
361                 AidlUtils.buildProfilePathForPrimaryCur(0 /* userId */, PKG_NAME, "primary")));
362         verify(mArtd).deleteProfile(deepEq(
363                 AidlUtils.buildProfilePathForPrimaryCur(2 /* userId */, PKG_NAME, "primary")));
364     }
365 
366     @Test
testDexoptMergesProfilesPreReboot()367     public void testDexoptMergesProfilesPreReboot() throws Exception {
368         when(mInjector.isPreReboot()).thenReturn(true);
369         mPublicOutputProfile.profilePath.finalPath.getForPrimary().isPreReboot = true;
370         mPrivateOutputProfile.profilePath.finalPath.getForPrimary().isPreReboot = true;
371 
372         checkDexoptMergesProfiles();
373 
374         verify(mArtd, never()).deleteProfile(any());
375     }
376 
377     @Test
testDexoptMergesProfilesMergeFailed()378     public void testDexoptMergesProfilesMergeFailed() throws Exception {
379         setPackageInstalledForUserIds(0, 2);
380 
381         when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
382 
383         makeProfileUsable(mRefProfile);
384         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
385                 .thenReturn(FileVisibility.OTHER_READABLE);
386 
387         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
388         verifyStatusAllOk(results);
389 
390         // It should still use "speed-profile", but with the existing reference profile only.
391         verify(mArtd).getDexoptNeeded(
392                 eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
393         checkDexoptWithProfile(
394                 verify(mArtd), mDexPath, "arm64", mRefProfile, true /* isOtherReadable */);
395 
396         verify(mArtd).getDexoptNeeded(
397                 eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
398         checkDexoptWithProfile(
399                 verify(mArtd), mDexPath, "arm", mRefProfile, true /* isOtherReadable */);
400 
401         verify(mArtd, never()).deleteProfile(any());
402         verify(mArtd, never()).commitTmpProfile(any());
403     }
404 
405     @Test
testDexoptMergesProfilesForceMerge()406     public void testDexoptMergesProfilesForceMerge() throws Exception {
407         mDexoptParams = mDexoptParams.toBuilder()
408                                 .setFlags(ArtFlags.FLAG_FORCE_MERGE_PROFILE,
409                                         ArtFlags.FLAG_FORCE_MERGE_PROFILE)
410                                 .build();
411         mPrimaryDexopter =
412                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
413 
414         setPackageInstalledForUserIds(0, 2);
415 
416         mMergeProfileOptions.forceMerge = true;
417         when(mArtd.mergeProfiles(any(), any(), any(), any(), deepEq(mMergeProfileOptions)))
418                 .thenReturn(true);
419 
420         makeProfileUsable(mRefProfile);
421         when(mArtd.getProfileVisibility(deepEq(mRefProfile)))
422                 .thenReturn(FileVisibility.OTHER_READABLE);
423 
424         mPrimaryDexopter.dexopt();
425     }
426 
checkDexoptUsesDmProfile()427     private void checkDexoptUsesDmProfile() throws Exception {
428         makeProfileUsable(mDmProfile);
429         makeEmbeddedProfileUsable(mDexPath);
430 
431         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
432         verifyStatusAllOk(results);
433 
434         verify(mArtd).copyAndRewriteProfile(
435                 deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
436 
437         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64",
438                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
439                 true /* isOtherReadable */);
440         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm",
441                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
442                 true /* isOtherReadable */);
443 
444         verifyProfileNotUsed(mRefProfile);
445         verifyProfileNotUsed(mPrebuiltProfile);
446         verifyEmbeddedProfileNotUsed(mDexPath);
447     }
448 
449     @Test
testDexoptUsesDmProfile()450     public void testDexoptUsesDmProfile() throws Exception {
451         checkDexoptUsesDmProfile();
452     }
453 
454     @Test
testDexoptUsesDmProfilePreReboot()455     public void testDexoptUsesDmProfilePreReboot() throws Exception {
456         when(mInjector.isPreReboot()).thenReturn(true);
457         mPublicOutputProfile.profilePath.finalPath.getForPrimary().isPreReboot = true;
458         mPrivateOutputProfile.profilePath.finalPath.getForPrimary().isPreReboot = true;
459 
460         checkDexoptUsesDmProfile();
461     }
462 
checkDexoptUsesEmbeddedProfile()463     private void checkDexoptUsesEmbeddedProfile() throws Exception {
464         makeEmbeddedProfileUsable(mDexPath);
465 
466         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
467         verifyStatusAllOk(results);
468 
469         verify(mArtd).copyAndRewriteProfile(
470                 deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
471 
472         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64",
473                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
474                 true /* isOtherReadable */);
475         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm",
476                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
477                 true /* isOtherReadable */);
478 
479         verifyProfileNotUsed(mRefProfile);
480         verifyProfileNotUsed(mPrebuiltProfile);
481         verifyProfileNotUsed(mDmProfile);
482     }
483 
484     @Test
testDexoptUsesEmbeddedProfileNoDm()485     public void testDexoptUsesEmbeddedProfileNoDm() throws Exception {
486         checkDexoptUsesEmbeddedProfile();
487     }
488 
489     @Test
testDexoptUsesEmbeddedProfileDmNoConfig()490     public void testDexoptUsesEmbeddedProfileDmNoConfig() throws Exception {
491         String dmPath = TestingUtils.createTempZipWithEntry("primary.vdex", new byte[0] /* data */);
492         doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
493         checkDexoptUsesEmbeddedProfile();
494     }
495 
496     @Test
testDexoptUsesEmbeddedProfileDmEmptyConfig()497     public void testDexoptUsesEmbeddedProfileDmEmptyConfig() throws Exception {
498         String dmPath = TestingUtils.createTempZipWithEntry("config.pb", new byte[0] /* data */);
499         doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
500         checkDexoptUsesEmbeddedProfile();
501     }
502 
503     @Test
testDexoptUsesEmbeddedProfileDmBadConfig()504     public void testDexoptUsesEmbeddedProfileDmBadConfig() throws Exception {
505         String dmPath = TestingUtils.createTempZipWithEntry(
506                 "config.pb", new byte[] {0x42, 0x43, 0x44} /* data */);
507         doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
508         checkDexoptUsesEmbeddedProfile();
509     }
510 
511     @Test
testDexoptUsesEmbeddedProfileDmDisableEmbeddedProfile()512     public void testDexoptUsesEmbeddedProfileDmDisableEmbeddedProfile() throws Exception {
513         var config = DexMetadataConfig.newBuilder().setEnableEmbeddedProfile(false).build();
514         String dmPath = TestingUtils.createTempZipWithEntry("config.pb", config.toByteArray());
515         doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
516 
517         makeEmbeddedProfileUsable(mDexPath);
518 
519         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
520         verifyStatusAllOk(results);
521 
522         checkDexoptWithNoProfile(verify(mArtd), mDexPath, "arm64", "verify");
523         checkDexoptWithNoProfile(verify(mArtd), mDexPath, "arm", "verify");
524 
525         verifyEmbeddedProfileNotUsed(mDexPath);
526     }
527 
528     @Test
testDexoptUsesEmbeddedProfileNoEmbeddedProfile()529     public void testDexoptUsesEmbeddedProfileNoEmbeddedProfile() throws Exception {
530         var config = DexMetadataConfig.newBuilder().setEnableEmbeddedProfile(true).build();
531         String dmPath = TestingUtils.createTempZipWithEntry("config.pb", config.toByteArray());
532         doReturn(new ZipFile(dmPath)).when(mDexMetadataHelperInjector).openZipFile(mDmPath);
533 
534         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
535         verifyStatusAllOk(results);
536 
537         checkDexoptWithNoProfile(verify(mArtd), mDexPath, "arm64", "verify");
538         checkDexoptWithNoProfile(verify(mArtd), mDexPath, "arm", "verify");
539 
540         verifyEmbeddedProfileNotUsed(mDexPath);
541     }
542 
543     @Test
testDexoptUsesEmbeddedProfilePreReboot()544     public void testDexoptUsesEmbeddedProfilePreReboot() throws Exception {
545         when(mInjector.isPreReboot()).thenReturn(true);
546         mPublicOutputProfile.profilePath.finalPath.getForPrimary().isPreReboot = true;
547         mPrivateOutputProfile.profilePath.finalPath.getForPrimary().isPreReboot = true;
548 
549         checkDexoptUsesEmbeddedProfile();
550     }
551 
552     @Test
testDexoptExternalProfileErrors()553     public void testDexoptExternalProfileErrors() throws Exception {
554         // Having no profile should not be reported.
555         // Having a bad profile should be reported.
556         lenient()
557                 .when(mArtd.copyAndRewriteProfile(deepEq(mDmProfile), any(), any()))
558                 .thenReturn(TestingUtils.createCopyAndRewriteProfileBadProfile("error_msg"));
559 
560         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
561 
562         assertThat(results.get(0).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
563         assertThat(results.get(0).getExtendedStatusFlags()
564                 & DexoptResult.EXTENDED_BAD_EXTERNAL_PROFILE)
565                 .isNotEqualTo(0);
566         assertThat(results.get(0).getExternalProfileErrors()).containsExactly("error_msg");
567         assertThat(results.get(1).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
568         assertThat(results.get(1).getExtendedStatusFlags()
569                 & DexoptResult.EXTENDED_BAD_EXTERNAL_PROFILE)
570                 .isNotEqualTo(0);
571         assertThat(results.get(1).getExternalProfileErrors()).containsExactly("error_msg");
572     }
573 
574     @Test
testDexoptDeletesProfileOnFailure()575     public void testDexoptDeletesProfileOnFailure() throws Exception {
576         makeProfileUsable(mDmProfile);
577 
578         when(mArtd.dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
579                      any(), any()))
580                 .thenThrow(ServiceSpecificException.class);
581 
582         mPrimaryDexopter.dexopt();
583 
584         verify(mArtd).deleteProfile(
585                 deepEq(ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath)));
586         verify(mArtd, never()).commitTmpProfile(deepEq(mPublicOutputProfile.profilePath));
587     }
588 
589     @Test
testDexoptNeedsToBeShared()590     public void testDexoptNeedsToBeShared() throws Exception {
591         when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mDexPath)))
592                 .thenReturn(true);
593         when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mSplit0DexPath)))
594                 .thenReturn(true);
595 
596         // The ref profile is usable but shouldn't be used.
597         makeProfileUsable(mRefProfile);
598 
599         makeProfileUsable(mDmProfile);
600 
601         // The existing artifacts are private.
602         when(mArtd.getArtifactsVisibility(
603                      argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
604                 .thenReturn(FileVisibility.NOT_OTHER_READABLE);
605 
606         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
607         verifyStatusAllOk(results);
608 
609         verify(mArtd).copyAndRewriteProfile(
610                 deepEq(mDmProfile), deepEq(mPublicOutputProfile), eq(mDexPath));
611 
612         // It should re-compile anyway.
613         verify(mArtd).getDexoptNeeded(
614                 eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
615         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm64",
616                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
617                 true /* isOtherReadable */);
618 
619         verify(mArtd).getDexoptNeeded(
620                 eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mForceDexoptTrigger));
621         checkDexoptWithProfile(verify(mArtd), mDexPath, "arm",
622                 ProfilePath.tmpProfilePath(mPublicOutputProfile.profilePath),
623                 true /* isOtherReadable */);
624 
625         checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm64", "speed");
626         checkDexoptWithNoProfile(verify(mArtd), mSplit0DexPath, "arm", "speed");
627 
628         verifyProfileNotUsed(mRefProfile);
629         verifyProfileNotUsed(mPrebuiltProfile);
630     }
631 
632     @Test
testDexoptNeedsToBeSharedArtifactsArePublic()633     public void testDexoptNeedsToBeSharedArtifactsArePublic() throws Exception {
634         // Same setup as above, but the existing artifacts are public.
635         when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mDexPath)))
636                 .thenReturn(true);
637         when(mDexUseManager.isPrimaryDexUsedByOtherApps(eq(PKG_NAME), eq(mSplit0DexPath)))
638                 .thenReturn(true);
639 
640         makeProfileUsable(mRefProfile);
641         makeProfileUsable(mDmProfile);
642         when(mArtd.getArtifactsVisibility(
643                      argThat(artifactsPath -> artifactsPath.dexPath == mDexPath)))
644                 .thenReturn(FileVisibility.OTHER_READABLE);
645 
646         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
647         verifyStatusAllOk(results);
648 
649         // It should use the default dexopt trigger.
650         verify(mArtd).getDexoptNeeded(
651                 eq(mDexPath), eq("arm64"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
652         verify(mArtd).getDexoptNeeded(
653                 eq(mDexPath), eq("arm"), any(), eq("speed-profile"), eq(mDefaultDexoptTrigger));
654     }
655 
656     @Test
testDexoptUsesProfileForSplit()657     public void testDexoptUsesProfileForSplit() throws Exception {
658         makeProfileUsable(mSplit0RefProfile);
659         when(mArtd.getProfileVisibility(deepEq(mSplit0RefProfile)))
660                 .thenReturn(FileVisibility.NOT_OTHER_READABLE);
661 
662         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
663         verifyStatusAllOk(results);
664 
665         verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm64"), any(), eq("speed-profile"),
666                 eq(mDefaultDexoptTrigger));
667         checkDexoptWithProfile(verify(mArtd), mSplit0DexPath, "arm64", mSplit0RefProfile,
668                 false /* isOtherReadable */);
669 
670         verify(mArtd).getDexoptNeeded(eq(mSplit0DexPath), eq("arm"), any(), eq("speed-profile"),
671                 eq(mDefaultDexoptTrigger));
672         checkDexoptWithProfile(verify(mArtd), mSplit0DexPath, "arm", mSplit0RefProfile,
673                 false /* isOtherReadable */);
674     }
675 
676     @Test
testDexoptCancelledBeforeDexopt()677     public void testDexoptCancelledBeforeDexopt() throws Exception {
678         mCancellationSignal.cancel();
679 
680         var artdCancellationSignal = mock(IArtdCancellationSignal.class);
681         when(mArtd.createCancellationSignal()).thenReturn(artdCancellationSignal);
682 
683         doAnswer(invocation -> {
684             verify(artdCancellationSignal).cancel();
685             return createArtdDexoptResult(true /* cancelled */);
686         })
687                 .when(mArtd)
688                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
689                         same(artdCancellationSignal));
690 
691         // The result should only contain one element: the result of the first file with
692         // DEXOPT_CANCELLED.
693         assertThat(mPrimaryDexopter.dexopt()
694                            .stream()
695                            .map(DexContainerFileDexoptResult::getStatus)
696                            .collect(Collectors.toList()))
697                 .containsExactly(DexoptResult.DEXOPT_CANCELLED);
698 
699         // It shouldn't continue after being cancelled on the first file.
700         verify(mArtd, times(1)).createCancellationSignal();
701         verify(mArtd, times(1))
702                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
703                         any());
704     }
705 
706     @Test
testDexoptCancelledDuringDexopt()707     public void testDexoptCancelledDuringDexopt() throws Exception {
708         Semaphore dexoptStarted = new Semaphore(0);
709         Semaphore dexoptCancelled = new Semaphore(0);
710         final long TIMEOUT_SEC = 10;
711 
712         var artdCancellationSignal = mock(IArtdCancellationSignal.class);
713         when(mArtd.createCancellationSignal()).thenReturn(artdCancellationSignal);
714 
715         doAnswer(invocation -> {
716             dexoptStarted.release();
717             assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
718             return createArtdDexoptResult(true /* cancelled */);
719         })
720                 .when(mArtd)
721                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
722                         same(artdCancellationSignal));
723         doAnswer(invocation -> {
724             dexoptCancelled.release();
725             return null;
726         })
727                 .when(artdCancellationSignal)
728                 .cancel();
729 
730         Future<List<DexContainerFileDexoptResult>> results =
731                 ForkJoinPool.commonPool().submit(() -> { return mPrimaryDexopter.dexopt(); });
732 
733         assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
734 
735         mCancellationSignal.cancel();
736 
737         assertThat(results.get()
738                            .stream()
739                            .map(DexContainerFileDexoptResult::getStatus)
740                            .collect(Collectors.toList()))
741                 .containsExactly(DexoptResult.DEXOPT_CANCELLED);
742 
743         // It shouldn't continue after being cancelled on the first file.
744         verify(mArtd, times(1)).createCancellationSignal();
745         verify(mArtd, times(1))
746                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
747                         any());
748     }
749 
750     @Test
testDexoptBaseApk()751     public void testDexoptBaseApk() throws Exception {
752         mDexoptParams =
753                 new DexoptParams.Builder("install")
754                         .setCompilerFilter("speed-profile")
755                         .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
756                         .setSplitName(null)
757                         .build();
758         mPrimaryDexopter =
759                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
760 
761         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
762         verifyStatusAllOk(results);
763 
764         verify(mArtd, times(2))
765                 .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
766                         any(), any());
767         verify(mArtd, never())
768                 .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), any(),
769                         anyInt(), any(), any());
770     }
771 
772     @Test
testDexoptSplitApk()773     public void testDexoptSplitApk() throws Exception {
774         mDexoptParams =
775                 new DexoptParams.Builder("install")
776                         .setCompilerFilter("speed-profile")
777                         .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
778                         .setSplitName("split_0")
779                         .build();
780         mPrimaryDexopter =
781                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
782 
783         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
784         verifyStatusAllOk(results);
785 
786         verify(mArtd, never())
787                 .dexopt(any(), eq(mDexPath), any(), any(), any(), any(), any(), any(), anyInt(),
788                         any(), any());
789         verify(mArtd, times(2))
790                 .dexopt(any(), eq(mSplit0DexPath), any(), any(), any(), any(), any(), any(),
791                         anyInt(), any(), any());
792     }
793 
794     @Test
testDexoptStorageLow()795     public void testDexoptStorageLow() throws Exception {
796         when(mStorageManager.getAllocatableBytes(any())).thenReturn(1l, 0l, 0l, 1l);
797 
798         mDexoptParams =
799                 new DexoptParams.Builder("install")
800                         .setCompilerFilter("speed-profile")
801                         .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_SKIP_IF_STORAGE_LOW)
802                         .build();
803         mPrimaryDexopter =
804                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
805 
806         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
807         assertThat(results.get(0).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
808         assertThat(
809                 results.get(0).getExtendedStatusFlags() & DexoptResult.EXTENDED_SKIPPED_STORAGE_LOW)
810                 .isEqualTo(0);
811         assertThat(results.get(1).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
812         assertThat(
813                 results.get(1).getExtendedStatusFlags() & DexoptResult.EXTENDED_SKIPPED_STORAGE_LOW)
814                 .isNotEqualTo(0);
815         assertThat(results.get(2).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
816         assertThat(
817                 results.get(2).getExtendedStatusFlags() & DexoptResult.EXTENDED_SKIPPED_STORAGE_LOW)
818                 .isNotEqualTo(0);
819         assertThat(results.get(3).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
820         assertThat(
821                 results.get(3).getExtendedStatusFlags() & DexoptResult.EXTENDED_SKIPPED_STORAGE_LOW)
822                 .isEqualTo(0);
823 
824         verify(mArtd, times(2))
825                 .dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
826                         any());
827     }
828 
829     @Test
testDexoptDexStatus()830     public void testDexoptDexStatus() throws Exception {
831         lenient()
832                 .when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
833                 .thenReturn(dexoptIsNotNeeded(false /* hasDexCode */),
834                         dexoptIsNotNeeded(false /* hasDexCode */),
835                         dexoptIsNotNeeded(true /* hasDexCode */), dexoptIsNeeded());
836 
837         mDexoptParams = new DexoptParams.Builder("install")
838                                 .setCompilerFilter("speed-profile")
839                                 .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX)
840                                 .build();
841         mPrimaryDexopter =
842                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
843 
844         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
845         assertThat(results.get(0).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
846         assertThat(
847                 results.get(0).getExtendedStatusFlags() & DexoptResult.EXTENDED_SKIPPED_NO_DEX_CODE)
848                 .isNotEqualTo(0);
849         assertThat(results.get(1).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
850         assertThat(
851                 results.get(1).getExtendedStatusFlags() & DexoptResult.EXTENDED_SKIPPED_NO_DEX_CODE)
852                 .isNotEqualTo(0);
853         assertThat(results.get(2).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
854         assertThat(
855                 results.get(2).getExtendedStatusFlags() & DexoptResult.EXTENDED_SKIPPED_NO_DEX_CODE)
856                 .isEqualTo(0);
857         assertThat(results.get(3).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
858         assertThat(
859                 results.get(3).getExtendedStatusFlags() & DexoptResult.EXTENDED_SKIPPED_NO_DEX_CODE)
860                 .isEqualTo(0);
861     }
862 
863     @Test
testDexoptPreRebootDexNotFound()864     public void testDexoptPreRebootDexNotFound() throws Exception {
865         when(mInjector.isPreReboot()).thenReturn(true);
866         doReturn(FileVisibility.NOT_FOUND).when(mArtd).getDexFileVisibility(mDexPath);
867         doReturn(FileVisibility.NOT_FOUND).when(mArtd).getDexFileVisibility(mSplit0DexPath);
868 
869         mPrimaryDexopter =
870                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
871 
872         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
873         assertThat(results).hasSize(0);
874     }
875 
876     @Test
testDexoptPreRebootSomeDexNotFound()877     public void testDexoptPreRebootSomeDexNotFound() throws Exception {
878         when(mInjector.isPreReboot()).thenReturn(true);
879         doReturn(FileVisibility.OTHER_READABLE).when(mArtd).getDexFileVisibility(mDexPath);
880         doReturn(FileVisibility.NOT_FOUND).when(mArtd).getDexFileVisibility(mSplit0DexPath);
881 
882         mPrimaryDexopter =
883                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
884 
885         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
886         assertThat(results).hasSize(2);
887         assertThat(results.get(0).getDexContainerFile()).isEqualTo(mDexPath);
888         assertThat(results.get(0).getAbi()).isEqualTo("arm64-v8a");
889         assertThat(results.get(1).getDexContainerFile()).isEqualTo(mDexPath);
890         assertThat(results.get(1).getAbi()).isEqualTo("armeabi-v7a");
891     }
892 
893     @Test
testDexoptPreRebootArtifactsExist()894     public void testDexoptPreRebootArtifactsExist() throws Exception {
895         when(mInjector.isPreReboot()).thenReturn(true);
896 
897         when(mArtd.getArtifactsVisibility(deepEq(AidlUtils.buildArtifactsPathAsInputPreReboot(
898                      mDexPath, "arm", false /* isInDalvikCache */))))
899                 .thenReturn(FileVisibility.OTHER_READABLE);
900 
901         mPrimaryDexopter =
902                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
903 
904         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
905         // Only the one at index 1 is skipped.
906         assertThat(results).hasSize(4);
907         assertThat(results.get(0).getDexContainerFile()).isEqualTo(mDexPath);
908         assertThat(results.get(0).getAbi()).isEqualTo("arm64-v8a");
909         assertThat(results.get(0).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
910         assertThat(results.get(0).getExtendedStatusFlags()
911                 & DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST)
912                 .isEqualTo(0);
913         assertThat(results.get(1).getDexContainerFile()).isEqualTo(mDexPath);
914         assertThat(results.get(1).getAbi()).isEqualTo("armeabi-v7a");
915         assertThat(results.get(1).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
916         assertThat(results.get(1).getExtendedStatusFlags()
917                 & DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST)
918                 .isNotEqualTo(0);
919         assertThat(results.get(2).getDexContainerFile()).isEqualTo(mSplit0DexPath);
920         assertThat(results.get(2).getAbi()).isEqualTo("arm64-v8a");
921         assertThat(results.get(2).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
922         assertThat(results.get(2).getExtendedStatusFlags()
923                 & DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST)
924                 .isEqualTo(0);
925         assertThat(results.get(3).getDexContainerFile()).isEqualTo(mSplit0DexPath);
926         assertThat(results.get(3).getAbi()).isEqualTo("armeabi-v7a");
927         assertThat(results.get(3).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
928         assertThat(results.get(3).getExtendedStatusFlags()
929                 & DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST)
930                 .isEqualTo(0);
931     }
932 
933     @Test
testDexoptNotAffectedByPreRebootArtifacts()934     public void testDexoptNotAffectedByPreRebootArtifacts() throws Exception {
935         // Same setup as above, but `isPreReboot` is false.
936         lenient()
937                 .when(mArtd.getArtifactsVisibility(
938                         deepEq(AidlUtils.buildArtifactsPathAsInputPreReboot(
939                                 mDexPath, "arm", false /* isInDalvikCache */))))
940                 .thenReturn(FileVisibility.OTHER_READABLE);
941 
942         mPrimaryDexopter =
943                 new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
944 
945         List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
946         assertThat(results).hasSize(4);
947         for (DexContainerFileDexoptResult result : results) {
948             assertThat(result.getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
949         }
950     }
951 
checkDexoptWithProfile(IArtd artd, String dexPath, String isa, ProfilePath profile, boolean isOtherReadable)952     private void checkDexoptWithProfile(IArtd artd, String dexPath, String isa, ProfilePath profile,
953             boolean isOtherReadable) throws Exception {
954         artd.dexopt(argThat(artifacts
955                             -> artifacts.permissionSettings.fileFsPermission.isOtherReadable
956                                     == isOtherReadable),
957                 eq(dexPath), eq(isa), any(), eq("speed-profile"), deepEq(profile), any(), any(),
958                 anyInt(), argThat(dexoptOptions -> dexoptOptions.generateAppImage == true), any());
959     }
960 
checkDexoptWithNoProfile( IArtd artd, String dexPath, String isa, String compilerFilter)961     private void checkDexoptWithNoProfile(
962             IArtd artd, String dexPath, String isa, String compilerFilter) throws Exception {
963         artd.dexopt(
964                 argThat(artifacts
965                         -> artifacts.permissionSettings.fileFsPermission.isOtherReadable == true),
966                 eq(dexPath), eq(isa), any(), eq(compilerFilter), isNull(), any(), any(), anyInt(),
967                 argThat(dexoptOptions -> dexoptOptions.generateAppImage == false), any());
968     }
969 
verifyProfileNotUsed(ProfilePath profile)970     private void verifyProfileNotUsed(ProfilePath profile) throws Exception {
971         assertThat(mUsedProfiles)
972                 .comparingElementsUsing(TestingUtils.<ProfilePath>deepEquality())
973                 .doesNotContain(profile);
974     }
975 
verifyEmbeddedProfileNotUsed(String dexPath)976     private void verifyEmbeddedProfileNotUsed(String dexPath) throws Exception {
977         assertThat(mUsedEmbeddedProfiles).doesNotContain(dexPath);
978     }
979 
makeProfileUsable(ProfilePath profile)980     private void makeProfileUsable(ProfilePath profile) throws Exception {
981         lenient().when(mArtd.isProfileUsable(deepEq(profile), any())).thenAnswer(invocation -> {
982             mUsedProfiles.add(invocation.<ProfilePath>getArgument(0));
983             return true;
984         });
985         lenient()
986                 .when(mArtd.copyAndRewriteProfile(deepEq(profile), any(), any()))
987                 .thenAnswer(invocation -> {
988                     mUsedProfiles.add(invocation.<ProfilePath>getArgument(0));
989                     return TestingUtils.createCopyAndRewriteProfileSuccess();
990                 });
991     }
992 
makeEmbeddedProfileUsable(String dexPath)993     private void makeEmbeddedProfileUsable(String dexPath) throws Exception {
994         lenient()
995                 .when(mArtd.copyAndRewriteEmbeddedProfile(any(), eq(dexPath)))
996                 .thenAnswer(invocation -> {
997                     mUsedEmbeddedProfiles.add(invocation.<String>getArgument(1));
998                     return TestingUtils.createCopyAndRewriteProfileSuccess();
999                 });
1000     }
1001 
verifyStatusAllOk(List<DexContainerFileDexoptResult> results)1002     private void verifyStatusAllOk(List<DexContainerFileDexoptResult> results) {
1003         for (DexContainerFileDexoptResult result : results) {
1004             assertThat(result.getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
1005             assertThat(result.getExtendedStatusFlags()).isEqualTo(0);
1006             assertThat(result.getExternalProfileErrors()).isEmpty();
1007         }
1008     }
1009 
1010     /** Dexopter relies on this information to determine which current profiles to check. */
setPackageInstalledForUserIds(int... userIds)1011     private void setPackageInstalledForUserIds(int... userIds) {
1012         for (int userId : userIds) {
1013             when(mPkgState.getStateForUser(eq(UserHandle.of(userId))))
1014                     .thenReturn(mPkgUserStateInstalled);
1015         }
1016     }
1017 }
1018