Chromium Code Reviews| 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 /** | 5 /** |
| 6 * Support for writing Dart unit tests. | 6 * Support for writing Dart unit tests. |
| 7 * | 7 * |
| 8 * For information on installing and importing this library, see the | 8 * For information on installing and importing this library, see the |
| 9 * [unittest package on pub.dartlang.org] | 9 * [unittest package on pub.dartlang.org] |
| 10 * (http://pub.dartlang.org/packages/unittest). | 10 * (http://pub.dartlang.org/packages/unittest). |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 139 import 'dart:collection'; | 139 import 'dart:collection'; |
| 140 import 'dart:isolate'; | 140 import 'dart:isolate'; |
| 141 import 'package:stack_trace/stack_trace.dart'; | 141 import 'package:stack_trace/stack_trace.dart'; |
| 142 | 142 |
| 143 import 'matcher.dart'; | 143 import 'matcher.dart'; |
| 144 export 'matcher.dart'; | 144 export 'matcher.dart'; |
| 145 | 145 |
| 146 import 'src/utils.dart'; | 146 import 'src/utils.dart'; |
| 147 | 147 |
| 148 part 'src/configuration.dart'; | 148 part 'src/configuration.dart'; |
| 149 part 'src/group_context.dart'; | |
| 149 part 'src/simple_configuration.dart'; | 150 part 'src/simple_configuration.dart'; |
| 151 part 'src/spread_args_helper.dart'; | |
|
Siggi Cherem (dart-lang)
2014/02/03 18:09:46
I was hoping we can start splitting this into mult
kevmoo
2014/02/03 18:22:57
Absolutely, but in another CL. :-)
| |
| 150 part 'src/test_case.dart'; | 152 part 'src/test_case.dart'; |
| 151 | 153 |
| 152 Configuration _config; | 154 Configuration _config; |
| 153 | 155 |
| 154 /** | 156 /** |
| 155 * [Configuration] used by the unittest library. Note that if a | 157 * [Configuration] used by the unittest library. Note that if a |
| 156 * configuration has not been set, calling this getter will create | 158 * configuration has not been set, calling this getter will create |
| 157 * a default configuration. | 159 * a default configuration. |
| 158 */ | 160 */ |
| 159 Configuration get unittestConfiguration { | 161 Configuration get unittestConfiguration { |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 200 | 202 |
| 201 /** | 203 /** |
| 202 * The set of tests to run can be restricted by using [solo_test] and | 204 * The set of tests to run can be restricted by using [solo_test] and |
| 203 * [solo_group]. | 205 * [solo_group]. |
| 204 * As groups can be nested we use a counter to keep track of the nest level | 206 * As groups can be nested we use a counter to keep track of the nest level |
| 205 * of soloing, and a flag to tell if we have seen any solo tests. | 207 * of soloing, and a flag to tell if we have seen any solo tests. |
| 206 */ | 208 */ |
| 207 int _soloNestingLevel = 0; | 209 int _soloNestingLevel = 0; |
| 208 bool _soloTestSeen = false; | 210 bool _soloTestSeen = false; |
| 209 | 211 |
| 210 /** | |
| 211 * Setup and teardown functions for a group and its parents, the latter | |
| 212 * for chaining. | |
| 213 */ | |
| 214 class _GroupContext { | |
| 215 final _GroupContext parent; | |
| 216 | |
| 217 /** Description text of the current test group. */ | |
| 218 final String _name; | |
| 219 | |
| 220 /** Setup function called before each test in a group. */ | |
| 221 Function _testSetup; | |
| 222 | |
| 223 get testSetup => _testSetup; | |
| 224 | |
| 225 get parentSetup => (parent == null) ? null : parent.testSetup; | |
| 226 | |
| 227 set testSetup(Function setup) { | |
| 228 var preSetup = parentSetup; | |
| 229 if (preSetup == null) { | |
| 230 _testSetup = setup; | |
| 231 } else { | |
| 232 _testSetup = () { | |
| 233 var f = preSetup(); | |
| 234 if (f is Future) { | |
| 235 return f.then((_) => setup()); | |
| 236 } else { | |
| 237 return setup(); | |
| 238 } | |
| 239 }; | |
| 240 } | |
| 241 } | |
| 242 | |
| 243 /** Teardown function called after each test in a group. */ | |
| 244 Function _testTeardown; | |
| 245 | |
| 246 get testTeardown => _testTeardown; | |
| 247 | |
| 248 get parentTeardown => (parent == null) ? null : parent.testTeardown; | |
| 249 | |
| 250 set testTeardown(Function teardown) { | |
| 251 var postTeardown = parentTeardown; | |
| 252 if (postTeardown == null) { | |
| 253 _testTeardown = teardown; | |
| 254 } else { | |
| 255 _testTeardown = () { | |
| 256 var f = teardown(); | |
| 257 if (f is Future) { | |
| 258 return f.then((_) => postTeardown()); | |
| 259 } else { | |
| 260 return postTeardown(); | |
| 261 } | |
| 262 }; | |
| 263 } | |
| 264 } | |
| 265 | |
| 266 String get fullName => (parent == null || parent == _rootContext) | |
| 267 ? _name | |
| 268 : "${parent.fullName}$groupSep$_name"; | |
| 269 | |
| 270 _GroupContext([this.parent, this._name = '']) { | |
| 271 _testSetup = parentSetup; | |
| 272 _testTeardown = parentTeardown; | |
| 273 } | |
| 274 } | |
| 275 | |
| 276 // We use a 'dummy' context for the top level to eliminate null | 212 // We use a 'dummy' context for the top level to eliminate null |
| 277 // checks when querying the context. This allows us to easily | 213 // checks when querying the context. This allows us to easily |
| 278 // support top-level setUp/tearDown functions as well. | 214 // support top-level setUp/tearDown functions as well. |
| 279 final _rootContext = new _GroupContext(); | 215 final _rootContext = new _GroupContext(); |
| 280 _GroupContext _currentContext = _rootContext; | 216 _GroupContext _currentContext = _rootContext; |
| 281 | 217 |
| 282 /** | 218 /** |
| 283 * Represents the index of the currently running test case | 219 * Represents the index of the currently running test case |
| 284 * == -1 implies the test system is not running | 220 * == -1 implies the test system is not running |
| 285 * == [number of test cases] is a short-lived state flagging that the last test | 221 * == [number of test cases] is a short-lived state flagging that the last test |
| (...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 361 } finally { | 297 } finally { |
| 362 --_soloNestingLevel; | 298 --_soloNestingLevel; |
| 363 } | 299 } |
| 364 } | 300 } |
| 365 | 301 |
| 366 /** Sentinel value for [_SpreadArgsHelper]. */ | 302 /** Sentinel value for [_SpreadArgsHelper]. */ |
| 367 class _Sentinel { | 303 class _Sentinel { |
| 368 const _Sentinel(); | 304 const _Sentinel(); |
| 369 } | 305 } |
| 370 | 306 |
| 371 /** Simulates spread arguments using named arguments. */ | |
| 372 // TODO(sigmund): remove this class and simply use a closure with named | |
| 373 // arguments (if still applicable). | |
| 374 class _SpreadArgsHelper { | |
| 375 final Function callback; | |
| 376 final int minExpectedCalls; | |
| 377 final int maxExpectedCalls; | |
| 378 final Function isDone; | |
| 379 final String id; | |
| 380 int actualCalls = 0; | |
| 381 final TestCase testCase; | |
| 382 bool complete; | |
| 383 static const sentinel = const _Sentinel(); | |
| 384 | |
| 385 _SpreadArgsHelper(Function callback, int minExpected, int maxExpected, | |
| 386 Function isDone, String id) | |
| 387 : this.callback = callback, | |
| 388 minExpectedCalls = minExpected, | |
| 389 maxExpectedCalls = (maxExpected == 0 && minExpected > 0) | |
| 390 ? minExpected | |
| 391 : maxExpected, | |
| 392 this.isDone = isDone, | |
| 393 this.testCase = currentTestCase, | |
| 394 this.id = _makeCallbackId(id, callback) { | |
| 395 ensureInitialized(); | |
| 396 if (testCase == null) { | |
| 397 throw new StateError("No valid test. Did you forget to run your test " | |
| 398 "inside a call to test()?"); | |
| 399 } | |
| 400 | |
| 401 if (isDone != null || minExpected > 0) { | |
| 402 testCase._callbackFunctionsOutstanding++; | |
| 403 complete = false; | |
| 404 } else { | |
| 405 complete = true; | |
| 406 } | |
| 407 } | |
| 408 | |
| 409 static String _makeCallbackId(String id, Function callback) { | |
| 410 // Try to create a reasonable id. | |
| 411 if (id != null) { | |
| 412 return "$id "; | |
| 413 } else { | |
| 414 // If the callback is not an anonymous closure, try to get the | |
| 415 // name. | |
| 416 var fname = callback.toString(); | |
| 417 var prefix = "Function '"; | |
| 418 var pos = fname.indexOf(prefix); | |
| 419 if (pos > 0) { | |
| 420 pos += prefix.length; | |
| 421 var epos = fname.indexOf("'", pos); | |
| 422 if (epos > 0) { | |
| 423 return "${fname.substring(pos, epos)} "; | |
| 424 } | |
| 425 } | |
| 426 } | |
| 427 return ''; | |
| 428 } | |
| 429 | |
| 430 bool shouldCallBack() { | |
| 431 ++actualCalls; | |
| 432 if (testCase.isComplete) { | |
| 433 // Don't run if the test is done. We don't throw here as this is not | |
| 434 // the current test, but we do mark the old test as having an error | |
| 435 // if it previously passed. | |
| 436 if (testCase.result == PASS) { | |
| 437 testCase.error( | |
| 438 'Callback ${id}called ($actualCalls) after test case ' | |
| 439 '${testCase.description} has already been marked as ' | |
| 440 '${testCase.result}.'); | |
| 441 } | |
| 442 return false; | |
| 443 } else if (maxExpectedCalls >= 0 && actualCalls > maxExpectedCalls) { | |
| 444 throw new TestFailure('Callback ${id}called more times than expected ' | |
| 445 '($maxExpectedCalls).'); | |
| 446 } | |
| 447 return true; | |
| 448 } | |
| 449 | |
| 450 void after() { | |
| 451 if (!complete) { | |
| 452 if (minExpectedCalls > 0 && actualCalls < minExpectedCalls) return; | |
| 453 if (isDone != null && !isDone()) return; | |
| 454 | |
| 455 // Mark this callback as complete and remove it from the testcase | |
| 456 // oustanding callback count; if that hits zero the testcase is done. | |
| 457 complete = true; | |
| 458 testCase._markCallbackComplete(); | |
| 459 } | |
| 460 } | |
| 461 | |
| 462 invoke0() { | |
| 463 return _guardAsync( | |
| 464 () { | |
| 465 if (shouldCallBack()) { | |
| 466 return callback(); | |
| 467 } | |
| 468 }, | |
| 469 after, testCase); | |
| 470 } | |
| 471 | |
| 472 invoke1(arg1) { | |
| 473 return _guardAsync( | |
| 474 () { | |
| 475 if (shouldCallBack()) { | |
| 476 return callback(arg1); | |
| 477 } | |
| 478 }, | |
| 479 after, testCase); | |
| 480 } | |
| 481 | |
| 482 invoke2(arg1, arg2) { | |
| 483 return _guardAsync( | |
| 484 () { | |
| 485 if (shouldCallBack()) { | |
| 486 return callback(arg1, arg2); | |
| 487 } | |
| 488 }, | |
| 489 after, testCase); | |
| 490 } | |
| 491 } | |
| 492 | |
| 493 /** | 307 /** |
| 494 * Indicate that [callback] is expected to be called a [count] number of times | 308 * Indicate that [callback] is expected to be called a [count] number of times |
| 495 * (by default 1). The unittest framework will wait for the callback to run the | 309 * (by default 1). The unittest framework will wait for the callback to run the |
| 496 * specified [count] times before it continues with the following test. Using | 310 * specified [count] times before it continues with the following test. Using |
| 497 * [expectAsync0] will also ensure that errors that occur within [callback] are | 311 * [expectAsync0] will also ensure that errors that occur within [callback] are |
| 498 * tracked and reported. [callback] should take 0 positional arguments (named | 312 * tracked and reported. [callback] should take 0 positional arguments (named |
| 499 * arguments are not supported). [id] can be used to provide more | 313 * arguments are not supported). [id] can be used to provide more |
| 500 * descriptive error messages if the callback is called more often than | 314 * descriptive error messages if the callback is called more often than |
| 501 * expected. [max] can be used to specify an upper bound on the number of | 315 * expected. [max] can be used to specify an upper bound on the number of |
| 502 * calls; if this is exceeded the test will fail (or be marked as in error if | 316 * calls; if this is exceeded the test will fail (or be marked as in error if |
| 503 * it was already complete). A value of 0 for [max] (the default) will set | 317 * it was already complete). A value of 0 for [max] (the default) will set |
| 504 * the upper bound to the same value as [count]; i.e. the callback should be | 318 * the upper bound to the same value as [count]; i.e. the callback should be |
| 505 * called exactly [count] times. A value of -1 for [max] will mean no upper | 319 * called exactly [count] times. A value of -1 for [max] will mean no upper |
| 506 * bound. | 320 * bound. |
| 507 */ | 321 */ |
| 508 // TODO(sigmund): deprecate this API when issue 2706 is fixed. | 322 // TODO(sigmund): deprecate this API when issue 2706 is fixed. |
|
Siggi Cherem (dart-lang)
2014/02/03 18:09:46
BTW - now that 2706 is fixed, we can fix up a bit
kevmoo
2014/02/03 18:22:57
I tried this awhile ago and failed because of chec
Siggi Cherem (dart-lang)
2014/02/03 22:54:39
yeah - exactly, I remember trying and failing with
| |
| 509 Function expectAsync0(Function callback, | 323 Function expectAsync0(Function callback, |
| 510 {int count: 1, int max: 0, String id}) { | 324 {int count: 1, int max: 0, String id}) { |
| 511 return new _SpreadArgsHelper(callback, count, max, null, id).invoke0; | 325 return new _SpreadArgsHelper(callback, count, max, null, id).invoke0; |
| 512 } | 326 } |
| 513 | 327 |
| 514 /** Like [expectAsync0] but [callback] should take 1 positional argument. */ | 328 /** Like [expectAsync0] but [callback] should take 1 positional argument. */ |
| 515 // TODO(sigmund): deprecate this API when issue 2706 is fixed. | 329 // TODO(sigmund): deprecate this API when issue 2706 is fixed. |
| 516 Function expectAsync1(Function callback, | 330 Function expectAsync1(Function callback, |
| 517 {int count: 1, int max: 0, String id}) { | 331 {int count: 1, int max: 0, String id}) { |
| 518 return new _SpreadArgsHelper(callback, count, max, null, id).invoke1; | 332 return new _SpreadArgsHelper(callback, count, max, null, id).invoke1; |
| (...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 656 _runTest(); | 470 _runTest(); |
| 657 } | 471 } |
| 658 | 472 |
| 659 /** Handle errors that happen outside the tests. */ | 473 /** Handle errors that happen outside the tests. */ |
| 660 // TODO(vsm): figure out how to expose the stack trace here | 474 // TODO(vsm): figure out how to expose the stack trace here |
| 661 // Currently e.message works in dartium, but not in dartc. | 475 // Currently e.message works in dartium, but not in dartc. |
| 662 void handleExternalError(e, String message, [stack]) { | 476 void handleExternalError(e, String message, [stack]) { |
| 663 var msg = '$message\nCaught $e'; | 477 var msg = '$message\nCaught $e'; |
| 664 | 478 |
| 665 if (currentTestCase != null) { | 479 if (currentTestCase != null) { |
| 666 currentTestCase.error(msg, stack); | 480 currentTestCase._error(msg, stack); |
| 667 } else { | 481 } else { |
| 668 _uncaughtErrorMessage = "$msg: $stack"; | 482 _uncaughtErrorMessage = "$msg: $stack"; |
| 669 } | 483 } |
| 670 } | 484 } |
| 671 | 485 |
| 672 void rerunTests() { | 486 void rerunTests() { |
| 673 _uncaughtErrorMessage = null; | 487 _uncaughtErrorMessage = null; |
| 674 _initialized = true; // We don't want to reset the test array. | 488 _initialized = true; // We don't want to reset the test array. |
| 675 runTests(); | 489 runTests(); |
| 676 } | 490 } |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 729 void registerException(e, [trace]) { | 543 void registerException(e, [trace]) { |
| 730 _registerException(currentTestCase, e, trace); | 544 _registerException(currentTestCase, e, trace); |
| 731 } | 545 } |
| 732 | 546 |
| 733 /** | 547 /** |
| 734 * Registers that an exception was caught for the current test. | 548 * Registers that an exception was caught for the current test. |
| 735 */ | 549 */ |
| 736 void _registerException(TestCase testCase, e, [trace]) { | 550 void _registerException(TestCase testCase, e, [trace]) { |
| 737 String message = (e is TestFailure) ? e.message : 'Caught $e'; | 551 String message = (e is TestFailure) ? e.message : 'Caught $e'; |
| 738 if (testCase.result == null) { | 552 if (testCase.result == null) { |
| 739 testCase.fail(message, trace); | 553 testCase._fail(message, trace); |
| 740 } else { | 554 } else { |
| 741 testCase.error(message, trace); | 555 testCase._error(message, trace); |
| 742 } | 556 } |
| 743 } | 557 } |
| 744 | 558 |
| 745 /** | 559 /** |
| 746 * Runs the next test. | 560 * Runs the next test. |
| 747 */ | 561 */ |
| 748 void _runTest() { | 562 void _runTest() { |
| 749 if (_currentTestCaseIndex >= testCases.length) { | 563 if (_currentTestCaseIndex >= testCases.length) { |
| 750 assert(_currentTestCaseIndex == testCases.length); | 564 assert(_currentTestCaseIndex == testCases.length); |
| 751 _completeTests(); | 565 _completeTests(); |
| 752 } else { | 566 } else { |
| 753 var testCase = testCases[_currentTestCaseIndex]; | 567 var testCase = testCases[_currentTestCaseIndex]; |
| 754 Future f = _guardAsync(testCase._run, null, testCase); | 568 Future f = _guardAsync(testCase._run, null, testCase); |
| 755 var timeout = unittestConfiguration.timeout; | 569 var timeout = unittestConfiguration.timeout; |
| 756 | 570 |
| 757 Timer timer; | 571 Timer timer; |
| 758 if (timeout != null) { | 572 if (timeout != null) { |
| 759 try { | 573 try { |
| 760 timer = new Timer(timeout, () { | 574 timer = new Timer(timeout, () { |
| 761 testCase.error("Test timed out after ${timeout.inSeconds} seconds."); | 575 testCase._error("Test timed out after ${timeout.inSeconds} seconds."); |
| 762 _nextTestCase(); | 576 _nextTestCase(); |
| 763 }); | 577 }); |
| 764 } on UnsupportedError catch (e) { | 578 } on UnsupportedError catch (e) { |
| 765 if (e.message != "Timer greater than 0.") rethrow; | 579 if (e.message != "Timer greater than 0.") rethrow; |
| 766 // Support running on d8 and jsshell which don't support timers. | 580 // Support running on d8 and jsshell which don't support timers. |
| 767 } | 581 } |
| 768 } | 582 } |
| 769 f.whenComplete(() { | 583 f.whenComplete(() { |
| 770 if (timer != null) timer.cancel(); | 584 if (timer != null) timer.cancel(); |
| 771 var now = new DateTime.now().millisecondsSinceEpoch; | 585 var now = new DateTime.now().millisecondsSinceEpoch; |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 833 } | 647 } |
| 834 | 648 |
| 835 /** Select a solo test by ID. */ | 649 /** Select a solo test by ID. */ |
| 836 void setSoloTest(int id) => | 650 void setSoloTest(int id) => |
| 837 _testCases.retainWhere((t) => t.id == id); | 651 _testCases.retainWhere((t) => t.id == id); |
| 838 | 652 |
| 839 /** Enable/disable a test by ID. */ | 653 /** Enable/disable a test by ID. */ |
| 840 void _setTestEnabledState(int testId, bool state) { | 654 void _setTestEnabledState(int testId, bool state) { |
| 841 // Try fast path first. | 655 // Try fast path first. |
| 842 if (testCases.length > testId && testCases[testId].id == testId) { | 656 if (testCases.length > testId && testCases[testId].id == testId) { |
| 843 testCases[testId].enabled = state; | 657 testCases[testId]._enabled = state; |
| 844 } else { | 658 } else { |
| 845 for (var i = 0; i < testCases.length; i++) { | 659 for (var i = 0; i < testCases.length; i++) { |
| 846 if (testCases[i].id == testId) { | 660 if (testCases[i].id == testId) { |
| 847 testCases[i].enabled = state; | 661 testCases[i]._enabled = state; |
| 848 break; | 662 break; |
| 849 } | 663 } |
| 850 } | 664 } |
| 851 } | 665 } |
| 852 } | 666 } |
| 853 | 667 |
| 854 /** Enable a test by ID. */ | 668 /** Enable a test by ID. */ |
| 855 void enableTest(int testId) => _setTestEnabledState(testId, true); | 669 void enableTest(int testId) => _setTestEnabledState(testId, true); |
| 856 | 670 |
| 857 /** Disable a test by ID. */ | 671 /** Disable a test by ID. */ |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 897 | 711 |
| 898 if (!filterStacks) return trace; | 712 if (!filterStacks) return trace; |
| 899 | 713 |
| 900 // Format the stack trace by removing everything above TestCase._runTest, | 714 // Format the stack trace by removing everything above TestCase._runTest, |
| 901 // which is usually going to be irrelevant. Also fold together unittest and | 715 // which is usually going to be irrelevant. Also fold together unittest and |
| 902 // core library calls so only the function the user called is visible. | 716 // core library calls so only the function the user called is visible. |
| 903 return new Trace(trace.frames.takeWhile((frame) { | 717 return new Trace(trace.frames.takeWhile((frame) { |
| 904 return frame.package != 'unittest' || frame.member != 'TestCase._runTest'; | 718 return frame.package != 'unittest' || frame.member != 'TestCase._runTest'; |
| 905 })).terse.foldFrames((frame) => frame.package == 'unittest' || frame.isCore); | 719 })).terse.foldFrames((frame) => frame.package == 'unittest' || frame.isCore); |
| 906 } | 720 } |
| OLD | NEW |