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