| OLD | NEW |
| 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 * A simple mocking/spy library. | 6 * A simple mocking/spy library. |
| 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 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 129 | 129 |
| 130 import 'matcher.dart'; | 130 import 'matcher.dart'; |
| 131 | 131 |
| 132 /** | 132 /** |
| 133 * The error formatter for mocking is a bit different from the default one | 133 * The error formatter for mocking is a bit different from the default one |
| 134 * for unit testing; instead of the third argument being a 'reason' | 134 * for unit testing; instead of the third argument being a 'reason' |
| 135 * it is instead a [signature] describing the method signature filter | 135 * it is instead a [signature] describing the method signature filter |
| 136 * that was used to select the logs that were verified. | 136 * that was used to select the logs that were verified. |
| 137 */ | 137 */ |
| 138 String _mockingErrorFormatter(actual, Matcher matcher, String signature, | 138 String _mockingErrorFormatter(actual, Matcher matcher, String signature, |
| 139 MatchState matchState, bool verbose) { | 139 Map matchState, bool verbose) { |
| 140 var description = new StringDescription(); | 140 var description = new StringDescription(); |
| 141 description.add('Expected ${signature} ').addDescriptionOf(matcher). | 141 description.add('Expected ${signature} ').addDescriptionOf(matcher). |
| 142 add('\n but: '); | 142 add('\n but: '); |
| 143 matcher.describeMismatch(actual, description, matchState, verbose).add('.'); | 143 matcher.describeMismatch(actual, description, matchState, verbose).add('.'); |
| 144 return description.toString(); | 144 return description.toString(); |
| 145 } | 145 } |
| 146 | 146 |
| 147 /** | 147 /** |
| 148 * The failure handler for the [expect()] calls that occur in [verify()] | 148 * The failure handler for the [expect()] calls that occur in [verify()] |
| 149 * methods in the mock objects. This calls the real failure handler used | 149 * methods in the mock objects. This calls the real failure handler used |
| 150 * by the unit test library after formatting the error message with | 150 * by the unit test library after formatting the error message with |
| 151 * the custom formatter. | 151 * the custom formatter. |
| 152 */ | 152 */ |
| 153 class _MockFailureHandler implements FailureHandler { | 153 class _MockFailureHandler implements FailureHandler { |
| 154 FailureHandler proxy; | 154 FailureHandler proxy; |
| 155 _MockFailureHandler(this.proxy); | 155 _MockFailureHandler(this.proxy); |
| 156 void fail(String reason) { | 156 void fail(String reason) { |
| 157 proxy.fail(reason); | 157 proxy.fail(reason); |
| 158 } | 158 } |
| 159 void failMatch(actual, Matcher matcher, String reason, | 159 void failMatch(actual, Matcher matcher, String reason, |
| 160 MatchState matchState, bool verbose) { | 160 Map matchState, bool verbose) { |
| 161 proxy.fail(_mockingErrorFormatter(actual, matcher, reason, | 161 proxy.fail(_mockingErrorFormatter(actual, matcher, reason, |
| 162 matchState, verbose)); | 162 matchState, verbose)); |
| 163 } | 163 } |
| 164 } | 164 } |
| 165 | 165 |
| 166 _MockFailureHandler _mockFailureHandler = null; | 166 _MockFailureHandler _mockFailureHandler = null; |
| 167 | 167 |
| 168 /** Sentinel value for representing no argument. */ | 168 /** Sentinel value for representing no argument. */ |
| 169 class _Sentinel { | 169 class _Sentinel { |
| 170 const _Sentinel(); | 170 const _Sentinel(); |
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 281 } | 281 } |
| 282 d.add(')'); | 282 d.add(')'); |
| 283 return d.toString(); | 283 return d.toString(); |
| 284 } | 284 } |
| 285 | 285 |
| 286 /** | 286 /** |
| 287 * Given a [method] name and list of [arguments], return true | 287 * Given a [method] name and list of [arguments], return true |
| 288 * if it matches this [CallMatcher. | 288 * if it matches this [CallMatcher. |
| 289 */ | 289 */ |
| 290 bool matches(String method, List arguments) { | 290 bool matches(String method, List arguments) { |
| 291 var matchState = new MatchState(); | 291 var matchState = {}; |
| 292 if (!nameFilter.matches(method, matchState)) { | 292 if (!nameFilter.matches(method, matchState)) { |
| 293 return false; | 293 return false; |
| 294 } | 294 } |
| 295 var numArgs = (arguments == null) ? 0 : arguments.length; | 295 var numArgs = (arguments == null) ? 0 : arguments.length; |
| 296 if (numArgs < argMatchers.length) { | 296 if (numArgs < argMatchers.length) { |
| 297 throw new Exception("Less arguments than matchers for $method."); | 297 throw new Exception("Less arguments than matchers for $method."); |
| 298 } | 298 } |
| 299 for (var i = 0; i < argMatchers.length; i++) { | 299 for (var i = 0; i < argMatchers.length; i++) { |
| 300 if (!argMatchers[i].matches(arguments[i], matchState)) { | 300 if (!argMatchers[i].matches(arguments[i], matchState)) { |
| 301 return false; | 301 return false; |
| (...skipping 232 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 534 Matcher actionMatcher, | 534 Matcher actionMatcher, |
| 535 bool destructive = false]) { | 535 bool destructive = false]) { |
| 536 if (mockNameFilter == null) { | 536 if (mockNameFilter == null) { |
| 537 mockNameFilter = anything; | 537 mockNameFilter = anything; |
| 538 } else { | 538 } else { |
| 539 mockNameFilter = wrapMatcher(mockNameFilter); | 539 mockNameFilter = wrapMatcher(mockNameFilter); |
| 540 } | 540 } |
| 541 Function entryFilter = _makePredicate(logFilter); | 541 Function entryFilter = _makePredicate(logFilter); |
| 542 String filterName = _qualifiedName(mockNameFilter, logFilter.toString()); | 542 String filterName = _qualifiedName(mockNameFilter, logFilter.toString()); |
| 543 LogEntryList rtn = new LogEntryList(filterName); | 543 LogEntryList rtn = new LogEntryList(filterName); |
| 544 MatchState matchState = new MatchState(); | 544 var matchState = {}; |
| 545 for (var i = 0; i < logs.length; i++) { | 545 for (var i = 0; i < logs.length; i++) { |
| 546 LogEntry entry = logs[i]; | 546 LogEntry entry = logs[i]; |
| 547 if (mockNameFilter.matches(entry.mockName, matchState) && | 547 if (mockNameFilter.matches(entry.mockName, matchState) && |
| 548 entryFilter(entry)) { | 548 entryFilter(entry)) { |
| 549 if (actionMatcher == null || | 549 if (actionMatcher == null || |
| 550 actionMatcher.matches(entry, matchState)) { | 550 actionMatcher.matches(entry, matchState)) { |
| 551 rtn.add(entry); | 551 rtn.add(entry); |
| 552 if (destructive) { | 552 if (destructive) { |
| 553 int startIndex = i--; | 553 int startIndex = i--; |
| 554 logs.removeRange(startIndex, startIndex + 1); | 554 logs.removeRange(startIndex, startIndex + 1); |
| (...skipping 333 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 888 List scratch = null; | 888 List scratch = null; |
| 889 int remainingCount = 0; | 889 int remainingCount = 0; |
| 890 if (isPreceding) { | 890 if (isPreceding) { |
| 891 scratch = new List(); | 891 scratch = new List(); |
| 892 remainingCount = logs.length; | 892 remainingCount = logs.length; |
| 893 } | 893 } |
| 894 | 894 |
| 895 var keyIterator = keys.logs.iterator; | 895 var keyIterator = keys.logs.iterator; |
| 896 keyIterator.moveNext(); | 896 keyIterator.moveNext(); |
| 897 LogEntry keyEntry = keyIterator.current; | 897 LogEntry keyEntry = keyIterator.current; |
| 898 MatchState matchState = new MatchState(); | 898 Map matchState = {}; |
| 899 | 899 |
| 900 for (LogEntry logEntry in logs) { | 900 for (LogEntry logEntry in logs) { |
| 901 // If we have a log entry match, copy the saved matches from the | 901 // If we have a log entry match, copy the saved matches from the |
| 902 // scratch buffer into the return list, as well as the matching entry, | 902 // scratch buffer into the return list, as well as the matching entry, |
| 903 // if appropriate, and reset the scratch buffer. Continue processing | 903 // if appropriate, and reset the scratch buffer. Continue processing |
| 904 // from the next key entry. | 904 // from the next key entry. |
| 905 if (keyEntry == logEntry) { | 905 if (keyEntry == logEntry) { |
| 906 if (scratch != null) { | 906 if (scratch != null) { |
| 907 int numToCopy = scratch.length; | 907 int numToCopy = scratch.length; |
| 908 if (distance > 0 && distance < numToCopy) { | 908 if (distance > 0 && distance < numToCopy) { |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 993 | 993 |
| 994 /** | 994 /** |
| 995 * [_TimesMatcher]s are used to make assertions about the number of | 995 * [_TimesMatcher]s are used to make assertions about the number of |
| 996 * times a method was called. | 996 * times a method was called. |
| 997 */ | 997 */ |
| 998 class _TimesMatcher extends BaseMatcher { | 998 class _TimesMatcher extends BaseMatcher { |
| 999 final int min, max; | 999 final int min, max; |
| 1000 | 1000 |
| 1001 const _TimesMatcher(this.min, [this.max = -1]); | 1001 const _TimesMatcher(this.min, [this.max = -1]); |
| 1002 | 1002 |
| 1003 bool matches(logList, MatchState matchState) => logList.length >= min && | 1003 bool matches(logList, Map matchState) => logList.length >= min && |
| 1004 (max < 0 || logList.length <= max); | 1004 (max < 0 || logList.length <= max); |
| 1005 | 1005 |
| 1006 Description describe(Description description) { | 1006 Description describe(Description description) { |
| 1007 description.add('to be called '); | 1007 description.add('to be called '); |
| 1008 if (max < 0) { | 1008 if (max < 0) { |
| 1009 description.add('at least $min'); | 1009 description.add('at least $min'); |
| 1010 } else if (max == min) { | 1010 } else if (max == min) { |
| 1011 description.add('$max'); | 1011 description.add('$max'); |
| 1012 } else if (min == 0) { | 1012 } else if (min == 0) { |
| 1013 description.add('at most $max'); | 1013 description.add('at most $max'); |
| 1014 } else { | 1014 } else { |
| 1015 description.add('between $min and $max'); | 1015 description.add('between $min and $max'); |
| 1016 } | 1016 } |
| 1017 return description.add(' times'); | 1017 return description.add(' times'); |
| 1018 } | 1018 } |
| 1019 | 1019 |
| 1020 Description describeMismatch(logList, Description mismatchDescription, | 1020 Description describeMismatch(logList, Description mismatchDescription, |
| 1021 MatchState matchState, bool verbose) => | 1021 Map matchState, bool verbose) => |
| 1022 mismatchDescription.add('was called ${logList.length} times'); | 1022 mismatchDescription.add('was called ${logList.length} times'); |
| 1023 } | 1023 } |
| 1024 | 1024 |
| 1025 /** [happenedExactly] matches an exact number of calls. */ | 1025 /** [happenedExactly] matches an exact number of calls. */ |
| 1026 Matcher happenedExactly(count) { | 1026 Matcher happenedExactly(count) { |
| 1027 return new _TimesMatcher(count, count); | 1027 return new _TimesMatcher(count, count); |
| 1028 } | 1028 } |
| 1029 | 1029 |
| 1030 /** [happenedAtLeast] matches a minimum number of calls. */ | 1030 /** [happenedAtLeast] matches a minimum number of calls. */ |
| 1031 Matcher happenedAtLeast(count) { | 1031 Matcher happenedAtLeast(count) { |
| (...skipping 20 matching lines...) Expand all Loading... |
| 1052 /** | 1052 /** |
| 1053 * [_ResultMatcher]s are used to make assertions about the results | 1053 * [_ResultMatcher]s are used to make assertions about the results |
| 1054 * of method calls. These can be used as optional parameters to [getLogs]. | 1054 * of method calls. These can be used as optional parameters to [getLogs]. |
| 1055 */ | 1055 */ |
| 1056 class _ResultMatcher extends BaseMatcher { | 1056 class _ResultMatcher extends BaseMatcher { |
| 1057 final Action action; | 1057 final Action action; |
| 1058 final Matcher value; | 1058 final Matcher value; |
| 1059 | 1059 |
| 1060 const _ResultMatcher(this.action, this.value); | 1060 const _ResultMatcher(this.action, this.value); |
| 1061 | 1061 |
| 1062 bool matches(item, MatchState matchState) { | 1062 bool matches(item, Map matchState) { |
| 1063 if (item is! LogEntry) { | 1063 if (item is! LogEntry) { |
| 1064 return false; | 1064 return false; |
| 1065 } | 1065 } |
| 1066 // normalize the action; _PROXY is like _RETURN. | 1066 // normalize the action; _PROXY is like _RETURN. |
| 1067 Action eaction = item.action; | 1067 Action eaction = item.action; |
| 1068 if (eaction == Action.PROXY) { | 1068 if (eaction == Action.PROXY) { |
| 1069 eaction = Action.RETURN; | 1069 eaction = Action.RETURN; |
| 1070 } | 1070 } |
| 1071 return (eaction == action && value.matches(item.value, matchState)); | 1071 return (eaction == action && value.matches(item.value, matchState)); |
| 1072 } | 1072 } |
| 1073 | 1073 |
| 1074 Description describe(Description description) { | 1074 Description describe(Description description) { |
| 1075 description.add(' to '); | 1075 description.add(' to '); |
| 1076 if (action == Action.RETURN || action == Action.PROXY) | 1076 if (action == Action.RETURN || action == Action.PROXY) |
| 1077 description.add('return '); | 1077 description.add('return '); |
| 1078 else | 1078 else |
| 1079 description.add('throw '); | 1079 description.add('throw '); |
| 1080 return description.addDescriptionOf(value); | 1080 return description.addDescriptionOf(value); |
| 1081 } | 1081 } |
| 1082 | 1082 |
| 1083 Description describeMismatch(item, Description mismatchDescription, | 1083 Description describeMismatch(item, Description mismatchDescription, |
| 1084 MatchState matchState, bool verbose) { | 1084 Map matchState, bool verbose) { |
| 1085 if (item.action == Action.RETURN || item.action == Action.PROXY) { | 1085 if (item.action == Action.RETURN || item.action == Action.PROXY) { |
| 1086 mismatchDescription.add('returned '); | 1086 mismatchDescription.add('returned '); |
| 1087 } else { | 1087 } else { |
| 1088 mismatchDescription.add('threw '); | 1088 mismatchDescription.add('threw '); |
| 1089 } | 1089 } |
| 1090 mismatchDescription.add(item.value); | 1090 mismatchDescription.add(item.value); |
| 1091 return mismatchDescription; | 1091 return mismatchDescription; |
| 1092 } | 1092 } |
| 1093 } | 1093 } |
| 1094 | 1094 |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1132 * We could make this class use _ResultMatcher but it doesn't buy that | 1132 * We could make this class use _ResultMatcher but it doesn't buy that |
| 1133 * match and adds some perf hit, so there is some duplication here. | 1133 * match and adds some perf hit, so there is some duplication here. |
| 1134 */ | 1134 */ |
| 1135 class _ResultSetMatcher extends BaseMatcher { | 1135 class _ResultSetMatcher extends BaseMatcher { |
| 1136 final Action action; | 1136 final Action action; |
| 1137 final Matcher value; | 1137 final Matcher value; |
| 1138 final _Frequency frequency; // ALL, SOME, or NONE. | 1138 final _Frequency frequency; // ALL, SOME, or NONE. |
| 1139 | 1139 |
| 1140 const _ResultSetMatcher(this.action, this.value, this.frequency); | 1140 const _ResultSetMatcher(this.action, this.value, this.frequency); |
| 1141 | 1141 |
| 1142 bool matches(logList, MatchState matchState) { | 1142 bool matches(logList, Map matchState) { |
| 1143 for (LogEntry entry in logList) { | 1143 for (LogEntry entry in logList) { |
| 1144 // normalize the action; PROXY is like RETURN. | 1144 // normalize the action; PROXY is like RETURN. |
| 1145 Action eaction = entry.action; | 1145 Action eaction = entry.action; |
| 1146 if (eaction == Action.PROXY) { | 1146 if (eaction == Action.PROXY) { |
| 1147 eaction = Action.RETURN; | 1147 eaction = Action.RETURN; |
| 1148 } | 1148 } |
| 1149 if (eaction == action && value.matches(entry.value, matchState)) { | 1149 if (eaction == action && value.matches(entry.value, matchState)) { |
| 1150 if (frequency == _Frequency.NONE) { | 1150 if (frequency == _Frequency.NONE) { |
| 1151 matchState.state = { | 1151 addStateInfo(matchState, {'entry': entry}); |
| 1152 'state' : matchState.state, | |
| 1153 'entry' : entry | |
| 1154 }; | |
| 1155 return false; | 1152 return false; |
| 1156 } else if (frequency == _Frequency.SOME) { | 1153 } else if (frequency == _Frequency.SOME) { |
| 1157 return true; | 1154 return true; |
| 1158 } | 1155 } |
| 1159 } else { | 1156 } else { |
| 1160 // Mismatch. | 1157 // Mismatch. |
| 1161 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail. | 1158 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail. |
| 1162 matchState.state = { | 1159 addStateInfo(matchState, {'entry': entry}); |
| 1163 'state' : matchState.state, | |
| 1164 'entry' : entry | |
| 1165 }; | |
| 1166 return false; | 1160 return false; |
| 1167 } | 1161 } |
| 1168 } | 1162 } |
| 1169 } | 1163 } |
| 1170 // If we get here, then if count is _ALL we got all matches and | 1164 // If we get here, then if count is _ALL we got all matches and |
| 1171 // this is success; otherwise we got all mismatched which is | 1165 // this is success; otherwise we got all mismatched which is |
| 1172 // success for count == _NONE and failure for count == _SOME. | 1166 // success for count == _NONE and failure for count == _SOME. |
| 1173 return (frequency != _Frequency.SOME); | 1167 return (frequency != _Frequency.SOME); |
| 1174 } | 1168 } |
| 1175 | 1169 |
| 1176 Description describe(Description description) { | 1170 Description describe(Description description) { |
| 1177 description.add(' to '); | 1171 description.add(' to '); |
| 1178 description.add(frequency == _Frequency.ALL ? 'alway ' : | 1172 description.add(frequency == _Frequency.ALL ? 'alway ' : |
| 1179 (frequency == _Frequency.NONE ? 'never ' : 'sometimes ')); | 1173 (frequency == _Frequency.NONE ? 'never ' : 'sometimes ')); |
| 1180 if (action == Action.RETURN || action == Action.PROXY) | 1174 if (action == Action.RETURN || action == Action.PROXY) |
| 1181 description.add('return '); | 1175 description.add('return '); |
| 1182 else | 1176 else |
| 1183 description.add('throw '); | 1177 description.add('throw '); |
| 1184 return description.addDescriptionOf(value); | 1178 return description.addDescriptionOf(value); |
| 1185 } | 1179 } |
| 1186 | 1180 |
| 1187 Description describeMismatch(logList, Description mismatchDescription, | 1181 Description describeMismatch(logList, Description mismatchDescription, |
| 1188 MatchState matchState, bool verbose) { | 1182 Map matchState, bool verbose) { |
| 1189 if (frequency != _Frequency.SOME) { | 1183 if (frequency != _Frequency.SOME) { |
| 1190 LogEntry entry = matchState.state['entry']; | 1184 LogEntry entry = matchState['entry']; |
| 1191 if (entry.action == Action.RETURN || entry.action == Action.PROXY) { | 1185 if (entry.action == Action.RETURN || entry.action == Action.PROXY) { |
| 1192 mismatchDescription.add('returned'); | 1186 mismatchDescription.add('returned'); |
| 1193 } else { | 1187 } else { |
| 1194 mismatchDescription.add('threw'); | 1188 mismatchDescription.add('threw'); |
| 1195 } | 1189 } |
| 1196 mismatchDescription.add(' value that '); | 1190 mismatchDescription.add(' value that '); |
| 1197 value.describeMismatch(entry.value, mismatchDescription, | 1191 value.describeMismatch(entry.value, mismatchDescription, |
| 1198 matchState.state['state'], verbose); | 1192 matchState['state'], verbose); |
| 1199 mismatchDescription.add(' at least once'); | 1193 mismatchDescription.add(' at least once'); |
| 1200 } else { | 1194 } else { |
| 1201 mismatchDescription.add('never did'); | 1195 mismatchDescription.add('never did'); |
| 1202 } | 1196 } |
| 1203 return mismatchDescription; | 1197 return mismatchDescription; |
| 1204 } | 1198 } |
| 1205 } | 1199 } |
| 1206 | 1200 |
| 1207 /** | 1201 /** |
| 1208 *[alwaysReturned] asserts that all matching calls to a method returned | 1202 *[alwaysReturned] asserts that all matching calls to a method returned |
| (...skipping 153 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1362 try { | 1356 try { |
| 1363 var result = mirror.delegate(invocation); | 1357 var result = mirror.delegate(invocation); |
| 1364 log.add(new LogEntry(name, method, args, Action.PROXY, result)); | 1358 log.add(new LogEntry(name, method, args, Action.PROXY, result)); |
| 1365 return result; | 1359 return result; |
| 1366 } catch (e) { | 1360 } catch (e) { |
| 1367 log.add(new LogEntry(name, method, args, Action.THROW, e)); | 1361 log.add(new LogEntry(name, method, args, Action.THROW, e)); |
| 1368 throw e; | 1362 throw e; |
| 1369 } | 1363 } |
| 1370 } | 1364 } |
| 1371 bool matchedMethodName = false; | 1365 bool matchedMethodName = false; |
| 1372 MatchState matchState = new MatchState(); | 1366 Map matchState = {}; |
| 1373 for (String k in _behaviors.keys) { | 1367 for (String k in _behaviors.keys) { |
| 1374 Behavior b = _behaviors[k]; | 1368 Behavior b = _behaviors[k]; |
| 1375 if (b.matcher.nameFilter.matches(method, matchState)) { | 1369 if (b.matcher.nameFilter.matches(method, matchState)) { |
| 1376 matchedMethodName = true; | 1370 matchedMethodName = true; |
| 1377 } | 1371 } |
| 1378 if (b.matches(method, args)) { | 1372 if (b.matches(method, args)) { |
| 1379 List actions = b.actions; | 1373 List actions = b.actions; |
| 1380 if (actions == null || actions.length == 0) { | 1374 if (actions == null || actions.length == 0) { |
| 1381 continue; // No return values left in this Behavior. | 1375 continue; // No return values left in this Behavior. |
| 1382 } | 1376 } |
| (...skipping 161 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1544 } | 1538 } |
| 1545 } | 1539 } |
| 1546 } | 1540 } |
| 1547 | 1541 |
| 1548 /** Clear both logs and behavior. */ | 1542 /** Clear both logs and behavior. */ |
| 1549 void reset() { | 1543 void reset() { |
| 1550 resetBehavior(); | 1544 resetBehavior(); |
| 1551 clearLogs(); | 1545 clearLogs(); |
| 1552 } | 1546 } |
| 1553 } | 1547 } |
| OLD | NEW |