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