Index: pkg/scheduled_test/lib/scheduled_test.dart |
diff --git a/pkg/scheduled_test/lib/scheduled_test.dart b/pkg/scheduled_test/lib/scheduled_test.dart |
index cf61c2599c9dfc64b11a3a0ff4e51a661deaca64..c1b1bda79085670f9427804a50a69813c067783b 100644 |
--- a/pkg/scheduled_test/lib/scheduled_test.dart |
+++ b/pkg/scheduled_test/lib/scheduled_test.dart |
@@ -30,10 +30,22 @@ export 'src/task.dart'; |
Schedule get currentSchedule => _currentSchedule; |
Schedule _currentSchedule; |
-/// The user-provided setUp function. This is set for each test during |
-/// `unittest.setUp`. |
+/// The user-provided set-up function for the currently-running test. |
+/// |
+/// This is set for each test during `unittest.setUp`. |
Function _setUpFn; |
+/// The user-provided tear-down function for the currently-running test. |
+/// |
+/// This is set for each test during `unittest.setUp`. |
+Function _tearDownFn; |
+ |
+/// The user-provided set-up function for the current test scope. |
+Function _setUpForGroup; |
+ |
+/// The user-provided tear-down function for the current test scope. |
+Function _tearDownForGroup; |
+ |
/// Creates a new test case with the given description and body. |
/// |
/// This has the same semantics as [unittest.test]. |
@@ -59,7 +71,7 @@ void _test(String description, body(), Function testFn) { |
} |
unittest.ensureInitialized(); |
- _ensureSetUpForTopLevel(); |
+ _initializeForGroup(); |
testFn(description, () { |
var completer = new Completer(); |
@@ -73,6 +85,7 @@ void _test(String description, body(), Function testFn) { |
return currentSchedule.run(() { |
if (_setUpFn != null) maybeWrapFuture(_setUpFn(), "set up"); |
maybeWrapFuture(body(), "test body"); |
+ if (_tearDownFn != null) maybeWrapFuture(_tearDownFn(), "tear down"); |
}).catchError((error, stackTrace) { |
if (error is ScheduleError) { |
assert(error.schedule.errors.contains(error)); |
@@ -96,11 +109,20 @@ bool _inGroup = false; |
/// [unittest.group]. |
void group(String description, void body()) { |
unittest.ensureInitialized(); |
- _ensureSetUpForTopLevel(); |
+ _initializeForGroup(); |
unittest.group(description, () { |
+ var oldSetUp = _setUpForGroup; |
+ var oldTearDown = _tearDownForGroup; |
+ var wasInitializedForGroup = _initializedForGroup; |
var wasInGroup = _inGroup; |
+ _setUpForGroup = null; |
+ _tearDownForGroup = null; |
+ _initializedForGroup = false; |
_inGroup = true; |
body(); |
+ _setUpForGroup = oldSetUp; |
+ _tearDownForGroup = oldTearDown; |
+ _initializedForGroup = wasInitializedForGroup; |
_inGroup = wasInGroup; |
}); |
} |
@@ -124,71 +146,92 @@ void group(String description, void body()) { |
Future schedule(fn(), [String description]) => |
currentSchedule.tasks.schedule(fn, description); |
-/// Register a [setUp] function for a test [group]. This has the same semantics |
-/// as [unittest.setUp]. Tasks may be scheduled using [schedule] within |
-/// [setUpFn], and [currentSchedule] may be accessed as well. |
+/// Register a [setUp] function for a test [group]. |
/// |
-/// Note that there is no associated [tearDown] function. Instead, tasks should |
-/// be scheduled for [currentSchedule.onComplete] or |
-/// [currentSchedule.onException]. These tasks will be run after each test's |
-/// schedule is completed. |
+/// This has the same semantics as [unittest.setUp]. Tasks may be scheduled |
+/// using [schedule] within [setUpFn], and [currentSchedule] may be accessed as |
+/// well. |
void setUp(setUpFn()) { |
- _setUpScheduledTest(setUpFn); |
+ _setUpForGroup = setUpFn; |
} |
-/// Whether [unittest.setUp] has been called in the top level scope. |
-bool _setUpForTopLevel = false; |
- |
-/// If we're in the top-level scope (that is, not in any [group]s) and |
-/// [unittest.setUp] hasn't been called yet, call it. |
-void _ensureSetUpForTopLevel() { |
- if (_inGroup || _setUpForTopLevel) return; |
- _setUpScheduledTest(); |
+/// Register a [tearDown] function for a test [group]. |
+/// |
+/// This has the same semantics as [unittest.tearDown]. Tasks may be scheduled |
+/// using [schedule] within [tearDownFn], and [currentSchedule] may be accessed |
+/// as well. Note that [tearDownFn] will be run synchronously after the test |
+/// body finishes running, which means it will run before any scheduled tasks |
+/// have begun. |
+/// |
+/// To run code after the schedule has finished running, use |
+/// `currentSchedule.onComplete.schedule`. |
+void tearDown(tearDownFn()) { |
+ _tearDownForGroup = tearDownFn; |
} |
+/// Whether [_initializeForGroup] has been called in this group scope. |
+bool _initializedForGroup = false; |
+ |
/// Registers callbacks for [unittest.setUp] and [unittest.tearDown] that set up |
-/// and tear down the scheduled test infrastructure. |
-void _setUpScheduledTest([void setUpFn()]) { |
- if (!_inGroup) { |
- _setUpForTopLevel = true; |
- var oldWrapAsync = unittest.wrapAsync; |
- unittest.setUp(() { |
- if (currentSchedule != null) { |
- throw new StateError('There seems to be another scheduled test ' |
- 'still running.'); |
- } |
- |
- unittest.wrapAsync = (f, [description]) { |
- // It's possible that this setup is run before a vanilla unittest test |
- // if [unittest.test] is run in the same context as |
- // [scheduled_test.test]. In that case, [currentSchedule] will never be |
- // set and we should forward to the [unittest.wrapAsync]. |
- if (currentSchedule == null) return oldWrapAsync(f, description); |
- return currentSchedule.wrapAsync(f, description); |
- }; |
- |
- if (_setUpFn != null) { |
- var parentFn = _setUpFn; |
- _setUpFn = () { parentFn(); setUpFn(); }; |
- } else { |
- _setUpFn = setUpFn; |
- } |
- }); |
- |
- unittest.tearDown(() { |
- unittest.wrapAsync = oldWrapAsync; |
- _currentSchedule = null; |
- _setUpFn = null; |
- }); |
- } else { |
- unittest.setUp(() { |
- if (_setUpFn != null) { |
- var parentFn = _setUpFn; |
- _setUpFn = () { parentFn(); setUpFn(); }; |
- } else { |
- _setUpFn = setUpFn; |
- } |
- }); |
+/// and tear down the scheduled test infrastructure and run the user's [setUp] |
+/// and [tearDown] callbacks. |
+void _initializeForGroup() { |
+ if (_initializedForGroup) return; |
+ _initializedForGroup = true; |
+ |
+ var setUpFn = _setUpForGroup; |
+ var tearDownFn = _tearDownForGroup; |
+ |
+ if (_inGroup) { |
+ unittest.setUp(() => _addSetUpTearDown(setUpFn, tearDownFn)); |
+ return; |
+ } |
+ |
+ var oldWrapAsync = unittest.wrapAsync; |
+ unittest.setUp(() { |
+ if (currentSchedule != null) { |
+ throw new StateError('There seems to be another scheduled test ' |
+ 'still running.'); |
+ } |
+ |
+ unittest.wrapAsync = (f, [description]) { |
+ // It's possible that this setup is run before a vanilla unittest test |
+ // if [unittest.test] is run in the same context as |
+ // [scheduled_test.test]. In that case, [currentSchedule] will never be |
+ // set and we should forward to the [unittest.wrapAsync]. |
+ if (currentSchedule == null) return oldWrapAsync(f, description); |
+ return currentSchedule.wrapAsync(f, description); |
+ }; |
+ |
+ _addSetUpTearDown(setUpFn, tearDownFn); |
+ }); |
+ |
+ unittest.tearDown(() { |
+ unittest.wrapAsync = oldWrapAsync; |
+ _currentSchedule = null; |
+ _setUpFn = null; |
+ _tearDownFn = null; |
+ }); |
+} |
+ |
+/// Set [_setUpFn] and [_tearDownFn] appropriately. |
+void _addSetUpTearDown(void setUpFn(), void tearDownFn()) { |
+ if (setUpFn != null) { |
+ if (_setUpFn != null) { |
+ var parentFn = _setUpFn; |
+ _setUpFn = () { parentFn(); setUpFn(); }; |
+ } else { |
+ _setUpFn = setUpFn; |
+ } |
+ } |
+ |
+ if (tearDownFn != null) { |
+ if (_tearDownFn != null) { |
+ var parentFn = _tearDownFn; |
+ _tearDownFn = () { parentFn(); tearDownFn(); }; |
+ } else { |
+ _tearDownFn = tearDownFn; |
+ } |
} |
} |