@@ -10,6 +10,7 @@ import '../frontend/expect.dart';
1010import '../runner/load_suite.dart' ;
1111import '../utils.dart' ;
1212import 'closed_exception.dart' ;
13+ import 'declarer.dart' ;
1314import 'group.dart' ;
1415import 'live_test.dart' ;
1516import 'live_test_controller.dart' ;
@@ -28,21 +29,38 @@ class LocalTest extends Test {
2829 final Metadata metadata;
2930 final Trace trace;
3031
32+ /// Whether this is a test defined using `setUpAll()` or `tearDownAll()` .
33+ final bool isScaffoldAll;
34+
3135 /// The test body.
32- final AsyncFunction _body;
36+ final Function () _body;
37+
38+ /// Whether the test is run in its own error zone.
39+ final bool _guarded;
3340
34- LocalTest (this .name, this .metadata, body (), {this .trace}) : _body = body;
41+ /// Creates a new [LocalTest] .
42+ ///
43+ /// If [guarded] is `true` , the test is run in its own error zone, and any
44+ /// errors that escape that zone cause the test to fail. If it's `false` , it's
45+ /// the caller's responsiblity to invoke [LiveTest.run] in the context of a
46+ /// call to [Invoker.guard] .
47+ LocalTest (this .name, this .metadata, this ._body,
48+ {this .trace, bool guarded: true , this .isScaffoldAll: false })
49+ : _guarded = guarded;
50+
51+ LocalTest ._(this .name, this .metadata, this ._body, this .trace, this ._guarded,
52+ this .isScaffoldAll);
3553
3654 /// Loads a single runnable instance of this test.
3755 LiveTest load (Suite suite, {Iterable <Group > groups}) {
38- var invoker = new Invoker ._(suite, this , groups: groups);
56+ var invoker = new Invoker ._(suite, this , groups: groups, guarded : _guarded );
3957 return invoker.liveTest;
4058 }
4159
4260 Test forPlatform (TestPlatform platform, {OperatingSystem os}) {
4361 if (! metadata.testOn.evaluate (platform, os: os)) return null ;
44- return new LocalTest (name, metadata.forPlatform (platform, os: os), _body,
45- trace: trace );
62+ return new LocalTest ._ (name, metadata.forPlatform (platform, os: os), _body,
63+ trace, _guarded, isScaffoldAll );
4664 }
4765}
4866
@@ -58,6 +76,9 @@ class Invoker {
5876 LiveTest get liveTest => _controller.liveTest;
5977 LiveTestController _controller;
6078
79+ /// Whether to run this test in its own error zone.
80+ final bool _guarded;
81+
6182 /// Whether the test can be closed in the current zone.
6283 bool get _closable => Zone .current[_closableKey];
6384
@@ -118,6 +139,22 @@ class Invoker {
118139 return Zone .current[#test.invoker];
119140 }
120141
142+ /// Runs [callback] in a zone where unhandled errors from [LiveTest] s are
143+ /// caught and dispatched to the appropriate [Invoker] .
144+ static T guard <T >(T callback ()) =>
145+ runZoned (callback, zoneSpecification: new ZoneSpecification (
146+ // Use [handleUncaughtError] rather than [onError] so we can
147+ // capture [zone] and with it the outstanding callback counter for
148+ // the zone in which [error] was thrown.
149+ handleUncaughtError: (self, _, zone, error, stackTrace) {
150+ var invoker = zone[#test.invoker];
151+ if (invoker != null ) {
152+ self.parent.run (() => invoker._handleError (zone, error, stackTrace));
153+ } else {
154+ self.parent.handleUncaughtError (error, stackTrace);
155+ }
156+ }));
157+
121158 /// The zone that the top level of [_test.body] is running in.
122159 ///
123160 /// Tracking this ensures that [_timeoutTimer] isn't created in a
@@ -135,7 +172,9 @@ class Invoker {
135172 /// Messages to print if and when this test fails.
136173 final _printsOnFailure = < String > [];
137174
138- Invoker ._(Suite suite, LocalTest test, {Iterable <Group > groups}) {
175+ Invoker ._(Suite suite, LocalTest test,
176+ {Iterable <Group > groups, bool guarded: true })
177+ : _guarded = guarded {
139178 _controller = new LiveTestController (
140179 suite, test, _onRun, _onCloseCompleter.complete,
141180 groups: groups);
@@ -147,7 +186,12 @@ class Invoker {
147186 /// run in the reverse of the order they're declared.
148187 void addTearDown (callback ()) {
149188 if (closed) throw new ClosedException ();
150- _tearDowns.add (callback);
189+
190+ if (_test.isScaffoldAll) {
191+ Declarer .current.addTearDownAll (callback);
192+ } else {
193+ _tearDowns.add (callback);
194+ }
151195 }
152196
153197 /// Tells the invoker that there's a callback running that it should wait for
@@ -282,7 +326,15 @@ class Invoker {
282326 void _handleError (Zone zone, error, [StackTrace stackTrace]) {
283327 // Ignore errors propagated from previous test runs
284328 if (_runCount != zone[#runCount]) return ;
285- if (stackTrace == null ) stackTrace = new Chain .current ();
329+
330+ // Get the chain information from the zone in which the error was thrown.
331+ zone.run (() {
332+ if (stackTrace == null ) {
333+ stackTrace = new Chain .current ();
334+ } else {
335+ stackTrace = new Chain .forTrace (stackTrace);
336+ }
337+ });
286338
287339 // Store these here because they'll change when we set the state below.
288340 var shouldBeDone = liveTest.state.shouldBeDone;
@@ -334,60 +386,68 @@ class Invoker {
334386
335387 _runCount++ ;
336388 Chain .capture (() {
337- runZoned (() async {
338- _invokerZone = Zone .current;
339- _outstandingCallbackZones.add (Zone .current);
340-
341- // Run the test asynchronously so that the "running" state change has
342- // a chance to hit its event handler(s) before the test produces an
343- // error. If an error is emitted before the first state change is
344- // handled, we can end up with [onError] callbacks firing before the
345- // corresponding [onStateChkange], which violates the timing
346- // guarantees.
347- //
348- // Using [new Future] also avoids starving the DOM or other
349- // microtask-level events.
350- new Future (() async {
351- await _test._body ();
352- await unclosable (_runTearDowns);
353- removeOutstandingCallback ();
354- });
355-
356- await _outstandingCallbacks.noOutstandingCallbacks;
357- if (_timeoutTimer != null ) _timeoutTimer.cancel ();
358-
359- if (liveTest.state.result != Result .success &&
360- _runCount < liveTest.test.metadata.retry + 1 ) {
389+ _guardIfGuarded (() {
390+ runZoned (() async {
391+ _invokerZone = Zone .current;
392+ _outstandingCallbackZones.add (Zone .current);
393+
394+ // Run the test asynchronously so that the "running" state change
395+ // has a chance to hit its event handler(s) before the test produces
396+ // an error. If an error is emitted before the first state change is
397+ // handled, we can end up with [onError] callbacks firing before the
398+ // corresponding [onStateChkange], which violates the timing
399+ // guarantees.
400+ //
401+ // Using [new Future] also avoids starving the DOM or other
402+ // microtask-level events.
403+ new Future (() async {
404+ await _test._body ();
405+ await unclosable (_runTearDowns);
406+ removeOutstandingCallback ();
407+ });
408+
409+ await _outstandingCallbacks.noOutstandingCallbacks;
410+ if (_timeoutTimer != null ) _timeoutTimer.cancel ();
411+
412+ if (liveTest.state.result != Result .success &&
413+ _runCount < liveTest.test.metadata.retry + 1 ) {
414+ _controller
415+ .message (new Message .print ("Retry: ${liveTest .test .name }" ));
416+ _onRun ();
417+ return ;
418+ }
419+
361420 _controller
362- .message (new Message .print ("Retry: ${liveTest .test .name }" ));
363- _onRun ();
364- return ;
365- }
421+ .setState (new State (Status .complete, liveTest.state.result));
422+
423+ _controller.completer.complete ();
424+ },
425+ zoneValues: {
426+ #test.invoker: this ,
427+ // Use the invoker as a key so that multiple invokers can have
428+ // different outstanding callback counters at once.
429+ _counterKey: outstandingCallbacksForBody,
430+ _closableKey: true ,
431+ #runCount: _runCount,
432+ },
433+ zoneSpecification: new ZoneSpecification (
434+ print: (_, __, ___, line) => _print (line)));
435+ });
436+ }, when : liveTest.test.metadata.chainStackTraces, errorZone: false );
437+ }
366438
367- _controller.setState (new State (Status .complete, liveTest.state.result));
368-
369- _controller.completer.complete ();
370- },
371- zoneValues: {
372- #test.invoker: this ,
373- // Use the invoker as a key so that multiple invokers can have different
374- // outstanding callback counters at once.
375- _counterKey: outstandingCallbacksForBody,
376- _closableKey: true ,
377- #runCount: _runCount
378- },
379- zoneSpecification: new ZoneSpecification (
380- print: (self, parent, zone, line) =>
381- _controller.message (new Message .print (line)),
382- // Use [handleUncaughtError] rather than [onError] so we can
383- // capture [zone] and with it the outstanding callback counter for
384- // the zone in which [error] was thrown.
385- handleUncaughtError: (self, _, zone, error, stackTrace) => self
386- .parent
387- .run (() => _handleError (zone, error, stackTrace))));
388- }, when : liveTest.test.metadata.chainStackTraces);
439+ /// Runs [callback] , in a [Invoker.guard] context if [_guarded] is `true` .
440+ void _guardIfGuarded (void callback ()) {
441+ if (_guarded) {
442+ Invoker .guard (callback);
443+ } else {
444+ callback ();
445+ }
389446 }
390447
448+ /// Prints [text] as a message to [_controller] .
449+ void _print (String text) => _controller.message (new Message .print (text));
450+
391451 /// Run [_tearDowns] in reverse order.
392452 Future _runTearDowns () async {
393453 while (_tearDowns.isNotEmpty) {
0 commit comments