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