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

Side by Side Diff: pkg/mock/lib/src/log_entry_list.dart

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

Powered by Google App Engine
This is Rietveld 408576698