1 /* 2 * Copyright (C) 2021 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.pm; 18 19 import static com.android.server.pm.BackgroundDexOptService.STATUS_DEX_OPT_FAILED; 20 import static com.android.server.pm.BackgroundDexOptService.STATUS_FATAL_ERROR; 21 import static com.android.server.pm.BackgroundDexOptService.STATUS_OK; 22 23 import static com.google.common.truth.Truth.assertThat; 24 25 import static org.mockito.ArgumentMatchers.any; 26 import static org.mockito.ArgumentMatchers.argThat; 27 import static org.mockito.Mockito.atLeastOnce; 28 import static org.mockito.Mockito.doThrow; 29 import static org.mockito.Mockito.inOrder; 30 import static org.mockito.Mockito.mock; 31 import static org.mockito.Mockito.never; 32 import static org.mockito.Mockito.reset; 33 import static org.mockito.Mockito.timeout; 34 import static org.mockito.Mockito.times; 35 import static org.mockito.Mockito.verify; 36 import static org.mockito.Mockito.when; 37 import static org.testng.Assert.assertThrows; 38 39 import android.annotation.Nullable; 40 import android.app.job.JobInfo; 41 import android.app.job.JobParameters; 42 import android.app.job.JobScheduler; 43 import android.content.BroadcastReceiver; 44 import android.content.Context; 45 import android.content.Intent; 46 import android.content.IntentFilter; 47 import android.os.HandlerThread; 48 import android.os.PowerManager; 49 import android.os.Process; 50 import android.os.SystemProperties; 51 import android.util.Log; 52 53 import com.android.internal.util.IndentingPrintWriter; 54 import com.android.server.LocalServices; 55 import com.android.server.PinnerService; 56 import com.android.server.pm.dex.DexManager; 57 import com.android.server.pm.dex.DexoptOptions; 58 59 import org.junit.After; 60 import org.junit.Assume; 61 import org.junit.Before; 62 import org.junit.Test; 63 import org.junit.runner.RunWith; 64 import org.mockito.ArgumentCaptor; 65 import org.mockito.InOrder; 66 import org.mockito.Mock; 67 import org.mockito.junit.MockitoJUnitRunner; 68 69 import java.io.ByteArrayOutputStream; 70 import java.io.PrintWriter; 71 import java.util.ArrayList; 72 import java.util.Arrays; 73 import java.util.Collections; 74 import java.util.List; 75 import java.util.concurrent.CountDownLatch; 76 import java.util.stream.Collectors; 77 78 @RunWith(MockitoJUnitRunner.class) 79 public final class BackgroundDexOptServiceUnitTest { 80 private static final String TAG = BackgroundDexOptServiceUnitTest.class.getSimpleName(); 81 82 private static final long USABLE_SPACE_NORMAL = 1_000_000_000; 83 private static final long STORAGE_LOW_BYTES = 1_000_000; 84 85 private static final long TEST_WAIT_TIMEOUT_MS = 10_000; 86 87 private static final String PACKAGE_AAA = "aaa"; 88 private static final List<String> DEFAULT_PACKAGE_LIST = List.of(PACKAGE_AAA, "bbb"); 89 private int mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED; 90 91 // Store expected dexopt sequence for verification. 92 private ArrayList<DexOptInfo> mDexInfoSequence = new ArrayList<>(); 93 94 @Mock 95 private Context mContext; 96 @Mock 97 private PackageManagerService mPackageManager; 98 @Mock 99 private DexOptHelper mDexOptHelper; 100 @Mock 101 private DexManager mDexManager; 102 @Mock 103 private PinnerService mPinnerService; 104 @Mock 105 private JobScheduler mJobScheduler; 106 @Mock 107 private BackgroundDexOptService.Injector mInjector; 108 @Mock 109 private BackgroundDexOptJobService mJobServiceForPostBoot; 110 @Mock 111 private BackgroundDexOptJobService mJobServiceForIdle; 112 113 private final JobParameters mJobParametersForPostBoot = 114 createJobParameters(BackgroundDexOptService.JOB_POST_BOOT_UPDATE); 115 private final JobParameters mJobParametersForIdle = 116 createJobParameters(BackgroundDexOptService.JOB_IDLE_OPTIMIZE); 117 createJobParameters(int jobId)118 private static JobParameters createJobParameters(int jobId) { 119 JobParameters params = mock(JobParameters.class); 120 when(params.getJobId()).thenReturn(jobId); 121 return params; 122 } 123 124 private BackgroundDexOptService mService; 125 126 private StartAndWaitThread mDexOptThread; 127 private StartAndWaitThread mCancelThread; 128 129 @Before setUp()130 public void setUp() throws Exception { 131 // These tests are only applicable to the legacy BackgroundDexOptService and cannot be run 132 // when ART Service is enabled. 133 Assume.assumeFalse(SystemProperties.getBoolean("dalvik.vm.useartservice", false)); 134 135 when(mInjector.getCallingUid()).thenReturn(Process.FIRST_APPLICATION_UID); 136 when(mInjector.getContext()).thenReturn(mContext); 137 when(mInjector.getDexOptHelper()).thenReturn(mDexOptHelper); 138 when(mInjector.getDexManager()).thenReturn(mDexManager); 139 when(mInjector.getPinnerService()).thenReturn(mPinnerService); 140 when(mInjector.getJobScheduler()).thenReturn(mJobScheduler); 141 when(mInjector.getPackageManagerService()).thenReturn(mPackageManager); 142 143 // These mocking can be overwritten in some tests but still keep it here as alternative 144 // takes too many repetitive codes. 145 when(mInjector.getDataDirUsableSpace()).thenReturn(USABLE_SPACE_NORMAL); 146 when(mInjector.getDataDirStorageLowBytes()).thenReturn(STORAGE_LOW_BYTES); 147 when(mInjector.getDexOptThermalCutoff()).thenReturn(PowerManager.THERMAL_STATUS_CRITICAL); 148 when(mInjector.getCurrentThermalStatus()).thenReturn(PowerManager.THERMAL_STATUS_NONE); 149 when(mInjector.supportSecondaryDex()).thenReturn(true); 150 setupDexOptHelper(); 151 152 mService = new BackgroundDexOptService(mInjector); 153 } 154 setupDexOptHelper()155 private void setupDexOptHelper() { 156 when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(DEFAULT_PACKAGE_LIST); 157 when(mDexOptHelper.performDexOptWithStatus(any())).thenAnswer(inv -> { 158 DexoptOptions opt = inv.getArgument(0); 159 if (opt.getPackageName().equals(PACKAGE_AAA)) { 160 return mDexOptResultForPackageAAA; 161 } 162 return PackageDexOptimizer.DEX_OPT_PERFORMED; 163 }); 164 when(mDexOptHelper.performDexOpt(any())).thenReturn(true); 165 } 166 167 @After tearDown()168 public void tearDown() throws Exception { 169 LocalServices.removeServiceForTest(BackgroundDexOptService.class); 170 } 171 172 @Test testGetService()173 public void testGetService() { 174 assertThat(BackgroundDexOptService.getService()).isEqualTo(mService); 175 } 176 177 @Test testBootCompleted()178 public void testBootCompleted() throws Exception { 179 initUntilBootCompleted(); 180 } 181 182 @Test testNoExecutionForIdleJobBeforePostBootUpdate()183 public void testNoExecutionForIdleJobBeforePostBootUpdate() throws Exception { 184 initUntilBootCompleted(); 185 186 assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isFalse(); 187 } 188 189 @Test testNoExecutionForLowStorage()190 public void testNoExecutionForLowStorage() throws Exception { 191 initUntilBootCompleted(); 192 when(mPackageManager.isStorageLow()).thenReturn(true); 193 194 assertThat(mService.onStartJob(mJobServiceForPostBoot, 195 mJobParametersForPostBoot)).isFalse(); 196 verify(mDexOptHelper, never()).performDexOpt(any()); 197 } 198 199 @Test testNoExecutionForNoOptimizablePackages()200 public void testNoExecutionForNoOptimizablePackages() throws Exception { 201 initUntilBootCompleted(); 202 when(mDexOptHelper.getOptimizablePackages(any())).thenReturn(Collections.emptyList()); 203 204 assertThat(mService.onStartJob(mJobServiceForPostBoot, 205 mJobParametersForPostBoot)).isFalse(); 206 verify(mDexOptHelper, never()).performDexOpt(any()); 207 } 208 209 @Test testPostBootUpdateFullRun()210 public void testPostBootUpdateFullRun() throws Exception { 211 initUntilBootCompleted(); 212 213 runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, 214 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, 215 /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null); 216 } 217 218 @Test testPostBootUpdateFullRunWithPackageFailure()219 public void testPostBootUpdateFullRunWithPackageFailure() throws Exception { 220 mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED; 221 222 initUntilBootCompleted(); 223 224 runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, 225 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_DEX_OPT_FAILED, 226 /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA); 227 228 assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA); 229 assertThat(getFailedPackageNamesSecondary()).isEmpty(); 230 } 231 232 @Test testIdleJobFullRun()233 public void testIdleJobFullRun() throws Exception { 234 initUntilBootCompleted(); 235 runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, 236 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, 237 /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null); 238 runFullJob(mJobServiceForIdle, mJobParametersForIdle, 239 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, 240 /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null); 241 } 242 243 @Test testIdleJobFullRunWithFailureOnceAndSuccessAfterUpdate()244 public void testIdleJobFullRunWithFailureOnceAndSuccessAfterUpdate() throws Exception { 245 mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_FAILED; 246 247 initUntilBootCompleted(); 248 249 runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, 250 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_DEX_OPT_FAILED, 251 /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA); 252 253 assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA); 254 assertThat(getFailedPackageNamesSecondary()).isEmpty(); 255 256 runFullJob(mJobServiceForIdle, mJobParametersForIdle, 257 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, 258 /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA); 259 260 assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA); 261 assertThat(getFailedPackageNamesSecondary()).isEmpty(); 262 263 mService.notifyPackageChanged(PACKAGE_AAA); 264 265 assertThat(getFailedPackageNamesPrimary()).isEmpty(); 266 assertThat(getFailedPackageNamesSecondary()).isEmpty(); 267 268 // Succeed this time. 269 mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED; 270 271 runFullJob(mJobServiceForIdle, mJobParametersForIdle, 272 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, 273 /* totalJobFinishedWithParams= */ 2, /* expectedSkippedPackage= */ null); 274 275 assertThat(getFailedPackageNamesPrimary()).isEmpty(); 276 assertThat(getFailedPackageNamesSecondary()).isEmpty(); 277 } 278 279 @Test testIdleJobFullRunWithFatalError()280 public void testIdleJobFullRunWithFatalError() throws Exception { 281 initUntilBootCompleted(); 282 runFullJob(mJobServiceForPostBoot, mJobParametersForPostBoot, 283 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK, 284 /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null); 285 286 doThrow(RuntimeException.class).when(mDexOptHelper).performDexOptWithStatus(any()); 287 288 runFullJob(mJobServiceForIdle, mJobParametersForIdle, 289 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_FATAL_ERROR, 290 /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null); 291 } 292 293 @Test testSystemReadyWhenDisabled()294 public void testSystemReadyWhenDisabled() throws Exception { 295 when(mInjector.isBackgroundDexOptDisabled()).thenReturn(true); 296 297 mService.systemReady(); 298 299 verify(mContext, never()).registerReceiver(any(), any()); 300 } 301 302 @Test testStopByCancelFlag()303 public void testStopByCancelFlag() throws Exception { 304 when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread()); 305 initUntilBootCompleted(); 306 307 assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue(); 308 309 ArgumentCaptor<Runnable> argDexOptThreadRunnable = ArgumentCaptor.forClass(Runnable.class); 310 verify(mInjector, atLeastOnce()).createAndStartThread(any(), 311 argDexOptThreadRunnable.capture()); 312 313 // Stopping requires a separate thread 314 HandlerThread cancelThread = new HandlerThread("Stopping"); 315 cancelThread.start(); 316 when(mInjector.createAndStartThread(any(), any())).thenReturn(cancelThread); 317 318 // Cancel 319 assertThat(mService.onStopJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue(); 320 321 // Capture Runnable for cancel 322 ArgumentCaptor<Runnable> argCancelThreadRunnable = ArgumentCaptor.forClass(Runnable.class); 323 verify(mInjector, atLeastOnce()).createAndStartThread(any(), 324 argCancelThreadRunnable.capture()); 325 326 // Execute cancelling part 327 cancelThread.getThreadHandler().post(argCancelThreadRunnable.getValue()); 328 329 verify(mDexOptHelper, timeout(TEST_WAIT_TIMEOUT_MS)).controlDexOptBlocking(true); 330 331 // Dexopt thread run and cancelled 332 argDexOptThreadRunnable.getValue().run(); 333 334 // Wait until cancellation Runnable is completed. 335 assertThat(cancelThread.getThreadHandler().runWithScissors( 336 argCancelThreadRunnable.getValue(), TEST_WAIT_TIMEOUT_MS)).isTrue(); 337 338 // Now cancel completed 339 verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, true); 340 verifyLastControlDexOptBlockingCall(false); 341 } 342 343 @Test testPostUpdateCancelFirst()344 public void testPostUpdateCancelFirst() throws Exception { 345 initUntilBootCompleted(); 346 when(mInjector.createAndStartThread(any(), any())).thenAnswer( 347 i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1))); 348 349 // Start 350 assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue(); 351 // Cancel 352 assertThat(mService.onStopJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue(); 353 354 mCancelThread.runActualRunnable(); 355 356 // Wait until cancel has set the flag. 357 verify(mDexOptHelper, timeout(TEST_WAIT_TIMEOUT_MS)).controlDexOptBlocking( 358 true); 359 360 mDexOptThread.runActualRunnable(); 361 362 // All threads should finish. 363 mDexOptThread.join(TEST_WAIT_TIMEOUT_MS); 364 mCancelThread.join(TEST_WAIT_TIMEOUT_MS); 365 366 // Retry later if post boot job was cancelled 367 verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, true); 368 verifyLastControlDexOptBlockingCall(false); 369 } 370 371 @Test testPostUpdateCancelLater()372 public void testPostUpdateCancelLater() throws Exception { 373 initUntilBootCompleted(); 374 when(mInjector.createAndStartThread(any(), any())).thenAnswer( 375 i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1))); 376 377 // Start 378 assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue(); 379 // Cancel 380 assertThat(mService.onStopJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue(); 381 382 // Dexopt thread runs and finishes 383 mDexOptThread.runActualRunnable(); 384 mDexOptThread.join(TEST_WAIT_TIMEOUT_MS); 385 386 mCancelThread.runActualRunnable(); 387 mCancelThread.join(TEST_WAIT_TIMEOUT_MS); 388 389 // Already completed before cancel, so no rescheduling. 390 verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, false); 391 verify(mDexOptHelper, never()).controlDexOptBlocking(true); 392 } 393 394 @Test testPeriodicJobCancelFirst()395 public void testPeriodicJobCancelFirst() throws Exception { 396 initUntilBootCompleted(); 397 when(mInjector.createAndStartThread(any(), any())).thenAnswer( 398 i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1))); 399 400 // Start and finish post boot job 401 assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue(); 402 mDexOptThread.runActualRunnable(); 403 mDexOptThread.join(TEST_WAIT_TIMEOUT_MS); 404 405 // Start 406 assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue(); 407 // Cancel 408 assertThat(mService.onStopJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue(); 409 410 mCancelThread.runActualRunnable(); 411 412 // Wait until cancel has set the flag. 413 verify(mDexOptHelper, timeout(TEST_WAIT_TIMEOUT_MS)).controlDexOptBlocking( 414 true); 415 416 mDexOptThread.runActualRunnable(); 417 418 // All threads should finish. 419 mDexOptThread.join(TEST_WAIT_TIMEOUT_MS); 420 mCancelThread.join(TEST_WAIT_TIMEOUT_MS); 421 422 // The job should be rescheduled. 423 verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, true /* wantsReschedule */); 424 verifyLastControlDexOptBlockingCall(false); 425 } 426 427 @Test testPeriodicJobCancelLater()428 public void testPeriodicJobCancelLater() throws Exception { 429 initUntilBootCompleted(); 430 when(mInjector.createAndStartThread(any(), any())).thenAnswer( 431 i -> createAndStartExecutionThread(i.getArgument(0), i.getArgument(1))); 432 433 // Start and finish post boot job 434 assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue(); 435 mDexOptThread.runActualRunnable(); 436 mDexOptThread.join(TEST_WAIT_TIMEOUT_MS); 437 438 // Start 439 assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue(); 440 // Cancel 441 assertThat(mService.onStopJob(mJobServiceForIdle, mJobParametersForIdle)).isTrue(); 442 443 // Dexopt thread finishes first. 444 mDexOptThread.runActualRunnable(); 445 mDexOptThread.join(TEST_WAIT_TIMEOUT_MS); 446 447 mCancelThread.runActualRunnable(); 448 mCancelThread.join(TEST_WAIT_TIMEOUT_MS); 449 450 // Always reschedule for periodic job 451 verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, false); 452 verify(mDexOptHelper, never()).controlDexOptBlocking(true); 453 } 454 455 @Test testStopByThermal()456 public void testStopByThermal() throws Exception { 457 when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread()); 458 initUntilBootCompleted(); 459 460 assertThat(mService.onStartJob(mJobServiceForPostBoot, mJobParametersForPostBoot)).isTrue(); 461 462 ArgumentCaptor<Runnable> argThreadRunnable = ArgumentCaptor.forClass(Runnable.class); 463 verify(mInjector, atLeastOnce()).createAndStartThread(any(), argThreadRunnable.capture()); 464 465 // Thermal cancel level 466 when(mInjector.getCurrentThermalStatus()).thenReturn(PowerManager.THERMAL_STATUS_CRITICAL); 467 468 argThreadRunnable.getValue().run(); 469 470 verify(mJobServiceForPostBoot).jobFinished(mJobParametersForPostBoot, true); 471 verifyLastControlDexOptBlockingCall(false); 472 } 473 474 @Test testRunShellCommandWithInvalidUid()475 public void testRunShellCommandWithInvalidUid() { 476 // Test uid cannot execute the command APIs 477 assertThrows(SecurityException.class, () -> mService.runBackgroundDexoptJob(null)); 478 } 479 480 @Test testCancelShellCommandWithInvalidUid()481 public void testCancelShellCommandWithInvalidUid() { 482 // Test uid cannot execute the command APIs 483 assertThrows(SecurityException.class, () -> mService.cancelBackgroundDexoptJob()); 484 } 485 486 @Test testDisableJobSchedulerJobs()487 public void testDisableJobSchedulerJobs() throws Exception { 488 when(mInjector.getCallingUid()).thenReturn(Process.SHELL_UID); 489 mService.setDisableJobSchedulerJobs(true); 490 assertThat(mService.onStartJob(mJobServiceForIdle, mJobParametersForIdle)).isFalse(); 491 verify(mDexOptHelper, never()).performDexOpt(any()); 492 verify(mDexOptHelper, never()).performDexOptWithStatus(any()); 493 } 494 495 @Test testSetDisableJobSchedulerJobsWithInvalidUid()496 public void testSetDisableJobSchedulerJobsWithInvalidUid() { 497 // Test uid cannot execute the command APIs 498 assertThrows(SecurityException.class, () -> mService.setDisableJobSchedulerJobs(true)); 499 } 500 initUntilBootCompleted()501 private void initUntilBootCompleted() throws Exception { 502 ArgumentCaptor<BroadcastReceiver> argReceiver = ArgumentCaptor.forClass( 503 BroadcastReceiver.class); 504 ArgumentCaptor<IntentFilter> argIntentFilter = ArgumentCaptor.forClass(IntentFilter.class); 505 506 mService.systemReady(); 507 508 verify(mContext).registerReceiver(argReceiver.capture(), argIntentFilter.capture()); 509 assertThat(argIntentFilter.getValue().getAction(0)).isEqualTo(Intent.ACTION_BOOT_COMPLETED); 510 511 argReceiver.getValue().onReceive(mContext, null); 512 513 verify(mContext).unregisterReceiver(argReceiver.getValue()); 514 ArgumentCaptor<JobInfo> argJobs = ArgumentCaptor.forClass(JobInfo.class); 515 verify(mJobScheduler, times(2)).schedule(argJobs.capture()); 516 517 List<Integer> expectedJobIds = Arrays.asList(BackgroundDexOptService.JOB_IDLE_OPTIMIZE, 518 BackgroundDexOptService.JOB_POST_BOOT_UPDATE); 519 List<Integer> jobIds = argJobs.getAllValues().stream().map(job -> job.getId()).collect( 520 Collectors.toList()); 521 assertThat(jobIds).containsExactlyElementsIn(expectedJobIds); 522 } 523 verifyLastControlDexOptBlockingCall(boolean expected)524 private void verifyLastControlDexOptBlockingCall(boolean expected) throws Exception { 525 ArgumentCaptor<Boolean> argDexOptBlock = ArgumentCaptor.forClass(Boolean.class); 526 verify(mDexOptHelper, atLeastOnce()).controlDexOptBlocking(argDexOptBlock.capture()); 527 assertThat(argDexOptBlock.getValue()).isEqualTo(expected); 528 } 529 runFullJob(BackgroundDexOptJobService jobService, JobParameters params, boolean expectedReschedule, int expectedStatus, int totalJobFinishedWithParams, @Nullable String expectedSkippedPackage)530 private void runFullJob(BackgroundDexOptJobService jobService, JobParameters params, 531 boolean expectedReschedule, int expectedStatus, int totalJobFinishedWithParams, 532 @Nullable String expectedSkippedPackage) throws Exception { 533 when(mInjector.createAndStartThread(any(), any())).thenReturn(Thread.currentThread()); 534 addFullRunSequence(expectedSkippedPackage); 535 assertThat(mService.onStartJob(jobService, params)).isTrue(); 536 537 ArgumentCaptor<Runnable> argThreadRunnable = ArgumentCaptor.forClass(Runnable.class); 538 verify(mInjector, atLeastOnce()).createAndStartThread(any(), argThreadRunnable.capture()); 539 540 try { 541 argThreadRunnable.getValue().run(); 542 } catch (RuntimeException e) { 543 if (expectedStatus != STATUS_FATAL_ERROR) { 544 throw e; 545 } 546 } 547 548 verify(jobService, times(totalJobFinishedWithParams)).jobFinished(params, 549 expectedReschedule); 550 // Never block 551 verify(mDexOptHelper, never()).controlDexOptBlocking(true); 552 if (expectedStatus != STATUS_FATAL_ERROR) { 553 verifyPerformDexOpt(); 554 } 555 assertThat(getLastExecutionStatus()).isEqualTo(expectedStatus); 556 } 557 verifyPerformDexOpt()558 private void verifyPerformDexOpt() { 559 InOrder inOrder = inOrder(mDexOptHelper); 560 inOrder.verify(mDexOptHelper).getOptimizablePackages(any()); 561 for (DexOptInfo info : mDexInfoSequence) { 562 if (info.isPrimary) { 563 verify(mDexOptHelper).performDexOptWithStatus( 564 argThat((option) -> option.getPackageName().equals(info.packageName) 565 && !option.isDexoptOnlySecondaryDex())); 566 } else { 567 inOrder.verify(mDexOptHelper).performDexOpt( 568 argThat((option) -> option.getPackageName().equals(info.packageName) 569 && option.isDexoptOnlySecondaryDex())); 570 } 571 } 572 573 // Even InOrder cannot check the order if the same call is made multiple times. 574 // To check the order across multiple runs, we reset the mock so that order can be checked 575 // in each call. 576 mDexInfoSequence.clear(); 577 reset(mDexOptHelper); 578 setupDexOptHelper(); 579 } 580 findDumpValueForKey(String key)581 private String findDumpValueForKey(String key) { 582 ByteArrayOutputStream out = new ByteArrayOutputStream(); 583 PrintWriter pw = new PrintWriter(out, true); 584 IndentingPrintWriter writer = new IndentingPrintWriter(pw, ""); 585 try { 586 mService.dump(writer); 587 writer.flush(); 588 Log.i(TAG, "dump output:" + out.toString()); 589 for (String line : out.toString().split(System.lineSeparator())) { 590 String[] vals = line.split(":"); 591 if (vals[0].equals(key)) { 592 if (vals.length == 2) { 593 return vals[1].strip(); 594 } else { 595 break; 596 } 597 } 598 } 599 return ""; 600 } finally { 601 writer.close(); 602 } 603 } 604 findStringListFromDump(String key)605 List<String> findStringListFromDump(String key) { 606 String values = findDumpValueForKey(key); 607 if (values.isEmpty()) { 608 return Collections.emptyList(); 609 } 610 return Arrays.asList(values.split(",")); 611 } 612 getFailedPackageNamesPrimary()613 private List<String> getFailedPackageNamesPrimary() { 614 return findStringListFromDump("mFailedPackageNamesPrimary"); 615 } 616 getFailedPackageNamesSecondary()617 private List<String> getFailedPackageNamesSecondary() { 618 return findStringListFromDump("mFailedPackageNamesSecondary"); 619 } 620 getLastExecutionStatus()621 private int getLastExecutionStatus() { 622 return Integer.parseInt(findDumpValueForKey("mLastExecutionStatus")); 623 } 624 625 private static class DexOptInfo { 626 public final String packageName; 627 public final boolean isPrimary; 628 DexOptInfo(String packageName, boolean isPrimary)629 private DexOptInfo(String packageName, boolean isPrimary) { 630 this.packageName = packageName; 631 this.isPrimary = isPrimary; 632 } 633 } 634 addFullRunSequence(@ullable String expectedSkippedPackage)635 private void addFullRunSequence(@Nullable String expectedSkippedPackage) { 636 for (String packageName : DEFAULT_PACKAGE_LIST) { 637 if (packageName.equals(expectedSkippedPackage)) { 638 // only fails primary dexopt in mocking but add secodary 639 mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ false)); 640 } else { 641 mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ true)); 642 mDexInfoSequence.add(new DexOptInfo(packageName, /* isPrimary= */ false)); 643 } 644 } 645 } 646 647 private static class StartAndWaitThread extends Thread { 648 private final Runnable mActualRunnable; 649 private final CountDownLatch mLatch = new CountDownLatch(1); 650 StartAndWaitThread(String name, Runnable runnable)651 private StartAndWaitThread(String name, Runnable runnable) { 652 super(name); 653 mActualRunnable = runnable; 654 } 655 runActualRunnable()656 private void runActualRunnable() { 657 mLatch.countDown(); 658 } 659 660 @Override run()661 public void run() { 662 // Thread is started but does not run actual code. This is for controlling the execution 663 // order while still meeting Thread.isAlive() check. 664 try { 665 mLatch.await(); 666 } catch (InterruptedException e) { 667 throw new RuntimeException(e); 668 } 669 mActualRunnable.run(); 670 } 671 } 672 createAndStartExecutionThread(String name, Runnable runnable)673 private Thread createAndStartExecutionThread(String name, Runnable runnable) { 674 final boolean isDexOptThread = !name.equals("DexOptCancel"); 675 StartAndWaitThread thread = new StartAndWaitThread(name, runnable); 676 if (isDexOptThread) { 677 mDexOptThread = thread; 678 } else { 679 mCancelThread = thread; 680 } 681 thread.start(); 682 return thread; 683 } 684 } 685