1 /*
2  * Copyright (C) 2011 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 com.android.dialer.util;
18 
19 import android.app.Instrumentation;
20 import android.os.AsyncTask;
21 
22 import com.google.common.collect.Lists;
23 
24 import junit.framework.Assert;
25 
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.concurrent.CountDownLatch;
29 import java.util.concurrent.Executor;
30 import java.util.concurrent.TimeUnit;
31 
32 import javax.annotation.concurrent.GuardedBy;
33 import javax.annotation.concurrent.ThreadSafe;
34 
35 /**
36  * Test implementation of AsyncTaskExecutor.
37  * <p>
38  * This class is thread-safe. As per the contract of the AsyncTaskExecutor, the submit methods must
39  * be called from the main ui thread, however the other public methods may be called from any thread
40  * (most commonly the test thread).
41  * <p>
42  * Tasks submitted to this executor will not be run immediately. Rather they will be stored in a
43  * list of submitted tasks, where they can be examined. They can also be run on-demand using the run
44  * methods, so that different ordering of AsyncTask execution can be simulated.
45  * <p>
46  * The onPreExecute method of the submitted AsyncTask will be called synchronously during the
47  * call to {@link #submit(Object, AsyncTask, Object...)}.
48  */
49 @ThreadSafe
50 public class FakeAsyncTaskExecutor implements AsyncTaskExecutor {
51     private static final long DEFAULT_TIMEOUT_MS = 10000;
52 
53     /** The maximum length of time in ms to wait for tasks to execute during tests. */
54     private final long mTimeoutMs = DEFAULT_TIMEOUT_MS;
55 
56     private final Object mLock = new Object();
57     @GuardedBy("mLock") private final List<SubmittedTask> mSubmittedTasks = Lists.newArrayList();
58 
59     private final DelayedExecutor mBlockingExecutor = new DelayedExecutor();
60     private final Instrumentation mInstrumentation;
61 
62     /** Create a fake AsyncTaskExecutor for use in unit tests. */
FakeAsyncTaskExecutor(Instrumentation instrumentation)63     public FakeAsyncTaskExecutor(Instrumentation instrumentation) {
64         Assert.assertNotNull(instrumentation);
65         mInstrumentation = instrumentation;
66     }
67 
68     /** Encapsulates an async task with the params and identifier it was submitted with. */
69     public interface SubmittedTask {
getRunnable()70         Runnable getRunnable();
getIdentifier()71         Object getIdentifier();
getAsyncTask()72         AsyncTask<?, ?, ?> getAsyncTask();
73     }
74 
75     private static final class SubmittedTaskImpl implements SubmittedTask {
76         private final Object mIdentifier;
77         private final Runnable mRunnable;
78         private final AsyncTask<?, ?, ?> mAsyncTask;
79 
SubmittedTaskImpl(Object identifier, Runnable runnable, AsyncTask<?, ?, ?> asyncTask)80         public SubmittedTaskImpl(Object identifier, Runnable runnable,
81                 AsyncTask<?, ?, ?> asyncTask) {
82             mIdentifier = identifier;
83             mRunnable = runnable;
84             mAsyncTask = asyncTask;
85         }
86 
87         @Override
getIdentifier()88         public Object getIdentifier() {
89             return mIdentifier;
90         }
91 
92         @Override
getRunnable()93         public Runnable getRunnable() {
94             return mRunnable;
95         }
96 
97         @Override
getAsyncTask()98         public AsyncTask<?, ?, ?> getAsyncTask() {
99             return mAsyncTask;
100         }
101 
102         @Override
toString()103         public String toString() {
104             return "SubmittedTaskImpl [mIdentifier=" + mIdentifier + "]";
105         }
106     }
107 
108     private class DelayedExecutor implements Executor {
109         private final Object mNextLock = new Object();
110         @GuardedBy("mNextLock") private Object mNextIdentifier;
111         @GuardedBy("mNextLock") private AsyncTask<?, ?, ?> mNextTask;
112 
113         @Override
execute(Runnable command)114         public void execute(Runnable command) {
115             synchronized (mNextLock) {
116                 Assert.assertNotNull(mNextTask);
117                 mSubmittedTasks.add(new SubmittedTaskImpl(mNextIdentifier,
118                         command, mNextTask));
119                 mNextIdentifier = null;
120                 mNextTask = null;
121             }
122         }
123 
submit(Object identifier, AsyncTask<T, ?, ?> task, T... params)124         public <T> AsyncTask<T, ?, ?> submit(Object identifier,
125                 AsyncTask<T, ?, ?> task, T... params) {
126             synchronized (mNextLock) {
127                 Assert.assertNull(mNextIdentifier);
128                 Assert.assertNull(mNextTask);
129                 mNextIdentifier = identifier;
130                 Assert.assertNotNull("Already had a valid task.\n"
131                         + "Are you calling AsyncTaskExecutor.submit(...) from within the "
132                         + "onPreExecute() method of another task being submitted?\n"
133                         + "Sorry!  Not that's not supported.", task);
134                 mNextTask = task;
135             }
136             return task.executeOnExecutor(this, params);
137         }
138     }
139 
140     @Override
submit(Object identifier, AsyncTask<T, ?, ?> task, T... params)141     public <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params) {
142         AsyncTaskExecutors.checkCalledFromUiThread();
143         return mBlockingExecutor.submit(identifier, task, params);
144     }
145 
146     /**
147      * Runs a single task matching the given identifier.
148      * <p>
149      * Removes the matching task from the list of submitted tasks, then runs it. The executor used
150      * to execute this async task will be a same-thread executor.
151      * <p>
152      * Fails if there was not exactly one task matching the given identifier.
153      * <p>
154      * This method blocks until the AsyncTask has completely finished executing.
155      */
runTask(Object identifier)156     public void runTask(Object identifier) throws InterruptedException {
157         List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true);
158         Assert.assertEquals("Expected one task " + identifier + ", got " + tasks, 1, tasks.size());
159         runTask(tasks.get(0));
160     }
161 
162     /**
163      * Runs all tasks whose identifier matches the given identifier.
164      * <p>
165      * Removes all matching tasks from the list of submitted tasks, and runs them. The executor used
166      * to execute these async tasks will be a same-thread executor.
167      * <p>
168      * Fails if there were no tasks matching the given identifier.
169      * <p>
170      * This method blocks until the AsyncTask objects have completely finished executing.
171      */
runAllTasks(Object identifier)172     public void runAllTasks(Object identifier) throws InterruptedException {
173         List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true);
174         Assert.assertTrue("There were no tasks with identifier " + identifier, tasks.size() > 0);
175         for (SubmittedTask task : tasks) {
176             runTask(task);
177         }
178     }
179 
180     /**
181      * Executes a single {@link SubmittedTask}.
182      * <p>
183      * Blocks until the task has completed running.
184      */
runTask(final SubmittedTask submittedTask)185     private <T> void runTask(final SubmittedTask submittedTask) throws InterruptedException {
186         submittedTask.getRunnable().run();
187         // Block until the onPostExecute or onCancelled has finished.
188         // Unfortunately we can't be sure when the AsyncTask will have posted its result handling
189         // code to the main ui thread, the best we can do is wait for the Status to be FINISHED.
190         final CountDownLatch latch = new CountDownLatch(1);
191         class AsyncTaskHasFinishedRunnable implements Runnable {
192             @Override
193             public void run() {
194                 if (submittedTask.getAsyncTask().getStatus() == AsyncTask.Status.FINISHED) {
195                     latch.countDown();
196                 } else {
197                     mInstrumentation.waitForIdle(this);
198                 }
199             }
200         }
201         mInstrumentation.waitForIdle(new AsyncTaskHasFinishedRunnable());
202         Assert.assertTrue(latch.await(mTimeoutMs, TimeUnit.MILLISECONDS));
203     }
204 
getSubmittedTasksByIdentifier( Object identifier, boolean remove)205     private List<SubmittedTask> getSubmittedTasksByIdentifier(
206             Object identifier, boolean remove) {
207         Assert.assertNotNull(identifier);
208         List<SubmittedTask> results = Lists.newArrayList();
209         synchronized (mLock) {
210             Iterator<SubmittedTask> iter = mSubmittedTasks.iterator();
211             while (iter.hasNext()) {
212                 SubmittedTask task = iter.next();
213                 if (identifier.equals(task.getIdentifier())) {
214                     results.add(task);
215                     iter.remove();
216                 }
217             }
218         }
219         return results;
220     }
221 
222     /** Get a factory that will return this instance - useful for testing. */
getFactory()223     public AsyncTaskExecutors.AsyncTaskExecutorFactory getFactory() {
224         return new AsyncTaskExecutors.AsyncTaskExecutorFactory() {
225             @Override
226             public AsyncTaskExecutor createAsyncTaskExeuctor() {
227                 return FakeAsyncTaskExecutor.this;
228             }
229         };
230     }
231 }
232