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

          Line data    Source code
       1             : // Copyright (c) 2013, 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             : import 'dart:convert';
       7             : import 'dart:math' as math;
       8             : import 'dart:typed_data';
       9             : 
      10             : import 'package:async/async.dart';
      11             : import 'package:matcher/matcher.dart';
      12             : import 'package:path/path.dart' as p;
      13             : import 'package:stream_channel/stream_channel.dart';
      14             : import 'package:term_glyph/term_glyph.dart' as glyph;
      15             : 
      16             : import 'backend/invoker.dart';
      17             : import 'backend/operating_system.dart';
      18             : 
      19             : /// The maximum console line length.
      20             : const _lineLength = 100;
      21             : 
      22             : /// A typedef for a possibly-asynchronous function.
      23             : ///
      24             : /// The return type should only ever by [Future] or void.
      25             : typedef AsyncFunction();
      26             : 
      27             : /// A typedef for a zero-argument callback function.
      28             : typedef void Callback();
      29             : 
      30             : /// A transformer that decodes bytes using UTF-8 and splits them on newlines.
      31             : final lineSplitter = new StreamTransformer<List<int>, String>(
      32             :     (stream, cancelOnError) => stream
      33             :         .transform(UTF8.decoder)
      34             :         .transform(const LineSplitter())
      35             :         .listen(null, cancelOnError: cancelOnError));
      36             : 
      37             : /// A [StreamChannelTransformer] that converts a chunked string channel to a
      38             : /// line-by-line channel. Note that this is only safe for channels whose
      39             : /// messages are guaranteed not to contain newlines.
      40             : final chunksToLines = new StreamChannelTransformer(
      41             :     const LineSplitter(),
      42             :     new StreamSinkTransformer.fromHandlers(
      43             :         handleData: (data, sink) => sink.add("$data\n")));
      44             : 
      45             : /// A regular expression to match the exception prefix that some exceptions'
      46             : /// [Object.toString] values contain.
      47             : final _exceptionPrefix = new RegExp(r'^([A-Z][a-zA-Z]*)?(Exception|Error): ');
      48             : 
      49             : /// A regular expression matching a single vowel.
      50             : final _vowel = new RegExp('[aeiou]');
      51             : 
      52             : /// Directories that are specific to OS X.
      53             : ///
      54             : /// This is used to try to distinguish OS X and Linux in [currentOSGuess].
      55             : final _macOSDirectories = new Set<String>.from(
      56             :     ["/Applications", "/Library", "/Network", "/System", "/Users"]);
      57             : 
      58             : /// Returns the best guess for the current operating system without using
      59             : /// `dart:io`.
      60             : ///
      61             : /// This is useful for running test files directly and skipping tests as
      62             : /// appropriate. The only OS-specific information we have is the current path,
      63             : /// which we try to use to figure out the OS.
      64             : final OperatingSystem currentOSGuess = (() {
      65          15 :   if (p.style == p.Style.url) return OperatingSystem.none;
      66          15 :   if (p.style == p.Style.windows) return OperatingSystem.windows;
      67          20 :   if (_macOSDirectories.any(p.current.startsWith)) return OperatingSystem.macOS;
      68             :   return OperatingSystem.linux;
      69             : })();
      70             : 
      71             : /// A regular expression matching a hyphenated identifier.
      72             : ///
      73             : /// This is like a standard Dart identifier, except that it can also contain
      74             : /// hyphens.
      75             : final hyphenatedIdentifier = new RegExp(r"[a-zA-Z_-][a-zA-Z0-9_-]*");
      76             : 
      77             : /// Like [hyphenatedIdentifier], but anchored so that it must match the entire
      78             : /// string.
      79             : final anchoredHyphenatedIdentifier =
      80             :     new RegExp("^${hyphenatedIdentifier.pattern}\$");
      81             : 
      82             : /// A pair of values.
      83             : class Pair<E, F> {
      84             :   E first;
      85             :   F last;
      86             : 
      87           0 :   Pair(this.first, this.last);
      88             : 
      89           0 :   String toString() => '($first, $last)';
      90             : 
      91             :   bool operator ==(other) {
      92           0 :     if (other is! Pair) return false;
      93           0 :     return other.first == first && other.last == last;
      94             :   }
      95             : 
      96           0 :   int get hashCode => first.hashCode ^ last.hashCode;
      97             : }
      98             : 
      99             : /// Get a string description of an exception.
     100             : ///
     101             : /// Many exceptions include the exception class name at the beginning of their
     102             : /// [toString], so we remove that if it exists.
     103             : String getErrorMessage(error) =>
     104           0 :     error.toString().replaceFirst(_exceptionPrefix, '');
     105             : 
     106             : /// Indent each line in [string] by [size] spaces.
     107             : ///
     108             : /// If [first] is passed, it's used in place of the first line's indentation and
     109             : /// [size] defaults to `first.length`. Otherwise, [size] defaults to 2.
     110             : String indent(String string, {int size, String first}) {
     111           0 :   size ??= first == null ? 2 : first.length;
     112           0 :   return prefixLines(string, " " * size, first: first);
     113             : }
     114             : 
     115             : /// Returns a sentence fragment listing the elements of [iter].
     116             : ///
     117             : /// This converts each element of [iter] to a string and separates them with
     118             : /// commas and/or [conjunction] where appropriate. The [conjunction] defaults to
     119             : /// "and".
     120             : String toSentence(Iterable iter, {String conjunction}) {
     121           0 :   if (iter.length == 1) return iter.first.toString();
     122             : 
     123           0 :   var result = iter.take(iter.length - 1).join(", ");
     124           0 :   if (iter.length > 2) result += ",";
     125           0 :   return "$result ${conjunction ?? 'and'} ${iter.last}";
     126             : }
     127             : 
     128             : /// Returns [name] if [number] is 1, or the plural of [name] otherwise.
     129             : ///
     130             : /// By default, this just adds "s" to the end of [name] to get the plural. If
     131             : /// [plural] is passed, that's used instead.
     132             : String pluralize(String name, int number, {String plural}) {
     133           0 :   if (number == 1) return name;
     134             :   if (plural != null) return plural;
     135           0 :   return '${name}s';
     136             : }
     137             : 
     138             : /// Returns [noun] with an indefinite article ("a" or "an") added, based on
     139             : /// whether its first letter is a vowel.
     140           0 : String a(String noun) => noun.startsWith(_vowel) ? "an $noun" : "a $noun";
     141             : 
     142             : /// Wraps [text] so that it fits within [lineLength], which defaults to 100
     143             : /// characters.
     144             : ///
     145             : /// This preserves existing newlines and doesn't consider terminal color escapes
     146             : /// part of a word's length.
     147             : String wordWrap(String text, {int lineLength}) {
     148             :   if (lineLength == null) lineLength = _lineLength;
     149           0 :   return text.split("\n").map((originalLine) {
     150           0 :     var buffer = new StringBuffer();
     151             :     var lengthSoFar = 0;
     152           0 :     for (var word in originalLine.split(" ")) {
     153           0 :       var wordLength = withoutColors(word).length;
     154           0 :       if (wordLength > lineLength) {
     155           0 :         if (lengthSoFar != 0) buffer.writeln();
     156           0 :         buffer.writeln(word);
     157           0 :       } else if (lengthSoFar == 0) {
     158           0 :         buffer.write(word);
     159             :         lengthSoFar = wordLength;
     160           0 :       } else if (lengthSoFar + 1 + wordLength > lineLength) {
     161           0 :         buffer.writeln();
     162           0 :         buffer.write(word);
     163             :         lengthSoFar = wordLength;
     164             :       } else {
     165           0 :         buffer.write(" $word");
     166           0 :         lengthSoFar += 1 + wordLength;
     167             :       }
     168             :     }
     169           0 :     return buffer.toString();
     170           0 :   }).join("\n");
     171             : }
     172             : 
     173             : /// A regular expression matching terminal color codes.
     174             : final _colorCode = new RegExp('\u001b\\[[0-9;]+m');
     175             : 
     176             : /// Returns [str] without any color codes.
     177           0 : String withoutColors(String str) => str.replaceAll(_colorCode, '');
     178             : 
     179             : /// Flattens nested [Iterable]s inside an [Iterable] into a single [List]
     180             : /// containing only non-[Iterable] elements.
     181             : List flatten(Iterable nested) {
     182           0 :   var result = [];
     183             :   helper(iter) {
     184           0 :     for (var element in iter) {
     185           0 :       if (element is Iterable) {
     186           0 :         helper(element);
     187             :       } else {
     188           0 :         result.add(element);
     189             :       }
     190             :     }
     191             :   }
     192             : 
     193           0 :   helper(nested);
     194             :   return result;
     195             : }
     196             : 
     197             : /// Truncates [text] to fit within [maxLength].
     198             : ///
     199             : /// This will try to truncate along word boundaries and preserve words both at
     200             : /// the beginning and the end of [text].
     201             : String truncate(String text, int maxLength) {
     202             :   // Return the full message if it fits.
     203           0 :   if (text.length <= maxLength) return text;
     204             : 
     205             :   // If we can fit the first and last three words, do so.
     206           0 :   var words = text.split(' ');
     207           0 :   if (words.length > 1) {
     208           0 :     var i = words.length;
     209           0 :     var length = words.first.length + 4;
     210             :     do {
     211           0 :       i--;
     212           0 :       length += 1 + words[i].length;
     213           0 :     } while (length <= maxLength && i > 0);
     214           0 :     if (length > maxLength || i == 0) i++;
     215           0 :     if (i < words.length - 4) {
     216             :       // Require at least 3 words at the end.
     217           0 :       var buffer = new StringBuffer();
     218           0 :       buffer.write(words.first);
     219           0 :       buffer.write(' ...');
     220           0 :       for (; i < words.length; i++) {
     221           0 :         buffer.write(' ');
     222           0 :         buffer.write(words[i]);
     223             :       }
     224           0 :       return buffer.toString();
     225             :     }
     226             :   }
     227             : 
     228             :   // Otherwise truncate to return the trailing text, but attempt to start at
     229             :   // the beginning of a word.
     230           0 :   var result = text.substring(text.length - maxLength + 4);
     231           0 :   var firstSpace = result.indexOf(' ');
     232           0 :   if (firstSpace > 0) {
     233           0 :     result = result.substring(firstSpace);
     234             :   }
     235           0 :   return '...$result';
     236             : }
     237             : 
     238             : /// Returns a human-friendly representation of [duration].
     239             : String niceDuration(Duration duration) {
     240           0 :   var minutes = duration.inMinutes;
     241           0 :   var seconds = duration.inSeconds % 60;
     242           0 :   var decaseconds = (duration.inMilliseconds % 1000) ~/ 100;
     243             : 
     244           0 :   var buffer = new StringBuffer();
     245           0 :   if (minutes != 0) buffer.write("$minutes minutes");
     246             : 
     247           0 :   if (minutes == 0 || seconds != 0) {
     248           0 :     if (minutes != 0) buffer.write(", ");
     249           0 :     buffer.write(seconds);
     250           0 :     if (decaseconds != 0) buffer.write(".$decaseconds");
     251           0 :     buffer.write(" seconds");
     252             :   }
     253             : 
     254           0 :   return buffer.toString();
     255             : }
     256             : 
     257             : /// Returns the first value [stream] emits, or `null` if [stream] closes before
     258             : /// emitting a value.
     259             : Future maybeFirst(Stream stream) {
     260           0 :   var completer = new Completer();
     261             : 
     262             :   var subscription;
     263           0 :   subscription = stream.listen((data) {
     264           0 :     completer.complete(data);
     265           0 :     subscription.cancel();
     266             :   }, onError: (error, stackTrace) {
     267           0 :     completer.completeError(error, stackTrace);
     268           0 :     subscription.cancel();
     269             :   }, onDone: () {
     270           0 :     completer.complete();
     271             :   });
     272             : 
     273           0 :   return completer.future;
     274             : }
     275             : 
     276             : /// Returns a single-subscription stream that emits the results of [operations]
     277             : /// in the order they complete.
     278             : ///
     279             : /// If the subscription is canceled, any pending operations are canceled as
     280             : /// well.
     281             : Stream<T> inCompletionOrder<T>(Iterable<CancelableOperation<T>> operations) {
     282           0 :   var operationSet = operations.toSet();
     283           0 :   var controller = new StreamController<T>(
     284             :       sync: true,
     285             :       onCancel: () {
     286           0 :         return Future.wait(operationSet.map((operation) => operation.cancel()));
     287             :       });
     288             : 
     289           0 :   for (var operation in operationSet) {
     290           0 :     operation.value
     291           0 :         .then((value) => controller.add(value))
     292           0 :         .catchError(controller.addError)
     293           0 :         .whenComplete(() {
     294           0 :       operationSet.remove(operation);
     295           0 :       if (operationSet.isEmpty) controller.close();
     296             :     });
     297             :   }
     298             : 
     299           0 :   return controller.stream;
     300             : }
     301             : 
     302             : /// Returns a stream that emits [error] and [stackTrace], then closes.
     303             : ///
     304             : /// This is useful for adding errors to streams defined via `async*`.
     305             : Stream errorStream(error, StackTrace stackTrace) {
     306           0 :   var controller = new StreamController();
     307           0 :   controller.addError(error, stackTrace);
     308           0 :   controller.close();
     309           0 :   return controller.stream;
     310             : }
     311             : 
     312             : /// Runs [fn] and discards its return value.
     313             : ///
     314             : /// This is useful for making a block of code async without forcing the
     315             : /// containing method to return a future.
     316             : void invoke(fn()) {
     317           0 :   fn();
     318             : }
     319             : 
     320             : /// Runs [body] with special error-handling behavior.
     321             : ///
     322             : /// Errors emitted [body] will still cause the current test to fail, but they
     323             : /// won't cause it to *stop*. In particular, they won't remove any outstanding
     324             : /// callbacks registered outside of [body].
     325             : ///
     326             : /// This may only be called within a test.
     327             : Future errorsDontStopTest(body()) {
     328           0 :   var completer = new Completer();
     329             : 
     330           0 :   Invoker.current.addOutstandingCallback();
     331           0 :   Invoker.current.waitForOutstandingCallbacks(() {
     332           0 :     new Future.sync(body).whenComplete(completer.complete);
     333           0 :   }).then((_) => Invoker.current.removeOutstandingCallback());
     334             : 
     335           0 :   return completer.future;
     336             : }
     337             : 
     338             : /// Returns a random base64 string containing [bytes] bytes of data.
     339             : ///
     340             : /// [seed] is passed to [math.Random].
     341             : String randomBase64(int bytes, {int seed}) {
     342           0 :   var random = new math.Random(seed);
     343           0 :   var data = new Uint8List(bytes);
     344           0 :   for (var i = 0; i < bytes; i++) {
     345           0 :     data[i] = random.nextInt(256);
     346             :   }
     347           0 :   return BASE64.encode(data);
     348             : }
     349             : 
     350             : /// Throws an [ArgumentError] if [message] isn't recursively JSON-safe.
     351             : void ensureJsonEncodable(Object message) {
     352             :   if (message == null ||
     353           0 :       message is String ||
     354           0 :       message is num ||
     355           0 :       message is bool) {
     356             :     // JSON-encodable, hooray!
     357           0 :   } else if (message is List) {
     358           0 :     for (var element in message) {
     359           0 :       ensureJsonEncodable(element);
     360             :     }
     361           0 :   } else if (message is Map) {
     362           0 :     message.forEach((key, value) {
     363           0 :       if (key is! String) {
     364           0 :         throw new ArgumentError("$message can't be JSON-encoded.");
     365             :       }
     366             : 
     367           0 :       ensureJsonEncodable(value);
     368             :     });
     369             :   } else {
     370           0 :     throw new ArgumentError.value("$message can't be JSON-encoded.");
     371             :   }
     372             : }
     373             : 
     374             : /// Prepends a vertical bar to [text].
     375           0 : String addBar(String text) => prefixLines(text, "${glyph.verticalLine} ",
     376           0 :     first: "${glyph.downEnd} ", last: "${glyph.upEnd} ", single: "| ");
     377             : 
     378             : /// Indents [text], and adds a bullet at the beginning.
     379             : String addBullet(String text) =>
     380           0 :     prefixLines(text, "  ", first: "${glyph.bullet} ");
     381             : 
     382             : /// Converts [strings] to a bulleted list.
     383           0 : String bullet(Iterable<String> strings) => strings.map(addBullet).join("\n");
     384             : 
     385             : /// Prepends each line in [text] with [prefix].
     386             : ///
     387             : /// If [first] or [last] is passed, the first and last lines, respectively, are
     388             : /// prefixed with those instead. If [single] is passed, it's used if there's
     389             : /// only a single line; otherwise, [first], [last], or [prefix] is used, in that
     390             : /// order of precedence.
     391             : String prefixLines(String text, String prefix,
     392             :     {String first, String last, String single}) {
     393             :   first ??= prefix;
     394             :   last ??= prefix;
     395             :   single ??= first ?? last ?? prefix;
     396             : 
     397           0 :   var lines = text.split('\n');
     398           0 :   if (lines.length == 1) return "$single$text";
     399             : 
     400           0 :   var buffer = new StringBuffer("$first${lines.first}\n");
     401             : 
     402             :   // Write out all but the first and last lines with [prefix].
     403           0 :   for (var line in lines.skip(1).take(lines.length - 2)) {
     404           0 :     buffer.writeln("$prefix$line");
     405             :   }
     406           0 :   buffer.write("$last${lines.last}");
     407           0 :   return buffer.toString();
     408             : }
     409             : 
     410             : /// Returns a pretty-printed representation of [value].
     411             : ///
     412             : /// The matcher package doesn't expose its pretty-print function directly, but
     413             : /// we can use it through StringDescription.
     414             : String prettyPrint(value) =>
     415           0 :     new StringDescription().addDescriptionOf(value).toString();

Generated by: LCOV version 1.13