• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 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 static org.junit.Assert.assertEquals;
8 import static org.junit.Assert.assertFalse;
9 import static org.junit.Assert.assertNull;
10 import static org.junit.Assert.assertTrue;
11 
12 import androidx.test.filters.SmallTest;
13 
14 import org.junit.After;
15 import org.junit.Assert;
16 import org.junit.Before;
17 import org.junit.Rule;
18 import org.junit.Test;
19 import org.junit.rules.ExpectedException;
20 import org.junit.runner.RunWith;
21 import org.robolectric.Robolectric;
22 import org.robolectric.android.util.concurrent.RoboExecutorService;
23 import org.robolectric.annotation.Config;
24 import org.robolectric.annotation.LooperMode;
25 import org.robolectric.shadows.ShadowLog;
26 import org.robolectric.util.Scheduler;
27 
28 import org.chromium.base.Log;
29 import org.chromium.base.task.AsyncTask.Status;
30 import org.chromium.base.test.BaseRobolectricTestRunner;
31 
32 import java.util.concurrent.CancellationException;
33 import java.util.concurrent.LinkedBlockingQueue;
34 import java.util.concurrent.TimeUnit;
35 
36 /** Tests for {@link AsyncTask}. */
37 @RunWith(BaseRobolectricTestRunner.class)
38 @Config(manifest = Config.NONE)
39 @LooperMode(LooperMode.Mode.LEGACY)
40 public class AsyncTaskThreadTest {
41     private static final String TAG = "AsyncTaskThreadTest";
42     private static final boolean DEBUG = false;
43 
44     private static class BlockAndGetFeedDataTask extends AsyncTask<Boolean> {
45         private final LinkedBlockingQueue<Boolean> mIncomingQueue =
46                 new LinkedBlockingQueue<Boolean>();
47         private final LinkedBlockingQueue<Boolean> mOutgoingQueue =
48                 new LinkedBlockingQueue<Boolean>();
49         private final LinkedBlockingQueue<Boolean> mInterruptedExceptionQueue =
50                 new LinkedBlockingQueue<Boolean>();
51         private Boolean mPostExecuteResult;
52 
53         @Override
doInBackground()54         protected Boolean doInBackground() {
55             if (DEBUG) Log.i(TAG, "doInBackground");
56             mOutgoingQueue.add(true);
57             return blockAndGetFeedData();
58         }
59 
60         @Override
onPostExecute(Boolean result)61         protected void onPostExecute(Boolean result) {
62             if (DEBUG) Log.i(TAG, "onPostExecute: " + result);
63             mPostExecuteResult = result;
64         }
65 
feedData(Boolean data)66         public void feedData(Boolean data) {
67             mIncomingQueue.add(data);
68         }
69 
blockAndGetFeedData()70         private Boolean blockAndGetFeedData() {
71             try {
72                 return mIncomingQueue.poll(3, TimeUnit.SECONDS);
73             } catch (InterruptedException e) {
74                 if (DEBUG) Log.i(TAG, "InterruptedException");
75                 mInterruptedExceptionQueue.add(true);
76                 return false;
77             }
78         }
79 
blockUntilDoInBackgroundStarts()80         public void blockUntilDoInBackgroundStarts() throws Exception {
81             mOutgoingQueue.poll(3, TimeUnit.SECONDS);
82         }
83 
getPostExecuteResult()84         public Boolean getPostExecuteResult() {
85             return mPostExecuteResult;
86         }
87 
getInterruptedExceptionQueue()88         public LinkedBlockingQueue<Boolean> getInterruptedExceptionQueue() {
89             return mInterruptedExceptionQueue;
90         }
91     }
92 
93     private final BlockAndGetFeedDataTask mTask = new BlockAndGetFeedDataTask();
94     private final RoboExecutorService mRoboExecutorService = new RoboExecutorService();
95     private final Scheduler mBackgroundScheduler = Robolectric.getBackgroundThreadScheduler();
96 
97     @Rule public ExpectedException thrown = ExpectedException.none();
98 
AsyncTaskThreadTest()99     public AsyncTaskThreadTest() {
100         if (DEBUG) ShadowLog.stream = System.out;
101     }
102 
103     @Before
setUp()104     public void setUp() {
105         mBackgroundScheduler.pause();
106         assertEquals(Status.PENDING, mTask.getStatus());
107     }
108 
109     @After
tearDown()110     public void tearDown() {
111         // No unexpected interrupted exception.
112         assertNull(mTask.getInterruptedExceptionQueue().poll());
113         Assert.assertTrue(mRoboExecutorService.shutdownNow().isEmpty());
114     }
115 
116     @Test
117     @SmallTest
testCancel_ReturnsFalseOnceTaskFinishes()118     public void testCancel_ReturnsFalseOnceTaskFinishes() throws Exception {
119         // This test requires robo executor service such that we can run
120         // one background task.
121         mTask.executeOnExecutor(mRoboExecutorService);
122 
123         // Ensure that the background thread is not blocked.
124         mTask.feedData(true);
125 
126         mBackgroundScheduler.runOneTask();
127 
128         // Cannot cancel. The task is already run.
129         assertFalse(mTask.cancel(/* mayInterruptIfRunning= */ false));
130         assertTrue(mTask.get());
131         assertEquals(Boolean.TRUE, mTask.getPostExecuteResult());
132 
133         // Note: This is somewhat counter-intuitive since cancel() failed.
134         assertTrue(mTask.isCancelled());
135         assertEquals(Status.FINISHED, mTask.getStatus());
136     }
137 
138     @Test
139     @SmallTest
testCancel_InPreExecute()140     public void testCancel_InPreExecute() throws Exception {
141         // Note that background loop is paused.
142         mTask.executeOnExecutor(mRoboExecutorService);
143 
144         // Ensure that the background thread is not blocked.
145         mTask.feedData(true);
146 
147         // cancel() can still return true
148         assertTrue(mTask.cancel(false));
149 
150         mBackgroundScheduler.runOneTask();
151 
152         try {
153             assertTrue(mTask.get());
154             Assert.fail();
155         } catch (CancellationException e) {
156             // expected
157         }
158 
159         assertTrue(mTask.isCancelled());
160         assertEquals(Status.FINISHED, mTask.getStatus());
161     }
162 
163     @Test
164     @SmallTest
testCancel_CanReturnTrueEvenAfterTaskStarts()165     public void testCancel_CanReturnTrueEvenAfterTaskStarts() throws Exception {
166         mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
167 
168         // Wait until the task is started. Note that data is not yet fed.
169         mTask.blockUntilDoInBackgroundStarts();
170         assertEquals(Status.RUNNING, mTask.getStatus());
171 
172         // This reflects FutureTask#cancel() behavior. Note that the task is
173         // started but cancel can still return true.
174         assertTrue(mTask.cancel(/* mayInterruptIfRunning= */ false));
175 
176         // Continue the task.
177         mTask.feedData(true);
178 
179         // get() will raise an exception although the task is started.
180         try {
181             mTask.get();
182             Assert.fail();
183         } catch (CancellationException e) {
184             // expected
185         }
186         assertNull(mTask.getPostExecuteResult()); // onPostExecute did not run.
187 
188         assertTrue(mTask.isCancelled());
189         assertEquals(Status.RUNNING, mTask.getStatus());
190     }
191 
192     @Test
193     @SmallTest
testCancel_MayInterrupt_ReturnsFalseOnceTaskFinishes()194     public void testCancel_MayInterrupt_ReturnsFalseOnceTaskFinishes() throws Exception {
195         // This test requires robo executor service such that we can run
196         // one background task.
197         mTask.executeOnExecutor(mRoboExecutorService);
198 
199         // Ensure that the background thread is not blocked.
200         mTask.feedData(true);
201 
202         mBackgroundScheduler.runOneTask();
203 
204         // Cannot cancel. The task is already run.
205         assertFalse(mTask.cancel(/* mayInterruptIfRunning= */ true));
206         assertTrue(mTask.get());
207         assertEquals(Boolean.TRUE, mTask.getPostExecuteResult());
208 
209         // Note: This is somewhat counter-intuitive since cancel() failed.
210         assertTrue(mTask.isCancelled());
211 
212         assertEquals(Status.FINISHED, mTask.getStatus());
213     }
214 
215     @Test
216     @SmallTest
testCancel_MayInterrupt_TaskIsInterrupted()217     public void testCancel_MayInterrupt_TaskIsInterrupted() throws Exception {
218         mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
219 
220         // Wait until the task is started. Note that data is not yet fed.
221         mTask.blockUntilDoInBackgroundStarts();
222 
223         assertEquals(Status.RUNNING, mTask.getStatus());
224 
225         // Cancel and interrupt the current task.
226         assertTrue(mTask.cancel(/* mayInterruptIfRunning= */ true));
227 
228         // Do not feed data here because task may finish before it gets interrupted.
229 
230         // get() will raise an exception although the task is started.
231         try {
232             mTask.get();
233             Assert.fail();
234         } catch (CancellationException e) {
235             // expected
236         }
237         assertNull(mTask.getPostExecuteResult()); // onPostExecute did not run.
238         // Task was interrupted.
239         // Note: interruption is raised and handled in the background thread, so we need to
240         // wait here.
241         assertEquals(Boolean.TRUE, mTask.getInterruptedExceptionQueue().poll(3, TimeUnit.SECONDS));
242 
243         assertTrue(mTask.isCancelled());
244         assertEquals(Status.RUNNING, mTask.getStatus());
245     }
246 
247     @Test
248     @SmallTest
testExecuteTwiceRaisesException()249     public void testExecuteTwiceRaisesException() throws Exception {
250         mTask.executeOnExecutor(mRoboExecutorService);
251         // Note that background loop is paused.
252         try {
253             // A second run should cause an exception.
254             mTask.executeOnExecutor(mRoboExecutorService);
255             Assert.fail();
256         } catch (IllegalStateException e) {
257             // expected
258         }
259         mBackgroundScheduler.runOneTask(); // ensure to pass tearDown check
260     }
261 }
262