diff --git a/lib/src/io.dart b/lib/src/io.dart index 968f3546fbc4bcf77b02797f0ab46dc99357435c..93e096b9dfc51cf45c9fdb97b875342ddefa3c31 100644 --- a/lib/src/io.dart +++ b/lib/src/io.dart @@ -605,6 +605,16 @@ Future<String> consumeStringInputStream(StringInputStream stream) { return completer.future; } +/// Wrap an InputStream in a ListInputStream. This eagerly drains the [source] +/// input stream. This is useful for spawned processes which will not exit until +/// their output streams have been drained. +/// TODO(rnystrom): Get rid of this once #7218 is fixed. +InputStream wrapInputStream(InputStream source) { + var sink = new ListInputStream(); + pipeInputToInput(source, sink); + return sink; +} + /// Spawns and runs the process located at [executable], passing in [args]. /// Returns a [Future] that will complete with the results of the process after /// it has ended. diff --git a/lib/src/log.dart b/lib/src/log.dart index 6ca1fe01cc8ffa17e5341195a92ef920df5e9349..029f72cf6e5693b8b35583b140d8af17866e8794 100644 --- a/lib/src/log.dart +++ b/lib/src/log.dart @@ -8,7 +8,7 @@ library log; import 'dart:io'; import 'io.dart'; -typedef LogFn(Level level, message); +typedef LogFn(Entry entry); final Map<Level, LogFn> _loggers = new Map<Level, LogFn>(); /// The list of recorded log messages. Will only be recorded if @@ -49,9 +49,9 @@ class Level { /// A single log entry. class Entry { final Level level; - final String message; + final List<String> lines; - Entry(this.level, this.message); + Entry(this.level, this.lines); } /// Logs [message] at [Level.ERROR]. @@ -73,12 +73,13 @@ void fine(message) => write(Level.FINE, message); void write(Level level, message) { if (_loggers.isEmpty) showNormal(); + var lines = message.toString().split(NEWLINE_PATTERN); + var entry = new Entry(level, lines); + var logFn = _loggers[level]; - if (logFn != null) logFn(level, message); + if (logFn != null) logFn(entry); - if (_transcript != null) { - _transcript.add(new Entry(level, '$message')); - } + if (_transcript != null) _transcript.add(entry); } /// Logs an asynchronous IO operation. Logs [startMessage] before the operation @@ -154,7 +155,7 @@ void dumpTranscript() { stderr.writeString('---- Log transcript ----\n'); for (var entry in _transcript) { - _logToStderrWithLabel(entry.level, entry.message); + _logToStderrWithLabel(entry); } stderr.writeString('---- End log transcript ----\n'); } @@ -188,31 +189,40 @@ void showAll() { } /// Log function that prints the message to stdout. -void _logToStdout(Level level, message) { - print('$message'); +void _logToStdout(Entry entry) { + _logToStream(stdout, entry, showLabel: false); } /// Log function that prints the message to stdout with the level name. -void _logToStdoutWithLabel(Level level, message) { - print(_splitAndPrefix(level, message)); +void _logToStdoutWithLabel(Entry entry) { + _logToStream(stdout, entry, showLabel: true); } /// Log function that prints the message to stderr. -void _logToStderr(Level level, message) { - stderr.writeString('$message\n'); +void _logToStderr(Entry entry) { + _logToStream(stderr, entry, showLabel: false); } /// Log function that prints the message to stderr with the level name. -void _logToStderrWithLabel(Level level, message) { - stderr.writeString(_splitAndPrefix(level, message)); - stderr.writeString('\n'); -} - -/// Add the level prefix to the first line of [message] and prefix subsequent -/// lines with "|". -String _splitAndPrefix(Level level, message) { - // TODO(rnystrom): We're doing lots of splitting and joining in here. If that - // becomes a performance problem, we can optimize this to write directly to - // stdout/stderr a line at a time. - return "$level: ${Strings.join(message.toString().split('\n'), '\n | ')}"; +void _logToStderrWithLabel(Entry entry) { + _logToStream(stderr, entry, showLabel: true); +} + +void _logToStream(OutputStream stream, Entry entry, {bool showLabel}) { + bool firstLine = true; + for (var line in entry.lines) { + if (showLabel) { + if (firstLine) { + stream.writeString(entry.level.name); + stream.writeString(': '); + } else { + stream.writeString(' | '); + } + } + + stream.writeString(line); + stream.writeString('\n'); + + firstLine = false; + } } diff --git a/test/test_pub.dart b/test/test_pub.dart index ba0ba0e86202c2b299e48abb88e1e2f3bf466eb9..61afc27b73c1eb5168a717b8244615d010a5a43e 100644 --- a/test/test_pub.dart +++ b/test/test_pub.dart @@ -1268,7 +1268,7 @@ Future<Pair<List<String>, List<String>>> schedulePackageValidation( }); } -/// A matcher that matches a Pair. +/// A matcher that matches a Pair. Matcher pairOf(Matcher firstMatcher, Matcher lastMatcher) => new _PairMatcher(firstMatcher, lastMatcher); @@ -1334,8 +1334,8 @@ class ScheduledProcess { /// Wraps a [Process] [Future] in a scheduled process. ScheduledProcess(this.name, Future<Process> process) : _process = process, - _stdout = process.transform((p) => new StringInputStream(p.stdout)), - _stderr = process.transform((p) => new StringInputStream(p.stderr)) { + _stdout = process.transform((p) => _wrapStream(p.stdout)), + _stderr = process.transform((p) => _wrapStream(p.stderr)) { _schedule((_) { if (!_endScheduled) { @@ -1466,6 +1466,12 @@ class ScheduledProcess { }); } + /// Wraps [source] and ensures it gets eagerly drained. We do this to make + /// sure a process will exit even if we don't care about its output. + static Future<StringInputStream> _wrapStream(InputStream source) { + return new StringInputStream(wrapInputStream(source)); + } + /// Prints the remaining data in the process's stdout and stderr streams. /// Prints nothing if the straems are empty. Future _printStreams() {