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

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

Issue 10804014: Temporal assertion support. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /** 5 /**
6 * The error formatter for mocking is a bit different from the default one 6 * The error formatter for mocking is a bit different from the default one
7 * for unit testing; instead of the third argument being a 'reason' 7 * for unit testing; instead of the third argument being a 'reason'
8 * it is instead a [signature] describing the method signature filter 8 * it is instead a [signature] describing the method signature filter
9 * that was used to select the logs that were verified. 9 * that was used to select the logs that were verified.
10 */ 10 */
(...skipping 23 matching lines...) Expand all
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
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
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
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
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
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
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 }
OLDNEW
« no previous file with comments | « no previous file | tests/lib/unittest/unittest_test.dart » ('j') | tests/lib/unittest/unittest_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698