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