| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013, 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 // TODO(nweiz): Add support for calling [schedule] while the schedule is already | |
| 6 // running. | |
| 7 // TODO(nweiz): Port the non-Pub-specific scheduled test libraries from Pub. | |
| 8 library scheduled_test; | |
| 9 | |
| 10 import 'dart:async'; | |
| 11 | |
| 12 import 'package:stack_trace/stack_trace.dart'; | |
| 13 import 'package:unittest/unittest.dart' as unittest; | |
| 14 | |
| 15 import 'src/schedule.dart'; | |
| 16 import 'src/schedule_error.dart'; | |
| 17 | |
| 18 export 'package:unittest/unittest.dart' hide | |
| 19 test, solo_test, group, setUp, tearDown, completes, completion; | |
| 20 | |
| 21 export 'src/schedule.dart'; | |
| 22 export 'src/schedule_error.dart'; | |
| 23 export 'src/scheduled_future_matchers.dart'; | |
| 24 export 'src/task.dart'; | |
| 25 | |
| 26 /// The [Schedule] for the current test. This is used to add new tasks and | |
| 27 /// inspect the state of the schedule. | |
| 28 /// | |
| 29 /// This is `null` when there's no test currently running. | |
| 30 Schedule get currentSchedule => _currentSchedule; | |
| 31 Schedule _currentSchedule; | |
| 32 | |
| 33 /// The user-provided set-up function for the currently-running test. | |
| 34 /// | |
| 35 /// This is set for each test during `unittest.setUp`. | |
| 36 Function _setUpFn; | |
| 37 | |
| 38 /// The user-provided tear-down function for the currently-running test. | |
| 39 /// | |
| 40 /// This is set for each test during `unittest.setUp`. | |
| 41 Function _tearDownFn; | |
| 42 | |
| 43 /// The user-provided set-up function for the current test scope. | |
| 44 Function _setUpForGroup; | |
| 45 | |
| 46 /// The user-provided tear-down function for the current test scope. | |
| 47 Function _tearDownForGroup; | |
| 48 | |
| 49 /// Creates a new test case with the given description and body. | |
| 50 /// | |
| 51 /// This has the same semantics as [unittest.test]. | |
| 52 /// | |
| 53 /// If [body] returns a [Future], that future will automatically be wrapped with | |
| 54 /// [wrapFuture]. | |
| 55 void test(String description, body()) => | |
| 56 _test(description, body, unittest.test); | |
| 57 | |
| 58 /// Creates a new test case with the given description and body that will be the | |
| 59 /// only test run in this file. | |
| 60 /// | |
| 61 /// This has the same semantics as [unittest.solo_test]. | |
| 62 /// | |
| 63 /// If [body] returns a [Future], that future will automatically be wrapped with | |
| 64 /// [wrapFuture]. | |
| 65 void solo_test(String description, body()) => | |
| 66 _test(description, body, unittest.solo_test); | |
| 67 | |
| 68 void _test(String description, body(), Function testFn) { | |
| 69 maybeWrapFuture(future, description) { | |
| 70 if (future != null) wrapFuture(future, description); | |
| 71 } | |
| 72 | |
| 73 unittest.ensureInitialized(); | |
| 74 _initializeForGroup(); | |
| 75 testFn(description, () { | |
| 76 var completer = new Completer(); | |
| 77 | |
| 78 // Capture this in a local variable in case we capture an out-of-band error | |
| 79 // after the schedule completes. | |
| 80 var errorHandler; | |
| 81 | |
| 82 Chain.capture(() { | |
| 83 _currentSchedule = new Schedule(); | |
| 84 errorHandler = _currentSchedule.signalError; | |
| 85 return currentSchedule.run(() { | |
| 86 if (_setUpFn != null) maybeWrapFuture(_setUpFn(), "set up"); | |
| 87 maybeWrapFuture(body(), "test body"); | |
| 88 if (_tearDownFn != null) maybeWrapFuture(_tearDownFn(), "tear down"); | |
| 89 }).catchError((error, stackTrace) { | |
| 90 if (error is ScheduleError) { | |
| 91 assert(error.schedule.errors.contains(error)); | |
| 92 assert(error.schedule == currentSchedule); | |
| 93 unittest.registerException(error.schedule.errorString()); | |
| 94 } else { | |
| 95 unittest.registerException(error, new Chain.forTrace(stackTrace)); | |
| 96 } | |
| 97 }).then(completer.complete); | |
| 98 }, onError: (error, stackTrace) => errorHandler(error, stackTrace)); | |
| 99 | |
| 100 return completer.future; | |
| 101 }); | |
| 102 } | |
| 103 | |
| 104 /// Whether or not the tests currently being defined are in a group. This is | |
| 105 /// only true when defining tests, not when executing them. | |
| 106 bool _inGroup = false; | |
| 107 | |
| 108 /// Creates a new named group of tests. This has the same semantics as | |
| 109 /// [unittest.group]. | |
| 110 void group(String description, void body()) { | |
| 111 unittest.ensureInitialized(); | |
| 112 _initializeForGroup(); | |
| 113 unittest.group(description, () { | |
| 114 var oldSetUp = _setUpForGroup; | |
| 115 var oldTearDown = _tearDownForGroup; | |
| 116 var wasInitializedForGroup = _initializedForGroup; | |
| 117 var wasInGroup = _inGroup; | |
| 118 _setUpForGroup = null; | |
| 119 _tearDownForGroup = null; | |
| 120 _initializedForGroup = false; | |
| 121 _inGroup = true; | |
| 122 body(); | |
| 123 _setUpForGroup = oldSetUp; | |
| 124 _tearDownForGroup = oldTearDown; | |
| 125 _initializedForGroup = wasInitializedForGroup; | |
| 126 _inGroup = wasInGroup; | |
| 127 }); | |
| 128 } | |
| 129 | |
| 130 /// Schedules a task, [fn], to run asynchronously as part of the main task queue | |
| 131 /// of [currentSchedule]. Tasks will be run in the order they're scheduled. If | |
| 132 /// [fn] returns a [Future], tasks after it won't be run until that [Future] | |
| 133 /// completes. | |
| 134 /// | |
| 135 /// The return value will be completed once the scheduled task has finished | |
| 136 /// running. Its return value is the same as the return value of [fn], or the | |
| 137 /// value it completes to if it's a [Future]. | |
| 138 /// | |
| 139 /// If [description] is passed, it's used to describe the task for debugging | |
| 140 /// purposes when an error occurs. | |
| 141 /// | |
| 142 /// If this is called when a task queue is currently running, it will run [fn] | |
| 143 /// on the next event loop iteration rather than adding it to a queue. The | |
| 144 /// current task will not complete until [fn] (and any [Future] it returns) has | |
| 145 /// finished running. Any errors in [fn] will automatically be handled. | |
| 146 Future schedule(fn(), [String description]) => | |
| 147 currentSchedule.tasks.schedule(fn, description); | |
| 148 | |
| 149 /// Register a [setUp] function for a test [group]. | |
| 150 /// | |
| 151 /// This has the same semantics as [unittest.setUp]. Tasks may be scheduled | |
| 152 /// using [schedule] within [setUpFn], and [currentSchedule] may be accessed as | |
| 153 /// well. | |
| 154 void setUp(setUpFn()) { | |
| 155 _setUpForGroup = setUpFn; | |
| 156 } | |
| 157 | |
| 158 /// Register a [tearDown] function for a test [group]. | |
| 159 /// | |
| 160 /// This has the same semantics as [unittest.tearDown]. Tasks may be scheduled | |
| 161 /// using [schedule] within [tearDownFn], and [currentSchedule] may be accessed | |
| 162 /// as well. Note that [tearDownFn] will be run synchronously after the test | |
| 163 /// body finishes running, which means it will run before any scheduled tasks | |
| 164 /// have begun. | |
| 165 /// | |
| 166 /// To run code after the schedule has finished running, use | |
| 167 /// `currentSchedule.onComplete.schedule`. | |
| 168 void tearDown(tearDownFn()) { | |
| 169 _tearDownForGroup = tearDownFn; | |
| 170 } | |
| 171 | |
| 172 /// Whether [_initializeForGroup] has been called in this group scope. | |
| 173 bool _initializedForGroup = false; | |
| 174 | |
| 175 /// Registers callbacks for [unittest.setUp] and [unittest.tearDown] that set up | |
| 176 /// and tear down the scheduled test infrastructure and run the user's [setUp] | |
| 177 /// and [tearDown] callbacks. | |
| 178 void _initializeForGroup() { | |
| 179 if (_initializedForGroup) return; | |
| 180 _initializedForGroup = true; | |
| 181 | |
| 182 var setUpFn = _setUpForGroup; | |
| 183 var tearDownFn = _tearDownForGroup; | |
| 184 | |
| 185 if (_inGroup) { | |
| 186 unittest.setUp(() => _addSetUpTearDown(setUpFn, tearDownFn)); | |
| 187 return; | |
| 188 } | |
| 189 | |
| 190 var oldWrapAsync = unittest.wrapAsync; | |
| 191 unittest.setUp(() { | |
| 192 if (currentSchedule != null) { | |
| 193 throw new StateError('There seems to be another scheduled test ' | |
| 194 'still running.'); | |
| 195 } | |
| 196 | |
| 197 unittest.wrapAsync = (f, [description]) { | |
| 198 // It's possible that this setup is run before a vanilla unittest test | |
| 199 // if [unittest.test] is run in the same context as | |
| 200 // [scheduled_test.test]. In that case, [currentSchedule] will never be | |
| 201 // set and we should forward to the [unittest.wrapAsync]. | |
| 202 if (currentSchedule == null) return oldWrapAsync(f, description); | |
| 203 return currentSchedule.wrapAsync(f, description); | |
| 204 }; | |
| 205 | |
| 206 _addSetUpTearDown(setUpFn, tearDownFn); | |
| 207 }); | |
| 208 | |
| 209 unittest.tearDown(() { | |
| 210 unittest.wrapAsync = oldWrapAsync; | |
| 211 _currentSchedule = null; | |
| 212 _setUpFn = null; | |
| 213 _tearDownFn = null; | |
| 214 }); | |
| 215 } | |
| 216 | |
| 217 /// Set [_setUpFn] and [_tearDownFn] appropriately. | |
| 218 void _addSetUpTearDown(void setUpFn(), void tearDownFn()) { | |
| 219 if (setUpFn != null) { | |
| 220 if (_setUpFn != null) { | |
| 221 var parentFn = _setUpFn; | |
| 222 _setUpFn = () { parentFn(); setUpFn(); }; | |
| 223 } else { | |
| 224 _setUpFn = setUpFn; | |
| 225 } | |
| 226 } | |
| 227 | |
| 228 if (tearDownFn != null) { | |
| 229 if (_tearDownFn != null) { | |
| 230 var parentFn = _tearDownFn; | |
| 231 _tearDownFn = () { parentFn(); tearDownFn(); }; | |
| 232 } else { | |
| 233 _tearDownFn = tearDownFn; | |
| 234 } | |
| 235 } | |
| 236 } | |
| 237 | |
| 238 /// Like [wrapAsync], this ensures that the current task queue waits for | |
| 239 /// out-of-band asynchronous code, and that errors raised in that code are | |
| 240 /// handled correctly. However, [wrapFuture] wraps a [Future] chain rather than | |
| 241 /// a single callback. | |
| 242 /// | |
| 243 /// The returned [Future] completes to the same value or error as [future]. | |
| 244 /// | |
| 245 /// [description] provides an optional description of the future, which is | |
| 246 /// used when generating error messages. | |
| 247 Future wrapFuture(Future future, [String description]) { | |
| 248 if (currentSchedule == null) { | |
| 249 throw new StateError("Unexpected call to wrapFuture with no current " | |
| 250 "schedule."); | |
| 251 } | |
| 252 | |
| 253 return currentSchedule.wrapFuture(future, description); | |
| 254 } | |
| OLD | NEW |