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