Unverified Commit 33b183e6 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Fix extra blank lines in logger output (#81607)

parent 96e4b47f
...@@ -323,7 +323,6 @@ class AndroidGradleBuilder implements AndroidBuilder { ...@@ -323,7 +323,6 @@ class AndroidGradleBuilder implements AndroidBuilder {
final Status status = _logger.startProgress( final Status status = _logger.startProgress(
"Running Gradle task '$assembleTask'...", "Running Gradle task '$assembleTask'...",
multilineOutput: true,
); );
final List<String> command = <String>[ final List<String> command = <String>[
...@@ -636,7 +635,6 @@ class AndroidGradleBuilder implements AndroidBuilder { ...@@ -636,7 +635,6 @@ class AndroidGradleBuilder implements AndroidBuilder {
final String aarTask = getAarTaskFor(buildInfo); final String aarTask = getAarTaskFor(buildInfo);
final Status status = _logger.startProgress( final Status status = _logger.startProgress(
"Running Gradle task '$aarTask'...", "Running Gradle task '$aarTask'...",
multilineOutput: true,
); );
final String flutterRoot = _fileSystem.path.absolute(Cache.flutterRoot); final String flutterRoot = _fileSystem.path.absolute(Cache.flutterRoot);
......
...@@ -21,7 +21,7 @@ class ToolExit implements Exception { ...@@ -21,7 +21,7 @@ class ToolExit implements Exception {
final int? exitCode; final int? exitCode;
@override @override
String toString() => 'Exception: $message'; String toString() => 'Exception: $message'; // TODO(ianh): Really this should say "Error".
} }
/// Indicates to the linter that the given future is intentionally not awaited. /// Indicates to the linter that the given future is intentionally not awaited.
......
...@@ -120,16 +120,16 @@ abstract class Logger { ...@@ -120,16 +120,16 @@ abstract class Logger {
/// The `progressId` argument provides an ID that can be used to identify /// The `progressId` argument provides an ID that can be used to identify
/// this type of progress (e.g. `hot.reload`, `hot.restart`). /// this type of progress (e.g. `hot.reload`, `hot.restart`).
/// ///
/// The `progressIndicatorPadding` can optionally be used to specify spacing /// The `progressIndicatorPadding` can optionally be used to specify the width
/// between the `message` and the progress indicator, if any. /// of the space into which the `message` is placed before the progress
/// indicator, if any. It is ignored if the message is longer.
Status startProgress( Status startProgress(
String message, { String message, {
String? progressId, String? progressId,
bool multilineOutput = false,
int progressIndicatorPadding = kDefaultStatusPadding, int progressIndicatorPadding = kDefaultStatusPadding,
}); });
/// A [SilentStatus] or an [AnsiSpinner] (depending on whether the /// A [SilentStatus] or an [AnonymousSpinnerStatus] (depending on whether the
/// terminal is fancy enough), already started. /// terminal is fancy enough), already started.
Status startSpinner({ VoidCallback? onFinish }); Status startSpinner({ VoidCallback? onFinish });
...@@ -223,12 +223,10 @@ class DelegatingLogger implements Logger { ...@@ -223,12 +223,10 @@ class DelegatingLogger implements Logger {
@override @override
Status startProgress(String message, { Status startProgress(String message, {
String? progressId, String? progressId,
bool multilineOutput = false,
int progressIndicatorPadding = kDefaultStatusPadding, int progressIndicatorPadding = kDefaultStatusPadding,
}) { }) {
return _delegate.startProgress(message, return _delegate.startProgress(message,
progressId: progressId, progressId: progressId,
multilineOutput: multilineOutput,
progressIndicatorPadding: progressIndicatorPadding, progressIndicatorPadding: progressIndicatorPadding,
); );
} }
...@@ -363,20 +361,17 @@ class StdoutLogger extends Logger { ...@@ -363,20 +361,17 @@ class StdoutLogger extends Logger {
Status startProgress( Status startProgress(
String message, { String message, {
String? progressId, String? progressId,
bool multilineOutput = false,
int progressIndicatorPadding = kDefaultStatusPadding, int progressIndicatorPadding = kDefaultStatusPadding,
}) { }) {
if (_status != null) { if (_status != null) {
// Ignore nested progresses; return a no-op status object. // Ignore nested progresses; return a no-op status object.
return SilentStatus( return SilentStatus(
onFinish: _clearStatus,
stopwatch: _stopwatchFactory.createStopwatch(), stopwatch: _stopwatchFactory.createStopwatch(),
)..start(); )..start();
} }
if (supportsColor) { if (supportsColor) {
_status = AnsiStatus( _status = SpinnerStatus(
message: message, message: message,
multilineOutput: multilineOutput,
padding: progressIndicatorPadding, padding: progressIndicatorPadding,
onFinish: _clearStatus, onFinish: _clearStatus,
stdio: _stdio, stdio: _stdio,
...@@ -397,18 +392,24 @@ class StdoutLogger extends Logger { ...@@ -397,18 +392,24 @@ class StdoutLogger extends Logger {
@override @override
Status startSpinner({ VoidCallback? onFinish }) { Status startSpinner({ VoidCallback? onFinish }) {
if (terminal.supportsColor) { if (_status != null || !supportsColor) {
return AnsiSpinner( return SilentStatus(
onFinish: onFinish, onFinish: onFinish,
stopwatch: _stopwatchFactory.createStopwatch(), stopwatch: _stopwatchFactory.createStopwatch(),
terminal: terminal,
stdio: _stdio,
)..start(); )..start();
} }
return SilentStatus( _status = AnonymousSpinnerStatus(
onFinish: onFinish, onFinish: () {
if (onFinish != null) {
onFinish();
}
_clearStatus();
},
stdio: _stdio,
stopwatch: _stopwatchFactory.createStopwatch(), stopwatch: _stopwatchFactory.createStopwatch(),
terminal: terminal,
)..start(); )..start();
return _status!;
} }
void _clearStatus() { void _clearStatus() {
...@@ -561,7 +562,6 @@ class BufferLogger extends Logger { ...@@ -561,7 +562,6 @@ class BufferLogger extends Logger {
Status startProgress( Status startProgress(
String message, { String message, {
String? progressId, String? progressId,
bool multilineOutput = false,
int progressIndicatorPadding = kDefaultStatusPadding, int progressIndicatorPadding = kDefaultStatusPadding,
}) { }) {
assert(progressIndicatorPadding != null); assert(progressIndicatorPadding != null);
...@@ -661,7 +661,6 @@ class VerboseLogger extends DelegatingLogger { ...@@ -661,7 +661,6 @@ class VerboseLogger extends DelegatingLogger {
Status startProgress( Status startProgress(
String message, { String message, {
String? progressId, String? progressId,
bool multilineOutput = false,
int progressIndicatorPadding = kDefaultStatusPadding, int progressIndicatorPadding = kDefaultStatusPadding,
}) { }) {
assert(progressIndicatorPadding != null); assert(progressIndicatorPadding != null);
...@@ -758,12 +757,11 @@ typedef SlowWarningCallback = String Function(); ...@@ -758,12 +757,11 @@ typedef SlowWarningCallback = String Function();
/// ///
/// The [SilentStatus] class never has any output. /// The [SilentStatus] class never has any output.
/// ///
/// The [AnsiSpinner] subclass shows a spinner, and replaces it with a single /// The [SpinnerStatus] subclass shows a message with a spinner, and replaces it
/// space character when stopped or canceled. /// with timing information when stopped. When canceled, the information isn't
/// shown. In either case, a newline is printed.
/// ///
/// The [AnsiStatus] subclass shows a spinner, and replaces it with timing /// The [AnonymousSpinnerStatus] subclass just shows a spinner.
/// information when stopped. When canceled, the information isn't shown. In
/// either case, a newline is printed.
/// ///
/// The [SummaryStatus] subclass shows only a static message (without an /// The [SummaryStatus] subclass shows only a static message (without an
/// indicator), then updates it when the operation ends. /// indicator), then updates it when the operation ends.
...@@ -835,6 +833,8 @@ class SilentStatus extends Status { ...@@ -835,6 +833,8 @@ class SilentStatus extends Status {
} }
} }
const int _kTimePadding = 8; // should fit "99,999ms"
/// Constructor writes [message] to [stdout]. On [cancel] or [stop], will call /// Constructor writes [message] to [stdout]. On [cancel] or [stop], will call
/// [onFinish]. On [stop], will additionally print out summary information. /// [onFinish]. On [stop], will additionally print out summary information.
class SummaryStatus extends Status { class SummaryStatus extends Status {
...@@ -876,7 +876,8 @@ class SummaryStatus extends Status { ...@@ -876,7 +876,8 @@ class SummaryStatus extends Status {
_printMessage(); _printMessage();
} }
super.stop(); super.stop();
writeSummaryInformation(); assert(_messageShowingOnCurrentLine);
_writeToStdOut(elapsedTime.padLeft(_kTimePadding));
_writeToStdOut('\n'); _writeToStdOut('\n');
} }
...@@ -888,57 +889,91 @@ class SummaryStatus extends Status { ...@@ -888,57 +889,91 @@ class SummaryStatus extends Status {
} }
} }
/// Prints a (minimum) 8 character padded time.
void writeSummaryInformation() {
assert(_messageShowingOnCurrentLine);
_writeToStdOut(elapsedTime.padLeft(_kTimePadding));
}
@override @override
void pause() { void pause() {
super.pause(); super.pause();
_writeToStdOut('\n'); if (_messageShowingOnCurrentLine) {
_messageShowingOnCurrentLine = false; _writeToStdOut('\n');
_messageShowingOnCurrentLine = false;
}
} }
} }
/// An [AnsiSpinner] is a simple animation that does nothing but implement a /// A kind of animated [Status] that has no message.
/// terminal spinner. When stopped or canceled, the animation erases itself. ///
class AnsiSpinner extends Status { /// Call [pause] before outputting any text while this is running.
AnsiSpinner({ class AnonymousSpinnerStatus extends Status {
required Stopwatch stopwatch, AnonymousSpinnerStatus({
required Terminal terminal,
VoidCallback? onFinish, VoidCallback? onFinish,
required Stopwatch stopwatch,
required Stdio stdio, required Stdio stdio,
required Terminal terminal,
}) : _stdio = stdio, }) : _stdio = stdio,
_terminal = terminal, _terminal = terminal,
_animation = _selectAnimation(terminal),
super( super(
onFinish: onFinish, onFinish: onFinish,
stopwatch: stopwatch, stopwatch: stopwatch,
); );
final String _backspaceChar = '\b';
final String _clearChar = ' ';
final Stdio _stdio; final Stdio _stdio;
final Terminal _terminal; final Terminal _terminal;
bool timedOut = false; static const String _backspaceChar = '\b';
static const String _clearChar = ' ';
static const List<String> _emojiAnimations = <String>[
'⣾⣽⣻⢿⡿⣟⣯⣷', // counter-clockwise
'⣾⣷⣯⣟⡿⢿⣻⣽', // clockwise
'⣾⣷⣯⣟⡿⢿⣻⣽⣷⣾⣽⣻⢿⡿⣟⣯⣷', // bouncing clockwise and counter-clockwise
'⣾⣷⣯⣽⣻⣟⡿⢿⣻⣟⣯⣽', // snaking
'⣾⣽⣻⢿⣿⣷⣯⣟⡿⣿', // alternating rain
'⣀⣠⣤⣦⣶⣾⣿⡿⠿⠻⠛⠋⠉⠙⠛⠟⠿⢿⣿⣷⣶⣴⣤⣄', // crawl up and down, large
'⠙⠚⠖⠦⢤⣠⣄⡤⠴⠲⠓⠋', // crawl up and down, small
'⣀⡠⠤⠔⠒⠊⠉⠑⠒⠢⠤⢄', // crawl up and down, tiny
'⡀⣄⣦⢷⠻⠙⠈⠀⠁⠋⠟⡾⣴⣠⢀⠀', // slide up and down
'⠙⠸⢰⣠⣄⡆⠇⠋', // clockwise line
'⠁⠈⠐⠠⢀⡀⠄⠂', // clockwise dot
'⢇⢣⢱⡸⡜⡎', // vertical wobble up
'⡇⡎⡜⡸⢸⢱⢣⢇', // vertical wobble down
'⡀⣀⣐⣒⣖⣶⣾⣿⢿⠿⠯⠭⠩⠉⠁⠀', // swirl
'⠁⠐⠄⢀⢈⢂⢠⣀⣁⣐⣄⣌⣆⣤⣥⣴⣼⣶⣷⣿⣾⣶⣦⣤⣠⣀⡀⠀⠀', // snowing and melting
'⠁⠋⠞⡴⣠⢀⠀⠈⠙⠻⢷⣦⣄⡀⠀⠉⠛⠲⢤⢀⠀', // falling water
'⠄⡢⢑⠈⠀⢀⣠⣤⡶⠞⠋⠁⠀⠈⠙⠳⣆⡀⠀⠆⡷⣹⢈⠀⠐⠪⢅⡀⠀', // fireworks
'⠐⢐⢒⣒⣲⣶⣷⣿⡿⡷⡧⠧⠇⠃⠁⠀⡀⡠⡡⡱⣱⣳⣷⣿⢿⢯⢧⠧⠣⠃⠂⠀⠈⠨⠸⠺⡺⡾⡿⣿⡿⡷⡗⡇⡅⡄⠄⠀⡀⡐⣐⣒⣓⣳⣻⣿⣾⣼⡼⡸⡘⡈⠈⠀', // fade
'⢸⡯⠭⠅⢸⣇⣀⡀⢸⣇⣸⡇⠈⢹⡏⠁⠈⢹⡏⠁⢸⣯⣭⡅⢸⡯⢕⡂⠀⠀', // text crawl
];
static const List<String> _asciiAnimations = <String>[
r'-\|/',
];
static List<String> _selectAnimation(Terminal terminal) {
final List<String> animations = terminal.supportsEmoji ? _emojiAnimations : _asciiAnimations;
return animations[terminal.preferredStyle % animations.length]
.runes
.map<String>((int scalar) => String.fromCharCode(scalar))
.toList();
}
int ticks = 0; final List<String> _animation;
Timer? timer;
// Windows console font has a limited set of Unicode characters. Timer? timer;
List<String> get _animation => !_terminal.supportsEmoji int ticks = 0;
? const <String>[r'-', r'\', r'|', r'/'] int _lastAnimationFrameLength = 0;
: const <String>['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
String get _currentAnimationFrame => _animation[ticks % _animation.length]; String get _currentAnimationFrame => _animation[ticks % _animation.length];
int get _currentLength => _currentAnimationFrame.length; int get _currentLineLength => _lastAnimationFrameLength;
String get _backspace => _backspaceChar * (spinnerIndent + _currentLength);
String get _clear => _clearChar * (spinnerIndent + _currentLength);
@protected void _writeToStdOut(String message) => _stdio.stdoutWrite(message);
int get spinnerIndent => 0;
void _clear(int length) {
_writeToStdOut(
'${_backspaceChar * length}'
'${_clearChar * length}'
'${_backspaceChar * length}'
);
}
@override @override
void start() { void start() {
...@@ -947,10 +982,7 @@ class AnsiSpinner extends Status { ...@@ -947,10 +982,7 @@ class AnsiSpinner extends Status {
_startSpinner(); _startSpinner();
} }
void _writeToStdOut(String message) => _stdio.stdoutWrite(message);
void _startSpinner() { void _startSpinner() {
_writeToStdOut(_clear); // for _callback to backspace over
timer = Timer.periodic(const Duration(milliseconds: 100), _callback); timer = Timer.periodic(const Duration(milliseconds: 100), _callback);
_callback(timer!); _callback(timer!);
} }
...@@ -959,30 +991,23 @@ class AnsiSpinner extends Status { ...@@ -959,30 +991,23 @@ class AnsiSpinner extends Status {
assert(this.timer == timer); assert(this.timer == timer);
assert(timer != null); assert(timer != null);
assert(timer.isActive); assert(timer.isActive);
_writeToStdOut(_backspace); _writeToStdOut(_backspaceChar * _lastAnimationFrameLength);
ticks += 1; ticks += 1;
_writeToStdOut('${_clearChar * spinnerIndent}$_currentAnimationFrame'); final String newFrame = _currentAnimationFrame;
} _lastAnimationFrameLength = newFrame.runes.length;
_writeToStdOut(newFrame);
@override
void finish() {
assert(timer != null);
assert(timer!.isActive);
timer?.cancel();
timer = null;
_clearSpinner();
super.finish();
}
void _clearSpinner() {
_writeToStdOut('$_backspace$_clear$_backspace');
} }
@override @override
void pause() { void pause() {
assert(timer != null); assert(timer != null);
assert(timer!.isActive); assert(timer!.isActive);
_clearSpinner(); if (_terminal.supportsColor) {
_writeToStdOut('\r\x1B[K'); // go to start of line and clear line
} else {
_clear(_currentLineLength);
}
_lastAnimationFrameLength = 0;
timer?.cancel(); timer?.cancel();
} }
...@@ -992,101 +1017,90 @@ class AnsiSpinner extends Status { ...@@ -992,101 +1017,90 @@ class AnsiSpinner extends Status {
assert(!timer!.isActive); assert(!timer!.isActive);
_startSpinner(); _startSpinner();
} }
}
const int _kTimePadding = 8; // should fit "99,999ms" @override
void finish() {
assert(timer != null);
assert(timer!.isActive);
timer?.cancel();
timer = null;
_clear(_lastAnimationFrameLength);
_lastAnimationFrameLength = 0;
super.finish();
}
}
/// Constructor writes [message] to [stdout] with padding, then starts an /// An animated version of [Status].
/// indeterminate progress indicator animation (it's a subclass of ///
/// [AnsiSpinner]). /// The constructor writes [message] to [stdout] with padding, then starts an
/// indeterminate progress indicator animation.
/// ///
/// On [cancel] or [stop], will call [onFinish]. On [stop], will /// On [cancel] or [stop], will call [onFinish]. On [stop], will
/// additionally print out summary information. /// additionally print out summary information.
class AnsiStatus extends AnsiSpinner { ///
AnsiStatus({ /// Call [pause] before outputting any text while this is running.
this.message = '', class SpinnerStatus extends AnonymousSpinnerStatus {
this.multilineOutput = false, SpinnerStatus({
required this.message,
this.padding = kDefaultStatusPadding, this.padding = kDefaultStatusPadding,
required Stopwatch stopwatch,
required Terminal terminal,
VoidCallback? onFinish, VoidCallback? onFinish,
required Stopwatch stopwatch,
required Stdio stdio, required Stdio stdio,
}) : assert(message != null), required Terminal terminal,
assert(multilineOutput != null), }) : super(
assert(padding != null),
super(
onFinish: onFinish, onFinish: onFinish,
stdio: stdio,
stopwatch: stopwatch, stopwatch: stopwatch,
stdio: stdio,
terminal: terminal, terminal: terminal,
); );
final String message; final String message;
final bool multilineOutput;
final int padding; final int padding;
static const String _margin = ' '; static final String _margin = AnonymousSpinnerStatus._clearChar * (5 + _kTimePadding - 1);
@override
int get spinnerIndent => _kTimePadding - 1;
int _totalMessageLength = 0; int _totalMessageLength = 0;
@override
int get _currentLineLength => _totalMessageLength + super._currentLineLength;
@override @override
void start() { void start() {
_startStatus(); _printStatus();
super.start(); super.start();
} }
void _startStatus() { void _printStatus() {
final String line = '${message.padRight(padding)}$_margin'; final String line = '${message.padRight(padding)}$_margin';
_totalMessageLength = line.length; _totalMessageLength = line.length;
_writeToStdOut(line); _writeToStdOut(line);
} }
@override @override
void stop() { void pause() {
super.stop(); super.pause();
writeSummaryInformation(); _totalMessageLength = 0;
_writeToStdOut('\n');
} }
@override @override
void cancel() { void resume() {
super.cancel(); _printStatus();
_writeToStdOut('\n'); super.resume();
}
/// Print summary information when a task is done.
///
/// If [multilineOutput] is false, replaces the spinner with the summary message.
///
/// If [multilineOutput] is true, then it prints the message again on a new
/// line before writing the elapsed time.
void writeSummaryInformation() {
if (multilineOutput) {
_writeToStdOut('\n${'$message Done'.padRight(padding)}$_margin');
}
_writeToStdOut(elapsedTime.padLeft(_kTimePadding));
}
void _clearStatus() {
_writeToStdOut(
'${_backspaceChar * _totalMessageLength}'
'${_clearChar * _totalMessageLength}'
'${_backspaceChar * _totalMessageLength}',
);
} }
@override @override
void pause() { void stop() {
super.pause(); super.stop(); // calls finish, which clears the spinner
_clearStatus(); assert(_totalMessageLength > _kTimePadding);
_writeToStdOut(AnonymousSpinnerStatus._backspaceChar * (_kTimePadding - 1));
_writeToStdOut(elapsedTime.padLeft(_kTimePadding));
_writeToStdOut('\n');
} }
@override @override
void resume() { void cancel() {
_startStatus(); super.cancel(); // calls finish, which clears the spinner
super.resume(); assert(_totalMessageLength > 0);
_writeToStdOut('\n');
} }
} }
...@@ -81,6 +81,10 @@ abstract class Terminal { ...@@ -81,6 +81,10 @@ abstract class Terminal {
/// Whether the current terminal can display emoji. /// Whether the current terminal can display emoji.
bool get supportsEmoji; bool get supportsEmoji;
/// When we have a choice of styles (e.g. animated spinners), this selects the
/// style to use.
int get preferredStyle;
/// Whether we are interacting with the flutter tool via the terminal. /// Whether we are interacting with the flutter tool via the terminal.
/// ///
/// If not set, defaults to false. /// If not set, defaults to false.
...@@ -143,12 +147,15 @@ class AnsiTerminal implements Terminal { ...@@ -143,12 +147,15 @@ class AnsiTerminal implements Terminal {
AnsiTerminal({ AnsiTerminal({
required io.Stdio stdio, required io.Stdio stdio,
required Platform platform, required Platform platform,
DateTime? now, // Time used to determine preferredStyle. Defaults to 0001-01-01 00:00.
}) })
: _stdio = stdio, : _stdio = stdio,
_platform = platform; _platform = platform,
_now = now ?? DateTime(1);
final io.Stdio _stdio; final io.Stdio _stdio;
final Platform _platform; final Platform _platform;
final DateTime _now;
static const String bold = '\u001B[1m'; static const String bold = '\u001B[1m';
static const String resetAll = '\u001B[0m'; static const String resetAll = '\u001B[0m';
...@@ -187,6 +194,15 @@ class AnsiTerminal implements Terminal { ...@@ -187,6 +194,15 @@ class AnsiTerminal implements Terminal {
bool get supportsEmoji => !_platform.isWindows bool get supportsEmoji => !_platform.isWindows
|| _platform.environment.containsKey('WT_SESSION'); || _platform.environment.containsKey('WT_SESSION');
@override
int get preferredStyle {
const int workdays = DateTime.friday;
if (_now.weekday <= workdays) {
return _now.weekday - 1;
}
return _now.hour + workdays;
}
final RegExp _boldControls = RegExp( final RegExp _boldControls = RegExp(
'(${RegExp.escape(resetBold)}|${RegExp.escape(bold)})', '(${RegExp.escape(resetBold)}|${RegExp.escape(bold)})',
); );
...@@ -355,6 +371,9 @@ class _TestTerminal implements Terminal { ...@@ -355,6 +371,9 @@ class _TestTerminal implements Terminal {
@override @override
final bool supportsEmoji; final bool supportsEmoji;
@override
int get preferredStyle => 0;
@override @override
bool get stdinHasTerminal => false; bool get stdinHasTerminal => false;
......
...@@ -163,6 +163,7 @@ AnsiTerminal get terminal { ...@@ -163,6 +163,7 @@ AnsiTerminal get terminal {
final AnsiTerminal _defaultAnsiTerminal = AnsiTerminal( final AnsiTerminal _defaultAnsiTerminal = AnsiTerminal(
stdio: stdio, stdio: stdio,
platform: platform, platform: platform,
now: DateTime.now(),
); );
/// The global Stdio wrapper. /// The global Stdio wrapper.
......
...@@ -192,17 +192,14 @@ void main() { ...@@ -192,17 +192,14 @@ void main() {
); );
const String progressId = 'progressId'; const String progressId = 'progressId';
const bool multilineOutput = true;
const int progressIndicatorPadding = kDefaultStatusPadding * 2; const int progressIndicatorPadding = kDefaultStatusPadding * 2;
expect( expect(
() => delegatingLogger.startProgress(message, () => delegatingLogger.startProgress(message,
progressId: progressId, progressId: progressId,
multilineOutput: multilineOutput,
progressIndicatorPadding: progressIndicatorPadding, progressIndicatorPadding: progressIndicatorPadding,
), ),
_throwsInvocationFor(() => fakeLogger.startProgress(message, _throwsInvocationFor(() => fakeLogger.startProgress(message,
progressId: progressId, progressId: progressId,
multilineOutput: multilineOutput,
progressIndicatorPadding: progressIndicatorPadding, progressIndicatorPadding: progressIndicatorPadding,
)), )),
); );
...@@ -399,7 +396,7 @@ void main() { ...@@ -399,7 +396,7 @@ void main() {
Platform ansiPlatform; Platform ansiPlatform;
AnsiTerminal terminal; AnsiTerminal terminal;
AnsiTerminal coloredTerminal; AnsiTerminal coloredTerminal;
AnsiStatus ansiStatus; SpinnerStatus spinnerStatus;
setUp(() { setUp(() {
platform = FakePlatform(stdoutSupportsAnsi: false); platform = FakePlatform(stdoutSupportsAnsi: false);
...@@ -414,7 +411,7 @@ void main() { ...@@ -414,7 +411,7 @@ void main() {
platform: ansiPlatform, platform: ansiPlatform,
); );
ansiStatus = AnsiStatus( spinnerStatus = SpinnerStatus(
message: 'Hello world', message: 'Hello world',
padding: 20, padding: 20,
onFinish: () => called += 1, onFinish: () => called += 1,
...@@ -424,35 +421,35 @@ void main() { ...@@ -424,35 +421,35 @@ void main() {
); );
}); });
testWithoutContext('AnsiSpinner works (1)', () async { testWithoutContext('AnonymousSpinnerStatus works (1)', () async {
bool done = false; bool done = false;
mockStopwatch = FakeStopwatch(); mockStopwatch = FakeStopwatch();
FakeAsync().run((FakeAsync time) { FakeAsync().run((FakeAsync time) {
final AnsiSpinner ansiSpinner = AnsiSpinner( final AnonymousSpinnerStatus spinner = AnonymousSpinnerStatus(
stdio: mockStdio, stdio: mockStdio,
stopwatch: stopwatchFactory.createStopwatch(), stopwatch: stopwatchFactory.createStopwatch(),
terminal: terminal, terminal: terminal,
)..start(); )..start();
doWhileAsync(time, () => ansiSpinner.ticks < 10); doWhileAsync(time, () => spinner.ticks < 10);
List<String> lines = outputStdout(); List<String> lines = outputStdout();
expect(lines[0], startsWith( expect(lines[0], startsWith(
terminal.supportsEmoji terminal.supportsEmoji
? ' \b\b⣻\b⢿\b⡿\b⣟\b⣯\b⣷\b⣾\b⣽\b⣻' ? '⣽\b⣻\b⢿\b⡿\b⣟\b⣯\b⣷\b⣾\b⣽\b⣻'
: ' \b\\\b|\b/\b-\b\\\b|\b/\b-' : '\\\b|\b/\b-\b\\\b|\b/\b-'
), ),
); );
expect(lines[0].endsWith('\n'), isFalse); expect(lines[0].endsWith('\n'), isFalse);
expect(lines.length, equals(1)); expect(lines.length, equals(1));
ansiSpinner.stop(); spinner.stop();
lines = outputStdout(); lines = outputStdout();
expect(lines[0], endsWith('\b \b')); expect(lines[0], endsWith('\b \b'));
expect(lines.length, equals(1)); expect(lines.length, equals(1));
// Verify that stopping or canceling multiple times throws. // Verify that stopping or canceling multiple times throws.
expect(ansiSpinner.stop, throwsAssertionError); expect(spinner.stop, throwsAssertionError);
expect(ansiSpinner.cancel, throwsAssertionError); expect(spinner.cancel, throwsAssertionError);
done = true; done = true;
}); });
expect(done, isTrue); expect(done, isTrue);
...@@ -472,12 +469,13 @@ void main() { ...@@ -472,12 +469,13 @@ void main() {
); );
expect(outputStderr().length, equals(1)); expect(outputStderr().length, equals(1));
expect(outputStderr().first, isEmpty); expect(outputStderr().first, isEmpty);
// the 5 below is the margin that is always included between the message and the time. // the 4 below is the margin that is always included between the message and the time.
// the 8 below is the space left for the time.
expect( expect(
outputStdout().join('\n'), outputStdout().join('\n'),
matches(terminal.supportsEmoji matches(terminal.supportsEmoji
? r'^Hello {15} {5} {8}[\b]{8} {7}⣽$' ? r'^Hello {15} {4} {8}⣽$'
: r'^Hello {15} {5} {8}[\b]{8} {7}\\$'), : r'^Hello {15} {4} {8}\\$'),
); );
mockStopwatch.elapsed = const Duration(seconds: 4, milliseconds: 100); mockStopwatch.elapsed = const Duration(seconds: 4, milliseconds: 100);
status.stop(); status.stop();
...@@ -485,8 +483,8 @@ void main() { ...@@ -485,8 +483,8 @@ void main() {
outputStdout().join('\n'), outputStdout().join('\n'),
matches( matches(
terminal.supportsEmoji terminal.supportsEmoji
? r'^Hello {15} {5} {8}[\b]{8} {7}⣽[\b]{8} {8}[\b]{8}[\d, ]{4}[\d]\.[\d]s[\n]$' ? r'^Hello {15} {4} {8}⣽[\b] [\b]{8}[\d, ]{4}[\d]\.[\d]s[\n]$'
: r'^Hello {15} {5} {8}[\b]{8} {7}\\[\b]{8} {8}[\b]{8}[\d, ]{4}[\d]\.[\d]s[\n]$', : r'^Hello {15} {4} {8}\\[\b] [\b]{8}[\d, ]{4}[\d]\.[\d]s[\n]$',
), ),
); );
}); });
...@@ -501,32 +499,30 @@ void main() { ...@@ -501,32 +499,30 @@ void main() {
outputPreferences: OutputPreferences.test(showColor: true), outputPreferences: OutputPreferences.test(showColor: true),
stopwatchFactory: stopwatchFactory, stopwatchFactory: stopwatchFactory,
); );
const String message = "Knock Knock, Who's There";
final Status status = logger.startProgress( final Status status = logger.startProgress(
"Knock Knock, Who's There", message,
progressIndicatorPadding: 10, progressIndicatorPadding: 10, // ignored
); );
logger.printStatus('Rude Interrupting Cow'); logger.printStatus('Rude Interrupting Cow');
status.stop(); status.stop();
final String a = terminal.supportsEmoji ? '⣽' : r'\'; final String a = terminal.supportsEmoji ? '⣽' : r'\';
final String b = terminal.supportsEmoji ? '⣻' : '|'; final String b = terminal.supportsEmoji ? '⣻' : '|';
const String blankLine = '\r\x1B[K';
expect( expect(
outputStdout().join('\n'), outputStdout().join('\n'),
"Knock Knock, Who's There " // initial message '$message' // initial message
' ' // placeholder so that spinner can backspace on its first tick '${" " * 4}${" " * 8}' // margin (4) and space for the time at the end (8)
// ignore: missing_whitespace_between_adjacent_strings // ignore: missing_whitespace_between_adjacent_strings
'\b\b\b\b\b\b\b\b $a' // first tick '$a' // first tick
'\b\b\b\b\b\b\b\b ' // clearing the spinner '$blankLine' // clearing the line
'\b\b\b\b\b\b\b\b' // clearing the clearing of the spinner
'\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b ' // clearing the message
'\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b' // clearing the clearing of the message
'Rude Interrupting Cow\n' // message 'Rude Interrupting Cow\n' // message
"Knock Knock, Who's There " // message restoration '$message' // message restoration
' ' // placeholder so that spinner can backspace on its second tick '${" " * 4}${" " * 8}' // margin (4) and space for the time at the end (8)
'$b' // second tick
// ignore: missing_whitespace_between_adjacent_strings // ignore: missing_whitespace_between_adjacent_strings
'\b\b\b\b\b\b\b\b $b' // second tick '\b \b' // backspace the tick, wipe the tick, backspace the wipe
'\b\b\b\b\b\b\b\b ' // clearing the spinner to put the time '\b\b\b\b\b\b\b' // backspace the space for the time
'\b\b\b\b\b\b\b\b' // clearing the clearing of the spinner
' 5.0s\n', // replacing it with the time ' 5.0s\n', // replacing it with the time
); );
done = true; done = true;
...@@ -534,66 +530,96 @@ void main() { ...@@ -534,66 +530,96 @@ void main() {
expect(done, isTrue); expect(done, isTrue);
}); });
testWithoutContext('AnsiStatus works when canceled', () async { testWithoutContext('Stdout startProgress on non-colored terminal pauses', () async {
bool done = false;
FakeAsync().run((FakeAsync time) {
mockStopwatch.elapsed = const Duration(seconds: 5);
final Logger logger = StdoutLogger(
terminal: terminal,
stdio: mockStdio,
outputPreferences: OutputPreferences.test(showColor: true),
stopwatchFactory: stopwatchFactory,
);
const String message = "Knock Knock, Who's There";
final Status status = logger.startProgress(
message,
progressIndicatorPadding: 10, // ignored
);
logger.printStatus('Rude Interrupting Cow');
status.stop();
expect(
outputStdout().join('\n'),
'$message' // initial message
' ' // margin
'\n' // clearing the line
'Rude Interrupting Cow\n' // message
'$message 5.0s\n' // message restoration
);
done = true;
});
expect(done, isTrue);
});
testWithoutContext('SpinnerStatus works when canceled', () async {
bool done = false; bool done = false;
FakeAsync().run((FakeAsync time) { FakeAsync().run((FakeAsync time) {
ansiStatus.start(); spinnerStatus.start();
mockStopwatch.elapsed = const Duration(seconds: 1); mockStopwatch.elapsed = const Duration(seconds: 1);
doWhileAsync(time, () => ansiStatus.ticks < 10); doWhileAsync(time, () => spinnerStatus.ticks < 10);
List<String> lines = outputStdout(); List<String> lines = outputStdout();
expect(lines[0], startsWith( expect(lines[0], startsWith(
terminal.supportsEmoji terminal.supportsEmoji
? 'Hello world \b\b\b\b\b\b\b\b ⣽\b\b\b\b\b\b\b\b ⣻\b\b\b\b\b\b\b\b ⢿\b\b\b\b\b\b\b\b ⡿\b\b\b\b\b\b\b\b ⣟\b\b\b\b\b\b\b\b ⣯\b\b\b\b\b\b\b\b ⣷\b\b\b\b\b\b\b\b ⣾\b\b\b\b\b\b\b\b ⣽\b\b\b\b\b\b\b\b ⣻' ? 'Hello world \b⣻\b⢿\b⡿\b⣟\b⣯\b⣷\b⣾\b⣽\b⣻'
: 'Hello world \b\b\b\b\b\b\b\b \\\b\b\b\b\b\b\b\b |\b\b\b\b\b\b\b\b /\b\b\b\b\b\b\b\b -\b\b\b\b\b\b\b\b \\\b\b\b\b\b\b\b\b |\b\b\b\b\b\b\b\b /\b\b\b\b\b\b\b\b -\b\b\b\b\b\b\b\b \\\b\b\b\b\b\b\b\b |', : 'Hello world \\\b|\b/\b-\b\\\b|\b/\b-\b\\\b|'
)); ));
expect(lines.length, equals(1)); expect(lines.length, equals(1));
expect(lines[0].endsWith('\n'), isFalse); expect(lines[0].endsWith('\n'), isFalse);
// Verify a cancel does _not_ print the time and prints a newline. // Verify a cancel does _not_ print the time and prints a newline.
ansiStatus.cancel(); spinnerStatus.cancel();
lines = outputStdout(); lines = outputStdout();
final List<Match> matches = secondDigits.allMatches(lines[0]).toList(); final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
expect(matches, isEmpty); expect(matches, isEmpty);
final String leading = terminal.supportsEmoji ? '⣻' : '|'; final String leading = terminal.supportsEmoji ? '⣻' : '|';
expect(lines[0], endsWith('$leading\b\b\b\b\b\b\b\b \b\b\b\b\b\b\b\b')); expect(lines[0], endsWith('$leading\b \b'));
expect(called, equals(1)); expect(called, equals(1));
expect(lines.length, equals(2)); expect(lines.length, equals(2));
expect(lines[1], equals('')); expect(lines[1], equals(''));
// Verify that stopping or canceling multiple times throws. // Verify that stopping or canceling multiple times throws.
expect(ansiStatus.cancel, throwsAssertionError); expect(spinnerStatus.cancel, throwsAssertionError);
expect(ansiStatus.stop, throwsAssertionError); expect(spinnerStatus.stop, throwsAssertionError);
done = true; done = true;
}); });
expect(done, isTrue); expect(done, isTrue);
}); });
testWithoutContext('AnsiStatus works when stopped', () async { testWithoutContext('SpinnerStatus works when stopped', () async {
bool done = false; bool done = false;
FakeAsync().run((FakeAsync time) { FakeAsync().run((FakeAsync time) {
ansiStatus.start(); spinnerStatus.start();
mockStopwatch.elapsed = const Duration(seconds: 1); mockStopwatch.elapsed = const Duration(seconds: 1);
doWhileAsync(time, () => ansiStatus.ticks < 10); doWhileAsync(time, () => spinnerStatus.ticks < 10);
List<String> lines = outputStdout(); List<String> lines = outputStdout();
expect(lines, hasLength(1)); expect(lines, hasLength(1));
expect( expect(
lines[0], lines[0],
terminal.supportsEmoji terminal.supportsEmoji
? 'Hello world \b\b\b\b\b\b\b\b ⣽\b\b\b\b\b\b\b\b ⣻\b\b\b\b\b\b\b\b ⢿\b\b\b\b\b\b\b\b ⡿\b\b\b\b\b\b\b\b ⣟\b\b\b\b\b\b\b\b ⣯\b\b\b\b\b\b\b\b ⣷\b\b\b\b\b\b\b\b ⣾\b\b\b\b\b\b\b\b ⣽\b\b\b\b\b\b\b\b ⣻' ? 'Hello world \b⣻\b⢿\b⡿\b⣟\b⣯\b⣷\b⣾\b⣽\b⣻'
: 'Hello world \b\b\b\b\b\b\b\b \\\b\b\b\b\b\b\b\b |\b\b\b\b\b\b\b\b /\b\b\b\b\b\b\b\b -\b\b\b\b\b\b\b\b \\\b\b\b\b\b\b\b\b |\b\b\b\b\b\b\b\b /\b\b\b\b\b\b\b\b -\b\b\b\b\b\b\b\b \\\b\b\b\b\b\b\b\b |', : 'Hello world \\\b|\b/\b-\b\\\b|\b/\b-\b\\\b|'
); );
// Verify a stop prints the time. // Verify a stop prints the time.
ansiStatus.stop(); spinnerStatus.stop();
lines = outputStdout(); lines = outputStdout();
expect(lines, hasLength(2)); expect(lines, hasLength(2));
expect(lines[0], matches( expect(lines[0], matches(
terminal.supportsEmoji terminal.supportsEmoji
? r'Hello world {8}[\b]{8} {7}⣽[\b]{8} {7}⣻[\b]{8} {7}⢿[\b]{8} {7}⡿[\b]{8} {7}⣟[\b]{8} {7}⣯[\b]{8} {7}⣷[\b]{8} {7}⣾[\b]{8} {7}⣽[\b]{8} {7}⣻[\b]{8} {7} [\b]{8}[\d., ]{5}[\d]ms$' ? r'Hello world ⣽[\b]⣻[\b]⢿[\b]⡿[\b]⣟[\b]⣯[\b]⣷[\b]⣾[\b]⣽[\b]⣻[\b] [\b]{8}[\d., ]{5}[\d]ms$'
: r'Hello world {8}[\b]{8} {7}\\[\b]{8} {7}|[\b]{8} {7}/[\b]{8} {7}-[\b]{8} {7}\\[\b]{8} {7}|[\b]{8} {7}/[\b]{8} {7}-[\b]{8} {7}\\[\b]{8} {7}|[\b]{8} {7} [\b]{8}[\d., ]{6}[\d]ms$', : r'Hello world \\[\b]|[\b]/[\b]-[\b]\\[\b]|[\b]/[\b]-[\b]\\[\b]|[\b] [\b]{8}[\d., ]{5}[\d]ms$'
)); ));
expect(lines[1], isEmpty); expect(lines[1], isEmpty);
final List<Match> times = secondDigits.allMatches(lines[0]).toList(); final List<Match> times = secondDigits.allMatches(lines[0]).toList();
...@@ -607,8 +633,8 @@ void main() { ...@@ -607,8 +633,8 @@ void main() {
expect(lines[1], equals('')); expect(lines[1], equals(''));
// Verify that stopping or canceling multiple times throws. // Verify that stopping or canceling multiple times throws.
expect(ansiStatus.stop, throwsAssertionError); expect(spinnerStatus.stop, throwsAssertionError);
expect(ansiStatus.cancel, throwsAssertionError); expect(spinnerStatus.cancel, throwsAssertionError);
done = true; done = true;
}); });
expect(done, isTrue); expect(done, isTrue);
......
...@@ -173,12 +173,53 @@ void main() { ...@@ -173,12 +173,53 @@ void main() {
..stdinHasTerminal = false; ..stdinHasTerminal = false;
final AnsiTerminal ansiTerminal = AnsiTerminal( final AnsiTerminal ansiTerminal = AnsiTerminal(
stdio: stdio, stdio: stdio,
platform: const LocalPlatform() platform: const LocalPlatform(),
); );
expect(() => ansiTerminal.singleCharMode = true, returnsNormally); expect(() => ansiTerminal.singleCharMode = true, returnsNormally);
}); });
}); });
testWithoutContext('AnsiTerminal.preferredStyle', () {
final Stdio stdio = FakeStdio();
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform()).preferredStyle, 0); // Defaults to 0 for backwards compatibility.
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 1)).preferredStyle, 0);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 2)).preferredStyle, 1);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 3)).preferredStyle, 2);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 4)).preferredStyle, 3);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 5)).preferredStyle, 4);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 6)).preferredStyle, 5);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 7)).preferredStyle, 5);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 8)).preferredStyle, 0);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 9)).preferredStyle, 1);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 10)).preferredStyle, 2);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 11)).preferredStyle, 3);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 1, 1)).preferredStyle, 0);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 2, 1)).preferredStyle, 1);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 3, 1)).preferredStyle, 2);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 4, 1)).preferredStyle, 3);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 5, 1)).preferredStyle, 4);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 6, 1)).preferredStyle, 6);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 7, 1)).preferredStyle, 6);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 8, 1)).preferredStyle, 0);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 9, 1)).preferredStyle, 1);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 10, 1)).preferredStyle, 2);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 11, 1)).preferredStyle, 3);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 1, 23)).preferredStyle, 0);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 2, 23)).preferredStyle, 1);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 3, 23)).preferredStyle, 2);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 4, 23)).preferredStyle, 3);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 5, 23)).preferredStyle, 4);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 6, 23)).preferredStyle, 28);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 7, 23)).preferredStyle, 28);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 8, 23)).preferredStyle, 0);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 9, 23)).preferredStyle, 1);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 10, 23)).preferredStyle, 2);
expect(AnsiTerminal(stdio: stdio, platform: const LocalPlatform(), now: DateTime(2018, 1, 11, 23)).preferredStyle, 3);
});
} }
late Stream<String> mockStdInStream; late Stream<String> mockStdInStream;
...@@ -187,7 +228,8 @@ class TestTerminal extends AnsiTerminal { ...@@ -187,7 +228,8 @@ class TestTerminal extends AnsiTerminal {
TestTerminal({ TestTerminal({
Stdio? stdio, Stdio? stdio,
Platform platform = const LocalPlatform(), Platform platform = const LocalPlatform(),
}) : super(stdio: stdio ?? Stdio(), platform: platform); DateTime? now,
}) : super(stdio: stdio ?? Stdio(), platform: platform, now: now ?? DateTime(2018));
@override @override
Stream<String> get keystrokes { Stream<String> get keystrokes {
...@@ -195,6 +237,9 @@ class TestTerminal extends AnsiTerminal { ...@@ -195,6 +237,9 @@ class TestTerminal extends AnsiTerminal {
} }
bool singleCharMode = false; bool singleCharMode = false;
@override
int get preferredStyle => 0;
} }
class FakeStdio extends Fake implements Stdio { class FakeStdio extends Fake implements Stdio {
......
...@@ -682,4 +682,7 @@ class TestTerminal extends AnsiTerminal { ...@@ -682,4 +682,7 @@ class TestTerminal extends AnsiTerminal {
Stream<String> get keystrokes { Stream<String> get keystrokes {
return mockTerminalStdInStream; return mockTerminalStdInStream;
} }
@override
int get preferredStyle => 0;
} }
...@@ -280,6 +280,8 @@ Future<ProcessTestResult> runFlutter( ...@@ -280,6 +280,8 @@ Future<ProcessTestResult> runFlutter(
return ProcessTestResult(exitCode, stdoutLog, stderrLog); return ProcessTestResult(exitCode, stdoutLog, stderrLog);
} }
const int progressMessageWidth = 64;
void main() { void main() {
testWithoutContext('flutter run writes and clears pidfile appropriately', () async { testWithoutContext('flutter run writes and clears pidfile appropriately', () async {
final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync(); final String tempDirectory = fileSystem.systemTempDirectory.createTempSync('flutter_overall_experience_test.').resolveSymbolicLinksSync();
...@@ -326,18 +328,18 @@ void main() { ...@@ -326,18 +328,18 @@ void main() {
<String>['run', '-dflutter-tester', '--report-ready', '--pid-file', pidFile, '--no-devtools', testScript], <String>['run', '-dflutter-tester', '--report-ready', '--pid-file', pidFile, '--no-devtools', testScript],
testDirectory, testDirectory,
<Transition>[ <Transition>[
Barrier('Flutter run key commands.', handler: (String line) { Multiple(<Pattern>['Flutter run key commands.', 'called paint'], handler: (String line) {
pid = int.parse(fileSystem.file(pidFile).readAsStringSync()); pid = int.parse(fileSystem.file(pidFile).readAsStringSync());
processManager.killPid(pid, ProcessSignal.sigusr1); processManager.killPid(pid, ProcessSignal.sigusr1);
return null; return null;
}), }),
Barrier(RegExp(r'^Performing hot reload\.\.\.'), logging: true), // sometimes this includes the "called reassemble" message Barrier('Performing hot reload...'.padRight(progressMessageWidth), logging: true),
Multiple(<Pattern>[RegExp(r'^Reloaded 0 libraries in [0-9]+ms\.$'), /*'called reassemble', (see TODO below)*/ 'called paint'], handler: (String line) { Multiple(<Pattern>[RegExp(r'^Reloaded 0 libraries in [0-9]+ms\.$'), 'called reassemble', 'called paint'], handler: (String line) {
processManager.killPid(pid, ProcessSignal.sigusr2); processManager.killPid(pid, ProcessSignal.sigusr2);
return null; return null;
}), }),
Barrier(RegExp(r'^Performing hot restart\.\.\.')), // sametimes this includes the "called main" message Barrier('Performing hot restart...'.padRight(progressMessageWidth)),
Multiple(<Pattern>[RegExp(r'^Restarted application in [0-9]+ms.$'), /*'called main', (see TODO below)*/ 'called paint'], handler: (String line) { Multiple(<Pattern>[RegExp(r'^Restarted application in [0-9]+ms.$'), 'called main', 'called paint'], handler: (String line) {
return 'q'; return 'q';
}), }),
const Barrier('Application finished.'), const Barrier('Application finished.'),
...@@ -347,25 +349,20 @@ void main() { ...@@ -347,25 +349,20 @@ void main() {
// We check the output from the app (all starts with "called ...") and the output from the tool // We check the output from the app (all starts with "called ...") and the output from the tool
// (everything else) separately, because their relative timing isn't guaranteed. Their rough timing // (everything else) separately, because their relative timing isn't guaranteed. Their rough timing
// is verified by the expected transitions above. // is verified by the expected transitions above.
// TODO(ianh): Fix the tool so that the output isn't garbled (right now we're putting debug output from expect(result.stdout.where((String line) => line.startsWith('called ')), <Object>[
// the app on the line where we're spinning the busy signal, rather than adding a newline).
expect(result.stdout.where((String line) => line.startsWith('called ') &&
line != 'called reassemble' /* see todo above*/ &&
line != 'called main' /* see todo above*/), <Object>[
// logs start after we receive the response to sending SIGUSR1 // logs start after we receive the response to sending SIGUSR1
// SIGUSR1: // SIGUSR1:
// 'called reassemble', // see todo above, this only sometimes gets included, other times it's on the "performing..." line 'called reassemble',
'called paint', 'called paint',
// SIGUSR2: // SIGUSR2:
// 'called main', // see todo above, this is sometimes on the "performing..." line 'called main',
'called paint', 'called paint',
]); ]);
expect(result.stdout.where((String line) => !line.startsWith('called ')), <Object>[ expect(result.stdout.where((String line) => !line.startsWith('called ')), <Object>[
// logs start after we receive the response to sending SIGUSR1 // logs start after we receive the response to sending SIGUSR1
startsWith('Performing hot reload...'), // see todo above, this sometimes ends with "called reassemble" 'Performing hot reload...'.padRight(progressMessageWidth),
'', // this newline is probably the misplaced one for the reassemble; see todo above
startsWith('Reloaded 0 libraries in '), startsWith('Reloaded 0 libraries in '),
'Performing hot restart... ', 'Performing hot restart...'.padRight(progressMessageWidth),
startsWith('Restarted application in '), startsWith('Restarted application in '),
'', // this newline is the one for after we hit "q" '', // this newline is the one for after we hit "q"
'Application finished.', 'Application finished.',
...@@ -386,14 +383,14 @@ void main() { ...@@ -386,14 +383,14 @@ void main() {
<String>['run', '-dflutter-tester', '--report-ready', '--no-devtools', testScript], <String>['run', '-dflutter-tester', '--report-ready', '--no-devtools', testScript],
testDirectory, testDirectory,
<Transition>[ <Transition>[
Multiple(<Pattern>['Flutter run key commands.', 'called main'], handler: (String line) { Multiple(<Pattern>['Flutter run key commands.', 'called main', 'called paint'], handler: (String line) {
return 'r'; return 'r';
}), }),
Barrier(RegExp(r'^Performing hot reload\.\.\.'), logging: true), Barrier('Performing hot reload...'.padRight(progressMessageWidth), logging: true),
Multiple(<Pattern>['ready', /*'reassemble', (see todo below)*/ 'called paint'], handler: (String line) { Multiple(<Pattern>['ready', 'called reassemble', 'called paint'], handler: (String line) {
return 'R'; return 'R';
}), }),
Barrier(RegExp(r'^Performing hot restart\.\.\.')), Barrier('Performing hot restart...'.padRight(progressMessageWidth)),
Multiple(<Pattern>['ready', 'called main', 'called paint'], handler: (String line) { Multiple(<Pattern>['ready', 'called main', 'called paint'], handler: (String line) {
return 'p'; return 'p';
}), }),
...@@ -410,11 +407,10 @@ void main() { ...@@ -410,11 +407,10 @@ void main() {
// We check the output from the app (all starts with "called ...") and the output from the tool // We check the output from the app (all starts with "called ...") and the output from the tool
// (everything else) separately, because their relative timing isn't guaranteed. Their rough timing // (everything else) separately, because their relative timing isn't guaranteed. Their rough timing
// is verified by the expected transitions above. // is verified by the expected transitions above.
// TODO(ianh): Fix the tool so that the output isn't garbled (right now we're putting debug output from expect(result.stdout.where((String line) => line.startsWith('called ')), <Object>[
// the app on the line where we're spinning the busy signal, rather than adding a newline). // logs start after we initiate the hot reload
expect(result.stdout.where((String line) => line.startsWith('called ') && line != 'called reassemble' /* see todo above*/), <Object>[
// hot reload: // hot reload:
// 'called reassemble', // see todo above, this sometimes gets placed on the "Performing hot reload..." line 'called reassemble',
'called paint', 'called paint',
// hot restart: // hot restart:
'called main', 'called main',
...@@ -427,12 +423,11 @@ void main() { ...@@ -427,12 +423,11 @@ void main() {
]); ]);
expect(result.stdout.where((String line) => !line.startsWith('called ')), <Object>[ expect(result.stdout.where((String line) => !line.startsWith('called ')), <Object>[
// logs start after we receive the response to hitting "r" // logs start after we receive the response to hitting "r"
startsWith('Performing hot reload...'), // see todo above, this sometimes ends with "called reassemble" 'Performing hot reload...'.padRight(progressMessageWidth),
'', // this newline is probably the misplaced one for the reassemble; see todo above
startsWith('Reloaded 0 libraries in '), startsWith('Reloaded 0 libraries in '),
'ready', 'ready',
'', // this newline is the one for after we hit "R" '', // this newline is the one for after we hit "R"
'Performing hot restart... ', 'Performing hot restart...'.padRight(progressMessageWidth),
startsWith('Restarted application in '), startsWith('Restarted application in '),
'ready', 'ready',
'', // newline for after we hit "p" the first time '', // newline for after we hit "p" the first time
...@@ -462,7 +457,7 @@ void main() { ...@@ -462,7 +457,7 @@ void main() {
Barrier(RegExp(r'^The Flutter DevTools debugger and profiler on Flutter test device is available at: '), handler: (String line) { Barrier(RegExp(r'^The Flutter DevTools debugger and profiler on Flutter test device is available at: '), handler: (String line) {
return 'r'; return 'r';
}), }),
Barrier(RegExp(r'^Performing hot reload\.\.\.'), logging: true), Barrier('Performing hot reload...'.padRight(progressMessageWidth), logging: true),
Barrier(RegExp(r'^Reloaded 0 libraries in [0-9]+ms.'), handler: (String line) { Barrier(RegExp(r'^Reloaded 0 libraries in [0-9]+ms.'), handler: (String line) {
return 'q'; return 'q';
}), }),
...@@ -472,6 +467,7 @@ void main() { ...@@ -472,6 +467,7 @@ void main() {
expect(result.exitCode, 0); expect(result.exitCode, 0);
expect(result.stdout, <Object>[ expect(result.stdout, <Object>[
startsWith('Performing hot reload...'), startsWith('Performing hot reload...'),
'',
'══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════', '══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════',
'The following assertion was thrown during layout:', 'The following assertion was thrown during layout:',
'A RenderFlex overflowed by 69200 pixels on the right.', 'A RenderFlex overflowed by 69200 pixels on the right.',
...@@ -506,7 +502,6 @@ void main() { ...@@ -506,7 +502,6 @@ void main() {
' verticalDirection: down', ' verticalDirection: down',
'◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤', '◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤◢◤',
'════════════════════════════════════════════════════════════════════════════════════════════════════', '════════════════════════════════════════════════════════════════════════════════════════════════════',
'',
startsWith('Reloaded 0 libraries in '), startsWith('Reloaded 0 libraries in '),
'', '',
'Application finished.', 'Application finished.',
......
...@@ -36,7 +36,7 @@ final Map<Type, Generator> _testbedDefaults = <Type, Generator>{ ...@@ -36,7 +36,7 @@ final Map<Type, Generator> _testbedDefaults = <Type, Generator>{
FileSystem: () => MemoryFileSystem(style: globals.platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix), FileSystem: () => MemoryFileSystem(style: globals.platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix),
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
Logger: () => BufferLogger( Logger: () => BufferLogger(
terminal: AnsiTerminal(stdio: globals.stdio, platform: globals.platform), // Danger, using real stdio. terminal: AnsiTerminal(stdio: globals.stdio, platform: globals.platform), // Danger, using real stdio.
outputPreferences: OutputPreferences.test(), outputPreferences: OutputPreferences.test(),
), // Allows reading logs and prevents stdout. ), // Allows reading logs and prevents stdout.
OperatingSystemUtils: () => FakeOperatingSystemUtils(), OperatingSystemUtils: () => FakeOperatingSystemUtils(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment