Home | History | Annotate | Download | only in util
      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