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

Generated by: LCOV version 1.13