1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5package org.chromium.base.test.util; 6 7import android.os.Handler; 8import android.os.Looper; 9 10import java.util.concurrent.atomic.AtomicBoolean; 11 12/** 13 * This class is usefull when writing instrumentation tests that exercise code that posts tasks 14 * (to the same thread). 15 * Since the test code is run in a single thread, the posted tasks are never executed. 16 * The TestThread class lets you run that code on a specific thread synchronously and flush the 17 * message loop on that thread. 18 * 19 * Example of test using this: 20 * 21 * public void testMyAwesomeClass() { 22 * TestThread testThread = new TestThread(); 23 * testThread.startAndWaitForReadyState(); 24 * 25 * testThread.runOnTestThreadSyncAndProcessPendingTasks(new Runnable() { 26 * @Override 27 * public void run() { 28 * MyAwesomeClass.doStuffAsync(); 29 * } 30 * }); 31 * // Once we get there we know doStuffAsync has been executed and all the tasks it posted. 32 * assertTrue(MyAwesomeClass.stuffWasDone()); 33 * } 34 * 35 * Notes: 36 * - this is only for tasks posted to the same thread. Anyway if you were posting to a different 37 * thread, you'd probably need to set that other thread up. 38 * - this only supports tasks posted using Handler.post(), it won't work with postDelayed and 39 * postAtTime. 40 * - if your test instanciates an object and that object is the one doing the posting of tasks, you 41 * probably want to instanciate it on the test thread as it might create the Handler it posts 42 * tasks to in the constructor. 43 */ 44 45public class TestThread extends Thread { 46 private Object mThreadReadyLock; 47 private AtomicBoolean mThreadReady; 48 private Handler mMainThreadHandler; 49 private Handler mTestThreadHandler; 50 51 public TestThread() { 52 mMainThreadHandler = new Handler(); 53 // We can't use the AtomicBoolean as the lock or findbugs will freak out... 54 mThreadReadyLock = new Object(); 55 mThreadReady = new AtomicBoolean(); 56 } 57 58 @Override 59 public void run() { 60 Looper.prepare(); 61 mTestThreadHandler = new Handler(); 62 mTestThreadHandler.post(new Runnable() { 63 @Override 64 public void run() { 65 synchronized (mThreadReadyLock) { 66 mThreadReady.set(true); 67 mThreadReadyLock.notify(); 68 } 69 } 70 }); 71 Looper.loop(); 72 } 73 74 /** 75 * Starts this TestThread and blocks until it's ready to accept calls. 76 */ 77 public void startAndWaitForReadyState() { 78 checkOnMainThread(); 79 start(); 80 synchronized (mThreadReadyLock) { 81 try { 82 // Note the mThreadReady and while are not really needed. 83 // There are there so findbugs don't report warnings. 84 while (!mThreadReady.get()) { 85 mThreadReadyLock.wait(); 86 } 87 } catch (InterruptedException ie) { 88 System.err.println("Error starting TestThread."); 89 ie.printStackTrace(); 90 } 91 } 92 } 93 94 /** 95 * Runs the passed Runnable synchronously on the TestThread and returns when all pending 96 * runnables have been excuted. 97 * Should be called from the main thread. 98 */ 99 public void runOnTestThreadSyncAndProcessPendingTasks(Runnable r) { 100 checkOnMainThread(); 101 102 runOnTestThreadSync(r); 103 104 // Run another task, when it's done it means all pendings tasks have executed. 105 runOnTestThreadSync(null); 106 } 107 108 /** 109 * Runs the passed Runnable on the test thread and blocks until it has finished executing. 110 * Should be called from the main thread. 111 * @param r The runnable to be executed. 112 */ 113 public void runOnTestThreadSync(final Runnable r) { 114 checkOnMainThread(); 115 final Object lock = new Object(); 116 // Task executed is not really needed since we are only on one thread, it is here to appease 117 // findbugs. 118 final AtomicBoolean taskExecuted = new AtomicBoolean(); 119 mTestThreadHandler.post(new Runnable() { 120 @Override 121 public void run() { 122 if (r != null) r.run(); 123 synchronized (lock) { 124 taskExecuted.set(true); 125 lock.notify(); 126 } 127 } 128 }); 129 synchronized (lock) { 130 try { 131 while (!taskExecuted.get()) { 132 lock.wait(); 133 } 134 } catch (InterruptedException ie) { 135 ie.printStackTrace(); 136 } 137 } 138 } 139 140 private void checkOnMainThread() { 141 assert Looper.myLooper() == mMainThreadHandler.getLooper(); 142 } 143} 144