OLD | NEW |
---|---|
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library test.backend.invoker; | 5 library test.backend.invoker; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 | 8 |
9 import 'package:stack_trace/stack_trace.dart'; | 9 import 'package:stack_trace/stack_trace.dart'; |
10 | 10 |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
86 } | 86 } |
87 | 87 |
88 /// The current invoker, or `null` if none is defined. | 88 /// The current invoker, or `null` if none is defined. |
89 /// | 89 /// |
90 /// An invoker is only set within the zone scope of a running test. | 90 /// An invoker is only set within the zone scope of a running test. |
91 static Invoker get current { | 91 static Invoker get current { |
92 // TODO(nweiz): Use a private symbol when dart2js supports it (issue 17526). | 92 // TODO(nweiz): Use a private symbol when dart2js supports it (issue 17526). |
93 return Zone.current[#test.invoker]; | 93 return Zone.current[#test.invoker]; |
94 } | 94 } |
95 | 95 |
96 /// The timer for tracking timeouts. | |
97 /// | |
98 /// This will be `null` until the test starts running. | |
99 Timer _timeoutTimer; | |
100 | |
96 Invoker._(Suite suite, LocalTest test) | 101 Invoker._(Suite suite, LocalTest test) |
97 : metadata = suite.metadata.merge(test.metadata) { | 102 : metadata = suite.metadata.merge(test.metadata) { |
98 _controller = new LiveTestController(suite, test, _onRun, () { | 103 _controller = new LiveTestController(suite, test, _onRun, () { |
99 _closed = true; | 104 _closed = true; |
100 }); | 105 }); |
101 } | 106 } |
102 | 107 |
103 /// Tells the invoker that there's a callback running that it should wait for | 108 /// Tells the invoker that there's a callback running that it should wait for |
104 /// before considering the test successful. | 109 /// before considering the test successful. |
105 /// | 110 /// |
106 /// Each call to [addOutstandingCallback] should be followed by a call to | 111 /// Each call to [addOutstandingCallback] should be followed by a call to |
107 /// [removeOutstandingCallback] once the callbak is no longer running. Note | 112 /// [removeOutstandingCallback] once the callbak is no longer running. Note |
108 /// that only successful tests wait for outstanding callbacks; as soon as a | 113 /// that only successful tests wait for outstanding callbacks; as soon as a |
109 /// test experiences an error, any further calls to [addOutstandingCallback] | 114 /// test experiences an error, any further calls to [addOutstandingCallback] |
110 /// or [removeOutstandingCallback] will do nothing. | 115 /// or [removeOutstandingCallback] will do nothing. |
111 /// | 116 /// |
112 /// Throws a [ClosedException] if this test has been closed. | 117 /// Throws a [ClosedException] if this test has been closed. |
113 void addOutstandingCallback() { | 118 void addOutstandingCallback() { |
114 if (closed) throw new ClosedException(); | 119 if (closed) throw new ClosedException(); |
115 _outstandingCallbacks.addOutstandingCallback(); | 120 _outstandingCallbacks.addOutstandingCallback(); |
116 } | 121 } |
117 | 122 |
118 /// Tells the invoker that a callback declared with [addOutstandingCallback] | 123 /// Tells the invoker that a callback declared with [addOutstandingCallback] |
119 /// is no longer running. | 124 /// is no longer running. |
120 void removeOutstandingCallback() => | 125 void removeOutstandingCallback() { |
121 _outstandingCallbacks.removeOutstandingCallback(); | 126 heartbeat(); |
127 _outstandingCallbacks.removeOutstandingCallback(); | |
128 } | |
122 | 129 |
123 /// Runs [fn] and returns once all (registered) outstanding callbacks it | 130 /// Runs [fn] and returns once all (registered) outstanding callbacks it |
124 /// transitively invokes have completed. | 131 /// transitively invokes have completed. |
125 /// | 132 /// |
126 /// If [fn] itself returns a future, this will automatically wait until that | 133 /// If [fn] itself returns a future, this will automatically wait until that |
127 /// future completes as well. | 134 /// future completes as well. |
128 /// | 135 /// |
129 /// Note that outstanding callbacks registered within [fn] will *not* be | 136 /// Note that outstanding callbacks registered within [fn] will *not* be |
130 /// registered as outstanding callback outside of [fn]. | 137 /// registered as outstanding callback outside of [fn]. |
131 Future waitForOutstandingCallbacks(fn()) { | 138 Future waitForOutstandingCallbacks(fn()) { |
139 heartbeat(); | |
140 | |
132 var counter = new OutstandingCallbackCounter(); | 141 var counter = new OutstandingCallbackCounter(); |
133 runZoned(() { | 142 runZoned(() { |
134 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in | 143 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in |
135 // two stable versions. | 144 // two stable versions. |
136 new Future.sync(fn).then((_) => counter.removeOutstandingCallback()); | 145 new Future.sync(fn).then((_) => counter.removeOutstandingCallback()); |
137 }, zoneValues: { | 146 }, zoneValues: { |
138 // Use the invoker as a key so that multiple invokers can have different | 147 // Use the invoker as a key so that multiple invokers can have different |
139 // outstanding callback counters at once. | 148 // outstanding callback counters at once. |
140 this: counter | 149 this: counter |
141 }); | 150 }); |
142 | 151 |
143 return counter.noOutstandingCallbacks; | 152 return counter.noOutstandingCallbacks; |
144 } | 153 } |
145 | 154 |
155 /// Notifies the invoker that progress is being made. | |
156 /// | |
157 /// Each heartbeat resets the timeout timer. This helps ensure that | |
158 /// long-running tests that still make progress don't time out. | |
159 void heartbeat() { | |
160 if (_liveTest.isComplete) return; | |
161 if (_timeoutTimer != null) _timeoutTimer.cancel(); | |
162 | |
163 var timeout = metadata.timeout.apply(new Duration(seconds: 30)); | |
kevmoo
2015/05/29 01:26:46
This implies that 30s is the max time allowed betw
nweiz
2015/05/29 20:25:39
This is already pluggable via the @Timeout annotat
| |
164 _timeoutTimer = new Timer(timeout, () { | |
165 if (liveTest.isComplete) return; | |
166 handleError( | |
167 new TimeoutException( | |
168 "Test timed out after ${niceDuration(timeout)}.", timeout)); | |
169 }); | |
170 } | |
171 | |
146 /// Notifies the invoker of an asynchronous error. | 172 /// Notifies the invoker of an asynchronous error. |
147 /// | 173 /// |
148 /// Note that calling this explicitly is rarely necessary, since any | 174 /// Note that calling this explicitly is rarely necessary, since any |
149 /// otherwise-uncaught errors will be forwarded to the invoker anyway. | 175 /// otherwise-uncaught errors will be forwarded to the invoker anyway. |
150 void handleError(error, [StackTrace stackTrace]) { | 176 void handleError(error, [StackTrace stackTrace]) { |
151 if (stackTrace == null) stackTrace = new Chain.current(); | 177 if (stackTrace == null) stackTrace = new Chain.current(); |
152 | 178 |
153 var afterSuccess = liveTest.isComplete && | 179 var afterSuccess = liveTest.isComplete && |
154 liveTest.state.result == Result.success; | 180 liveTest.state.result == Result.success; |
155 | 181 |
(...skipping 19 matching lines...) Expand all Loading... | |
175 /// The method that's run when the test is started. | 201 /// The method that's run when the test is started. |
176 void _onRun() { | 202 void _onRun() { |
177 _controller.setState(const State(Status.running, Result.success)); | 203 _controller.setState(const State(Status.running, Result.success)); |
178 | 204 |
179 var outstandingCallbacksForBody = new OutstandingCallbackCounter(); | 205 var outstandingCallbacksForBody = new OutstandingCallbackCounter(); |
180 | 206 |
181 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in two | 207 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in two |
182 // stable versions. | 208 // stable versions. |
183 Chain.capture(() { | 209 Chain.capture(() { |
184 runZonedWithValues(() { | 210 runZonedWithValues(() { |
185 // TODO(nweiz): Reset this timer whenever the user's code interacts | |
186 // with the library. | |
187 var timeout = metadata.timeout.apply(new Duration(seconds: 30)); | |
188 var timer = new Timer(timeout, () { | |
189 if (liveTest.isComplete) return; | |
190 handleError( | |
191 new TimeoutException( | |
192 "Test timed out after ${niceDuration(timeout)}.", timeout)); | |
193 }); | |
194 | |
195 // Run the test asynchronously so that the "running" state change has | 211 // Run the test asynchronously so that the "running" state change has |
196 // a chance to hit its event handler(s) before the test produces an | 212 // a chance to hit its event handler(s) before the test produces an |
197 // error. If an error is emitted before the first state change is | 213 // error. If an error is emitted before the first state change is |
198 // handled, we can end up with [onError] callbacks firing before the | 214 // handled, we can end up with [onError] callbacks firing before the |
199 // corresponding [onStateChange], which violates the timing | 215 // corresponding [onStateChange], which violates the timing |
200 // guarantees. | 216 // guarantees. |
201 new Future(_test._body) | 217 new Future(_test._body) |
202 .then((_) => removeOutstandingCallback()); | 218 .then((_) => removeOutstandingCallback()); |
203 | 219 |
204 _outstandingCallbacks.noOutstandingCallbacks.then((_) { | 220 _outstandingCallbacks.noOutstandingCallbacks.then((_) { |
205 if (_test._tearDown == null) return null; | 221 if (_test._tearDown == null) return null; |
206 | 222 |
207 // Reset the outstanding callback counter to wait for callbacks from | 223 // Reset the outstanding callback counter to wait for callbacks from |
208 // the test's `tearDown` to complete. | 224 // the test's `tearDown` to complete. |
209 return waitForOutstandingCallbacks(() => | 225 return waitForOutstandingCallbacks(() => |
210 runZoned(_test._tearDown, onError: handleError)); | 226 runZoned(_test._tearDown, onError: handleError)); |
211 }).then((_) { | 227 }).then((_) { |
212 timer.cancel(); | 228 _timeoutTimer.cancel(); |
213 _controller.setState( | 229 _controller.setState( |
214 new State(Status.complete, liveTest.state.result)); | 230 new State(Status.complete, liveTest.state.result)); |
215 | 231 |
216 // Use [Timer.run] here to avoid starving the DOM or other | 232 // Use [Timer.run] here to avoid starving the DOM or other |
217 // non-microtask events. | 233 // non-microtask events. |
218 Timer.run(_controller.completer.complete); | 234 Timer.run(_controller.completer.complete); |
219 }); | 235 }); |
220 }, zoneValues: { | 236 }, zoneValues: { |
221 #test.invoker: this, | 237 #test.invoker: this, |
222 // Use the invoker as a key so that multiple invokers can have different | 238 // Use the invoker as a key so that multiple invokers can have different |
223 // outstanding callback counters at once. | 239 // outstanding callback counters at once. |
224 this: outstandingCallbacksForBody | 240 this: outstandingCallbacksForBody |
225 }, | 241 }, |
226 zoneSpecification: new ZoneSpecification( | 242 zoneSpecification: new ZoneSpecification( |
227 print: (self, parent, zone, line) => _controller.print(line)), | 243 print: (self, parent, zone, line) => _controller.print(line)), |
228 onError: handleError); | 244 onError: handleError); |
229 }); | 245 }); |
230 } | 246 } |
231 } | 247 } |
OLD | NEW |