LCOV - code coverage report
Current view: top level - test_api-0.4.8/lib/src/expect - stream_matcher.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 38 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             : import 'package:test_api/hooks.dart';
       8             : 
       9             : import 'async_matcher.dart';
      10             : import 'util/pretty_print.dart';
      11             : 
      12             : /// A matcher that matches events from [Stream]s or [StreamQueue]s.
      13             : ///
      14             : /// Stream matchers are designed to make it straightforward to create complex
      15             : /// expectations for streams, and to interleave expectations with the rest of a
      16             : /// test. They can be used on a [Stream] to match all events it emits:
      17             : ///
      18             : /// ```dart
      19             : /// expect(stream, emitsInOrder([
      20             : ///   // Values match individual events.
      21             : ///   "Ready.",
      22             : ///
      23             : ///   // Matchers also run against individual events.
      24             : ///   startsWith("Loading took"),
      25             : ///
      26             : ///   // Stream matchers can be nested. This asserts that one of two events are
      27             : ///   // emitted after the "Loading took" line.
      28             : ///   emitsAnyOf(["Succeeded!", "Failed!"]),
      29             : ///
      30             : ///   // By default, more events are allowed after the matcher finishes
      31             : ///   // matching. This asserts instead that the stream emits a done event and
      32             : ///   // nothing else.
      33             : ///   emitsDone
      34             : /// ]));
      35             : /// ```
      36             : ///
      37             : /// It can also match a [StreamQueue], in which case it consumes the matched
      38             : /// events. The call to [expect] returns a [Future] that completes when the
      39             : /// matcher is done matching. You can `await` this to consume different events
      40             : /// at different times:
      41             : ///
      42             : /// ```dart
      43             : /// var stdout = StreamQueue(stdoutLineStream);
      44             : ///
      45             : /// // Ignore lines from the process until it's about to emit the URL.
      46             : /// await expectLater(stdout, emitsThrough('WebSocket URL:'));
      47             : ///
      48             : /// // Parse the next line as a URL.
      49             : /// var url = Uri.parse(await stdout.next);
      50             : /// expect(url.host, equals('localhost'));
      51             : ///
      52             : /// // You can match against the same StreamQueue multiple times.
      53             : /// await expectLater(stdout, emits('Waiting for connection...'));
      54             : /// ```
      55             : ///
      56             : /// Users can call [new StreamMatcher] to create custom matchers.
      57             : abstract class StreamMatcher extends Matcher {
      58             :   /// The description of this matcher.
      59             :   ///
      60             :   /// This is in the subjunctive mood, which means it can be used after the word
      61             :   /// "should". For example, it might be "emit the right events".
      62             :   String get description;
      63             : 
      64             :   /// Creates a new [StreamMatcher] described by [description] that matches
      65             :   /// events with [matchQueue].
      66             :   ///
      67             :   /// The [matchQueue] callback is used to implement [StreamMatcher.matchQueue],
      68             :   /// and should follow all the guarantees of that method. In particular:
      69             :   ///
      70             :   /// * If it matches successfully, it should return `null` and possibly consume
      71             :   ///   events.
      72             :   /// * If it fails to match, consume no events and return a description of the
      73             :   ///   failure.
      74             :   /// * The description should be in past tense.
      75             :   /// * The description should be gramatically valid when used after "the
      76             :   ///   stream"—"emitted the wrong events", for example.
      77             :   ///
      78             :   /// The [matchQueue] callback may return the empty string to indicate a
      79             :   /// failure if it has no information to add beyond the description of the
      80             :   /// failure and the events actually emitted by the stream.
      81             :   ///
      82             :   /// The [description] should be in the subjunctive mood. This means that it
      83             :   /// should be grammatically valid when used after the word "should". For
      84             :   /// example, it might be "emit the right events".
      85             :   factory StreamMatcher(Future<String?> Function(StreamQueue) matchQueue,
      86             :       String description) = _StreamMatcher;
      87             : 
      88             :   /// Tries to match events emitted by [queue].
      89             :   ///
      90             :   /// If this matches successfully, it consumes the matching events from [queue]
      91             :   /// and returns `null`.
      92             :   ///
      93             :   /// If this fails to match, it doesn't consume any events and returns a
      94             :   /// description of the failure. This description is in the past tense, and
      95             :   /// could grammatically be used after "the stream". For example, it might
      96             :   /// return "emitted the wrong events".
      97             :   ///
      98             :   /// The description string may also be empty, which indicates that the
      99             :   /// matcher's description and the events actually emitted by the stream are
     100             :   /// enough to understand the failure.
     101             :   ///
     102             :   /// If the queue emits an error, that error is re-thrown unless otherwise
     103             :   /// indicated by the matcher.
     104             :   Future<String?> matchQueue(StreamQueue queue);
     105             : }
     106             : 
     107             : /// A concrete implementation of [StreamMatcher].
     108             : ///
     109             : /// This is separate from the original type to hide the private [AsyncMatcher]
     110             : /// interface.
     111             : class _StreamMatcher extends AsyncMatcher implements StreamMatcher {
     112             :   @override
     113             :   final String description;
     114             : 
     115             :   /// The callback used to implement [matchQueue].
     116             :   final Future<String?> Function(StreamQueue) _matchQueue;
     117             : 
     118           0 :   _StreamMatcher(this._matchQueue, this.description);
     119             : 
     120           0 :   @override
     121           0 :   Future<String?> matchQueue(StreamQueue queue) => _matchQueue(queue);
     122             : 
     123           0 :   @override
     124             :   dynamic /*FutureOr<String>*/ matchAsync(item) {
     125             :     StreamQueue queue;
     126             :     var shouldCancelQueue = false;
     127           0 :     if (item is StreamQueue) {
     128             :       queue = item;
     129           0 :     } else if (item is Stream) {
     130           0 :       queue = StreamQueue(item);
     131             :       shouldCancelQueue = true;
     132             :     } else {
     133             :       return 'was not a Stream or a StreamQueue';
     134             :     }
     135             : 
     136             :     // Avoid async/await in the outer method so that we synchronously error out
     137             :     // for an invalid argument type.
     138           0 :     var transaction = queue.startTransaction();
     139           0 :     var copy = transaction.newQueue();
     140           0 :     return matchQueue(copy).then((result) async {
     141             :       // Accept the transaction if the result is null, indicating that the match
     142             :       // succeeded.
     143             :       if (result == null) {
     144           0 :         transaction.commit(copy);
     145             :         return null;
     146             :       }
     147             : 
     148             :       // Get a list of events emitted by the stream so we can emit them as part
     149             :       // of the error message.
     150           0 :       var replay = transaction.newQueue();
     151           0 :       var events = <Result?>[];
     152             :       var subscription = Result.captureStreamTransformer
     153           0 :           .bind(replay.rest.cast())
     154           0 :           .listen(events.add, onDone: () => events.add(null));
     155             : 
     156             :       // Wait on a timer tick so all buffered events are emitted.
     157           0 :       await Future.delayed(Duration.zero);
     158           0 :       _unawaited(subscription.cancel());
     159             : 
     160           0 :       var eventsString = events.map((event) {
     161             :         if (event == null) {
     162             :           return 'x Stream closed.';
     163           0 :         } else if (event.isValue) {
     164           0 :           return addBullet(event.asValue!.value.toString());
     165             :         } else {
     166           0 :           var error = event.asError!;
     167           0 :           var chain = TestHandle.current.formatStackTrace(error.stackTrace);
     168           0 :           var text = '${error.error}\n$chain';
     169           0 :           return indent(text, first: '! ');
     170             :         }
     171           0 :       }).join('\n');
     172           0 :       if (eventsString.isEmpty) eventsString = 'no events';
     173             : 
     174           0 :       transaction.reject();
     175             : 
     176           0 :       var buffer = StringBuffer();
     177           0 :       buffer.writeln(indent(eventsString, first: 'emitted '));
     178           0 :       if (result.isNotEmpty) buffer.writeln(indent(result, first: '  which '));
     179           0 :       return buffer.toString().trimRight();
     180           0 :     }, onError: (Object error) {
     181           0 :       transaction.reject();
     182             :       throw error;
     183           0 :     }).then((result) {
     184           0 :       if (shouldCancelQueue) queue.cancel();
     185             :       return result;
     186             :     });
     187             :   }
     188             : 
     189           0 :   @override
     190             :   Description describe(Description description) =>
     191           0 :       description.add('should ').add(this.description);
     192             : }
     193             : 
     194           0 : void _unawaited(Future<void> f) {}

Generated by: LCOV version 1.14