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 155 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
166 */ | 166 */ |
167 library unittest; | 167 library unittest; |
168 | 168 |
169 import 'dart:async'; | 169 import 'dart:async'; |
170 import 'dart:collection'; | 170 import 'dart:collection'; |
171 import 'dart:isolate'; | 171 import 'dart:isolate'; |
172 import 'dart:math' show max; | 172 import 'dart:math' show max; |
173 import 'matcher.dart'; | 173 import 'matcher.dart'; |
174 export 'matcher.dart'; | 174 export 'matcher.dart'; |
175 | 175 |
| 176 import 'package:stack_trace/stack_trace.dart'; |
| 177 |
| 178 import 'src/utils.dart'; |
176 part 'src/config.dart'; | 179 part 'src/config.dart'; |
177 part 'src/test_case.dart'; | 180 part 'src/test_case.dart'; |
178 | 181 |
179 Configuration _config; | 182 Configuration _config; |
180 | 183 |
181 /** | 184 /** |
182 * [Configuration] used by the unittest library. Note that if a | 185 * [Configuration] used by the unittest library. Note that if a |
183 * configuration has not been set, calling this getter will create | 186 * configuration has not been set, calling this getter will create |
184 * a default configuration. | 187 * a default configuration. |
185 */ | 188 */ |
(...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
446 bool shouldCallBack() { | 449 bool shouldCallBack() { |
447 ++actualCalls; | 450 ++actualCalls; |
448 if (testCase.isComplete) { | 451 if (testCase.isComplete) { |
449 // Don't run if the test is done. We don't throw here as this is not | 452 // Don't run if the test is done. We don't throw here as this is not |
450 // the current test, but we do mark the old test as having an error | 453 // the current test, but we do mark the old test as having an error |
451 // if it previously passed. | 454 // if it previously passed. |
452 if (testCase.result == PASS) { | 455 if (testCase.result == PASS) { |
453 testCase.error( | 456 testCase.error( |
454 'Callback ${id}called ($actualCalls) after test case ' | 457 'Callback ${id}called ($actualCalls) after test case ' |
455 '${testCase.description} has already been marked as ' | 458 '${testCase.description} has already been marked as ' |
456 '${testCase.result}.', ''); | 459 '${testCase.result}.'); |
457 } | 460 } |
458 return false; | 461 return false; |
459 } else if (maxExpectedCalls >= 0 && actualCalls > maxExpectedCalls) { | 462 } else if (maxExpectedCalls >= 0 && actualCalls > maxExpectedCalls) { |
460 throw new TestFailure('Callback ${id}called more times than expected ' | 463 throw new TestFailure('Callback ${id}called more times than expected ' |
461 '($maxExpectedCalls).'); | 464 '($maxExpectedCalls).'); |
462 } | 465 } |
463 return true; | 466 return true; |
464 } | 467 } |
465 | 468 |
466 void after() { | 469 void after() { |
(...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
667 runAsync(() { | 670 runAsync(() { |
668 _currentTestCaseIndex++; | 671 _currentTestCaseIndex++; |
669 _nextBatch(); | 672 _nextBatch(); |
670 }); | 673 }); |
671 } | 674 } |
672 | 675 |
673 /** | 676 /** |
674 * Utility function that can be used to notify the test framework that an | 677 * Utility function that can be used to notify the test framework that an |
675 * error was caught outside of this library. | 678 * error was caught outside of this library. |
676 */ | 679 */ |
677 void _reportTestError(String msg, String trace) { | 680 void _reportTestError(String msg, trace) { |
678 if (_currentTestCaseIndex < testCases.length) { | 681 if (_currentTestCaseIndex < testCases.length) { |
679 final testCase = testCases[_currentTestCaseIndex]; | 682 final testCase = testCases[_currentTestCaseIndex]; |
680 testCase.error(msg, trace); | 683 testCase.error(msg, trace); |
681 } else { | 684 } else { |
682 _uncaughtErrorMessage = "$msg: $trace"; | 685 _uncaughtErrorMessage = "$msg: $trace"; |
683 } | 686 } |
684 } | 687 } |
685 | 688 |
686 void rerunTests() { | 689 void rerunTests() { |
687 _uncaughtErrorMessage = null; | 690 _uncaughtErrorMessage = null; |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
744 * Registers that an exception was caught for the current test. | 747 * Registers that an exception was caught for the current test. |
745 */ | 748 */ |
746 void registerException(e, [trace]) { | 749 void registerException(e, [trace]) { |
747 _registerException(currentTestCase, e, trace); | 750 _registerException(currentTestCase, e, trace); |
748 } | 751 } |
749 | 752 |
750 /** | 753 /** |
751 * Registers that an exception was caught for the current test. | 754 * Registers that an exception was caught for the current test. |
752 */ | 755 */ |
753 void _registerException(TestCase testCase, e, [trace]) { | 756 void _registerException(TestCase testCase, e, [trace]) { |
754 trace = trace == null ? '' : trace.toString(); | |
755 String message = (e is TestFailure) ? e.message : 'Caught $e'; | 757 String message = (e is TestFailure) ? e.message : 'Caught $e'; |
756 if (testCase.result == null) { | 758 if (testCase.result == null) { |
757 testCase.fail(message, trace); | 759 testCase.fail(message, trace); |
758 } else { | 760 } else { |
759 testCase.error(message, trace); | 761 testCase.error(message, trace); |
760 } | 762 } |
761 } | 763 } |
762 | 764 |
763 /** | 765 /** |
764 * Runs a batch of tests, yielding whenever an asynchronous test starts | 766 * Runs a batch of tests, yielding whenever an asynchronous test starts |
(...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
857 /** Enable a test by ID. */ | 859 /** Enable a test by ID. */ |
858 void enableTest(int testId) => _setTestEnabledState(testId, true); | 860 void enableTest(int testId) => _setTestEnabledState(testId, true); |
859 | 861 |
860 /** Disable a test by ID. */ | 862 /** Disable a test by ID. */ |
861 void disableTest(int testId) => _setTestEnabledState(testId, false); | 863 void disableTest(int testId) => _setTestEnabledState(testId, false); |
862 | 864 |
863 /** Signature for a test function. */ | 865 /** Signature for a test function. */ |
864 typedef dynamic TestFunction(); | 866 typedef dynamic TestFunction(); |
865 | 867 |
866 /** | 868 /** |
867 * A flag that controls whether we hide unittest details in exception stacks. | 869 * A flag that controls whether we hide unittest and core library details in |
| 870 * exception stacks. |
| 871 * |
868 * Useful to disable when debugging unittest or matcher customizations. | 872 * Useful to disable when debugging unittest or matcher customizations. |
869 */ | 873 */ |
870 bool formatStacks = true; | 874 bool formatStacks = true; |
871 | 875 |
872 // Stack formatting utility. Strips extraneous content from a stack trace. | 876 /** Returns a Trace object from a StackTrace object or a String. */ |
873 // Stack frame lines are parsed with a regexp, which has been tested | 877 Trace _getTrace(stack) { |
874 // in Chrome, Firefox and the VM. If a line fails to be parsed it is | 878 Trace trace; |
875 // included in the output to be conservative. | 879 if (stack == null) return null; |
876 // | 880 if (stack is String) { |
877 // The output stack consists of everything after the call to TestCase._run. | 881 trace = new Trace.parse(stack); |
878 // If we see an 'expect' in the frame we will prune everything above that | 882 } else if (stack is StackTrace) { |
879 // as well. | 883 trace = new Trace.from(stack); |
880 final _frameRegExp = new RegExp( | |
881 r'^\s*' // Skip leading spaces. | |
882 r'(?:' // Group of choices for the prefix. | |
883 r'(?:#\d+\s*)|' // Skip VM's #<frameNumber>. | |
884 r'(?:at )|' // Skip Firefox's 'at '. | |
885 r'(?:))' // Other environments have nothing here. | |
886 r'(.+)' // Extract the function/method. | |
887 r'\s*[@\(]' // Skip space and @ or (. | |
888 r'(' // This group of choices is for the source file. | |
889 r'(?:.+:\/\/.+\/[^:]*)|' // Handle file:// or http:// URLs. | |
890 r'(?:dart:[^:]*)|' // Handle dart:<lib>. | |
891 r'(?:package:[^:]*)' // Handle package:<path> | |
892 r'):([:\d]+)[\)]?$'); // Get the line number and optional column number. | |
893 | |
894 String _formatStack(stack) { | |
895 if (!formatStacks) return "$stack"; | |
896 var lines; | |
897 if (stack is StackTrace) { | |
898 lines = stack.toString().split('\n'); | |
899 } else if (stack is String) { | |
900 lines = stack.split('\n'); | |
901 } else { | 884 } else { |
902 return stack.toString(); | 885 throw new Exception('Invalid stack type ${stack.runtimeType} for $stack.'); |
903 } | 886 } |
904 | 887 |
905 // Calculate the max width of first column so we can | 888 if (!formatStacks) return trace; |
906 // pad to align the second columns. | |
907 int padding = lines.fold(0, (n, line) { | |
908 var match = _frameRegExp.firstMatch(line); | |
909 if (match == null) return n; | |
910 return max(n, match[1].length + 1); | |
911 }); | |
912 | 889 |
913 // We remove all entries that have a location in unittest. | 890 // Format the stack trace by removing everything above TestCase._runTest, |
914 // We strip out anything before _nextBatch too. | 891 // which is usually going to be irrelevant. Also fold together unittest and |
915 var sb = new StringBuffer(); | 892 // core library calls so only the function the user called is visible. |
916 for (var i = 0; i < lines.length; i++) { | 893 return new Trace(trace.frames.takeWhile((frame) { |
917 var line = lines[i]; | 894 return frame.package != 'unittest' || frame.member != 'TestCase._runTest'; |
918 if (line == '') continue; | 895 })).terse.foldFrames((frame) => frame.package == 'unittest' || frame.isCore); |
919 var match = _frameRegExp.firstMatch(line); | |
920 if (match == null) { | |
921 sb.write(line); | |
922 sb.write('\n'); | |
923 } else { | |
924 var member = match[1]; | |
925 var location = match[2]; | |
926 var position = match[3]; | |
927 if (member.indexOf('TestCase._runTest') >= 0) { | |
928 // Don't include anything after this. | |
929 break; | |
930 } else if (member.indexOf('expect') >= 0) { | |
931 // It looks like this was an expect() failure; | |
932 // drop all the frames up to here. | |
933 sb.clear(); | |
934 } else { | |
935 sb.write(member); | |
936 // Pad second column to a fixed position. | |
937 for (var j = 0; j <= padding - member.length; j++) { | |
938 sb.write(' '); | |
939 } | |
940 sb.write(location); | |
941 sb.write(' '); | |
942 sb.write(position); | |
943 sb.write('\n'); | |
944 } | |
945 } | |
946 } | |
947 return sb.toString(); | |
948 } | 896 } |
OLD | NEW |