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