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 |
11 import '../frontend/expect.dart'; | 11 import '../frontend/expect.dart'; |
12 import '../utils.dart'; | 12 import '../utils.dart'; |
| 13 import 'closed_exception.dart'; |
13 import 'live_test.dart'; | 14 import 'live_test.dart'; |
14 import 'live_test_controller.dart'; | 15 import 'live_test_controller.dart'; |
15 import 'metadata.dart'; | 16 import 'metadata.dart'; |
16 import 'state.dart'; | 17 import 'state.dart'; |
17 import 'suite.dart'; | 18 import 'suite.dart'; |
18 import 'test.dart'; | 19 import 'test.dart'; |
19 | 20 |
20 /// A test in this isolate. | 21 /// A test in this isolate. |
21 class LocalTest implements Test { | 22 class LocalTest implements Test { |
22 final String name; | 23 final String name; |
(...skipping 25 matching lines...) Expand all Loading... |
48 /// The current invoker is accessible within the zone scope of the running test | 49 /// The current invoker is accessible within the zone scope of the running test |
49 /// using [Invoker.current]. It's used to track asynchronous callbacks and | 50 /// using [Invoker.current]. It's used to track asynchronous callbacks and |
50 /// report asynchronous errors. | 51 /// report asynchronous errors. |
51 class Invoker { | 52 class Invoker { |
52 /// The live test being driven by the invoker. | 53 /// The live test being driven by the invoker. |
53 /// | 54 /// |
54 /// This provides a view into the state of the test being executed. | 55 /// This provides a view into the state of the test being executed. |
55 LiveTest get liveTest => _controller.liveTest; | 56 LiveTest get liveTest => _controller.liveTest; |
56 LiveTestController _controller; | 57 LiveTestController _controller; |
57 | 58 |
| 59 /// Whether the test has been closed. |
| 60 /// |
| 61 /// Once the test is closed, [expect] and [expectAsync] will throw |
| 62 /// [ClosedException]s whenever accessed to help the test stop executing as |
| 63 /// soon as possible. |
| 64 bool get closed => _closed; |
| 65 bool _closed = false; |
| 66 |
58 /// The test being run. | 67 /// The test being run. |
59 LocalTest get _test => liveTest.test as LocalTest; | 68 LocalTest get _test => liveTest.test as LocalTest; |
60 | 69 |
61 /// Note that this is meaningless once [_onCompleteCompleter] is complete. | 70 /// Note that this is meaningless once [_onCompleteCompleter] is complete. |
62 var _outstandingCallbacks = 0; | 71 var _outstandingCallbacks = 0; |
63 | 72 |
64 /// The completer to complete once the test body finishes. | 73 /// The completer to complete once the test body finishes. |
65 /// | 74 /// |
66 /// This is distinct from [_controller.completer] because a tear-down may need | 75 /// This is distinct from [_controller.completer] because a tear-down may need |
67 /// to run before the test is truly finished. | 76 /// to run before the test is truly finished. |
68 final _completer = new Completer(); | 77 final _completer = new Completer(); |
69 | 78 |
70 /// The current invoker, or `null` if none is defined. | 79 /// The current invoker, or `null` if none is defined. |
71 /// | 80 /// |
72 /// An invoker is only set within the zone scope of a running test. | 81 /// An invoker is only set within the zone scope of a running test. |
73 static Invoker get current { | 82 static Invoker get current { |
74 // TODO(nweiz): Use a private symbol when dart2js supports it (issue 17526). | 83 // TODO(nweiz): Use a private symbol when dart2js supports it (issue 17526). |
75 return Zone.current[#test.invoker]; | 84 return Zone.current[#test.invoker]; |
76 } | 85 } |
77 | 86 |
78 Invoker._(Suite suite, LocalTest test) { | 87 Invoker._(Suite suite, LocalTest test) { |
79 _controller = new LiveTestController(suite, test, _onRun); | 88 _controller = new LiveTestController(suite, test, _onRun, () { |
| 89 _closed = true; |
| 90 }); |
80 } | 91 } |
81 | 92 |
82 /// Tells the invoker that there's a callback running that it should wait for | 93 /// Tells the invoker that there's a callback running that it should wait for |
83 /// before considering the test successful. | 94 /// before considering the test successful. |
84 /// | 95 /// |
85 /// Each call to [addOutstandingCallback] should be followed by a call to | 96 /// Each call to [addOutstandingCallback] should be followed by a call to |
86 /// [removeOutstandingCallback] once the callbak is no longer running. Note | 97 /// [removeOutstandingCallback] once the callbak is no longer running. Note |
87 /// that only successful tests wait for outstanding callbacks; as soon as a | 98 /// that only successful tests wait for outstanding callbacks; as soon as a |
88 /// test experiences an error, any further calls to [addOutstandingCallback] | 99 /// test experiences an error, any further calls to [addOutstandingCallback] |
89 /// or [removeOutstandingCallback] will do nothing. | 100 /// or [removeOutstandingCallback] will do nothing. |
| 101 /// |
| 102 /// Throws a [ClosedException] if this test has been closed. |
90 void addOutstandingCallback() { | 103 void addOutstandingCallback() { |
| 104 if (closed) throw new ClosedException(); |
91 _outstandingCallbacks++; | 105 _outstandingCallbacks++; |
92 } | 106 } |
93 | 107 |
94 /// Tells the invoker that a callback declared with [addOutstandingCallback] | 108 /// Tells the invoker that a callback declared with [addOutstandingCallback] |
95 /// is no longer running. | 109 /// is no longer running. |
96 void removeOutstandingCallback() { | 110 void removeOutstandingCallback() { |
97 _outstandingCallbacks--; | 111 _outstandingCallbacks--; |
98 | 112 |
99 if (_outstandingCallbacks != 0) return; | 113 if (_outstandingCallbacks != 0) return; |
100 if (_completer.isCompleted) return; | 114 if (_completer.isCompleted) return; |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
155 addOutstandingCallback(); | 169 addOutstandingCallback(); |
156 | 170 |
157 // Run the test asynchronously so that the "running" state change has a | 171 // Run the test asynchronously so that the "running" state change has a |
158 // chance to hit its event handler(s) before the test produces an error. | 172 // chance to hit its event handler(s) before the test produces an error. |
159 // If an error is emitted before the first state change is handled, we | 173 // If an error is emitted before the first state change is handled, we |
160 // can end up with [onError] callbacks firing before the corresponding | 174 // can end up with [onError] callbacks firing before the corresponding |
161 // [onStateChange], which violates the timing guarantees. | 175 // [onStateChange], which violates the timing guarantees. |
162 new Future(_test._body) | 176 new Future(_test._body) |
163 .then((_) => removeOutstandingCallback()); | 177 .then((_) => removeOutstandingCallback()); |
164 | 178 |
165 // Explicitly handle an error here so that we can return the [Future]. | |
166 // If a [Future] returned from an error zone would throw an error | |
167 // through the zone boundary, it instead never completes, and we want to | |
168 // avoid that. | |
169 _completer.future.then((_) { | 179 _completer.future.then((_) { |
170 if (_test._tearDown == null) return null; | 180 if (_test._tearDown == null) return null; |
171 return new Future.sync(_test._tearDown); | 181 return new Future.sync(_test._tearDown); |
172 }).catchError(Zone.current.handleUncaughtError).then((_) { | 182 }).catchError(Zone.current.handleUncaughtError).then((_) { |
173 timer.cancel(); | 183 timer.cancel(); |
174 _controller.setState( | 184 _controller.setState( |
175 new State(Status.complete, liveTest.state.result)); | 185 new State(Status.complete, liveTest.state.result)); |
176 | 186 |
177 // Use [Timer.run] here to avoid starving the DOM or other | 187 // Use [Timer.run] here to avoid starving the DOM or other |
178 // non-microtask events. | 188 // non-microtask events. |
179 Timer.run(_controller.completer.complete); | 189 Timer.run(_controller.completer.complete); |
180 }); | 190 }); |
181 }, | 191 }, |
182 zoneSpecification: new ZoneSpecification( | 192 zoneSpecification: new ZoneSpecification( |
183 print: (self, parent, zone, line) => _controller.print(line)), | 193 print: (self, parent, zone, line) => _controller.print(line)), |
184 zoneValues: {#test.invoker: this}, | 194 zoneValues: {#test.invoker: this}, |
185 onError: handleError); | 195 onError: handleError); |
186 }); | 196 }); |
187 } | 197 } |
188 } | 198 } |
OLD | NEW |