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