| // 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(); |
| } |
| } |