Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(763)

Side by Side Diff: lib/unittest/mock.dart

Issue 10832058: Improved the way we generate mismatch descriptions. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, 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 * The error formatter for mocking is a bit different from the default one 6 * The error formatter for mocking is a bit different from the default one
7 * for unit testing; instead of the third argument being a 'reason' 7 * for unit testing; instead of the third argument being a 'reason'
8 * it is instead a [signature] describing the method signature filter 8 * it is instead a [signature] describing the method signature filter
9 * that was used to select the logs that were verified. 9 * that was used to select the logs that were verified.
10 */ 10 */
11 String _mockingErrorFormatter(actual, Matcher matcher, String signature) { 11 String _mockingErrorFormatter(actual, Matcher matcher, String signature,
12 MatchState matchState, bool verbose) {
12 var description = new StringDescription(); 13 var description = new StringDescription();
13 description.add('Expected ${signature} ').addDescriptionOf(matcher). 14 description.add('Expected ${signature} ').addDescriptionOf(matcher).
14 add('\n but: '); 15 add('\n but: ');
15 matcher.describeMismatch(actual, description).add('.'); 16 matcher.describeMismatch(actual, description, matchState, verbose).add('.');
16 return description.toString(); 17 return description.toString();
17 } 18 }
18 19
19 /** 20 /**
20 * The failure handler for the [expect()] calls that occur in [verify()] 21 * The failure handler for the [expect()] calls that occur in [verify()]
21 * methods in the mock objects. This calls the real failure handler used 22 * methods in the mock objects. This calls the real failure handler used
22 * by the unit test library after formatting the error message with 23 * by the unit test library after formatting the error message with
23 * the custom formatter. 24 * the custom formatter.
24 */ 25 */
25 class _MockFailureHandler implements FailureHandler { 26 class _MockFailureHandler implements FailureHandler {
26 FailureHandler proxy; 27 FailureHandler proxy;
27 _MockFailureHandler(this.proxy); 28 _MockFailureHandler(this.proxy);
28 void fail(String reason) { 29 void fail(String reason) {
29 proxy.fail(reason); 30 proxy.fail(reason);
30 } 31 }
31 void failMatch(actual, Matcher matcher, String reason) { 32 void failMatch(actual, Matcher matcher, String reason,
32 proxy.fail(_mockingErrorFormatter(actual, matcher, reason)); 33 MatchState matchState, bool verbose) {
34 proxy.fail(_mockingErrorFormatter(actual, matcher, reason,
35 matchState, verbose));
33 } 36 }
34 } 37 }
35 38
36 _MockFailureHandler _mockFailureHandler = null; 39 _MockFailureHandler _mockFailureHandler = null;
37 40
38 /** 41 /**
39 * [_noArg] is a sentinel value representing no argument. 42 * [_noArg] is a sentinel value representing no argument.
40 */ 43 */
41 final _noArg = const _Sentinel(); 44 final _noArg = const _Sentinel();
42 45
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after
150 } 153 }
151 d.add(')'); 154 d.add(')');
152 return d.toString(); 155 return d.toString();
153 } 156 }
154 157
155 /** 158 /**
156 * Given a [method] name and list of [arguments], return true 159 * Given a [method] name and list of [arguments], return true
157 * if it matches this [CallMatcher. 160 * if it matches this [CallMatcher.
158 */ 161 */
159 bool matches(String method, List arguments) { 162 bool matches(String method, List arguments) {
160 if (!nameFilter.matches(method)) { 163 var matchState = new MatchState();
164 if (!nameFilter.matches(method, matchState)) {
161 return false; 165 return false;
162 } 166 }
163 if (arguments.length < argMatchers.length) { 167 if (arguments.length < argMatchers.length) {
164 throw new Exception("Less arguments than matchers for $method."); 168 throw new Exception("Less arguments than matchers for $method.");
165 } 169 }
166 for (var i = 0; i < argMatchers.length; i++) { 170 for (var i = 0; i < argMatchers.length; i++) {
167 if (!argMatchers[i].matches(arguments[i])) { 171 if (!argMatchers[i].matches(arguments[i], matchState)) {
168 return false; 172 return false;
169 } 173 }
170 } 174 }
171 return true; 175 return true;
172 } 176 }
173 } 177 }
174 178
175 /** 179 /**
176 * Returns a [CallMatcher] for the specified signature. [method] can be 180 * Returns a [CallMatcher] for the specified signature. [method] can be
177 * null to match anything, or a literal [String], a predicate [Function], 181 * null to match anything, or a literal [String], a predicate [Function],
(...skipping 207 matching lines...) Expand 10 before | Expand all | Expand 10 after
385 Matcher actionMatcher, 389 Matcher actionMatcher,
386 bool destructive = false]) { 390 bool destructive = false]) {
387 if (mockNameFilter == null) { 391 if (mockNameFilter == null) {
388 mockNameFilter = anything; 392 mockNameFilter = anything;
389 } else { 393 } else {
390 mockNameFilter = wrapMatcher(mockNameFilter); 394 mockNameFilter = wrapMatcher(mockNameFilter);
391 } 395 }
392 Function entryFilter = _makePredicate(logFilter); 396 Function entryFilter = _makePredicate(logFilter);
393 String filterName = _qualifiedName(mockNameFilter, logFilter.toString()); 397 String filterName = _qualifiedName(mockNameFilter, logFilter.toString());
394 LogEntryList rtn = new LogEntryList(filterName); 398 LogEntryList rtn = new LogEntryList(filterName);
399 MatchState matchState = new MatchState();
395 for (var i = 0; i < logs.length; i++) { 400 for (var i = 0; i < logs.length; i++) {
396 LogEntry entry = logs[i]; 401 LogEntry entry = logs[i];
397 if (mockNameFilter.matches(entry.mockName) && entryFilter(entry)) { 402 if (mockNameFilter.matches(entry.mockName, matchState) &&
398 if (actionMatcher == null || actionMatcher.matches(entry)) { 403 entryFilter(entry)) {
404 if (actionMatcher == null ||
405 actionMatcher.matches(entry, matchState)) {
399 rtn.add(entry); 406 rtn.add(entry);
400 if (destructive) { 407 if (destructive) {
401 logs.removeRange(i--, 1); 408 logs.removeRange(i--, 1);
402 } 409 }
403 } 410 }
404 } 411 }
405 } 412 }
406 return rtn; 413 return rtn;
407 } 414 }
408 415
(...skipping 278 matching lines...) Expand 10 before | Expand all | Expand 10 after
687 // we get our first key match). 694 // we get our first key match).
688 List scratch = null; 695 List scratch = null;
689 int remainingCount = 0; 696 int remainingCount = 0;
690 if (isPreceding) { 697 if (isPreceding) {
691 scratch = new List(); 698 scratch = new List();
692 remainingCount = logs.length; 699 remainingCount = logs.length;
693 } 700 }
694 701
695 var keyIterator = keys.logs.iterator(); 702 var keyIterator = keys.logs.iterator();
696 LogEntry keyEntry = keyIterator.next(); 703 LogEntry keyEntry = keyIterator.next();
704 MatchState matchState = new MatchState();
697 705
698 for (LogEntry logEntry in logs) { 706 for (LogEntry logEntry in logs) {
699 // If we have a log entry match, copy the saved matches from the 707 // If we have a log entry match, copy the saved matches from the
700 // scratch buffer into the return list, as well as the matching entry, 708 // scratch buffer into the return list, as well as the matching entry,
701 // if appropriate, and reset the scratch buffer. Continue processing 709 // if appropriate, and reset the scratch buffer. Continue processing
702 // from the next key entry. 710 // from the next key entry.
703 if (keyEntry == logEntry) { 711 if (keyEntry == logEntry) {
704 if (scratch != null) { 712 if (scratch != null) {
705 int numToCopy = scratch.length; 713 int numToCopy = scratch.length;
706 if (distance > 0 && distance < numToCopy) { 714 if (distance > 0 && distance < numToCopy) {
707 numToCopy = distance; 715 numToCopy = distance;
708 } 716 }
709 for (var i = scratch.length - numToCopy; i < scratch.length; i++) { 717 for (var i = scratch.length - numToCopy; i < scratch.length; i++) {
710 rtn.logs.add(scratch[i]); 718 rtn.logs.add(scratch[i]);
711 } 719 }
712 scratch.clear(); 720 scratch.clear();
713 } else { 721 } else {
714 remainingCount = distance > 0 ? distance : logs.length; 722 remainingCount = distance > 0 ? distance : logs.length;
715 } 723 }
716 if (includeKeys) { 724 if (includeKeys) {
717 rtn.logs.add(keyEntry); 725 rtn.logs.add(keyEntry);
718 } 726 }
719 if (keyIterator.hasNext()) { 727 if (keyIterator.hasNext()) {
720 keyEntry = keyIterator.next(); 728 keyEntry = keyIterator.next();
721 } else if (isPreceding) { // We're done. 729 } else if (isPreceding) { // We're done.
722 break; 730 break;
723 } 731 }
724 } else if (remainingCount > 0 && 732 } else if (remainingCount > 0 &&
725 mockNameFilter.matches(logEntry.mockName) && 733 mockNameFilter.matches(logEntry.mockName, matchState) &&
726 logFilter(logEntry)) { 734 logFilter(logEntry)) {
727 if (scratch != null) { 735 if (scratch != null) {
728 scratch.add(logEntry); 736 scratch.add(logEntry);
729 } else { 737 } else {
730 rtn.logs.add(logEntry); 738 rtn.logs.add(logEntry);
731 --remainingCount; 739 --remainingCount;
732 } 740 }
733 } 741 }
734 } 742 }
735 return rtn; 743 return rtn;
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
791 799
792 /** 800 /**
793 * [_TimesMatcher]s are used to make assertions about the number of 801 * [_TimesMatcher]s are used to make assertions about the number of
794 * times a method was called. 802 * times a method was called.
795 */ 803 */
796 class _TimesMatcher extends BaseMatcher { 804 class _TimesMatcher extends BaseMatcher {
797 final int min, max; 805 final int min, max;
798 806
799 const _TimesMatcher(this.min, [this.max = -1]); 807 const _TimesMatcher(this.min, [this.max = -1]);
800 808
801 bool matches(logList) => logList.length >= min && 809 bool matches(logList, MatchState matchState) => logList.length >= min &&
802 (max < 0 || logList.length <= max); 810 (max < 0 || logList.length <= max);
803 811
804 Description describe(Description description) { 812 Description describe(Description description) {
805 description.add(' to be called '); 813 description.add('to be called ');
806 if (max < 0) { 814 if (max < 0) {
807 description.add('at least $min'); 815 description.add('at least $min');
808 } else if (max == min) { 816 } else if (max == min) {
809 description.add('$max'); 817 description.add('$max');
810 } else if (min == 0) { 818 } else if (min == 0) {
811 description.add('at most $max'); 819 description.add('at most $max');
812 } else { 820 } else {
813 description.add('between $min and $max'); 821 description.add('between $min and $max');
814 } 822 }
815 return description.add(' times'); 823 return description.add(' times');
816 } 824 }
817 825
818 Description describeMismatch(logList, Description mismatchDescription) => 826 Description describeMismatch(logList, Description mismatchDescription,
827 MatchState matchState, bool verbose) =>
819 mismatchDescription.add('was called ${logList.length} times'); 828 mismatchDescription.add('was called ${logList.length} times');
820 } 829 }
821 830
822 /** [happenedExactly] matches an exact number of calls. */ 831 /** [happenedExactly] matches an exact number of calls. */
823 Matcher happenedExactly(count) { 832 Matcher happenedExactly(count) {
824 return new _TimesMatcher(count, count); 833 return new _TimesMatcher(count, count);
825 } 834 }
826 835
827 /** [happenedAtLeast] matches a minimum number of calls. */ 836 /** [happenedAtLeast] matches a minimum number of calls. */
828 Matcher happenedAtLeast(count) { 837 Matcher happenedAtLeast(count) {
(...skipping 20 matching lines...) Expand all
849 /** 858 /**
850 * [_ResultMatcher]s are used to make assertions about the results 859 * [_ResultMatcher]s are used to make assertions about the results
851 * of method calls. These can be used as optional parameters to [getLogs]. 860 * of method calls. These can be used as optional parameters to [getLogs].
852 */ 861 */
853 class _ResultMatcher extends BaseMatcher { 862 class _ResultMatcher extends BaseMatcher {
854 final Action action; 863 final Action action;
855 final Matcher value; 864 final Matcher value;
856 865
857 const _ResultMatcher(this.action, this.value); 866 const _ResultMatcher(this.action, this.value);
858 867
859 bool matches(item) { 868 bool matches(item, MatchState matchState) {
860 if (item is! LogEntry) { 869 if (item is! LogEntry) {
861 return false; 870 return false;
862 } 871 }
863 // normalize the action; _PROXY is like _RETURN. 872 // normalize the action; _PROXY is like _RETURN.
864 Action eaction = item.action; 873 Action eaction = item.action;
865 if (eaction == Action.PROXY) { 874 if (eaction == Action.PROXY) {
866 eaction = Action.RETURN; 875 eaction = Action.RETURN;
867 } 876 }
868 return (eaction == action && value.matches(item.value)); 877 return (eaction == action && value.matches(item.value, matchState));
869 } 878 }
870 879
871 Description describe(Description description) { 880 Description describe(Description description) {
872 description.add(' to '); 881 description.add(' to ');
873 if (action == Action.RETURN || action == Action.PROXY) 882 if (action == Action.RETURN || action == Action.PROXY)
874 description.add('return '); 883 description.add('return ');
875 else 884 else
876 description.add('throw '); 885 description.add('throw ');
877 return description.addDescriptionOf(value); 886 return description.addDescriptionOf(value);
878 } 887 }
879 888
880 Description describeMismatch(item, Description mismatchDescription) { 889 Description describeMismatch(item, Description mismatchDescription,
890 MatchState matchState, bool verbose) {
881 if (item.action == Action.RETURN || item.action == Action.PROXY) { 891 if (item.action == Action.RETURN || item.action == Action.PROXY) {
882 mismatchDescription.add('returned '); 892 mismatchDescription.add('returned ');
883 } else { 893 } else {
884 mismatchDescription.add('threw '); 894 mismatchDescription.add('threw ');
885 } 895 }
886 mismatchDescription.add(item.value); 896 mismatchDescription.add(item.value);
887 return mismatchDescription; 897 return mismatchDescription;
888 } 898 }
889 } 899 }
890 900
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
928 * We could make this class use _ResultMatcher but it doesn't buy that 938 * We could make this class use _ResultMatcher but it doesn't buy that
929 * match and adds some perf hit, so there is some duplication here. 939 * match and adds some perf hit, so there is some duplication here.
930 */ 940 */
931 class _ResultSetMatcher extends BaseMatcher { 941 class _ResultSetMatcher extends BaseMatcher {
932 final Action action; 942 final Action action;
933 final Matcher value; 943 final Matcher value;
934 final _Frequency frequency; // ALL, SOME, or NONE. 944 final _Frequency frequency; // ALL, SOME, or NONE.
935 945
936 const _ResultSetMatcher(this.action, this.value, this.frequency); 946 const _ResultSetMatcher(this.action, this.value, this.frequency);
937 947
938 bool matches(logList) { 948 bool matches(logList, MatchState matchState) {
939 for (LogEntry entry in logList) { 949 for (LogEntry entry in logList) {
940 // normalize the action; PROXY is like RETURN. 950 // normalize the action; PROXY is like RETURN.
941 Action eaction = entry.action; 951 Action eaction = entry.action;
942 if (eaction == Action.PROXY) { 952 if (eaction == Action.PROXY) {
943 eaction = Action.RETURN; 953 eaction = Action.RETURN;
944 } 954 }
945 if (eaction == action && value.matches(entry.value)) { 955 if (eaction == action && value.matches(entry.value, matchState)) {
946 if (frequency == _Frequency.NONE) { 956 if (frequency == _Frequency.NONE) {
957 matchState.state = {
958 'state' : matchState.state,
959 'entry' : entry
960 };
947 return false; 961 return false;
948 } else if (frequency == _Frequency.SOME) { 962 } else if (frequency == _Frequency.SOME) {
949 return true; 963 return true;
950 } 964 }
951 } else { 965 } else {
952 // Mismatch. 966 // Mismatch.
953 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail. 967 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail.
968 matchState.state = {
969 'state' : matchState.state,
970 'entry' : entry
971 };
954 return false; 972 return false;
955 } 973 }
956 } 974 }
957 } 975 }
958 // If we get here, then if count is _ALL we got all matches and 976 // If we get here, then if count is _ALL we got all matches and
959 // this is success; otherwise we got all mismatched which is 977 // this is success; otherwise we got all mismatched which is
960 // success for count == _NONE and failure for count == _SOME. 978 // success for count == _NONE and failure for count == _SOME.
961 return (frequency != _Frequency.SOME); 979 return (frequency != _Frequency.SOME);
962 } 980 }
963 981
964 Description describe(Description description) { 982 Description describe(Description description) {
965 description.add(' to '); 983 description.add(' to ');
966 description.add(frequency == _Frequency.ALL ? 'alway ' : 984 description.add(frequency == _Frequency.ALL ? 'alway ' :
967 (frequency == _Frequency.NONE ? 'never ' : 'sometimes ')); 985 (frequency == _Frequency.NONE ? 'never ' : 'sometimes '));
968 if (action == Action.RETURN || action == Action.PROXY) 986 if (action == Action.RETURN || action == Action.PROXY)
969 description.add('return '); 987 description.add('return ');
970 else 988 else
971 description.add('throw '); 989 description.add('throw ');
972 return description.addDescriptionOf(value); 990 return description.addDescriptionOf(value);
973 } 991 }
974 992
975 Description describeMismatch(logList, Description mismatchDescription) { 993 Description describeMismatch(logList, Description mismatchDescription,
994 MatchState matchState, bool verbose) {
976 if (frequency != _Frequency.SOME) { 995 if (frequency != _Frequency.SOME) {
977 for (LogEntry entry in logList) { 996 LogEntry entry = matchState.state['entry'];
978 if (entry.action != action || !value.matches(entry.value)) { 997 if (entry.action == Action.RETURN || entry.action == Action.PROXY) {
979 if (entry.action == Action.RETURN || entry.action == Action.PROXY) 998 mismatchDescription.add('returned ');
980 mismatchDescription.add('returned '); 999 } else {
981 else 1000 mismatchDescription.add('threw ');
982 mismatchDescription.add('threw ');
983 mismatchDescription.add(entry.value);
984 mismatchDescription.add(' at least once');
985 break;
986 }
987 } 1001 }
1002 mismatchDescription.add(entry.value);
1003 mismatchDescription.add(' that ');
1004 value.describeMismatch(entry.value, mismatchDescription,
1005 matchState.state['state'], verbose);
1006 mismatchDescription.add(' at least once');
988 } else { 1007 } else {
989 mismatchDescription.add('never did'); 1008 mismatchDescription.add('never did');
990 } 1009 }
991 return mismatchDescription; 1010 return mismatchDescription;
992 } 1011 }
993 } 1012 }
994 1013
995 /** 1014 /**
996 *[alwaysReturned] asserts that all matching calls to a method returned 1015 *[alwaysReturned] asserts that all matching calls to a method returned
997 * a value that matched [value]. 1016 * a value that matched [value].
(...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after
1191 * of [Behavior]s, and find the first match that still has return 1210 * of [Behavior]s, and find the first match that still has return
1192 * values available, and then do the action specified by that 1211 * values available, and then do the action specified by that
1193 * return value. If we find no [Behavior] to apply an exception is 1212 * return value. If we find no [Behavior] to apply an exception is
1194 * thrown. 1213 * thrown.
1195 */ 1214 */
1196 noSuchMethod(String method, List args) { 1215 noSuchMethod(String method, List args) {
1197 if (method.startsWith('get:')) { 1216 if (method.startsWith('get:')) {
1198 method = 'get ${method.substring(4)}'; 1217 method = 'get ${method.substring(4)}';
1199 } 1218 }
1200 bool matchedMethodName = false; 1219 bool matchedMethodName = false;
1220 MatchState matchState = new MatchState();
1201 for (String k in _behaviors.getKeys()) { 1221 for (String k in _behaviors.getKeys()) {
1202 Behavior b = _behaviors[k]; 1222 Behavior b = _behaviors[k];
1203 if (b.matcher.nameFilter.matches(method)) { 1223 if (b.matcher.nameFilter.matches(method, matchState)) {
1204 matchedMethodName = true; 1224 matchedMethodName = true;
1205 } 1225 }
1206 if (b.matches(method, args)) { 1226 if (b.matches(method, args)) {
1207 List actions = b.actions; 1227 List actions = b.actions;
1208 if (actions == null || actions.length == 0) { 1228 if (actions == null || actions.length == 0) {
1209 continue; // No return values left in this Behavior. 1229 continue; // No return values left in this Behavior.
1210 } 1230 }
1211 // Get the first response. 1231 // Get the first response.
1212 Responder response = actions[0]; 1232 Responder response = actions[0];
1213 // If it is exhausted, remove it from the list. 1233 // If it is exhausted, remove it from the list.
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after
1348 arg3 = _noArg, 1368 arg3 = _noArg,
1349 arg4 = _noArg, 1369 arg4 = _noArg,
1350 arg5 = _noArg, 1370 arg5 = _noArg,
1351 arg6 = _noArg, 1371 arg6 = _noArg,
1352 arg7 = _noArg, 1372 arg7 = _noArg,
1353 arg8 = _noArg, 1373 arg8 = _noArg,
1354 arg9 = _noArg]) => 1374 arg9 = _noArg]) =>
1355 getLogs(callsTo(method, arg0, arg1, arg2, arg3, arg4, 1375 getLogs(callsTo(method, arg0, arg1, arg2, arg3, arg4,
1356 arg5, arg6, arg7, arg8, arg9)); 1376 arg5, arg6, arg7, arg8, arg9));
1357 } 1377 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698