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() { | |
|
Siggi Cherem (dart-lang)
2012/07/19 18:05:00
we might fit this in a one liner :)
get first() =>
gram
2012/07/19 18:52:19
Done.
| |
| 354 if (logs == null || logs.length == 0) { | |
| 355 return null; | |
| 356 } | |
| 357 return logs[0]; | |
| 358 } | |
| 359 | |
| 360 /** Get the last entry, or null if no entries. */ | |
| 361 get last() { | |
|
Siggi Cherem (dart-lang)
2012/07/19 18:05:00
ditto
gram
2012/07/19 18:52:19
Done.
| |
| 362 if (logs == null || logs.length == 0) { | |
| 363 return null; | |
| 364 } | |
| 365 return logs[logs.length-1]; | |
|
Siggi Cherem (dart-lang)
2012/07/19 18:05:00
use logs.last() instead?
gram
2012/07/19 18:52:19
Done.
| |
| 366 } | |
| 367 | |
| 368 /** Creates a LogEntry predicate function from the argument. */ | |
| 369 Function _makePredicate(arg) { | |
| 370 if (arg == null) { | |
| 371 return (e) => true; | |
| 372 } else if (arg is CallMatcher) { | |
| 373 return (e) => arg.matches(e.methodName, e.args); | |
| 374 } else if (arg is Function) { | |
| 375 return arg; | |
| 376 } else { | |
| 377 throw new Exception("Invalid argument to _makePredicate."); | |
| 378 } | |
| 379 } | |
| 380 | |
| 352 /** | 381 /** |
| 353 * Create a new [LogEntryList] consisting of [LogEntry]s from | 382 * Create a new [LogEntryList] consisting of [LogEntry]s from |
| 354 * this list that match the specified [mockNameFilter] and [logFilter]. | 383 * this list that match the specified [mockNameFilter] and [logFilter]. |
| 355 * [mockNameFilter] can be null, a [String], a predicate [Function], | 384 * [mockNameFilter] can be null, a [String], a predicate [Function], |
| 356 * or a [Matcher]. If [mockNameFilter] is null, this is the same as | 385 * or a [Matcher]. If [mockNameFilter] is null, this is the same as |
| 357 * [anything]. | 386 * [anything]. |
| 358 * If [logFilter] is null, all entries in the log will be returned. | 387 * If [logFilter] is null, all entries in the log will be returned. |
| 388 * Otherwise [logFilter] should be a [CallMatcher] or predicate function | |
| 389 * that takes a [LogEntry] and returns a bool. | |
| 359 * If [destructive] is true, the log entries are removed from the | 390 * If [destructive] is true, the log entries are removed from the |
| 360 * original list. | 391 * original list. |
| 361 */ | 392 */ |
| 362 LogEntryList getMatches([mockNameFilter, | 393 LogEntryList getMatches([mockNameFilter, |
| 363 CallMatcher logFilter, | 394 logFilter, |
| 364 Matcher actionMatcher, | 395 Matcher actionMatcher, |
| 365 bool destructive = false]) { | 396 bool destructive = false]) { |
| 366 if (mockNameFilter == null) { | 397 if (mockNameFilter == null) { |
| 367 mockNameFilter = anything; | 398 mockNameFilter = anything; |
| 368 } else { | 399 } else { |
| 369 mockNameFilter = wrapMatcher(mockNameFilter); | 400 mockNameFilter = wrapMatcher(mockNameFilter); |
| 370 } | 401 } |
| 371 if (logFilter == null) { | 402 Function entryFilter = _makePredicate(logFilter); |
| 372 logFilter = new CallMatcher(); | |
| 373 } | |
| 374 String filterName = _qualifiedName(mockNameFilter, logFilter.toString()); | 403 String filterName = _qualifiedName(mockNameFilter, logFilter.toString()); |
| 375 LogEntryList rtn = new LogEntryList(filterName); | 404 LogEntryList rtn = new LogEntryList(filterName); |
| 376 for (var i = 0; i < logs.length; i++) { | 405 for (var i = 0; i < logs.length; i++) { |
| 377 LogEntry entry = logs[i]; | 406 LogEntry entry = logs[i]; |
| 378 if (!mockNameFilter.matches(entry.mockName)) { | 407 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)) { | 408 if (actionMatcher == null || actionMatcher.matches(entry)) { |
| 383 rtn.add(entry); | 409 rtn.add(entry); |
| 384 if (destructive) { | 410 if (destructive) { |
| 385 logs.removeRange(i--, 1); | 411 logs.removeRange(i--, 1); |
| 386 } | 412 } |
| 387 } | 413 } |
| 388 } | 414 } |
| 389 } | 415 } |
| 390 return rtn; | 416 return rtn; |
| 391 } | 417 } |
| 392 | 418 |
| 393 /** Apply a unit test [Matcher] to the [LogEntryList]. */ | 419 /** Apply a unit test [Matcher] to the [LogEntryList]. */ |
| 394 LogEntryList verify(Matcher matcher) { | 420 LogEntryList verify(Matcher matcher) { |
| 395 if (_mockFailureHandler == null) { | 421 if (_mockFailureHandler == null) { |
| 396 _mockFailureHandler = | 422 _mockFailureHandler = |
| 397 new _MockFailureHandler(getOrCreateExpectFailureHandler()); | 423 new _MockFailureHandler(getOrCreateExpectFailureHandler()); |
| 398 } | 424 } |
| 399 expect(logs, matcher, filter, _mockFailureHandler); | 425 expect(logs, matcher, filter, _mockFailureHandler); |
| 400 return this; | 426 return this; |
| 401 } | 427 } |
| 402 | 428 |
| 429 /** | |
| 430 * Turn the logs into human-readable text. If [baseTime] is specified | |
| 431 * then each entry is prefixed with the offset from that time in | |
| 432 * milliseconds; otherwise the time of day is used. | |
| 433 */ | |
| 403 String toString([Date baseTime]) { | 434 String toString([Date baseTime]) { |
| 404 String s = ''; | 435 String s = ''; |
| 405 for (var e in logs) { | 436 for (var e in logs) { |
| 406 s = '$s${e.toString(baseTime)}\n'; | 437 s = '$s${e.toString(baseTime)}\n'; |
| 407 } | 438 } |
| 408 return s; | 439 return s; |
| 409 } | 440 } |
| 441 | |
| 442 /** | |
| 443 * Find the first log entry that satisfies [logFilter] and | |
| 444 * return its position. A search [start] position can be provided | |
| 445 * to allow for repeated searches. [logFilter] can be a [CallMatcher], | |
| 446 * or a predicate function that takes a [LogEntry] argument and returns | |
| 447 * a bool. If [logFilter] is null, it will match any [LogEntry]. | |
| 448 * If no entry is found, then [failureReturnValue] is returned. | |
| 449 */ | |
| 450 int findLogEntry(logFilter, [int start = 0, int failureReturnValue = -1]) { | |
| 451 logFilter = _makePredicate(logFilter); | |
| 452 int pos = start; | |
| 453 while (pos < logs.length) { | |
| 454 if (logFilter(logs[pos])) { | |
| 455 return pos; | |
| 456 } | |
| 457 ++pos; | |
| 458 } | |
| 459 return failureReturnValue; | |
| 460 } | |
| 461 | |
| 462 /** | |
| 463 * Returns log events that happened up to the first one that | |
| 464 * satisfies [logFilter]. If [inPlace] is true, then returns | |
| 465 * this LogEntryList after removing the from the first satisfier; | |
| 466 * onwards otherwise a new list is created. [description] | |
| 467 * is used to create a new name for the resulting list. | |
| 468 * [defaultPosition] is used as the index of the matching item in | |
| 469 * the case that no match is found. | |
| 470 */ | |
| 471 LogEntryList _head(logFilter, bool inPlace, | |
| 472 String description, int defaultPosition) { | |
| 473 if (filter != null) { | |
| 474 description = '$filter $description'; | |
| 475 } | |
| 476 int pos = findLogEntry(logFilter, 0, defaultPosition); | |
| 477 if (inPlace) { | |
| 478 if (pos < logs.length) { | |
| 479 logs.removeRange(pos, logs.length - pos); | |
| 480 } | |
| 481 filter = description; | |
| 482 return this; | |
| 483 } else { | |
| 484 LogEntryList newList = new LogEntryList(description); | |
| 485 for (var i = 0; i < pos; i++) { | |
| 486 newList.logs.add(logs[i]); | |
| 487 } | |
| 488 return newList; | |
| 489 } | |
| 490 } | |
| 491 | |
| 492 /** | |
| 493 * Returns log events that happened from the first one that | |
| 494 * satisfies [logFilter]. If [inPlace] is true, then returns | |
| 495 * this LogEntryList after removing the entries up to the first | |
| 496 * satisfier; otherwise a new list is created. [description] | |
| 497 * is used to create a new name for the resulting list. | |
| 498 * [defaultPosition] is used as the index of the matching item in | |
| 499 * the case that no match is found. | |
| 500 */ | |
| 501 LogEntryList _tail(logFilter, bool inPlace, | |
| 502 String description, int defaultPosition) { | |
| 503 if (filter != null) { | |
| 504 description = '$filter $description'; | |
| 505 } | |
| 506 int pos = findLogEntry(logFilter, 0, defaultPosition); | |
| 507 if (inPlace) { | |
| 508 if (pos > 0) { | |
| 509 logs.removeRange(0, pos); | |
| 510 } | |
| 511 filter = description; | |
| 512 return this; | |
| 513 } else { | |
| 514 LogEntryList newList = new LogEntryList(description); | |
| 515 while (pos < logs.length) { | |
| 516 newList.logs.add(logs[pos++]); | |
| 517 } | |
| 518 return newList; | |
| 519 } | |
| 520 } | |
| 521 | |
| 522 /** | |
| 523 * Returns log events that happened after [when]. If [inPlace] | |
| 524 * is true, then it returns this LogEntryList after removing | |
| 525 * the entries that happened up to [when]; otherwise a new | |
| 526 * list is created. | |
| 527 */ | |
| 528 LogEntryList after(Date when, [bool inPlace = false]) => | |
| 529 _tail((e) => e.time > when, inPlace, 'after $when', logs.length); | |
| 530 | |
| 531 /** | |
| 532 * Returns log events that happened from [when] onwards. If | |
| 533 * [inPlace] is true, then it returns this LogEntryList after | |
| 534 * removing the entries that happened before [when]; otherwise | |
| 535 * a new list is created. | |
| 536 */ | |
| 537 LogEntryList from(Date when, [bool inPlace = false]) => | |
| 538 _tail((e) => e.time >= when, inPlace, 'from $when', logs.length); | |
| 539 | |
| 540 /** | |
| 541 * Returns log events that happened until [when]. If [inPlace] | |
| 542 * is true, then it returns this LogEntryList after removing | |
| 543 * the entries that happened after [when]; otherwise a new | |
| 544 * list is created. | |
| 545 */ | |
| 546 LogEntryList until(Date when, [bool inPlace = false]) => | |
| 547 _head((e) => e.time > when, inPlace, 'until $when', logs.length); | |
| 548 | |
| 549 /** | |
| 550 * Returns log events that happened before [when]. If [inPlace] | |
| 551 * is true, then it returns this LogEntryList after removing | |
| 552 * the entries that happened from [when] onwards; otherwise a new | |
| 553 * list is created. | |
| 554 */ | |
| 555 LogEntryList before(Date when, [bool inPlace = false]) => | |
| 556 _head((e) => e.time >= when, inPlace, 'before $when', logs.length); | |
| 557 | |
| 558 /** | |
| 559 * Returns log events that happened after [logEntry]'s time. | |
| 560 * If [inPlace] is true, then it returns this LogEntryList after | |
| 561 * removing the entries that happened up to [when]; otherwise a new | |
| 562 * list is created. If [logEntry] is null the current time is used. | |
| 563 */ | |
| 564 LogEntryList afterEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 565 after(logEntry == null ? new Date.now() : logEntry.time); | |
| 566 | |
| 567 /** | |
| 568 * Returns log events that happened from [logEntry]'s time onwards. | |
| 569 * If [inPlace] is true, then it returns this LogEntryList after | |
| 570 * removing the entries that happened before [when]; otherwise | |
| 571 * a new list is created. If [logEntry] is null the current time is used. | |
| 572 */ | |
| 573 LogEntryList fromEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 574 from(logEntry == null ? new Date.now() : logEntry.time); | |
| 575 | |
| 576 /** | |
| 577 * Returns log events that happened until [logEntry]'s time. If | |
| 578 * [inPlace] is true, then it returns this LogEntryList after removing | |
| 579 * the entries that happened after [when]; otherwise a new | |
| 580 * list is created. If [logEntry] is null the epoch time is used. | |
| 581 */ | |
| 582 LogEntryList untilEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 583 until(logEntry == null ? | |
| 584 new Date.fromMillisecondsSinceEpoch(0) : logEntry.time); | |
| 585 | |
| 586 /** | |
| 587 * Returns log events that happened before [logEntry]'s time. If | |
| 588 * [inPlace] is true, then it returns this LogEntryList after removing | |
| 589 * the entries that happened from [when] onwards; otherwise a new | |
| 590 * list is created. If [logEntry] is null the epoch time is used. | |
| 591 */ | |
| 592 LogEntryList beforeEntry(LogEntry logEntry, [bool inPlace = false]) => | |
| 593 before(logEntry == null ? | |
| 594 new Date.fromMillisecondsSinceEpoch(0) : logEntry.time); | |
| 595 | |
| 596 /** | |
| 597 * Returns log events that happened after the first event in [segment]. | |
| 598 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 599 * the entries that happened earlier; otherwise a new list is created. | |
| 600 */ | |
| 601 LogEntryList afterFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 602 afterEntry(segment.first, inPlace); | |
| 603 | |
| 604 /** | |
| 605 * Returns log events that happened after the last event in [segment]. | |
| 606 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 607 * the entries that happened earlier; otherwise a new list is created. | |
| 608 */ | |
| 609 LogEntryList afterLast(LogEntryList segment, [bool inPlace = false]) => | |
| 610 afterEntry(segment.last, inPlace); | |
| 611 | |
| 612 /** | |
| 613 * Returns log events that happened from the time of the first event in | |
| 614 * [segment] onwards. If [inPlace] is true, then it returns this | |
| 615 * LogEntryList after removing the earlier entries; otherwise a new list | |
| 616 * is created. | |
| 617 */ | |
| 618 LogEntryList fromFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 619 fromEntry(segment.first, inPlace); | |
| 620 | |
| 621 /** | |
| 622 * Returns log events that happened from the time of the last event in | |
| 623 * [segment] onwards. If [inPlace] is true, then it returns this | |
| 624 * LogEntryList after removing the earlier entries; otherwise a new list | |
| 625 * is created. | |
| 626 */ | |
| 627 LogEntryList fromLast(LogEntryList segment, [bool inPlace = false]) => | |
|
Siggi Cherem (dart-lang)
2012/07/19 18:05:00
we should clarify on these comments the edge condi
gram
2012/07/19 18:52:19
segment here need not have any elements in common
Siggi Cherem (dart-lang)
2012/07/19 20:02:25
Thanks for the clarification, it makes a lot of se
| |
| 628 fromEntry(segment.last, inPlace); | |
| 629 | |
| 630 /** | |
| 631 * Returns log events that happened until the first event in [segment]. | |
| 632 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 633 * the entries that happened later; otherwise a new list is created. | |
| 634 */ | |
| 635 LogEntryList untilFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 636 untilEntry(segment.first, inPlace); | |
| 637 | |
| 638 /** | |
| 639 * Returns log events that happened until the last event in [segment]. | |
| 640 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 641 * the entries that happened later; otherwise a new list is created. | |
| 642 */ | |
| 643 LogEntryList untilLast(LogEntryList segment, [bool inPlace = false]) => | |
| 644 untilEntry(segment.last, inPlace); | |
| 645 | |
| 646 /** | |
| 647 * Returns log events that happened before the first event in [segment]. | |
| 648 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 649 * the entries that happened later; otherwise a new list is created. | |
| 650 */ | |
| 651 LogEntryList beforeFirst(LogEntryList segment, [bool inPlace = false]) => | |
| 652 beforeEntry(segment.first, inPlace); | |
| 653 | |
| 654 /** | |
| 655 * Returns log events that happened before the last event in [segment]. | |
| 656 * If [inPlace] is true, then it returns this LogEntryList after removing | |
| 657 * the entries that happened later; otherwise a new list is created. | |
| 658 */ | |
| 659 LogEntryList beforeLast(LogEntryList segment, [bool inPlace = false]) => | |
| 660 beforeEntry(segment.last, inPlace); | |
| 661 | |
| 662 /** | |
| 663 * Iterate through the LogEntryList looking for matches to the entries | |
| 664 * in [keys]; for each match found the closest [distance] neighboring log | |
| 665 * entries that match [mocknameFilter] and [logFilter] will be included in | |
|
Siggi Cherem (dart-lang)
2012/07/19 18:05:00
mockname -> mockName (capital N)
gram
2012/07/19 18:52:19
Done.
| |
| 666 * the result. If [isPreceding] is true we use the neighbors that precede | |
| 667 * the matched entry; else we use the neighbors that followed. | |
| 668 * If [includeKeys] is true then the entries in [keys] that resulted in | |
| 669 * entries in the output list are themselves included in the output list. If | |
| 670 * [distance] is zero then all matches are included. | |
| 671 */ | |
| 672 LogEntryList _neighboring(bool isPreceding, | |
| 673 LogEntryList keys, | |
| 674 mockNameFilter, | |
| 675 logFilter, | |
| 676 int distance, | |
| 677 bool includeKeys) { | |
| 678 LogEntryList rtn = new LogEntryList(); | |
| 679 | |
| 680 // Deal with the trivial case. | |
| 681 if (logs.length == 0 || keys.logs.length == 0) { | |
| 682 return rtn; | |
| 683 } | |
| 684 | |
| 685 // Normalize the mockNameFilter and logFilter values. | |
| 686 if (mockNameFilter == null) { | |
| 687 mockNameFilter = anything; | |
| 688 } else { | |
| 689 mockNameFilter = wrapMatcher(mockNameFilter); | |
| 690 } | |
| 691 logFilter = _makePredicate(logFilter); | |
| 692 | |
| 693 var keyIterator = keys.logs.iterator(); | |
| 694 var logIterator = logs.iterator(); | |
| 695 // The scratch list is used to hold matching entries when we | |
| 696 // are doing preceding neighbors. The remainingCount is used to | |
| 697 // keep track of how many matching entries we can still add in the | |
| 698 // current segment (0 if we are doing doing following neighbors, until | |
| 699 // we get our first key match). | |
| 700 List scratch = null; | |
| 701 int remainingCount = 0; | |
| 702 if (isPreceding) { | |
| 703 scratch = new List(); | |
| 704 remainingCount = logs.length; | |
| 705 } | |
| 706 | |
| 707 bool gotEntryMatches = false; | |
| 708 LogEntry keyEntry = keyIterator.next(); | |
| 709 | |
| 710 while (logIterator.hasNext()) { | |
| 711 LogEntry logEntry = logIterator.next(); | |
|
Siggi Cherem (dart-lang)
2012/07/19 18:05:00
I don't see why you need to use the iterator expli
gram
2012/07/19 18:52:19
You're right - I iterated on a few versions of thi
| |
| 712 | |
| 713 // If we have a log entry match, copy the saved matches from the | |
| 714 // scratch buffer into the return list, as well as the matching entry, | |
| 715 // if appropriate, and reset the scratch buffer. Continue processing | |
| 716 // from the next key entry. | |
| 717 if (keyEntry == logEntry) { | |
| 718 if (scratch != null) { | |
| 719 int numToCopy = scratch.length; | |
| 720 if (distance > 0 && distance < numToCopy) { | |
| 721 numToCopy = distance; | |
| 722 } | |
| 723 for (var i = scratch.length - numToCopy; i < scratch.length; i++) { | |
| 724 rtn.logs.add(scratch[i]); | |
| 725 } | |
| 726 scratch.clear(); | |
| 727 } else { | |
| 728 remainingCount = distance > 0 ? distance : logs.length; | |
| 729 } | |
| 730 if (includeKeys) { | |
| 731 rtn.logs.add(keyEntry); | |
| 732 } | |
| 733 if (keyIterator.hasNext()) { | |
| 734 keyEntry = keyIterator.next(); | |
| 735 } else if (isPreceding) { // We're done. | |
| 736 break; | |
| 737 } | |
| 738 } else if (remainingCount > 0 && | |
| 739 mockNameFilter.matches(logEntry.mockName) && | |
| 740 logFilter(logEntry)) { | |
| 741 if (scratch != null) { | |
| 742 scratch.add(logEntry); | |
| 743 } else { | |
| 744 rtn.logs.add(logEntry); | |
| 745 --remainingCount; | |
| 746 } | |
| 747 } | |
| 748 } | |
| 749 return rtn; | |
| 750 } | |
| 751 | |
| 752 /** | |
| 753 * Iterate through the LogEntryList looking for matches to the entries | |
| 754 * in [keys]; for each match found the closest [distance] prior log entries | |
| 755 * that match [mocknameFilter] and [logFilter] will be included in the result. | |
| 756 * If [includeKeys] is true then the entries in [keys] that resulted in | |
| 757 * entries in the output list are themselves included in the output list. If | |
| 758 * [distance] is zero then all matches are included. | |
| 759 */ | |
| 760 LogEntryList preceding(LogEntryList keys, | |
|
Siggi Cherem (dart-lang)
2012/07/19 18:05:00
I find these methods quite strange. Maybe we need
gram
2012/07/19 18:52:19
I respectfully disagree, other than providing more
| |
| 761 [mockNameFilter = null, | |
| 762 logFilter = null, | |
| 763 int distance = 1, | |
| 764 bool includeKeys = false]) => | |
| 765 _neighboring(true, keys, mockNameFilter, logFilter, | |
| 766 distance, includeKeys); | |
| 767 | |
| 768 /** | |
| 769 * Iterate through the LogEntryList looking for matches to the entries | |
| 770 * in [keys]; for each match found the closest [distance] subsequent log | |
| 771 * entries that match [mocknameFilter] and [logFilter] will be included in | |
| 772 * the result. If [includeKeys] is true then the entries in [keys] that | |
| 773 * resulted in entries in the output list are themselves included in the | |
| 774 * output list. If [distance] is zero then all matches are included. | |
| 775 */ | |
| 776 LogEntryList following(LogEntryList keys, | |
| 777 [mockNameFilter = null, | |
| 778 logFilter = null, | |
| 779 int distance = 1, | |
| 780 bool includeKeys = false]) => | |
| 781 _neighboring(false, keys, mockNameFilter, logFilter, | |
| 782 distance, includeKeys); | |
| 410 } | 783 } |
| 411 | 784 |
| 412 /** | 785 /** |
| 413 * [_TimesMatcher]s are used to make assertions about the number of | 786 * [_TimesMatcher]s are used to make assertions about the number of |
| 414 * times a method was called. | 787 * times a method was called. |
| 415 */ | 788 */ |
| 416 class _TimesMatcher extends BaseMatcher { | 789 class _TimesMatcher extends BaseMatcher { |
| 417 final int min, max; | 790 final int min, max; |
| 418 | 791 |
| 419 const _TimesMatcher(this.min, [this.max = -1]); | 792 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); | 836 final Matcher happenedAtLeastOnce = const _TimesMatcher(1); |
| 464 | 837 |
| 465 /** [happenedAtMostOnce] matches zero or one call. */ | 838 /** [happenedAtMostOnce] matches zero or one call. */ |
| 466 final Matcher happenedAtMostOnce = const _TimesMatcher(0, 1); | 839 final Matcher happenedAtMostOnce = const _TimesMatcher(0, 1); |
| 467 | 840 |
| 468 /** | 841 /** |
| 469 * [_ResultMatcher]s are used to make assertions about the results | 842 * [_ResultMatcher]s are used to make assertions about the results |
| 470 * of method calls. These can be used as optional parameters to [getLogs]. | 843 * of method calls. These can be used as optional parameters to [getLogs]. |
| 471 */ | 844 */ |
| 472 class _ResultMatcher extends BaseMatcher { | 845 class _ResultMatcher extends BaseMatcher { |
| 473 final _Action action; | 846 final Action action; |
| 474 final Matcher value; | 847 final Matcher value; |
| 475 | 848 |
| 476 const _ResultMatcher(this.action, this.value); | 849 const _ResultMatcher(this.action, this.value); |
| 477 | 850 |
| 478 bool matches(item) { | 851 bool matches(item) { |
| 479 if (item is! LogEntry) { | 852 if (item is! LogEntry) { |
| 480 return false; | 853 return false; |
| 481 } | 854 } |
| 482 // normalize the action; _PROXY is like _RETURN. | 855 // normalize the action; _PROXY is like _RETURN. |
| 483 _Action eaction = item.action; | 856 Action eaction = item.action; |
| 484 if (eaction == _Action.PROXY) { | 857 if (eaction == Action.PROXY) { |
| 485 eaction = _Action.RETURN; | 858 eaction = Action.RETURN; |
| 486 } | 859 } |
| 487 return (eaction == action && value.matches(item.value)); | 860 return (eaction == action && value.matches(item.value)); |
| 488 } | 861 } |
| 489 | 862 |
| 490 Description describe(Description description) { | 863 Description describe(Description description) { |
| 491 description.add(' to '); | 864 description.add(' to '); |
| 492 if (action == _Action.RETURN || action == _Action.PROXY) | 865 if (action == Action.RETURN || action == Action.PROXY) |
| 493 description.add('return '); | 866 description.add('return '); |
| 494 else | 867 else |
| 495 description.add('throw '); | 868 description.add('throw '); |
| 496 return description.addDescriptionOf(value); | 869 return description.addDescriptionOf(value); |
| 497 } | 870 } |
| 498 | 871 |
| 499 Description describeMismatch(item, Description mismatchDescription) { | 872 Description describeMismatch(item, Description mismatchDescription) { |
| 500 if (item.action == _Action.RETURN || item.action == _Action.PROXY) { | 873 if (item.action == Action.RETURN || item.action == Action.PROXY) { |
| 501 mismatchDescription.add('returned '); | 874 mismatchDescription.add('returned '); |
| 502 } else { | 875 } else { |
| 503 mismatchDescription.add('threw '); | 876 mismatchDescription.add('threw '); |
| 504 } | 877 } |
| 505 mismatchDescription.add(item.value); | 878 mismatchDescription.add(item.value); |
| 506 return mismatchDescription; | 879 return mismatchDescription; |
| 507 } | 880 } |
| 508 } | 881 } |
| 509 | 882 |
| 510 /** | 883 /** |
| 511 *[returning] matches log entries where the call to a method returned | 884 *[returning] matches log entries where the call to a method returned |
| 512 * a value that matched [value]. | 885 * a value that matched [value]. |
| 513 */ | 886 */ |
| 514 Matcher returning(value) => | 887 Matcher returning(value) => |
| 515 new _ResultMatcher(_Action.RETURN, wrapMatcher(value)); | 888 new _ResultMatcher(Action.RETURN, wrapMatcher(value)); |
| 516 | 889 |
| 517 /** | 890 /** |
| 518 *[throwing] matches log entrues where the call to a method threw | 891 *[throwing] matches log entrues where the call to a method threw |
| 519 * a value that matched [value]. | 892 * a value that matched [value]. |
| 520 */ | 893 */ |
| 521 Matcher throwing(value) => | 894 Matcher throwing(value) => |
| 522 new _ResultMatcher(_Action.THROW, wrapMatcher(value)); | 895 new _ResultMatcher(Action.THROW, wrapMatcher(value)); |
| 523 | 896 |
| 524 /** Special values for use with [_ResultSetMatcher] [frequency]. */ | 897 /** Special values for use with [_ResultSetMatcher] [frequency]. */ |
| 525 class _Frequency { | 898 class _Frequency { |
| 526 /** Every call/throw must match */ | 899 /** Every call/throw must match */ |
| 527 static final ALL = const _Frequency._('ALL'); | 900 static final ALL = const _Frequency._('ALL'); |
| 528 | 901 |
| 529 /** At least one call/throw must match. */ | 902 /** At least one call/throw must match. */ |
| 530 static final SOME = const _Frequency._('SOME'); | 903 static final SOME = const _Frequency._('SOME'); |
| 531 | 904 |
| 532 /** No calls/throws should match. */ | 905 /** No calls/throws should match. */ |
| 533 static final NONE = const _Frequency._('NONE'); | 906 static final NONE = const _Frequency._('NONE'); |
| 534 | 907 |
| 535 const _Frequency._(this.name); | 908 const _Frequency._(this.name); |
| 536 | 909 |
| 537 final String name; | 910 final String name; |
| 538 } | 911 } |
| 539 | 912 |
| 540 /** | 913 /** |
| 541 * [_ResultSetMatcher]s are used to make assertions about the results | 914 * [_ResultSetMatcher]s are used to make assertions about the results |
| 542 * of method calls. When filtering an execution log by calling | 915 * of method calls. When filtering an execution log by calling |
| 543 * [getLogs], a [LogEntrySet] of matching call logs is returned; | 916 * [getLogs], a [LogEntrySet] of matching call logs is returned; |
| 544 * [_ResultSetMatcher]s can then assert various things about this | 917 * [_ResultSetMatcher]s can then assert various things about this |
| 545 * (sub)set of logs. | 918 * (sub)set of logs. |
| 546 * | 919 * |
| 547 * We could make this class use _ResultMatcher but it doesn't buy that | 920 * 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. | 921 * match and adds some perf hit, so there is some duplication here. |
| 549 */ | 922 */ |
| 550 class _ResultSetMatcher extends BaseMatcher { | 923 class _ResultSetMatcher extends BaseMatcher { |
| 551 final _Action action; | 924 final Action action; |
| 552 final Matcher value; | 925 final Matcher value; |
| 553 final _Frequency frequency; // ALL, SOME, or NONE. | 926 final _Frequency frequency; // ALL, SOME, or NONE. |
| 554 | 927 |
| 555 const _ResultSetMatcher(this.action, this.value, this.frequency); | 928 const _ResultSetMatcher(this.action, this.value, this.frequency); |
| 556 | 929 |
| 557 bool matches(log) { | 930 bool matches(log) { |
| 558 for (LogEntry entry in log) { | 931 for (LogEntry entry in log) { |
| 559 // normalize the action; _PROXY is like _RETURN. | 932 // normalize the action; PROXY is like RETURN. |
| 560 _Action eaction = entry.action; | 933 Action eaction = entry.action; |
| 561 if (eaction == _Action.PROXY) { | 934 if (eaction == Action.PROXY) { |
| 562 eaction = _Action.RETURN; | 935 eaction = Action.RETURN; |
| 563 } | 936 } |
| 564 if (eaction == action && value.matches(entry.value)) { | 937 if (eaction == action && value.matches(entry.value)) { |
| 565 if (frequency == _Frequency.NONE) { | 938 if (frequency == _Frequency.NONE) { |
| 566 return false; | 939 return false; |
| 567 } else if (frequency == _Frequency.SOME) { | 940 } else if (frequency == _Frequency.SOME) { |
| 568 return true; | 941 return true; |
| 569 } | 942 } |
| 570 } else { | 943 } else { |
| 571 // Mismatch. | 944 // Mismatch. |
| 572 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail. | 945 if (frequency == _Frequency.ALL) { // We need just one mismatch to fail. |
| 573 return false; | 946 return false; |
| 574 } | 947 } |
| 575 } | 948 } |
| 576 } | 949 } |
| 577 // If we get here, then if count is _ALL we got all matches and | 950 // 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 | 951 // this is success; otherwise we got all mismatched which is |
| 579 // success for count == _NONE and failure for count == _SOME. | 952 // success for count == _NONE and failure for count == _SOME. |
| 580 return (frequency != _Frequency.SOME); | 953 return (frequency != _Frequency.SOME); |
| 581 } | 954 } |
| 582 | 955 |
| 583 Description describe(Description description) { | 956 Description describe(Description description) { |
| 584 description.add(' to '); | 957 description.add(' to '); |
| 585 description.add(frequency == _Frequency.ALL ? 'alway ' : | 958 description.add(frequency == _Frequency.ALL ? 'alway ' : |
| 586 (frequency == _Frequency.NONE ? 'never ' : 'sometimes ')); | 959 (frequency == _Frequency.NONE ? 'never ' : 'sometimes ')); |
| 587 if (action == _Action.RETURN || action == __Action.PROXY) | 960 if (action == Action.RETURN || action == Action.PROXY) |
| 588 description.add('return '); | 961 description.add('return '); |
| 589 else | 962 else |
| 590 description.add('throw '); | 963 description.add('throw '); |
| 591 return description.addDescriptionOf(value); | 964 return description.addDescriptionOf(value); |
| 592 } | 965 } |
| 593 | 966 |
| 594 Description describeMismatch(log, Description mismatchDescription) { | 967 Description describeMismatch(log, Description mismatchDescription) { |
| 595 if (frequency != _Frequency.SOME) { | 968 if (frequency != _Frequency.SOME) { |
| 596 for (LogEntry entry in log) { | 969 for (LogEntry entry in log) { |
| 597 if (entry.action != action || !value.matches(entry.value)) { | 970 if (entry.action != action || !value.matches(entry.value)) { |
| 598 if (entry.action == _Action.RETURN || entry.action == _Action.PROXY) | 971 if (entry.action == Action.RETURN || entry.action == Action.PROXY) |
| 599 mismatchDescription.add('returned '); | 972 mismatchDescription.add('returned '); |
| 600 else | 973 else |
| 601 mismatchDescription.add('threw '); | 974 mismatchDescription.add('threw '); |
| 602 mismatchDescription.add(entry.value); | 975 mismatchDescription.add(entry.value); |
| 603 mismatchDescription.add(' at least once'); | 976 mismatchDescription.add(' at least once'); |
| 604 break; | 977 break; |
| 605 } | 978 } |
| 606 } | 979 } |
| 607 } else { | 980 } else { |
| 608 mismatchDescription.add('never did'); | 981 mismatchDescription.add('never did'); |
| 609 } | 982 } |
| 610 return mismatchDescription; | 983 return mismatchDescription; |
| 611 } | 984 } |
| 612 } | 985 } |
| 613 | 986 |
| 614 /** | 987 /** |
| 615 *[alwaysReturned] asserts that all matching calls to a method returned | 988 *[alwaysReturned] asserts that all matching calls to a method returned |
| 616 * a value that matched [value]. | 989 * a value that matched [value]. |
| 617 */ | 990 */ |
| 618 Matcher alwaysReturned(value) => | 991 Matcher alwaysReturned(value) => |
| 619 new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.ALL); | 992 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.ALL); |
| 620 | 993 |
| 621 /** | 994 /** |
| 622 *[sometimeReturned] asserts that at least one matching call to a method | 995 *[sometimeReturned] asserts that at least one matching call to a method |
| 623 * returned a value that matched [value]. | 996 * returned a value that matched [value]. |
| 624 */ | 997 */ |
| 625 Matcher sometimeReturned(value) => | 998 Matcher sometimeReturned(value) => |
| 626 new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.SOME); | 999 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.SOME); |
| 627 | 1000 |
| 628 /** | 1001 /** |
| 629 *[neverReturned] asserts that no matching calls to a method returned | 1002 *[neverReturned] asserts that no matching calls to a method returned |
| 630 * a value that matched [value]. | 1003 * a value that matched [value]. |
| 631 */ | 1004 */ |
| 632 Matcher neverReturned(value) => | 1005 Matcher neverReturned(value) => |
| 633 new _ResultSetMatcher(_Action.RETURN, wrapMatcher(value), _Frequency.NONE); | 1006 new _ResultSetMatcher(Action.RETURN, wrapMatcher(value), _Frequency.NONE); |
| 634 | 1007 |
| 635 /** | 1008 /** |
| 636 *[alwaysThrew] asserts that all matching calls to a method threw | 1009 *[alwaysThrew] asserts that all matching calls to a method threw |
| 637 * a value that matched [value]. | 1010 * a value that matched [value]. |
| 638 */ | 1011 */ |
| 639 Matcher alwaysThrew(value) => | 1012 Matcher alwaysThrew(value) => |
| 640 new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.ALL); | 1013 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.ALL); |
| 641 | 1014 |
| 642 /** | 1015 /** |
| 643 *[sometimeThrew] asserts that at least one matching call to a method threw | 1016 *[sometimeThrew] asserts that at least one matching call to a method threw |
| 644 * a value that matched [value]. | 1017 * a value that matched [value]. |
| 645 */ | 1018 */ |
| 646 Matcher sometimeThrew(value) => | 1019 Matcher sometimeThrew(value) => |
| 647 new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.SOME); | 1020 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.SOME); |
| 648 | 1021 |
| 649 /** | 1022 /** |
| 650 *[neverThrew] asserts that no matching call to a method threw | 1023 *[neverThrew] asserts that no matching call to a method threw |
| 651 * a value that matched [value]. | 1024 * a value that matched [value]. |
| 652 */ | 1025 */ |
| 653 Matcher neverThrew(value) => | 1026 Matcher neverThrew(value) => |
| 654 new _ResultSetMatcher(_Action.THROW, wrapMatcher(value), _Frequency.NONE); | 1027 new _ResultSetMatcher(Action.THROW, wrapMatcher(value), _Frequency.NONE); |
| 655 | 1028 |
| 656 /** The shared log used for named mocks. */ | 1029 /** The shared log used for named mocks. */ |
| 657 LogEntryList sharedLog = null; | 1030 LogEntryList sharedLog = null; |
| 658 | 1031 |
| 659 /** | 1032 /** |
| 660 * [Mock] is the base class for all mocked objects, with | 1033 * [Mock] is the base class for all mocked objects, with |
| 661 * support for basic mocking. | 1034 * support for basic mocking. |
| 662 * | 1035 * |
| 663 * To create a mock objects for some class T, create a new class using: | 1036 * To create a mock objects for some class T, create a new class using: |
| 664 * | 1037 * |
| (...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. */ | 1116 /** The [log] of calls made. Only used if [name] is null. */ |
| 744 LogEntryList log; | 1117 LogEntryList log; |
| 745 | 1118 |
| 746 /** How to handle unknown method calls - swallow or throw. */ | 1119 /** How to handle unknown method calls - swallow or throw. */ |
| 747 final bool _throwIfNoBehavior; | 1120 final bool _throwIfNoBehavior; |
| 748 | 1121 |
| 749 /** Whether to create an audit log or not. */ | 1122 /** Whether to create an audit log or not. */ |
| 750 bool _logging; | 1123 bool _logging; |
| 751 | 1124 |
| 752 bool get logging() => _logging; | 1125 bool get logging() => _logging; |
| 753 bool set logging(bool value) { | 1126 set logging(bool value) { |
| 754 if (value && log == null) { | 1127 if (value && log == null) { |
| 755 log = new LogEntryList(); | 1128 log = new LogEntryList(); |
| 756 } | 1129 } |
| 757 _logging = value; | 1130 _logging = value; |
| 758 } | 1131 } |
| 759 | 1132 |
| 760 /** | 1133 /** |
| 761 * Default constructor. Unknown method calls are allowed and logged, | 1134 * Default constructor. Unknown method calls are allowed and logged, |
| 762 * the mock has no name, and has its own log. | 1135 * the mock has no name, and has its own log. |
| 763 */ | 1136 */ |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 830 // Get the first response. | 1203 // Get the first response. |
| 831 Responder response = actions[0]; | 1204 Responder response = actions[0]; |
| 832 // If it is exhausted, remove it from the list. | 1205 // If it is exhausted, remove it from the list. |
| 833 // Note that for endlessly repeating values, we started the count at | 1206 // Note that for endlessly repeating values, we started the count at |
| 834 // 0, so we get a potentially useful value here, which is the | 1207 // 0, so we get a potentially useful value here, which is the |
| 835 // (negation of) the number of times we returned the value. | 1208 // (negation of) the number of times we returned the value. |
| 836 if (--response.count == 0) { | 1209 if (--response.count == 0) { |
| 837 actions.removeRange(0, 1); | 1210 actions.removeRange(0, 1); |
| 838 } | 1211 } |
| 839 // Do the response. | 1212 // Do the response. |
| 840 _Action action = response.action; | 1213 Action action = response.action; |
| 841 var value = response.value; | 1214 var value = response.value; |
| 842 if (action == _Action.RETURN) { | 1215 if (action == Action.RETURN) { |
| 843 if (_logging) { | 1216 if (_logging) { |
| 844 log.add(new LogEntry(name, method, args, action, value)); | 1217 log.add(new LogEntry(name, method, args, action, value)); |
| 845 } | 1218 } |
| 846 return value; | 1219 return value; |
| 847 } else if (action == _Action.THROW) { | 1220 } else if (action == Action.THROW) { |
| 848 if (_logging) { | 1221 if (_logging) { |
| 849 log.add(new LogEntry(name, method, args, action, value)); | 1222 log.add(new LogEntry(name, method, args, action, value)); |
| 850 } | 1223 } |
| 851 throw value; | 1224 throw value; |
| 852 } else if (action == _Action.PROXY) { | 1225 } else if (action == Action.PROXY) { |
| 853 var rtn; | 1226 var rtn; |
| 854 switch (args.length) { | 1227 switch (args.length) { |
| 855 case 0: | 1228 case 0: |
| 856 rtn = value(); | 1229 rtn = value(); |
| 857 break; | 1230 break; |
| 858 case 1: | 1231 case 1: |
| 859 rtn = value(args[0]); | 1232 rtn = value(args[0]); |
| 860 break; | 1233 break; |
| 861 case 2: | 1234 case 2: |
| 862 rtn = value(args[0], args[1]); | 1235 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. | 1279 // actions are exhausted. This is considered an error. |
| 907 throw new Exception('No more actions for method ' | 1280 throw new Exception('No more actions for method ' |
| 908 '${_qualifiedName(name, method)}.'); | 1281 '${_qualifiedName(name, method)}.'); |
| 909 } else if (_throwIfNoBehavior) { | 1282 } else if (_throwIfNoBehavior) { |
| 910 throw new Exception('No behavior specified for method ' | 1283 throw new Exception('No behavior specified for method ' |
| 911 '${_qualifiedName(name, method)}.'); | 1284 '${_qualifiedName(name, method)}.'); |
| 912 } | 1285 } |
| 913 // Otherwise user hasn't specified behavior for this method; we don't throw | 1286 // Otherwise user hasn't specified behavior for this method; we don't throw |
| 914 // so we can underspecify. | 1287 // so we can underspecify. |
| 915 if (_logging) { | 1288 if (_logging) { |
| 916 log.add(new LogEntry(name, method, args, _Action.IGNORE)); | 1289 log.add(new LogEntry(name, method, args, Action.IGNORE)); |
| 917 } | 1290 } |
| 918 } | 1291 } |
| 919 | 1292 |
| 920 /** [verifyZeroInteractions] returns true if no calls were made */ | 1293 /** [verifyZeroInteractions] returns true if no calls were made */ |
| 921 bool verifyZeroInteractions() { | 1294 bool verifyZeroInteractions() { |
| 922 if (log == null) { | 1295 if (log == null) { |
| 923 // This means we created the mock with logging off and have never turned | 1296 // 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. | 1297 // it on, so it doesn't make sense to verify behavior on such a mock. |
| 925 throw new | 1298 throw new |
| 926 Exception("Can't verify behavior when logging was never enabled."); | 1299 Exception("Can't verify behavior when logging was never enabled."); |
| 927 } | 1300 } |
| 928 return log.logs.length == 0; | 1301 return log.logs.length == 0; |
| 929 } | 1302 } |
| 930 | 1303 |
| 931 /** | 1304 /** |
| 932 * [getLogs] extracts all calls from the call log that match the | 1305 * [getLogs] extracts all calls from the call log that match the |
| 933 * [logFilter] [CallMatcher], and returns the matching list of | 1306 * [logFilter], and returns the matching list of [LogEntry]s. If |
| 934 * [LogEntry]s. If [destructive] is false (the default) the matching | 1307 * [destructive] is false (the default) the matching calls are left |
| 935 * calls are left in the log, else they are removed. Removal allows | 1308 * 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 | 1309 * set of interactions and then verify that there are no other |
| 937 * no other interactions left. [actionMatcher] can be used to further | 1310 * interactions left. [actionMatcher] can be used to further |
| 938 * restrict the returned logs based on the action the mock performed. | 1311 * restrict the returned logs based on the action the mock performed. |
| 1312 * [logFilter] can be a [CallMatcher] or a predicate function that | |
| 1313 * takes a [LogEntry] and returns a bool. | |
| 939 * | 1314 * |
| 940 * Typical usage: | 1315 * Typical usage: |
| 941 * | 1316 * |
| 942 * getLogs(callsTo(...)).verify(...); | 1317 * getLogs(callsTo(...)).verify(...); |
| 943 */ | 1318 */ |
| 944 LogEntryList getLogs([CallMatcher logFilter, | 1319 LogEntryList getLogs([CallMatcher logFilter, |
| 945 Matcher actionMatcher, | 1320 Matcher actionMatcher, |
| 946 bool destructive = false]) { | 1321 bool destructive = false]) { |
| 947 if (log == null) { | 1322 if (log == null) { |
| 948 // This means we created the mock with logging off and have never turned | 1323 // 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. | 1324 // it on, so it doesn't make sense to get logs from such a mock. |
| 950 throw new | 1325 throw new |
| 951 Exception("Can't retrieve logs when logging was never enabled."); | 1326 Exception("Can't retrieve logs when logging was never enabled."); |
| 952 } else { | 1327 } else { |
| 953 return log.getMatches(name, logFilter, actionMatcher, destructive); | 1328 return log.getMatches(name, logFilter, actionMatcher, destructive); |
| 954 } | 1329 } |
| 955 } | 1330 } |
| 1331 | |
| 1332 /** | |
| 1333 * Useful shorthand method that creates a [CallMatcher] from its arguments | |
| 1334 * and then calls [getLogs]. | |
| 1335 */ | |
| 1336 LogEntryList calls(method, | |
| 1337 [arg0 = _noArg, | |
| 1338 arg1 = _noArg, | |
| 1339 arg2 = _noArg, | |
| 1340 arg3 = _noArg, | |
| 1341 arg4 = _noArg, | |
| 1342 arg5 = _noArg, | |
| 1343 arg6 = _noArg, | |
| 1344 arg7 = _noArg, | |
| 1345 arg8 = _noArg, | |
| 1346 arg9 = _noArg]) => | |
| 1347 getLogs(callsTo(method, arg0, arg1, arg2, arg3, arg4, | |
| 1348 arg5, arg6, arg7, arg8, arg9)); | |
| 956 } | 1349 } |
| OLD | NEW |