Chromium Code Reviews| 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 |