• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.base.task;
6 
7 import org.junit.After;
8 import org.junit.Assert;
9 import org.junit.Before;
10 import org.junit.Rule;
11 import org.junit.Test;
12 import org.junit.runner.RunWith;
13 import org.robolectric.annotation.Config;
14 
15 import org.chromium.base.test.BaseRobolectricTestRunner;
16 import org.chromium.base.test.util.JniMocker;
17 
18 import java.util.List;
19 import java.util.concurrent.CountDownLatch;
20 import java.util.concurrent.Executor;
21 import java.util.concurrent.ExecutorService;
22 import java.util.concurrent.Executors;
23 import java.util.concurrent.TimeUnit;
24 import java.util.concurrent.atomic.AtomicInteger;
25 
26 /**
27  * Unit tests for {@link SequencedTaskRunnerImpl} that focuses on the transition/migration that
28  * happens as native initializes.
29  */
30 @RunWith(BaseRobolectricTestRunner.class)
31 @Config(manifest = Config.NONE)
32 public class SequencedTaskRunnerTaskMigrationTest {
33     @Rule public JniMocker mMocker = new JniMocker();
34 
35     // It might be tempting to use fake executor similar to Robolectric's scheduler that is driven
36     // from the test's main thread. Unfortunately this approach means that only two states of the
37     // TaskRunner are observable: the posted task resides in the internal queue or the task is
38     // removed from the queue and has its execution completed. The tricky case is the another state:
39     // the task is already removed but is not yet completed. This can only be modelled with real
40     // concurrency.
41     private final ExecutorService mConcurrentExecutor = Executors.newCachedThreadPool();
42 
43     @Before
setUp()44     public void setUp() throws Exception {
45         PostTask.setPrenativeThreadPoolExecutorForTesting(mConcurrentExecutor);
46     }
47 
48     @After
tearDown()49     public void tearDown() throws Exception {
50         PostTask.resetPrenativeThreadPoolExecutorForTesting();
51         // Ensure that no stuck threads left behind.
52         List<Runnable> queuedRunnables = mConcurrentExecutor.shutdownNow();
53         Assert.assertTrue("Some task is stuck in thread pool queue", queuedRunnables.isEmpty());
54         // Termination will be immediate if tests aren't broken. Generous timeout prevents test
55         // from being stuck forever.
56         Assert.assertTrue(
57                 "Some task is stuck in thread pool",
58                 mConcurrentExecutor.awaitTermination(10, TimeUnit.SECONDS));
59     }
60 
61     @Test
nativeRunnerShouldNotExecuteTasksIfJavaThreadIsWorking()62     public void nativeRunnerShouldNotExecuteTasksIfJavaThreadIsWorking() {
63         Executor noopExecutor = runnable -> {};
64         FakeTaskRunnerImplNatives fakeTaskRunnerNatives =
65                 new FakeTaskRunnerImplNatives(noopExecutor);
66         mMocker.mock(TaskRunnerImplJni.TEST_HOOKS, fakeTaskRunnerNatives);
67         BlockingTask preNativeTask = new BlockingTask();
68         SequencedTaskRunnerImpl taskRunner = new SequencedTaskRunnerImpl(TaskTraits.USER_VISIBLE);
69 
70         taskRunner.postTask(preNativeTask);
71         // Dummy task that is planned to be executed on native pool.
72         taskRunner.postTask(() -> {});
73 
74         // Ensure that first task is running on pre-native thread pool: avoid race between
75         // starting the task and requesting native task runner's init.
76         preNativeTask.awaitTaskStarted();
77         taskRunner.initNativeTaskRunner();
78 
79         Assert.assertFalse(
80                 "Native task should not start before java task completion",
81                 fakeTaskRunnerNatives.hasReceivedTasks());
82     }
83 
84     @Test
pendingTasksShouldBeExecutedOnNativeRunnerAfterInit()85     public void pendingTasksShouldBeExecutedOnNativeRunnerAfterInit() {
86         FakeTaskRunnerImplNatives fakeTaskRunnerNatives =
87                 new FakeTaskRunnerImplNatives(mConcurrentExecutor);
88         mMocker.mock(TaskRunnerImplJni.TEST_HOOKS, fakeTaskRunnerNatives);
89         BlockingTask preNativeTask = new BlockingTask();
90         AwaitableTask nativeTask = new AwaitableTask();
91         SequencedTaskRunnerImpl taskRunner = new SequencedTaskRunnerImpl(TaskTraits.USER_VISIBLE);
92 
93         taskRunner.postTask(preNativeTask);
94         taskRunner.postTask(nativeTask);
95 
96         // Ensure that first task is running on pre-native thread pool: avoid race between
97         // starting the task and requesting native task runner's init.
98         preNativeTask.awaitTaskStarted();
99         taskRunner.initNativeTaskRunner();
100         // Allow pre-native task to complete. Second task is going to be run on native pool because
101         // native task runner is available.
102         preNativeTask.allowComplete();
103 
104         // Wait for second task to be started: avoid race between submitting task to the native task
105         // runner and checking the state of the latter in assertion below.
106         nativeTask.awaitTaskStarted();
107 
108         Assert.assertTrue(
109                 "Second task should run on the native pool",
110                 fakeTaskRunnerNatives.hasReceivedTasks());
111     }
112 
113     @Test
taskPostedAfterNativeInitShouldRunInNativePool()114     public void taskPostedAfterNativeInitShouldRunInNativePool() {
115         FakeTaskRunnerImplNatives fakeTaskRunnerNatives =
116                 new FakeTaskRunnerImplNatives(mConcurrentExecutor);
117         mMocker.mock(TaskRunnerImplJni.TEST_HOOKS, fakeTaskRunnerNatives);
118 
119         SequencedTaskRunnerImpl taskRunner = new SequencedTaskRunnerImpl(TaskTraits.USER_VISIBLE);
120         taskRunner.initNativeTaskRunner();
121 
122         AwaitableTask nativeTask = new AwaitableTask();
123         taskRunner.postTask(nativeTask);
124 
125         // Wait for the task to be started: avoid race between submitting task to the native task
126         // runner and checking the state of the latter in assertion below.
127         nativeTask.awaitTaskStarted();
128         Assert.assertTrue(
129                 "Task should run on the native pool", fakeTaskRunnerNatives.hasReceivedTasks());
130     }
131 
awaitNoInterruptedException(CountDownLatch taskLatch)132     private static void awaitNoInterruptedException(CountDownLatch taskLatch) {
133         try {
134             // Generous timeout prevents test from being stuck forever. Actual delay is going to
135             // be a few milliseconds.
136             Assert.assertTrue(
137                     "Timed out waiting for latch to count down",
138                     taskLatch.await(10, TimeUnit.SECONDS));
139         } catch (InterruptedException e) {
140             Thread.currentThread().interrupt();
141         }
142     }
143 
144     private static class AwaitableTask implements Runnable {
145         private final CountDownLatch mTaskStartedLatch = new CountDownLatch(1);
146 
147         @Override
run()148         public void run() {
149             mTaskStartedLatch.countDown();
150         }
151 
awaitTaskStarted()152         public void awaitTaskStarted() {
153             awaitNoInterruptedException(mTaskStartedLatch);
154         }
155     }
156 
157     private static class BlockingTask extends AwaitableTask {
158         private final CountDownLatch mTaskAllowedToComplete = new CountDownLatch(1);
159 
160         @Override
run()161         public void run() {
162             super.run();
163             awaitNoInterruptedException(mTaskAllowedToComplete);
164         }
165 
allowComplete()166         public void allowComplete() {
167             mTaskAllowedToComplete.countDown();
168         }
169     }
170 
171     private static class FakeTaskRunnerImplNatives implements TaskRunnerImpl.Natives {
172         private final AtomicInteger mReceivedTasksCount = new AtomicInteger();
173         private final Executor mExecutor;
174 
FakeTaskRunnerImplNatives(Executor executor)175         public FakeTaskRunnerImplNatives(Executor executor) {
176             mExecutor = executor;
177         }
178 
179         @Override
init(int taskRunnerType, int taskTraits)180         public long init(int taskRunnerType, int taskTraits) {
181             return 1;
182         }
183 
184         @Override
destroy(long nativeTaskRunnerAndroid)185         public void destroy(long nativeTaskRunnerAndroid) {}
186 
187         @Override
postDelayedTask( long nativeTaskRunnerAndroid, Runnable task, long delay, String runnableClassName)188         public void postDelayedTask(
189                 long nativeTaskRunnerAndroid, Runnable task, long delay, String runnableClassName) {
190             mReceivedTasksCount.incrementAndGet();
191             mExecutor.execute(task);
192         }
193 
194         @Override
belongsToCurrentThread(long nativeTaskRunnerAndroid)195         public boolean belongsToCurrentThread(long nativeTaskRunnerAndroid) {
196             return false;
197         }
198 
hasReceivedTasks()199         public boolean hasReceivedTasks() {
200             return mReceivedTasksCount.get() > 0;
201         }
202     }
203 }
204