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

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

Issue 1116443002: Support future matchers and expectAsync in tearDowns. (Closed) Base URL: git@github.com:dart-lang/unittest.git@master
Patch Set: Created 5 years, 7 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
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
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 'closed_exception.dart';
14 import 'live_test.dart'; 14 import 'live_test.dart';
15 import 'live_test_controller.dart'; 15 import 'live_test_controller.dart';
16 import 'metadata.dart'; 16 import 'metadata.dart';
17 import 'outstanding_callback_counter.dart';
17 import 'state.dart'; 18 import 'state.dart';
18 import 'suite.dart'; 19 import 'suite.dart';
19 import 'test.dart'; 20 import 'test.dart';
20 21
21 /// A test in this isolate. 22 /// A test in this isolate.
22 class LocalTest implements Test { 23 class LocalTest implements Test {
23 final String name; 24 final String name;
24 final Metadata metadata; 25 final Metadata metadata;
25 26
26 /// The test body. 27 /// The test body.
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
69 /// soon as possible. 70 /// soon as possible.
70 bool get closed => _closed; 71 bool get closed => _closed;
71 bool _closed = false; 72 bool _closed = false;
72 73
73 /// The test being run. 74 /// The test being run.
74 LocalTest get _test => liveTest.test as LocalTest; 75 LocalTest get _test => liveTest.test as LocalTest;
75 76
76 /// The test metadata merged with the suite metadata. 77 /// The test metadata merged with the suite metadata.
77 final Metadata metadata; 78 final Metadata metadata;
78 79
79 /// Note that this is meaningless once [_onCompleteCompleter] is complete. 80 /// The outstanding callback counter for the current zone.
80 var _outstandingCallbacks = 0; 81 OutstandingCallbackCounter get _outstandingCallbacks {
81 82 var counter = Zone.current[this];
82 /// The completer to complete once the test body finishes. 83 if (counter != null) return counter;
83 /// 84 throw new StateError("Can't add or remove outstanding callbacks outside "
84 /// This is distinct from [_controller.completer] because a tear-down may need 85 "of a test body.");
85 /// to run before the test is truly finished. 86 }
86 final _completer = new Completer();
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 Invoker._(Suite suite, LocalTest test) 96 Invoker._(Suite suite, LocalTest test)
97 : metadata = suite.metadata.merge(test.metadata) { 97 : metadata = suite.metadata.merge(test.metadata) {
98 _controller = new LiveTestController(suite, test, _onRun, () { 98 _controller = new LiveTestController(suite, test, _onRun, () {
99 _closed = true; 99 _closed = true;
100 }); 100 });
101 } 101 }
102 102
103 /// Tells the invoker that there's a callback running that it should wait for 103 /// Tells the invoker that there's a callback running that it should wait for
104 /// before considering the test successful. 104 /// before considering the test successful.
105 /// 105 ///
106 /// Each call to [addOutstandingCallback] should be followed by a call to 106 /// Each call to [addOutstandingCallback] should be followed by a call to
107 /// [removeOutstandingCallback] once the callbak is no longer running. Note 107 /// [removeOutstandingCallback] once the callbak is no longer running. Note
108 /// that only successful tests wait for outstanding callbacks; as soon as a 108 /// that only successful tests wait for outstanding callbacks; as soon as a
109 /// test experiences an error, any further calls to [addOutstandingCallback] 109 /// test experiences an error, any further calls to [addOutstandingCallback]
110 /// or [removeOutstandingCallback] will do nothing. 110 /// or [removeOutstandingCallback] will do nothing.
111 /// 111 ///
112 /// Throws a [ClosedException] if this test has been closed. 112 /// Throws a [ClosedException] if this test has been closed.
113 void addOutstandingCallback() { 113 void addOutstandingCallback() {
114 if (closed) throw new ClosedException(); 114 if (closed) throw new ClosedException();
115 _outstandingCallbacks++; 115 _outstandingCallbacks.addOutstandingCallback();
116 } 116 }
117 117
118 /// Tells the invoker that a callback declared with [addOutstandingCallback] 118 /// Tells the invoker that a callback declared with [addOutstandingCallback]
119 /// is no longer running. 119 /// is no longer running.
120 void removeOutstandingCallback() { 120 void removeOutstandingCallback() =>
121 _outstandingCallbacks--; 121 _outstandingCallbacks.removeOutstandingCallback();
122
123 if (_outstandingCallbacks != 0) return;
124 if (_completer.isCompleted) return;
125
126 // The test must be passing if we get here, because if there were an error
127 // the completer would already be completed.
128 assert(liveTest.state.result == Result.success);
129 _completer.complete();
130 }
131 122
132 /// Notifies the invoker of an asynchronous error. 123 /// Notifies the invoker of an asynchronous error.
133 /// 124 ///
134 /// Note that calling this explicitly is rarely necessary, since any 125 /// Note that calling this explicitly is rarely necessary, since any
135 /// otherwise-uncaught errors will be forwarded to the invoker anyway. 126 /// otherwise-uncaught errors will be forwarded to the invoker anyway.
136 void handleError(error, [StackTrace stackTrace]) { 127 void handleError(error, [StackTrace stackTrace]) {
137 if (stackTrace == null) stackTrace = new Chain.current(); 128 if (stackTrace == null) stackTrace = new Chain.current();
138 129
139 var afterSuccess = liveTest.isComplete && 130 var afterSuccess = liveTest.isComplete &&
140 liveTest.state.result == Result.success; 131 liveTest.state.result == Result.success;
141 132
142 if (error is! TestFailure) { 133 if (error is! TestFailure) {
143 _controller.setState(const State(Status.complete, Result.error)); 134 _controller.setState(const State(Status.complete, Result.error));
144 } else if (liveTest.state.result != Result.error) { 135 } else if (liveTest.state.result != Result.error) {
145 _controller.setState(const State(Status.complete, Result.failure)); 136 _controller.setState(const State(Status.complete, Result.failure));
146 } 137 }
147 138
148 _controller.addError(error, stackTrace); 139 _controller.addError(error, stackTrace);
149 140 _outstandingCallbacks.removeAllOutstandingCallbacks();
150 if (!_completer.isCompleted) _completer.complete();
151 141
152 // If a test was marked as success but then had an error, that indicates 142 // If a test was marked as success but then had an error, that indicates
153 // that it was poorly-written and could be flaky. 143 // that it was poorly-written and could be flaky.
154 if (!afterSuccess) return; 144 if (!afterSuccess) return;
155 handleError( 145 handleError(
156 "This test failed after it had already completed. Make sure to use " 146 "This test failed after it had already completed. Make sure to use "
157 "[expectAsync]\n" 147 "[expectAsync]\n"
158 "or the [completes] matcher when testing async code.", 148 "or the [completes] matcher when testing async code.",
159 stackTrace); 149 stackTrace);
160 } 150 }
161 151
162 /// The method that's run when the test is started. 152 /// The method that's run when the test is started.
163 void _onRun() { 153 void _onRun() {
164 _controller.setState(const State(Status.running, Result.success)); 154 _controller.setState(const State(Status.running, Result.success));
165 155
156 var outstandingCallbacksForBody = new OutstandingCallbackCounter();
157
166 Chain.capture(() { 158 Chain.capture(() {
167 runZoned(() { 159 runZonedWithValues(() {
168 // TODO(nweiz): Make the timeout configurable. 160 // TODO(nweiz): Reset this timer whenever the user's code interacts
169 // TODO(nweiz): Reset this timer whenever the user's code interacts with 161 // with the library.
170 // the library.
171 var timeout = metadata.timeout.apply(new Duration(seconds: 30)); 162 var timeout = metadata.timeout.apply(new Duration(seconds: 30));
172 var timer = new Timer(timeout, () { 163 var timer = new Timer(timeout, () {
173 if (liveTest.isComplete) return; 164 if (liveTest.isComplete) return;
174 handleError( 165 handleError(
175 new TimeoutException( 166 new TimeoutException(
176 "Test timed out after ${niceDuration(timeout)}.", timeout)); 167 "Test timed out after ${niceDuration(timeout)}.", timeout));
177 }); 168 });
178 169
179 addOutstandingCallback(); 170 // Run the test asynchronously so that the "running" state change has
180 171 // a chance to hit its event handler(s) before the test produces an
181 // Run the test asynchronously so that the "running" state change has a 172 // error. If an error is emitted before the first state change is
182 // chance to hit its event handler(s) before the test produces an error. 173 // handled, we can end up with [onError] callbacks firing before the
183 // If an error is emitted before the first state change is handled, we 174 // corresponding [onStateChange], which violates the timing
184 // can end up with [onError] callbacks firing before the corresponding 175 // guarantees.
185 // [onStateChange], which violates the timing guarantees.
186 new Future(_test._body) 176 new Future(_test._body)
187 .then((_) => removeOutstandingCallback()); 177 .then((_) => removeOutstandingCallback());
188 178
189 _completer.future.then((_) { 179 _outstandingCallbacks.noOutstandingCallbacks.then((_) {
190 if (_test._tearDown == null) return null; 180 if (_test._tearDown == null) return null;
191 return new Future.sync(_test._tearDown); 181
192 }).catchError(Zone.current.handleUncaughtError).then((_) { 182 // Reset the outstanding callback counter to wait for callbacks from
183 // the test's `tearDown` to complete.
184 var outstandingCallbacksForTearDown = new OutstandingCallbackCounter() ;
185 runZonedWithValues(() {
186 new Future.sync(_test._tearDown)
187 .then((_) => removeOutstandingCallback());
188 }, onError: handleError, zoneValues: {
189 this: outstandingCallbacksForTearDown
190 });
191
192 return outstandingCallbacksForTearDown.noOutstandingCallbacks;
193 }).then((_) {
193 timer.cancel(); 194 timer.cancel();
194 _controller.setState( 195 _controller.setState(
195 new State(Status.complete, liveTest.state.result)); 196 new State(Status.complete, liveTest.state.result));
196 197
197 // Use [Timer.run] here to avoid starving the DOM or other 198 // Use [Timer.run] here to avoid starving the DOM or other
198 // non-microtask events. 199 // non-microtask events.
199 Timer.run(_controller.completer.complete); 200 Timer.run(_controller.completer.complete);
200 }); 201 });
202 }, zoneValues: {
203 #test.invoker: this,
204 // Use the invoker as a key so that multiple invokers can have different
205 // outstanding callback counters at once.
206 this: outstandingCallbacksForBody
201 }, 207 },
202 zoneSpecification: new ZoneSpecification( 208 zoneSpecification: new ZoneSpecification(
203 print: (self, parent, zone, line) => _controller.print(line)), 209 print: (self, parent, zone, line) => _controller.print(line)),
204 zoneValues: {#test.invoker: this},
205 onError: handleError); 210 onError: handleError);
206 }); 211 });
207 } 212 }
208 } 213 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698