blob: 52cdf7e77cf5cdbf20c05a36d288ab31532be09a [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.dialer.util;
import android.app.Instrumentation;
import android.os.AsyncTask;
import com.google.common.collect.Lists;
import junit.framework.Assert;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
/**
* Test implementation of AsyncTaskExecutor.
* <p>
* This class is thread-safe. As per the contract of the AsyncTaskExecutor, the submit methods must
* be called from the main ui thread, however the other public methods may be called from any thread
* (most commonly the test thread).
* <p>
* Tasks submitted to this executor will not be run immediately. Rather they will be stored in a
* list of submitted tasks, where they can be examined. They can also be run on-demand using the run
* methods, so that different ordering of AsyncTask execution can be simulated.
* <p>
* The onPreExecute method of the submitted AsyncTask will be called synchronously during the
* call to {@link #submit(Object, AsyncTask, Object...)}.
*/
@ThreadSafe
public class FakeAsyncTaskExecutor implements AsyncTaskExecutor {
private static final long DEFAULT_TIMEOUT_MS = 10000;
/** The maximum length of time in ms to wait for tasks to execute during tests. */
private final long mTimeoutMs = DEFAULT_TIMEOUT_MS;
private final Object mLock = new Object();
@GuardedBy("mLock") private final List<SubmittedTask> mSubmittedTasks = Lists.newArrayList();
private final DelayedExecutor mBlockingExecutor = new DelayedExecutor();
private final Instrumentation mInstrumentation;
/** Create a fake AsyncTaskExecutor for use in unit tests. */
public FakeAsyncTaskExecutor(Instrumentation instrumentation) {
Assert.assertNotNull(instrumentation);
mInstrumentation = instrumentation;
}
/** Encapsulates an async task with the params and identifier it was submitted with. */
public interface SubmittedTask {
Runnable getRunnable();
Object getIdentifier();
AsyncTask<?, ?, ?> getAsyncTask();
}
private static final class SubmittedTaskImpl implements SubmittedTask {
private final Object mIdentifier;
private final Runnable mRunnable;
private final AsyncTask<?, ?, ?> mAsyncTask;
public SubmittedTaskImpl(Object identifier, Runnable runnable,
AsyncTask<?, ?, ?> asyncTask) {
mIdentifier = identifier;
mRunnable = runnable;
mAsyncTask = asyncTask;
}
@Override
public Object getIdentifier() {
return mIdentifier;
}
@Override
public Runnable getRunnable() {
return mRunnable;
}
@Override
public AsyncTask<?, ?, ?> getAsyncTask() {
return mAsyncTask;
}
@Override
public String toString() {
return "SubmittedTaskImpl [mIdentifier=" + mIdentifier + "]";
}
}
private class DelayedExecutor implements Executor {
private final Object mNextLock = new Object();
@GuardedBy("mNextLock") private Object mNextIdentifier;
@GuardedBy("mNextLock") private AsyncTask<?, ?, ?> mNextTask;
@Override
public void execute(Runnable command) {
synchronized (mNextLock) {
Assert.assertNotNull(mNextTask);
mSubmittedTasks.add(new SubmittedTaskImpl(mNextIdentifier,
command, mNextTask));
mNextIdentifier = null;
mNextTask = null;
}
}
public <T> AsyncTask<T, ?, ?> submit(Object identifier,
AsyncTask<T, ?, ?> task, T... params) {
synchronized (mNextLock) {
Assert.assertNull(mNextIdentifier);
Assert.assertNull(mNextTask);
mNextIdentifier = identifier;
Assert.assertNotNull("Already had a valid task.\n"
+ "Are you calling AsyncTaskExecutor.submit(...) from within the "
+ "onPreExecute() method of another task being submitted?\n"
+ "Sorry! Not that's not supported.", task);
mNextTask = task;
}
return task.executeOnExecutor(this, params);
}
}
@Override
public <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params) {
AsyncTaskExecutors.checkCalledFromUiThread();
return mBlockingExecutor.submit(identifier, task, params);
}
/**
* Runs a single task matching the given identifier.
* <p>
* Removes the matching task from the list of submitted tasks, then runs it. The executor used
* to execute this async task will be a same-thread executor.
* <p>
* Fails if there was not exactly one task matching the given identifier.
* <p>
* This method blocks until the AsyncTask has completely finished executing.
*/
public void runTask(Object identifier) throws InterruptedException {
List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true);
Assert.assertEquals("Expected one task " + identifier + ", got " + tasks, 1, tasks.size());
runTask(tasks.get(0));
}
/**
* Runs all tasks whose identifier matches the given identifier.
* <p>
* Removes all matching tasks from the list of submitted tasks, and runs them. The executor used
* to execute these async tasks will be a same-thread executor.
* <p>
* Fails if there were no tasks matching the given identifier.
* <p>
* This method blocks until the AsyncTask objects have completely finished executing.
*/
public void runAllTasks(Object identifier) throws InterruptedException {
List<SubmittedTask> tasks = getSubmittedTasksByIdentifier(identifier, true);
Assert.assertTrue("There were no tasks with identifier " + identifier, tasks.size() > 0);
for (SubmittedTask task : tasks) {
runTask(task);
}
}
/**
* Executes a single {@link SubmittedTask}.
* <p>
* Blocks until the task has completed running.
*/
private <T> void runTask(final SubmittedTask submittedTask) throws InterruptedException {
submittedTask.getRunnable().run();
// Block until the onPostExecute or onCancelled has finished.
// Unfortunately we can't be sure when the AsyncTask will have posted its result handling
// code to the main ui thread, the best we can do is wait for the Status to be FINISHED.
final CountDownLatch latch = new CountDownLatch(1);
class AsyncTaskHasFinishedRunnable implements Runnable {
@Override
public void run() {
if (submittedTask.getAsyncTask().getStatus() == AsyncTask.Status.FINISHED) {
latch.countDown();
} else {
mInstrumentation.waitForIdle(this);
}
}
}
mInstrumentation.waitForIdle(new AsyncTaskHasFinishedRunnable());
Assert.assertTrue(latch.await(mTimeoutMs, TimeUnit.MILLISECONDS));
}
private List<SubmittedTask> getSubmittedTasksByIdentifier(
Object identifier, boolean remove) {
Assert.assertNotNull(identifier);
List<SubmittedTask> results = Lists.newArrayList();
synchronized (mLock) {
Iterator<SubmittedTask> iter = mSubmittedTasks.iterator();
while (iter.hasNext()) {
SubmittedTask task = iter.next();
if (identifier.equals(task.getIdentifier())) {
results.add(task);
iter.remove();
}
}
}
return results;
}
/** Get a factory that will return this instance - useful for testing. */
public AsyncTaskExecutors.AsyncTaskExecutorFactory getFactory() {
return new AsyncTaskExecutors.AsyncTaskExecutorFactory() {
@Override
public AsyncTaskExecutor createAsyncTaskExeuctor() {
return FakeAsyncTaskExecutor.this;
}
};
}
}