| 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 library unittest; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:collection'; |
| 9 |
| 10 import '../matcher/matcher.dart' show TestFailure, wrapAsync; |
| 11 |
| 12 import 'src/configuration.dart'; |
| 13 import 'src/expected_function.dart'; |
| 14 import 'src/group_context.dart'; |
| 15 import 'src/internal_test_case.dart'; |
| 16 import 'src/test_case.dart'; |
| 17 import 'src/test_environment.dart'; |
| 18 |
| 19 export '../matcher/matcher.dart'; |
| 20 |
| 21 export 'src/configuration.dart'; |
| 22 export 'src/simple_configuration.dart'; |
| 23 export 'src/test_case.dart'; |
| 24 |
| 25 /// The signature for a function passed to [test]. |
| 26 typedef dynamic TestFunction(); |
| 27 |
| 28 /// [Configuration] used by the unittest library. |
| 29 /// |
| 30 /// Note that if a configuration has not been set, calling this getter will |
| 31 /// create a default configuration. |
| 32 Configuration get unittestConfiguration { |
| 33 if (config == null) environment.config = new Configuration(); |
| 34 return config; |
| 35 } |
| 36 |
| 37 /// If `true`, stack traces are reformatted to be more readable. |
| 38 bool formatStacks = true; |
| 39 |
| 40 /// If `true`, irrelevant frames are filtered from the stack trace. |
| 41 /// |
| 42 /// This does nothing if [formatStacks] is false. |
| 43 bool filterStacks = true; |
| 44 |
| 45 /// Separator used between group names and test names. |
| 46 String groupSep = ' '; |
| 47 |
| 48 /// Sets the [Configuration] used by the unittest library. |
| 49 /// |
| 50 /// Throws a [StateError] if there is an existing, incompatible value. |
| 51 void set unittestConfiguration(Configuration value) { |
| 52 if (identical(config, value)) return; |
| 53 if (config != null) { |
| 54 logMessage('Warning: The unittestConfiguration has already been set. New ' |
| 55 'unittestConfiguration ignored.'); |
| 56 } else { |
| 57 environment.config = value; |
| 58 } |
| 59 } |
| 60 |
| 61 /// Logs [message] associated with the current test case. |
| 62 /// |
| 63 /// Tests should use this instead of [print]. |
| 64 void logMessage(String message) => |
| 65 config.onLogMessage(currentTestCase, message); |
| 66 |
| 67 /// The test cases that have been defined so far. |
| 68 List<TestCase> get testCases => |
| 69 new UnmodifiableListView<TestCase>(environment.testCases); |
| 70 |
| 71 /// The interval (in milliseconds) after which a non-microtask asynchronous |
| 72 /// delay will be scheduled between tests. |
| 73 /// |
| 74 /// This is used to avoid starving the DOM or other non-microtask events. |
| 75 const int BREATH_INTERVAL = 200; |
| 76 |
| 77 /// The [TestCase] currently being executed. |
| 78 TestCase get currentTestCase => (environment.currentTestCaseIndex >= 0 && |
| 79 environment.currentTestCaseIndex < testCases.length) |
| 80 ? testCases[environment.currentTestCaseIndex] |
| 81 : null; |
| 82 |
| 83 /// The same as [currentTestCase], but typed as an [InternalTestCase]. |
| 84 InternalTestCase get _currentTestCase => currentTestCase as InternalTestCase; |
| 85 |
| 86 /// The result string for a passing test case. |
| 87 const PASS = 'pass'; |
| 88 |
| 89 /// The result string for a failing test case. |
| 90 const FAIL = 'fail'; |
| 91 |
| 92 /// The result string for an test case with an error. |
| 93 const ERROR = 'error'; |
| 94 |
| 95 /// Creates a new test case with the given description and body. |
| 96 /// |
| 97 /// The description will be added to the descriptions of any surrounding |
| 98 /// [group]s. |
| 99 void test(String description, TestFunction body) { |
| 100 _requireNotRunning(); |
| 101 ensureInitialized(); |
| 102 |
| 103 if (environment.soloTestSeen && environment.soloNestingLevel == 0) return; |
| 104 var testCase = new InternalTestCase( |
| 105 testCases.length + 1, _fullDescription(description), body); |
| 106 environment.testCases.add(testCase); |
| 107 } |
| 108 |
| 109 /// Returns [description] with all of its group prefixes prepended. |
| 110 String _fullDescription(String description) { |
| 111 var group = environment.currentContext.fullName; |
| 112 if (description == null) return group; |
| 113 return group != '' ? '$group$groupSep$description' : description; |
| 114 } |
| 115 |
| 116 /// A convenience function for skipping a test. |
| 117 void skip_test(String spec, TestFunction body) {} |
| 118 |
| 119 /// Creates a new test case with the given description and body. |
| 120 /// |
| 121 /// If [solo_test] is used instead of [test], then all non-solo tests will be |
| 122 /// disabled. Note that if [solo_group] is used as well, all tests in the group |
| 123 /// will be enabled, regardless of whether they use [test] or [solo_test], or |
| 124 /// whether they are in a nested [group] versus [solo_group]. Put another way, |
| 125 /// if there are any calls to [solo_test] or [solo_group] in a test file, all |
| 126 /// tests that are not inside a [solo_group] will be disabled unless they are |
| 127 /// [solo_test]s. |
| 128 void solo_test(String spec, TestFunction body) { |
| 129 _requireNotRunning(); |
| 130 ensureInitialized(); |
| 131 if (!environment.soloTestSeen) { |
| 132 environment.soloTestSeen = true; |
| 133 // This is the first solo-ed test. Discard all tests up to now. |
| 134 environment.testCases.clear(); |
| 135 } |
| 136 environment.soloNestingLevel++; |
| 137 try { |
| 138 test(spec, body); |
| 139 } finally { |
| 140 environment.soloNestingLevel--; |
| 141 } |
| 142 } |
| 143 |
| 144 /// Indicate that [callback] is expected to be called [count] number of times |
| 145 /// (by default 1). |
| 146 /// |
| 147 /// The unittest framework will wait for the callback to run the [count] times |
| 148 /// before it considers the current test to be complete. Using [expectAsync] |
| 149 /// will also ensure that errors that occur within [callback] are tracked and |
| 150 /// reported. [callback] may take up to six optional or required positional |
| 151 /// arguments; named arguments are not supported. |
| 152 /// |
| 153 /// [max] can be used to specify an upper bound on the number of calls; if this |
| 154 /// is exceeded the test will fail. If [max] is `0` (the default), the callback |
| 155 /// is expected to be called exactly [count] times. If [max] is `-1`, the |
| 156 /// callback is allowed to be called any number of times greater than [count]. |
| 157 /// |
| 158 /// Both [id] and [reason] are optional and provide extra information about the |
| 159 /// callback when debugging. [id] should be the name of the callback, while |
| 160 /// [reason] should be the reason the callback is expected to be called. |
| 161 Function expectAsync(Function callback, |
| 162 {int count: 1, int max: 0, String id, String reason}) => |
| 163 new ExpectedFunction(callback, count, max, id: id, reason: reason).func; |
| 164 |
| 165 /// Indicate that [callback] is expected to be called until [isDone] returns |
| 166 /// true. |
| 167 /// |
| 168 /// [isDone] is called after each time the function is run. Only when it returns |
| 169 /// true will the callback be considered complete. Using [expectAsyncUntil] will |
| 170 /// also ensure that errors that occur within [callback] are tracked and |
| 171 /// reported. [callback] may take up to six optional or required positional |
| 172 /// arguments; named arguments are not supported. |
| 173 /// |
| 174 /// Both [id] and [reason] are optional and provide extra information about the |
| 175 /// callback when debugging. [id] should be the name of the callback, while |
| 176 /// [reason] should be the reason the callback is expected to be called. |
| 177 Function expectAsyncUntil(Function callback, bool isDone(), |
| 178 {String id, String reason}) => new ExpectedFunction(callback, 0, -1, |
| 179 id: id, reason: reason, isDone: isDone).func; |
| 180 |
| 181 /// Creates a group of tests. |
| 182 /// |
| 183 /// A group's description is included in the descriptions of any tests or |
| 184 /// sub-groups it contains. [setUp] and [tearDown] are also scoped to the |
| 185 /// containing group. |
| 186 void group(String description, void body()) { |
| 187 ensureInitialized(); |
| 188 _requireNotRunning(); |
| 189 environment.currentContext = |
| 190 new GroupContext(environment.currentContext, description); |
| 191 try { |
| 192 body(); |
| 193 } catch (e, trace) { |
| 194 var stack = (trace == null) ? '' : ': ${trace.toString()}'; |
| 195 environment.uncaughtErrorMessage = "${e.toString()}$stack"; |
| 196 } finally { |
| 197 // Now that the group is over, restore the previous one. |
| 198 environment.currentContext = environment.currentContext.parent; |
| 199 } |
| 200 } |
| 201 |
| 202 /// A convenience function for skipping a group of tests. |
| 203 void skip_group(String description, void body()) {} |
| 204 |
| 205 /// Creates a group of tests. |
| 206 /// |
| 207 /// If [solo_group] is used instead of [group], then all tests not declared with |
| 208 /// [solo_test] or in a [solo_group] will be disabled. Note that all tests in a |
| 209 /// [solo_group] will be run, regardless of whether they're declared with [test] |
| 210 /// or [solo_test]. |
| 211 /// |
| 212 /// [skip_test] and [skip_group] take precedence over [solo_group]. |
| 213 void solo_group(String description, void body()) { |
| 214 _requireNotRunning(); |
| 215 ensureInitialized(); |
| 216 if (!environment.soloTestSeen) { |
| 217 environment.soloTestSeen = true; |
| 218 // This is the first solo-ed group. Discard all tests up to now. |
| 219 environment.testCases.clear(); |
| 220 } |
| 221 ++environment.soloNestingLevel; |
| 222 try { |
| 223 group(description, body); |
| 224 } finally { |
| 225 --environment.soloNestingLevel; |
| 226 } |
| 227 } |
| 228 |
| 229 /// Registers a function to be run before tests. |
| 230 /// |
| 231 /// This function will be called before each test is run. [callback] may be |
| 232 /// asynchronous; if so, it must return a [Future]. |
| 233 /// |
| 234 /// If this is called within a test group, it applies only to tests in that |
| 235 /// group. [callback] will be run after any set-up callbacks in parent groups or |
| 236 /// at the top level. |
| 237 void setUp(Function callback) { |
| 238 _requireNotRunning(); |
| 239 environment.currentContext.testSetUp = callback; |
| 240 } |
| 241 |
| 242 /// Registers a function to be run after tests. |
| 243 /// |
| 244 /// This function will be called after each test is run. [callback] may be |
| 245 /// asynchronous; if so, it must return a [Future]. |
| 246 /// |
| 247 /// If this is called within a test group, it applies only to tests in that |
| 248 /// group. [callback] will be run before any tear-down callbacks in parent group
s or |
| 249 /// at the top level. |
| 250 void tearDown(Function callback) { |
| 251 _requireNotRunning(); |
| 252 environment.currentContext.testTearDown = callback; |
| 253 } |
| 254 |
| 255 /// Advance to the next test case. |
| 256 void _nextTestCase() { |
| 257 environment.currentTestCaseIndex++; |
| 258 _runTest(); |
| 259 } |
| 260 |
| 261 /// Handle an error that occurs outside of any test. |
| 262 void handleExternalError(e, String message, [stackTrace]) { |
| 263 var msg = '$message\nCaught $e'; |
| 264 |
| 265 if (currentTestCase != null) { |
| 266 _currentTestCase.error(msg, stackTrace); |
| 267 } else { |
| 268 environment.uncaughtErrorMessage = "$msg: $stackTrace"; |
| 269 } |
| 270 } |
| 271 |
| 272 /// Remove any tests that match [testFilter]. |
| 273 /// |
| 274 /// [testFilter] can be a predicate function, a [RegExp], or a [String]. If it's |
| 275 /// a function, it's called with each [TestCase]. If it's a [String], it's |
| 276 /// parsed as a [RegExp] and matched against each [TestCase.description]. |
| 277 /// |
| 278 /// This is different from enabling or disabling tests in that it removes the |
| 279 /// tests completely. |
| 280 void filterTests(testFilter) { |
| 281 var filterFunction; |
| 282 if (testFilter is String) { |
| 283 var re = new RegExp(testFilter); |
| 284 filterFunction = (t) => re.hasMatch(t.description); |
| 285 } else if (testFilter is RegExp) { |
| 286 filterFunction = (t) => testFilter.hasMatch(t.description); |
| 287 } else if (testFilter is Function) { |
| 288 filterFunction = testFilter; |
| 289 } |
| 290 environment.testCases.retainWhere(filterFunction); |
| 291 } |
| 292 |
| 293 /// Runs all queued tests, one at a time. |
| 294 void runTests() { |
| 295 _requireNotRunning(); |
| 296 _ensureInitialized(false); |
| 297 environment.currentTestCaseIndex = 0; |
| 298 config.onStart(); |
| 299 _runTest(); |
| 300 } |
| 301 |
| 302 /// Registers an exception that was caught for the current test. |
| 303 void registerException(error, [StackTrace stackTrace]) => |
| 304 _currentTestCase.registerException(error, stackTrace); |
| 305 |
| 306 /// Runs the next test. |
| 307 void _runTest() { |
| 308 if (environment.currentTestCaseIndex >= testCases.length) { |
| 309 assert(environment.currentTestCaseIndex == testCases.length); |
| 310 _completeTests(); |
| 311 return; |
| 312 } |
| 313 |
| 314 var testCase = _currentTestCase; |
| 315 var f = runZoned(testCase.run, onError: (error, stack) { |
| 316 // TODO(kevmoo) Do a better job of flagging these are async errors. |
| 317 // https://code.google.com/p/dart/issues/detail?id=16530 |
| 318 testCase.registerException(error, stack); |
| 319 }); |
| 320 |
| 321 var timer; |
| 322 var timeout = unittestConfiguration.timeout; |
| 323 if (timeout != null) { |
| 324 try { |
| 325 timer = new Timer(timeout, () { |
| 326 testCase.error("Test timed out after ${timeout.inSeconds} seconds."); |
| 327 _nextTestCase(); |
| 328 }); |
| 329 } on UnsupportedError catch (e) { |
| 330 if (e.message != "Timer greater than 0.") rethrow; |
| 331 // Support running on d8 and jsshell which don't support timers. |
| 332 } |
| 333 } |
| 334 |
| 335 f.whenComplete(() { |
| 336 if (timer != null) timer.cancel(); |
| 337 var now = new DateTime.now().millisecondsSinceEpoch; |
| 338 if (now - environment.lastBreath >= BREATH_INTERVAL) { |
| 339 environment.lastBreath = now; |
| 340 Timer.run(_nextTestCase); |
| 341 } else { |
| 342 scheduleMicrotask(_nextTestCase); // Schedule the next test. |
| 343 } |
| 344 }); |
| 345 } |
| 346 |
| 347 /// Notify the configuration that the testing has finished. |
| 348 void _completeTests() { |
| 349 if (!environment.initialized) return; |
| 350 |
| 351 var passed = 0; |
| 352 var failed = 0; |
| 353 var errors = 0; |
| 354 for (var testCase in testCases) { |
| 355 switch (testCase.result) { |
| 356 case PASS: |
| 357 passed++; |
| 358 break; |
| 359 case FAIL: |
| 360 failed++; |
| 361 break; |
| 362 case ERROR: |
| 363 errors++; |
| 364 break; |
| 365 } |
| 366 } |
| 367 |
| 368 config.onSummary( |
| 369 passed, failed, errors, testCases, environment.uncaughtErrorMessage); |
| 370 config.onDone(passed > 0 && |
| 371 failed == 0 && |
| 372 errors == 0 && |
| 373 environment.uncaughtErrorMessage == null); |
| 374 environment.initialized = false; |
| 375 environment.currentTestCaseIndex = -1; |
| 376 } |
| 377 |
| 378 /// Initializes the test environment if it hasn't already been initialized. |
| 379 void ensureInitialized() { |
| 380 _ensureInitialized(true); |
| 381 } |
| 382 |
| 383 /// Initializes the test environment. |
| 384 /// |
| 385 /// If [configAutoStart] is `true`, schedule a microtask to run the tests. This |
| 386 /// microtask is expected to run after all the tests are defined. |
| 387 void _ensureInitialized(bool configAutoStart) { |
| 388 if (environment.initialized) return; |
| 389 |
| 390 environment.initialized = true; |
| 391 // Hook our async guard into the matcher library. |
| 392 wrapAsync = (f, [id]) => expectAsync(f, id: id); |
| 393 |
| 394 environment.uncaughtErrorMessage = null; |
| 395 |
| 396 unittestConfiguration.onInit(); |
| 397 |
| 398 // Immediately queue the suite up. It will run after a timeout (i.e. after |
| 399 // main() has returned). |
| 400 if (configAutoStart && config.autoStart) scheduleMicrotask(runTests); |
| 401 } |
| 402 |
| 403 /// Remove all tests other than the one identified by [id]. |
| 404 void setSoloTest(int id) => |
| 405 environment.testCases.retainWhere((t) => t.id == id); |
| 406 |
| 407 /// Enable the test identified by [id]. |
| 408 void enableTest(int id) => _setTestEnabledState(id, enable: true); |
| 409 |
| 410 /// Disable the test by [id]. |
| 411 void disableTest(int id) => _setTestEnabledState(id, enable: false); |
| 412 |
| 413 /// Enable or disable the test identified by [id]. |
| 414 void _setTestEnabledState(int id, {bool enable: true}) { |
| 415 // Try fast path first. |
| 416 if (testCases.length > id && testCases[id].id == id) { |
| 417 environment.testCases[id].enabled = enable; |
| 418 } else { |
| 419 for (var i = 0; i < testCases.length; i++) { |
| 420 if (testCases[i].id != id) continue; |
| 421 environment.testCases[i].enabled = enable; |
| 422 break; |
| 423 } |
| 424 } |
| 425 } |
| 426 |
| 427 /// Throws a [StateError] if tests are running. |
| 428 void _requireNotRunning() { |
| 429 if (environment.currentTestCaseIndex == -1) return; |
| 430 throw new StateError('Not allowed when tests are running.'); |
| 431 } |
| 432 |
| 433 /// Creates a test environment running in its own zone scope. |
| 434 /// |
| 435 /// This allows for multiple invocations of the unittest library in the same |
| 436 /// application instance. This is useful when, for example, creating a test |
| 437 /// runner application which needs to create a new pristine test environment on |
| 438 /// each invocation to run a given set of tests. |
| 439 withTestEnvironment(callback()) { |
| 440 return runZoned(callback, |
| 441 zoneValues: {#unittest.environment: new TestEnvironment()}); |
| 442 } |
| OLD | NEW |