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