xref: /aosp_15_r20/art/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
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.ArtManagerLocal.DexoptDoneCallback;
20 import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
21 import static com.android.server.art.model.DexoptResult.DexoptResultStatus;
22 import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
23 
24 import static com.google.common.truth.Truth.assertThat;
25 
26 import static org.junit.Assert.assertThrows;
27 import static org.mockito.Mockito.any;
28 import static org.mockito.Mockito.eq;
29 import static org.mockito.Mockito.inOrder;
30 import static org.mockito.Mockito.lenient;
31 import static org.mockito.Mockito.mock;
32 import static org.mockito.Mockito.never;
33 import static org.mockito.Mockito.same;
34 import static org.mockito.Mockito.times;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.when;
37 
38 import android.apphibernation.AppHibernationManager;
39 import android.os.CancellationSignal;
40 
41 import androidx.test.filters.SmallTest;
42 import androidx.test.runner.AndroidJUnit4;
43 
44 import com.android.modules.utils.pm.PackageStateModulesUtils;
45 import com.android.server.art.model.ArtFlags;
46 import com.android.server.art.model.Config;
47 import com.android.server.art.model.DexoptParams;
48 import com.android.server.art.model.DexoptResult;
49 import com.android.server.art.model.OperationProgress;
50 import com.android.server.art.testing.StaticMockitoRule;
51 import com.android.server.pm.PackageManagerLocal;
52 import com.android.server.pm.pkg.AndroidPackage;
53 import com.android.server.pm.pkg.AndroidPackageSplit;
54 import com.android.server.pm.pkg.PackageState;
55 import com.android.server.pm.pkg.SharedLibrary;
56 
57 import org.junit.After;
58 import org.junit.Before;
59 import org.junit.Rule;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 import org.mockito.InOrder;
63 import org.mockito.Mock;
64 
65 import java.util.ArrayList;
66 import java.util.List;
67 import java.util.concurrent.Executor;
68 import java.util.concurrent.ExecutorService;
69 import java.util.concurrent.Executors;
70 import java.util.concurrent.ForkJoinPool;
71 import java.util.concurrent.Future;
72 import java.util.concurrent.Semaphore;
73 import java.util.concurrent.TimeUnit;
74 import java.util.concurrent.atomic.AtomicBoolean;
75 import java.util.function.Consumer;
76 import java.util.stream.Collectors;
77 import java.util.stream.Stream;
78 
79 @SmallTest
80 @RunWith(AndroidJUnit4.class)
81 public class DexoptHelperTest {
82     private static final String PKG_NAME_FOO = "com.example.foo";
83     private static final String PKG_NAME_BAR = "com.example.bar";
84     private static final String PKG_NAME_LIB1 = "com.example.lib1";
85     private static final String PKG_NAME_LIB2 = "com.example.lib2";
86     private static final String PKG_NAME_LIB3 = "com.example.lib3";
87     private static final String PKG_NAME_LIB4 = "com.example.lib4";
88     private static final String PKG_NAME_LIBBAZ = "com.example.libbaz";
89     private static final String PKG_NAME_SDK = "com.example.sdk";
90 
91     @Rule
92     public StaticMockitoRule mockitoRule = new StaticMockitoRule(PackageStateModulesUtils.class);
93 
94     @Mock private DexoptHelper.Injector mInjector;
95     @Mock private PrimaryDexopter mPrimaryDexopter;
96     @Mock private SecondaryDexopter mSecondaryDexopter;
97     @Mock private AppHibernationManager mAhm;
98     @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
99     private PackageState mPkgStateFoo;
100     private PackageState mPkgStateBar;
101     private PackageState mPkgStateLib1;
102     private PackageState mPkgStateLib2;
103     private PackageState mPkgStateLib4;
104     private PackageState mPkgStateLibbaz;
105     private AndroidPackage mPkgFoo;
106     private AndroidPackage mPkgBar;
107     private AndroidPackage mPkgLib1;
108     private AndroidPackage mPkgLib2;
109     private AndroidPackage mPkgLib4;
110     private AndroidPackage mPkgLibbaz;
111     private CancellationSignal mCancellationSignal;
112     private ExecutorService mExecutor;
113     private List<DexContainerFileDexoptResult> mPrimaryResults;
114     private List<DexContainerFileDexoptResult> mSecondaryResults;
115     private Config mConfig;
116     private DexoptParams mParams;
117     private List<String> mRequestedPackages;
118     private DexoptHelper mDexoptHelper;
119 
120     @Before
setUp()121     public void setUp() throws Exception {
122         lenient().when(mAhm.isHibernatingGlobally(any())).thenReturn(false);
123         lenient().when(mAhm.isOatArtifactDeletionEnabled()).thenReturn(true);
124 
125         mCancellationSignal = new CancellationSignal();
126         mExecutor = Executors.newSingleThreadExecutor();
127         mConfig = new Config();
128 
129         preparePackagesAndLibraries();
130 
131         mPrimaryResults = createResults("/somewhere/app/foo/base.apk",
132                 DexoptResult.DEXOPT_PERFORMED /* status1 */,
133                 DexoptResult.DEXOPT_PERFORMED /* status2 */);
134         mSecondaryResults = createResults("/data/user_de/0/foo/foo.apk",
135                 DexoptResult.DEXOPT_PERFORMED /* status1 */,
136                 DexoptResult.DEXOPT_PERFORMED /* status2 */);
137 
138         lenient()
139                 .when(mInjector.getPrimaryDexopter(any(), any(), any(), any()))
140                 .thenReturn(mPrimaryDexopter);
141         lenient().when(mPrimaryDexopter.dexopt()).thenReturn(mPrimaryResults);
142 
143         lenient()
144                 .when(mInjector.getSecondaryDexopter(any(), any(), any(), any()))
145                 .thenReturn(mSecondaryDexopter);
146         lenient().when(mSecondaryDexopter.dexopt()).thenReturn(mSecondaryResults);
147 
148         mParams = new DexoptParams.Builder("install")
149                           .setCompilerFilter("speed-profile")
150                           .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX
151                                           | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
152                                   ArtFlags.FLAG_FOR_SECONDARY_DEX
153                                           | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
154                           .build();
155 
156         lenient().when(mInjector.getAppHibernationManager()).thenReturn(mAhm);
157         lenient().when(mInjector.getConfig()).thenReturn(mConfig);
158 
159         mDexoptHelper = new DexoptHelper(mInjector);
160     }
161 
162     @After
tearDown()163     public void tearDown() {
164         mExecutor.shutdown();
165     }
166 
167     @Test
testDexopt()168     public void testDexopt() throws Exception {
169         // Only package libbaz fails.
170         var failingPrimaryDexopter = mock(PrimaryDexopter.class);
171         List<DexContainerFileDexoptResult> partialFailureResults = createResults(
172                 "/somewhere/app/foo/base.apk", DexoptResult.DEXOPT_PERFORMED /* status1 */,
173                 DexoptResult.DEXOPT_FAILED /* status2 */);
174         lenient().when(failingPrimaryDexopter.dexopt()).thenReturn(partialFailureResults);
175         when(mInjector.getPrimaryDexopter(same(mPkgStateLibbaz), any(), any(), any()))
176                 .thenReturn(failingPrimaryDexopter);
177 
178         DexoptResult result = mDexoptHelper.dexopt(
179                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
180 
181         assertThat(result.getRequestedCompilerFilter()).isEqualTo("speed-profile");
182         assertThat(result.getReason()).isEqualTo("install");
183         assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_FAILED);
184 
185         // The requested packages must come first.
186         assertThat(result.getPackageDexoptResults()).hasSize(6);
187         checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
188                 List.of(mPrimaryResults, mSecondaryResults));
189         checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
190                 List.of(mPrimaryResults, mSecondaryResults));
191         checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_FAILED,
192                 List.of(partialFailureResults, mSecondaryResults));
193         checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED,
194                 List.of(mPrimaryResults, mSecondaryResults));
195         checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED,
196                 List.of(mPrimaryResults, mSecondaryResults));
197         checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED,
198                 List.of(mPrimaryResults, mSecondaryResults));
199 
200         // The order matters. When running in a single thread, it should dexopt primary dex files
201         // and the secondary dex files together for each package, and it should dexopt requested
202         // packages, in the given order, and then dexopt dependencies.
203         InOrder inOrder = inOrder(mInjector);
204         inOrder.verify(mInjector).getPrimaryDexopter(
205                 same(mPkgStateFoo), same(mPkgFoo), same(mParams), any());
206         inOrder.verify(mInjector).getSecondaryDexopter(
207                 same(mPkgStateFoo), same(mPkgFoo), same(mParams), any());
208         inOrder.verify(mInjector).getPrimaryDexopter(
209                 same(mPkgStateBar), same(mPkgBar), same(mParams), any());
210         inOrder.verify(mInjector).getSecondaryDexopter(
211                 same(mPkgStateBar), same(mPkgBar), same(mParams), any());
212         inOrder.verify(mInjector).getPrimaryDexopter(
213                 same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), any());
214         inOrder.verify(mInjector).getSecondaryDexopter(
215                 same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), any());
216         inOrder.verify(mInjector).getPrimaryDexopter(
217                 same(mPkgStateLib1), same(mPkgLib1), same(mParams), any());
218         inOrder.verify(mInjector).getSecondaryDexopter(
219                 same(mPkgStateLib1), same(mPkgLib1), same(mParams), any());
220         inOrder.verify(mInjector).getPrimaryDexopter(
221                 same(mPkgStateLib2), same(mPkgLib2), same(mParams), any());
222         inOrder.verify(mInjector).getSecondaryDexopter(
223                 same(mPkgStateLib2), same(mPkgLib2), same(mParams), any());
224         inOrder.verify(mInjector).getPrimaryDexopter(
225                 same(mPkgStateLib4), same(mPkgLib4), same(mParams), any());
226         inOrder.verify(mInjector).getSecondaryDexopter(
227                 same(mPkgStateLib4), same(mPkgLib4), same(mParams), any());
228 
229         verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 6 /* expectedSecondaryTimes */);
230     }
231 
232     @Test
testDexoptNoDependencies()233     public void testDexoptNoDependencies() throws Exception {
234         mParams = new DexoptParams.Builder("install")
235                           .setCompilerFilter("speed-profile")
236                           .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX,
237                                   ArtFlags.FLAG_FOR_SECONDARY_DEX
238                                           | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
239                           .build();
240 
241         DexoptResult result = mDexoptHelper.dexopt(
242                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
243 
244         assertThat(result.getPackageDexoptResults()).hasSize(3);
245         checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
246                 List.of(mPrimaryResults, mSecondaryResults));
247         checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
248                 List.of(mPrimaryResults, mSecondaryResults));
249         checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
250                 List.of(mPrimaryResults, mSecondaryResults));
251 
252         verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 3 /* expectedSecondaryTimes */);
253     }
254 
255     @Test
testDexoptPrimaryOnly()256     public void testDexoptPrimaryOnly() throws Exception {
257         mParams = new DexoptParams.Builder("install")
258                           .setCompilerFilter("speed-profile")
259                           .setFlags(ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
260                                   ArtFlags.FLAG_FOR_SECONDARY_DEX
261                                           | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
262                           .build();
263 
264         DexoptResult result = mDexoptHelper.dexopt(
265                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
266 
267         assertThat(result.getPackageDexoptResults()).hasSize(6);
268         checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
269                 List.of(mPrimaryResults));
270         checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
271                 List.of(mPrimaryResults));
272         checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
273                 List.of(mPrimaryResults));
274         checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED,
275                 List.of(mPrimaryResults));
276         checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED,
277                 List.of(mPrimaryResults));
278         checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED,
279                 List.of(mPrimaryResults));
280 
281         verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
282     }
283 
284     @Test
testDexoptSdkPrimaryOnly()285     public void testDexoptSdkPrimaryOnly() throws Exception {
286         mParams = new DexoptParams.Builder("bg-dexopt")
287                           .setCompilerFilter("speed-profile")
288                           .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SECONDARY_DEX)
289                           .build();
290 
291         PackageState sdkPackageState =
292                 createPackageState(PKG_NAME_SDK, -1 /* appId */, List.of(), false);
293         lenient().when(mSnapshot.getPackageState(PKG_NAME_SDK)).thenReturn(sdkPackageState);
294         mRequestedPackages = List.of(PKG_NAME_SDK);
295 
296         DexoptResult result = mDexoptHelper.dexopt(
297                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
298 
299         assertThat(result.getPackageDexoptResults()).hasSize(1);
300         checkPackageResult(result, 0 /* index */, PKG_NAME_SDK, DexoptResult.DEXOPT_PERFORMED,
301                 List.of(mPrimaryResults));
302 
303         verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
304     }
305 
306     @Test
testDexoptPrimaryOnlyNoDependencies()307     public void testDexoptPrimaryOnlyNoDependencies() throws Exception {
308         mParams = new DexoptParams.Builder("install")
309                           .setCompilerFilter("speed-profile")
310                           .setFlags(0,
311                                   ArtFlags.FLAG_FOR_SECONDARY_DEX
312                                           | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
313                           .build();
314 
315         DexoptResult result = mDexoptHelper.dexopt(
316                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
317 
318         assertThat(result.getPackageDexoptResults()).hasSize(3);
319         checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
320                 List.of(mPrimaryResults));
321         checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
322                 List.of(mPrimaryResults));
323         checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
324                 List.of(mPrimaryResults));
325 
326         verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
327     }
328 
329     @Test
testDexoptCancelledBetweenDex2oatInvocations()330     public void testDexoptCancelledBetweenDex2oatInvocations() throws Exception {
331         when(mPrimaryDexopter.dexopt()).thenAnswer(invocation -> {
332             mCancellationSignal.cancel();
333             return mPrimaryResults;
334         });
335 
336         DexoptResult result = mDexoptHelper.dexopt(
337                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
338 
339         assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_CANCELLED);
340 
341         assertThat(result.getPackageDexoptResults()).hasSize(6);
342         checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_CANCELLED,
343                 List.of(mPrimaryResults));
344         checkPackageResult(
345                 result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_CANCELLED, List.of());
346         checkPackageResult(
347                 result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_CANCELLED, List.of());
348         checkPackageResult(
349                 result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_CANCELLED, List.of());
350         checkPackageResult(
351                 result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_CANCELLED, List.of());
352         checkPackageResult(
353                 result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_CANCELLED, List.of());
354 
355         verify(mInjector).getPrimaryDexopter(
356                 same(mPkgStateFoo), same(mPkgFoo), same(mParams), any());
357 
358         verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
359     }
360 
361     // This test verifies that every child thread can register its own listener on the cancellation
362     // signal through `setOnCancelListener` (i.e., the listeners don't overwrite each other).
363     @Test
testDexoptCancelledDuringDex2oatInvocationsMultiThreaded()364     public void testDexoptCancelledDuringDex2oatInvocationsMultiThreaded() throws Exception {
365         final int NUM_PACKAGES = 6;
366         final long TIMEOUT_SEC = 10;
367         var dexoptStarted = new Semaphore(0);
368         var dexoptCancelled = new Semaphore(0);
369 
370         when(mInjector.getPrimaryDexopter(any(), any(), any(), any())).thenAnswer(invocation -> {
371             var cancellationSignal = invocation.<CancellationSignal>getArgument(3);
372             var dexopter = mock(PrimaryDexopter.class);
373             when(dexopter.dexopt()).thenAnswer(innerInvocation -> {
374                 // Simulate that the child thread registers its own listener.
375                 var isListenerCalled = new AtomicBoolean(false);
376                 cancellationSignal.setOnCancelListener(() -> isListenerCalled.set(true));
377 
378                 dexoptStarted.release();
379                 assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
380 
381                 // Verify that the listener is called.
382                 assertThat(isListenerCalled.get()).isTrue();
383 
384                 return mPrimaryResults;
385             });
386             return dexopter;
387         });
388 
389         ExecutorService dexoptExecutor = Executors.newFixedThreadPool(NUM_PACKAGES);
390         Future<DexoptResult> future = ForkJoinPool.commonPool().submit(() -> {
391             return mDexoptHelper.dexopt(
392                     mSnapshot, mRequestedPackages, mParams, mCancellationSignal, dexoptExecutor);
393         });
394 
395         try {
396             // Wait for all dexopt operations to start.
397             for (int i = 0; i < NUM_PACKAGES; i++) {
398                 assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
399             }
400 
401             mCancellationSignal.cancel();
402 
403             for (int i = 0; i < NUM_PACKAGES; i++) {
404                 dexoptCancelled.release();
405             }
406         } finally {
407             dexoptExecutor.shutdown();
408             Utils.getFuture(future);
409         }
410     }
411 
412     // This test verifies that dexopt operation on the current thread can be cancelled.
413     @Test
testDexoptCancelledDuringDex2oatInvocationsOnCurrentThread()414     public void testDexoptCancelledDuringDex2oatInvocationsOnCurrentThread() throws Exception {
415         final long TIMEOUT_SEC = 10;
416         var dexoptStarted = new Semaphore(0);
417         var dexoptCancelled = new Semaphore(0);
418 
419         when(mInjector.getPrimaryDexopter(any(), any(), any(), any())).thenAnswer(invocation -> {
420             var cancellationSignal = invocation.<CancellationSignal>getArgument(3);
421             var dexopter = mock(PrimaryDexopter.class);
422             when(dexopter.dexopt()).thenAnswer(innerInvocation -> {
423                 if (cancellationSignal.isCanceled()) {
424                     return mPrimaryResults;
425                 }
426 
427                 var isListenerCalled = new AtomicBoolean(false);
428                 cancellationSignal.setOnCancelListener(() -> isListenerCalled.set(true));
429 
430                 dexoptStarted.release();
431                 assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
432 
433                 // Verify that the listener is called.
434                 assertThat(isListenerCalled.get()).isTrue();
435 
436                 return mPrimaryResults;
437             });
438             return dexopter;
439         });
440 
441         // Use the current thread (the one in ForkJoinPool).
442         Executor dexoptExecutor = Runnable::run;
443         Future<DexoptResult> future = ForkJoinPool.commonPool().submit(() -> {
444             return mDexoptHelper.dexopt(
445                     mSnapshot, mRequestedPackages, mParams, mCancellationSignal, dexoptExecutor);
446         });
447 
448         try {
449             // Only one dexopt operation should start.
450             assertThat(dexoptStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
451 
452             mCancellationSignal.cancel();
453 
454             dexoptCancelled.release();
455         } finally {
456             Utils.getFuture(future);
457         }
458     }
459 
460     @Test
testDexoptNotDexoptable()461     public void testDexoptNotDexoptable() throws Exception {
462         when(PackageStateModulesUtils.isDexoptable(mPkgStateFoo)).thenReturn(false);
463 
464         mRequestedPackages = List.of(PKG_NAME_FOO);
465         DexoptResult result = mDexoptHelper.dexopt(
466                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
467 
468         assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
469         assertThat(result.getPackageDexoptResults()).hasSize(1);
470         checkPackageResult(
471                 result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_SKIPPED, List.of());
472 
473         verifyNoDexopt();
474     }
475 
476     @Test
testDexoptLibraryNotDexoptable()477     public void testDexoptLibraryNotDexoptable() throws Exception {
478         when(PackageStateModulesUtils.isDexoptable(mPkgStateLib1)).thenReturn(false);
479 
480         mRequestedPackages = List.of(PKG_NAME_FOO);
481         DexoptResult result = mDexoptHelper.dexopt(
482                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
483 
484         assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
485         assertThat(result.getPackageDexoptResults()).hasSize(1);
486         checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
487                 List.of(mPrimaryResults, mSecondaryResults));
488 
489         verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 1 /* expectedSecondaryTimes */);
490     }
491 
492     @Test
testDexoptIsHibernating()493     public void testDexoptIsHibernating() throws Exception {
494         lenient().when(mAhm.isHibernatingGlobally(PKG_NAME_FOO)).thenReturn(true);
495 
496         mRequestedPackages = List.of(PKG_NAME_FOO);
497         DexoptResult result = mDexoptHelper.dexopt(
498                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
499 
500         assertThat(result.getFinalStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
501         checkPackageResult(
502                 result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_SKIPPED, List.of());
503 
504         verifyNoDexopt();
505     }
506 
507     @Test
testDexoptIsHibernatingButOatArtifactDeletionDisabled()508     public void testDexoptIsHibernatingButOatArtifactDeletionDisabled() throws Exception {
509         lenient().when(mAhm.isHibernatingGlobally(PKG_NAME_FOO)).thenReturn(true);
510         lenient().when(mAhm.isOatArtifactDeletionEnabled()).thenReturn(false);
511 
512         DexoptResult result = mDexoptHelper.dexopt(
513                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
514 
515         assertThat(result.getPackageDexoptResults()).hasSize(6);
516         checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, DexoptResult.DEXOPT_PERFORMED,
517                 List.of(mPrimaryResults, mSecondaryResults));
518         checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, DexoptResult.DEXOPT_PERFORMED,
519                 List.of(mPrimaryResults, mSecondaryResults));
520         checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, DexoptResult.DEXOPT_PERFORMED,
521                 List.of(mPrimaryResults, mSecondaryResults));
522         checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, DexoptResult.DEXOPT_PERFORMED,
523                 List.of(mPrimaryResults, mSecondaryResults));
524         checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, DexoptResult.DEXOPT_PERFORMED,
525                 List.of(mPrimaryResults, mSecondaryResults));
526         checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, DexoptResult.DEXOPT_PERFORMED,
527                 List.of(mPrimaryResults, mSecondaryResults));
528     }
529 
530     @Test(expected = IllegalArgumentException.class)
testDexoptPackageNotFound()531     public void testDexoptPackageNotFound() throws Exception {
532         when(mSnapshot.getPackageState(any())).thenReturn(null);
533 
534         mDexoptHelper.dexopt(
535                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
536 
537         verifyNoDexopt();
538     }
539 
540     @Test(expected = IllegalArgumentException.class)
testDexoptNoPackage()541     public void testDexoptNoPackage() throws Exception {
542         lenient().when(mPkgStateFoo.getAndroidPackage()).thenReturn(null);
543 
544         mDexoptHelper.dexopt(
545                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
546 
547         verifyNoDexopt();
548     }
549 
550     @Test
testDexoptSplit()551     public void testDexoptSplit() throws Exception {
552         mRequestedPackages = List.of(PKG_NAME_FOO);
553         mParams = new DexoptParams.Builder("install")
554                           .setCompilerFilter("speed-profile")
555                           .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
556                           .setSplitName("split_0")
557                           .build();
558 
559         mDexoptHelper.dexopt(
560                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
561     }
562 
563     @Test
testDexoptSplitNotFound()564     public void testDexoptSplitNotFound() throws Exception {
565         mRequestedPackages = List.of(PKG_NAME_FOO);
566         mParams = new DexoptParams.Builder("install")
567                           .setCompilerFilter("speed-profile")
568                           .setFlags(ArtFlags.FLAG_FOR_PRIMARY_DEX | ArtFlags.FLAG_FOR_SINGLE_SPLIT)
569                           .setSplitName("split_bogus")
570                           .build();
571 
572         assertThrows(IllegalArgumentException.class, () -> {
573             mDexoptHelper.dexopt(
574                     mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
575         });
576     }
577 
578     @Test
testCallbacks()579     public void testCallbacks() throws Exception {
580         List<DexoptResult> list1 = new ArrayList<>();
581         mConfig.addDexoptDoneCallback(
582                 false /* onlyIncludeUpdates */, Runnable::run, result -> list1.add(result));
583 
584         List<DexoptResult> list2 = new ArrayList<>();
585         mConfig.addDexoptDoneCallback(
586                 false /* onlyIncludeUpdates */, Runnable::run, result -> list2.add(result));
587 
588         DexoptResult result = mDexoptHelper.dexopt(
589                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
590 
591         assertThat(list1).containsExactly(result);
592         assertThat(list2).containsExactly(result);
593     }
594 
595     @Test
testCallbackRemoved()596     public void testCallbackRemoved() throws Exception {
597         List<DexoptResult> list1 = new ArrayList<>();
598         DexoptDoneCallback callback1 = result -> list1.add(result);
599         mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback1);
600 
601         List<DexoptResult> list2 = new ArrayList<>();
602         mConfig.addDexoptDoneCallback(
603                 false /* onlyIncludeUpdates */, Runnable::run, result -> list2.add(result));
604 
605         mConfig.removeDexoptDoneCallback(callback1);
606 
607         DexoptResult result = mDexoptHelper.dexopt(
608                 mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
609 
610         assertThat(list1).isEmpty();
611         assertThat(list2).containsExactly(result);
612     }
613 
614     @Test(expected = IllegalStateException.class)
testCallbackAlreadyAdded()615     public void testCallbackAlreadyAdded() throws Exception {
616         List<DexoptResult> list = new ArrayList<>();
617         DexoptDoneCallback callback = result -> list.add(result);
618         mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback);
619         mConfig.addDexoptDoneCallback(false /* onlyIncludeUpdates */, Runnable::run, callback);
620     }
621 
622     // Tests `addDexoptDoneCallback` with `onlyIncludeUpdates` being true and false.
623     @Test
testCallbackWithFailureResults()624     public void testCallbackWithFailureResults() throws Exception {
625         mParams = new DexoptParams.Builder("install")
626                           .setCompilerFilter("speed-profile")
627                           .setFlags(0,
628                                   ArtFlags.FLAG_FOR_SECONDARY_DEX
629                                           | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
630                           .build();
631 
632         // This list should collect all results.
633         List<DexoptResult> listAll = new ArrayList<>();
634         mConfig.addDexoptDoneCallback(
635                 false /* onlyIncludeUpdates */, Runnable::run, result -> listAll.add(result));
636 
637         // This list should only collect results that have updates.
638         List<DexoptResult> listOnlyIncludeUpdates = new ArrayList<>();
639         mConfig.addDexoptDoneCallback(true /* onlyIncludeUpdates */, Runnable::run,
640                 result -> listOnlyIncludeUpdates.add(result));
641 
642         // Dexopt partially fails on package "foo".
643         List<DexContainerFileDexoptResult> partialFailureResults = createResults(
644                 "/somewhere/app/foo/base.apk", DexoptResult.DEXOPT_PERFORMED /* status1 */,
645                 DexoptResult.DEXOPT_FAILED /* status2 */);
646         var fooPrimaryDexopter = mock(PrimaryDexopter.class);
647         when(mInjector.getPrimaryDexopter(same(mPkgStateFoo), any(), any(), any()))
648                 .thenReturn(fooPrimaryDexopter);
649         when(fooPrimaryDexopter.dexopt()).thenReturn(partialFailureResults);
650 
651         // Dexopt totally fails on package "bar".
652         List<DexContainerFileDexoptResult> totalFailureResults = createResults(
653                 "/somewhere/app/bar/base.apk", DexoptResult.DEXOPT_FAILED /* status1 */,
654                 DexoptResult.DEXOPT_FAILED /* status2 */);
655         var barPrimaryDexopter = mock(PrimaryDexopter.class);
656         when(mInjector.getPrimaryDexopter(same(mPkgStateBar), any(), any(), any()))
657                 .thenReturn(barPrimaryDexopter);
658         when(barPrimaryDexopter.dexopt()).thenReturn(totalFailureResults);
659 
660         DexoptResult resultWithSomeUpdates = mDexoptHelper.dexopt(mSnapshot,
661                 List.of(PKG_NAME_FOO, PKG_NAME_BAR), mParams, mCancellationSignal, mExecutor);
662         DexoptResult resultWithNoUpdates = mDexoptHelper.dexopt(
663                 mSnapshot, List.of(PKG_NAME_BAR), mParams, mCancellationSignal, mExecutor);
664 
665         assertThat(listAll).containsExactly(resultWithSomeUpdates, resultWithNoUpdates);
666 
667         assertThat(listOnlyIncludeUpdates).hasSize(1);
668         assertThat(listOnlyIncludeUpdates.get(0)
669                            .getPackageDexoptResults()
670                            .stream()
671                            .map(PackageDexoptResult::getPackageName)
672                            .collect(Collectors.toList()))
673                 .containsExactly(PKG_NAME_FOO);
674     }
675 
676     @Test
testProgressCallback()677     public void testProgressCallback() throws Exception {
678         mParams = new DexoptParams.Builder("install")
679                           .setCompilerFilter("speed-profile")
680                           .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX,
681                                   ArtFlags.FLAG_FOR_SECONDARY_DEX
682                                           | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
683                           .build();
684 
685         // Delay the executor to verify that the commands passed to the executor are not bound to
686         // changing variables.
687         var progressCallbackExecutor = new DelayedExecutor();
688         Consumer<OperationProgress> progressCallback = mock(Consumer.class);
689 
690         mDexoptHelper.dexopt(mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor,
691                 progressCallbackExecutor, progressCallback);
692 
693         progressCallbackExecutor.runAll();
694 
695         List<DexContainerFileDexoptResult> fileResults =
696                 Stream.concat(mPrimaryResults.stream(), mSecondaryResults.stream())
697                         .collect(Collectors.toList());
698 
699         InOrder inOrder = inOrder(progressCallback);
700         inOrder.verify(progressCallback)
701                 .accept(eq(OperationProgress.create(
702                         0 /* current */, 3 /* total */, null /* packageDexoptResult */)));
703         inOrder.verify(progressCallback)
704                 .accept(eq(OperationProgress.create(1 /* current */, 3 /* total */,
705                         PackageDexoptResult.create(
706                                 PKG_NAME_FOO, fileResults, null /* packageLevelStatus */))));
707         inOrder.verify(progressCallback)
708                 .accept(eq(OperationProgress.create(2 /* current */, 3 /* total */,
709                         PackageDexoptResult.create(
710                                 PKG_NAME_BAR, fileResults, null /* packageLevelStatus */))));
711         inOrder.verify(progressCallback)
712                 .accept(eq(OperationProgress.create(3 /* current */, 3 /* total */,
713                         PackageDexoptResult.create(
714                                 PKG_NAME_LIBBAZ, fileResults, null /* packageLevelStatus */))));
715     }
716 
createPackage(boolean multiSplit)717     private AndroidPackage createPackage(boolean multiSplit) {
718         AndroidPackage pkg = mock(AndroidPackage.class);
719 
720         var baseSplit = mock(AndroidPackageSplit.class);
721 
722         if (multiSplit) {
723             var split0 = mock(AndroidPackageSplit.class);
724             lenient().when(split0.getName()).thenReturn("split_0");
725 
726             lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit, split0));
727         } else {
728             lenient().when(pkg.getSplits()).thenReturn(List.of(baseSplit));
729         }
730 
731         return pkg;
732     }
733 
createPackageState( String packageName, List<SharedLibrary> deps, boolean multiSplit)734     private PackageState createPackageState(
735             String packageName, List<SharedLibrary> deps, boolean multiSplit) {
736         return createPackageState(packageName, 12345, deps, multiSplit);
737     }
738 
createPackageState( String packageName, int appId, List<SharedLibrary> deps, boolean multiSplit)739     private PackageState createPackageState(
740             String packageName, int appId, List<SharedLibrary> deps, boolean multiSplit) {
741         PackageState pkgState = mock(PackageState.class);
742         lenient().when(pkgState.getPackageName()).thenReturn(packageName);
743         lenient().when(pkgState.getAppId()).thenReturn(appId);
744         lenient().when(pkgState.getSharedLibraryDependencies()).thenReturn(deps);
745         AndroidPackage pkg = createPackage(multiSplit);
746         lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
747         lenient().when(PackageStateModulesUtils.isDexoptable(pkgState)).thenReturn(true);
748         return pkgState;
749     }
750 
createLibrary( String libraryName, String packageName, List<SharedLibrary> deps)751     private SharedLibrary createLibrary(
752             String libraryName, String packageName, List<SharedLibrary> deps) {
753         SharedLibrary library = mock(SharedLibrary.class);
754         lenient().when(library.getName()).thenReturn(libraryName);
755         lenient().when(library.getPackageName()).thenReturn(packageName);
756         lenient().when(library.getDependencies()).thenReturn(deps);
757         lenient().when(library.isNative()).thenReturn(false);
758         return library;
759     }
760 
preparePackagesAndLibraries()761     private void preparePackagesAndLibraries() {
762         // Dependency graph:
763         //                foo                bar
764         //                 |                  |
765         //            lib1a (lib1)       lib1b (lib1)       lib1c (lib1)
766         //               /   \             /   \                  |
767         //              /     \           /     \                 |
768         //  libbaz (libbaz)    lib2 (lib2)    lib4 (lib4)    lib3 (lib3)
769         //
770         // "lib1a", "lib1b", and "lib1c" belong to the same package "lib1".
771 
772         mRequestedPackages = List.of(PKG_NAME_FOO, PKG_NAME_BAR, PKG_NAME_LIBBAZ);
773 
774         // The native library is not dexoptable.
775         SharedLibrary libNative = createLibrary("libnative", "com.example.libnative", List.of());
776         lenient().when(libNative.isNative()).thenReturn(true);
777 
778         SharedLibrary libbaz = createLibrary("libbaz", PKG_NAME_LIBBAZ, List.of());
779         SharedLibrary lib4 = createLibrary("lib4", PKG_NAME_LIB4, List.of());
780         SharedLibrary lib3 = createLibrary("lib3", PKG_NAME_LIB3, List.of());
781         SharedLibrary lib2 = createLibrary("lib2", PKG_NAME_LIB2, List.of());
782         SharedLibrary lib1a = createLibrary("lib1a", PKG_NAME_LIB1, List.of(libbaz, lib2));
783         SharedLibrary lib1b = createLibrary("lib1b", PKG_NAME_LIB1, List.of(lib2, libNative, lib4));
784         SharedLibrary lib1c = createLibrary("lib1c", PKG_NAME_LIB1, List.of(lib3));
785 
786         mPkgStateFoo =
787                 createPackageState(PKG_NAME_FOO, List.of(lib1a, libNative), true /* multiSplit */);
788         mPkgFoo = mPkgStateFoo.getAndroidPackage();
789         lenient().when(mSnapshot.getPackageState(PKG_NAME_FOO)).thenReturn(mPkgStateFoo);
790 
791         mPkgStateBar = createPackageState(PKG_NAME_BAR, List.of(lib1b), false /* multiSplit */);
792         mPkgBar = mPkgStateBar.getAndroidPackage();
793         lenient().when(mSnapshot.getPackageState(PKG_NAME_BAR)).thenReturn(mPkgStateBar);
794 
795         mPkgStateLib1 = createPackageState(
796                 PKG_NAME_LIB1, List.of(libbaz, lib2, lib3, lib4), false /* multiSplit */);
797         mPkgLib1 = mPkgStateLib1.getAndroidPackage();
798         lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB1)).thenReturn(mPkgStateLib1);
799 
800         mPkgStateLib2 = createPackageState(PKG_NAME_LIB2, List.of(), false /* multiSplit */);
801         mPkgLib2 = mPkgStateLib2.getAndroidPackage();
802         lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB2)).thenReturn(mPkgStateLib2);
803 
804         // This should not be considered as a transitive dependency of any requested package, even
805         // though it is a dependency of package "lib1".
806         PackageState pkgStateLib3 =
807                 createPackageState(PKG_NAME_LIB3, List.of(), false /* multiSplit */);
808         lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB3)).thenReturn(pkgStateLib3);
809 
810         mPkgStateLib4 = createPackageState(PKG_NAME_LIB4, List.of(), false /* multiSplit */);
811         mPkgLib4 = mPkgStateLib4.getAndroidPackage();
812         lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB4)).thenReturn(mPkgStateLib4);
813 
814         mPkgStateLibbaz = createPackageState(PKG_NAME_LIBBAZ, List.of(), false /* multiSplit */);
815         mPkgLibbaz = mPkgStateLibbaz.getAndroidPackage();
816         lenient().when(mSnapshot.getPackageState(PKG_NAME_LIBBAZ)).thenReturn(mPkgStateLibbaz);
817     }
818 
verifyNoDexopt()819     private void verifyNoDexopt() {
820         verify(mInjector, never()).getPrimaryDexopter(any(), any(), any(), any());
821         verify(mInjector, never()).getSecondaryDexopter(any(), any(), any(), any());
822     }
823 
verifyNoMoreDexopt(int expectedPrimaryTimes, int expectedSecondaryTimes)824     private void verifyNoMoreDexopt(int expectedPrimaryTimes, int expectedSecondaryTimes) {
825         verify(mInjector, times(expectedPrimaryTimes))
826                 .getPrimaryDexopter(any(), any(), any(), any());
827         verify(mInjector, times(expectedSecondaryTimes))
828                 .getSecondaryDexopter(any(), any(), any(), any());
829     }
830 
createResults( String dexPath, @DexoptResultStatus int status1, @DexoptResultStatus int status2)831     private List<DexContainerFileDexoptResult> createResults(
832             String dexPath, @DexoptResultStatus int status1, @DexoptResultStatus int status2) {
833         return List.of(DexContainerFileDexoptResult.create(
834                                dexPath, true /* isPrimaryAbi */, "arm64-v8a", "verify", status1),
835                 DexContainerFileDexoptResult.create(
836                         dexPath, false /* isPrimaryAbi */, "armeabi-v7a", "verify", status2));
837     }
838 
checkPackageResult(DexoptResult result, int index, String packageName, @DexoptResult.DexoptResultStatus int status, List<List<DexContainerFileDexoptResult>> dexContainerFileDexoptResults)839     private void checkPackageResult(DexoptResult result, int index, String packageName,
840             @DexoptResult.DexoptResultStatus int status,
841             List<List<DexContainerFileDexoptResult>> dexContainerFileDexoptResults) {
842         PackageDexoptResult packageResult = result.getPackageDexoptResults().get(index);
843         assertThat(packageResult.getPackageName()).isEqualTo(packageName);
844         assertThat(packageResult.getStatus()).isEqualTo(status);
845         assertThat(packageResult.getDexContainerFileDexoptResults())
846                 .containsExactlyElementsIn(dexContainerFileDexoptResults.stream()
847                                                    .flatMap(r -> r.stream())
848                                                    .collect(Collectors.toList()));
849     }
850 
851     /** An executor that delays execution until `runAll` is called. */
852     private static class DelayedExecutor implements Executor {
853         private List<Runnable> mCommands = new ArrayList<>();
854 
execute(Runnable command)855         public void execute(Runnable command) {
856             mCommands.add(command);
857         }
858 
runAll()859         public void runAll() {
860             for (Runnable command : mCommands) {
861                 command.run();
862             }
863             mCommands.clear();
864         }
865     }
866 }
867