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 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
85 LocalTest get _test => liveTest.test as LocalTest; | 85 LocalTest get _test => liveTest.test as LocalTest; |
86 | 86 |
87 /// The outstanding callback counter for the current zone. | 87 /// The outstanding callback counter for the current zone. |
88 OutstandingCallbackCounter get _outstandingCallbacks { | 88 OutstandingCallbackCounter get _outstandingCallbacks { |
89 var counter = Zone.current[_counterKey]; | 89 var counter = Zone.current[_counterKey]; |
90 if (counter != null) return counter; | 90 if (counter != null) return counter; |
91 throw new StateError("Can't add or remove outstanding callbacks outside " | 91 throw new StateError("Can't add or remove outstanding callbacks outside " |
92 "of a test body."); | 92 "of a test body."); |
93 } | 93 } |
94 | 94 |
| 95 /// All the zones created by [waitForOutstandingCallbacks], in the order they |
| 96 /// were created. |
| 97 /// |
| 98 /// This is used to throw timeout errors in the most recent zone. |
| 99 final _outstandingCallbackZones = <Zone>[]; |
| 100 |
95 /// An opaque object used as a key in the zone value map to identify | 101 /// An opaque object used as a key in the zone value map to identify |
96 /// [_outstandingCallbacks]. | 102 /// [_outstandingCallbacks]. |
97 /// | 103 /// |
98 /// This is an instance variable to ensure that multiple invokers don't step | 104 /// This is an instance variable to ensure that multiple invokers don't step |
99 /// on one anothers' toes. | 105 /// on one anothers' toes. |
100 final _counterKey = new Object(); | 106 final _counterKey = new Object(); |
101 | 107 |
102 /// The current invoker, or `null` if none is defined. | 108 /// The current invoker, or `null` if none is defined. |
103 /// | 109 /// |
104 /// An invoker is only set within the zone scope of a running test. | 110 /// An invoker is only set within the zone scope of a running test. |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
157 /// | 163 /// |
158 /// If [fn] itself returns a future, this will automatically wait until that | 164 /// If [fn] itself returns a future, this will automatically wait until that |
159 /// future completes as well. Note that outstanding callbacks registered | 165 /// future completes as well. Note that outstanding callbacks registered |
160 /// within [fn] will *not* be registered as outstanding callback outside of | 166 /// within [fn] will *not* be registered as outstanding callback outside of |
161 /// [fn]. | 167 /// [fn]. |
162 /// | 168 /// |
163 /// If [fn] produces an unhandled error, this marks the current test as | 169 /// If [fn] produces an unhandled error, this marks the current test as |
164 /// failed, removes all outstanding callbacks registered within [fn], and | 170 /// failed, removes all outstanding callbacks registered within [fn], and |
165 /// completes the returned future. It does not remove any outstanding | 171 /// completes the returned future. It does not remove any outstanding |
166 /// callbacks registered outside of [fn]. | 172 /// callbacks registered outside of [fn]. |
| 173 /// |
| 174 /// If the test times out, the *most recent* call to |
| 175 /// [waitForOutstandingCallbacks] will treat that error as occurring within |
| 176 /// [fn]—that is, it will complete immediately. |
167 Future waitForOutstandingCallbacks(fn()) { | 177 Future waitForOutstandingCallbacks(fn()) { |
168 heartbeat(); | 178 heartbeat(); |
169 | 179 |
| 180 var zone; |
170 var counter = new OutstandingCallbackCounter(); | 181 var counter = new OutstandingCallbackCounter(); |
171 runZoned(() { | 182 runZoned(() { |
172 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in | 183 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in |
173 // two stable versions. | 184 // two stable versions. |
174 runZoned(() { | 185 runZoned(() { |
| 186 zone = Zone.current; |
| 187 _outstandingCallbackZones.add(zone); |
175 new Future.sync(fn).then((_) => counter.removeOutstandingCallback()); | 188 new Future.sync(fn).then((_) => counter.removeOutstandingCallback()); |
176 }, onError: _handleError); | 189 }, onError: _handleError); |
177 }, zoneValues: { | 190 }, zoneValues: { |
178 _counterKey: counter | 191 _counterKey: counter |
179 }); | 192 }); |
180 | 193 |
181 return counter.noOutstandingCallbacks; | 194 return counter.noOutstandingCallbacks.whenComplete(() { |
| 195 _outstandingCallbackZones.remove(zone); |
| 196 }); |
182 } | 197 } |
183 | 198 |
184 /// Runs [fn] in a zone where [closed] is always `false`. | 199 /// Runs [fn] in a zone where [closed] is always `false`. |
185 /// | 200 /// |
186 /// This is useful for running code that should be able to register callbacks | 201 /// This is useful for running code that should be able to register callbacks |
187 /// and interact with the test framework normally even when the invoker is | 202 /// and interact with the test framework normally even when the invoker is |
188 /// closed, for example cleanup code. | 203 /// closed, for example cleanup code. |
189 unclosable(fn()) { | 204 unclosable(fn()) { |
190 heartbeat(); | 205 heartbeat(); |
191 | 206 |
192 return runZoned(fn, zoneValues: { | 207 return runZoned(fn, zoneValues: { |
193 _closableKey: false | 208 _closableKey: false |
194 }); | 209 }); |
195 } | 210 } |
196 | 211 |
197 /// Notifies the invoker that progress is being made. | 212 /// Notifies the invoker that progress is being made. |
198 /// | 213 /// |
199 /// Each heartbeat resets the timeout timer. This helps ensure that | 214 /// Each heartbeat resets the timeout timer. This helps ensure that |
200 /// long-running tests that still make progress don't time out. | 215 /// long-running tests that still make progress don't time out. |
201 void heartbeat() { | 216 void heartbeat() { |
202 if (liveTest.isComplete) return; | 217 if (liveTest.isComplete) return; |
203 if (_timeoutTimer != null) _timeoutTimer.cancel(); | 218 if (_timeoutTimer != null) _timeoutTimer.cancel(); |
204 | 219 |
205 var timeout = liveTest.test.metadata.timeout | 220 var timeout = liveTest.test.metadata.timeout |
206 .apply(new Duration(seconds: 30)); | 221 .apply(new Duration(seconds: 30)); |
207 if (timeout == null) return; | 222 if (timeout == null) return; |
208 _timeoutTimer = _invokerZone.createTimer(timeout, | 223 _timeoutTimer = _invokerZone.createTimer(timeout, () { |
209 Zone.current.bindCallback(() { | 224 _outstandingCallbackZones.last.run(() { |
210 if (liveTest.isComplete) return; | 225 if (liveTest.isComplete) return; |
211 _handleError( | 226 _handleError( |
212 new TimeoutException( | 227 new TimeoutException( |
213 "Test timed out after ${niceDuration(timeout)}.", timeout)); | 228 "Test timed out after ${niceDuration(timeout)}.", timeout)); |
214 })); | 229 }); |
| 230 }); |
215 } | 231 } |
216 | 232 |
217 /// Notifies the invoker of an asynchronous error. | 233 /// Notifies the invoker of an asynchronous error. |
218 void _handleError(error, [StackTrace stackTrace]) { | 234 void _handleError(error, [StackTrace stackTrace]) { |
219 if (stackTrace == null) stackTrace = new Chain.current(); | 235 if (stackTrace == null) stackTrace = new Chain.current(); |
220 | 236 |
221 var afterSuccess = liveTest.isComplete && | 237 var afterSuccess = liveTest.isComplete && |
222 liveTest.state.result == Result.success; | 238 liveTest.state.result == Result.success; |
223 | 239 |
224 if (error is! TestFailure) { | 240 if (error is! TestFailure) { |
(...skipping 19 matching lines...) Expand all Loading... |
244 void _onRun() { | 260 void _onRun() { |
245 _controller.setState(const State(Status.running, Result.success)); | 261 _controller.setState(const State(Status.running, Result.success)); |
246 | 262 |
247 var outstandingCallbacksForBody = new OutstandingCallbackCounter(); | 263 var outstandingCallbacksForBody = new OutstandingCallbackCounter(); |
248 | 264 |
249 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in two | 265 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in two |
250 // stable versions. | 266 // stable versions. |
251 Chain.capture(() { | 267 Chain.capture(() { |
252 runZonedWithValues(() { | 268 runZonedWithValues(() { |
253 _invokerZone = Zone.current; | 269 _invokerZone = Zone.current; |
254 | 270 _outstandingCallbackZones.add(Zone.current); |
255 heartbeat(); | |
256 | 271 |
257 // Run the test asynchronously so that the "running" state change has | 272 // Run the test asynchronously so that the "running" state change has |
258 // a chance to hit its event handler(s) before the test produces an | 273 // a chance to hit its event handler(s) before the test produces an |
259 // error. If an error is emitted before the first state change is | 274 // error. If an error is emitted before the first state change is |
260 // handled, we can end up with [onError] callbacks firing before the | 275 // handled, we can end up with [onError] callbacks firing before the |
261 // corresponding [onStateChange], which violates the timing | 276 // corresponding [onStateChange], which violates the timing |
262 // guarantees. | 277 // guarantees. |
263 new Future(_test._body) | 278 new Future(_test._body) |
264 .then((_) => removeOutstandingCallback()); | 279 .then((_) => removeOutstandingCallback()); |
265 | 280 |
(...skipping 12 matching lines...) Expand all Loading... |
278 // outstanding callback counters at once. | 293 // outstanding callback counters at once. |
279 _counterKey: outstandingCallbacksForBody, | 294 _counterKey: outstandingCallbacksForBody, |
280 _closableKey: true | 295 _closableKey: true |
281 }, | 296 }, |
282 zoneSpecification: new ZoneSpecification( | 297 zoneSpecification: new ZoneSpecification( |
283 print: (self, parent, zone, line) => _controller.print(line)), | 298 print: (self, parent, zone, line) => _controller.print(line)), |
284 onError: _handleError); | 299 onError: _handleError); |
285 }); | 300 }); |
286 } | 301 } |
287 } | 302 } |
OLD | NEW |