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