| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 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. | |
| 4 | |
| 5 library mock.log_entry_list; | |
| 6 | |
| 7 import 'package:matcher/matcher.dart'; | |
| 8 | |
| 9 import 'call_matcher.dart'; | |
| 10 import 'log_entry.dart'; | |
| 11 import 'util.dart'; | |
| 12 | |
| 13 /** | |
| 14 * [StepValidator]s are used by [stepwiseValidate] in [LogEntryList], which | |
| 15 * iterates through the list and call the [StepValidator] function with the | |
| 16 * log [List] and position. The [StepValidator] should return the number of | |
| 17 * positions to advance upon success, or zero upon failure. When zero is | |
| 18 * returned an error is reported. | |
| 19 */ | |
| 20 typedef int StepValidator(List<LogEntry> logs, int pos); | |
| 21 | |
| 22 /** | |
| 23 * We do verification on a list of [LogEntry]s. To allow chaining | |
| 24 * of calls to verify, we encapsulate such a list in the [LogEntryList] | |
| 25 * class. | |
| 26 */ | |
| 27 class LogEntryList { | |
| 28 String filter; | |
| 29 List<LogEntry> logs; | |
| 30 LogEntryList([this.filter]) { | |
| 31 logs = new List<LogEntry>(); | |
| 32 } | |
| 33 | |
| 34 /** Add a [LogEntry] to the log. */ | |
| 35 add(LogEntry entry) => logs.add(entry); | |
| 36 | |
| 37 /** Get the first entry, or null if no entries. */ | |
| 38 get first => (logs == null || logs.length == 0) ? null : logs[0]; | |
| 39 | |
| 40 /** Get the last entry, or null if no entries. */ | |
| 41 get last => (logs == null || logs.length == 0) ? null : logs.last; | |
| 42 | |
| 43 /** Creates a LogEntry predicate function from the argument. */ | |
| 44 Function _makePredicate(arg) { | |
| 45 if (arg == null) { | |
| 46 return (e) => true; | |
| 47 } else if (arg is CallMatcher) { | |
| 48 return (e) => arg.matches(e.methodName, e.args); | |
| 49 } else if (arg is Function) { | |
| 50 return arg; | |
| 51 } else { | |
| 52 throw new Exception("Invalid argument to _makePredicate."); | |
| 53 } | |
| 54 } | |
| 55 | |
| 56 /** | |
| 57 * Create a new [LogEntryList] consisting of [LogEntry]s from | |
| 58 * this list that match the specified [mockNameFilter] and [logFilter]. | |
| 59 * [mockNameFilter] can be null, a [String], a predicate [Function], | |
| 60 * or a [Matcher]. If [mockNameFilter] is null, this is the same as | |
| 61 * [anything]. | |
| 62 * If [logFilter] is null, all entries in the log will be returned. | |
| 63 * Otherwise [logFilter] should be a [CallMatcher] or predicate function | |
| 64 * that takes a [LogEntry] and returns a bool. | |
| 65 * If [destructive] is true, the log entries are removed from the | |
| 66 * original list. | |
| 67 */ | |
| 68 LogEntryList getMatches([mockNameFilter, | |
| 69 logFilter, | |
| 70 Matcher actionMatcher, | |
| 71 bool destructive = false]) { | |
| 72 if (mockNameFilter == null) { | |
| 73 mockNameFilter = anything; | |
| 74 } else { | |
| 75 mockNameFilter = wrapMatcher(mockNameFilter); | |
| 76 } | |
| 77 Function entryFilter = _makePredicate(logFilter); | |
| 78 String filterName = qualifiedName(mockNameFilter, logFilter.toString()); | |
| 79 LogEntryList rtn = new LogEntryList(filterName); | |
| 80 var matchState = {}; | |
| 81 for (var i = 0; i < logs.length; i++) { | |
| 82 LogEntry entry = logs[i]; | |
| 83 if (mockNameFilter.matches(entry.mockName, matchState) && | |
| 84 entryFilter(entry)) { | |
| 85 if (actionMatcher == null || | |
| 86 actionMatcher.matches(entry, matchState)) { | |
| 87 rtn.add(entry); | |
| 88 if (destructive) { | |
| 89 int startIndex = i--; | |
| 90 logs.removeRange(startIndex, startIndex + 1); | |
| 91 } | |
| 92 } | |
| 93 } | |
| 94 } | |
| 95 return rtn; | |
| 96 } | |
| 97 | |
| 98 /** Apply a unit test [Matcher] to the [LogEntryList]. */ | |
| 99 LogEntryList verify(Matcher matcher) { | |
| 100 if (_mockFailureHandler == null) { | |
| 101 _mockFailureHandler = | |
| 102 new _MockFailureHandler(getOrCreateExpectFailureHandler()); | |
| 103 } | |
| 104 expect(logs, matcher, reason: filter, failureHandler: _mockFailureHandler); | |
| 105 return this; | |
| 106 } | |
| 107 | |
| 108 /** | |
| 109 * Iterate through the list and call the [validator] function with the | |
| 110 * log [List] and position. The [validator] should return the number of | |
| 111 * positions to advance upon success, or zero upon failure. When zero is | |
| 112 * returned an error is reported. [reason] can be used to provide a | |
| 113 * more descriptive failure message. If a failure occurred false will be | |
| 114 * returned (unless the failure handler itself threw an exception); | |
| 115 * otherwise true is returned. | |
| 116 * The use case here is to perform more complex validations; for example | |
| 117 * we may want to assert that the return value from some function is | |
| 118 * later used as a parameter to a following function. If we filter the logs | |
| 119 * to include just these two functions we can write a simple validator to | |
| 120 * do this check. | |
| 121 */ | |
| 122 bool stepwiseValidate(StepValidator validator, [String reason = '']) { | |
| 123 if (_mockFailureHandler == null) { | |
| 124 _mockFailureHandler = | |
| 125 new _MockFailureHandler(getOrCreateExpectFailureHandler()); | |
| 126 } | |
| 127 var i = 0; | |
| 128 while (i < logs.length) { | |
| 129 var n = validator(logs, i); | |
| 130 if (n == 0) { | |
| 131 if (reason.length > 0) { | |
| 132 reason = ': $reason'; | |
| 133 } | |
| 134 _mockFailureHandler.fail("Stepwise validation failed at $filter " | |
| 135 "position $i$reason"); | |
| 136 return false; | |
| 137 } else { | |
| 138 i += n; | |
| 139 } | |
| 140 } | |
| 141 return true; | |
| 142 } | |
| 143 | |
| 144 /** | |
| 145 * Turn the logs into human-readable text. If [baseTime] is specified | |
| 146 * then each entry is prefixed with the offset from that time in | |
| 147 * milliseconds; otherwise the time of day is used. | |
| 148 */ | |
| 149 String toString([DateTime baseTime]) { | |
| 150 String s = ''; | |
| 151 for (var e in logs) { | |
| 152 s = '$s${e.toString(baseTime)}\n'; | |
| 153 } | |
| 154 return s; | |
| 155 } | |
| 156 | |
| 157 /** | |
| 158 * Find the first log entry that satisfies [logFilter] and | |
| 159 * return its position. A search [start] position can be provided | |
| 160 * to allow for repeated searches. [logFilter] can be a [CallMatcher], | |
| 161 * or a predicate function that takes a [LogEntry] argument and returns | |
| 162 * a bool. If [logFilter] is null, it will match any [LogEntry]. | |
| 163 * If no entry is found, then [failureReturnValue] is returned. | |
| 164 * After each check the position is updated by [skip], so using | |
| 165 * [skip] of -1 allows backward searches, using a [skip] of 2 can | |
| 166 * be used to check pairs of adjacent entries, and so on. | |
| 167 */ | |
| 168 int findLogEntry(logFilter, [int start = 0, int failureReturnValue = -1, | |
| 169 skip = 1]) { | |
| 170 logFilter = _makePredicate(logFilter); | |
| 171 int pos = start; | |
| 172 while (pos >= 0 && pos < logs.length) { | |
| 173 if (logFilter(logs[pos])) { | |
| 174 return pos; | |
| 175 } | |
| 176 pos += skip; | |
| 177 } | |
| 178 return failureReturnValue; | |
| 179 } | |
| 180 | |
| 181 /** | |
| 182 * Returns log events that happened up to the first one that | |
| 183 * satisfies [logFilter]. If [inPlace] is true, then returns | |
| 184 * this LogEntryList after removing the from the first satisfier; | |
| 185 * onwards otherwise a new list is created. [description] | |
| 186 * is used to create a new name for the resulting list. | |
| 187 * [defaultPosition] is used as the index of the matching item in | |
| 188 * the case that no match is found. | |
| 189 */ | |
| 190 LogEntryList _head(logFilter, bool inPlace, | |
| 191 String description, int defaultPosition) { | |
| 192 if (filter != null) { | |
| 193 description = '$filter $description'; | |
| 194 } | |
| 195 int pos = findLogEntry(logFilter, 0, defaultPosition); | |
| 196 if (inPlace) { | |
| 197 if (pos < logs.length) { | |
| 198 logs.removeRange(pos, logs.length); | |
| 199 } | |
| 200 filter = description; | |
| 201 return this; | |
| 202 } else { | |
| 203 LogEntryList newList = new LogEntryList(description); | |
| 204 for (var i = 0; i < pos; i++) { | |
| 205 newList.logs.add(logs[i]); | |
| 206 } | |
| 207 return newList; | |
| 208 } | |
| 209 } | |
| 210 | |
| 211 /** | |
| 212 * Returns log events that happened from the first one that | |
| 213 * satisfies [logFilter]. If [inPlace] is true, then returns | |
| 214 * this LogEntryList after removing the entries up to the first | |
| 215 * satisfier; otherwise a new list is created. [description] | |
| 216 * is used to create a new name for the resulting list. | |
| 217 * [defaultPosition] is used as the index of the matching item in | |
| 218 * the case that no match is found. | |
| 219 */ | |
| 220 LogEntryList _tail(logFilter, bool inPlace, | |
| 221 String description, int defaultPosition) { | |
| 222 if (filter != null) { | |
| 223 description = '$filter $description'; | |
| 224 } | |
| 225 int pos = findLogEntry(logFilter, 0, defaultPosition); | |
| 226 if (inPlace) { | |
| 227 if (pos > 0) { | |
| 228 logs.removeRange(0, pos); | |
| 229 } | |
| 230 filter = description; | |
| 231 return this; | |
| 232 } else { | |
| 233 LogEntryList newList = new LogEntryList(description); | |
| 234 while (pos < logs.length) { | |
| 235 newList.logs.add(logs[pos++]); | |
| 236 } | |
| 237 return newList; | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 /** | |
| 242 * Returns log events that happened after [when]. If [inPlace] | |
| 243 * is true, then it returns this LogEntryList after removing | |
| 244 * the entries that happened up to [when]; otherwise a new | |
| 245 * list is created. | |
| 246 */ | |
| 247 LogEntryList after(DateTime when, [bool inPlace = false]) => | |
| 248 _tail((e) => e.time.isAfter(when), inPlace, 'after $when', logs.length); | |
| 249 | |
| 250 /** | |
| 251 * Returns log events that happened from [when] onwards. If | |
| 252 * [inPlace] is true, then it returns this LogEntryList after | |
| 253 * removing the entries that happened before [when]; otherwise | |
| 254 * a new list is created. | |
| 255 */ | |
| 256 LogEntryList from(DateTime when, [bool inPlace = false]) => | |
| 257 _tail((e) => !e.time.isBefore(when), inPlace, 'from $when', logs.length); | |
| 258 | |
| 259 /** | |
| 260 * Returns log events that happened until [when]. If [inPlace] | |
| 261 * is true, then it returns this LogEntryList after removing | |
| 262 * the entries that happened after [when]; otherwise a new | |
| 263 * list is created. | |
| 264 */ | |
| 265 LogEntryList until(DateTime when, [bool inPlace = false]) => | |
| 266 _head((e) => e.time.isAfter(when), inPlace, 'until $when', logs.length); | |
| 267 | |
| 268 /** | |
| 269 * Returns log events that happened before [when]. If [inPlace] | |
| 270 * is true, then it returns this LogEntryList after removing | |
| 271 * the entries that happened from [when] onwards; otherwise a new | |
| 272 * list is created. | |
| 273 */ | |
| 274 LogEntryList before(DateTime when, [bool inPlace = false]) => | |
| 275 _head((e) => !e.time.isBefore(when), | |
| 276 inPlace, | |
| 277 'before $when', | |
| 278 logs.length); | |
| 279 | |
| 280 /** | |
| 281 * Returns log events that happened after [logEntry]'s time. | |
| 282 * If [inPlace] is true, then it returns this LogEntryList after | |
| 283 * removing the entries that happened up to [when]; otherwise a new | |
| 284 * list is created. If [logEntry] is null the current time is used. | |
| 285 */ | |
| 286 LogEntryList afterEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 287 after(logEntry == null ? new DateTime.now() : logEntry.time); | |
| 288 | |
| 289 /** | |
| 290 * Returns log events that happened from [logEntry]'s time onwards. | |
| 291 * If [inPlace] is true, then it returns this LogEntryList after | |
| 292 * removing the entries that happened before [when]; otherwise | |
| 293 * a new list is created. If [logEntry] is null the current time is used. | |
| 294 */ | |
| 295 LogEntryList fromEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 296 from(logEntry == null ? new DateTime.now() : logEntry.time); | |
| 297 | |
| 298 /** | |
| 299 * Returns log events that happened until [logEntry]'s time. If | |
| 300 * [inPlace] is true, then it returns this LogEntryList after removing | |
| 301 * the entries that happened after [when]; otherwise a new | |
| 302 * list is created. If [logEntry] is null the epoch time is used. | |
| 303 */ | |
| 304 LogEntryList untilEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 305 until(logEntry == null ? | |
| 306 new DateTime.fromMillisecondsSinceEpoch(0) : logEntry.time); | |
| 307 | |
| 308 /** | |
| 309 * Returns log events that happened before [logEntry]'s time. If | |
| 310 * [inPlace] is true, then it returns this LogEntryList after removing | |
| 311 * the entries that happened from [when] onwards; otherwise a new | |
| 312 * list is created. If [logEntry] is null the epoch time is used. | |
| 313 */ | |
| 314 LogEntryList beforeEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 315 before(logEntry == null ? | |
| 316 new DateTime.fromMillisecondsSinceEpoch(0) : logEntry.time); | |
| 317 | |
| 318 /** | |
| 319 * Returns log events that happened after the first event in [segment]. | |
| 320 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 321 * the entries that happened earlier; otherwise a new list is created. | |
| 322 */ | |
| 323 LogEntryList afterFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 324 afterEntry(segment.first, inPlace); | |
| 325 | |
| 326 /** | |
| 327 * Returns log events that happened after the last event in [segment]. | |
| 328 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 329 * the entries that happened earlier; otherwise a new list is created. | |
| 330 */ | |
| 331 LogEntryList afterLast(LogEntryList segment, [bool inPlace = false]) => | |
| 332 afterEntry(segment.last, inPlace); | |
| 333 | |
| 334 /** | |
| 335 * Returns log events that happened from the time of the first event in | |
| 336 * [segment] onwards. If [inPlace] is true, then it returns this | |
| 337 * LogEntryList after removing the earlier entries; otherwise a new list | |
| 338 * is created. | |
| 339 */ | |
| 340 LogEntryList fromFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 341 fromEntry(segment.first, inPlace); | |
| 342 | |
| 343 /** | |
| 344 * Returns log events that happened from the time of the last event in | |
| 345 * [segment] onwards. If [inPlace] is true, then it returns this | |
| 346 * LogEntryList after removing the earlier entries; otherwise a new list | |
| 347 * is created. | |
| 348 */ | |
| 349 LogEntryList fromLast(LogEntryList segment, [bool inPlace = false]) => | |
| 350 fromEntry(segment.last, inPlace); | |
| 351 | |
| 352 /** | |
| 353 * Returns log events that happened until the first event in [segment]. | |
| 354 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 355 * the entries that happened later; otherwise a new list is created. | |
| 356 */ | |
| 357 LogEntryList untilFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 358 untilEntry(segment.first, inPlace); | |
| 359 | |
| 360 /** | |
| 361 * Returns log events that happened until the last event in [segment]. | |
| 362 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 363 * the entries that happened later; otherwise a new list is created. | |
| 364 */ | |
| 365 LogEntryList untilLast(LogEntryList segment, [bool inPlace = false]) => | |
| 366 untilEntry(segment.last, inPlace); | |
| 367 | |
| 368 /** | |
| 369 * Returns log events that happened before the first event in [segment]. | |
| 370 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 371 * the entries that happened later; otherwise a new list is created. | |
| 372 */ | |
| 373 LogEntryList beforeFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 374 beforeEntry(segment.first, inPlace); | |
| 375 | |
| 376 /** | |
| 377 * Returns log events that happened before the last event in [segment]. | |
| 378 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 379 * the entries that happened later; otherwise a new list is created. | |
| 380 */ | |
| 381 LogEntryList beforeLast(LogEntryList segment, [bool inPlace = false]) => | |
| 382 beforeEntry(segment.last, inPlace); | |
| 383 | |
| 384 /** | |
| 385 * Iterate through the LogEntryList looking for matches to the entries | |
| 386 * in [keys]; for each match found the closest [distance] neighboring log | |
| 387 * entries that match [mockNameFilter] and [logFilter] will be included in | |
| 388 * the result. If [isPreceding] is true we use the neighbors that precede | |
| 389 * the matched entry; else we use the neighbors that followed. | |
| 390 * If [includeKeys] is true then the entries in [keys] that resulted in | |
| 391 * entries in the output list are themselves included in the output list. If | |
| 392 * [distance] is zero then all matches are included. | |
| 393 */ | |
| 394 LogEntryList _neighboring(bool isPreceding, | |
| 395 LogEntryList keys, | |
| 396 mockNameFilter, | |
| 397 logFilter, | |
| 398 int distance, | |
| 399 bool includeKeys) { | |
| 400 String filterName = 'Calls to ' | |
| 401 '${qualifiedName(mockNameFilter, logFilter.toString())} ' | |
| 402 '${isPreceding?"preceding":"following"} ${keys.filter}'; | |
| 403 | |
| 404 LogEntryList rtn = new LogEntryList(filterName); | |
| 405 | |
| 406 // Deal with the trivial case. | |
| 407 if (logs.length == 0 || keys.logs.length == 0) { | |
| 408 return rtn; | |
| 409 } | |
| 410 | |
| 411 // Normalize the mockNameFilter and logFilter values. | |
| 412 if (mockNameFilter == null) { | |
| 413 mockNameFilter = anything; | |
| 414 } else { | |
| 415 mockNameFilter = wrapMatcher(mockNameFilter); | |
| 416 } | |
| 417 logFilter = _makePredicate(logFilter); | |
| 418 | |
| 419 // The scratch list is used to hold matching entries when we | |
| 420 // are doing preceding neighbors. The remainingCount is used to | |
| 421 // keep track of how many matching entries we can still add in the | |
| 422 // current segment (0 if we are doing doing following neighbors, until | |
| 423 // we get our first key match). | |
| 424 List scratch = null; | |
| 425 int remainingCount = 0; | |
| 426 if (isPreceding) { | |
| 427 scratch = new List(); | |
| 428 remainingCount = logs.length; | |
| 429 } | |
| 430 | |
| 431 var keyIterator = keys.logs.iterator; | |
| 432 keyIterator.moveNext(); | |
| 433 LogEntry keyEntry = keyIterator.current; | |
| 434 Map matchState = {}; | |
| 435 | |
| 436 for (LogEntry logEntry in logs) { | |
| 437 // If we have a log entry match, copy the saved matches from the | |
| 438 // scratch buffer into the return list, as well as the matching entry, | |
| 439 // if appropriate, and reset the scratch buffer. Continue processing | |
| 440 // from the next key entry. | |
| 441 if (keyEntry == logEntry) { | |
| 442 if (scratch != null) { | |
| 443 int numToCopy = scratch.length; | |
| 444 if (distance > 0 && distance < numToCopy) { | |
| 445 numToCopy = distance; | |
| 446 } | |
| 447 for (var i = scratch.length - numToCopy; i < scratch.length; i++) { | |
| 448 rtn.logs.add(scratch[i]); | |
| 449 } | |
| 450 scratch.clear(); | |
| 451 } else { | |
| 452 remainingCount = distance > 0 ? distance : logs.length; | |
| 453 } | |
| 454 if (includeKeys) { | |
| 455 rtn.logs.add(keyEntry); | |
| 456 } | |
| 457 if (keyIterator.moveNext()) { | |
| 458 keyEntry = keyIterator.current; | |
| 459 } else if (isPreceding) { // We're done. | |
| 460 break; | |
| 461 } | |
| 462 } else if (remainingCount > 0 && | |
| 463 mockNameFilter.matches(logEntry.mockName, matchState) && | |
| 464 logFilter(logEntry)) { | |
| 465 if (scratch != null) { | |
| 466 scratch.add(logEntry); | |
| 467 } else { | |
| 468 rtn.logs.add(logEntry); | |
| 469 --remainingCount; | |
| 470 } | |
| 471 } | |
| 472 } | |
| 473 return rtn; | |
| 474 } | |
| 475 | |
| 476 /** | |
| 477 * Iterate through the LogEntryList looking for matches to the entries | |
| 478 * in [keys]; for each match found the closest [distance] prior log entries | |
| 479 * that match [mocknameFilter] and [logFilter] will be included in the result. | |
| 480 * If [includeKeys] is true then the entries in [keys] that resulted in | |
| 481 * entries in the output list are themselves included in the output list. If | |
| 482 * [distance] is zero then all matches are included. | |
| 483 * | |
| 484 * The idea here is that you could find log entries that are related to | |
| 485 * other logs entries in some temporal sense. For example, say we have a | |
| 486 * method commit() that returns -1 on failure. Before commit() gets called | |
| 487 * the value being committed is created by process(). We may want to find | |
| 488 * the calls to process() that preceded calls to commit() that failed. | |
| 489 * We could do this with: | |
| 490 * | |
| 491 * print(log.preceding(log.getLogs(callsTo('commit'), returning(-1)), | |
| 492 * logFilter: callsTo('process')).toString()); | |
| 493 * | |
| 494 * We might want to include the details of the failing calls to commit() | |
| 495 * to see what parameters were passed in, in which case we would set | |
| 496 * [includeKeys]. | |
| 497 * | |
| 498 * As another simple example, say we wanted to know the three method | |
| 499 * calls that immediately preceded each failing call to commit(): | |
| 500 * | |
| 501 * print(log.preceding(log.getLogs(callsTo('commit'), returning(-1)), | |
| 502 * distance: 3).toString()); | |
| 503 */ | |
| 504 LogEntryList preceding(LogEntryList keys, | |
| 505 {mockNameFilter: null, | |
| 506 logFilter: null, | |
| 507 int distance: 1, | |
| 508 bool includeKeys: false}) => | |
| 509 _neighboring(true, keys, mockNameFilter, logFilter, | |
| 510 distance, includeKeys); | |
| 511 | |
| 512 /** | |
| 513 * Iterate through the LogEntryList looking for matches to the entries | |
| 514 * in [keys]; for each match found the closest [distance] subsequent log | |
| 515 * entries that match [mocknameFilter] and [logFilter] will be included in | |
| 516 * the result. If [includeKeys] is true then the entries in [keys] that | |
| 517 * resulted in entries in the output list are themselves included in the | |
| 518 * output list. If [distance] is zero then all matches are included. | |
| 519 * See [preceding] for a usage example. | |
| 520 */ | |
| 521 LogEntryList following(LogEntryList keys, | |
| 522 {mockNameFilter: null, | |
| 523 logFilter: null, | |
| 524 int distance: 1, | |
| 525 bool includeKeys: false}) => | |
| 526 _neighboring(false, keys, mockNameFilter, logFilter, | |
| 527 distance, includeKeys); | |
| 528 } | |
| 529 | |
| 530 _MockFailureHandler _mockFailureHandler = null; | |
| 531 | |
| 532 /** | |
| 533 * The failure handler for the [expect()] calls that occur in [verify()] | |
| 534 * methods in the mock objects. This calls the real failure handler used | |
| 535 * by the unit test library after formatting the error message with | |
| 536 * the custom formatter. | |
| 537 */ | |
| 538 class _MockFailureHandler implements FailureHandler { | |
| 539 FailureHandler proxy; | |
| 540 _MockFailureHandler(this.proxy); | |
| 541 void fail(String reason) { | |
| 542 proxy.fail(reason); | |
| 543 } | |
| 544 void failMatch(actual, Matcher matcher, String reason, | |
| 545 Map matchState, bool verbose) { | |
| 546 proxy.fail(_mockingErrorFormatter(actual, matcher, reason, | |
| 547 matchState, verbose)); | |
| 548 } | |
| 549 } | |
| 550 | |
| 551 /** | |
| 552 * The error formatter for mocking is a bit different from the default one | |
| 553 * for unit testing; instead of the third argument being a 'reason' | |
| 554 * it is instead a [signature] describing the method signature filter | |
| 555 * that was used to select the logs that were verified. | |
| 556 */ | |
| 557 String _mockingErrorFormatter(actual, Matcher matcher, String signature, | |
| 558 Map matchState, bool verbose) { | |
| 559 var description = new StringDescription(); | |
| 560 description.add('Expected ${signature} ').addDescriptionOf(matcher). | |
| 561 add('\n but: '); | |
| 562 matcher.describeMismatch(actual, description, matchState, verbose).add('.'); | |
| 563 return description.toString(); | |
| 564 } | |
| OLD | NEW |