LCOV - code coverage report
Current view: top level - test-0.12.24+8/lib/src/frontend - stream_matchers.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 0 128 0.0 %
Date: 2017-10-10 20:17:03 Functions: 0 0 -

          Line data    Source code
       1             : // Copyright (c) 2017, 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             : import 'dart:async';
       6             : 
       7             : import 'package:async/async.dart';
       8             : import 'package:matcher/matcher.dart';
       9             : 
      10             : import '../utils.dart';
      11             : import 'async_matcher.dart';
      12             : import 'stream_matcher.dart';
      13             : import 'throws_matcher.dart';
      14             : 
      15             : /// Returns a [StreamMatcher] that asserts that the stream emits a "done" event.
      16             : final emitsDone = new StreamMatcher(
      17             :     (queue) async => (await queue.hasNext) ? "" : null, "be done");
      18             : 
      19             : /// Returns a [StreamMatcher] for [matcher].
      20             : ///
      21             : /// If [matcher] is already a [StreamMatcher], it's returned as-is. If it's any
      22             : /// other [Matcher], this matches a single event that matches that matcher. If
      23             : /// it's any other Object, this matches a single event that's equal to that
      24             : /// object.
      25             : ///
      26             : /// This functions like [wrapMatcher] for [StreamMatcher]s: it can convert any
      27             : /// matcher-like value into a proper [StreamMatcher].
      28             : StreamMatcher emits(matcher) {
      29           0 :   if (matcher is StreamMatcher) return matcher;
      30           0 :   var wrapped = wrapMatcher(matcher);
      31             : 
      32           0 :   var matcherDescription = wrapped.describe(new StringDescription());
      33             : 
      34           0 :   return new StreamMatcher((queue) async {
      35           0 :     if (!await queue.hasNext) return "";
      36             : 
      37           0 :     var matchState = {};
      38           0 :     var actual = await queue.next;
      39           0 :     if (wrapped.matches(actual, matchState)) return null;
      40             : 
      41           0 :     var mismatchDescription = new StringDescription();
      42           0 :     wrapped.describeMismatch(actual, mismatchDescription, matchState, false);
      43             : 
      44           0 :     if (mismatchDescription.length == 0) return "";
      45           0 :     return "emitted an event that $mismatchDescription";
      46           0 :   },
      47             :       // TODO(nweiz): add "should" once matcher#42 is fixed.
      48           0 :       "emit an event that $matcherDescription");
      49             : }
      50             : 
      51             : /// Returns a [StreamMatcher] that matches a single error event that matches
      52             : /// [matcher].
      53             : StreamMatcher emitsError(matcher) {
      54           0 :   var wrapped = wrapMatcher(matcher);
      55           0 :   var matcherDescription = wrapped.describe(new StringDescription());
      56           0 :   var throwsMatcher = throwsA(wrapped) as AsyncMatcher;
      57             : 
      58           0 :   return new StreamMatcher(
      59           0 :       (queue) => throwsMatcher.matchAsync(queue.next),
      60             :       // TODO(nweiz): add "should" once matcher#42 is fixed.
      61           0 :       "emit an error that $matcherDescription");
      62             : }
      63             : 
      64             : /// Returns a [StreamMatcher] that allows (but doesn't require) [matcher] to
      65             : /// match the stream.
      66             : ///
      67             : /// This matcher always succeeds; if [matcher] doesn't match, this just consumes
      68             : /// no events.
      69             : StreamMatcher mayEmit(matcher) {
      70           0 :   var streamMatcher = emits(matcher);
      71           0 :   return new StreamMatcher((queue) async {
      72           0 :     await queue.withTransaction(
      73           0 :         (copy) async => (await streamMatcher.matchQueue(copy)) == null);
      74             :     return null;
      75           0 :   }, "maybe ${streamMatcher.description}");
      76             : }
      77             : 
      78             : /// Returns a [StreamMatcher] that matches the stream if at least one of
      79             : /// [matchers] matches.
      80             : ///
      81             : /// If multiple matchers match the stream, this chooses the matcher that
      82             : /// consumes as many events as possible.
      83             : ///
      84             : /// If any matchers match the stream, no errors from other matchers are thrown.
      85             : /// If no matchers match and multiple matchers threw errors, the first error is
      86             : /// re-thrown.
      87             : StreamMatcher emitsAnyOf(Iterable matchers) {
      88           0 :   var streamMatchers = matchers.map(emits).toList();
      89           0 :   if (streamMatchers.isEmpty) {
      90           0 :     throw new ArgumentError("matcher may not be empty");
      91             :   }
      92             : 
      93           0 :   if (streamMatchers.length == 1) return streamMatchers.first;
      94           0 :   var description = "do one of the following:\n" +
      95           0 :       bullet(streamMatchers.map((matcher) => matcher.description));
      96             : 
      97           0 :   return new StreamMatcher((queue) async {
      98           0 :     var transaction = queue.startTransaction();
      99             : 
     100             :     // Allocate the failures list ahead of time so that its order matches the
     101             :     // order of [matchers], and thus the order the matchers will be listed in
     102             :     // the description.
     103           0 :     var failures = new List<String>(matchers.length);
     104             : 
     105             :     // The first error thrown. If no matchers match and this exists, we rethrow
     106             :     // it.
     107             :     Object firstError;
     108             :     StackTrace firstStackTrace;
     109             : 
     110           0 :     var futures = <Future>[];
     111             :     StreamQueue consumedMost;
     112           0 :     for (var i = 0; i < matchers.length; i++) {
     113           0 :       futures.add(() async {
     114           0 :         var copy = transaction.newQueue();
     115             : 
     116             :         String result;
     117             :         try {
     118           0 :           result = await streamMatchers[i].matchQueue(copy);
     119             :         } catch (error, stackTrace) {
     120             :           if (firstError == null) {
     121             :             firstError = error;
     122             :             firstStackTrace = stackTrace;
     123             :           }
     124             :           return;
     125             :         }
     126             : 
     127             :         if (result != null) {
     128           0 :           failures[i] = result;
     129             :         } else if (consumedMost == null ||
     130           0 :             consumedMost.eventsDispatched < copy.eventsDispatched) {
     131             :           consumedMost = copy;
     132             :         }
     133           0 :       }());
     134             :     }
     135             : 
     136           0 :     await Future.wait(futures);
     137             : 
     138             :     if (consumedMost == null) {
     139           0 :       transaction.reject();
     140             :       if (firstError != null) {
     141           0 :         await new Future.error(firstError, firstStackTrace);
     142             :       }
     143             : 
     144           0 :       var failureMessages = <String>[];
     145           0 :       for (var i = 0; i < matchers.length; i++) {
     146           0 :         var message = "failed to ${streamMatchers[i].description}";
     147           0 :         if (failures[i].isNotEmpty) {
     148           0 :           message += message.contains("\n") ? "\n" : " ";
     149           0 :           message += "because it ${failures[i]}";
     150             :         }
     151             : 
     152           0 :         failureMessages.add(message);
     153             :       }
     154             : 
     155           0 :       return "failed all options:\n${bullet(failureMessages)}";
     156             :     } else {
     157           0 :       transaction.commit(consumedMost);
     158             :       return null;
     159             :     }
     160           0 :   }, description);
     161             : }
     162             : 
     163             : /// Returns a [StreamMatcher] that matches the stream if each matcher in
     164             : /// [matchers] matches, one after another.
     165             : ///
     166             : /// If any matcher fails to match, this fails and consumes no events.
     167             : StreamMatcher emitsInOrder(Iterable matchers) {
     168           0 :   var streamMatchers = matchers.map(emits).toList();
     169           0 :   if (streamMatchers.length == 1) return streamMatchers.first;
     170             : 
     171           0 :   var description = "do the following in order:\n" +
     172           0 :       bullet(streamMatchers.map((matcher) => matcher.description));
     173             : 
     174           0 :   return new StreamMatcher((queue) async {
     175           0 :     for (var i = 0; i < streamMatchers.length; i++) {
     176           0 :       var matcher = streamMatchers[i];
     177           0 :       var result = await matcher.matchQueue(queue);
     178             :       if (result == null) continue;
     179             : 
     180           0 :       var newResult = "didn't ${matcher.description}";
     181           0 :       if (result.isNotEmpty) {
     182           0 :         newResult += newResult.contains("\n") ? "\n" : " ";
     183           0 :         newResult += "because it $result";
     184             :       }
     185             :       return newResult;
     186             :     }
     187           0 :   }, description);
     188             : }
     189             : 
     190             : /// Returns a [StreamMatcher] that matches any number of events followed by
     191             : /// events that match [matcher].
     192             : ///
     193             : /// This consumes all events matched by [matcher], as well as all events before.
     194             : /// If the stream emits a done event without matching [matcher], this fails and
     195             : /// consumes no events.
     196             : StreamMatcher emitsThrough(matcher) {
     197           0 :   var streamMatcher = emits(matcher);
     198           0 :   return new StreamMatcher((queue) async {
     199           0 :     var failures = <String>[];
     200             : 
     201           0 :     tryHere() => queue.withTransaction((copy) async {
     202           0 :           var result = await streamMatcher.matchQueue(copy);
     203             :           if (result == null) return true;
     204           0 :           failures.add(result);
     205             :           return false;
     206           0 :         });
     207             : 
     208           0 :     while (await queue.hasNext) {
     209           0 :       if (await tryHere()) return null;
     210           0 :       await queue.next;
     211             :     }
     212             : 
     213             :     // Try after the queue is done in case the matcher can match an empty
     214             :     // stream.
     215           0 :     if (await tryHere()) return null;
     216             : 
     217           0 :     var result = "never did ${streamMatcher.description}";
     218             : 
     219             :     var failureMessages =
     220           0 :         bullet(failures.where((failure) => failure.isNotEmpty));
     221           0 :     if (failureMessages.isNotEmpty) {
     222           0 :       result += result.contains("\n") ? "\n" : " ";
     223           0 :       result += "because it:\n$failureMessages";
     224             :     }
     225             : 
     226             :     return result;
     227           0 :   }, "eventually ${streamMatcher.description}");
     228             : }
     229             : 
     230             : /// Returns a [StreamMatcher] that matches any number of events that match
     231             : /// [matcher].
     232             : ///
     233             : /// This consumes events until [matcher] no longer matches. It always succeeds;
     234             : /// if [matcher] doesn't match, this just consumes no events. It never rethrows
     235             : /// errors.
     236             : StreamMatcher mayEmitMultiple(matcher) {
     237           0 :   var streamMatcher = emits(matcher);
     238             : 
     239           0 :   var description = streamMatcher.description;
     240           0 :   description += description.contains("\n") ? "\n" : " ";
     241           0 :   description += "zero or more times";
     242             : 
     243           0 :   return new StreamMatcher((queue) async {
     244           0 :     while (await _tryMatch(queue, streamMatcher)) {
     245             :       // Do nothing; the matcher presumably already consumed events.
     246             :     }
     247             :     return null;
     248           0 :   }, description);
     249             : }
     250             : 
     251             : /// Returns a [StreamMatcher] that matches a stream that never matches
     252             : /// [matcher].
     253             : ///
     254             : /// This doesn't complete until the stream emits a done event. It never consumes
     255             : /// any events. It never re-throws errors.
     256             : StreamMatcher neverEmits(matcher) {
     257           0 :   var streamMatcher = emits(matcher);
     258           0 :   return new StreamMatcher((queue) async {
     259             :     var events = 0;
     260             :     var matched = false;
     261           0 :     await queue.withTransaction((copy) async {
     262           0 :       while (await copy.hasNext) {
     263           0 :         matched = await _tryMatch(copy, streamMatcher);
     264             :         if (matched) return false;
     265             : 
     266           0 :         events++;
     267             : 
     268             :         try {
     269           0 :           await copy.next;
     270             :         } catch (_) {
     271             :           // Ignore errors events.
     272             :         }
     273             :       }
     274             : 
     275           0 :       matched = await _tryMatch(copy, streamMatcher);
     276             :       return false;
     277           0 :     });
     278             : 
     279             :     if (!matched) return null;
     280           0 :     return "after $events ${pluralize('event', events)} did "
     281           0 :         "${streamMatcher.description}";
     282           0 :   }, "never ${streamMatcher.description}");
     283             : }
     284             : 
     285             : /// Returns whether [matcher] matches [queue] at its current position.
     286             : ///
     287             : /// This treats errors as failures to match.
     288             : Future<bool> _tryMatch(StreamQueue queue, StreamMatcher matcher) {
     289           0 :   return queue.withTransaction((copy) async {
     290             :     try {
     291           0 :       return (await matcher.matchQueue(copy)) == null;
     292             :     } catch (_) {
     293             :       return false;
     294             :     }
     295           0 :   });
     296             : }
     297             : 
     298             : /// Returns a [StreamMatcher] that matches the stream if each matcher in
     299             : /// [matchers] matches, in any order.
     300             : ///
     301             : /// If any matcher fails to match, this fails and consumes no events. If the
     302             : /// matchers match in multiple different possible orders, this chooses the order
     303             : /// that consumes as many events as possible.
     304             : ///
     305             : /// If any sequence of matchers matches the stream, no errors from other
     306             : /// sequences are thrown. If no sequences match and multiple sequences throw
     307             : /// errors, the first error is re-thrown.
     308             : ///
     309             : /// Note that checking every ordering of [matchers] is O(n!) in the worst case,
     310             : /// so this should only be called when there are very few [matchers].
     311             : StreamMatcher emitsInAnyOrder(Iterable matchers) {
     312           0 :   var streamMatchers = matchers.map(emits).toSet();
     313           0 :   if (streamMatchers.length == 1) return streamMatchers.first;
     314           0 :   var description = "do the following in any order:\n" +
     315           0 :       bullet(streamMatchers.map((matcher) => matcher.description));
     316             : 
     317           0 :   return new StreamMatcher(
     318           0 :       (queue) async => await _tryInAnyOrder(queue, streamMatchers) ? null : "",
     319             :       description);
     320             : }
     321             : 
     322             : /// Returns whether [queue] matches [matchers] in any order.
     323             : Future<bool> _tryInAnyOrder(
     324             :     StreamQueue queue, Set<StreamMatcher> matchers) async {
     325           0 :   if (matchers.length == 1)
     326           0 :     return await matchers.first.matchQueue(queue) == null;
     327             : 
     328           0 :   var transaction = queue.startTransaction();
     329             :   StreamQueue consumedMost;
     330             : 
     331             :   // The first error thrown. If no matchers match and this exists, we rethrow
     332             :   // it.
     333             :   Object firstError;
     334             :   StackTrace firstStackTrace;
     335             : 
     336           0 :   await Future.wait(matchers.map((matcher) async {
     337           0 :     var copy = transaction.newQueue();
     338             :     try {
     339           0 :       if (await matcher.matchQueue(copy) != null) return;
     340             :     } catch (error, stackTrace) {
     341             :       if (firstError == null) {
     342             :         firstError = error;
     343             :         firstStackTrace = stackTrace;
     344             :       }
     345             :       return;
     346             :     }
     347             : 
     348           0 :     var rest = new Set<StreamMatcher>.from(matchers);
     349           0 :     rest.remove(matcher);
     350             : 
     351             :     try {
     352           0 :       if (!await _tryInAnyOrder(copy, rest)) return;
     353             :     } catch (error, stackTrace) {
     354             :       if (firstError == null) {
     355             :         firstError = error;
     356             :         firstStackTrace = stackTrace;
     357             :       }
     358             :       return;
     359             :     }
     360             : 
     361             :     if (consumedMost == null ||
     362           0 :         consumedMost.eventsDispatched < copy.eventsDispatched) {
     363             :       consumedMost = copy;
     364             :     }
     365           0 :   }));
     366             : 
     367             :   if (consumedMost == null) {
     368           0 :     transaction.reject();
     369           0 :     if (firstError != null) await new Future.error(firstError, firstStackTrace);
     370             :     return false;
     371             :   } else {
     372           0 :     transaction.commit(consumedMost);
     373             :     return true;
     374             :   }
     375           0 : }

Generated by: LCOV version 1.13