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 * To import this library, install the | 8 * To import this library, install the |
9 * [unittest package](http://pub.dartlang.org/packages/unittest) via the pub | 9 * [unittest package](http://pub.dartlang.org/packages/unittest) via the pub |
10 * package manager. See the [Getting Started](http://pub.dartlang.org/doc) | 10 * package manager. See the [Getting Started](http://pub.dartlang.org/doc) |
(...skipping 137 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
148 * async.complete(); | 148 * async.complete(); |
149 * }); | 149 * }); |
150 * }); | 150 * }); |
151 * } | 151 * } |
152 * | 152 * |
153 */ | 153 */ |
154 library unittest; | 154 library unittest; |
155 | 155 |
156 import 'dart:async'; | 156 import 'dart:async'; |
157 import 'dart:isolate'; | 157 import 'dart:isolate'; |
| 158 import 'dart:collection'; |
158 import 'matcher.dart'; | 159 import 'matcher.dart'; |
159 export 'matcher.dart'; | 160 export 'matcher.dart'; |
160 | 161 |
161 // TODO(amouravski): We should not need to import mock here, but it's necessary | 162 // TODO(amouravski): We should not need to import mock here, but it's necessary |
162 // to enable dartdoc on the mock library, as it's not picked up normally. | 163 // to enable dartdoc on the mock library, as it's not picked up normally. |
163 import 'mock.dart'; | 164 import 'mock.dart'; |
164 | 165 |
165 part 'src/config.dart'; | 166 part 'src/config.dart'; |
166 part 'src/test_case.dart'; | 167 part 'src/test_case.dart'; |
167 | 168 |
(...skipping 21 matching lines...) Expand all Loading... |
189 /** | 190 /** |
190 * Description text of the current test group. If multiple groups are nested, | 191 * Description text of the current test group. If multiple groups are nested, |
191 * this will contain all of their text concatenated. | 192 * this will contain all of their text concatenated. |
192 */ | 193 */ |
193 String _currentGroup = ''; | 194 String _currentGroup = ''; |
194 | 195 |
195 /** Separator used between group names and test names. */ | 196 /** Separator used between group names and test names. */ |
196 String groupSep = ' '; | 197 String groupSep = ' '; |
197 | 198 |
198 /** Tests executed in this suite. */ | 199 /** Tests executed in this suite. */ |
199 List<TestCase> _tests; | 200 final List<TestCase> _testCases = new List<TestCase>(); |
200 | 201 |
201 /** Get the list of tests. */ | 202 /** Get the list of tests. */ |
202 List<TestCase> get testCases => _tests; | 203 final List<TestCase> testCases = new UnmodifiableListView(_testCases); |
203 | 204 |
204 /** Setup function called before each test in a group */ | 205 /** Setup function called before each test in a group */ |
205 Function _testSetup; | 206 Function _testSetup; |
206 | 207 |
207 /** Teardown function called after each test in a group */ | 208 /** Teardown function called after each test in a group */ |
208 Function _testTeardown; | 209 Function _testTeardown; |
209 | 210 |
210 /** Current test being executed. */ | 211 int _currentTestCaseIndex = 0; |
211 int _currentTest = 0; | |
212 TestCase _currentTestCase; | |
213 | 212 |
| 213 /** [TestCase] currently being executed. */ |
214 TestCase get currentTestCase => | 214 TestCase get currentTestCase => |
215 (_currentTest >= 0 && _currentTest < _tests.length) | 215 (_currentTestCaseIndex >= 0 && _currentTestCaseIndex < _testCases.length) |
216 ? _tests[_currentTest] | 216 ? _testCases[_currentTestCaseIndex] |
217 : null; | 217 : null; |
218 | 218 |
219 /** Whether the framework is in an initialized state. */ | 219 /** Whether the framework is in an initialized state. */ |
220 bool _initialized = false; | 220 bool _initialized = false; |
221 | 221 |
222 String _uncaughtErrorMessage = null; | 222 String _uncaughtErrorMessage = null; |
223 | 223 |
224 /** Test case result strings. */ | 224 /** Test case result strings. */ |
225 // TODO(gram) we should change these constants to use a different string | 225 // TODO(gram) we should change these constants to use a different string |
226 // (so that writing 'FAIL' in the middle of a test doesn't | 226 // (so that writing 'FAIL' in the middle of a test doesn't |
(...skipping 15 matching lines...) Expand all Loading... |
242 */ | 242 */ |
243 Map testState = {}; | 243 Map testState = {}; |
244 | 244 |
245 /** | 245 /** |
246 * Creates a new test case with the given description and body. The | 246 * Creates a new test case with the given description and body. The |
247 * description will include the descriptions of any surrounding group() | 247 * description will include the descriptions of any surrounding group() |
248 * calls. | 248 * calls. |
249 */ | 249 */ |
250 void test(String spec, TestFunction body) { | 250 void test(String spec, TestFunction body) { |
251 ensureInitialized(); | 251 ensureInitialized(); |
252 _tests.add(new TestCase._internal(_tests.length + 1, _fullSpec(spec), body)); | 252 _testCases.add(new TestCase._internal(_testCases.length + 1, _fullSpec(spec), |
| 253 body)); |
253 } | 254 } |
254 | 255 |
255 /** | 256 /** |
256 * Creates a new test case with the given description and body. The | 257 * Creates a new test case with the given description and body. The |
257 * description will include the descriptions of any surrounding group() | 258 * description will include the descriptions of any surrounding group() |
258 * calls. | 259 * calls. |
259 * | 260 * |
260 * "solo_" means that this will be the only test that is run. All other tests | 261 * "solo_" means that this will be the only test that is run. All other tests |
261 * will be skipped. This is a convenience function to let you quickly isolate | 262 * will be skipped. This is a convenience function to let you quickly isolate |
262 * a single test by adding "solo_" before it to temporarily disable all other | 263 * a single test by adding "solo_" before it to temporarily disable all other |
263 * tests. | 264 * tests. |
264 */ | 265 */ |
265 void solo_test(String spec, TestFunction body) { | 266 void solo_test(String spec, TestFunction body) { |
266 // TODO(rnystrom): Support multiple solos. If more than one test is solo-ed, | 267 // TODO(rnystrom): Support multiple solos. If more than one test is solo-ed, |
267 // all of the solo-ed tests and none of the non-solo-ed ones should run. | 268 // all of the solo-ed tests and none of the non-solo-ed ones should run. |
268 if (_soloTest != null) { | 269 if (_soloTest != null) { |
269 throw new Exception('Only one test can be soloed right now.'); | 270 throw new Exception('Only one test can be soloed right now.'); |
270 } | 271 } |
271 | 272 |
272 ensureInitialized(); | 273 ensureInitialized(); |
273 | 274 |
274 _soloTest = new TestCase._internal(_tests.length + 1, _fullSpec(spec), body); | 275 _soloTest = new TestCase._internal(_testCases.length + 1, _fullSpec(spec), bod
y); |
275 _tests.add(_soloTest); | 276 _testCases.add(_soloTest); |
276 } | 277 } |
277 | 278 |
278 /** Sentinel value for [_SpreadArgsHelper]. */ | 279 /** Sentinel value for [_SpreadArgsHelper]. */ |
279 class _Sentinel { | 280 class _Sentinel { |
280 const _Sentinel(); | 281 const _Sentinel(); |
281 } | 282 } |
282 | 283 |
283 /** Simulates spread arguments using named arguments. */ | 284 /** Simulates spread arguments using named arguments. */ |
284 // TODO(sigmund): remove this class and simply use a closure with named | 285 // TODO(sigmund): remove this class and simply use a closure with named |
285 // arguments (if still applicable). | 286 // arguments (if still applicable). |
(...skipping 10 matching lines...) Expand all Loading... |
296 static const sentinel = const _Sentinel(); | 297 static const sentinel = const _Sentinel(); |
297 | 298 |
298 _SpreadArgsHelper(Function callback, int minExpected, int maxExpected, | 299 _SpreadArgsHelper(Function callback, int minExpected, int maxExpected, |
299 Function isDone, String id) | 300 Function isDone, String id) |
300 : this.callback = callback, | 301 : this.callback = callback, |
301 minExpectedCalls = minExpected, | 302 minExpectedCalls = minExpected, |
302 maxExpectedCalls = (maxExpected == 0 && minExpected > 0) | 303 maxExpectedCalls = (maxExpected == 0 && minExpected > 0) |
303 ? minExpected | 304 ? minExpected |
304 : maxExpected, | 305 : maxExpected, |
305 this.isDone = isDone, | 306 this.isDone = isDone, |
306 testNum = _currentTest, | 307 testNum = _currentTestCaseIndex, |
307 this.id = _makeCallbackId(id, callback) { | 308 this.id = _makeCallbackId(id, callback) { |
308 ensureInitialized(); | 309 ensureInitialized(); |
309 if (!(_currentTest >= 0 && | 310 if (!(_currentTestCaseIndex >= 0 && |
310 _currentTest < _tests.length && | 311 _currentTestCaseIndex < _testCases.length && |
311 _tests[_currentTest] != null)) { | 312 _testCases[_currentTestCaseIndex] != null)) { |
312 print("No valid test, did you forget to run your test inside a call " | 313 print("No valid test, did you forget to run your test inside a call " |
313 "to test()?"); | 314 "to test()?"); |
314 } | 315 } |
315 assert(_currentTest >= 0 && | 316 assert(_currentTestCaseIndex >= 0 && |
316 _currentTest < _tests.length && | 317 _currentTestCaseIndex < _testCases.length && |
317 _tests[_currentTest] != null); | 318 _testCases[_currentTestCaseIndex] != null); |
318 testCase = _tests[_currentTest]; | 319 testCase = _testCases[_currentTestCaseIndex]; |
319 if (isDone != null || minExpected > 0) { | 320 if (isDone != null || minExpected > 0) { |
320 testCase._callbackFunctionsOutstanding++; | 321 testCase._callbackFunctionsOutstanding++; |
321 complete = false; | 322 complete = false; |
322 } else { | 323 } else { |
323 complete = true; | 324 complete = true; |
324 } | 325 } |
325 } | 326 } |
326 | 327 |
327 static _makeCallbackId(String id, Function callback) { | 328 static _makeCallbackId(String id, Function callback) { |
328 // Try to create a reasonable id. | 329 // Try to create a reasonable id. |
(...skipping 298 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
627 * calls to [test]. The [teardownTest] function can be asynchronous; in this | 628 * calls to [test]. The [teardownTest] function can be asynchronous; in this |
628 * case it must return a [Future]. | 629 * case it must return a [Future]. |
629 */ | 630 */ |
630 void tearDown(Function teardownTest) { | 631 void tearDown(Function teardownTest) { |
631 _testTeardown = teardownTest; | 632 _testTeardown = teardownTest; |
632 } | 633 } |
633 | 634 |
634 /** Advance to the next test case. */ | 635 /** Advance to the next test case. */ |
635 void _nextTestCase() { | 636 void _nextTestCase() { |
636 _defer(() { | 637 _defer(() { |
637 _currentTest++; | 638 _currentTestCaseIndex++; |
638 _nextBatch(); | 639 _nextBatch(); |
639 }); | 640 }); |
640 } | 641 } |
641 | 642 |
642 /** | 643 /** |
643 * Utility function that can be used to notify the test framework that an | 644 * Utility function that can be used to notify the test framework that an |
644 * error was caught outside of this library. | 645 * error was caught outside of this library. |
645 */ | 646 */ |
646 void _reportTestError(String msg, String trace) { | 647 void _reportTestError(String msg, String trace) { |
647 if (_currentTest < _tests.length) { | 648 if (_currentTestCaseIndex < _testCases.length) { |
648 final testCase = _tests[_currentTest]; | 649 final testCase = _testCases[_currentTestCaseIndex]; |
649 testCase.error(msg, trace); | 650 testCase.error(msg, trace); |
650 } else { | 651 } else { |
651 _uncaughtErrorMessage = "$msg: $trace"; | 652 _uncaughtErrorMessage = "$msg: $trace"; |
652 } | 653 } |
653 } | 654 } |
654 | 655 |
655 /** | 656 /** |
656 * Runs [callback] at the end of the event loop. Note that we don't wrap | 657 * Runs [callback] at the end of the event loop. Note that we don't wrap |
657 * the callback in guardAsync; this is for test framework functions which | 658 * the callback in guardAsync; this is for test framework functions which |
658 * should not be throwing unexpected exceptions that end up failing test | 659 * should not be throwing unexpected exceptions that end up failing test |
(...skipping 20 matching lines...) Expand all Loading... |
679 void filterTests(testFilter) { | 680 void filterTests(testFilter) { |
680 var filterFunction; | 681 var filterFunction; |
681 if (testFilter is String) { | 682 if (testFilter is String) { |
682 RegExp re = new RegExp(testFilter); | 683 RegExp re = new RegExp(testFilter); |
683 filterFunction = (t) => re.hasMatch(t.description); | 684 filterFunction = (t) => re.hasMatch(t.description); |
684 } else if (testFilter is RegExp) { | 685 } else if (testFilter is RegExp) { |
685 filterFunction = (t) => testFilter.hasMatch(t.description); | 686 filterFunction = (t) => testFilter.hasMatch(t.description); |
686 } else if (testFilter is Function) { | 687 } else if (testFilter is Function) { |
687 filterFunction = testFilter; | 688 filterFunction = testFilter; |
688 } | 689 } |
689 _tests.retainWhere(filterFunction); | 690 _testCases.retainWhere(filterFunction); |
690 } | 691 } |
691 | 692 |
692 /** Runs all queued tests, one at a time. */ | 693 /** Runs all queued tests, one at a time. */ |
693 void runTests() { | 694 void runTests() { |
694 _currentTest = 0; | 695 _currentTestCaseIndex = 0; |
695 _currentGroup = ''; | 696 _currentGroup = ''; |
696 | 697 |
697 // If we are soloing a test, remove all the others. | 698 // If we are soloing a test, remove all the others. |
698 if (_soloTest != null) { | 699 if (_soloTest != null) { |
699 filterTests((t) => t == _soloTest); | 700 filterTests((t) => t == _soloTest); |
700 } | 701 } |
701 | 702 |
702 _config.onStart(); | 703 _config.onStart(); |
703 | 704 |
704 _defer(() { | 705 _defer(() { |
705 _nextBatch(); | 706 _nextBatch(); |
706 }); | 707 }); |
707 } | 708 } |
708 | 709 |
709 /** | 710 /** |
710 * Run [tryBody] guarded in a try-catch block. If an exception is thrown, it is | 711 * Run [tryBody] guarded in a try-catch block. If an exception is thrown, it is |
711 * passed to the corresponding test. | 712 * passed to the corresponding test. |
712 * | 713 * |
713 * The value returned by [tryBody] (if any) is returned by [guardAsync]. | 714 * The value returned by [tryBody] (if any) is returned by [guardAsync]. |
714 */ | 715 */ |
715 guardAsync(Function tryBody) { | 716 guardAsync(Function tryBody) { |
716 return _guardAsync(tryBody, null, _currentTest); | 717 return _guardAsync(tryBody, null, _currentTestCaseIndex); |
717 } | 718 } |
718 | 719 |
719 _guardAsync(Function tryBody, Function finallyBody, int testNum) { | 720 _guardAsync(Function tryBody, Function finallyBody, int testNum) { |
720 assert(testNum >= 0); | 721 assert(testNum >= 0); |
721 try { | 722 try { |
722 return tryBody(); | 723 return tryBody(); |
723 } catch (e, trace) { | 724 } catch (e, trace) { |
724 _registerException(testNum, e, trace); | 725 _registerException(testNum, e, trace); |
725 } finally { | 726 } finally { |
726 if (finallyBody != null) finallyBody(); | 727 if (finallyBody != null) finallyBody(); |
727 } | 728 } |
728 } | 729 } |
729 | 730 |
730 /** | 731 /** |
731 * Registers that an exception was caught for the current test. | 732 * Registers that an exception was caught for the current test. |
732 */ | 733 */ |
733 void registerException(e, [trace]) { | 734 void registerException(e, [trace]) { |
734 _registerException(_currentTest, e, trace); | 735 _registerException(_currentTestCaseIndex, e, trace); |
735 } | 736 } |
736 | 737 |
737 /** | 738 /** |
738 * Registers that an exception was caught for the current test. | 739 * Registers that an exception was caught for the current test. |
739 */ | 740 */ |
740 void _registerException(testNum, e, [trace]) { | 741 void _registerException(testNum, e, [trace]) { |
741 trace = trace == null ? '' : trace.toString(); | 742 trace = trace == null ? '' : trace.toString(); |
742 String message = (e is TestFailure) ? e.message : 'Caught $e'; | 743 String message = (e is TestFailure) ? e.message : 'Caught $e'; |
743 if (_tests[testNum].result == null) { | 744 if (_testCases[testNum].result == null) { |
744 _tests[testNum].fail(message, trace); | 745 _testCases[testNum].fail(message, trace); |
745 } else { | 746 } else { |
746 _tests[testNum].error(message, trace); | 747 _testCases[testNum].error(message, trace); |
747 } | 748 } |
748 } | 749 } |
749 | 750 |
750 /** | 751 /** |
751 * Runs a batch of tests, yielding whenever an asynchronous test starts | 752 * Runs a batch of tests, yielding whenever an asynchronous test starts |
752 * running. Tests will resume executing when such asynchronous test calls | 753 * running. Tests will resume executing when such asynchronous test calls |
753 * [done] or if it fails with an exception. | 754 * [done] or if it fails with an exception. |
754 */ | 755 */ |
755 void _nextBatch() { | 756 void _nextBatch() { |
756 while (true) { | 757 while (true) { |
757 if (_currentTest >= _tests.length) { | 758 if (_currentTestCaseIndex >= _testCases.length) { |
758 _completeTests(); | 759 _completeTests(); |
759 break; | 760 break; |
760 } | 761 } |
761 final testCase = _tests[_currentTest]; | 762 final testCase = _testCases[_currentTestCaseIndex]; |
762 var f = _guardAsync(testCase._run, null, _currentTest); | 763 var f = _guardAsync(testCase._run, null, _currentTestCaseIndex); |
763 if (f != null) { | 764 if (f != null) { |
764 f.whenComplete(() { | 765 f.whenComplete(() { |
765 _nextTestCase(); // Schedule the next test. | 766 _nextTestCase(); // Schedule the next test. |
766 }); | 767 }); |
767 break; | 768 break; |
768 } | 769 } |
769 _currentTest++; | 770 _currentTestCaseIndex++; |
770 } | 771 } |
771 } | 772 } |
772 | 773 |
773 /** Publish results on the page and notify controller. */ | 774 /** Publish results on the page and notify controller. */ |
774 void _completeTests() { | 775 void _completeTests() { |
775 if (!_initialized) return; | 776 if (!_initialized) return; |
776 int passed = 0; | 777 int passed = 0; |
777 int failed = 0; | 778 int failed = 0; |
778 int errors = 0; | 779 int errors = 0; |
779 | 780 |
780 for (TestCase t in _tests) { | 781 for (TestCase t in _testCases) { |
781 switch (t.result) { | 782 switch (t.result) { |
782 case PASS: passed++; break; | 783 case PASS: passed++; break; |
783 case FAIL: failed++; break; | 784 case FAIL: failed++; break; |
784 case ERROR: errors++; break; | 785 case ERROR: errors++; break; |
785 } | 786 } |
786 } | 787 } |
787 _config.onSummary(passed, failed, errors, _tests, _uncaughtErrorMessage); | 788 _config.onSummary(passed, failed, errors, _testCases, _uncaughtErrorMessage); |
788 _config.onDone(passed > 0 && failed == 0 && errors == 0 && | 789 _config.onDone(passed > 0 && failed == 0 && errors == 0 && |
789 _uncaughtErrorMessage == null); | 790 _uncaughtErrorMessage == null); |
790 _initialized = false; | 791 _initialized = false; |
791 } | 792 } |
792 | 793 |
793 String _fullSpec(String spec) { | 794 String _fullSpec(String spec) { |
794 if (spec == null) return '$_currentGroup'; | 795 if (spec == null) return '$_currentGroup'; |
795 return _currentGroup != '' ? '$_currentGroup$groupSep$spec' : spec; | 796 return _currentGroup != '' ? '$_currentGroup$groupSep$spec' : spec; |
796 } | 797 } |
797 | 798 |
798 /** | 799 /** |
799 * Lazily initializes the test library if not already initialized. | 800 * Lazily initializes the test library if not already initialized. |
800 */ | 801 */ |
801 void ensureInitialized() { | 802 void ensureInitialized() { |
802 if (_initialized) { | 803 if (_initialized) { |
803 return; | 804 return; |
804 } | 805 } |
805 _initialized = true; | 806 _initialized = true; |
806 // Hook our async guard into the matcher library. | 807 // Hook our async guard into the matcher library. |
807 wrapAsync = (f, [id]) => expectAsync1(f, id: id); | 808 wrapAsync = (f, [id]) => expectAsync1(f, id: id); |
808 | 809 |
809 _tests = <TestCase>[]; | |
810 _uncaughtErrorMessage = null; | 810 _uncaughtErrorMessage = null; |
811 | 811 |
812 if (_config == null) { | 812 if (_config == null) { |
813 unittestConfiguration = new Configuration(); | 813 unittestConfiguration = new Configuration(); |
814 } | 814 } |
815 _config.onInit(); | 815 _config.onInit(); |
816 | 816 |
817 if (_config.autoStart) { | 817 if (_config.autoStart) { |
818 // Immediately queue the suite up. It will run after a timeout (i.e. after | 818 // Immediately queue the suite up. It will run after a timeout (i.e. after |
819 // main() has returned). | 819 // main() has returned). |
820 _defer(runTests); | 820 _defer(runTests); |
821 } | 821 } |
822 } | 822 } |
823 | 823 |
824 /** Select a solo test by ID. */ | 824 /** Select a solo test by ID. */ |
825 void setSoloTest(int id) { | 825 void setSoloTest(int id) { |
826 for (var i = 0; i < _tests.length; i++) { | 826 for (var i = 0; i < _testCases.length; i++) { |
827 if (_tests[i].id == id) { | 827 if (_testCases[i].id == id) { |
828 _soloTest = _tests[i]; | 828 _soloTest = _testCases[i]; |
829 break; | 829 break; |
830 } | 830 } |
831 } | 831 } |
832 } | 832 } |
833 | 833 |
834 /** Enable/disable a test by ID. */ | 834 /** Enable/disable a test by ID. */ |
835 void _setTestEnabledState(int testId, bool state) { | 835 void _setTestEnabledState(int testId, bool state) { |
836 // Try fast path first. | 836 // Try fast path first. |
837 if (_tests.length > testId && _tests[testId].id == testId) { | 837 if (_testCases.length > testId && _testCases[testId].id == testId) { |
838 _tests[testId].enabled = state; | 838 _testCases[testId].enabled = state; |
839 } else { | 839 } else { |
840 for (var i = 0; i < _tests.length; i++) { | 840 for (var i = 0; i < _testCases.length; i++) { |
841 if (_tests[i].id == testId) { | 841 if (_testCases[i].id == testId) { |
842 _tests[i].enabled = state; | 842 _testCases[i].enabled = state; |
843 break; | 843 break; |
844 } | 844 } |
845 } | 845 } |
846 } | 846 } |
847 } | 847 } |
848 | 848 |
849 /** Enable a test by ID. */ | 849 /** Enable a test by ID. */ |
850 void enableTest(int testId) => _setTestEnabledState(testId, true); | 850 void enableTest(int testId) => _setTestEnabledState(testId, true); |
851 | 851 |
852 /** Disable a test by ID. */ | 852 /** Disable a test by ID. */ |
853 void disableTest(int testId) => _setTestEnabledState(testId, false); | 853 void disableTest(int testId) => _setTestEnabledState(testId, false); |
854 | 854 |
855 /** Signature for a test function. */ | 855 /** Signature for a test function. */ |
856 typedef dynamic TestFunction(); | 856 typedef dynamic TestFunction(); |
OLD | NEW |