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.google.common.collect.Lists; 23 24import junit.framework.Assert; 25 26import java.util.Iterator; 27import java.util.List; 28import java.util.concurrent.CountDownLatch; 29import java.util.concurrent.Executor; 30import java.util.concurrent.TimeUnit; 31 32import javax.annotation.concurrent.GuardedBy; 33import 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 50public 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. */ 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 { 70 Runnable getRunnable(); 71 Object getIdentifier(); 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 80 public SubmittedTaskImpl(Object identifier, Runnable runnable, 81 AsyncTask<?, ?, ?> asyncTask) { 82 mIdentifier = identifier; 83 mRunnable = runnable; 84 mAsyncTask = asyncTask; 85 } 86 87 @Override 88 public Object getIdentifier() { 89 return mIdentifier; 90 } 91 92 @Override 93 public Runnable getRunnable() { 94 return mRunnable; 95 } 96 97 @Override 98 public AsyncTask<?, ?, ?> getAsyncTask() { 99 return mAsyncTask; 100 } 101 102 @Override 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 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 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 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 */ 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 */ 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 */ 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 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. */ 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