Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(83)

Side by Side Diff: lib/src/backend/invoker.dart

Issue 1156493010: Add a heartbeat for the test timeout. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « CHANGELOG.md ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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 }
OLDNEW
« no previous file with comments | « CHANGELOG.md ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698