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

This reverts commit 0636c6fe6026c24763738886db94ba948e7fd5a5.
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 4fa661b..d1a0cc3 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -93,34 +93,29 @@
   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) {
diff --git a/packages/flutter_tools/lib/src/base/logger.dart b/packages/flutter_tools/lib/src/base/logger.dart
index dcab62c..7248c77 100644
--- a/packages/flutter_tools/lib/src/base/logger.dart
+++ b/packages/flutter_tools/lib/src/base/logger.dart
@@ -13,8 +13,6 @@
 
 const int kDefaultStatusPadding = 59;
 
-typedef void VoidCallback();
-
 abstract class Logger {
   bool get isVerbose => false;
 
@@ -55,6 +53,8 @@
   });
 }
 
+typedef void _FinishCallback();
+
 class StdoutLogger extends Logger {
 
   Status _status;
@@ -66,6 +66,7 @@
   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 @@
   }) {
     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 @@
     int progressIndicatorPadding = kDefaultStatusPadding,
   }) {
     printStatus(message);
-    return new Status()..start();
+    return new Status();
   }
 
   /// Clears all buffers.
@@ -238,9 +230,7 @@
     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 @@
 /// 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 });
-
-  /// A straight [Status] or an [AnsiSpinner] (depending on whether the
-  /// terminal is fancy enough), already started.
-  factory Status.withSpinner({ VoidCallback onFinish }) {
-    if (terminal.supportsColor)
-      return new AnsiSpinner(onFinish: onFinish)..start();
-    return new Status(onFinish: onFinish)..start();
-  }
-
-  final VoidCallback onFinish;
+  Status();
 
   bool _isStarted = false;
 
-  /// Call to start spinning.
+  factory Status.withSpinner() {
+    if (terminal.supportsColor)
+      return new AnsiSpinner()..start();
+    return new Status()..start();
+  }
+
+  /// Display summary information for this spinner; called by [stop].
+  void summaryInformation() {}
+
+  /// 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 @@
 /// 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');
     }
   }
 }
diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart
index 716d33c..0e3cb61 100644
--- a/packages/flutter_tools/lib/src/cache.dart
+++ b/packages/flutter_tools/lib/src/cache.dart
@@ -295,15 +295,11 @@
     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);
diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart
index f28a1d2..5bf9bb8 100644
--- a/packages/flutter_tools/lib/src/commands/build_aot.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aot.dart
@@ -74,10 +74,8 @@
     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 @@
             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 @@
             ..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 @@
           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;
     }
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index eaf51f7..202fbf2 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -901,14 +901,7 @@
       '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 _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;
diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart
index e3925f8..b80ba46 100644
--- a/packages/flutter_tools/lib/src/commands/update_packages.dart
+++ b/packages/flutter_tools/lib/src/commands/update_packages.dart
@@ -84,10 +84,7 @@
   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');
diff --git a/packages/flutter_tools/lib/src/dart/pub.dart b/packages/flutter_tools/lib/src/dart/pub.dart
index 90ef41b..d633374 100644
--- a/packages/flutter_tools/lib/src/dart/pub.dart
+++ b/packages/flutter_tools/lib/src/dart/pub.dart
@@ -109,10 +109,8 @@
         failureMessage: 'pub $command failed',
         retry: true,
       );
+    } finally {
       status.stop();
-    } catch (exception) {
-      status.cancel();
-      rethrow;
     }
   }
 
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart
index f97f2ff..9d9dc90 100644
--- a/packages/flutter_tools/lib/src/doctor.dart
+++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -145,13 +145,9 @@
     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) {
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 14b1b91..3ebc8ba 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -299,6 +299,7 @@
         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');
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index a18ea9d..ca16780 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -385,7 +385,7 @@
   }) async {
     final Status devFSStatus = logger.startProgress(
       'Syncing files to device ${device.name}...',
-      expectSlowOperation: true,
+      expectSlowOperation: true
     );
     int bytes = 0;
     try {
@@ -554,9 +554,8 @@
           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 @@
             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 @@
       status.stop();
       printStatus('Screenshot written to ${fs.path.relative(outputFile.path)} (${sizeKB}kB).');
     } catch (error) {
-      status.cancel();
+      status.stop();
       printError('Error taking screenshot: $error');
     }
   }
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index 39ee197..f28cf49 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -497,20 +497,23 @@
     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 @@
         '$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;
     }
   }
 
diff --git a/packages/flutter_tools/test/base/logger_test.dart b/packages/flutter_tools/test/base/logger_test.dart
index 24d0f0f..5fa9778 100644
--- a/packages/flutter_tools/test/base/logger_test.dart
+++ b/packages/flutter_tools/test/base/logger_test.dart
@@ -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 @@
       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 @@
       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(called, equals(1));
       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));
+      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(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 @@
       // 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});
   });
 }