|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 package org.mozilla.gecko.background.testhelpers; |
|
5 |
|
6 import java.util.concurrent.ArrayBlockingQueue; |
|
7 import java.util.concurrent.BlockingQueue; |
|
8 import java.util.concurrent.TimeUnit; |
|
9 |
|
10 import org.mozilla.gecko.background.common.log.Logger; |
|
11 |
|
12 /** |
|
13 * Implements waiting for asynchronous test events. |
|
14 * |
|
15 * Call WaitHelper.getTestWaiter() to get the unique instance. |
|
16 * |
|
17 * Call performWait(runnable) to execute runnable synchronously. |
|
18 * runnable *must* call performNotify() on all exit paths to signal to |
|
19 * the TestWaiter that the runnable has completed. |
|
20 * |
|
21 * @author rnewman |
|
22 * @author nalexander |
|
23 */ |
|
24 public class WaitHelper { |
|
25 |
|
26 public static final String LOG_TAG = "WaitHelper"; |
|
27 |
|
28 public static class Result { |
|
29 public Throwable error; |
|
30 public Result() { |
|
31 error = null; |
|
32 } |
|
33 |
|
34 public Result(Throwable error) { |
|
35 this.error = error; |
|
36 } |
|
37 } |
|
38 |
|
39 public static abstract class WaitHelperError extends Error { |
|
40 private static final long serialVersionUID = 7074690961681883619L; |
|
41 } |
|
42 |
|
43 /** |
|
44 * Immutable. |
|
45 * |
|
46 * @author rnewman |
|
47 */ |
|
48 public static class TimeoutError extends WaitHelperError { |
|
49 private static final long serialVersionUID = 8591672555848651736L; |
|
50 public final int waitTimeInMillis; |
|
51 |
|
52 public TimeoutError(int waitTimeInMillis) { |
|
53 this.waitTimeInMillis = waitTimeInMillis; |
|
54 } |
|
55 } |
|
56 |
|
57 public static class MultipleNotificationsError extends WaitHelperError { |
|
58 private static final long serialVersionUID = -9072736521571635495L; |
|
59 } |
|
60 |
|
61 public static class InterruptedError extends WaitHelperError { |
|
62 private static final long serialVersionUID = 8383948170038639308L; |
|
63 } |
|
64 |
|
65 public static class InnerError extends WaitHelperError { |
|
66 private static final long serialVersionUID = 3008502618576773778L; |
|
67 public Throwable innerError; |
|
68 |
|
69 public InnerError(Throwable e) { |
|
70 innerError = e; |
|
71 if (e != null) { |
|
72 // Eclipse prints the stack trace of the cause. |
|
73 this.initCause(e); |
|
74 } |
|
75 } |
|
76 } |
|
77 |
|
78 public BlockingQueue<Result> queue = new ArrayBlockingQueue<Result>(1); |
|
79 |
|
80 /** |
|
81 * How long performWait should wait for, in milliseconds, with the |
|
82 * convention that a negative value means "wait forever". |
|
83 */ |
|
84 public static int defaultWaitTimeoutInMillis = -1; |
|
85 |
|
86 public void performWait(Runnable action) throws WaitHelperError { |
|
87 this.performWait(defaultWaitTimeoutInMillis, action); |
|
88 } |
|
89 |
|
90 public void performWait(int waitTimeoutInMillis, Runnable action) throws WaitHelperError { |
|
91 Logger.debug(LOG_TAG, "performWait called."); |
|
92 |
|
93 Result result = null; |
|
94 |
|
95 try { |
|
96 if (action != null) { |
|
97 try { |
|
98 action.run(); |
|
99 Logger.debug(LOG_TAG, "Action done."); |
|
100 } catch (Exception ex) { |
|
101 Logger.debug(LOG_TAG, "Performing action threw: " + ex.getMessage()); |
|
102 throw new InnerError(ex); |
|
103 } |
|
104 } |
|
105 |
|
106 if (waitTimeoutInMillis < 0) { |
|
107 result = queue.take(); |
|
108 } else { |
|
109 result = queue.poll(waitTimeoutInMillis, TimeUnit.MILLISECONDS); |
|
110 } |
|
111 Logger.debug(LOG_TAG, "Got result from queue: " + result); |
|
112 } catch (InterruptedException e) { |
|
113 // We were interrupted. |
|
114 Logger.debug(LOG_TAG, "performNotify interrupted with InterruptedException " + e); |
|
115 final InterruptedError interruptedError = new InterruptedError(); |
|
116 interruptedError.initCause(e); |
|
117 throw interruptedError; |
|
118 } |
|
119 |
|
120 if (result == null) { |
|
121 // We timed out. |
|
122 throw new TimeoutError(waitTimeoutInMillis); |
|
123 } else if (result.error != null) { |
|
124 Logger.debug(LOG_TAG, "Notified with error: " + result.error.getMessage()); |
|
125 |
|
126 // Rethrow any assertion with which we were notified. |
|
127 InnerError innerError = new InnerError(result.error); |
|
128 throw innerError; |
|
129 } |
|
130 // Success! |
|
131 } |
|
132 |
|
133 public void performNotify(final Throwable e) { |
|
134 if (e != null) { |
|
135 Logger.debug(LOG_TAG, "performNotify called with Throwable: " + e.getMessage()); |
|
136 } else { |
|
137 Logger.debug(LOG_TAG, "performNotify called."); |
|
138 } |
|
139 |
|
140 if (!queue.offer(new Result(e))) { |
|
141 // This could happen if performNotify is called multiple times (which is an error). |
|
142 throw new MultipleNotificationsError(); |
|
143 } |
|
144 } |
|
145 |
|
146 public void performNotify() { |
|
147 this.performNotify(null); |
|
148 } |
|
149 |
|
150 public static Runnable onThreadRunnable(final Runnable r) { |
|
151 return new Runnable() { |
|
152 @Override |
|
153 public void run() { |
|
154 new Thread(r).start(); |
|
155 } |
|
156 }; |
|
157 } |
|
158 |
|
159 private static WaitHelper singleWaiter = new WaitHelper(); |
|
160 public static WaitHelper getTestWaiter() { |
|
161 return singleWaiter; |
|
162 } |
|
163 |
|
164 public static void resetTestWaiter() { |
|
165 singleWaiter = new WaitHelper(); |
|
166 } |
|
167 |
|
168 public boolean isIdle() { |
|
169 return queue.isEmpty(); |
|
170 } |
|
171 } |