1 /*
2  * Copyright 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.work.testing
18 
19 import android.content.Context
20 import android.os.Handler
21 import android.os.Looper
22 import androidx.test.core.app.ApplicationProvider
23 import androidx.test.ext.junit.runners.AndroidJUnit4
24 import androidx.test.filters.MediumTest
25 import androidx.work.Constraints
26 import androidx.work.ExistingWorkPolicy.REPLACE
27 import androidx.work.NetworkType
28 import androidx.work.OneTimeWorkRequest
29 import androidx.work.OneTimeWorkRequestBuilder
30 import androidx.work.PeriodicWorkRequestBuilder
31 import androidx.work.WorkInfo
32 import androidx.work.WorkInfo.State.ENQUEUED
33 import androidx.work.impl.WorkManagerImpl
34 import androidx.work.impl.model.WorkSpec
35 import androidx.work.impl.utils.taskexecutor.SerialExecutor
36 import androidx.work.testing.WorkManagerTestInitHelper.ExecutorsMode.PRESERVE_EXECUTORS
37 import androidx.work.testing.workers.CountingTestWorker
38 import androidx.work.testing.workers.RetryWorker
39 import androidx.work.testing.workers.TestWorker
40 import com.google.common.truth.Truth.assertThat
41 import java.util.UUID
42 import java.util.concurrent.CountDownLatch
43 import java.util.concurrent.TimeUnit
44 import org.junit.Test
45 import org.junit.runner.RunWith
46 
47 @RunWith(AndroidJUnit4::class)
48 @MediumTest
49 class TestSchedulerRealExecutorTest {
50     val context = ApplicationProvider.getApplicationContext<Context>()
51 
52     init {
53         WorkManagerTestInitHelper.initializeTestWorkManager(context, PRESERVE_EXECUTORS)
54         CountingTestWorker.COUNT.set(0)
55     }
56 
57     val wm = WorkManagerImpl.getInstance(context)
58     val handler = Handler(Looper.getMainLooper())
59     val driver = WorkManagerTestInitHelper.getTestDriver(context)!!
60 
61     @Test
testWorker_withDependentWork_shouldSucceedSynchronouslynull62     fun testWorker_withDependentWork_shouldSucceedSynchronously() {
63         val request = OneTimeWorkRequest.from(TestWorker::class.java)
64         val dependentRequest = OneTimeWorkRequest.from(TestWorker::class.java)
65         wm.beginWith(request).then(dependentRequest).enqueue()
66         awaitSuccess(dependentRequest.id)
67     }
68 
69     @Test
testWorker_withConstraints_shouldSucceedAfterSetConstraintsnull70     fun testWorker_withConstraints_shouldSucceedAfterSetConstraints() {
71         val request =
72             OneTimeWorkRequestBuilder<TestWorker>()
73                 .setConstraints(Constraints(requiredNetworkType = NetworkType.UNMETERED))
74                 .build()
75         wm.enqueue(request).result.get()
76         driver.setAllConstraintsMet(request.id)
77         awaitSuccess(request.id)
78     }
79 
80     @Test
testWorker_withPeriodDelay_shouldRunAfterEachSetPeriodDelaynull81     fun testWorker_withPeriodDelay_shouldRunAfterEachSetPeriodDelay() {
82         val request = PeriodicWorkRequestBuilder<CountingTestWorker>(10, TimeUnit.DAYS).build()
83         wm.enqueue(request).result.get()
84         val periodicLatch = CountDownLatch(1)
85         // TODO: specifically removing deduplication for periodic workers
86         // so runs aren't dedupped. We need periodicity data in workinfo
87         val workInfo =
88             wm.workDatabase.workSpecDao().getWorkStatusPojoLiveDataForIds(listOf("${request.id}"))
89         var expectedCounter = 1
90         val maxCount = 5
91         handler.post {
92             workInfo.observeForever {
93                 val info = it.first()
94                 val isEnqueued = info.state == ENQUEUED
95                 val counter = CountingTestWorker.COUNT.get()
96                 if (isEnqueued && counter == maxCount) {
97                     periodicLatch.countDown()
98                 } else if (isEnqueued && counter == expectedCounter) {
99                     expectedCounter++
100                     driver.setPeriodDelayMet(request.id)
101                 }
102             }
103         }
104         assertThat(periodicLatch.await(10, TimeUnit.SECONDS)).isTrue()
105     }
106 
107     @Test
testWorker_withPeriodicWorkerWithInitialDelay_shouldRunnull108     fun testWorker_withPeriodicWorkerWithInitialDelay_shouldRun() {
109         val request =
110             PeriodicWorkRequestBuilder<CountingTestWorker>(10, TimeUnit.DAYS)
111                 .setInitialDelay(10, TimeUnit.DAYS)
112                 .build()
113         wm.enqueue(request).result.get()
114         driver.setInitialDelayMet(request.id)
115         awaitPeriodicRunOnce(request.id)
116         driver.setPeriodDelayMet(request.id)
117         awaitCondition(request.id) {
118             it.state == WorkInfo.State.ENQUEUED && CountingTestWorker.COUNT.get() == 2
119         }
120     }
121 
122     @Test
testWorker_withPeriodicWorkerFlex_shouldRunnull123     fun testWorker_withPeriodicWorkerFlex_shouldRun() {
124         val request =
125             PeriodicWorkRequestBuilder<CountingTestWorker>(10, TimeUnit.DAYS, 1, TimeUnit.DAYS)
126                 .build()
127         wm.enqueue(request).result.get()
128         awaitPeriodicRunOnce(request.id)
129     }
130 
131     @Test
testWorker_afterSuccessfulRun_postConditionsnull132     fun testWorker_afterSuccessfulRun_postConditions() {
133         val request = OneTimeWorkRequest.from(TestWorker::class.java)
134         wm.enqueue(request).result.get()
135         awaitSuccess(request.id)
136         driver.setAllConstraintsMet(request.id)
137         driver.setInitialDelayMet(request.id)
138     }
139 
140     @Test
testWorkerUniquenull141     fun testWorkerUnique() {
142         val request1 =
143             OneTimeWorkRequestBuilder<TestWorker>().setInitialDelay(1, TimeUnit.DAYS).build()
144         wm.enqueueUniqueWork("name", REPLACE, request1).result.get()
145         val request2 =
146             OneTimeWorkRequestBuilder<TestWorker>().setInitialDelay(1, TimeUnit.DAYS).build()
147         wm.enqueueUniqueWork("name", REPLACE, request2).result.get()
148 
149         try {
150             driver.setInitialDelayMet(request1.id)
151             throw AssertionError()
152         } catch (e: IllegalArgumentException) {
153             // expected
154         }
155         driver.setInitialDelayMet(request2.id)
156         awaitSuccess(request2.id)
157     }
158 
159     @Test
testWorker_afterSuccessfulRun_throwsExceptionWhenSetPeriodDelayMetnull160     fun testWorker_afterSuccessfulRun_throwsExceptionWhenSetPeriodDelayMet() {
161         val request = OneTimeWorkRequest.from(TestWorker::class.java)
162         wm.enqueue(request).result.get()
163         awaitSuccess(request.id)
164         try {
165             driver.setPeriodDelayMet(request.id)
166             throw AssertionError()
167         } catch (e: IllegalArgumentException) {
168             // expected
169         }
170     }
171 
172     @Test
testSetAllConstraintsDontTriggerSecondRunnull173     fun testSetAllConstraintsDontTriggerSecondRun() {
174         val request =
175             PeriodicWorkRequestBuilder<CountingTestWorker>(10, TimeUnit.DAYS)
176                 .setConstraints(Constraints(requiresCharging = true))
177                 .build()
178         wm.enqueue(request).result.get()
179         val latch = CountDownLatch(1)
180         wm.workTaskExecutor.serialTaskExecutor.execute { latch.await() }
181         // double call to setAllConstraint. It shouldn't lead to double execution of worker.
182         // It should run once, but second time it shouldn't run because "setPeriodDelayMet" wasn't
183         // called.
184         driver.setAllConstraintsMet(request.id)
185         driver.setAllConstraintsMet(request.id)
186         latch.countDown()
187         awaitCondition(request.id) { it.state == ENQUEUED && CountingTestWorker.COUNT.get() > 0 }
188 
189         drainSerialExecutor()
190         assertThat(wm.getWorkInfoById(request.id).get()!!.state).isEqualTo(ENQUEUED)
191         assertThat(CountingTestWorker.COUNT.get()).isEqualTo(1)
192     }
193 
194     @Test
testOneTimeWorkerRetrynull195     fun testOneTimeWorkerRetry() {
196         val request = OneTimeWorkRequest.from(RetryWorker::class.java)
197         wm.enqueue(request).result.get()
198         awaitReenqueuedAfterRetry(request.id)
199     }
200 
201     @Test
testPeriodicWorkerRetrynull202     fun testPeriodicWorkerRetry() {
203         val request = PeriodicWorkRequestBuilder<RetryWorker>(1, TimeUnit.DAYS).build()
204         wm.enqueue(request).result.get()
205         awaitReenqueuedAfterRetry(request.id)
206     }
207 
<lambda>null208     private fun awaitSuccess(id: UUID) = awaitCondition(id) { it.state == WorkInfo.State.SUCCEEDED }
209 
awaitPeriodicRunOncenull210     private fun awaitPeriodicRunOnce(id: UUID) =
211         awaitCondition(id) { it.state == ENQUEUED && CountingTestWorker.COUNT.get() == 1 }
212 
awaitReenqueuedAfterRetrynull213     private fun awaitReenqueuedAfterRetry(id: UUID) =
214         awaitCondition(id) { it.state == ENQUEUED && it.runAttemptCount == 1 }
215 
awaitConditionnull216     private fun awaitCondition(id: UUID, predicate: (WorkSpec.WorkInfoPojo) -> Boolean) {
217         val latch = CountDownLatch(1)
218         // TODO: specifically removing deduplication for periodic workers
219         // so runs aren't dedupped. We need periodicity data in workinfo
220         val workInfo = wm.workDatabase.workSpecDao().getWorkStatusPojoLiveDataForIds(listOf("$id"))
221 
222         handler.post {
223             workInfo.observeForever {
224                 if (predicate(it.first())) {
225                     latch.countDown()
226                 }
227             }
228         }
229         assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue()
230     }
231 
drainSerialExecutornull232     private fun drainSerialExecutor() {
233         val latch = CountDownLatch(1)
234 
235         class DrainTask(val executor: SerialExecutor) : Runnable {
236             override fun run() {
237                 if (executor.hasPendingTasks()) {
238                     executor.execute(this)
239                 } else {
240                     latch.countDown()
241                 }
242             }
243         }
244 
245         val executor = wm.workTaskExecutor.serialTaskExecutor
246         executor.execute(DrainTask(executor))
247         latch.await()
248     }
249 }
250