TestThread.java revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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