| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.work.impl; |
| |
| import static androidx.work.worker.CheckLimitsWorker.KEY_EXCEEDS_SCHEDULER_LIMIT; |
| import static androidx.work.worker.CheckLimitsWorker.KEY_LIMIT_TO_ENFORCE; |
| |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| |
| import static java.util.concurrent.TimeUnit.SECONDS; |
| |
| import android.arch.core.executor.ArchTaskExecutor; |
| import android.arch.core.executor.TaskExecutor; |
| import android.arch.lifecycle.Observer; |
| import android.content.Context; |
| import android.support.annotation.NonNull; |
| import android.support.annotation.Nullable; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.filters.LargeTest; |
| import android.support.test.filters.SdkSuppress; |
| import android.support.test.runner.AndroidJUnit4; |
| |
| import androidx.work.Configuration; |
| import androidx.work.Data; |
| import androidx.work.OneTimeWorkRequest; |
| import androidx.work.TestLifecycleOwner; |
| import androidx.work.WorkContinuation; |
| import androidx.work.WorkStatus; |
| import androidx.work.impl.utils.taskexecutor.InstantTaskExecutorRule; |
| import androidx.work.worker.CheckLimitsWorker; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.UUID; |
| import java.util.concurrent.BlockingQueue; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.LinkedBlockingQueue; |
| import java.util.concurrent.ThreadPoolExecutor; |
| import java.util.concurrent.TimeUnit; |
| |
| @RunWith(AndroidJUnit4.class) |
| public class WorkManagerImplLargeExecutorTest { |
| |
| private static final int NUM_WORKERS = 500; |
| private static final int TEST_TIMEOUT_SECONDS = 30; |
| |
| // ThreadPoolExecutor parameters. |
| private static final int MIN_POOL_SIZE = 0; |
| // Allocate more threads than the MAX_SCHEDULER_LIMIT |
| private static final int MAX_POOL_SIZE = 150; |
| // Keep alive time for a thread before its claimed. |
| private static final long KEEP_ALIVE_TIME = 2L; |
| |
| private WorkManagerImpl mWorkManagerImpl; |
| private TestLifecycleOwner mLifecycleOwner; |
| |
| @Rule |
| public InstantTaskExecutorRule mRule = new InstantTaskExecutorRule(); |
| |
| @Before |
| public void setUp() { |
| ArchTaskExecutor.getInstance().setDelegate(new TaskExecutor() { |
| @Override |
| public void executeOnDiskIO(@NonNull Runnable runnable) { |
| runnable.run(); |
| } |
| |
| @Override |
| public void postToMainThread(@NonNull Runnable runnable) { |
| runnable.run(); |
| } |
| |
| @Override |
| public boolean isMainThread() { |
| return true; |
| } |
| }); |
| |
| Context context = InstrumentationRegistry.getTargetContext(); |
| BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(); |
| Executor executor = new ThreadPoolExecutor( |
| MIN_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME, SECONDS, queue); |
| Configuration configuration = new Configuration.Builder() |
| .setExecutor(executor) |
| .setMaxSchedulerLimit(50) |
| .build(); |
| mWorkManagerImpl = new WorkManagerImpl(context, configuration, true); |
| mLifecycleOwner = new TestLifecycleOwner(); |
| WorkManagerImpl.setDelegate(mWorkManagerImpl); |
| } |
| |
| @After |
| public void tearDown() { |
| WorkManagerImpl.setDelegate(null); |
| ArchTaskExecutor.getInstance().setDelegate(null); |
| } |
| |
| @Test |
| @LargeTest |
| @SdkSuppress(maxSdkVersion = 22) |
| public void testSchedulerLimits() throws InterruptedException { |
| List<OneTimeWorkRequest> workRequests = new ArrayList<>(NUM_WORKERS); |
| final Set<UUID> completed = new HashSet<>(NUM_WORKERS); |
| final int schedulerLimit = mWorkManagerImpl |
| .getConfiguration() |
| .getMaxSchedulerLimit(); |
| |
| final Data input = new Data.Builder() |
| .putInt(KEY_LIMIT_TO_ENFORCE, schedulerLimit) |
| .build(); |
| |
| for (int i = 0; i < NUM_WORKERS; i++) { |
| OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(CheckLimitsWorker.class) |
| .setInputData(input) |
| .build(); |
| |
| workRequests.add(request); |
| } |
| |
| |
| final CountDownLatch latch = new CountDownLatch(NUM_WORKERS); |
| WorkContinuation continuation = mWorkManagerImpl.beginWith(workRequests); |
| |
| continuation.getStatuses() |
| .observe(mLifecycleOwner, new Observer<List<WorkStatus>>() { |
| @Override |
| public void onChanged(@Nullable List<WorkStatus> workStatuses) { |
| if (workStatuses == null || workStatuses.isEmpty()) { |
| return; |
| } |
| |
| for (WorkStatus workStatus: workStatuses) { |
| if (workStatus.getState().isFinished()) { |
| |
| Data output = workStatus.getOutputData(); |
| |
| boolean exceededLimits = output.getBoolean( |
| KEY_EXCEEDS_SCHEDULER_LIMIT, true); |
| |
| assertThat(exceededLimits, is(false)); |
| if (!completed.contains(workStatus.getId())) { |
| completed.add(workStatus.getId()); |
| latch.countDown(); |
| } |
| } |
| } |
| } |
| }); |
| |
| continuation.enqueue(); |
| latch.await(TEST_TIMEOUT_SECONDS, TimeUnit.SECONDS); |
| assertThat(latch.getCount(), is(0L)); |
| } |
| } |