Skip to content

Commit 4180dc1

Browse files
committed
Add an addTearDown() function.
See #109 Closes #93
1 parent e80252f commit 4180dc1

File tree

6 files changed

+399
-41
lines changed

6 files changed

+399
-41
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
## 0.12.18
22

3+
* Add an `addTearDown()` function, which allows tests to register additional
4+
tear-down callbacks as they're running.
5+
36
* Add the `spawnHybridUri()` and `spawnHybridCode()` functions, which allow
47
browser tests to run code on the VM.
58

lib/src/backend/declarer.dart

+14-38
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,24 @@ class Declarer {
105105
tags: tags));
106106

107107
_entries.add(new LocalTest(_prefix(name), metadata, () async {
108-
// TODO(nweiz): It might be useful to throw an error here if a test starts
109-
// running while other tests from the same declarer are also running,
110-
// since they might share closurized state.
108+
var parents = [];
109+
for (var declarer = this; declarer != null; declarer = declarer._parent) {
110+
parents.add(declarer);
111+
}
112+
113+
// Register all tear-down functions in all declarers. Iterate through
114+
// parents outside-in so that the Invoker gets the functions in the order
115+
// they were declared in source.
116+
for (var declarer in parents.reversed) {
117+
for (var tearDown in declarer._tearDowns) {
118+
Invoker.current.addTearDown(tearDown);
119+
}
120+
}
111121

112122
await Invoker.current.waitForOutstandingCallbacks(() async {
113123
await _runSetUps();
114124
await body();
115125
});
116-
await _runTearDowns();
117126
}, trace: _collectTraces ? new Trace.current(2) : null));
118127
}
119128

@@ -197,23 +206,6 @@ class Declarer {
197206
await Future.forEach(_setUps, (setUp) => setUp());
198207
}
199208

200-
/// Run the tear-up functions for this and any parent groups.
201-
///
202-
/// If no set-up functions are declared, this returns a [Future] that
203-
/// completes immediately.
204-
///
205-
/// This should only be called within a test.
206-
Future _runTearDowns() {
207-
return Invoker.current.unclosable(() {
208-
var tearDowns = [];
209-
for (var declarer = this; declarer != null; declarer = declarer._parent) {
210-
tearDowns.addAll(declarer._tearDowns.reversed);
211-
}
212-
213-
return Future.forEach(tearDowns, _errorsDontStopTest);
214-
});
215-
}
216-
217209
/// Returns a [Test] that runs the callbacks in [_setUpAll].
218210
Test get _setUpAll {
219211
if (_setUpAlls.isEmpty) return null;
@@ -229,24 +221,8 @@ class Declarer {
229221

230222
return new LocalTest(_prefix("(tearDownAll)"), _metadata, () {
231223
return Invoker.current.unclosable(() {
232-
return Future.forEach(_tearDownAlls.reversed, _errorsDontStopTest);
224+
return Future.forEach(_tearDownAlls.reversed, errorsDontStopTest);
233225
});
234226
}, trace: _tearDownAllTrace);
235227
}
236-
237-
/// Runs [body] with special error-handling behavior.
238-
///
239-
/// Errors emitted [body] will still cause the current test to fail, but they
240-
/// won't cause it to *stop*. In particular, they won't remove any outstanding
241-
/// callbacks registered outside of [body].
242-
Future _errorsDontStopTest(body()) {
243-
var completer = new Completer();
244-
245-
Invoker.current.addOutstandingCallback();
246-
Invoker.current.waitForOutstandingCallbacks(() {
247-
new Future.sync(body).whenComplete(completer.complete);
248-
}).then((_) => Invoker.current.removeOutstandingCallback());
249-
250-
return completer.future;
251-
}
252228
}

lib/src/backend/invoker.dart

+19-3
Original file line numberDiff line numberDiff line change
@@ -126,11 +126,23 @@ class Invoker {
126126
/// This will be `null` until the test starts running.
127127
Timer _timeoutTimer;
128128

129+
/// The tear-down functions to run when this test finishes.
130+
final _tearDowns = new List<AsyncFunction>();
131+
129132
Invoker._(Suite suite, LocalTest test, {Iterable<Group> groups}) {
130133
_controller = new LiveTestController(
131134
suite, test, _onRun, _onCloseCompleter.complete, groups: groups);
132135
}
133136

137+
/// Runs [callback] after this test completes.
138+
///
139+
/// The [callback] may return a [Future]. Like all tear-downs, callbacks are
140+
/// run in the reverse of the order they're declared.
141+
void addTearDown(callback()) {
142+
if (closed) throw new ClosedException();
143+
_tearDowns.add(callback);
144+
}
145+
134146
/// Tells the invoker that there's a callback running that it should wait for
135147
/// before considering the test successful.
136148
///
@@ -301,13 +313,17 @@ class Invoker {
301313
// a chance to hit its event handler(s) before the test produces an
302314
// error. If an error is emitted before the first state change is
303315
// handled, we can end up with [onError] callbacks firing before the
304-
// corresponding [onStateChange], which violates the timing
316+
// corresponding [onStateChkange], which violates the timing
305317
// guarantees.
306318
//
307319
// Using [new Future] also avoids starving the DOM or other
308320
// microtask-level events.
309-
new Future(_test._body)
310-
.then((_) => removeOutstandingCallback());
321+
new Future(() async {
322+
await _test._body();
323+
await unclosable(() =>
324+
Future.forEach(_tearDowns.reversed, errorsDontStopTest));
325+
removeOutstandingCallback();
326+
});
311327

312328
await _outstandingCallbacks.noOutstandingCallbacks;
313329
if (_timeoutTimer != null) _timeoutTimer.cancel();

lib/src/utils.dart

+19
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:async/async.dart' hide StreamQueue;
1111
import 'package:path/path.dart' as p;
1212
import 'package:stack_trace/stack_trace.dart';
1313

14+
import 'backend/invoker.dart';
1415
import 'backend/operating_system.dart';
1516
import 'util/stream_queue.dart';
1617

@@ -347,6 +348,24 @@ void invoke(fn()) {
347348
fn();
348349
}
349350

351+
/// Runs [body] with special error-handling behavior.
352+
///
353+
/// Errors emitted [body] will still cause the current test to fail, but they
354+
/// won't cause it to *stop*. In particular, they won't remove any outstanding
355+
/// callbacks registered outside of [body].
356+
///
357+
/// This may only be called within a test.
358+
Future errorsDontStopTest(body()) {
359+
var completer = new Completer();
360+
361+
Invoker.current.addOutstandingCallback();
362+
Invoker.current.waitForOutstandingCallbacks(() {
363+
new Future.sync(body).whenComplete(completer.complete);
364+
}).then((_) => Invoker.current.removeOutstandingCallback());
365+
366+
return completer.future;
367+
}
368+
350369
/// Returns a random base64 string containing [bytes] bytes of data.
351370
///
352371
/// [seed] is passed to [math.Random].

lib/test.dart

+19
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:async';
77
import 'package:path/path.dart' as p;
88

99
import 'src/backend/declarer.dart';
10+
import 'src/backend/invoker.dart';
1011
import 'src/backend/test_platform.dart';
1112
import 'src/frontend/timeout.dart';
1213
import 'src/runner/configuration/suite.dart';
@@ -228,8 +229,26 @@ void setUp(callback()) => _declarer.setUp(callback);
228229
///
229230
/// Each callback at the top level or in a given group will be run in the
230231
/// reverse of the order they were declared.
232+
///
233+
/// See also [addTearDown], which adds tear-downs to a running test.
231234
void tearDown(callback()) => _declarer.tearDown(callback);
232235

236+
/// Registers a function to be run after the current test.
237+
///
238+
/// This is called within a running test, and adds a tear-down only for the
239+
/// current test. It allows testing libraries to add cleanup logic as soon as
240+
/// there's something to clean up.
241+
///
242+
/// The [callback] is run before any callbacks registered with [tearDown]. Like
243+
/// [tearDown], the most recently registered callback is run first.
244+
void addTearDown(callback()) {
245+
if (Invoker.current == null) {
246+
throw new StateError("addTearDown() may only be called within a test.");
247+
}
248+
249+
Invoker.current.addTearDown(callback);
250+
}
251+
233252
/// Registers a function to be run once before all tests.
234253
///
235254
/// [callback] may be asynchronous; if so, it must return a [Future].

0 commit comments

Comments
 (0)