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

Side by Side Diff: mojo/public/dart/third_party/test/lib/src/backend/invoker.dart

Issue 1346773002: Stop running pub get at gclient sync time and fix build bugs (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 3 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
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library test.backend.invoker;
6
7 import 'dart:async';
8
9 import 'package:stack_trace/stack_trace.dart';
10
11 import '../frontend/expect.dart';
12 import '../utils.dart';
13 import 'closed_exception.dart';
14 import 'live_test.dart';
15 import 'live_test_controller.dart';
16 import 'metadata.dart';
17 import 'outstanding_callback_counter.dart';
18 import 'state.dart';
19 import 'suite.dart';
20 import 'test.dart';
21
22 /// A test in this isolate.
23 class LocalTest implements Test {
24 final String name;
25 final Metadata metadata;
26
27 /// The test body.
28 final AsyncFunction _body;
29
30 /// The callback used to clean up after the test.
31 ///
32 /// This is separated out from [_body] because it needs to run once the test's
33 /// asynchronous computation has finished, even if that's different from the
34 /// completion of the main body of the test.
35 final AsyncFunction _tearDown;
36
37 LocalTest(this.name, this.metadata, body(), {tearDown()})
38 : _body = body,
39 _tearDown = tearDown;
40
41 /// Loads a single runnable instance of this test.
42 LiveTest load(Suite suite) {
43 var invoker = new Invoker._(suite, this);
44 return invoker.liveTest;
45 }
46
47 Test change({String name, Metadata metadata}) {
48 if (name == name && metadata == this.metadata) return this;
49 if (name == null) name = this.name;
50 if (metadata == null) metadata = this.metadata;
51 return new LocalTest(name, metadata, _body, tearDown: _tearDown);
52 }
53 }
54
55 /// The class responsible for managing the lifecycle of a single local test.
56 ///
57 /// The current invoker is accessible within the zone scope of the running test
58 /// using [Invoker.current]. It's used to track asynchronous callbacks and
59 /// report asynchronous errors.
60 class Invoker {
61 /// The live test being driven by the invoker.
62 ///
63 /// This provides a view into the state of the test being executed.
64 LiveTest get liveTest => _controller.liveTest;
65 LiveTestController _controller;
66
67 /// Whether the test has been closed.
68 ///
69 /// Once the test is closed, [expect] and [expectAsync] will throw
70 /// [ClosedException]s whenever accessed to help the test stop executing as
71 /// soon as possible.
72 bool get closed => _onCloseCompleter.isCompleted;
73
74 /// A future that completes once the test has been closed.
75 Future get onClose => _onCloseCompleter.future;
76 final _onCloseCompleter = new Completer();
77
78 /// The test being run.
79 LocalTest get _test => liveTest.test as LocalTest;
80
81 /// The test metadata merged with the suite metadata.
82 final Metadata metadata;
83
84 /// The outstanding callback counter for the current zone.
85 OutstandingCallbackCounter get _outstandingCallbacks {
86 var counter = Zone.current[this];
87 if (counter != null) return counter;
88 throw new StateError("Can't add or remove outstanding callbacks outside "
89 "of a test body.");
90 }
91
92 /// The current invoker, or `null` if none is defined.
93 ///
94 /// An invoker is only set within the zone scope of a running test.
95 static Invoker get current {
96 // TODO(nweiz): Use a private symbol when dart2js supports it (issue 17526).
97 return Zone.current[#test.invoker];
98 }
99
100 /// The zone that the top level of [_test.body] is running in.
101 ///
102 /// Tracking this ensures that [_timeoutTimer] isn't created in a
103 /// timer-mocking zone created by the test.
104 Zone _invokerZone;
105
106 /// The timer for tracking timeouts.
107 ///
108 /// This will be `null` until the test starts running.
109 Timer _timeoutTimer;
110
111 Invoker._(Suite suite, LocalTest test)
112 : metadata = suite.metadata.merge(test.metadata) {
113 _controller = new LiveTestController(
114 suite, test, _onRun, _onCloseCompleter.complete);
115 }
116
117 /// Tells the invoker that there's a callback running that it should wait for
118 /// before considering the test successful.
119 ///
120 /// Each call to [addOutstandingCallback] should be followed by a call to
121 /// [removeOutstandingCallback] once the callbak is no longer running. Note
122 /// that only successful tests wait for outstanding callbacks; as soon as a
123 /// test experiences an error, any further calls to [addOutstandingCallback]
124 /// or [removeOutstandingCallback] will do nothing.
125 ///
126 /// Throws a [ClosedException] if this test has been closed.
127 void addOutstandingCallback() {
128 if (closed) throw new ClosedException();
129 _outstandingCallbacks.addOutstandingCallback();
130 }
131
132 /// Tells the invoker that a callback declared with [addOutstandingCallback]
133 /// is no longer running.
134 void removeOutstandingCallback() {
135 heartbeat();
136 _outstandingCallbacks.removeOutstandingCallback();
137 }
138
139 /// Removes all outstanding callbacks, for example when an error occurs.
140 ///
141 /// Future calls to [addOutstandingCallback] and [removeOutstandingCallback]
142 /// will be ignored.
143 void removeAllOutstandingCallbacks() =>
144 _outstandingCallbacks.removeAllOutstandingCallbacks();
145
146 /// Runs [fn] and returns once all (registered) outstanding callbacks it
147 /// transitively invokes have completed.
148 ///
149 /// If [fn] itself returns a future, this will automatically wait until that
150 /// future completes as well.
151 ///
152 /// Note that outstanding callbacks registered within [fn] will *not* be
153 /// registered as outstanding callback outside of [fn].
154 Future waitForOutstandingCallbacks(fn()) {
155 heartbeat();
156
157 var counter = new OutstandingCallbackCounter();
158 runZoned(() {
159 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in
160 // two stable versions.
161 new Future.sync(fn).then((_) => counter.removeOutstandingCallback());
162 }, zoneValues: {
163 // Use the invoker as a key so that multiple invokers can have different
164 // outstanding callback counters at once.
165 this: counter
166 });
167
168 return counter.noOutstandingCallbacks;
169 }
170
171 /// Notifies the invoker that progress is being made.
172 ///
173 /// Each heartbeat resets the timeout timer. This helps ensure that
174 /// long-running tests that still make progress don't time out.
175 void heartbeat() {
176 if (liveTest.isComplete) return;
177 if (_timeoutTimer != null) _timeoutTimer.cancel();
178
179 var timeout = metadata.timeout.apply(new Duration(seconds: 30));
180 if (timeout == null) return;
181 _timeoutTimer = _invokerZone.createTimer(timeout,
182 Zone.current.bindCallback(() {
183 if (liveTest.isComplete) return;
184 _handleError(
185 new TimeoutException(
186 "Test timed out after ${niceDuration(timeout)}.", timeout));
187 }));
188 }
189
190 /// Notifies the invoker of an asynchronous error.
191 void _handleError(error, [StackTrace stackTrace]) {
192 if (stackTrace == null) stackTrace = new Chain.current();
193
194 var afterSuccess = liveTest.isComplete &&
195 liveTest.state.result == Result.success;
196
197 if (error is! TestFailure) {
198 _controller.setState(const State(Status.complete, Result.error));
199 } else if (liveTest.state.result != Result.error) {
200 _controller.setState(const State(Status.complete, Result.failure));
201 }
202
203 _controller.addError(error, stackTrace);
204 removeAllOutstandingCallbacks();
205
206 // If a test was marked as success but then had an error, that indicates
207 // that it was poorly-written and could be flaky.
208 if (!afterSuccess) return;
209 _handleError(
210 "This test failed after it had already completed. Make sure to use "
211 "[expectAsync]\n"
212 "or the [completes] matcher when testing async code.",
213 stackTrace);
214 }
215
216 /// The method that's run when the test is started.
217 void _onRun() {
218 _controller.setState(const State(Status.running, Result.success));
219
220 var outstandingCallbacksForBody = new OutstandingCallbackCounter();
221
222 // TODO(nweiz): Use async/await here once issue 23497 has been fixed in two
223 // stable versions.
224 Chain.capture(() {
225 runZonedWithValues(() {
226 _invokerZone = Zone.current;
227
228 heartbeat();
229
230 // Run the test asynchronously so that the "running" state change has
231 // a chance to hit its event handler(s) before the test produces an
232 // error. If an error is emitted before the first state change is
233 // handled, we can end up with [onError] callbacks firing before the
234 // corresponding [onStateChange], which violates the timing
235 // guarantees.
236 new Future(_test._body)
237 .then((_) => removeOutstandingCallback());
238
239 _outstandingCallbacks.noOutstandingCallbacks.then((_) {
240 if (_test._tearDown == null) return null;
241
242 // Reset the outstanding callback counter to wait for callbacks from
243 // the test's `tearDown` to complete.
244 return waitForOutstandingCallbacks(() =>
245 runZoned(_test._tearDown, onError: _handleError));
246 }).then((_) {
247 if (_timeoutTimer != null) _timeoutTimer.cancel();
248 _controller.setState(
249 new State(Status.complete, liveTest.state.result));
250
251 // Use [Timer.run] here to avoid starving the DOM or other
252 // non-microtask events.
253 Timer.run(_controller.completer.complete);
254 });
255 }, zoneValues: {
256 #test.invoker: this,
257 // Use the invoker as a key so that multiple invokers can have different
258 // outstanding callback counters at once.
259 this: outstandingCallbacksForBody
260 },
261 zoneSpecification: new ZoneSpecification(
262 print: (self, parent, zone, line) => _controller.print(line)),
263 onError: _handleError);
264 });
265 }
266 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698