1 /* 2 * Copyright 2017 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 androidx.work.impl; 18 19 import static android.app.job.JobParameters.STOP_REASON_CONSTRAINT_CHARGING; 20 21 import static androidx.work.WorkInfo.State.BLOCKED; 22 import static androidx.work.WorkInfo.State.CANCELLED; 23 import static androidx.work.WorkInfo.State.ENQUEUED; 24 import static androidx.work.WorkInfo.State.FAILED; 25 import static androidx.work.WorkInfo.State.RUNNING; 26 import static androidx.work.WorkInfo.State.SUCCEEDED; 27 28 import static org.hamcrest.CoreMatchers.containsString; 29 import static org.hamcrest.CoreMatchers.equalTo; 30 import static org.hamcrest.CoreMatchers.instanceOf; 31 import static org.hamcrest.CoreMatchers.is; 32 import static org.hamcrest.CoreMatchers.notNullValue; 33 import static org.hamcrest.CoreMatchers.nullValue; 34 import static org.hamcrest.MatcherAssert.assertThat; 35 import static org.hamcrest.Matchers.contains; 36 import static org.hamcrest.Matchers.containsInAnyOrder; 37 import static org.hamcrest.Matchers.greaterThan; 38 import static org.hamcrest.Matchers.greaterThanOrEqualTo; 39 import static org.hamcrest.Matchers.isOneOf; 40 import static org.mockito.Mockito.mock; 41 import static org.mockito.Mockito.verify; 42 import static org.mockito.Mockito.when; 43 44 import static java.util.concurrent.TimeUnit.HOURS; 45 import static java.util.concurrent.TimeUnit.MINUTES; 46 47 import android.annotation.SuppressLint; 48 import android.content.Context; 49 import android.net.Uri; 50 import android.util.Log; 51 52 import androidx.core.util.Consumer; 53 import androidx.test.core.app.ApplicationProvider; 54 import androidx.test.ext.junit.runners.AndroidJUnit4; 55 import androidx.test.filters.LargeTest; 56 import androidx.test.filters.MediumTest; 57 import androidx.test.filters.SdkSuppress; 58 import androidx.test.filters.SmallTest; 59 import androidx.testutils.RepeatRule; 60 import androidx.work.ArrayCreatingInputMerger; 61 import androidx.work.BackoffPolicy; 62 import androidx.work.Configuration; 63 import androidx.work.Data; 64 import androidx.work.DatabaseTest; 65 import androidx.work.ForegroundUpdater; 66 import androidx.work.ListenableWorker; 67 import androidx.work.OneTimeWorkRequest; 68 import androidx.work.PeriodicWorkRequest; 69 import androidx.work.ProgressUpdater; 70 import androidx.work.Tracer; 71 import androidx.work.WorkInfo; 72 import androidx.work.WorkRequest; 73 import androidx.work.Worker; 74 import androidx.work.WorkerExceptionInfo; 75 import androidx.work.WorkerFactory; 76 import androidx.work.WorkerParameters; 77 import androidx.work.impl.foreground.ForegroundProcessor; 78 import androidx.work.impl.model.Dependency; 79 import androidx.work.impl.model.DependencyDao; 80 import androidx.work.impl.model.WorkSpec; 81 import androidx.work.impl.model.WorkSpecDao; 82 import androidx.work.impl.testutils.TestOverrideClock; 83 import androidx.work.impl.testutils.TrackingWorkerFactory; 84 import androidx.work.impl.utils.SynchronousExecutor; 85 import androidx.work.impl.utils.taskexecutor.InstantWorkTaskExecutor; 86 import androidx.work.impl.utils.taskexecutor.TaskExecutor; 87 import androidx.work.worker.ChainedArgumentWorker; 88 import androidx.work.worker.EchoingWorker; 89 import androidx.work.worker.ExceptionInConstructionWorker; 90 import androidx.work.worker.ExceptionWorker; 91 import androidx.work.worker.FailureWorker; 92 import androidx.work.worker.InterruptionAwareWorker; 93 import androidx.work.worker.LatchWorker; 94 import androidx.work.worker.NeverResolvedWorker; 95 import androidx.work.worker.RetryWorker; 96 import androidx.work.worker.ReturnNullResultWorker; 97 import androidx.work.worker.TestWorker; 98 import androidx.work.worker.UsedWorker; 99 100 import com.google.common.util.concurrent.ListenableFuture; 101 102 import kotlinx.coroutines.Dispatchers; 103 104 import org.jspecify.annotations.NonNull; 105 import org.jspecify.annotations.Nullable; 106 import org.junit.After; 107 import org.junit.Before; 108 import org.junit.Rule; 109 import org.junit.Test; 110 import org.junit.runner.RunWith; 111 import org.mockito.ArgumentCaptor; 112 113 import java.lang.reflect.InvocationTargetException; 114 import java.util.Arrays; 115 import java.util.Collections; 116 import java.util.List; 117 import java.util.UUID; 118 import java.util.concurrent.ExecutionException; 119 import java.util.concurrent.Executor; 120 import java.util.concurrent.ExecutorService; 121 import java.util.concurrent.Executors; 122 import java.util.concurrent.TimeUnit; 123 124 @RunWith(AndroidJUnit4.class) 125 public class WorkerWrapperTest extends DatabaseTest { 126 127 private Configuration mConfiguration; 128 private TaskExecutor mWorkTaskExecutor; 129 private WorkSpecDao mWorkSpecDao; 130 private DependencyDao mDependencyDao; 131 private Context mContext; 132 private TestOverrideClock mTestClock = new TestOverrideClock(); 133 private ForegroundProcessor mMockForegroundProcessor; 134 private ProgressUpdater mMockProgressUpdater; 135 private ForegroundUpdater mMockForegroundUpdater; 136 private Executor mSynchronousExecutor = new SynchronousExecutor(); 137 private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor(); 138 private TestWorkerExceptionHandler mWorkerExceptionHandler; 139 private Tracer mTracer; 140 141 @Before setUp()142 public void setUp() { 143 mContext = ApplicationProvider.getApplicationContext(); 144 mTracer = mock(Tracer.class); 145 // Turn on tracing so we can ensure trace sections are correctly emitted. 146 when(mTracer.isEnabled()).thenReturn(true); 147 mWorkerExceptionHandler = new TestWorkerExceptionHandler(); 148 mConfiguration = new Configuration.Builder() 149 .setExecutor(new SynchronousExecutor()) 150 .setMinimumLoggingLevel(Log.VERBOSE) 151 .setClock(mTestClock) 152 .setWorkerInitializationExceptionHandler(mWorkerExceptionHandler) 153 .setWorkerExecutionExceptionHandler(mWorkerExceptionHandler) 154 .setTracer(mTracer) 155 .build(); 156 mWorkTaskExecutor = new InstantWorkTaskExecutor(); 157 mWorkSpecDao = mDatabase.workSpecDao(); 158 mDependencyDao = mDatabase.dependencyDao(); 159 mMockForegroundProcessor = mock(ForegroundProcessor.class); 160 mMockProgressUpdater = mock(ProgressUpdater.class); 161 mMockForegroundUpdater = mock(ForegroundUpdater.class); 162 } 163 164 @After tearDown()165 public void tearDown() { 166 mExecutorService.shutdown(); 167 try { 168 assertThat(mExecutorService.awaitTermination(3, TimeUnit.SECONDS), is(true)); 169 } catch (InterruptedException e) { 170 throw new RuntimeException(e); 171 } 172 mDatabase.close(); 173 } 174 175 @Rule 176 public RepeatRule mRepeatRule = new RepeatRule(); 177 178 @Test 179 @SmallTest testSuccess()180 public void testSuccess() { 181 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); 182 insertWork(work); 183 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 184 FutureListener listener = createAndAddFutureListener(workerWrapper); 185 assertThat(listener.mResult, is(false)); 186 assertThat(mWorkSpecDao.getState(work.getStringId()), is(SUCCEEDED)); 187 assertBeginEndTraceSpans(work.getWorkSpec()); 188 } 189 190 @Test 191 @SmallTest testRunAttemptCountIncremented_successfulExecution()192 public void testRunAttemptCountIncremented_successfulExecution() { 193 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); 194 insertWork(work); 195 createBuilder(work.getStringId()) 196 .build() 197 .launch(); 198 WorkSpec latestWorkSpec = mWorkSpecDao.getWorkSpec(work.getStringId()); 199 assertThat(latestWorkSpec.runAttemptCount, is(1)); 200 assertBeginEndTraceSpans(work.getWorkSpec()); 201 } 202 203 @Test 204 @SmallTest testRunAttemptCountIncremented_failedExecution()205 public void testRunAttemptCountIncremented_failedExecution() { 206 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build(); 207 insertWork(work); 208 createBuilder(work.getStringId()) 209 .build() 210 .launch(); 211 WorkSpec latestWorkSpec = mWorkSpecDao.getWorkSpec(work.getStringId()); 212 assertThat(latestWorkSpec.runAttemptCount, is(1)); 213 assertBeginEndTraceSpans(work.getWorkSpec()); 214 } 215 216 @Test 217 @SmallTest testInvalidWorkerClassName()218 public void testInvalidWorkerClassName() { 219 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); 220 work.getWorkSpec().workerClassName = "dummy"; 221 insertWork(work); 222 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 223 FutureListener listener = createAndAddFutureListener(workerWrapper); 224 assertThat(listener.mResult, is(false)); 225 assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED)); 226 } 227 228 @Test(expected = IllegalStateException.class) 229 @SmallTest testUsedWorker_failsExecution()230 public void testUsedWorker_failsExecution() { 231 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); 232 insertWork(work); 233 234 UsedWorker usedWorker = (UsedWorker) mConfiguration.getWorkerFactory() 235 .createWorkerWithDefaultFallback( 236 mContext.getApplicationContext(), 237 UsedWorker.class.getName(), 238 new WorkerParameters( 239 work.getId(), 240 Data.EMPTY, 241 work.getTags(), 242 new WorkerParameters.RuntimeExtras(), 243 1, 244 0, 245 mSynchronousExecutor, 246 Dispatchers.getDefault(), 247 mWorkTaskExecutor, 248 mConfiguration.getWorkerFactory(), 249 mMockProgressUpdater, 250 mMockForegroundUpdater)); 251 252 WorkerWrapper workerWrapper = createBuilder(work.getStringId()) 253 .withWorker(usedWorker) 254 .build(); 255 workerWrapper.launch(); 256 assertBeginEndTraceSpans(work.getWorkSpec()); 257 } 258 259 @Test 260 @SmallTest testNotEnqueued()261 public void testNotEnqueued() { 262 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class) 263 .setInitialState(RUNNING) 264 .build(); 265 insertWork(work); 266 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 267 FutureListener listener = createAndAddFutureListener(workerWrapper); 268 assertThat(listener.mResult, is(true)); 269 } 270 271 @Test 272 @SmallTest testCancelled()273 public void testCancelled() { 274 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class) 275 .setInitialState(CANCELLED) 276 .build(); 277 insertWork(work); 278 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 279 FutureListener listener = createAndAddFutureListener(workerWrapper); 280 assertThat(listener.mResult, is(false)); 281 assertThat(mWorkSpecDao.getState(work.getStringId()), is(CANCELLED)); 282 assertBeginEndTraceSpans(work.getWorkSpec()); 283 } 284 285 @Test 286 @SmallTest testPermanentErrorWithInvalidWorkerClass()287 public void testPermanentErrorWithInvalidWorkerClass() { 288 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); 289 work.getWorkSpec().workerClassName = "INVALID_CLASS_NAME"; 290 insertWork(work); 291 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 292 FutureListener listener = createAndAddFutureListener(workerWrapper); 293 assertThat(listener.mResult, is(false)); 294 assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED)); 295 } 296 297 @Test 298 @SmallTest testPermanentErrorWithInvalidInputMergerClass()299 public void testPermanentErrorWithInvalidInputMergerClass() { 300 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); 301 work.getWorkSpec().inputMergerClassName = "INVALID_CLASS_NAME"; 302 insertWork(work); 303 WorkerWrapper workerWrapper = createBuilder(work.getStringId()) 304 .build(); 305 FutureListener listener = createAndAddFutureListener(workerWrapper); 306 assertThat(listener.mResult, is(false)); 307 assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED)); 308 } 309 310 @Test 311 @SmallTest testFailed()312 public void testFailed() { 313 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build(); 314 insertWork(work); 315 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 316 FutureListener listener = createAndAddFutureListener(workerWrapper); 317 assertThat(listener.mResult, is(false)); 318 assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED)); 319 } 320 321 @Test 322 @LargeTest testFailedOnDeepHierarchy()323 public void testFailedOnDeepHierarchy() { 324 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(FailureWorker.class).build(); 325 insertWork(work); 326 String previousId = work.getStringId(); 327 String firstWorkId = previousId; 328 for (int i = 0; i < 500; ++i) { 329 work = new OneTimeWorkRequest.Builder(FailureWorker.class).build(); 330 insertWork(work); 331 mDependencyDao.insertDependency(new Dependency(work.getStringId(), previousId)); 332 previousId = work.getStringId(); 333 } 334 WorkerWrapper workerWrapper = createBuilder(firstWorkId).build(); 335 workerWrapper.setFailed(new ListenableWorker.Result.Failure()); 336 assertThat(mWorkSpecDao.getState(firstWorkId), is(FAILED)); 337 assertThat(mWorkSpecDao.getState(previousId), is(FAILED)); 338 } 339 340 @Test 341 @SmallTest testRunning_onlyWhenEnqueued()342 public void testRunning_onlyWhenEnqueued() { 343 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class) 344 .setInitialState(RUNNING) 345 .build(); 346 insertWork(work); 347 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 348 FutureListener listener = createAndAddFutureListener(workerWrapper); 349 assertThat(listener.mResult, is(true)); 350 assertBeginEndTraceSpans(work.getWorkSpec()); 351 } 352 353 @Test 354 @SmallTest testDependencies_passesOutputs()355 public void testDependencies_passesOutputs() { 356 OneTimeWorkRequest prerequisiteWork = 357 new OneTimeWorkRequest.Builder(ChainedArgumentWorker.class).build(); 358 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class) 359 .setInitialState(BLOCKED) 360 .build(); 361 Dependency dependency = new Dependency(work.getStringId(), prerequisiteWork.getStringId()); 362 363 mDatabase.beginTransaction(); 364 try { 365 insertWork(prerequisiteWork); 366 insertWork(work); 367 mDependencyDao.insertDependency(dependency); 368 mDatabase.setTransactionSuccessful(); 369 } finally { 370 mDatabase.endTransaction(); 371 } 372 373 createBuilder(prerequisiteWork.getStringId()) 374 .build().launch(); 375 376 List<Data> arguments = mWorkSpecDao.getInputsFromPrerequisites(work.getStringId()); 377 assertThat(arguments.size(), is(1)); 378 assertThat(arguments, contains(ChainedArgumentWorker.getChainedArguments())); 379 assertBeginEndTraceSpans(prerequisiteWork.getWorkSpec()); 380 } 381 382 @Test 383 @SmallTest testDependencies_passesMergedOutputs()384 public void testDependencies_passesMergedOutputs() { 385 String key = "key"; 386 String value1 = "value1"; 387 String value2 = "value2"; 388 389 OneTimeWorkRequest prerequisiteWork1 = new OneTimeWorkRequest.Builder(EchoingWorker.class) 390 .setInputData(new Data.Builder().putString(key, value1).build()) 391 .build(); 392 OneTimeWorkRequest prerequisiteWork2 = new OneTimeWorkRequest.Builder(EchoingWorker.class) 393 .setInputData(new Data.Builder().putString(key, value2).build()) 394 .build(); 395 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class) 396 .setInputMerger(ArrayCreatingInputMerger.class) 397 .build(); 398 String id = work.getStringId(); 399 Dependency dependency1 = 400 new Dependency(id, prerequisiteWork1.getStringId()); 401 Dependency dependency2 = 402 new Dependency(id, prerequisiteWork2.getStringId()); 403 404 mDatabase.beginTransaction(); 405 try { 406 insertWork(prerequisiteWork1); 407 insertWork(prerequisiteWork2); 408 insertWork(work); 409 mDependencyDao.insertDependency(dependency1); 410 mDependencyDao.insertDependency(dependency2); 411 mDatabase.setTransactionSuccessful(); 412 } finally { 413 mDatabase.endTransaction(); 414 } 415 416 // Run the prerequisites. 417 createBuilder(prerequisiteWork1.getStringId()).build().launch(); 418 419 createBuilder(prerequisiteWork2.getStringId()).build().launch(); 420 421 TrackingWorkerFactory factory = new TrackingWorkerFactory(); 422 Configuration configuration = new Configuration.Builder(mConfiguration) 423 .setWorkerFactory(factory) 424 .build(); 425 WorkerWrapper workerWrapper = new WorkerWrapper.Builder( 426 mContext, 427 configuration, 428 mWorkTaskExecutor, 429 mMockForegroundProcessor, 430 mDatabase, 431 mWorkSpecDao.getWorkSpec(id), 432 mDatabase.workTagDao().getTagsForWorkSpecId(id) 433 ).build(); 434 // Create and run the dependent work. 435 workerWrapper.launch(); 436 437 ListenableWorker worker = factory.awaitWorker(UUID.fromString(id)); 438 Data input = worker.getInputData(); 439 assertThat(input.size(), is(1)); 440 assertThat(Arrays.asList(input.getStringArray(key)), containsInAnyOrder(value1, value2)); 441 } 442 443 @Test 444 @SmallTest testDependencies_setsPeriodStartTimesForUnblockedWork()445 public void testDependencies_setsPeriodStartTimesForUnblockedWork() { 446 OneTimeWorkRequest prerequisiteWork = 447 new OneTimeWorkRequest.Builder(TestWorker.class).build(); 448 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class) 449 .setInitialState(BLOCKED) 450 .build(); 451 Dependency dependency = new Dependency(work.getStringId(), prerequisiteWork.getStringId()); 452 453 mDatabase.beginTransaction(); 454 try { 455 insertWork(prerequisiteWork); 456 insertWork(work); 457 mDependencyDao.insertDependency(dependency); 458 mDatabase.setTransactionSuccessful(); 459 } finally { 460 mDatabase.endTransaction(); 461 } 462 463 assertThat(mWorkSpecDao.getWorkSpec(work.getStringId()).lastEnqueueTime, is(-1L)); 464 465 long beforeUnblockedTime = System.currentTimeMillis(); 466 467 createBuilder(prerequisiteWork.getStringId()).build().launch(); 468 469 WorkSpec workSpec = mWorkSpecDao.getWorkSpec(work.getStringId()); 470 assertThat(workSpec.lastEnqueueTime, is(greaterThanOrEqualTo(beforeUnblockedTime))); 471 } 472 473 @Test 474 @SmallTest testDependencies_enqueuesBlockedDependentsOnSuccess()475 public void testDependencies_enqueuesBlockedDependentsOnSuccess() { 476 OneTimeWorkRequest prerequisiteWork = 477 new OneTimeWorkRequest.Builder(TestWorker.class).build(); 478 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class) 479 .setInitialState(BLOCKED) 480 .build(); 481 OneTimeWorkRequest cancelledWork = new OneTimeWorkRequest.Builder(TestWorker.class) 482 .setInitialState(CANCELLED) 483 .build(); 484 Dependency dependency1 = new Dependency(work.getStringId(), prerequisiteWork.getStringId()); 485 Dependency dependency2 = 486 new Dependency(cancelledWork.getStringId(), prerequisiteWork.getStringId()); 487 488 mDatabase.beginTransaction(); 489 try { 490 insertWork(prerequisiteWork); 491 insertWork(work); 492 insertWork(cancelledWork); 493 mDependencyDao.insertDependency(dependency1); 494 mDependencyDao.insertDependency(dependency2); 495 mDatabase.setTransactionSuccessful(); 496 } finally { 497 mDatabase.endTransaction(); 498 } 499 500 createBuilder(prerequisiteWork.getStringId()) 501 .build() 502 .launch(); 503 504 assertThat(mWorkSpecDao.getState(prerequisiteWork.getStringId()), is(SUCCEEDED)); 505 assertThat(mWorkSpecDao.getState(work.getStringId()), 506 isOneOf(ENQUEUED, RUNNING, SUCCEEDED)); 507 assertThat(mWorkSpecDao.getState(cancelledWork.getStringId()), is(CANCELLED)); 508 assertBeginEndTraceSpans(prerequisiteWork.getWorkSpec()); 509 } 510 511 @Test 512 @SmallTest testDependencies_failsUncancelledDependentsOnFailure()513 public void testDependencies_failsUncancelledDependentsOnFailure() { 514 OneTimeWorkRequest prerequisiteWork = 515 new OneTimeWorkRequest.Builder(FailureWorker.class).build(); 516 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class) 517 .setInitialState(BLOCKED) 518 .build(); 519 OneTimeWorkRequest cancelledWork = new OneTimeWorkRequest.Builder(TestWorker.class) 520 .setInitialState(CANCELLED) 521 .build(); 522 Dependency dependency1 = new Dependency(work.getStringId(), prerequisiteWork.getStringId()); 523 Dependency dependency2 = 524 new Dependency(cancelledWork.getStringId(), prerequisiteWork.getStringId()); 525 526 mDatabase.beginTransaction(); 527 try { 528 insertWork(prerequisiteWork); 529 insertWork(work); 530 insertWork(cancelledWork); 531 mDependencyDao.insertDependency(dependency1); 532 mDependencyDao.insertDependency(dependency2); 533 mDatabase.setTransactionSuccessful(); 534 } finally { 535 mDatabase.endTransaction(); 536 } 537 538 createBuilder(prerequisiteWork.getStringId()) 539 .build() 540 .launch(); 541 542 assertThat(mWorkSpecDao.getState(prerequisiteWork.getStringId()), is(FAILED)); 543 assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED)); 544 assertThat(mWorkSpecDao.getState(cancelledWork.getStringId()), is(CANCELLED)); 545 } 546 547 @Test 548 @SmallTest testBackedOffOneTimeWork_doesNotRun()549 public void testBackedOffOneTimeWork_doesNotRun() { 550 OneTimeWorkRequest retryWork = 551 new OneTimeWorkRequest.Builder(RetryWorker.class).build(); 552 553 long future = System.currentTimeMillis() + HOURS.toMillis(1); 554 mDatabase.beginTransaction(); 555 try { 556 mWorkSpecDao.insertWorkSpec(retryWork.getWorkSpec()); 557 mWorkSpecDao.setLastEnqueueTime(retryWork.getStringId(), future); 558 mWorkSpecDao.incrementWorkSpecRunAttemptCount(retryWork.getStringId()); 559 mDatabase.setTransactionSuccessful(); 560 } finally { 561 mDatabase.endTransaction(); 562 } 563 564 createBuilder(retryWork.getStringId()) 565 .build() 566 .launch(); 567 568 WorkSpec workSpec = mWorkSpecDao.getWorkSpec(retryWork.getStringId()); 569 // The run attempt count should remain the same 570 assertThat(workSpec.runAttemptCount, is(1)); 571 assertBeginEndTraceSpans(workSpec); 572 } 573 574 @Test 575 @SmallTest testRun_periodicWork_success_updatesPeriodStartTime()576 public void testRun_periodicWork_success_updatesPeriodStartTime() { 577 long intervalDurationMillis = PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS; 578 long periodStartTimeMillis = System.currentTimeMillis(); 579 580 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 581 TestWorker.class, intervalDurationMillis, TimeUnit.MILLISECONDS).build(); 582 583 periodicWork.getWorkSpec().lastEnqueueTime = periodStartTimeMillis; 584 insertWork(periodicWork); 585 586 createBuilder(periodicWork.getStringId()) 587 .build() 588 .launch(); 589 590 WorkSpec updatedWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 591 assertThat(updatedWorkSpec.calculateNextRunTime(), greaterThan(periodStartTimeMillis)); 592 assertBeginEndTraceSpans(periodicWork.getWorkSpec()); 593 } 594 595 @Test 596 @SmallTest testRun_periodicWork_failure_updatesPeriodStartTime()597 public void testRun_periodicWork_failure_updatesPeriodStartTime() { 598 long intervalDurationMillis = PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS; 599 long periodStartTimeMillis = System.currentTimeMillis(); 600 601 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 602 FailureWorker.class, intervalDurationMillis, TimeUnit.MILLISECONDS).build(); 603 604 periodicWork.getWorkSpec().lastEnqueueTime = periodStartTimeMillis; 605 insertWork(periodicWork); 606 607 createBuilder(periodicWork.getStringId()) 608 .build() 609 .launch(); 610 611 WorkSpec updatedWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 612 assertThat(updatedWorkSpec.calculateNextRunTime(), greaterThan(periodStartTimeMillis)); 613 assertBeginEndTraceSpans(periodicWork.getWorkSpec()); 614 } 615 616 @Test 617 @SmallTest testPeriodicWork_success()618 public void testPeriodicWork_success() { 619 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 620 TestWorker.class, 621 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, 622 TimeUnit.MILLISECONDS) 623 .build(); 624 625 final String periodicWorkId = periodicWork.getStringId(); 626 insertWork(periodicWork); 627 WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build(); 628 FutureListener listener = createAndAddFutureListener(workerWrapper); 629 630 WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId); 631 assertThat(listener.mResult, is(false)); 632 assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(0)); 633 assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED)); 634 assertBeginEndTraceSpans(periodicWork.getWorkSpec()); 635 } 636 637 @Test 638 @SmallTest testPeriodicWork_fail()639 public void testPeriodicWork_fail() { 640 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 641 FailureWorker.class, 642 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, 643 TimeUnit.MILLISECONDS) 644 .build(); 645 646 final String periodicWorkId = periodicWork.getStringId(); 647 insertWork(periodicWork); 648 WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build(); 649 FutureListener listener = createAndAddFutureListener(workerWrapper); 650 651 WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId); 652 assertThat(listener.mResult, is(false)); 653 assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(0)); 654 assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED)); 655 assertBeginEndTraceSpans(periodicWork.getWorkSpec()); 656 } 657 658 @Test 659 @SmallTest testPeriodicWork_retry()660 public void testPeriodicWork_retry() { 661 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 662 RetryWorker.class, 663 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, 664 TimeUnit.MILLISECONDS) 665 .build(); 666 667 final String periodicWorkId = periodicWork.getStringId(); 668 insertWork(periodicWork); 669 WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build(); 670 FutureListener listener = createAndAddFutureListener(workerWrapper); 671 672 WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId); 673 assertThat(listener.mResult, is(true)); 674 assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(1)); 675 assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED)); 676 assertBeginEndTraceSpans(periodicWork.getWorkSpec()); 677 } 678 679 680 @Test 681 @SmallTest testPeriodic_dedupe()682 public void testPeriodic_dedupe() { 683 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 684 TestWorker.class, 685 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, 686 TimeUnit.MILLISECONDS) 687 .build(); 688 689 final String periodicWorkId = periodicWork.getStringId(); 690 final WorkSpec workSpec = periodicWork.getWorkSpec(); 691 long now = System.currentTimeMillis(); 692 workSpec.lastEnqueueTime = now + workSpec.intervalDuration; 693 workSpec.setPeriodCount(1); 694 insertWork(periodicWork); 695 WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build(); 696 FutureListener listener = createAndAddFutureListener(workerWrapper); 697 // Should get rescheduled 698 assertThat(listener.mResult, is(true)); 699 assertBeginEndTraceSpans(periodicWork.getWorkSpec()); 700 } 701 702 @Test 703 @SmallTest testPeriodic_firstRun_flexApplied()704 public void testPeriodic_firstRun_flexApplied() { 705 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 706 TestWorker.class, 707 PeriodicWorkRequest.MIN_PERIODIC_INTERVAL_MILLIS, 708 TimeUnit.MILLISECONDS, 709 PeriodicWorkRequest.MIN_PERIODIC_FLEX_MILLIS, 710 TimeUnit.MILLISECONDS) 711 .build(); 712 713 final String periodicWorkId = periodicWork.getStringId(); 714 final WorkSpec workSpec = periodicWork.getWorkSpec(); 715 workSpec.lastEnqueueTime = System.currentTimeMillis(); 716 insertWork(periodicWork); 717 WorkerWrapper workerWrapper = createBuilder(periodicWorkId).build(); 718 FutureListener listener = createAndAddFutureListener(workerWrapper); 719 // Should get rescheduled because flex should be respected. 720 assertThat(listener.mResult, is(true)); 721 assertBeginEndTraceSpans(periodicWork.getWorkSpec()); 722 } 723 724 @Test 725 @SmallTest testNextScheduleTimeOverride_delayNormalNextSchedule()726 public void testNextScheduleTimeOverride_delayNormalNextSchedule() { 727 mTestClock.currentTimeMillis = HOURS.toMillis(5); 728 long lastEnqueueTimeMillis = HOURS.toMillis(4); 729 long intervalDurationMillis = HOURS.toMillis(1); 730 // Delay the next run 731 long nextScheduleTimeOverrideMillis = lastEnqueueTimeMillis + HOURS.toMillis(10); 732 733 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 734 TestWorker.class, intervalDurationMillis, TimeUnit.MILLISECONDS) 735 .setNextScheduleTimeOverride(nextScheduleTimeOverrideMillis) 736 .build(); 737 738 periodicWork.getWorkSpec().lastEnqueueTime = lastEnqueueTimeMillis; 739 insertWork(periodicWork); 740 741 // Try to run when the normal period would have happened 742 mTestClock.currentTimeMillis = lastEnqueueTimeMillis + intervalDurationMillis + 1; 743 createBuilder(periodicWork.getStringId()).build().launch(); 744 745 // Didn't actually run or do anything, since it's too soon to run 746 WorkSpec firstTryWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 747 assertThat(firstTryWorkSpec.getNextScheduleTimeOverride(), 748 equalTo(nextScheduleTimeOverrideMillis)); 749 assertThat(firstTryWorkSpec.getPeriodCount(), equalTo(0)); 750 assertThat(firstTryWorkSpec.calculateNextRunTime(), 751 equalTo(nextScheduleTimeOverrideMillis)); 752 753 // Try again at the override time 754 long actualWorkRunTime = nextScheduleTimeOverrideMillis; 755 mTestClock.currentTimeMillis = actualWorkRunTime; 756 createBuilder(periodicWork.getStringId()).build().launch(); 757 758 // Override is cleared and we're scheduled for now + period 759 WorkSpec afterRunWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 760 assertThat(afterRunWorkSpec.getNextScheduleTimeOverride(), equalTo(Long.MAX_VALUE)); 761 assertThat(afterRunWorkSpec.getPeriodCount(), equalTo(1)); 762 assertThat(afterRunWorkSpec.calculateNextRunTime(), 763 equalTo(actualWorkRunTime + intervalDurationMillis)); 764 } 765 766 @Test 767 @SmallTest testNextScheduleTimeOverride_backsOffNextTimeAfterRetry()768 public void testNextScheduleTimeOverride_backsOffNextTimeAfterRetry() { 769 mTestClock.currentTimeMillis = HOURS.toMillis(5); 770 long lastEnqueueTimeMillis = HOURS.toMillis(4); 771 long intervalDurationMillis = HOURS.toMillis(100); 772 long nextScheduleTimeOverrideMillis = lastEnqueueTimeMillis + HOURS.toMillis(10); 773 long backoffLinearDurationMillis = MINUTES.toMillis(30); 774 775 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 776 // RetryWorker always returns Result.Retry 777 RetryWorker.class, intervalDurationMillis, TimeUnit.MILLISECONDS) 778 .setBackoffCriteria(BackoffPolicy.LINEAR, backoffLinearDurationMillis, 779 TimeUnit.MILLISECONDS) 780 .setNextScheduleTimeOverride(nextScheduleTimeOverrideMillis) 781 .build(); 782 783 periodicWork.getWorkSpec().lastEnqueueTime = lastEnqueueTimeMillis; 784 mTestClock.currentTimeMillis = nextScheduleTimeOverrideMillis; 785 insertWork(periodicWork); 786 787 createBuilder(periodicWork.getStringId()).build().launch(); 788 789 // Override is cleared and we're rescheduled according to the backoff policy 790 WorkSpec afterRunWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 791 assertThat(afterRunWorkSpec.getPeriodCount(), equalTo(0)); 792 assertThat(afterRunWorkSpec.runAttemptCount, equalTo(1)); 793 assertThat(afterRunWorkSpec.state, is(ENQUEUED)); 794 assertThat(afterRunWorkSpec.getNextScheduleTimeOverride(), equalTo(Long.MAX_VALUE)); 795 // Should be scheduled again for now + one backoff. 796 assertThat(afterRunWorkSpec.calculateNextRunTime(), 797 equalTo(nextScheduleTimeOverrideMillis + backoffLinearDurationMillis)); 798 } 799 800 @Test 801 @SmallTest testNextScheduleTimeOverride_whileWorkerIsRunning_appliesToNextRun()802 public void testNextScheduleTimeOverride_whileWorkerIsRunning_appliesToNextRun() 803 throws ExecutionException, InterruptedException { 804 mTestClock.currentTimeMillis = HOURS.toMillis(5); 805 long lastEnqueueTimeMillis = HOURS.toMillis(4); 806 long intervalDurationMillis = HOURS.toMillis(100); 807 808 long firstOverride = lastEnqueueTimeMillis + HOURS.toMillis(10); 809 long secondOverride = firstOverride + HOURS.toMillis(20); 810 long backoffLinearDurationMillis = MINUTES.toMillis(30); 811 812 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 813 LatchWorker.class, intervalDurationMillis, TimeUnit.MILLISECONDS) 814 .setBackoffCriteria(BackoffPolicy.LINEAR, backoffLinearDurationMillis, 815 TimeUnit.MILLISECONDS) 816 .build(); 817 818 periodicWork.getWorkSpec().lastEnqueueTime = lastEnqueueTimeMillis; 819 mTestClock.currentTimeMillis = firstOverride; 820 insertWork(periodicWork); 821 822 LatchWorker latchWorker = getLatchWorker(periodicWork, mExecutorService); 823 FutureListener listener = runWorker(periodicWork, latchWorker); 824 825 // Wait for the worker to start, to verify WorkerWrapper got through its initialization 826 latchWorker.mEntrySignal.await(); 827 828 // Update the work with a new 'next' runtime while it's already running. 829 WorkSpec inflightWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 830 inflightWorkSpec.setNextScheduleTimeOverride(secondOverride); 831 inflightWorkSpec.setNextScheduleTimeOverrideGeneration(3); 832 mDatabase.workSpecDao().delete(inflightWorkSpec.id); 833 mDatabase.workSpecDao().insertWorkSpec(inflightWorkSpec); 834 835 // Finish the Worker so WorkerWrapper can clean up.... 836 latchWorker.mLatch.countDown(); 837 listener.mFuture.get(); 838 839 // We should still be overridden, even though the worker finished after the override 840 WorkSpec afterRunWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 841 assertThat(afterRunWorkSpec.getPeriodCount(), equalTo(1)); 842 assertThat(afterRunWorkSpec.runAttemptCount, equalTo(0)); 843 assertThat(afterRunWorkSpec.state, is(ENQUEUED)); 844 assertThat(afterRunWorkSpec.getNextScheduleTimeOverrideGeneration(), equalTo(3)); 845 assertThat(afterRunWorkSpec.getNextScheduleTimeOverride(), equalTo(secondOverride)); 846 assertThat(afterRunWorkSpec.calculateNextRunTime(), 847 equalTo(secondOverride)); 848 assertBeginEndTraceSpans(periodicWork.getWorkSpec()); 849 } 850 851 @Test 852 @SmallTest testNextScheduleTimeOverride_whileWorkerIsRunning_returnsRetry_usesOverride()853 public void testNextScheduleTimeOverride_whileWorkerIsRunning_returnsRetry_usesOverride() 854 throws ExecutionException, InterruptedException { 855 mTestClock.currentTimeMillis = HOURS.toMillis(5); 856 long lastEnqueueTimeMillis = HOURS.toMillis(4); 857 long intervalDurationMillis = HOURS.toMillis(100); 858 859 long firstOverrideMillis = lastEnqueueTimeMillis + HOURS.toMillis(10); 860 long secondOverrideMillis = firstOverrideMillis + HOURS.toMillis(20); 861 long backoffLinearDurationMillis = MINUTES.toMillis(30); 862 863 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 864 LatchWorker.class, intervalDurationMillis, TimeUnit.MILLISECONDS) 865 .setBackoffCriteria(BackoffPolicy.LINEAR, backoffLinearDurationMillis, 866 TimeUnit.MILLISECONDS) 867 .build(); 868 869 periodicWork.getWorkSpec().lastEnqueueTime = lastEnqueueTimeMillis; 870 mTestClock.currentTimeMillis = firstOverrideMillis; 871 insertWork(periodicWork); 872 873 // Start the worker running 874 LatchWorker latchWorker = getLatchWorker(periodicWork, mExecutorService); 875 latchWorker.returnResult = ListenableWorker.Result.retry(); 876 FutureListener listener = runWorker(periodicWork, latchWorker); 877 878 // Wait for the worker to start, to verify WorkerWrapper got through its initialization 879 latchWorker.mEntrySignal.await(); 880 881 // Update the work with a new 'next' runtime while it's already running. 882 WorkSpec inflightWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 883 inflightWorkSpec.setNextScheduleTimeOverride(secondOverrideMillis); 884 inflightWorkSpec.setNextScheduleTimeOverrideGeneration(3); 885 mDatabase.workSpecDao().delete(inflightWorkSpec.id); 886 mDatabase.workSpecDao().insertWorkSpec(inflightWorkSpec); 887 888 // Allow the worker to finish 889 latchWorker.mLatch.countDown(); 890 listener.mFuture.get(); 891 892 // We should still be overridden, even though the worker finished after the override 893 WorkSpec afterRunWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 894 assertThat(afterRunWorkSpec.getPeriodCount(), equalTo(0)); 895 // We still write runAttemptCount, etc, so if the override is cleared the 'correct' retry 896 // time is applied based on the actual previous run time. 897 assertThat(afterRunWorkSpec.runAttemptCount, equalTo(1)); 898 assertThat(afterRunWorkSpec.state, is(ENQUEUED)); 899 assertThat(afterRunWorkSpec.getNextScheduleTimeOverrideGeneration(), equalTo(3)); 900 assertThat(afterRunWorkSpec.getNextScheduleTimeOverride(), equalTo(secondOverrideMillis)); 901 assertThat(afterRunWorkSpec.calculateNextRunTime(), 902 equalTo(secondOverrideMillis)); 903 assertBeginEndTraceSpans(periodicWork.getWorkSpec()); 904 } 905 906 @Test 907 @SmallTest testClearNextScheduleTimeOverride_whileWorkerIsRunning_schedulesNextBasedOnEnqueue()908 public void testClearNextScheduleTimeOverride_whileWorkerIsRunning_schedulesNextBasedOnEnqueue() 909 throws InterruptedException, ExecutionException { 910 long lastEnqueueTimeMillis = HOURS.toMillis(4); 911 long intervalDurationMillis = HOURS.toMillis(100); 912 long nextScheduleTimeOverrideMillis = lastEnqueueTimeMillis + HOURS.toMillis(10); 913 mTestClock.currentTimeMillis = nextScheduleTimeOverrideMillis; 914 915 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 916 LatchWorker.class, intervalDurationMillis, TimeUnit.MILLISECONDS) 917 .build(); 918 periodicWork.getWorkSpec().lastEnqueueTime = lastEnqueueTimeMillis; 919 insertWork(periodicWork); 920 921 // Start the worker running 922 LatchWorker latchWorker = getLatchWorker(periodicWork, mExecutorService); 923 latchWorker.returnResult = ListenableWorker.Result.success(); 924 FutureListener listener = runWorker(periodicWork, latchWorker); 925 926 // Wait for the worker to start, to verify WorkerWrapper got through its initialization 927 latchWorker.mEntrySignal.await(); 928 929 // Update the override generation, but leave the override time to MAX_LONG. 930 // This replicates calling .override(), then .clearOverride() to return /back/ to normal. 931 WorkSpec inflightWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 932 inflightWorkSpec.setNextScheduleTimeOverride(Long.MAX_VALUE); 933 inflightWorkSpec.setNextScheduleTimeOverrideGeneration(5); 934 mDatabase.workSpecDao().delete(inflightWorkSpec.id); 935 mDatabase.workSpecDao().insertWorkSpec(inflightWorkSpec); 936 937 // Allow the worker to finish 938 latchWorker.mLatch.countDown(); 939 listener.mFuture.get(); 940 941 // We should be scheduled for a "normal" next time, even though overrideGen was changed. 942 WorkSpec afterRunWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 943 assertThat(afterRunWorkSpec.getPeriodCount(), equalTo(1)); 944 assertThat(afterRunWorkSpec.runAttemptCount, equalTo(0)); 945 assertThat(afterRunWorkSpec.state, is(ENQUEUED)); 946 assertThat(afterRunWorkSpec.getNextScheduleTimeOverrideGeneration(), equalTo(5)); 947 assertThat(afterRunWorkSpec.getNextScheduleTimeOverride(), equalTo(Long.MAX_VALUE)); 948 // Normal next period is scheduled. 949 assertThat(afterRunWorkSpec.calculateNextRunTime(), 950 equalTo(mTestClock.currentTimeMillis + intervalDurationMillis)); 951 assertBeginEndTraceSpans(periodicWork.getWorkSpec()); 952 } 953 954 955 @Test 956 @SmallTest testClearNextScheduleTimeOverride_whileWorkerIsRunning_schedulesNextBasedOnBackoff()957 public void testClearNextScheduleTimeOverride_whileWorkerIsRunning_schedulesNextBasedOnBackoff() 958 throws InterruptedException, ExecutionException { 959 long lastEnqueueTimeMillis = HOURS.toMillis(4); 960 long intervalDurationMillis = HOURS.toMillis(10); 961 long backoffLinearDurationMillis = HOURS.toMillis(1); 962 long nextScheduleTimeOverrideMillis = lastEnqueueTimeMillis + HOURS.toMillis(2); 963 mTestClock.currentTimeMillis = nextScheduleTimeOverrideMillis; 964 965 PeriodicWorkRequest periodicWork = new PeriodicWorkRequest.Builder( 966 LatchWorker.class, intervalDurationMillis, TimeUnit.MILLISECONDS) 967 .setBackoffCriteria(BackoffPolicy.LINEAR, backoffLinearDurationMillis, 968 TimeUnit.MILLISECONDS) 969 .build(); 970 periodicWork.getWorkSpec().lastEnqueueTime = lastEnqueueTimeMillis; 971 insertWork(periodicWork); 972 973 // Start the worker running 974 LatchWorker latchWorker = getLatchWorker(periodicWork, mExecutorService); 975 latchWorker.returnResult = ListenableWorker.Result.retry(); 976 FutureListener listener = runWorker(periodicWork, latchWorker); 977 978 // Wait for the worker to start, to verify WorkerWrapper got through its initialization 979 latchWorker.mEntrySignal.await(); 980 981 // Update the override generation, but leave the override time to MAX_LONG. 982 // This replicates calling .override(), then .clearOverride() to return /back/ to normal. 983 WorkSpec inflightWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 984 inflightWorkSpec.setNextScheduleTimeOverride(Long.MAX_VALUE); 985 inflightWorkSpec.setNextScheduleTimeOverrideGeneration(5); 986 mDatabase.workSpecDao().delete(inflightWorkSpec.id); 987 mDatabase.workSpecDao().insertWorkSpec(inflightWorkSpec); 988 989 // Allow the worker to finish 990 latchWorker.mLatch.countDown(); 991 listener.mFuture.get(); 992 993 // We should be scheduled for a normal backoff, even though overrideGen was changed. 994 WorkSpec afterRunWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getStringId()); 995 assertThat(afterRunWorkSpec.getPeriodCount(), equalTo(0)); 996 assertThat(afterRunWorkSpec.runAttemptCount, equalTo(1)); 997 assertThat(afterRunWorkSpec.state, is(ENQUEUED)); 998 assertThat(afterRunWorkSpec.getNextScheduleTimeOverrideGeneration(), equalTo(5)); 999 assertThat(afterRunWorkSpec.getNextScheduleTimeOverride(), equalTo(Long.MAX_VALUE)); 1000 // Backoff timing is respected 1001 assertThat(afterRunWorkSpec.calculateNextRunTime(), 1002 equalTo(mTestClock.currentTimeMillis + backoffLinearDurationMillis)); 1003 assertBeginEndTraceSpans(periodicWork.getWorkSpec()); 1004 } 1005 runWorker(PeriodicWorkRequest periodicWork, Worker worker)1006 private @NonNull FutureListener runWorker(PeriodicWorkRequest periodicWork, Worker worker) { 1007 WorkerWrapper workerWrapper = 1008 createBuilder(periodicWork.getStringId()).withWorker(worker).build(); 1009 FutureListener listener = createAndAddFutureListener(workerWrapper); 1010 return listener; 1011 } 1012 1013 @Test 1014 @SmallTest testFromWorkSpec_hasAppContext()1015 public void testFromWorkSpec_hasAppContext() { 1016 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); 1017 ListenableWorker worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback( 1018 mContext.getApplicationContext(), 1019 TestWorker.class.getName(), 1020 new WorkerParameters( 1021 work.getId(), 1022 Data.EMPTY, 1023 work.getTags(), 1024 new WorkerParameters.RuntimeExtras(), 1025 1, 1026 0, 1027 mSynchronousExecutor, 1028 Dispatchers.getDefault(), 1029 mWorkTaskExecutor, 1030 mConfiguration.getWorkerFactory(), 1031 mMockProgressUpdater, 1032 mMockForegroundUpdater)); 1033 1034 assertThat(worker, is(notNullValue())); 1035 assertThat(worker.getApplicationContext(), is(equalTo(mContext.getApplicationContext()))); 1036 } 1037 1038 @Test 1039 @SmallTest testFromWorkSpec_hasCorrectArguments()1040 public void testFromWorkSpec_hasCorrectArguments() { 1041 String key = "KEY"; 1042 String expectedValue = "VALUE"; 1043 Data input = new Data.Builder().putString(key, expectedValue).build(); 1044 1045 OneTimeWorkRequest work = 1046 new OneTimeWorkRequest.Builder(TestWorker.class).setInputData(input).build(); 1047 ListenableWorker worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback( 1048 mContext.getApplicationContext(), 1049 TestWorker.class.getName(), 1050 new WorkerParameters( 1051 work.getId(), 1052 input, 1053 work.getTags(), 1054 new WorkerParameters.RuntimeExtras(), 1055 1, 1056 0, 1057 mSynchronousExecutor, 1058 Dispatchers.getDefault(), 1059 mWorkTaskExecutor, 1060 mConfiguration.getWorkerFactory(), 1061 mMockProgressUpdater, 1062 mMockForegroundUpdater)); 1063 1064 assertThat(worker, is(notNullValue())); 1065 assertThat(worker.getInputData().getString(key), is(expectedValue)); 1066 1067 work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); 1068 worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback( 1069 mContext.getApplicationContext(), 1070 TestWorker.class.getName(), 1071 new WorkerParameters( 1072 work.getId(), 1073 Data.EMPTY, 1074 work.getTags(), 1075 new WorkerParameters.RuntimeExtras(), 1076 1, 1077 0, 1078 mSynchronousExecutor, 1079 Dispatchers.getDefault(), 1080 mWorkTaskExecutor, 1081 mConfiguration.getWorkerFactory(), 1082 mMockProgressUpdater, 1083 mMockForegroundUpdater)); 1084 1085 assertThat(worker, is(notNullValue())); 1086 assertThat(worker.getInputData().size(), is(0)); 1087 } 1088 1089 @Test 1090 @SmallTest testFromWorkSpec_hasCorrectTags()1091 public void testFromWorkSpec_hasCorrectTags() { 1092 OneTimeWorkRequest work = 1093 new OneTimeWorkRequest.Builder(TestWorker.class) 1094 .addTag("one") 1095 .addTag("two") 1096 .addTag("three") 1097 .build(); 1098 ListenableWorker worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback( 1099 mContext.getApplicationContext(), 1100 TestWorker.class.getName(), 1101 new WorkerParameters( 1102 work.getId(), 1103 Data.EMPTY, 1104 Arrays.asList("one", "two", "three"), 1105 new WorkerParameters.RuntimeExtras(), 1106 1, 1107 0, 1108 mSynchronousExecutor, 1109 Dispatchers.getDefault(), 1110 mWorkTaskExecutor, 1111 mConfiguration.getWorkerFactory(), 1112 mMockProgressUpdater, 1113 mMockForegroundUpdater)); 1114 1115 assertThat(worker, is(notNullValue())); 1116 assertThat(worker.getTags(), containsInAnyOrder("one", "two", "three")); 1117 } 1118 1119 @Test 1120 @SmallTest 1121 @SdkSuppress(minSdkVersion = 24) testFromWorkSpec_hasCorrectRuntimeExtras()1122 public void testFromWorkSpec_hasCorrectRuntimeExtras() { 1123 OneTimeWorkRequest work = 1124 new OneTimeWorkRequest.Builder(TestWorker.class).build(); 1125 WorkerParameters.RuntimeExtras runtimeExtras = new WorkerParameters.RuntimeExtras(); 1126 runtimeExtras.triggeredContentAuthorities = Arrays.asList("tca1", "tca2", "tca3"); 1127 runtimeExtras.triggeredContentUris = Arrays.asList(Uri.parse("tcu1"), Uri.parse("tcu2")); 1128 1129 ListenableWorker worker = mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback( 1130 mContext.getApplicationContext(), 1131 TestWorker.class.getName(), 1132 new WorkerParameters( 1133 work.getId(), 1134 Data.EMPTY, 1135 work.getTags(), 1136 runtimeExtras, 1137 1, 1138 0, 1139 mSynchronousExecutor, 1140 Dispatchers.getDefault(), 1141 mWorkTaskExecutor, 1142 mConfiguration.getWorkerFactory(), 1143 mMockProgressUpdater, 1144 mMockForegroundUpdater)); 1145 1146 assertThat(worker, is(notNullValue())); 1147 assertThat(worker.getTriggeredContentAuthorities(), 1148 containsInAnyOrder(runtimeExtras.triggeredContentAuthorities.toArray())); 1149 assertThat(worker.getTriggeredContentUris(), 1150 containsInAnyOrder(runtimeExtras.triggeredContentUris.toArray())); 1151 } 1152 1153 // getStopReason() requires API level 31, but only because JobScheduler provides them 1154 // since API level 31, but in this isolated test we don't care. 1155 @SuppressLint("NewApi") 1156 @Test 1157 @SmallTest testInterruption_isMarkedOnRunningWorker()1158 public void testInterruption_isMarkedOnRunningWorker() throws InterruptedException { 1159 OneTimeWorkRequest work = 1160 new OneTimeWorkRequest.Builder(InterruptionAwareWorker.class).build(); 1161 insertWork(work); 1162 1163 InterruptionAwareWorker worker = (InterruptionAwareWorker) 1164 mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback( 1165 mContext.getApplicationContext(), 1166 InterruptionAwareWorker.class.getName(), 1167 new WorkerParameters( 1168 work.getId(), 1169 Data.EMPTY, 1170 Collections.<String>emptyList(), 1171 new WorkerParameters.RuntimeExtras(), 1172 1, 1173 0, 1174 mExecutorService, 1175 Dispatchers.getDefault(), 1176 mWorkTaskExecutor, 1177 mConfiguration.getWorkerFactory(), 1178 mMockProgressUpdater, 1179 mMockForegroundUpdater)); 1180 assertThat(worker, is(notNullValue())); 1181 assertThat(worker.isStopped(), is(false)); 1182 1183 WorkerWrapper workerWrapper = 1184 createBuilder(work.getStringId()).withWorker(worker).build(); 1185 workerWrapper.launch(); 1186 worker.doWorkLatch.await(); 1187 workerWrapper.interrupt(STOP_REASON_CONSTRAINT_CHARGING); 1188 assertThat(worker.isStopped(), is(true)); 1189 assertThat(worker.getStopReason(), is(STOP_REASON_CONSTRAINT_CHARGING)); 1190 assertThat(mWorkSpecDao.getState(work.getStringId()), is(ENQUEUED)); 1191 } 1192 1193 @Test 1194 @SmallTest testException_isTreatedAsFailure()1195 public void testException_isTreatedAsFailure() { 1196 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(ExceptionWorker.class).build(); 1197 insertWork(work); 1198 1199 createBuilder(work.getStringId()).build().launch(); 1200 1201 assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED)); 1202 } 1203 1204 @Test 1205 @SmallTest testWorkerThatReturnsNullResult()1206 public void testWorkerThatReturnsNullResult() { 1207 OneTimeWorkRequest work = 1208 new OneTimeWorkRequest.Builder(ReturnNullResultWorker.class).build(); 1209 insertWork(work); 1210 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 1211 FutureListener listener = createAndAddFutureListener(workerWrapper); 1212 workerWrapper.launch(); 1213 assertThat(listener.mResult, is(false)); 1214 assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED)); 1215 } 1216 1217 @Test 1218 @SmallTest testWorkerThatThrowsAnException()1219 public void testWorkerThatThrowsAnException() { 1220 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(ExceptionWorker.class) 1221 .setInputData(new Data.Builder().putString("foo", "bar").build()).build(); 1222 insertWork(work); 1223 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 1224 FutureListener listener = createAndAddFutureListener(workerWrapper); 1225 assertThat(listener.mResult, is(false)); 1226 assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED)); 1227 assertThat(mWorkerExceptionHandler.mWorkerClassName, 1228 is("androidx.work.worker.ExceptionWorker")); 1229 assertThat(mWorkerExceptionHandler.mWorkerParameters.getInputData().getString("foo"), 1230 is("bar")); 1231 assertThat(mWorkerExceptionHandler.mThrowable, instanceOf(IllegalStateException.class)); 1232 assertThat(mWorkerExceptionHandler.mThrowable.getMessage(), is( 1233 "Thrown in doWork Exception")); 1234 } 1235 1236 @Test 1237 @SmallTest testWorkerThatThrowsAnExceptionInConstruction()1238 public void testWorkerThatThrowsAnExceptionInConstruction() { 1239 OneTimeWorkRequest work = 1240 new OneTimeWorkRequest.Builder(ExceptionInConstructionWorker.class) 1241 .setInputData(new Data.Builder().putString("foo", "bar").build()).build(); 1242 insertWork(work); 1243 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 1244 FutureListener listener = createAndAddFutureListener(workerWrapper); 1245 assertThat(listener.mResult, is(false)); 1246 assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED)); 1247 assertThat(mWorkerExceptionHandler.mWorkerClassName, 1248 is("androidx.work.worker.ExceptionInConstructionWorker")); 1249 assertThat(mWorkerExceptionHandler.mWorkerParameters.getInputData().getString("foo"), 1250 is("bar")); 1251 assertThat(mWorkerExceptionHandler.mThrowable, 1252 is(instanceOf(InvocationTargetException.class))); 1253 Throwable e = ((InvocationTargetException) mWorkerExceptionHandler.mThrowable) 1254 .getTargetException(); 1255 assertThat(e, instanceOf(IllegalStateException.class)); 1256 assertThat(e.getMessage(), is("Thrown in constructor Exception")); 1257 } 1258 1259 @Test 1260 @SmallTest testCancellationDoesNotTriggerExceptionHandler()1261 public void testCancellationDoesNotTriggerExceptionHandler() { 1262 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(NeverResolvedWorker.class) 1263 .build(); 1264 insertWork(work); 1265 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 1266 FutureListener listener = createAndAddFutureListener(workerWrapper); 1267 1268 assertThat(mWorkSpecDao.getState(work.getStringId()), is(RUNNING)); 1269 workerWrapper.interrupt(0); 1270 assertThat(listener.mResult, is(true)); 1271 assertThat(mWorkSpecDao.getState(work.getStringId()), is(ENQUEUED)); 1272 assertThat(mWorkerExceptionHandler.mThrowable, nullValue()); 1273 } 1274 1275 @Test 1276 @SmallTest testExceptionInWorkerFactory()1277 public void testExceptionInWorkerFactory() { 1278 OneTimeWorkRequest work = 1279 new OneTimeWorkRequest.Builder(TestWorker.class) 1280 .setInputData(new Data.Builder().putString("foo", "bar").build()).build(); 1281 insertWork(work); 1282 WorkerFactory factory = new WorkerFactory() { 1283 @Override 1284 public @Nullable ListenableWorker createWorker(@NonNull Context appContext, 1285 @NonNull String workerClassName, @NonNull WorkerParameters workerParameters) { 1286 throw new IllegalStateException("Thrown in WorkerFactory Exception"); 1287 } 1288 }; 1289 Configuration configuration = new Configuration.Builder(mConfiguration) 1290 .setWorkerFactory(factory) 1291 .build(); 1292 WorkerWrapper workerWrapper = new WorkerWrapper.Builder( 1293 mContext, 1294 configuration, 1295 mWorkTaskExecutor, 1296 mMockForegroundProcessor, 1297 mDatabase, 1298 mWorkSpecDao.getWorkSpec(work.getStringId()), 1299 mDatabase.workTagDao().getWorkSpecIdsWithTag(work.getStringId()) 1300 ).build(); 1301 FutureListener listener = createAndAddFutureListener(workerWrapper); 1302 assertThat(listener.mResult, is(false)); 1303 assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED)); 1304 assertThat(mWorkerExceptionHandler.mWorkerClassName, 1305 is("androidx.work.worker.TestWorker")); 1306 assertThat(mWorkerExceptionHandler.mWorkerParameters.getInputData().getString("foo"), 1307 is("bar")); 1308 assertThat(mWorkerExceptionHandler.mThrowable, 1309 is(instanceOf(IllegalStateException.class))); 1310 assertThat(mWorkerExceptionHandler.mThrowable.getMessage(), 1311 is("Thrown in WorkerFactory Exception")); 1312 } 1313 1314 @SuppressLint("NewApi") 1315 @Test 1316 @MediumTest 1317 @SdkSuppress(minSdkVersion = 21) testInterruptionsAfterCompletion()1318 public void testInterruptionsAfterCompletion() { 1319 // Suppressing this test prior to API 21, because creating a spy() ends up loading 1320 // android.net.Network class which does not exist before API 21. 1321 1322 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); 1323 insertWork(work); 1324 TrackingWorkerFactory factory = new TrackingWorkerFactory(); 1325 Configuration configuration = new Configuration.Builder(mConfiguration) 1326 .setWorkerFactory(factory) 1327 .build(); 1328 1329 String id = work.getStringId(); 1330 WorkerWrapper workerWrapper = new WorkerWrapper.Builder( 1331 mContext, 1332 configuration, 1333 mWorkTaskExecutor, 1334 mMockForegroundProcessor, 1335 mDatabase, 1336 mWorkSpecDao.getWorkSpec(id), 1337 mDatabase.workTagDao().getTagsForWorkSpecId(id) 1338 ).build(); 1339 1340 FutureListener listener = createAndAddFutureListener(workerWrapper); 1341 assertThat(listener.mResult, is(false)); 1342 assertThat(mWorkSpecDao.getState(id), is(SUCCEEDED)); 1343 workerWrapper.interrupt(0); 1344 ListenableWorker worker = factory.awaitWorker(UUID.fromString(id)); 1345 assertThat(worker.getStopReason(), is(WorkInfo.STOP_REASON_NOT_STOPPED)); 1346 } 1347 1348 @Test 1349 @MediumTest 1350 @SdkSuppress(minSdkVersion = 21) testInterruptionsBeforeCompletion()1351 public void testInterruptionsBeforeCompletion() { 1352 // Suppressing this test prior to API 21, because creating a spy() ends up loading 1353 // android.net.Network class which does not exist before API 21. 1354 1355 OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(TestWorker.class).build(); 1356 insertWork(work); 1357 // Mark scheduled 1358 mWorkSpecDao.markWorkSpecScheduled(work.getStringId(), System.currentTimeMillis()); 1359 1360 WorkerWrapper workerWrapper = new WorkerWrapper.Builder( 1361 mContext, 1362 mConfiguration, 1363 mWorkTaskExecutor, 1364 mMockForegroundProcessor, 1365 mDatabase, 1366 mWorkSpecDao.getWorkSpec(work.getStringId()), 1367 mDatabase.workTagDao().getWorkSpecIdsWithTag(work.getStringId()) 1368 ).build(); 1369 1370 workerWrapper.interrupt(0); 1371 workerWrapper.launch(); 1372 WorkSpec workSpec = mWorkSpecDao.getWorkSpec(work.getStringId()); 1373 assertThat(workSpec.scheduleRequestedAt, is(-1L)); 1374 } 1375 1376 @Test 1377 @SmallTest testWorkRequest_withInvalidClassName()1378 public void testWorkRequest_withInvalidClassName() { 1379 OneTimeWorkRequest work = 1380 new OneTimeWorkRequest.Builder(TestWorker.class).build(); 1381 work.getWorkSpec().workerClassName = "Bad class name"; 1382 insertWork(work); 1383 WorkerWrapper workerWrapper = createBuilder(work.getStringId()).build(); 1384 workerWrapper.launch(); 1385 assertThat(mWorkSpecDao.getState(work.getStringId()), is(FAILED)); 1386 } 1387 1388 @Test 1389 @SmallTest testWorkRequest_truncatedTraceTag()1390 public void testWorkRequest_truncatedTraceTag() { 1391 char[] aLongTag = new char[256]; 1392 Arrays.fill(aLongTag, 'W'); 1393 OneTimeWorkRequest work = 1394 new OneTimeWorkRequest.Builder(TestWorker.class) 1395 .setTraceTag(new String(aLongTag)) 1396 .build(); 1397 assertThat(work.getWorkSpec().getTraceTag().length(), is(127)); 1398 } 1399 createBuilder(String workSpecId)1400 private WorkerWrapper.Builder createBuilder(String workSpecId) { 1401 return new WorkerWrapper.Builder( 1402 mContext, 1403 mConfiguration, 1404 mWorkTaskExecutor, 1405 mMockForegroundProcessor, 1406 mDatabase, 1407 mWorkSpecDao.getWorkSpec(workSpecId), 1408 mDatabase.workTagDao().getWorkSpecIdsWithTag(workSpecId) 1409 ); 1410 } 1411 getLatchWorker(WorkRequest work, ExecutorService executorService)1412 private @Nullable LatchWorker getLatchWorker(WorkRequest work, 1413 ExecutorService executorService) { 1414 return (LatchWorker) mConfiguration.getWorkerFactory().createWorkerWithDefaultFallback( 1415 mContext.getApplicationContext(), 1416 LatchWorker.class.getName(), 1417 new WorkerParameters( 1418 work.getId(), 1419 Data.EMPTY, 1420 work.getTags(), 1421 new WorkerParameters.RuntimeExtras(), 1422 1, 1423 0, 1424 executorService, 1425 Dispatchers.getDefault(), 1426 mWorkTaskExecutor, 1427 mConfiguration.getWorkerFactory(), 1428 mMockProgressUpdater, 1429 mMockForegroundUpdater)); 1430 } 1431 createAndAddFutureListener(WorkerWrapper workerWrapper)1432 private FutureListener createAndAddFutureListener(WorkerWrapper workerWrapper) { 1433 ListenableFuture<Boolean> future = workerWrapper.launch(); 1434 FutureListener listener = new FutureListener(future); 1435 future.addListener(listener, mSynchronousExecutor); 1436 return listener; 1437 } 1438 assertBeginEndTraceSpans(WorkSpec workSpec)1439 private void assertBeginEndTraceSpans(WorkSpec workSpec) { 1440 ArgumentCaptor<String> traceSpan = ArgumentCaptor.forClass(String.class); 1441 ArgumentCaptor<Integer> cookie = ArgumentCaptor.forClass(Integer.class); 1442 verify(mTracer).beginAsyncSection(traceSpan.capture(), cookie.capture()); 1443 assertThat(workSpec.workerClassName, containsString(traceSpan.getValue())); 1444 assertThat(workSpec.hashCode(), is(cookie.getValue())); 1445 verify(mTracer).beginAsyncSection(traceSpan.capture(), cookie.capture()); 1446 assertThat(workSpec.workerClassName, containsString(traceSpan.getValue())); 1447 assertThat(workSpec.hashCode(), is(cookie.getValue())); 1448 } 1449 1450 private static class FutureListener implements Runnable { 1451 1452 ListenableFuture<Boolean> mFuture; 1453 Boolean mResult; 1454 FutureListener(ListenableFuture<Boolean> future)1455 FutureListener(ListenableFuture<Boolean> future) { 1456 mFuture = future; 1457 } 1458 1459 @Override run()1460 public void run() { 1461 try { 1462 mResult = mFuture.get(); 1463 } catch (InterruptedException | ExecutionException e) { 1464 // Do nothing. 1465 } 1466 } 1467 } 1468 1469 private static class TestWorkerExceptionHandler implements Consumer<WorkerExceptionInfo> { 1470 String mWorkerClassName; 1471 WorkerParameters mWorkerParameters; 1472 Throwable mThrowable; 1473 1474 @Override accept(WorkerExceptionInfo params)1475 public void accept(WorkerExceptionInfo params) { 1476 this.mWorkerClassName = params.getWorkerClassName(); 1477 this.mWorkerParameters = params.getWorkerParameters(); 1478 this.mThrowable = params.getThrowable(); 1479 } 1480 } 1481 } 1482