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.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. */
     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