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