Chromium Code Reviews| 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 * 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 */ |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 34 } | 34 } |
| 35 | 35 |
| 36 _MockFailureHandler _mockFailureHandler = null; | 36 _MockFailureHandler _mockFailureHandler = null; |
| 37 | 37 |
| 38 /** | 38 /** |
| 39 * [_noArg] is a sentinel value representing no argument. | 39 * [_noArg] is a sentinel value representing no argument. |
| 40 */ | 40 */ |
| 41 final _noArg = const _Sentinel(); | 41 final _noArg = const _Sentinel(); |
| 42 | 42 |
| 43 /** The ways in which a call to a mock method can be handled. */ | 43 /** The ways in which a call to a mock method can be handled. */ |
| 44 class _Action { | 44 class Action { |
| 45 /** Do nothing (void method) */ | 45 /** Do nothing (void method) */ |
| 46 static final IGNORE = const _Action._('IGNORE'); | 46 static final IGNORE = const Action._('IGNORE'); |
| 47 | 47 |
| 48 /** Return a supplied value. */ | 48 /** Return a supplied value. */ |
| 49 static final RETURN = const _Action._('RETURN'); | 49 static final RETURN = const Action._('RETURN'); |
| 50 | 50 |
| 51 /** Throw a supplied value. */ | 51 /** Throw a supplied value. */ |
| 52 static final THROW = const _Action._('THROW'); | 52 static final THROW = const Action._('THROW'); |
| 53 | 53 |
| 54 /** Call a supplied function. */ | 54 /** Call a supplied function. */ |
| 55 static final PROXY = const _Action._('PROXY'); | 55 static final PROXY = const Action._('PROXY'); |
| 56 | 56 |
| 57 const _Action._(this.name); | 57 const Action._(this.name); |
| 58 | 58 |
| 59 final String name; | 59 final String name; |
| 60 } | 60 } |
| 61 | 61 |
| 62 /** | 62 /** |
| 63 * The behavior of a method call in the mock library is specified | 63 * The behavior of a method call in the mock library is specified |
| 64 * with [Responder]s. A [Responder] has a [value] to throw | 64 * with [Responder]s. A [Responder] has a [value] to throw |
| 65 * or return (depending on whether [isThrow] is true or not, respectively), | 65 * or return (depending on whether [isThrow] is true or not, respectively), |
| 66 * and can either be one-shot, multi-shot, or infinitely repeating, | 66 * and can either be one-shot, multi-shot, or infinitely repeating, |
| 67 * depending on the value of [count (1, greater than 1, or 0 respectively). | 67 * depending on the value of [count (1, greater than 1, or 0 respectively). |
| 68 */ | 68 */ |
| 69 class Responder { | 69 class Responder { |
| 70 var value; | 70 var value; |
| 71 _Action action; | 71 Action action; |
| 72 int count; | 72 int count; |
| 73 Responder(this.value, [this.count = 1, this.action = _Action.RETURN]); | 73 Responder(this.value, [this.count = 1, this.action = Action.RETURN]); |
| 74 } | 74 } |
| 75 | 75 |
| 76 /** | 76 /** |
| 77 * A [CallMatcher] is a special matcher used to match method calls (i.e. | 77 * A [CallMatcher] is a special matcher used to match method calls (i.e. |
| 78 * a method name and set of arguments). It is not a [Matcher] like the | 78 * a method name and set of arguments). It is not a [Matcher] like the |
| 79 * unit test [Matcher], but instead represents a method name and a | 79 * unit test [Matcher], but instead represents a method name and a |
| 80 * collection of [Matcher]s, one per argument, that will be applied | 80 * collection of [Matcher]s, one per argument, that will be applied |
| 81 * to the parameters to decide if the method call is a match. | 81 * to the parameters to decide if the method call is a match. |
| 82 */ | 82 */ |
| 83 class CallMatcher { | 83 class CallMatcher { |
| (...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 202 | 202 |
| 203 Behavior (this.matcher) { | 203 Behavior (this.matcher) { |
| 204 actions = new List<Responder>(); | 204 actions = new List<Responder>(); |
| 205 } | 205 } |
| 206 | 206 |
| 207 /** | 207 /** |
| 208 * Adds a [Responder] that returns a [value] for [count] calls | 208 * Adds a [Responder] that returns a [value] for [count] calls |
| 209 * (1 by default). | 209 * (1 by default). |
| 210 */ | 210 */ |
| 211 Behavior thenReturn(value, [count = 1]) { | 211 Behavior thenReturn(value, [count = 1]) { |
| 212 actions.add(new Responder(value, count, _Action.RETURN)); | 212 actions.add(new Responder(value, count, Action.RETURN)); |
| 213 return this; // For chaining calls. | 213 return this; // For chaining calls. |
| 214 } | 214 } |
| 215 | 215 |
| 216 /** Adds a [Responder] that repeatedly returns a [value]. */ | 216 /** Adds a [Responder] that repeatedly returns a [value]. */ |
| 217 Behavior alwaysReturn(value) { | 217 Behavior alwaysReturn(value) { |
| 218 return thenReturn(value, 0); | 218 return thenReturn(value, 0); |
| 219 } | 219 } |
| 220 | 220 |
| 221 /** | 221 /** |
| 222 * Adds a [Responder] that throws [value] [count] | 222 * Adds a [Responder] that throws [value] [count] |
| 223 * times (1 by default). | 223 * times (1 by default). |
| 224 */ | 224 */ |
| 225 Behavior thenThrow(value, [count = 1]) { | 225 Behavior thenThrow(value, [count = 1]) { |
| 226 actions.add(new Responder(value, count, _Action.THROW)); | 226 actions.add(new Responder(value, count, Action.THROW)); |
| 227 return this; // For chaining calls. | 227 return this; // For chaining calls. |
| 228 } | 228 } |
| 229 | 229 |
| 230 /** Adds a [Responder] that throws [value] endlessly. */ | 230 /** Adds a [Responder] that throws [value] endlessly. */ |
| 231 Behavior alwaysThrow(value) { | 231 Behavior alwaysThrow(value) { |
| 232 return thenThrow(value, 0); | 232 return thenThrow(value, 0); |
| 233 } | 233 } |
| 234 | 234 |
| 235 /** | 235 /** |
| 236 * [thenCall] creates a proxy Responder, that is called [count] | 236 * [thenCall] creates a proxy Responder, that is called [count] |
| 237 * times (1 by default; 0 is used for unlimited calls, and is | 237 * times (1 by default; 0 is used for unlimited calls, and is |
| 238 * exposed as [alwaysCall]). [value] is the function that will | 238 * exposed as [alwaysCall]). [value] is the function that will |
| 239 * be called with the same arguments that were passed to the | 239 * be called with the same arguments that were passed to the |
| 240 * mock. Proxies can be used to wrap real objects or to define | 240 * mock. Proxies can be used to wrap real objects or to define |
| 241 * more complex return/throw behavior. You could even (if you | 241 * more complex return/throw behavior. You could even (if you |
| 242 * wanted) use proxies to emulate the behavior of thenReturn; | 242 * wanted) use proxies to emulate the behavior of thenReturn; |
| 243 * e.g.: | 243 * e.g.: |
| 244 * | 244 * |
| 245 * m.when(callsTo('foo')).thenReturn(0) | 245 * m.when(callsTo('foo')).thenReturn(0) |
| 246 * | 246 * |
| 247 * is equivalent to: | 247 * is equivalent to: |
| 248 * | 248 * |
| 249 * m.when(callsTo('foo')).thenCall(() => 0) | 249 * m.when(callsTo('foo')).thenCall(() => 0) |
| 250 */ | 250 */ |
| 251 Behavior thenCall(value, [count = 1]) { | 251 Behavior thenCall(value, [count = 1]) { |
| 252 actions.add(new Responder(value, count, _Action.PROXY)); | 252 actions.add(new Responder(value, count, Action.PROXY)); |
| 253 return this; // For chaining calls. | 253 return this; // For chaining calls. |
| 254 } | 254 } |
| 255 | 255 |
| 256 /** Creates a repeating proxy call. */ | 256 /** Creates a repeating proxy call. */ |
| 257 Behavior alwaysCall(value) { | 257 Behavior alwaysCall(value) { |
| 258 return thenCall(value, 0); | 258 return thenCall(value, 0); |
| 259 } | 259 } |
| 260 | 260 |
| 261 /** Returns true if a method call matches the [Behavior]. */ | 261 /** Returns true if a method call matches the [Behavior]. */ |
| 262 bool matches(String method, List args) => matcher.matches(method, args); | 262 bool matches(String method, List args) => matcher.matches(method, args); |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 276 /** The mock object name, if any. */ | 276 /** The mock object name, if any. */ |
| 277 final String mockName; | 277 final String mockName; |
| 278 | 278 |
| 279 /** The method name. */ | 279 /** The method name. */ |
| 280 final String methodName; | 280 final String methodName; |
| 281 | 281 |
| 282 /** The parameters. */ | 282 /** The parameters. */ |
| 283 final List args; | 283 final List args; |
| 284 | 284 |
| 285 /** The behavior that resulted. */ | 285 /** The behavior that resulted. */ |
| 286 final _Action action; | 286 final Action action; |
| 287 | 287 |
| 288 /** The value that was returned (if no throw). */ | 288 /** The value that was returned (if no throw). */ |
| 289 final value; | 289 final value; |
| 290 | 290 |
| 291 LogEntry(this.mockName, this.methodName, | 291 LogEntry(this.mockName, this.methodName, |
| 292 this.args, this.action, [this.value]) { | 292 this.args, this.action, [this.value]) { |
| 293 time = new Date.now(); | 293 time = new Date.now(); |
| 294 } | 294 } |
| 295 | 295 |
| 296 String _pad2(int val) => (val >= 10 ? '$val' : '0$val'); | 296 String _pad2(int val) => (val >= 10 ? '$val' : '0$val'); |
| 297 | 297 |
| 298 String toString([Date baseTime]) { | 298 String toString([Date baseTime]) { |
| 299 Description d = new StringDescription(); | 299 Description d = new StringDescription(); |
| 300 if (baseTime == null) { | 300 if (baseTime == null) { |
| 301 // Show absolute time. | 301 // Show absolute time. |
| 302 d.add('${time.hour}:${_pad2(time.minute)}:' | 302 d.add('${time.hour}:${_pad2(time.minute)}:' |
| 303 '${_pad2(time.second)}.${time.millisecond}> '); | 303 '${_pad2(time.second)}.${time.millisecond}> '); |
| 304 } else { | 304 } else { |
| 305 // Show relative time. | 305 // Show relative time. |
| 306 int delta = time.millisecondsSinceEpoch - baseTime.millisecondsSinceEpoch; | 306 int delta = time.millisecondsSinceEpoch - baseTime.millisecondsSinceEpoch; |
| 307 int secs = delta ~/ 1000; | 307 int secs = delta ~/ 1000; |
| 308 int msecs = delta % 1000; | 308 int msecs = delta % 1000; |
| 309 d.add('$secs.$msecs> '); | 309 d.add('$secs.$msecs> '); |
| 310 } | 310 } |
| 311 d.add('${_qualifiedName(mockName, methodName)}('); | 311 d.add('${_qualifiedName(mockName, methodName)}('); |
| 312 for (var i = 0; i < args.length; i++) { | 312 for (var i = 0; i < args.length; i++) { |
| 313 if (i != 0) d.add(', '); | 313 if (i != 0) d.add(', '); |
| 314 d.addDescriptionOf(args[i]); | 314 d.addDescriptionOf(args[i]); |
| 315 } | 315 } |
| 316 d.add(') ${action == _Action.THROW ? "threw" : "returned"} '); | 316 d.add(') ${action == Action.THROW ? "threw" : "returned"} '); |
| 317 d.addDescriptionOf(value); | 317 d.addDescriptionOf(value); |
| 318 return d.toString(); | 318 return d.toString(); |
| 319 } | 319 } |
| 320 } | 320 } |
| 321 | 321 |
| 322 /** Utility function for optionally qualified method names */ | 322 /** Utility function for optionally qualified method names */ |
| 323 String _qualifiedName(owner, String method) { | 323 String _qualifiedName(owner, String method) { |
| 324 if (owner == null || owner === anything) { | 324 if (owner == null || owner === anything) { |
| 325 return method; | 325 return method; |
| 326 } else if (owner is Matcher) { | 326 } else if (owner is Matcher) { |
| 327 Description d = new StringDescription(); | 327 Description d = new StringDescription(); |
| 328 d.addDescriptionOf(owner); | 328 d.addDescriptionOf(owner); |
| 329 d.add('.'); | 329 d.add('.'); |
| 330 d.add(method); | 330 d.add(method); |
| 331 return d.toString(); | 331 return d.toString(); |
| 332 } else { | 332 } else { |
| 333 return '$owner.$method'; | 333 return '$owner.$method'; |
| 334 } | 334 } |
| 335 } | 335 } |
| 336 | 336 |
| 337 /** | 337 /** |
| 338 * We do verification on a list of [LogEntry]s. To allow chaining | 338 * We do verification on a list of [LogEntry]s. To allow chaining |
| 339 * of calls to verify, we encapsulate such a list in the [LogEntryList] | 339 * of calls to verify, we encapsulate such a list in the [LogEntryList] |
| 340 * class. | 340 * class. |
| 341 */ | 341 */ |
| 342 class LogEntryList { | 342 class LogEntryList { |
| 343 final String filter; | 343 String filter; |
| 344 List<LogEntry> logs; | 344 List<LogEntry> logs; |
| 345 LogEntryList([this.filter]) { | 345 LogEntryList([this.filter]) { |
| 346 logs = new List<LogEntry>(); | 346 logs = new List<LogEntry>(); |
| 347 } | 347 } |
| 348 | 348 |
| 349 /** Add a [LogEntry] to the log. */ | 349 /** Add a [LogEntry] to the log. */ |
| 350 add(LogEntry entry) => logs.add(entry); | 350 add(LogEntry entry) => logs.add(entry); |
| 351 | 351 |
| 352 /** Get the first entry, or null if no entries. */ | |
| 353 get first() => (logs == null || logs.length == 0) ? null : logs[0]; | |
| 354 | |
| 355 /** Get the last entry, or null if no entries. */ | |
| 356 get last() => (logs == null || logs.length == 0) ? null : logs.last(); | |
| 357 | |
| 358 /** Creates a LogEntry predicate function from the argument. */ | |
| 359 Function _makePredicate(arg) { | |
| 360 if (arg == null) { | |
| 361 return (e) => true; | |
| 362 } else if (arg is CallMatcher) { | |
| 363 return (e) => arg.matches(e.methodName, e.args); | |
| 364 } else if (arg is Function) { | |
| 365 return arg; | |
| 366 } else { | |
| 367 throw new Exception("Invalid argument to _makePredicate."); | |
| 368 } | |
| 369 } | |
| 370 | |
| 352 /** | 371 /** |
| 353 * Create a new [LogEntryList] consisting of [LogEntry]s from | 372 * Create a new [LogEntryList] consisting of [LogEntry]s from |
| 354 * this list that match the specified [mockNameFilter] and [logFilter]. | 373 * this list that match the specified [mockNameFilter] and [logFilter]. |
| 355 * [mockNameFilter] can be null, a [String], a predicate [Function], | 374 * [mockNameFilter] can be null, a [String], a predicate [Function], |
| 356 * or a [Matcher]. If [mockNameFilter] is null, this is the same as | 375 * or a [Matcher]. If [mockNameFilter] is null, this is the same as |
| 357 * [anything]. | 376 * [anything]. |
| 358 * If [logFilter] is null, all entries in the log will be returned. | 377 * If [logFilter] is null, all entries in the log will be returned. |
| 378 * Otherwise [logFilter] should be a [CallMatcher] or predicate function | |
| 379 * that takes a [LogEntry] and returns a bool. | |
| 359 * If [destructive] is true, the log entries are removed from the | 380 * If [destructive] is true, the log entries are removed from the |
| 360 * original list. | 381 * original list. |
| 361 */ | 382 */ |
| 362 LogEntryList getMatches([mockNameFilter, | 383 LogEntryList getMatches([mockNameFilter, |
| 363 CallMatcher logFilter, | 384 logFilter, |
| 364 Matcher actionMatcher, | 385 Matcher actionMatcher, |
| 365 bool destructive = false]) { | 386 bool destructive = false]) { |
| 366 if (mockNameFilter == null) { | 387 if (mockNameFilter == null) { |
| 367 mockNameFilter = anything; | 388 mockNameFilter = anything; |
| 368 } else { | 389 } else { |
| 369 mockNameFilter = wrapMatcher(mockNameFilter); | 390 mockNameFilter = wrapMatcher(mockNameFilter); |
| 370 } | 391 } |
| 371 if (logFilter == null) { | 392 Function entryFilter = _makePredicate(logFilter); |
| 372 logFilter = new CallMatcher(); | |
| 373 } | |
| 374 String filterName = _qualifiedName(mockNameFilter, logFilter.toString()); | 393 String filterName = _qualifiedName(mockNameFilter, logFilter.toString()); |
| 375 LogEntryList rtn = new LogEntryList(filterName); | 394 LogEntryList rtn = new LogEntryList(filterName); |
| 376 for (var i = 0; i < logs.length; i++) { | 395 for (var i = 0; i < logs.length; i++) { |
| 377 LogEntry entry = logs[i]; | 396 LogEntry entry = logs[i]; |
| 378 if (!mockNameFilter.matches(entry.mockName)) { | 397 if (mockNameFilter.matches(entry.mockName) && entryFilter(entry)) { |
| 379 continue; | |
| 380 } | |
| 381 if (logFilter.matches(entry.methodName, entry.args)) { | |
| 382 if (actionMatcher == null || actionMatcher.matches(entry)) { | 398 if (actionMatcher == null || actionMatcher.matches(entry)) { |
| 383 rtn.add(entry); | 399 rtn.add(entry); |
| 384 if (destructive) { | 400 if (destructive) { |
| 385 logs.removeRange(i--, 1); | 401 logs.removeRange(i--, 1); |
| 386 } | 402 } |
| 387 } | 403 } |
| 388 } | 404 } |
| 389 } | 405 } |
| 390 return rtn; | 406 return rtn; |
| 391 } | 407 } |
| 392 | 408 |
| 393 /** Apply a unit test [Matcher] to the [LogEntryList]. */ | 409 /** Apply a unit test [Matcher] to the [LogEntryList]. */ |
| 394 LogEntryList verify(Matcher matcher) { | 410 LogEntryList verify(Matcher matcher) { |
| 395 if (_mockFailureHandler == null) { | 411 if (_mockFailureHandler == null) { |
| 396 _mockFailureHandler = | 412 _mockFailureHandler = |
| 397 new _MockFailureHandler(getOrCreateExpectFailureHandler()); | 413 new _MockFailureHandler(getOrCreateExpectFailureHandler()); |
| 398 } | 414 } |
| 399 expect(logs, matcher, filter, _mockFailureHandler); | 415 expect(logs, matcher, filter, _mockFailureHandler); |
| 400 return this; | 416 return this; |
| 401 } | 417 } |
| 402 | 418 |
| 419 /** | |
| 420 * Turn the logs into human-readable text. If [baseTime] is specified | |
| 421 * then each entry is prefixed with the offset from that time in | |
| 422 * milliseconds; otherwise the time of day is used. | |
| 423 */ | |
| 403 String toString([Date baseTime]) { | 424 String toString([Date baseTime]) { |
| 404 String s = ''; | 425 String s = ''; |
| 405 for (var e in logs) { | 426 for (var e in logs) { |
| 406 s = '$s${e.toString(baseTime)}\n'; | 427 s = '$s${e.toString(baseTime)}\n'; |
| 407 } | 428 } |
| 408 return s; | 429 return s; |
| 409 } | 430 } |
| 431 | |
| 432 /** | |
| 433 * Find the first log entry that satisfies [logFilter] and | |
| 434 * return its position. A search [start] position can be provided | |
| 435 * to allow for repeated searches. [logFilter] can be a [CallMatcher], | |
| 436 * or a predicate function that takes a [LogEntry] argument and returns | |
| 437 * a bool. If [logFilter] is null, it will match any [LogEntry]. | |
| 438 * If no entry is found, then [failureReturnValue] is returned. | |
| 439 */ | |
| 440 int findLogEntry(logFilter, [int start = 0, int failureReturnValue = -1]) { | |
| 441 logFilter = _makePredicate(logFilter); | |
| 442 int pos = start; | |
| 443 while (pos < logs.length) { | |
| 444 if (logFilter(logs[pos])) { | |
| 445 return pos; | |
| 446 } | |
| 447 ++pos; | |
| 448 } | |
| 449 return failureReturnValue; | |
| 450 } | |
| 451 | |
| 452 /** | |
| 453 * Returns log events that happened up to the first one that | |
| 454 * satisfies [logFilter]. If [inPlace] is true, then returns | |
| 455 * this LogEntryList after removing the from the first satisfier; | |
| 456 * onwards otherwise a new list is created. [description] | |
| 457 * is used to create a new name for the resulting list. | |
| 458 * [defaultPosition] is used as the index of the matching item in | |
| 459 * the case that no match is found. | |
| 460 */ | |
| 461 LogEntryList _head(logFilter, bool inPlace, | |
| 462 String description, int defaultPosition) { | |
| 463 if (filter != null) { | |
| 464 description = '$filter $description'; | |
| 465 } | |
| 466 int pos = findLogEntry(logFilter, 0, defaultPosition); | |
| 467 if (inPlace) { | |
| 468 if (pos < logs.length) { | |
| 469 logs.removeRange(pos, logs.length - pos); | |
| 470 } | |
| 471 filter = description; | |
| 472 return this; | |
| 473 } else { | |
| 474 LogEntryList newList = new LogEntryList(description); | |
| 475 for (var i = 0; i < pos; i++) { | |
| 476 newList.logs.add(logs[i]); | |
| 477 } | |
| 478 return newList; | |
| 479 } | |
| 480 } | |
| 481 | |
| 482 /** | |
| 483 * Returns log events that happened from the first one that | |
| 484 * satisfies [logFilter]. If [inPlace] is true, then returns | |
| 485 * this LogEntryList after removing the entries up to the first | |
| 486 * satisfier; otherwise a new list is created. [description] | |
| 487 * is used to create a new name for the resulting list. | |
| 488 * [defaultPosition] is used as the index of the matching item in | |
| 489 * the case that no match is found. | |
| 490 */ | |
| 491 LogEntryList _tail(logFilter, bool inPlace, | |
| 492 String description, int defaultPosition) { | |
| 493 if (filter != null) { | |
| 494 description = '$filter $description'; | |
| 495 } | |
| 496 int pos = findLogEntry(logFilter, 0, defaultPosition); | |
| 497 if (inPlace) { | |
| 498 if (pos > 0) { | |
| 499 logs.removeRange(0, pos); | |
| 500 } | |
| 501 filter = description; | |
| 502 return this; | |
| 503 } else { | |
| 504 LogEntryList newList = new LogEntryList(description); | |
| 505 while (pos < logs.length) { | |
| 506 newList.logs.add(logs[pos++]); | |
| 507 } | |
| 508 return newList; | |
| 509 } | |
| 510 } | |
| 511 | |
| 512 /** | |
| 513 * Returns log events that happened after [when]. If [inPlace] | |
| 514 * is true, then it returns this LogEntryList after removing | |
| 515 * the entries that happened up to [when]; otherwise a new | |
| 516 * list is created. | |
| 517 */ | |
| 518 LogEntryList after(Date when, [bool inPlace = false]) => | |
| 519 _tail((e) => e.time > when, inPlace, 'after $when', logs.length); | |
| 520 | |
| 521 /** | |
| 522 * Returns log events that happened from [when] onwards. If | |
| 523 * [inPlace] is true, then it returns this LogEntryList after | |
| 524 * removing the entries that happened before [when]; otherwise | |
| 525 * a new list is created. | |
| 526 */ | |
| 527 LogEntryList from(Date when, [bool inPlace = false]) => | |
| 528 _tail((e) => e.time >= when, inPlace, 'from $when', logs.length); | |
| 529 | |
| 530 /** | |
| 531 * Returns log events that happened until [when]. If [inPlace] | |
| 532 * is true, then it returns this LogEntryList after removing | |
| 533 * the entries that happened after [when]; otherwise a new | |
| 534 * list is created. | |
| 535 */ | |
| 536 LogEntryList until(Date when, [bool inPlace = false]) => | |
| 537 _head((e) => e.time > when, inPlace, 'until $when', logs.length); | |
| 538 | |
| 539 /** | |
| 540 * Returns log events that happened before [when]. If [inPlace] | |
| 541 * is true, then it returns this LogEntryList after removing | |
| 542 * the entries that happened from [when] onwards; otherwise a new | |
| 543 * list is created. | |
| 544 */ | |
| 545 LogEntryList before(Date when, [bool inPlace = false]) => | |
| 546 _head((e) => e.time >= when, inPlace, 'before $when', logs.length); | |
| 547 | |
| 548 /** | |
| 549 * Returns log events that happened after [logEntry]'s time. | |
| 550 * If [inPlace] is true, then it returns this LogEntryList after | |
| 551 * removing the entries that happened up to [when]; otherwise a new | |
| 552 * list is created. If [logEntry] is null the current time is used. | |
| 553 */ | |
| 554 LogEntryList afterEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 555 after(logEntry == null ? new Date.now() : logEntry.time); | |
| 556 | |
| 557 /** | |
| 558 * Returns log events that happened from [logEntry]'s time onwards. | |
| 559 * If [inPlace] is true, then it returns this LogEntryList after | |
| 560 * removing the entries that happened before [when]; otherwise | |
| 561 * a new list is created. If [logEntry] is null the current time is used. | |
| 562 */ | |
| 563 LogEntryList fromEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 564 from(logEntry == null ? new Date.now() : logEntry.time); | |
| 565 | |
| 566 /** | |
| 567 * Returns log events that happened until [logEntry]'s time. If | |
| 568 * [inPlace] is true, then it returns this LogEntryList after removing | |
| 569 * the entries that happened after [when]; otherwise a new | |
| 570 * list is created. If [logEntry] is null the epoch time is used. | |
| 571 */ | |
| 572 LogEntryList untilEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 573 until(logEntry == null ? | |
| 574 new Date.fromMillisecondsSinceEpoch(0) : logEntry.time); | |
| 575 | |
| 576 /** | |
| 577 * Returns log events that happened before [logEntry]'s time. If | |
| 578 * [inPlace] is true, then it returns this LogEntryList after removing | |
| 579 * the entries that happened from [when] onwards; otherwise a new | |
| 580 * list is created. If [logEntry] is null the epoch time is used. | |
| 581 */ | |
| 582 LogEntryList beforeEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 583 before(logEntry == null ? | |
| 584 new Date.fromMillisecondsSinceEpoch(0) : logEntry.time); | |
| 585 | |
| 586 /** | |
| 587 * Returns log events that happened after the first event in [segment]. | |
| 588 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 589 * the entries that happened earlier; otherwise a new list is created. | |
| 590 */ | |
| 591 LogEntryList afterFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 592 afterEntry(segment.first, inPlace); | |
| 593 | |
| 594 /** | |
| 595 * Returns log events that happened after the last event in [segment]. | |
| 596 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 597 * the entries that happened earlier; otherwise a new list is created. | |
| 598 */ | |
| 599 LogEntryList afterLast(LogEntryList segment, [bool inPlace = false]) => | |
| 600 afterEntry(segment.last, inPlace); | |
| 601 | |
| 602 /** | |
| 603 * Returns log events that happened from the time of the first event in | |
| 604 * [segment] onwards. If [inPlace] is true, then it returns this | |
| 605 * LogEntryList after removing the earlier entries; otherwise a new list | |
| 606 * is created. | |
| 607 */ | |
| 608 LogEntryList fromFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 609 fromEntry(segment.first, inPlace); | |
| 610 | |
| 611 /** | |
| 612 * Returns log events that happened from the time of the last event in | |
| 613 * [segment] onwards. If [inPlace] is true, then it returns this | |
| 614 * LogEntryList after removing the earlier entries; otherwise a new list | |
| 615 * is created. | |
| 616 */ | |
| 617 LogEntryList fromLast(LogEntryList segment, [bool inPlace = false]) => | |
| 618 fromEntry(segment.last, inPlace); | |
| 619 | |
| 620 /** | |
| 621 * Returns log events that happened until the first event in [segment]. | |
| 622 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 623 * the entries that happened later; otherwise a new list is created. | |
| 624 */ | |
| 625 LogEntryList untilFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 626 untilEntry(segment.first, inPlace); | |
| 627 | |
| 628 /** | |
| 629 * Returns log events that happened until the last event in [segment]. | |
| 630 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 631 * the entries that happened later; otherwise a new list is created. | |
| 632 */ | |
| 633 LogEntryList untilLast(LogEntryList segment, [bool inPlace = false]) => | |
| 634 untilEntry(segment.last, inPlace); | |
| 635 | |
| 636 /** | |
| 637 * Returns log events that happened before the first event in [segment]. | |
| 638 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 639 * the entries that happened later; otherwise a new list is created. | |
| 640 */ | |
| 641 LogEntryList beforeFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 642 beforeEntry(segment.first, inPlace); | |
| 643 | |
| 644 /** | |
| 645 * Returns log events that happened before the last event in [segment]. | |
| 646 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 647 * the entries that happened later; otherwise a new list is created. | |
| 648 */ | |
| 649 LogEntryList beforeLast(LogEntryList segment, [bool inPlace = false]) => | |
| 650 beforeEntry(segment.last, inPlace); | |
| 651 | |
| 652 /** | |
| 653 * Iterate through the LogEntryList looking for matches to the entries | |
| 654 * in [keys]; for each match found the closest [distance] neighboring log | |
| 655 * entries that match [mockNameFilter] and [logFilter] will be included in | |
| 656 * the result. If [isPreceding] is true we use the neighbors that precede | |
| 657 * the matched entry; else we use the neighbors that followed. | |
| 658 * If [includeKeys] is true then the entries in [keys] that resulted in | |
| 659 * entries in the output list are themselves included in the output list. If | |
| 660 * [distance] is zero then all matches are included. | |
| 661 */ | |
| 662 LogEntryList _neighboring(bool isPreceding, | |
| 663 LogEntryList keys, | |
| 664 mockNameFilter, | |
| 665 logFilter, | |
| 666 int distance, | |
| 667 bool includeKeys) { | |
| 668 LogEntryList rtn = new LogEntryList(); | |
| 669 | |
| 670 // Deal with the trivial case. | |
| 671 if (logs.length == 0 || keys.logs.length == 0) { | |
| 672 return rtn; | |
| 673 } | |
| 674 | |
| 675 // Normalize the mockNameFilter and logFilter values. | |
| 676 if (mockNameFilter == null) { | |
| 677 mockNameFilter = anything; | |
| 678 } else { | |
| 679 mockNameFilter = wrapMatcher(mockNameFilter); | |
| 680 } | |
| 681 logFilter = _makePredicate(logFilter); | |
| 682 | |
| 683 // The scratch list is used to hold matching entries when we | |
| 684 // are doing preceding neighbors. The remainingCount is used to | |
| 685 // keep track of how many matching entries we can still add in the | |
| 686 // current segment (0 if we are doing doing following neighbors, until | |
| 687 // we get our first key match). | |
| 688 List scratch = null; | |
| 689 int remainingCount = 0; | |
| 690 if (isPreceding) { | |
| 691 scratch = new List(); | |
| 692 remainingCount = logs.length; | |
| 693 } | |
| 694 | |
| 695 var keyIterator = keys.logs.iterator(); | |
| 696 LogEntry keyEntry = keyIterator.next(); | |
| 697 | |
| 698 for (LogEntry logEntry in logs) { | |
| 699 // 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, | |
| 701 // if appropriate, and reset the scratch buffer. Continue processing | |
| 702 // from the next key entry. | |
| 703 if (keyEntry == logEntry) { | |
| 704 if (scratch != null) { | |
| 705 int numToCopy = scratch.length; | |
| 706 if (distance > 0 && distance < numToCopy) { | |
| 707 numToCopy = distance; | |
| 708 } | |
| 709 for (var i = scratch.length - numToCopy; i < scratch.length; i++) { | |
| 710 rtn.logs.add(scratch[i]); | |
| 711 } | |
| 712 scratch.clear(); | |
| 713 } else { | |
| 714 remainingCount = distance > 0 ? distance : logs.length; | |
| 715 } | |
| 716 if (includeKeys) { | |
| 717 rtn.logs.add(keyEntry); | |
| 718 } | |
| 719 if (keyIterator.hasNext()) { | |
| 720 keyEntry = keyIterator.next(); | |
| 721 } else if (isPreceding) { // We're done. | |
| 722 break; | |
| 723 } | |
| 724 } else if (remainingCount > 0 && | |
| 725 mockNameFilter.matches(logEntry.mockName) && | |
| 726 logFilter(logEntry)) { | |
| 727 if (scratch != null) { | |
| 728 scratch.add(logEntry); | |
| 729 } else { | |
| 730 rtn.logs.add(logEntry); | |
| 731 --remainingCount; | |
| 732 } | |
| 733 } | |
| 734 } | |
| 735 return rtn; | |
| 736 } | |
| 737 | |
| 738 /** | |
| 739 * Iterate through the LogEntryList looking for matches to the entries | |
| 740 * in [keys]; for each match found the closest [distance] prior log entries | |
| 741 * that match [mocknameFilter] and [logFilter] will be included in the result. | |
| 742 * If [includeKeys] is true then the entries in [keys] that resulted in | |
| 743 * entries in the output list are themselves included in the output list. If | |
| 744 * [distance] is zero then all matches are included. | |
| 745 * | |
| 746 * The idea here is that you could find log entries that are related to | |
| 747 * other logs entries in some temporal sense. For example, say we have a | |
| 748 * method commit() that returns -1 on failure. Before commit() gets called | |
| 749 * the value being committed is created by process(). We may want to find | |
| 750 * the calls to process() that preceded calls to commit() that failed. | |
| 751 * We could do this with: | |
| 752 * | |
| 753 * print(log.preceding(log.getLogs(callsTo('commit'), returning(-1)), | |
| 754 * logFilter: callsTo('process')).toString()); | |
|
Siggi Cherem (dart-lang)
2012/07/19 20:02:25
Awesome example!
The print/toString was slighly di
| |
| 755 * | |
| 756 * We might want to include the details of the failing calls to commit() | |
| 757 * to see what parameters were passed in, in which case we would set | |
| 758 * [includeKeys]. | |
| 759 * | |
| 760 * As another simple example, say we wanted to know the three method | |
| 761 * calls that immediately preceded each failing call to commit(): | |
| 762 * | |
| 763 * print(log.preceding(log.getLogs(callsTo('commit'), returning(-1)), | |
| 764 * distance: 3).toString()); | |
|
Siggi Cherem (dart-lang)
2012/07/19 20:02:25
ditto
| |
| 765 */ | |
| 766 LogEntryList preceding(LogEntryList keys, | |
| 767 [mockNameFilter = null, | |
| 768 logFilter = null, | |
| 769 int distance = 1, | |
| 770 bool includeKeys = false]) => | |
| 771 _neighboring(true, keys, mockNameFilter, logFilter, | |
| 772 distance, includeKeys); | |
| 773 | |
| 774 /** | |
| 775 * Iterate through the LogEntryList looking for matches to the entries | |
| 776 * in [keys]; for each match found the closest [distance] subsequent log | |
| 777 * entries that match [mocknameFilter] and [logFilter] will be included in | |
| 778 * the result. If [includeKeys] is true then the entries in [keys] that | |
| 779 * resulted in entries in the output list are themselves included in the | |
| 780 * output list. If [distance] is zero then all matches are included. | |
| 781 * See [preceding] for a usage example. | |
| 782 */ | |
| 783 LogEntryList following(LogEntryList keys, | |
| 784 [mockNameFilter = null, | |
| 785 logFilter = null, | |
| 786 int distance = 1, | |
| 787 bool includeKeys = false]) => | |
| 788 _neighboring(false, keys, mockNameFilter, logFilter, | |
| 789 distance, includeKeys); | |
| 410 } | 790 } |
| 411 | 791 |
| 412 /** | 792 /** |
| 413 * [_TimesMatcher]s are used to make assertions about the number of | 793 * [_TimesMatcher]s are used to make assertions about the number of |
| 414 * times a method was called. | 794 * times a method was called. |
| 415 */ | 795 */ |
| 416 class _TimesMatcher extends BaseMatcher { | 796 class _TimesMatcher extends BaseMatcher { |
| 417 final int min, max; | 797 final int min, max; |
| 418 | 798 |
| 419 const _TimesMatcher(this.min, [this.max = -1]); | 799 const _TimesMatcher(this.min, [this.max = -1]); |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 463 final Matcher happenedAtLeastOnce = const _TimesMatcher(1); | 843 final Matcher happenedAtLeastOnce = const _TimesMatcher(1); |
| 464 | 844 |
| 465 /** [happenedAtMostOnce] matches zero or one call. */ | 845 /** [happenedAtMostOnce] matches zero or one call. */ |
| 466 final Matcher happenedAtMostOnce = const _TimesMatcher(0, 1); | 846 final Matcher happenedAtMostOnce = const _TimesMatcher(0, 1); |
| 467 | 847 |
| 468 /** | 848 /** |
| 469 * [_ResultMatcher]s are used to make assertions about the results | 849 * [_ResultMatcher]s are used to make assertions about the results |
| 470 * of method calls. These can be used as optional parameters to [getLogs]. | 850 * of method calls. These can be used as optional parameters to [getLogs]. |
| 471 */ | 851 */ |
| 472 class _ResultMatcher extends BaseMatcher { | 852 class _ResultMatcher extends BaseMatcher { |
| 473 final _Action action; | 853 final Action action; |
| 474 final Matcher value; | 854 final Matcher value; |
| 475 | 855 |
| 476 const _ResultMatcher(this.action, this.value); | 856 const _ResultMatcher(this.action, this.value); |
| 477 | 857 |
| 478 bool matches(item) { | 858 bool matches(item) { |
| 479 if (item is! LogEntry) { | 859 if (item is! LogEntry) { |
| 480 return false; | 860 return false; |
| 481 } | 861 } |
| 482 // normalize the action; _PROXY is like _RETURN. | 862 // normalize the action; _PROXY is like _RETURN. |
| 483 _Action eaction = item.action; | 863 Action eaction = item.action; |
| 484 if (eaction == _Action.PROXY) { | 864 if (eaction == Action.PROXY) { |
| 485 eaction = _Action.RETURN; | 865 eaction = Action.RETURN; |
| 486 } | 866 } |
| 487 return (eaction == action && value.matches(item.value)); | 867 return (eaction == action && value.matches(item.value)); |
| 488 } | 868 } |
| 489 | 869 |
| 490 Description describe(Description description) { | 870 Description describe(Description description) { |
| 491 description.add(' to '); | 871 description.add(' to '); |
| 492 if (action == _Action.RETURN || action == _Action.PROXY) | 872 if (action == Action.RETURN || action == Action.PROXY) |
| 493 description.add('return '); | 873 description.add('return '); |
| 494 else | 874 else |
| 495 description.add('throw '); | 875 description.add('throw '); |
| 496 return description.addDescriptionOf(value); | 876 return description.addDescriptionOf(value); |
| 497 } | 877 } |
| 498 | 878 |
| 499 Description describeMismatch(item, Description mismatchDescription) { | 879 Description describeMismatch(item, Description mismatchDescription) { |
| 500 if (item.action == _Action.RETURN || item.action == _Action.PROXY) { | 880 if (item.action == Action.RETURN || item.action == Action.PROXY) { |
| 501 mismatchDescription.add('returned '); | 881 mismatchDescription.add('returned '); |
| 502 } else { | 882 } else { |
| 503 mismatchDescription.add('threw '); | 883 mismatchDescription.add('threw '); |
| 504 } | 884 } |
| 505 mismatchDescription.add(item.value); | 885 mismatchDescription.add(item.value); |
| 506 return mismatchDescription; | 886 return mismatchDescription; |
| 507 } | 887 } |
| 508 } | 888 } |
| 509 | 889 |
| 510 /** | 890 /** |
| 511 *[returning] matches log entries where the call to a method returned | 891 *[returning] matches log entries where the call to a method returned |
| 512 * a value that matched [value]. | 892 * a value that matched [value]. |
| 513 */ | 893 */ |
| 514 Matcher returning(value) => | 894 Matcher returning(value) => |
| 515 new _ResultMatcher(_Action.RETURN, wrapMatcher(value)); | 895 new _ResultMatcher(Action.RETURN, wrapMatcher(value)); |
| 516 | 896 |
| 517 /** | 897 /** |
| 518 *[throwing] matches log entrues where the call to a method threw | 898 *[throwing] matches log entrues where the call to a method threw |
| 519 * a value that matched [value]. | 899 * a value that matched [value]. |
| 520 */ | 900 */ |
| 521 Matcher throwing(value) => | 901 Matcher throwing(value) => |
| 522 new _ResultMatcher(_Action.THROW, wrapMatcher(value)); | 902 new _ResultMatcher(Action.THROW, wrapMatcher(value)); |
| 523 | 903 |
| 524 /** Special values for use with [_ResultSetMatcher] [frequency]. */ | 904 /** Special values for use with [_ResultSetMatcher] [frequency]. */ |
| 525 class _Frequency { | 905 class _Frequency { |
| 526 /** Every call/throw must match */ | 906 /** Every call/throw must match */ |
| 527 static final ALL = const _Frequency._('ALL'); | 907 static final ALL = const _Frequency._('ALL'); |
| 528 | 908 |
| 529 /** At least one call/throw must match. */ | 909 /** At least one call/throw must match. */ |
| 530 static final SOME = const _Frequency._('SOME'); | 910 static final SOME = const _Frequency._('SOME'); |
| 531 | 911 |
| 532 /** No calls/throws should match. */ | 912 /** No calls/throws should match. */ |
| 533 static final NONE = const _Frequency._('NONE'); | 913 static final NONE = const _Frequency._('NONE'); |
| 534 | 914 |
| 535 const _Frequency._(this.name); | 915 const _Frequency._(this.name); |
| 536 | 916 |
| 537 final String name; | 917 final String name; |
| 538 } | 918 } |
| 539 | 919 |
| 540 /** | 920 /** |
| 541 * [_ResultSetMatcher]s are used to make assertions about the results | 921 * [_ResultSetMatcher]s are used to make assertions about the results |
| 542 * of method calls. When filtering an execution log by calling | 922 * of method calls. When filtering an execution log by calling |
| 543 * [getLogs], a [LogEntrySet] of matching call logs is returned; | 923 * [getLogs], a [LogEntrySet] of matching call logs is returned; |
| 544 * [_ResultSetMatcher]s can then assert various things about this | 924 * [_ResultSetMatcher]s can then assert various things about this |
| 545 * (sub)set of logs. | 925 * (sub)set of logs. |
| 546 * | 926 * |
| 547 * We could make this class use _ResultMatcher but it doesn't buy that | 927 * We could make this class use _ResultMatcher but it doesn't buy that |
| 548 * match and adds some perf hit, so there is some duplication here. | 928 * match and adds some perf hit, so there is some duplication here. |
| 549 */ | 929 */ |
| 550 class _ResultSetMatcher extends BaseMatcher { | 930 class _ResultSetMatcher extends BaseMatcher { |
| 551 final _Action action; | 931 final Action action; |
| 552 final Matcher value; | 932 final Matcher value; |
| 553 final _Frequency frequency; // ALL, SOME, or NONE. | 933 final _Frequency frequency; // ALL, SOME, or NONE. |
| 554 | 934 |
| 555 const _ResultSetMatcher(this.action, this.value, this.frequency); | 935 const _ResultSetMatcher(this.action, this.value, this.frequency); |
| 556 | 936 |
| 557 bool matches(log) { | 937 bool matches(log) { |
| 558 for (LogEntry entry in log) { | 938 for (LogEntry entry in log) { |
| 559 // normalize the action; _PROXY is like _RETURN. | 939 // normalize the action; PROXY is like RETURN. |
| 560 _Action eaction = entry.action; | 940 Action eaction = entry.action; |
| 561 if (eaction == _Action.PROXY) { | 941 if (eaction == Action.PROXY) { |
| 562 eaction = _Action.RETURN; | 942 eaction = Action.RETURN; |
| 563 } | 943 } |
| 564 if (eaction == action && value.matches(entry.value)) { | 944 if (eaction == action && value.matches(entry.value)) { |
| 565 if (frequency == _Frequency.NONE) { | 945 if (frequency == _Frequency.NONE) { |
| 566 return false; | 946 return false; |
| 567 } else if (frequency == _Frequency.SOME) { | 947 } else if (frequency == _Frequency.SOME) { |
| 568 return true; | 948 return true; |
| 569 } | 949 } |
| 570 } else { | 950 } else { |
| 571 // Mismatch. | 951 // Mismatch. |
| 572 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail. | 952 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail. |
| 573 return false; | 953 return false; |
| 574 } | 954 } |
| 575 } | 955 } |
| 576 } | 956 } |
| 577 // If we get here, then if count is _ALL we got all matches and | 957 // If we get here, then if count is _ALL we got all matches and |
| 578 // this is success; otherwise we got all mismatched which is | 958 // this is success; otherwise we got all mismatched which is |
| 579 // success for count == _NONE and failure for count == _SOME. | 959 // success for count == _NONE and failure for count == _SOME. |
| 580 return (frequency != _Frequency.SOME); | 960 return (frequency != _Frequency.SOME); |
| 581 } | 961 } |
| 582 | 962 |
| 583 Description describe(Description description) { | 963 Description describe(Description description) { |
| 584 description.add(' to '); | 964 description.add(' to '); |
| 585 description.add(frequency == _Frequency.ALL ? 'alway ' : | 965 description.add(frequency == _Frequency.ALL ? 'alway ' : |
| 586 (frequency == _Frequency.NONE ? 'never ' : 'sometimes ')); | 966 (frequency == _Frequency.NONE ? 'never ' : 'sometimes ')); |
| 587 if (action == _Action.RETURN || action == __Action.PROXY) | 967 if (action == Action.RETURN || action == Action.PROXY) |
| 588 description.add('return '); | 968 description.add('return '); |
| 589 else | 969 else |
| 590 description.add('throw '); | 970 description.add('throw '); |
| 591 return description.addDescriptionOf(value); | 971 return description.addDescriptionOf(value); |
| 592 } | 972 } |
| 593 | 973 |
| 594 Description describeMismatch(log, Description mismatchDescription) { | 974 Description describeMismatch(log, Description mismatchDescription) { |
| 595 if (frequency != _Frequency.SOME) { | 975 if (frequency != _Frequency.SOME) { |
| 596 for (LogEntry entry in log) { | 976 for (LogEntry entry in log) { |
| 597 if (entry.action != action || !value.matches(entry.value)) { | 977 if (entry.action != action || !value.matches(entry.value)) { |
| 598 if (entry.action == _Action.RETURN || entry.action == _Action.PROXY) | 978 if (entry.action == Action.RETURN || entry.action == Action.PROXY) |
| 599 mismatchDescription.add('returned '); | 979 mismatchDescription.add('returned '); |
| 600 else | 980 else |
| 601 mismatchDescription.add('threw '); | 981 mismatchDescription.add('threw '); |
| 602 mismatchDescription.add(entry.value); | 982 mismatchDescription.add(entry.value); |
| 603 mismatchDescription.add(' at least once'); | 983 mismatchDescription.add(' at least once'); |
| 604 break; | 984 break; |
| 605 } | 985 } |
| 606 } | 986 } |
| 607 } else { | 987 } else { |
| 608 mismatchDescription.add('never did'); | 988 mismatchDescription.add('never did'); |
| 609 } | 989 } |
| 610 return mismatchDescription; | 990 return mismatchDescription; |
| 611 } | 991 } |
| 612 } | 992 } |
| 613 | 993 |
| 614 /** | 994 /** |
| 615 *[alwaysReturned] asserts that all matching calls to a method returned | 995 *[alwaysReturned] asserts that all matching calls to a method returned |
| 616 * a value that matched [value]. | 996 * a value that matched [value]. |
| 617 */ | 997 */ |
| 618 Matcher alwaysReturned(value) => | 998 Matcher alwaysReturned(value) => |
| 619 new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.ALL); | 999 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.ALL); |
| 620 | 1000 |
| 621 /** | 1001 /** |
| 622 *[sometimeReturned] asserts that at least one matching call to a method | 1002 *[sometimeReturned] asserts that at least one matching call to a method |
| 623 * returned a value that matched [value]. | 1003 * returned a value that matched [value]. |
| 624 */ | 1004 */ |
| 625 Matcher sometimeReturned(value) => | 1005 Matcher sometimeReturned(value) => |
| 626 new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.SOME); | 1006 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.SOME); |
| 627 | 1007 |
| 628 /** | 1008 /** |
| 629 *[neverReturned] asserts that no matching calls to a method returned | 1009 *[neverReturned] asserts that no matching calls to a method returned |
| 630 * a value that matched [value]. | 1010 * a value that matched [value]. |
| 631 */ | 1011 */ |
| 632 Matcher neverReturned(value) => | 1012 Matcher neverReturned(value) => |
| 633 new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.NONE); | 1013 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.NONE); |
| 634 | 1014 |
| 635 /** | 1015 /** |
| 636 *[alwaysThrew] asserts that all matching calls to a method threw | 1016 *[alwaysThrew] asserts that all matching calls to a method threw |
| 637 * a value that matched [value]. | 1017 * a value that matched [value]. |
| 638 */ | 1018 */ |
| 639 Matcher alwaysThrew(value) => | 1019 Matcher alwaysThrew(value) => |
| 640 new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.ALL); | 1020 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.ALL); |
| 641 | 1021 |
| 642 /** | 1022 /** |
| 643 *[sometimeThrew] asserts that at least one matching call to a method threw | 1023 *[sometimeThrew] asserts that at least one matching call to a method threw |
| 644 * a value that matched [value]. | 1024 * a value that matched [value]. |
| 645 */ | 1025 */ |
| 646 Matcher sometimeThrew(value) => | 1026 Matcher sometimeThrew(value) => |
| 647 new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.SOME); | 1027 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.SOME); |
| 648 | 1028 |
| 649 /** | 1029 /** |
| 650 *[neverThrew] asserts that no matching call to a method threw | 1030 *[neverThrew] asserts that no matching call to a method threw |
| 651 * a value that matched [value]. | 1031 * a value that matched [value]. |
| 652 */ | 1032 */ |
| 653 Matcher neverThrew(value) => | 1033 Matcher neverThrew(value) => |
| 654 new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.NONE); | 1034 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.NONE); |
| 655 | 1035 |
| 656 /** The shared log used for named mocks. */ | 1036 /** The shared log used for named mocks. */ |
| 657 LogEntryList sharedLog = null; | 1037 LogEntryList sharedLog = null; |
| 658 | 1038 |
| 659 /** | 1039 /** |
| 660 * [Mock] is the base class for all mocked objects, with | 1040 * [Mock] is the base class for all mocked objects, with |
| 661 * support for basic mocking. | 1041 * support for basic mocking. |
| 662 * | 1042 * |
| 663 * To create a mock objects for some class T, create a new class using: | 1043 * To create a mock objects for some class T, create a new class using: |
| 664 * | 1044 * |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 743 /** The [log] of calls made. Only used if [name] is null. */ | 1123 /** The [log] of calls made. Only used if [name] is null. */ |
| 744 LogEntryList log; | 1124 LogEntryList log; |
| 745 | 1125 |
| 746 /** How to handle unknown method calls - swallow or throw. */ | 1126 /** How to handle unknown method calls - swallow or throw. */ |
| 747 final bool _throwIfNoBehavior; | 1127 final bool _throwIfNoBehavior; |
| 748 | 1128 |
| 749 /** Whether to create an audit log or not. */ | 1129 /** Whether to create an audit log or not. */ |
| 750 bool _logging; | 1130 bool _logging; |
| 751 | 1131 |
| 752 bool get logging() => _logging; | 1132 bool get logging() => _logging; |
| 753 bool set logging(bool value) { | 1133 set logging(bool value) { |
| 754 if (value && log == null) { | 1134 if (value && log == null) { |
| 755 log = new LogEntryList(); | 1135 log = new LogEntryList(); |
| 756 } | 1136 } |
| 757 _logging = value; | 1137 _logging = value; |
| 758 } | 1138 } |
| 759 | 1139 |
| 760 /** | 1140 /** |
| 761 * Default constructor. Unknown method calls are allowed and logged, | 1141 * Default constructor. Unknown method calls are allowed and logged, |
| 762 * the mock has no name, and has its own log. | 1142 * the mock has no name, and has its own log. |
| 763 */ | 1143 */ |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 830 // Get the first response. | 1210 // Get the first response. |
| 831 Responder response = actions[0]; | 1211 Responder response = actions[0]; |
| 832 // If it is exhausted, remove it from the list. | 1212 // If it is exhausted, remove it from the list. |
| 833 // Note that for endlessly repeating values, we started the count at | 1213 // Note that for endlessly repeating values, we started the count at |
| 834 // 0, so we get a potentially useful value here, which is the | 1214 // 0, so we get a potentially useful value here, which is the |
| 835 // (negation of) the number of times we returned the value. | 1215 // (negation of) the number of times we returned the value. |
| 836 if (--response.count == 0) { | 1216 if (--response.count == 0) { |
| 837 actions.removeRange(0, 1); | 1217 actions.removeRange(0, 1); |
| 838 } | 1218 } |
| 839 // Do the response. | 1219 // Do the response. |
| 840 _Action action = response.action; | 1220 Action action = response.action; |
| 841 var value = response.value; | 1221 var value = response.value; |
| 842 if (action == _Action.RETURN) { | 1222 if (action == Action.RETURN) { |
| 843 if (_logging) { | 1223 if (_logging) { |
| 844 log.add(new LogEntry(name, method, args, action, value)); | 1224 log.add(new LogEntry(name, method, args, action, value)); |
| 845 } | 1225 } |
| 846 return value; | 1226 return value; |
| 847 } else if (action == _Action.THROW) { | 1227 } else if (action == Action.THROW) { |
| 848 if (_logging) { | 1228 if (_logging) { |
| 849 log.add(new LogEntry(name, method, args, action, value)); | 1229 log.add(new LogEntry(name, method, args, action, value)); |
| 850 } | 1230 } |
| 851 throw value; | 1231 throw value; |
| 852 } else if (action == _Action.PROXY) { | 1232 } else if (action == Action.PROXY) { |
| 853 var rtn; | 1233 var rtn; |
| 854 switch (args.length) { | 1234 switch (args.length) { |
| 855 case 0: | 1235 case 0: |
| 856 rtn = value(); | 1236 rtn = value(); |
| 857 break; | 1237 break; |
| 858 case 1: | 1238 case 1: |
| 859 rtn = value(args[0]); | 1239 rtn = value(args[0]); |
| 860 break; | 1240 break; |
| 861 case 2: | 1241 case 2: |
| 862 rtn = value(args[0], args[1]); | 1242 rtn = value(args[0], args[1]); |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 906 // actions are exhausted. This is considered an error. | 1286 // actions are exhausted. This is considered an error. |
| 907 throw new Exception('No more actions for method ' | 1287 throw new Exception('No more actions for method ' |
| 908 '${_qualifiedName(name, method)}.'); | 1288 '${_qualifiedName(name, method)}.'); |
| 909 } else if (_throwIfNoBehavior) { | 1289 } else if (_throwIfNoBehavior) { |
| 910 throw new Exception('No behavior specified for method ' | 1290 throw new Exception('No behavior specified for method ' |
| 911 '${_qualifiedName(name, method)}.'); | 1291 '${_qualifiedName(name, method)}.'); |
| 912 } | 1292 } |
| 913 // Otherwise user hasn't specified behavior for this method; we don't throw | 1293 // Otherwise user hasn't specified behavior for this method; we don't throw |
| 914 // so we can underspecify. | 1294 // so we can underspecify. |
| 915 if (_logging) { | 1295 if (_logging) { |
| 916 log.add(new LogEntry(name, method, args, _Action.IGNORE)); | 1296 log.add(new LogEntry(name, method, args, Action.IGNORE)); |
| 917 } | 1297 } |
| 918 } | 1298 } |
| 919 | 1299 |
| 920 /** [verifyZeroInteractions] returns true if no calls were made */ | 1300 /** [verifyZeroInteractions] returns true if no calls were made */ |
| 921 bool verifyZeroInteractions() { | 1301 bool verifyZeroInteractions() { |
| 922 if (log == null) { | 1302 if (log == null) { |
| 923 // This means we created the mock with logging off and have never turned | 1303 // This means we created the mock with logging off and have never turned |
| 924 // it on, so it doesn't make sense to verify behavior on such a mock. | 1304 // it on, so it doesn't make sense to verify behavior on such a mock. |
| 925 throw new | 1305 throw new |
| 926 Exception("Can't verify behavior when logging was never enabled."); | 1306 Exception("Can't verify behavior when logging was never enabled."); |
| 927 } | 1307 } |
| 928 return log.logs.length == 0; | 1308 return log.logs.length == 0; |
| 929 } | 1309 } |
| 930 | 1310 |
| 931 /** | 1311 /** |
| 932 * [getLogs] extracts all calls from the call log that match the | 1312 * [getLogs] extracts all calls from the call log that match the |
| 933 * [logFilter] [CallMatcher], and returns the matching list of | 1313 * [logFilter], and returns the matching list of [LogEntry]s. If |
| 934 * [LogEntry]s. If [destructive] is false (the default) the matching | 1314 * [destructive] is false (the default) the matching calls are left |
| 935 * calls are left in the log, else they are removed. Removal allows | 1315 * in the log, else they are removed. Removal allows us to verify a |
| 936 * us to verify a set of interactions and then verify that there are | 1316 * set of interactions and then verify that there are no other |
| 937 * no other interactions left. [actionMatcher] can be used to further | 1317 * interactions left. [actionMatcher] can be used to further |
| 938 * restrict the returned logs based on the action the mock performed. | 1318 * restrict the returned logs based on the action the mock performed. |
| 1319 * [logFilter] can be a [CallMatcher] or a predicate function that | |
| 1320 * takes a [LogEntry] and returns a bool. | |
| 939 * | 1321 * |
| 940 * Typical usage: | 1322 * Typical usage: |
| 941 * | 1323 * |
| 942 * getLogs(callsTo(...)).verify(...); | 1324 * getLogs(callsTo(...)).verify(...); |
| 943 */ | 1325 */ |
| 944 LogEntryList getLogs([CallMatcher logFilter, | 1326 LogEntryList getLogs([CallMatcher logFilter, |
| 945 Matcher actionMatcher, | 1327 Matcher actionMatcher, |
| 946 bool destructive = false]) { | 1328 bool destructive = false]) { |
| 947 if (log == null) { | 1329 if (log == null) { |
| 948 // This means we created the mock with logging off and have never turned | 1330 // This means we created the mock with logging off and have never turned |
| 949 // it on, so it doesn't make sense to get logs from such a mock. | 1331 // it on, so it doesn't make sense to get logs from such a mock. |
| 950 throw new | 1332 throw new |
| 951 Exception("Can't retrieve logs when logging was never enabled."); | 1333 Exception("Can't retrieve logs when logging was never enabled."); |
| 952 } else { | 1334 } else { |
| 953 return log.getMatches(name, logFilter, actionMatcher, destructive); | 1335 return log.getMatches(name, logFilter, actionMatcher, destructive); |
| 954 } | 1336 } |
| 955 } | 1337 } |
| 1338 | |
| 1339 /** | |
| 1340 * Useful shorthand method that creates a [CallMatcher] from its arguments | |
| 1341 * and then calls [getLogs]. | |
| 1342 */ | |
| 1343 LogEntryList calls(method, | |
| 1344 [arg0 = _noArg, | |
| 1345 arg1 = _noArg, | |
| 1346 arg2 = _noArg, | |
| 1347 arg3 = _noArg, | |
| 1348 arg4 = _noArg, | |
| 1349 arg5 = _noArg, | |
| 1350 arg6 = _noArg, | |
| 1351 arg7 = _noArg, | |
| 1352 arg8 = _noArg, | |
| 1353 arg9 = _noArg]) => | |
| 1354 getLogs(callsTo(method, arg0, arg1, arg2, arg3, arg4, | |
| 1355 arg5, arg6, arg7, arg8, arg9)); | |
| 956 } | 1356 } |
| OLD | NEW |