WorkerWrapperTest.java revision 9f91ee8c71606f36a51177cd0b5c3005834be1ff
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
17package androidx.work.impl;
18
19import static androidx.work.State.BLOCKED;
20import static androidx.work.State.CANCELLED;
21import static androidx.work.State.ENQUEUED;
22import static androidx.work.State.FAILED;
23import static androidx.work.State.RUNNING;
24import static androidx.work.State.SUCCEEDED;
25
26import static org.hamcrest.CoreMatchers.equalTo;
27import static org.hamcrest.CoreMatchers.is;
28import static org.hamcrest.CoreMatchers.notNullValue;
29import static org.hamcrest.MatcherAssert.assertThat;
30import static org.hamcrest.Matchers.contains;
31import static org.hamcrest.Matchers.containsInAnyOrder;
32import static org.hamcrest.Matchers.greaterThan;
33import static org.mockito.Mockito.mock;
34import static org.mockito.Mockito.spy;
35import static org.mockito.Mockito.times;
36import static org.mockito.Mockito.verify;
37
38import android.content.Context;
39import android.support.test.InstrumentationRegistry;
40import android.support.test.filters.LargeTest;
41import android.support.test.filters.SmallTest;
42import android.support.test.runner.AndroidJUnit4;
43
44import androidx.work.Arguments;
45import androidx.work.ArrayCreatingInputMerger;
46import androidx.work.DatabaseTest;
47import androidx.work.PeriodicWork;
48import androidx.work.Work;
49import androidx.work.Worker;
50import androidx.work.impl.model.Dependency;
51import androidx.work.impl.model.DependencyDao;
52import androidx.work.impl.model.WorkSpec;
53import androidx.work.impl.model.WorkSpecDao;
54import androidx.work.impl.utils.taskexecutor.InstantTaskExecutorRule;
55import androidx.work.worker.ChainedArgumentWorker;
56import androidx.work.worker.EchoingWorker;
57import androidx.work.worker.FailureWorker;
58import androidx.work.worker.RetryWorker;
59import androidx.work.worker.SleepTestWorker;
60import androidx.work.worker.TestWorker;
61
62import org.junit.Before;
63import org.junit.Rule;
64import org.junit.Test;
65import org.junit.runner.RunWith;
66import org.mockito.ArgumentCaptor;
67
68import java.util.Arrays;
69import java.util.Collections;
70import java.util.List;
71import java.util.concurrent.Executors;
72import java.util.concurrent.TimeUnit;
73
74@RunWith(AndroidJUnit4.class)
75public class WorkerWrapperTest extends DatabaseTest {
76    private WorkSpecDao mWorkSpecDao;
77    private DependencyDao mDependencyDao;
78    private Context mContext;
79    private ExecutionListener mMockListener;
80    private Scheduler mMockScheduler;
81
82    @Rule
83    public InstantTaskExecutorRule mRule = new InstantTaskExecutorRule();
84
85    @Before
86    public void setUp() {
87        mContext = InstrumentationRegistry.getTargetContext();
88        mWorkSpecDao = spy(mDatabase.workSpecDao());
89        mDependencyDao = mDatabase.dependencyDao();
90        mMockListener = mock(ExecutionListener.class);
91        mMockScheduler = mock(Scheduler.class);
92    }
93
94    @Test
95    @SmallTest
96    public void testSuccess() throws InterruptedException {
97        Work work = new Work.Builder(TestWorker.class).build();
98        insertWork(work);
99        new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
100                .withListener(mMockListener)
101                .build()
102                .run();
103        verify(mMockListener).onExecuted(work.getId(), true, false);
104        assertThat(mWorkSpecDao.getState(work.getId()), is(SUCCEEDED));
105    }
106
107    @Test
108    @SmallTest
109    public void testRunAttemptCountIncremented_successfulExecution() {
110        Work work = new Work.Builder(TestWorker.class).build();
111        insertWork(work);
112        new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
113                .withSchedulers(Collections.singletonList(mMockScheduler))
114                .withListener(mMockListener)
115                .build()
116                .run();
117        WorkSpec latestWorkSpec = mWorkSpecDao.getWorkSpec(work.getId());
118        assertThat(latestWorkSpec.runAttemptCount, is(1));
119    }
120
121    @Test
122    @SmallTest
123    public void testRunAttemptCountIncremented_failedExecution() {
124        Work work = new Work.Builder(FailureWorker.class).build();
125        insertWork(work);
126        new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
127                .withSchedulers(Collections.singletonList(mMockScheduler))
128                .withListener(mMockListener)
129                .build()
130                .run();
131        WorkSpec latestWorkSpec = mWorkSpecDao.getWorkSpec(work.getId());
132        assertThat(latestWorkSpec.runAttemptCount, is(1));
133    }
134
135    @Test
136    @SmallTest
137    public void testPermanentErrorWithInvalidWorkSpecId() throws InterruptedException {
138        final String invalidWorkSpecId = "INVALID_ID";
139        new WorkerWrapper.Builder(mContext, mDatabase, invalidWorkSpecId)
140                .withListener(mMockListener)
141                .build()
142                .run();
143        verify(mMockListener).onExecuted(invalidWorkSpecId, false, false);
144    }
145
146    @Test
147    @SmallTest
148    public void testNotEnqueued() throws InterruptedException {
149        Work work = new Work.Builder(TestWorker.class).withInitialState(RUNNING).build();
150        insertWork(work);
151        new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
152                .withListener(mMockListener)
153                .build()
154                .run();
155        verify(mMockListener).onExecuted(work.getId(), false, true);
156    }
157
158    @Test
159    @SmallTest
160    public void testCancelled() throws InterruptedException {
161        Work work = new Work.Builder(TestWorker.class).withInitialState(CANCELLED).build();
162        insertWork(work);
163        new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
164                .withListener(mMockListener)
165                .build()
166                .run();
167        verify(mMockListener).onExecuted(work.getId(), false, false);
168        assertThat(mWorkSpecDao.getState(work.getId()), is(CANCELLED));
169    }
170
171    @Test
172    @SmallTest
173    public void testPermanentErrorWithInvalidWorkerClass() throws InterruptedException {
174        Work work = new Work.Builder(TestWorker.class).build();
175        getWorkSpec(work).workerClassName = "INVALID_CLASS_NAME";
176        insertWork(work);
177        new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
178                .withListener(mMockListener)
179                .build()
180                .run();
181        verify(mMockListener).onExecuted(work.getId(), false, false);
182        assertThat(mWorkSpecDao.getState(work.getId()), is(FAILED));
183    }
184
185    @Test
186    @SmallTest
187    public void testPermanentErrorWithInvalidInputMergerClass() throws InterruptedException {
188        Work work = new Work.Builder(TestWorker.class).build();
189        getWorkSpec(work).inputMergerClassName = "INVALID_CLASS_NAME";
190        insertWork(work);
191        new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
192                .withSchedulers(Collections.singletonList(mMockScheduler))
193                .withListener(mMockListener)
194                .build()
195                .run();
196        verify(mMockListener).onExecuted(work.getId(), false, false);
197        assertThat(mWorkSpecDao.getState(work.getId()), is(FAILED));
198    }
199
200    @Test
201    @SmallTest
202    public void testFailed() throws InterruptedException {
203        Work work = new Work.Builder(FailureWorker.class).build();
204        insertWork(work);
205        new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
206                .withListener(mMockListener)
207                .build()
208                .run();
209        verify(mMockListener).onExecuted(work.getId(), false, false);
210        assertThat(mWorkSpecDao.getState(work.getId()), is(FAILED));
211    }
212
213    @Test
214    @LargeTest
215    public void testRunning() throws InterruptedException {
216        Work work = new Work.Builder(SleepTestWorker.class).build();
217        insertWork(work);
218        WorkerWrapper wrapper = new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
219                .withListener(mMockListener)
220                .build();
221        Executors.newSingleThreadExecutor().submit(wrapper);
222        Thread.sleep(2000L); // Async wait duration.
223        assertThat(mWorkSpecDao.getState(work.getId()), is(RUNNING));
224        Thread.sleep(SleepTestWorker.SLEEP_DURATION);
225        verify(mMockListener).onExecuted(work.getId(), true, false);
226    }
227
228    @Test
229    @SmallTest
230    public void testDependencies() {
231        Work prerequisiteWork = new Work.Builder(TestWorker.class).build();
232        Work work = new Work.Builder(TestWorker.class)
233                .withInitialState(BLOCKED).build();
234        Dependency dependency = new Dependency(work.getId(), prerequisiteWork.getId());
235
236        mDatabase.beginTransaction();
237        try {
238            insertWork(prerequisiteWork);
239            insertWork(work);
240            mDependencyDao.insertDependency(dependency);
241            mDatabase.setTransactionSuccessful();
242        } finally {
243            mDatabase.endTransaction();
244        }
245
246        assertThat(mWorkSpecDao.getState(prerequisiteWork.getId()), is(ENQUEUED));
247        assertThat(mWorkSpecDao.getState(work.getId()), is(BLOCKED));
248        assertThat(mDependencyDao.hasCompletedAllPrerequisites(work.getId()), is(false));
249
250        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getId())
251                .withListener(mMockListener)
252                .withSchedulers(Collections.singletonList(mMockScheduler))
253                .build()
254                .run();
255
256        assertThat(mWorkSpecDao.getState(prerequisiteWork.getId()), is(SUCCEEDED));
257        assertThat(mWorkSpecDao.getState(work.getId()), is(ENQUEUED));
258        assertThat(mDependencyDao.hasCompletedAllPrerequisites(work.getId()), is(true));
259
260        ArgumentCaptor<WorkSpec> captor = ArgumentCaptor.forClass(WorkSpec.class);
261        verify(mMockScheduler).schedule(captor.capture());
262        assertThat(captor.getValue().id, is(work.getId()));
263    }
264
265    @Test
266    @SmallTest
267    public void testDependencies_passesOutputs() {
268        Work prerequisiteWork = new Work.Builder(ChainedArgumentWorker.class).build();
269        Work work = new Work.Builder(TestWorker.class).withInitialState(BLOCKED).build();
270        Dependency dependency = new Dependency(work.getId(), prerequisiteWork.getId());
271
272        mDatabase.beginTransaction();
273        try {
274            insertWork(prerequisiteWork);
275            insertWork(work);
276            mDependencyDao.insertDependency(dependency);
277            mDatabase.setTransactionSuccessful();
278        } finally {
279            mDatabase.endTransaction();
280        }
281
282        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getId())
283                .withSchedulers(Collections.singletonList(mMockScheduler))
284                .build().run();
285
286        List<Arguments> arguments = mWorkSpecDao.getInputsFromPrerequisites(work.getId());
287        assertThat(arguments.size(), is(1));
288        assertThat(arguments, contains(ChainedArgumentWorker.getChainedArguments()));
289    }
290
291    @Test
292    @SmallTest
293    public void testDependencies_passesMergedOutputs() {
294        String key = "key";
295        String value1 = "value1";
296        String value2 = "value2";
297
298        Work prerequisiteWork1 = new Work.Builder(EchoingWorker.class)
299                .withArguments(new Arguments.Builder().putString(key, value1).build())
300                .build();
301        Work prerequisiteWork2 = new Work.Builder(EchoingWorker.class)
302                .withArguments(new Arguments.Builder().putString(key, value2).build())
303                .build();
304        Work work = new Work.Builder(TestWorker.class)
305                .withInputMerger(ArrayCreatingInputMerger.class)
306                .build();
307        Dependency dependency1 = new Dependency(work.getId(), prerequisiteWork1.getId());
308        Dependency dependency2 = new Dependency(work.getId(), prerequisiteWork2.getId());
309
310        mDatabase.beginTransaction();
311        try {
312            insertWork(prerequisiteWork1);
313            insertWork(prerequisiteWork2);
314            insertWork(work);
315            mDependencyDao.insertDependency(dependency1);
316            mDependencyDao.insertDependency(dependency2);
317            mDatabase.setTransactionSuccessful();
318        } finally {
319            mDatabase.endTransaction();
320        }
321
322        // Run the prerequisites.
323        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork1.getId())
324                .withSchedulers(Collections.singletonList(mMockScheduler))
325                .build().run();
326
327        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork2.getId())
328                .withSchedulers(Collections.singletonList(mMockScheduler))
329                .build().run();
330
331        // Create and run the dependent work.
332        WorkerWrapper workerWrapper = new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
333                .withSchedulers(Collections.singletonList(mMockScheduler))
334                .build();
335        workerWrapper.run();
336
337        Arguments arguments = workerWrapper.mWorker.getArguments();
338        assertThat(arguments.size(), is(1));
339        assertThat(Arrays.asList(arguments.getStringArray(key)),
340                containsInAnyOrder(value1, value2));
341    }
342
343    @Test
344    @SmallTest
345    public void testDependencies_setsPeriodStartTimesForUnblockedWork() {
346        Work prerequisiteWork = new Work.Builder(TestWorker.class).build();
347        Work work = new Work.Builder(TestWorker.class).withInitialState(BLOCKED).build();
348        Dependency dependency = new Dependency(work.getId(), prerequisiteWork.getId());
349
350        mDatabase.beginTransaction();
351        try {
352            insertWork(prerequisiteWork);
353            insertWork(work);
354            mDependencyDao.insertDependency(dependency);
355            mDatabase.setTransactionSuccessful();
356        } finally {
357            mDatabase.endTransaction();
358        }
359
360        long beforeUnblockedTime = System.currentTimeMillis();
361
362        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getId())
363                .withListener(mMockListener)
364                .withSchedulers(Collections.singletonList(mMockScheduler))
365                .build()
366                .run();
367
368        WorkSpec workSpec = mWorkSpecDao.getWorkSpec(work.getId());
369        assertThat(workSpec.periodStartTime, is(greaterThan(beforeUnblockedTime)));
370    }
371
372    @Test
373    @SmallTest
374    public void testDependencies_failsUncancelledDependentsOnFailure() {
375        Work prerequisiteWork = new Work.Builder(FailureWorker.class).build();
376        Work work = new Work.Builder(TestWorker.class).withInitialState(BLOCKED).build();
377        Work cancelledWork = new Work.Builder(TestWorker.class).withInitialState(CANCELLED).build();
378        Dependency dependency1 = new Dependency(work.getId(), prerequisiteWork.getId());
379        Dependency dependency2 = new Dependency(cancelledWork.getId(), prerequisiteWork.getId());
380
381        mDatabase.beginTransaction();
382        try {
383            insertWork(prerequisiteWork);
384            insertWork(work);
385            insertWork(cancelledWork);
386            mDependencyDao.insertDependency(dependency1);
387            mDependencyDao.insertDependency(dependency2);
388            mDatabase.setTransactionSuccessful();
389        } finally {
390            mDatabase.endTransaction();
391        }
392
393        new WorkerWrapper.Builder(mContext, mDatabase, prerequisiteWork.getId()).build().run();
394
395        assertThat(mWorkSpecDao.getState(prerequisiteWork.getId()), is(FAILED));
396        assertThat(mWorkSpecDao.getState(work.getId()), is(FAILED));
397        assertThat(mWorkSpecDao.getState(cancelledWork.getId()), is(CANCELLED));
398    }
399
400    @Test
401    @SmallTest
402    public void testRun_periodicWork_success_updatesPeriodStartTime() {
403        long intervalDuration = PeriodicWork.MIN_PERIODIC_INTERVAL_MILLIS;
404        long periodStartTime = System.currentTimeMillis();
405        long expectedNextPeriodStartTime = periodStartTime + intervalDuration;
406
407        PeriodicWork periodicWork = new PeriodicWork.Builder(
408                TestWorker.class, intervalDuration, TimeUnit.MILLISECONDS).build();
409
410        getWorkSpec(periodicWork).periodStartTime = periodStartTime;
411
412        insertWork(periodicWork);
413
414        new WorkerWrapper.Builder(mContext, mDatabase, periodicWork.getId())
415                .withListener(mMockListener)
416                .build()
417                .run();
418
419        WorkSpec updatedWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getId());
420        assertThat(updatedWorkSpec.periodStartTime, is(expectedNextPeriodStartTime));
421    }
422
423    @Test
424    @SmallTest
425    public void testRun_periodicWork_failure_updatesPeriodStartTime() {
426        long intervalDuration = PeriodicWork.MIN_PERIODIC_INTERVAL_MILLIS;
427        long periodStartTime = System.currentTimeMillis();
428        long expectedNextPeriodStartTime = periodStartTime + intervalDuration;
429
430        PeriodicWork periodicWork = new PeriodicWork.Builder(
431                FailureWorker.class, intervalDuration, TimeUnit.MILLISECONDS).build();
432
433        getWorkSpec(periodicWork).periodStartTime = periodStartTime;
434
435        insertWork(periodicWork);
436
437        new WorkerWrapper.Builder(mContext, mDatabase, periodicWork.getId())
438                .withListener(mMockListener)
439                .build()
440                .run();
441
442        WorkSpec updatedWorkSpec = mWorkSpecDao.getWorkSpec(periodicWork.getId());
443        assertThat(updatedWorkSpec.periodStartTime, is(expectedNextPeriodStartTime));
444    }
445
446    @Test
447    @SmallTest
448    public void testPeriodicWork_success() throws InterruptedException {
449        PeriodicWork periodicWork = new PeriodicWork.Builder(
450                TestWorker.class,
451                PeriodicWork.MIN_PERIODIC_INTERVAL_MILLIS,
452                TimeUnit.MILLISECONDS)
453                .build();
454
455        final String periodicWorkId = periodicWork.getId();
456        insertWork(periodicWork);
457        new WorkerWrapper.Builder(mContext, mDatabase, periodicWorkId)
458                .withListener(mMockListener)
459                .build()
460                .run();
461
462        WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId);
463        verify(mMockListener).onExecuted(periodicWorkId, true, false);
464        assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(0));
465        assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED));
466    }
467
468    @Test
469    @SmallTest
470    public void testPeriodicWork_fail() throws InterruptedException {
471        PeriodicWork periodicWork = new PeriodicWork.Builder(
472                FailureWorker.class,
473                PeriodicWork.MIN_PERIODIC_INTERVAL_MILLIS,
474                TimeUnit.MILLISECONDS)
475                .build();
476
477        final String periodicWorkId = periodicWork.getId();
478        insertWork(periodicWork);
479        new WorkerWrapper.Builder(mContext, mDatabase, periodicWorkId)
480                .withListener(mMockListener)
481                .build()
482                .run();
483
484        WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId);
485        verify(mMockListener).onExecuted(periodicWorkId, false, false);
486        assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(0));
487        assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED));
488    }
489
490    @Test
491    @SmallTest
492    public void testPeriodicWork_retry() throws InterruptedException {
493        PeriodicWork periodicWork = new PeriodicWork.Builder(
494                RetryWorker.class,
495                PeriodicWork.MIN_PERIODIC_INTERVAL_MILLIS,
496                TimeUnit.MILLISECONDS)
497                .build();
498
499        final String periodicWorkId = periodicWork.getId();
500        insertWork(periodicWork);
501        new WorkerWrapper.Builder(mContext, mDatabase, periodicWorkId)
502                .withListener(mMockListener)
503                .build()
504                .run();
505
506        WorkSpec periodicWorkSpecAfterFirstRun = mWorkSpecDao.getWorkSpec(periodicWorkId);
507        verify(mMockListener).onExecuted(periodicWorkId, false, true);
508        assertThat(periodicWorkSpecAfterFirstRun.runAttemptCount, is(1));
509        assertThat(periodicWorkSpecAfterFirstRun.state, is(ENQUEUED));
510    }
511
512    @Test
513    @SmallTest
514    public void testScheduler() throws InterruptedException {
515        Work work = new Work.Builder(TestWorker.class).build();
516        insertWork(work);
517        Scheduler mockScheduler = mock(Scheduler.class);
518
519        new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
520                .withSchedulers(Collections.singletonList(mockScheduler))
521                .build()
522                .run();
523
524        verify(mockScheduler).schedule();
525    }
526
527    @Test
528    @SmallTest
529    public void testFromWorkSpec_hasAppContext() throws InterruptedException {
530        Work work = new Work.Builder(TestWorker.class).build();
531        Worker worker =
532                WorkerWrapper.workerFromWorkSpec(mContext, getWorkSpec(work), Arguments.EMPTY);
533
534        assertThat(worker, is(notNullValue()));
535        assertThat(worker.getAppContext(), is(equalTo(mContext.getApplicationContext())));
536    }
537
538    @Test
539    @SmallTest
540    public void testFromWorkSpec_hasCorrectArguments() throws InterruptedException {
541        String key = "KEY";
542        String expectedValue = "VALUE";
543        Arguments arguments = new Arguments.Builder().putString(key, expectedValue).build();
544
545        Work work = new Work.Builder(TestWorker.class).withArguments(arguments).build();
546        Worker worker = WorkerWrapper.workerFromWorkSpec(mContext, getWorkSpec(work), arguments);
547
548        assertThat(worker, is(notNullValue()));
549        assertThat(worker.getArguments().getString(key, null), is(expectedValue));
550
551        work = new Work.Builder(TestWorker.class).build();
552        worker = WorkerWrapper.workerFromWorkSpec(mContext, getWorkSpec(work), Arguments.EMPTY);
553
554        assertThat(worker, is(notNullValue()));
555        assertThat(worker.getArguments().size(), is(0));
556    }
557
558    @Test
559    @SmallTest
560    public void testSuccess_withPendingScheduledWork() {
561        Work work = new Work.Builder(TestWorker.class).build();
562        insertWork(work);
563
564        Work unscheduled = new Work.Builder(TestWorker.class).build();
565        insertWork(unscheduled);
566
567        new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
568                .withSchedulers(Collections.singletonList(mMockScheduler))
569                .withListener(mMockListener)
570                .build()
571                .run();
572
573        verify(mMockScheduler, times(1)).schedule(unscheduled.getWorkSpec());
574        verify(mMockListener).onExecuted(work.getId(), true, false);
575        assertThat(mWorkSpecDao.getState(work.getId()), is(SUCCEEDED));
576    }
577
578    @Test
579    @SmallTest
580    public void testFailure_withPendingScheduledWork() {
581        Work work = new Work.Builder(FailureWorker.class).build();
582        insertWork(work);
583
584        Work unscheduled = new Work.Builder(TestWorker.class).build();
585        insertWork(unscheduled);
586
587        new WorkerWrapper.Builder(mContext, mDatabase, work.getId())
588                .withSchedulers(Collections.singletonList(mMockScheduler))
589                .withListener(mMockListener)
590                .build()
591                .run();
592
593        verify(mMockScheduler, times(1)).schedule(unscheduled.getWorkSpec());
594        verify(mMockListener).onExecuted(work.getId(), false, false);
595        assertThat(mWorkSpecDao.getState(work.getId()), is(FAILED));
596    }
597}
598