Unverified Commit 9c159638 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Revert "Clean up startProgress logic. (#19695)" (#19842)

This reverts commit 0636c6fe.
parent 0636c6fe
...@@ -93,34 +93,29 @@ Future<GradleProject> _readGradleProject() async { ...@@ -93,34 +93,29 @@ Future<GradleProject> _readGradleProject() async {
final FlutterProject flutterProject = new FlutterProject(fs.currentDirectory); final FlutterProject flutterProject = new FlutterProject(fs.currentDirectory);
final String gradle = await _ensureGradle(flutterProject); final String gradle = await _ensureGradle(flutterProject);
await updateLocalProperties(project: flutterProject); await updateLocalProperties(project: flutterProject);
final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true);
GradleProject project;
try { try {
final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true);
final RunResult runResult = await runCheckedAsync( final RunResult runResult = await runCheckedAsync(
<String>[gradle, 'app:properties'], <String>[gradle, 'app:properties'],
workingDirectory: flutterProject.android.directory.path, workingDirectory: flutterProject.android.directory.path,
environment: _gradleEnv, environment: _gradleEnv,
); );
final String properties = runResult.stdout.trim(); final String properties = runResult.stdout.trim();
project = new GradleProject.fromAppProperties(properties); final GradleProject project = new GradleProject.fromAppProperties(properties);
} catch (exception) { status.stop();
return project;
} catch (e) {
if (getFlutterPluginVersion(flutterProject.android) == FlutterPluginVersion.managed) { if (getFlutterPluginVersion(flutterProject.android) == FlutterPluginVersion.managed) {
status.cancel();
// Handle known exceptions. This will exit if handled. // Handle known exceptions. This will exit if handled.
handleKnownGradleExceptions(exception); handleKnownGradleExceptions(e);
// Print a general Gradle error and exit. // Print a general Gradle error and exit.
printError('* Error running Gradle:\n$exception\n'); printError('* Error running Gradle:\n$e\n');
throwToolExit('Please review your Gradle project setup in the android/ folder.'); throwToolExit('Please review your Gradle project setup in the android/ folder.');
} }
// Fall back to the default
project = new GradleProject(
<String>['debug', 'profile', 'release'],
<String>[], flutterProject.android.gradleAppOutV1Directory,
);
} }
status.stop(); // Fall back to the default
return project; return new GradleProject(<String>['debug', 'profile', 'release'], <String>[], flutterProject.android.gradleAppOutV1Directory);
} }
void handleKnownGradleExceptions(String exceptionString) { void handleKnownGradleExceptions(String exceptionString) {
......
...@@ -13,8 +13,6 @@ import 'utils.dart'; ...@@ -13,8 +13,6 @@ import 'utils.dart';
const int kDefaultStatusPadding = 59; const int kDefaultStatusPadding = 59;
typedef void VoidCallback();
abstract class Logger { abstract class Logger {
bool get isVerbose => false; bool get isVerbose => false;
...@@ -55,6 +53,8 @@ abstract class Logger { ...@@ -55,6 +53,8 @@ abstract class Logger {
}); });
} }
typedef void _FinishCallback();
class StdoutLogger extends Logger { class StdoutLogger extends Logger {
Status _status; Status _status;
...@@ -66,6 +66,7 @@ class StdoutLogger extends Logger { ...@@ -66,6 +66,7 @@ class StdoutLogger extends Logger {
void printError(String message, { StackTrace stackTrace, bool emphasis = false }) { void printError(String message, { StackTrace stackTrace, bool emphasis = false }) {
_status?.cancel(); _status?.cancel();
_status = null; _status = null;
if (emphasis) if (emphasis)
message = terminal.bolden(message); message = terminal.bolden(message);
stderr.writeln(message); stderr.writeln(message);
...@@ -108,25 +109,16 @@ class StdoutLogger extends Logger { ...@@ -108,25 +109,16 @@ class StdoutLogger extends Logger {
}) { }) {
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 new Status(onFinish: _clearStatus)..start(); return new Status()..start();
} }
if (terminal.supportsColor) { if (terminal.supportsColor) {
_status = new AnsiStatus( _status = new AnsiStatus(message, expectSlowOperation, () { _status = null; }, progressIndicatorPadding)..start();
message: message,
expectSlowOperation: expectSlowOperation,
padding: progressIndicatorPadding,
onFinish: _clearStatus,
)..start();
} else { } else {
printStatus(message); printStatus(message);
_status = new Status(onFinish: _clearStatus)..start(); _status = new Status()..start();
} }
return _status; return _status;
} }
void _clearStatus() {
_status = null;
}
} }
/// A [StdoutLogger] which replaces Unicode characters that cannot be printed to /// A [StdoutLogger] which replaces Unicode characters that cannot be printed to
...@@ -188,7 +180,7 @@ class BufferLogger extends Logger { ...@@ -188,7 +180,7 @@ class BufferLogger extends Logger {
int progressIndicatorPadding = kDefaultStatusPadding, int progressIndicatorPadding = kDefaultStatusPadding,
}) { }) {
printStatus(message); printStatus(message);
return new Status()..start(); return new Status();
} }
/// Clears all buffers. /// Clears all buffers.
...@@ -238,9 +230,7 @@ class VerboseLogger extends Logger { ...@@ -238,9 +230,7 @@ class VerboseLogger extends Logger {
int progressIndicatorPadding = kDefaultStatusPadding, int progressIndicatorPadding = kDefaultStatusPadding,
}) { }) {
printStatus(message); printStatus(message);
return new Status(onFinish: () { return new Status();
printTrace('$message (completed)');
})..start();
} }
void _emit(_LogType type, String message, [StackTrace stackTrace]) { void _emit(_LogType type, String message, [StackTrace stackTrace]) {
...@@ -285,91 +275,75 @@ enum _LogType { ...@@ -285,91 +275,75 @@ enum _LogType {
/// A [Status] class begins when start is called, and may produce progress /// A [Status] class begins when start is called, and may produce progress
/// information asynchronously. /// information asynchronously.
/// ///
/// The [Status] class itself never has any output. /// When stop is called, summary information supported by this class is printed.
/// /// If cancel is called, no summary information is displayed.
/// The [AnsiSpinner] subclass shows a spinner, and replaces it with a single /// The base class displays nothing at all.
/// space character when stopped or canceled.
///
/// The [AnsiStatus] subclass shows a spinner, and replaces it with timing
/// information when stopped. When canceled, the information isn't shown. In
/// either case, a newline is printed.
///
/// Generally, consider `logger.startProgress` instead of directly creating
/// a [Status] or one of its subclasses.
class Status { class Status {
Status({ this.onFinish }); Status();
/// A straight [Status] or an [AnsiSpinner] (depending on whether the bool _isStarted = false;
/// terminal is fancy enough), already started.
factory Status.withSpinner({ VoidCallback onFinish }) { factory Status.withSpinner() {
if (terminal.supportsColor) if (terminal.supportsColor)
return new AnsiSpinner(onFinish: onFinish)..start(); return new AnsiSpinner()..start();
return new Status(onFinish: onFinish)..start(); return new Status()..start();
} }
final VoidCallback onFinish; /// Display summary information for this spinner; called by [stop].
void summaryInformation() {}
bool _isStarted = false;
/// Call to start spinning. /// Call to start spinning. Call this method via super at the beginning
/// of a subclass [start] method.
void start() { void start() {
assert(!_isStarted);
_isStarted = true; _isStarted = true;
} }
/// Call to stop spinning after success. /// Call to stop spinning and delete the spinner. Print summary information,
/// if applicable to the spinner.
void stop() { void stop() {
assert(_isStarted); if (_isStarted) {
_isStarted = false; cancel();
if (onFinish != null) summaryInformation();
onFinish(); }
} }
/// Call to cancel the spinner after failure or cancelation. /// Call to cancel the spinner without printing any summary output. Call
/// this method via super at the end of a subclass [cancel] method.
void cancel() { void cancel() {
assert(_isStarted);
_isStarted = false; _isStarted = false;
if (onFinish != null)
onFinish();
} }
} }
/// An [AnsiSpinner] is a simple animation that does nothing but implement an /// An [AnsiSpinner] is a simple animation that does nothing but implement an
/// ASCII spinner. When stopped or canceled, the animation erases itself. /// ASCII spinner. When stopped or canceled, the animation erases itself.
class AnsiSpinner extends Status { class AnsiSpinner extends Status {
AnsiSpinner({ VoidCallback onFinish }) : super(onFinish: onFinish);
int ticks = 0; int ticks = 0;
Timer timer; Timer timer;
static final List<String> _progress = <String>[r'-', r'\', r'|', r'/']; static final List<String> _progress = <String>['-', r'\', '|', r'/'];
void _callback(Timer timer) { void _callback(Timer _) {
stdout.write('\b${_progress[ticks++ % _progress.length]}'); stdout.write('\b${_progress[ticks++ % _progress.length]}');
} }
@override @override
void start() { void start() {
super.start(); super.start();
assert(timer == null);
stdout.write(' '); stdout.write(' ');
_callback(null);
timer = new Timer.periodic(const Duration(milliseconds: 100), _callback); timer = new Timer.periodic(const Duration(milliseconds: 100), _callback);
_callback(timer);
}
@override
void stop() {
assert(timer.isActive);
timer.cancel();
stdout.write('\b \b');
super.stop();
} }
@override @override
/// Clears the spinner. After cancel, the cursor will be one space right
/// of where it was when [start] was called (assuming no other input).
void cancel() { void cancel() {
assert(timer.isActive); if (timer?.isActive == true) {
timer.cancel(); timer.cancel();
stdout.write('\b \b'); // Many terminals do not interpret backspace as deleting a character,
// but rather just moving the cursor back one.
stdout.write('\b \b');
}
super.cancel(); super.cancel();
} }
} }
...@@ -379,50 +353,59 @@ class AnsiSpinner extends Status { ...@@ -379,50 +353,59 @@ class AnsiSpinner extends Status {
/// On [stop], will additionally print out summary information in /// On [stop], will additionally print out summary information in
/// milliseconds if [expectSlowOperation] is false, as seconds otherwise. /// milliseconds if [expectSlowOperation] is false, as seconds otherwise.
class AnsiStatus extends AnsiSpinner { class AnsiStatus extends AnsiSpinner {
AnsiStatus({ AnsiStatus(this.message, this.expectSlowOperation, this.onFinish, this.padding);
this.message,
this.expectSlowOperation,
this.padding,
VoidCallback onFinish,
}) : super(onFinish: onFinish);
final String message; final String message;
final bool expectSlowOperation; final bool expectSlowOperation;
final _FinishCallback onFinish;
final int padding; final int padding;
Stopwatch stopwatch; Stopwatch stopwatch;
bool _finished = false;
@override @override
/// Writes [message] to [stdout] with padding, then begins spinning.
void start() { void start() {
stopwatch = new Stopwatch()..start(); stopwatch = new Stopwatch()..start();
stdout.write('${message.padRight(padding)} '); stdout.write('${message.padRight(padding)} ');
assert(!_finished);
super.start(); super.start();
} }
@override @override
/// Calls onFinish.
void stop() { void stop() {
super.stop(); if (!_finished) {
writeSummaryInformation(); onFinish();
stdout.write('\n'); _finished = true;
super.cancel();
summaryInformation();
}
} }
@override @override
void cancel() {
super.cancel();
stdout.write('\n');
}
/// Backs up 4 characters and prints a (minimum) 5 character padded time. If /// Backs up 4 characters and prints a (minimum) 5 character padded time. If
/// [expectSlowOperation] is true, the time is in seconds; otherwise, /// [expectSlowOperation] is true, the time is in seconds; otherwise,
/// milliseconds. Only backs up 4 characters because [super.cancel] backs /// milliseconds. Only backs up 4 characters because [super.cancel] backs
/// up one. /// up one.
/// ///
/// Example: '\b\b\b\b 0.5s', '\b\b\b\b150ms', '\b\b\b\b1600ms' /// Example: '\b\b\b\b 0.5s', '\b\b\b\b150ms', '\b\b\b\b1600ms'
void writeSummaryInformation() { void summaryInformation() {
if (expectSlowOperation) { if (expectSlowOperation) {
stdout.write('\b\b\b\b${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}'); stdout.writeln('\b\b\b\b${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}');
} else { } else {
stdout.write('\b\b\b\b${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}'); stdout.writeln('\b\b\b\b${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}');
}
}
@override
/// Calls [onFinish].
void cancel() {
if (!_finished) {
onFinish();
_finished = true;
super.cancel();
stdout.write('\n');
} }
} }
} }
...@@ -295,15 +295,11 @@ abstract class CachedArtifact { ...@@ -295,15 +295,11 @@ abstract class CachedArtifact {
return _withDownloadFile('${flattenNameSubdirs(url)}', (File tempFile) async { return _withDownloadFile('${flattenNameSubdirs(url)}', (File tempFile) async {
if (!verifier(tempFile)) { if (!verifier(tempFile)) {
final Status status = logger.startProgress(message, expectSlowOperation: true); final Status status = logger.startProgress(message, expectSlowOperation: true);
try { await _downloadFile(url, tempFile).then<Null>((_) {
await _downloadFile(url, tempFile);
status.stop(); status.stop();
} catch (exception) { }).whenComplete(status.cancel);
status.cancel();
rethrow;
}
} else { } else {
logger.printTrace('$message (cached)'); logger.printStatus('$message(cached)');
} }
_ensureExists(location); _ensureExists(location);
extractor(tempFile, location); extractor(tempFile, location);
......
...@@ -74,10 +74,8 @@ class BuildAotCommand extends BuildSubCommand { ...@@ -74,10 +74,8 @@ class BuildAotCommand extends BuildSubCommand {
Status status; Status status;
if (!argResults['quiet']) { if (!argResults['quiet']) {
final String typeName = artifacts.getEngineType(platform, buildMode); final String typeName = artifacts.getEngineType(platform, buildMode);
status = logger.startProgress( status = logger.startProgress('Building AOT snapshot in ${getModeName(getBuildMode())} mode ($typeName)...',
'Building AOT snapshot in ${getModeName(getBuildMode())} mode ($typeName)...', expectSlowOperation: true);
expectSlowOperation: true,
);
} }
final String outputPath = argResults['output-dir'] ?? getAotBuildDirectory(); final String outputPath = argResults['output-dir'] ?? getAotBuildDirectory();
try { try {
...@@ -122,6 +120,8 @@ class BuildAotCommand extends BuildSubCommand { ...@@ -122,6 +120,8 @@ class BuildAotCommand extends BuildSubCommand {
buildSharedLibrary: false, buildSharedLibrary: false,
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions], extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
).then((int buildExitCode) { ).then((int buildExitCode) {
if (buildExitCode != 0)
printError('Snapshotting ($iosArch) exited with non-zero exit code: $buildExitCode');
return buildExitCode; return buildExitCode;
}); });
}); });
...@@ -134,12 +134,6 @@ class BuildAotCommand extends BuildSubCommand { ...@@ -134,12 +134,6 @@ class BuildAotCommand extends BuildSubCommand {
..addAll(dylibs) ..addAll(dylibs)
..addAll(<String>['-create', '-output', fs.path.join(outputPath, 'App.framework', 'App')]), ..addAll(<String>['-create', '-output', fs.path.join(outputPath, 'App.framework', 'App')]),
); );
} else {
status?.cancel();
exitCodes.forEach((IOSArch iosArch, Future<int> exitCodeFuture) async {
final int buildExitCode = await exitCodeFuture;
printError('Snapshotting ($iosArch) exited with non-zero exit code: $buildExitCode');
});
} }
} else { } else {
// Android AOT snapshot. // Android AOT snapshot.
...@@ -154,14 +148,12 @@ class BuildAotCommand extends BuildSubCommand { ...@@ -154,14 +148,12 @@ class BuildAotCommand extends BuildSubCommand {
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions], extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
); );
if (snapshotExitCode != 0) { if (snapshotExitCode != 0) {
status?.cancel();
printError('Snapshotting exited with non-zero exit code: $snapshotExitCode'); printError('Snapshotting exited with non-zero exit code: $snapshotExitCode');
return; return;
} }
} }
} on String catch (error) { } on String catch (error) {
// Catch the String exceptions thrown from the `runCheckedSync` methods below. // Catch the String exceptions thrown from the `runCheckedSync` methods below.
status?.cancel();
printError(error); printError(error);
return; return;
} }
......
...@@ -901,14 +901,7 @@ class _AppRunLogger extends Logger { ...@@ -901,14 +901,7 @@ class _AppRunLogger extends Logger {
'message': message, 'message': message,
}); });
_status = new Status(onFinish: () { _status = new _AppLoggerStatus(this, id, progressId);
_status = null;
_sendProgressEvent(<String, dynamic>{
'id': id.toString(),
'progressId': progressId,
'finished': true
});
});
return _status; return _status;
} }
...@@ -931,6 +924,37 @@ class _AppRunLogger extends Logger { ...@@ -931,6 +924,37 @@ class _AppRunLogger extends Logger {
} }
} }
class _AppLoggerStatus extends Status {
_AppLoggerStatus(this.logger, this.id, this.progressId);
final _AppRunLogger logger;
final int id;
final String progressId;
@override
void start() {}
@override
void stop() {
logger._status = null;
_sendFinished();
}
@override
void cancel() {
logger._status = null;
_sendFinished();
}
void _sendFinished() {
logger._sendProgressEvent(<String, dynamic>{
'id': id.toString(),
'progressId': progressId,
'finished': true
});
}
}
class LogMessage { class LogMessage {
final String level; final String level;
final String message; final String message;
......
...@@ -84,10 +84,7 @@ class UpdatePackagesCommand extends FlutterCommand { ...@@ -84,10 +84,7 @@ class UpdatePackagesCommand extends FlutterCommand {
final bool hidden; final bool hidden;
Future<Null> _downloadCoverageData() async { Future<Null> _downloadCoverageData() async {
final Status status = logger.startProgress( final Status status = logger.startProgress('Downloading lcov data for package:flutter...', expectSlowOperation: true);
'Downloading lcov data for package:flutter...',
expectSlowOperation: true,
);
final String urlBase = platform.environment['FLUTTER_STORAGE_BASE_URL'] ?? 'https://storage.googleapis.com'; final String urlBase = platform.environment['FLUTTER_STORAGE_BASE_URL'] ?? 'https://storage.googleapis.com';
final List<int> data = await fetchUrl(Uri.parse('$urlBase/flutter_infra/flutter/coverage/lcov.info')); final List<int> data = await fetchUrl(Uri.parse('$urlBase/flutter_infra/flutter/coverage/lcov.info'));
final String coverageDir = fs.path.join(Cache.flutterRoot, 'packages/flutter/coverage'); final String coverageDir = fs.path.join(Cache.flutterRoot, 'packages/flutter/coverage');
......
...@@ -109,10 +109,8 @@ Future<Null> pubGet({ ...@@ -109,10 +109,8 @@ Future<Null> pubGet({
failureMessage: 'pub $command failed', failureMessage: 'pub $command failed',
retry: true, retry: true,
); );
} finally {
status.stop(); status.stop();
} catch (exception) {
status.cancel();
rethrow;
} }
} }
......
...@@ -145,13 +145,9 @@ class Doctor { ...@@ -145,13 +145,9 @@ class Doctor {
for (ValidatorTask validatorTask in startValidatorTasks()) { for (ValidatorTask validatorTask in startValidatorTasks()) {
final DoctorValidator validator = validatorTask.validator; final DoctorValidator validator = validatorTask.validator;
final Status status = new Status.withSpinner(); final Status status = new Status.withSpinner();
try { await (validatorTask.result).then<void>((_) {
await validatorTask.result; status.stop();
} catch (exception) { }).whenComplete(status.cancel);
status.cancel();
rethrow;
}
status.stop();
final ValidationResult result = await validatorTask.result; final ValidationResult result = await validatorTask.result;
if (result.type == ValidationType.missing) { if (result.type == ValidationType.missing) {
......
...@@ -299,6 +299,7 @@ class IOSDevice extends Device { ...@@ -299,6 +299,7 @@ class IOSDevice extends Device {
bundlePath: bundle.path, bundlePath: bundle.path,
launchArguments: launchArguments, launchArguments: launchArguments,
); );
installStatus.stop();
} else { } else {
// Debugging is enabled, look for the observatory server port post launch. // Debugging is enabled, look for the observatory server port post launch.
printTrace('Debugging is enabled, connecting to observatory'); printTrace('Debugging is enabled, connecting to observatory');
......
...@@ -385,7 +385,7 @@ class FlutterDevice { ...@@ -385,7 +385,7 @@ class FlutterDevice {
}) async { }) async {
final Status devFSStatus = logger.startProgress( final Status devFSStatus = logger.startProgress(
'Syncing files to device ${device.name}...', 'Syncing files to device ${device.name}...',
expectSlowOperation: true, expectSlowOperation: true
); );
int bytes = 0; int bytes = 0;
try { try {
...@@ -554,9 +554,8 @@ abstract class ResidentRunner { ...@@ -554,9 +554,8 @@ abstract class ResidentRunner {
for (FlutterView view in device.views) for (FlutterView view in device.views)
await view.uiIsolate.flutterDebugAllowBanner(false); await view.uiIsolate.flutterDebugAllowBanner(false);
} catch (error) { } catch (error) {
status.cancel(); status.stop();
printError('Error communicating with Flutter on the device: $error'); printError('Error communicating with Flutter on the device: $error');
return;
} }
} }
try { try {
...@@ -567,9 +566,8 @@ abstract class ResidentRunner { ...@@ -567,9 +566,8 @@ abstract class ResidentRunner {
for (FlutterView view in device.views) for (FlutterView view in device.views)
await view.uiIsolate.flutterDebugAllowBanner(true); await view.uiIsolate.flutterDebugAllowBanner(true);
} catch (error) { } catch (error) {
status.cancel(); status.stop();
printError('Error communicating with Flutter on the device: $error'); printError('Error communicating with Flutter on the device: $error');
return;
} }
} }
} }
...@@ -577,7 +575,7 @@ abstract class ResidentRunner { ...@@ -577,7 +575,7 @@ abstract class ResidentRunner {
status.stop(); status.stop();
printStatus('Screenshot written to ${fs.path.relative(outputFile.path)} (${sizeKB}kB).'); printStatus('Screenshot written to ${fs.path.relative(outputFile.path)} (${sizeKB}kB).');
} catch (error) { } catch (error) {
status.cancel(); status.stop();
printError('Error taking screenshot: $error'); printError('Error taking screenshot: $error');
} }
} }
......
...@@ -497,20 +497,23 @@ class HotRunner extends ResidentRunner { ...@@ -497,20 +497,23 @@ class HotRunner extends ResidentRunner {
if (fullRestart) { if (fullRestart) {
final Status status = logger.startProgress( final Status status = logger.startProgress(
'Performing hot restart...', 'Performing hot restart...',
progressId: 'hot.restart', progressId: 'hot.restart'
); );
try { try {
final Stopwatch timer = new Stopwatch()..start();
if (!(await hotRunnerConfig.setupHotRestart())) { if (!(await hotRunnerConfig.setupHotRestart())) {
status.cancel(); status.cancel();
return new OperationResult(1, 'setupHotRestart failed'); return new OperationResult(1, 'setupHotRestart failed');
} }
await _restartFromSources(); await _restartFromSources();
timer.stop();
status.cancel();
printStatus('Restarted app in ${getElapsedAsMilliseconds(timer.elapsed)}.');
return OperationResult.ok;
} catch (error) { } catch (error) {
status.cancel(); status.cancel();
rethrow; rethrow;
} }
status.stop(); // Prints timing information.
return OperationResult.ok;
} else { } else {
final bool reloadOnTopOfSnapshot = _runningFromSnapshot; final bool reloadOnTopOfSnapshot = _runningFromSnapshot;
final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing'; final String progressPrefix = reloadOnTopOfSnapshot ? 'Initializing' : 'Performing';
...@@ -518,21 +521,20 @@ class HotRunner extends ResidentRunner { ...@@ -518,21 +521,20 @@ class HotRunner extends ResidentRunner {
'$progressPrefix hot reload...', '$progressPrefix hot reload...',
progressId: 'hot.reload' progressId: 'hot.reload'
); );
final Stopwatch timer = new Stopwatch()..start();
OperationResult result;
try { try {
result = await _reloadSources(pause: pauseAfterRestart); final Stopwatch timer = new Stopwatch()..start();
final OperationResult result = await _reloadSources(pause: pauseAfterRestart);
timer.stop();
status.cancel();
if (result.isOk)
printStatus('${result.message} in ${getElapsedAsMilliseconds(timer.elapsed)}.');
if (result.hintMessage != null)
printStatus('\n${result.hintMessage}');
return result;
} catch (error) { } catch (error) {
status?.cancel(); status.cancel();
rethrow; rethrow;
} }
timer.stop();
status.cancel(); // Do not show summary information, since we show it in more detail below.
if (result.isOk)
printStatus('${result.message} in ${getElapsedAsMilliseconds(timer.elapsed)}.');
if (result.hintMessage != null)
printStatus('\n${result.hintMessage}');
return result;
} }
} }
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter_tools/src/base/context.dart';
import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
...@@ -41,27 +40,23 @@ void main() { ...@@ -41,27 +40,23 @@ void main() {
mockStdio = new MockStdio(); mockStdio = new MockStdio();
ansiSpinner = new AnsiSpinner(); ansiSpinner = new AnsiSpinner();
called = 0; called = 0;
ansiStatus = new AnsiStatus( ansiStatus = new AnsiStatus('Hello world', true, () => called++, 20);
message: 'Hello world',
expectSlowOperation: true,
padding: 20,
onFinish: () => called++,
);
}); });
List<String> outputLines() => mockStdio.writtenToStdout.join('').split('\n'); List<String> outputLines() => mockStdio.writtenToStdout.join('').split('\n');
Future<void> doWhileAsync(bool doThis()) async { Future<void> doWhile(bool doThis()) async {
return Future.doWhile(() { return Future.doWhile(() async {
// We want to let other tasks run at the same time, so we schedule these // Future.doWhile() isn't enough by itself, because the VM never gets
// using a timer rather than a microtask. // around to scheduling the other tasks for some reason.
return Future<bool>.delayed(Duration.zero, doThis); await new Future<void>.delayed(const Duration(milliseconds: 0));
return doThis();
}); });
} }
testUsingContext('AnsiSpinner works', () async { testUsingContext('AnsiSpinner works', () async {
ansiSpinner.start(); ansiSpinner.start();
await doWhileAsync(() => ansiSpinner.ticks < 10); await doWhile(() => ansiSpinner.ticks < 10);
List<String> lines = outputLines(); List<String> lines = outputLines();
expect(lines[0], startsWith(' \b-\b\\\b|\b/\b-\b\\\b|\b/')); expect(lines[0], startsWith(' \b-\b\\\b|\b/\b-\b\\\b|\b/'));
expect(lines[0].endsWith('\n'), isFalse); expect(lines[0].endsWith('\n'), isFalse);
...@@ -71,37 +66,44 @@ void main() { ...@@ -71,37 +66,44 @@ void main() {
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 multiple times doesn't clear multiple times.
expect(() { ansiSpinner.stop(); }, throwsA(const isInstanceOf<AssertionError>())); ansiSpinner.stop();
expect(() { ansiSpinner.cancel(); }, throwsA(const isInstanceOf<AssertionError>())); lines = outputLines();
expect(lines[0].endsWith('\b \b '), isFalse);
expect(lines.length, equals(1));
ansiSpinner.cancel();
lines = outputLines();
expect(lines[0].endsWith('\b \b '), isFalse);
expect(lines.length, equals(1));
}, overrides: <Type, Generator>{Stdio: () => mockStdio}); }, overrides: <Type, Generator>{Stdio: () => mockStdio});
testUsingContext('AnsiStatus works when cancelled', () async { testUsingContext('AnsiStatus works when cancelled', () async {
ansiStatus.start(); ansiStatus.start();
await doWhileAsync(() => ansiStatus.ticks < 10); await doWhile(() => ansiStatus.ticks < 10);
List<String> lines = outputLines(); List<String> lines = outputLines();
expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-')); expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
expect(lines.length, equals(1));
expect(lines[0].endsWith('\n'), isFalse); expect(lines[0].endsWith('\n'), isFalse);
expect(lines.length, equals(1));
// Verify a cancel does _not_ print the time and prints a newline.
ansiStatus.cancel(); ansiStatus.cancel();
lines = outputLines(); lines = outputLines();
final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
expect(matches, isEmpty);
expect(lines[0], endsWith('\b \b')); expect(lines[0], endsWith('\b \b'));
expect(lines.length, equals(2));
expect(called, equals(1));
ansiStatus.cancel();
lines = outputLines();
expect(lines[0].endsWith('\b \b\b \b'), isFalse);
expect(lines.length, equals(2));
expect(called, equals(1)); expect(called, equals(1));
ansiStatus.stop();
lines = outputLines();
expect(lines[0].endsWith('\b \b\b \b'), isFalse);
expect(lines.length, equals(2)); expect(lines.length, equals(2));
expect(lines[1], equals('')); expect(called, equals(1));
// Verify that stopping or canceling multiple times throws.
expect(() { ansiStatus.cancel(); }, throwsA(const isInstanceOf<AssertionError>()));
expect(() { ansiStatus.stop(); }, throwsA(const isInstanceOf<AssertionError>()));
}, overrides: <Type, Generator>{Stdio: () => mockStdio}); }, overrides: <Type, Generator>{Stdio: () => mockStdio});
testUsingContext('AnsiStatus works when stopped', () async { testUsingContext('AnsiStatus works when stopped', () async {
ansiStatus.start(); ansiStatus.start();
await doWhileAsync(() => ansiStatus.ticks < 10); await doWhile(() => ansiStatus.ticks < 10);
List<String> lines = outputLines(); List<String> lines = outputLines();
expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-')); expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
expect(lines.length, equals(1)); expect(lines.length, equals(1));
...@@ -109,55 +111,55 @@ void main() { ...@@ -109,55 +111,55 @@ void main() {
// Verify a stop prints the time. // Verify a stop prints the time.
ansiStatus.stop(); ansiStatus.stop();
lines = outputLines(); lines = outputLines();
final List<Match> matches = secondDigits.allMatches(lines[0]).toList(); List<Match> matches = secondDigits.allMatches(lines[0]).toList();
expect(matches, isNotNull); expect(matches, isNotNull);
expect(matches, hasLength(1)); expect(matches, hasLength(1));
final Match match = matches.first; Match match = matches.first;
expect(lines[0], endsWith(match.group(0))); expect(lines[0], endsWith(match.group(0)));
final String initialTime = match.group(0);
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 stopping more than once generates no additional output.
expect(() { ansiStatus.stop(); }, throwsA(const isInstanceOf<AssertionError>())); ansiStatus.stop();
expect(() { ansiStatus.cancel(); }, throwsA(const isInstanceOf<AssertionError>())); lines = outputLines();
matches = secondDigits.allMatches(lines[0]).toList();
expect(matches, hasLength(1));
match = matches.first;
expect(lines[0], endsWith(initialTime));
expect(called, equals(1));
expect(lines.length, equals(2));
expect(lines[1], equals(''));
}, overrides: <Type, Generator>{Stdio: () => mockStdio}); }, overrides: <Type, Generator>{Stdio: () => mockStdio});
testUsingContext('sequential startProgress calls with StdoutLogger', () async { testUsingContext('AnsiStatus works when cancelled', () async {
context[Logger].startProgress('AAA')..stop(); ansiStatus.start();
context[Logger].startProgress('BBB')..stop(); await doWhile(() => ansiStatus.ticks < 10);
expect(outputLines(), <String>[ List<String> lines = outputLines();
'AAA', expect(lines[0], startsWith('Hello world \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
'BBB', expect(lines.length, equals(1));
'',
]);
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Logger: () => new StdoutLogger(),
});
testUsingContext('sequential startProgress calls with VerboseLogger and StdoutLogger', () async { // Verify a cancel does _not_ print the time and prints a newline.
context[Logger].startProgress('AAA')..stop(); ansiStatus.cancel();
context[Logger].startProgress('BBB')..stop(); lines = outputLines();
expect(outputLines(), <String>[ List<Match> matches = secondDigits.allMatches(lines[0]).toList();
'[ ] AAA', expect(matches, isEmpty);
'[ ] AAA (completed)', expect(lines[0], endsWith('\b \b'));
'[ ] BBB', expect(called, equals(1));
'[ ] BBB (completed)', // TODO(jcollins-g): Consider having status objects print the newline
'' // when canceled, or never printing a newline at all.
]); expect(lines.length, equals(2));
}, overrides: <Type, Generator>{
Stdio: () => mockStdio,
Logger: () => new VerboseLogger(new StdoutLogger()),
});
testUsingContext('sequential startProgress calls with BufferLogger', () async { // Verifying calling stop after cancel doesn't print anything weird.
context[Logger].startProgress('AAA')..stop(); ansiStatus.stop();
context[Logger].startProgress('BBB')..stop(); lines = outputLines();
final BufferLogger logger = context[Logger]; matches = secondDigits.allMatches(lines[0]).toList();
expect(logger.statusText, 'AAA\nBBB\n'); expect(matches, isEmpty);
}, overrides: <Type, Generator>{ expect(lines[0], endsWith('\b \b'));
Logger: () => new BufferLogger(), expect(called, equals(1));
}); expect(lines[0], isNot(endsWith('\b \b\b \b')));
expect(lines.length, equals(2));
}, overrides: <Type, Generator>{Stdio: () => mockStdio});
}); });
} }
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