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

Generated by: LCOV version 1.14