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 * A library for writing dart unit tests. | 6 * A library for writing dart unit tests. |
7 * | 7 * |
8 * ## Installing ## | 8 * ## Installing ## |
9 * | 9 * |
10 * Use [pub][] to install this package. Add the following to your `pubspec.yaml` | 10 * Use [pub][] to install this package. Add the following to your `pubspec.yaml` |
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
198 } | 198 } |
199 } | 199 } |
200 | 200 |
201 /** | 201 /** |
202 * Can be called by tests to log status. Tests should use this | 202 * Can be called by tests to log status. Tests should use this |
203 * instead of [print]. | 203 * instead of [print]. |
204 */ | 204 */ |
205 void logMessage(String message) => | 205 void logMessage(String message) => |
206 _config.onLogMessage(currentTestCase, message); | 206 _config.onLogMessage(currentTestCase, message); |
207 | 207 |
208 /** | |
209 * Description text of the current test group. If multiple groups are nested, | |
210 * this will contain all of their text concatenated. | |
211 */ | |
212 String _currentGroup = ''; | |
213 | |
214 /** Separator used between group names and test names. */ | 208 /** Separator used between group names and test names. */ |
215 String groupSep = ' '; | 209 String groupSep = ' '; |
216 | 210 |
217 final List<TestCase> _testCases = new List<TestCase>(); | 211 final List<TestCase> _testCases = new List<TestCase>(); |
218 | 212 |
219 /** Tests executed in this suite. */ | 213 /** Tests executed in this suite. */ |
220 final List<TestCase> testCases = new UnmodifiableListView<TestCase>(_testCases); | 214 final List<TestCase> testCases = new UnmodifiableListView<TestCase>(_testCases); |
221 | 215 |
222 /** Setup function called before each test in a group */ | 216 /** |
223 Function _testSetup; | 217 * Setup and teardown functions for a group and its parents, the latter |
| 218 * for chaining. |
| 219 */ |
| 220 class GroupContext { |
| 221 /** Setup function called before each test in a group. */ |
| 222 Function testSetup = null; |
224 | 223 |
225 /** Teardown function called after each test in a group */ | 224 /** Teardown function called after each test in a group. */ |
226 Function _testTeardown; | 225 Function testTeardown = null; |
| 226 |
| 227 /** Setup and teardown functions of parent group, for chaining. */ |
| 228 Function parentSetup = null; |
| 229 Function parentTeardown = null; |
| 230 |
| 231 /** |
| 232 * Description text of the current test group. If multiple groups are nested, |
| 233 * this will contain all of their text concatenated. |
| 234 */ |
| 235 String groupName = ''; |
| 236 } |
| 237 |
| 238 GroupContext _currentContext = new GroupContext(); |
227 | 239 |
228 int _currentTestCaseIndex = 0; | 240 int _currentTestCaseIndex = 0; |
229 | 241 |
230 /** [TestCase] currently being executed. */ | 242 /** [TestCase] currently being executed. */ |
231 TestCase get currentTestCase => | 243 TestCase get currentTestCase => |
232 (_currentTestCaseIndex >= 0 && _currentTestCaseIndex < testCases.length) | 244 (_currentTestCaseIndex >= 0 && _currentTestCaseIndex < testCases.length) |
233 ? testCases[_currentTestCaseIndex] | 245 ? testCases[_currentTestCaseIndex] |
234 : null; | 246 : null; |
235 | 247 |
236 /** Whether the framework is in an initialized state. */ | 248 /** Whether the framework is in an initialized state. */ |
(...skipping 352 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
589 Function protectAsync2(Function callback, {String id}) { | 601 Function protectAsync2(Function callback, {String id}) { |
590 return new _SpreadArgsHelper(callback, 0, -1, null, id).invoke2; | 602 return new _SpreadArgsHelper(callback, 0, -1, null, id).invoke2; |
591 } | 603 } |
592 | 604 |
593 /** | 605 /** |
594 * Creates a new named group of tests. Calls to group() or test() within the | 606 * Creates a new named group of tests. Calls to group() or test() within the |
595 * body of the function passed to this will inherit this group's description. | 607 * body of the function passed to this will inherit this group's description. |
596 */ | 608 */ |
597 void group(String description, void body()) { | 609 void group(String description, void body()) { |
598 ensureInitialized(); | 610 ensureInitialized(); |
| 611 // Groups can be nested, so we need to preserve the current |
| 612 // settings for test setup/teardown. We use a local copy here so we |
| 613 // can nest multiple levels; we also have the global parent variables |
| 614 // which are used for chaining. |
| 615 var oldContext = _currentContext; |
| 616 _currentContext = new GroupContext(); |
| 617 _currentContext.testSetup = _currentContext.parentSetup = |
| 618 oldContext.testSetup; |
| 619 _currentContext.testTeardown = _currentContext.parentTeardown = |
| 620 oldContext.testTeardown; |
| 621 |
599 // Concatenate the new group. | 622 // Concatenate the new group. |
600 final parentGroup = _currentGroup; | 623 if (oldContext.groupName != '') { |
601 if (_currentGroup != '') { | |
602 // Add a space. | 624 // Add a space. |
603 _currentGroup = '$_currentGroup$groupSep$description'; | 625 _currentContext.groupName = '${oldContext.groupName}$groupSep$description'; |
604 } else { | 626 } else { |
605 // The first group. | 627 // The first group. |
606 _currentGroup = description; | 628 _currentContext.groupName = description; |
607 } | 629 } |
608 | 630 |
609 // Groups can be nested, so we need to preserve the current | |
610 // settings for test setup/teardown. | |
611 Function parentSetup = _testSetup; | |
612 Function parentTeardown = _testTeardown; | |
613 | 631 |
614 try { | 632 try { |
615 _testSetup = null; | |
616 _testTeardown = null; | |
617 body(); | 633 body(); |
618 } catch (e, trace) { | 634 } catch (e, trace) { |
619 var stack = (trace == null) ? '' : ': ${trace.toString()}'; | 635 var stack = (trace == null) ? '' : ': ${trace.toString()}'; |
620 _uncaughtErrorMessage = "${e.toString()}$stack"; | 636 _uncaughtErrorMessage = "${e.toString()}$stack"; |
621 } finally { | 637 } finally { |
622 // Now that the group is over, restore the previous one. | 638 // Now that the group is over, restore the previous one. |
623 _currentGroup = parentGroup; | 639 _currentContext = oldContext; |
624 _testSetup = parentSetup; | |
625 _testTeardown = parentTeardown; | |
626 } | 640 } |
627 } | 641 } |
628 | 642 |
629 /** | 643 /** |
630 * Register a [setUp] function for a test [group]. This function will | 644 * Register a [setUp] function for a test [group]. This function will |
631 * be called before each test in the group is run. Note that if groups | 645 * be called before each test in the group is run. |
632 * are nested only the most locally scoped [setUpTest] function will be run. | |
633 * [setUp] and [tearDown] should be called within the [group] before any | 646 * [setUp] and [tearDown] should be called within the [group] before any |
634 * calls to [test]. The [setupTest] function can be asynchronous; in this | 647 * calls to [test]. The [setupTest] function can be asynchronous; in this |
635 * case it must return a [Future]. | 648 * case it must return a [Future]. |
636 */ | 649 */ |
637 void setUp(Function setupTest) { | 650 void setUp(Function setupTest) { |
638 _testSetup = setupTest; | 651 var parent = _currentContext.parentSetup; |
| 652 _currentContext.testSetup = () { |
| 653 var f = parent == null ? null : parent(); |
| 654 if (f is Future) { |
| 655 return f.then((_) => setupTest()); |
| 656 } else { |
| 657 return setupTest(); |
| 658 } |
| 659 }; |
639 } | 660 } |
640 | 661 |
641 /** | 662 /** |
642 * Register a [tearDown] function for a test [group]. This function will | 663 * Register a [tearDown] function for a test [group]. This function will |
643 * be called after each test in the group is run. Note that if groups | 664 * be called after each test in the group is run. Note that if groups |
644 * are nested only the most locally scoped [teardownTest] function will be run. | 665 * are nested only the most locally scoped [teardownTest] function will be run. |
645 * [setUp] and [tearDown] should be called within the [group] before any | 666 * [setUp] and [tearDown] should be called within the [group] before any |
646 * calls to [test]. The [teardownTest] function can be asynchronous; in this | 667 * calls to [test]. The [teardownTest] function can be asynchronous; in this |
647 * case it must return a [Future]. | 668 * case it must return a [Future]. |
648 */ | 669 */ |
649 void tearDown(Function teardownTest) { | 670 void tearDown(Function teardownTest) { |
650 _testTeardown = teardownTest; | 671 var parent = _currentContext.parentTeardown; |
| 672 _currentContext.testTeardown = () { |
| 673 var f = teardownTest(); |
| 674 if (parent == null) return f; |
| 675 if (f is Future) { |
| 676 // TODO(gram): as _parentTeardown is a global, do we need |
| 677 // to first take a local copy so the value is fixed at the |
| 678 // point that tearDown is called? |
| 679 return f.then((_) => parent()); |
| 680 } else { |
| 681 return parent(); |
| 682 } |
| 683 }; |
651 } | 684 } |
652 | 685 |
653 /** Advance to the next test case. */ | 686 /** Advance to the next test case. */ |
654 void _nextTestCase() { | 687 void _nextTestCase() { |
655 _defer(() { | 688 _defer(() { |
656 _currentTestCaseIndex++; | 689 _currentTestCaseIndex++; |
657 _nextBatch(); | 690 _nextBatch(); |
658 }); | 691 }); |
659 } | 692 } |
660 | 693 |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
705 } else if (testFilter is Function) { | 738 } else if (testFilter is Function) { |
706 filterFunction = testFilter; | 739 filterFunction = testFilter; |
707 } | 740 } |
708 _testCases.retainWhere(filterFunction); | 741 _testCases.retainWhere(filterFunction); |
709 } | 742 } |
710 | 743 |
711 /** Runs all queued tests, one at a time. */ | 744 /** Runs all queued tests, one at a time. */ |
712 void runTests() { | 745 void runTests() { |
713 _ensureInitialized(false); | 746 _ensureInitialized(false); |
714 _currentTestCaseIndex = 0; | 747 _currentTestCaseIndex = 0; |
715 _currentGroup = ''; | 748 _currentContext = new GroupContext(); |
716 | 749 |
717 // If we are soloing a test, remove all the others. | 750 // If we are soloing a test, remove all the others. |
718 if (_soloTest != null) { | 751 if (_soloTest != null) { |
719 filterTests((t) => t == _soloTest); | 752 filterTests((t) => t == _soloTest); |
720 } | 753 } |
721 | 754 |
722 _config.onStart(); | 755 _config.onStart(); |
723 | 756 |
724 _defer(() { | 757 _defer(() { |
725 _nextBatch(); | 758 _nextBatch(); |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
804 case ERROR: errors++; break; | 837 case ERROR: errors++; break; |
805 } | 838 } |
806 } | 839 } |
807 _config.onSummary(passed, failed, errors, testCases, _uncaughtErrorMessage); | 840 _config.onSummary(passed, failed, errors, testCases, _uncaughtErrorMessage); |
808 _config.onDone(passed > 0 && failed == 0 && errors == 0 && | 841 _config.onDone(passed > 0 && failed == 0 && errors == 0 && |
809 _uncaughtErrorMessage == null); | 842 _uncaughtErrorMessage == null); |
810 _initialized = false; | 843 _initialized = false; |
811 } | 844 } |
812 | 845 |
813 String _fullSpec(String spec) { | 846 String _fullSpec(String spec) { |
814 if (spec == null) return '$_currentGroup'; | 847 var group = '${_currentContext.groupName}'; |
815 return _currentGroup != '' ? '$_currentGroup$groupSep$spec' : spec; | 848 if (spec == null) return group; |
| 849 return group != '' ? '$group$groupSep$spec' : spec; |
816 } | 850 } |
817 | 851 |
818 /** | 852 /** |
819 * Lazily initializes the test library if not already initialized. | 853 * Lazily initializes the test library if not already initialized. |
820 */ | 854 */ |
821 void ensureInitialized() { | 855 void ensureInitialized() { |
822 _ensureInitialized(true); | 856 _ensureInitialized(true); |
823 } | 857 } |
824 | 858 |
825 void _ensureInitialized(bool configAutoStart) { | 859 void _ensureInitialized(bool configAutoStart) { |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
870 } | 904 } |
871 | 905 |
872 /** Enable a test by ID. */ | 906 /** Enable a test by ID. */ |
873 void enableTest(int testId) => _setTestEnabledState(testId, true); | 907 void enableTest(int testId) => _setTestEnabledState(testId, true); |
874 | 908 |
875 /** Disable a test by ID. */ | 909 /** Disable a test by ID. */ |
876 void disableTest(int testId) => _setTestEnabledState(testId, false); | 910 void disableTest(int testId) => _setTestEnabledState(testId, false); |
877 | 911 |
878 /** Signature for a test function. */ | 912 /** Signature for a test function. */ |
879 typedef dynamic TestFunction(); | 913 typedef dynamic TestFunction(); |
OLD | NEW |