summaryrefslogtreecommitdiff
path: root/base/test/android/javatests/src/org/chromium/base/test/util/TestThread.java
blob: 4f6296924cd5ed89a3a3fa26b01701dbec9664f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// 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 final 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();
    }
}