// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base.test.util;
import android.os.Handler;
import android.os.Looper;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* This class is usefull when writing instrumentation tests that exercise code that posts tasks
* (to the same thread).
* Since the test code is run in a single thread, the posted tasks are never executed.
* The TestThread class lets you run that code on a specific thread synchronously and flush the
* message loop on that thread.
*
* Example of test using this:
*
* public void testMyAwesomeClass() {
* TestThread testThread = new TestThread();
* testThread.startAndWaitForReadyState();
*
* testThread.runOnTestThreadSyncAndProcessPendingTasks(new Runnable() {
* @Override
* public void run() {
* MyAwesomeClass.doStuffAsync();
* }
* });
* // Once we get there we know doStuffAsync has been executed and all the tasks it posted.
* assertTrue(MyAwesomeClass.stuffWasDone());
* }
*
* Notes:
* - this is only for tasks posted to the same thread. Anyway if you were posting to a different
* thread, you'd probably need to set that other thread up.
* - this only supports tasks posted using Handler.post(), it won't work with postDelayed and
* postAtTime.
* - if your test instanciates an object and that object is the one doing the posting of tasks, you
* probably want to instanciate it on the test thread as it might create the Handler it posts
* tasks to in the constructor.
*/
public class TestThread extends Thread {
private Object mThreadReadyLock;
private AtomicBoolean mThreadReady;
private Handler mMainThreadHandler;
private Handler mTestThreadHandler;
public TestThread() {
mMainThreadHandler = new Handler();
// We can't use the AtomicBoolean as the lock or findbugs will freak out...
mThreadReadyLock = new Object();
mThreadReady = new AtomicBoolean();
}
@Override
public void run() {
Looper.prepare();
mTestThreadHandler = new Handler();
mTestThreadHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mThreadReadyLock) {
mThreadReady.set(true);
mThreadReadyLock.notify();
}
}
});
Looper.loop();
}
/**
* Starts this TestThread and blocks until it's ready to accept calls.
*/
public void startAndWaitForReadyState() {
checkOnMainThread();
start();
synchronized (mThreadReadyLock) {
try {
// Note the mThreadReady and while are not really needed.
// There are there so findbugs don't report warnings.
while (!mThreadReady.get()) {
mThreadReadyLock.wait();
}
} catch (InterruptedException ie) {
System.err.println("Error starting TestThread.");
ie.printStackTrace();
}
}
}
/**
* Runs the passed Runnable synchronously on the TestThread and returns when all pending
* runnables have been excuted.
* Should be called from the main thread.
*/
public void runOnTestThreadSyncAndProcessPendingTasks(Runnable r) {
checkOnMainThread();
runOnTestThreadSync(r);
// Run another task, when it's done it means all pendings tasks have executed.
runOnTestThreadSync(null);
}
/**
* Runs the passed Runnable on the test thread and blocks until it has finished executing.
* Should be called from the main thread.
* @param r The runnable to be executed.
*/
public void runOnTestThreadSync(final Runnable r) {
checkOnMainThread();
final Object lock = new Object();
// Task executed is not really needed since we are only on one thread, it is here to appease
// findbugs.
final AtomicBoolean taskExecuted = new AtomicBoolean();
mTestThreadHandler.post(new Runnable() {
@Override
public void run() {
if (r != null) r.run();
synchronized (lock) {
taskExecuted.set(true);
lock.notify();
}
}
});
synchronized (lock) {
try {
while (!taskExecuted.get()) {
lock.wait();
}
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
private void checkOnMainThread() {
assert Looper.myLooper() == mMainThreadHandler.getLooper();
}
}