Simplify Gradle compiler output. (#21760)

This changes the compiler output for gradle to be less verbose and more easily read.

This only applies to compilation error messages: other gradle messages will continue to print as before.

It also fixes a small problem with the performance measurement printing (see that "7.1s" on it's own line in the original?) so that if something is expected to have multiple lines of output, it prints an initial line, and a "Done" line with the elapsed time, so that it's possible to know what the time applies to.

It also updates the spinner to be fancier, at least on platforms other than Windows (which is missing a lot of symbols in its console font).

Addresses #17307
diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle
index 6f4cbe3..390bb2d 100644
--- a/packages/flutter_tools/gradle/flutter.gradle
+++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -45,6 +45,13 @@
     private File dynamicProfileFlutterJar
     private File dynamicReleaseFlutterJar
 
+    // The name prefix for flutter builds.  This is used to identify gradle tasks
+    // where we expect the flutter tool to provide any error output, and skip the
+    // standard Gradle error output in the FlutterEventLogger. If you change this,
+    // be sure to change any instances of this string in symbols in the code below
+    // to match.
+    static final String flutterBuildPrefix = "flutterBuild"
+
     private Properties readPropertiesIfExist(File propertiesFile) {
         Properties result = new Properties()
         if (propertiesFile.exists()) {
@@ -152,7 +159,7 @@
 
             // Add x86/x86_64 native library. Debug mode only, for now.
             flutterX86Jar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/flutter-x86.jar")
-            Task flutterX86JarTask = project.tasks.create("flutterBuildX86Jar", Jar) {
+            Task flutterX86JarTask = project.tasks.create("${flutterBuildPrefix}X86Jar", Jar) {
                 destinationDir flutterX86Jar.parentFile
                 archiveName flutterX86Jar.name
                 from("${flutterRoot}/bin/cache/artifacts/engine/android-x86/libflutter.so") {
@@ -319,7 +326,7 @@
 
         def addFlutterDeps = { variant ->
             String flutterBuildMode = buildModeFor(variant.buildType)
-            if (flutterBuildMode == 'debug' && project.tasks.findByName('flutterBuildX86Jar')) {
+            if (flutterBuildMode == 'debug' && project.tasks.findByName('${flutterBuildPrefix}X86Jar')) {
                 Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac")
                 if (task) {
                     task.dependsOn project.flutterBuildX86Jar
@@ -330,7 +337,7 @@
                 }
             }
 
-            FlutterTask flutterTask = project.tasks.create(name: "flutterBuild${variant.name.capitalize()}", type: FlutterTask) {
+            FlutterTask flutterTask = project.tasks.create(name: "${flutterBuildPrefix}${variant.name.capitalize()}", type: FlutterTask) {
                 flutterRoot this.flutterRoot
                 flutterExecutable this.flutterExecutable
                 buildMode flutterBuildMode
@@ -584,3 +591,23 @@
         buildBundle()
     }
 }
+
+gradle.useLogger(new FlutterEventLogger())
+
+class FlutterEventLogger extends BuildAdapter implements TaskExecutionListener {
+    String mostRecentTask = ""
+
+    void beforeExecute(Task task) {
+        mostRecentTask = task.name
+    }
+
+    void afterExecute(Task task, TaskState state) {}
+
+    void buildFinished(BuildResult result) {
+        if (result.failure != null) {
+            if (!(result.failure instanceof GradleException) || !mostRecentTask.startsWith(FlutterPlugin.flutterBuildPrefix)) {
+                result.rethrowFailure()
+            }
+        }
+    }
+}
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index e5bdc36..8fc2620 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -299,7 +299,11 @@
 
 Future<Null> _buildGradleProjectV1(FlutterProject project, String gradle) async {
   // Run 'gradlew build'.
-  final Status status = logger.startProgress('Running \'gradlew build\'...', expectSlowOperation: true);
+  final Status status = logger.startProgress(
+    "Running 'gradlew build'...",
+    expectSlowOperation: true,
+    multilineOutput: true,
+  );
   final int exitCode = await runCommandAndStreamOutput(
     <String>[fs.file(gradle).absolute.path, 'build'],
     workingDirectory: project.android.hostAppGradleRoot.path,
@@ -337,7 +341,11 @@
       throwToolExit('Gradle build aborted.');
     }
   }
-  final Status status = logger.startProgress('Running \'gradlew $assembleTask\'...', expectSlowOperation: true);
+  final Status status = logger.startProgress(
+    "Gradle task '$assembleTask'...",
+    expectSlowOperation: true,
+    multilineOutput: true,
+  );
   final String gradlePath = fs.file(gradle).absolute.path;
   final List<String> command = <String>[gradlePath];
   if (logger.isVerbose) {
@@ -382,7 +390,7 @@
   status.stop();
 
   if (exitCode != 0)
-    throwToolExit('Gradle build failed: $exitCode', exitCode: exitCode);
+    throwToolExit('Gradle task $assembleTask failed with exit code $exitCode', exitCode: exitCode);
 
   final File apkFile = _findApkFile(project, buildInfo);
   if (apkFile == null)
diff --git a/packages/flutter_tools/lib/src/base/io.dart b/packages/flutter_tools/lib/src/base/io.dart
index b26e20e..fb9be51 100644
--- a/packages/flutter_tools/lib/src/base/io.dart
+++ b/packages/flutter_tools/lib/src/base/io.dart
@@ -155,6 +155,11 @@
   Stream<List<int>> get stdin => io.stdin;
   io.IOSink get stdout => io.stdout;
   io.IOSink get stderr => io.stderr;
+
+  bool get hasTerminal => io.stdout.hasTerminal;
+  int get terminalColumns => hasTerminal ? io.stdout.terminalColumns : null;
+  int get terminalLines => hasTerminal ? io.stdout.terminalLines : null;
+  bool get supportsAnsiEscapes => hasTerminal ? io.stdout.supportsAnsiEscapes : false;
 }
 
 io.IOSink get stderr => context[Stdio].stderr;
@@ -162,3 +167,5 @@
 Stream<List<int>> get stdin => context[Stdio].stdin;
 
 io.IOSink get stdout => context[Stdio].stdout;
+
+Stdio get stdio => context[Stdio];
diff --git a/packages/flutter_tools/lib/src/base/logger.dart b/packages/flutter_tools/lib/src/base/logger.dart
index e97d222..9f8cf7d 100644
--- a/packages/flutter_tools/lib/src/base/logger.dart
+++ b/packages/flutter_tools/lib/src/base/logger.dart
@@ -8,6 +8,7 @@
 import 'package:meta/meta.dart';
 
 import 'io.dart';
+import 'platform.dart';
 import 'terminal.dart';
 import 'utils.dart';
 
@@ -25,6 +26,8 @@
     terminal.supportsColor = value;
   }
 
+  bool get hasTerminal => stdio.hasTerminal;
+
   /// Display an error level message to the user. Commands should use this if they
   /// fail in some way.
   void printError(
@@ -62,6 +65,7 @@
     String message, {
     String progressId,
     bool expectSlowOperation,
+    bool multilineOutput,
     int progressIndicatorPadding,
   });
 }
@@ -129,6 +133,7 @@
     String message, {
     String progressId,
     bool expectSlowOperation,
+    bool multilineOutput,
     int progressIndicatorPadding,
   }) {
     expectSlowOperation ??= false;
@@ -141,6 +146,7 @@
       _status = AnsiStatus(
         message: message,
         expectSlowOperation: expectSlowOperation,
+        multilineOutput: multilineOutput,
         padding: progressIndicatorPadding,
         onFinish: _clearStatus,
       )..start();
@@ -223,6 +229,7 @@
     String message, {
     String progressId,
     bool expectSlowOperation,
+    bool multilineOutput,
     int progressIndicatorPadding,
   }) {
     printStatus(message);
@@ -280,6 +287,7 @@
     String message, {
     String progressId,
     bool expectSlowOperation,
+    bool multilineOutput,
     int progressIndicatorPadding,
   }) {
     printStatus(message);
@@ -366,7 +374,7 @@
       onFinish();
   }
 
-  /// Call to cancel the spinner after failure or cancelation.
+  /// Call to cancel the spinner after failure or cancellation.
   void cancel() {
     assert(_isStarted);
     _isStarted = false;
@@ -375,18 +383,25 @@
   }
 }
 
-/// An [AnsiSpinner] is a simple animation that does nothing but implement an
-/// ASCII spinner. When stopped or canceled, the animation erases itself.
+/// An [AnsiSpinner] is a simple animation that does nothing but implement a
+/// ASCII/Unicode 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'/'];
+  // Windows console font has a limited set of Unicode characters.
+  List<String> get _animation => platform.isWindows
+      ? <String>[r'-', r'\', r'|', r'/']
+      : <String>['🌕', '🌖', '🌗', '🌘', '🌑', '🌒', '🌓', '🌔'];
+
+  String get _backspace => '\b' * _animation[0].length;
+  String get _clear => ' ' *  _animation[0].length;
 
   void _callback(Timer timer) {
-    stdout.write('\b${_progress[ticks++ % _progress.length]}');
+    stdout.write('$_backspace${_animation[ticks++ % _animation.length]}');
   }
 
   @override
@@ -402,7 +417,7 @@
   void stop() {
     assert(timer.isActive);
     timer.cancel();
-    stdout.write('\b \b');
+    stdout.write('$_backspace$_clear$_backspace');
     super.stop();
   }
 
@@ -410,7 +425,7 @@
   void cancel() {
     assert(timer.isActive);
     timer.cancel();
-    stdout.write('\b \b');
+    stdout.write('$_backspace$_clear$_backspace');
     super.cancel();
   }
 }
@@ -423,23 +438,29 @@
   AnsiStatus({
     String message,
     bool expectSlowOperation,
+    bool multilineOutput,
     int padding,
     VoidCallback onFinish,
   })  : message = message ?? '',
         padding = padding ?? 0,
         expectSlowOperation = expectSlowOperation ?? false,
+        multilineOutput = multilineOutput ?? false,
         super(onFinish: onFinish);
 
   final String message;
   final bool expectSlowOperation;
+  final bool multilineOutput;
   final int padding;
 
   Stopwatch stopwatch;
 
+  static const String _margin = '     ';
+
   @override
   void start() {
+    assert(stopwatch == null || !stopwatch.isRunning);
     stopwatch = Stopwatch()..start();
-    stdout.write('${message.padRight(padding)}     ');
+    stdout.write('${message.padRight(padding)}$_margin');
     super.start();
   }
 
@@ -456,17 +477,23 @@
     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.
+  /// Print summary information when a task is done.
   ///
-  /// Example: '\b\b\b\b 0.5s', '\b\b\b\b150ms', '\b\b\b\b1600ms'
+  /// If [multilineOutput] is false, 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.
+  ///
+  /// If [multilineOutput] is true, then it prints the message again on a new
+  /// line before writing the elapsed time, and doesn't back up at all.
   void writeSummaryInformation() {
+    final String prefix = multilineOutput
+        ? '\n${'$message Done'.padRight(padding - 4)}$_margin'
+        : '\b\b\b\b';
     if (expectSlowOperation) {
-      stdout.write('\b\b\b\b${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}');
+      stdout.write('$prefix${getElapsedAsSeconds(stopwatch.elapsed).padLeft(5)}');
     } else {
-      stdout.write('\b\b\b\b${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}');
+      stdout.write('$prefix${getElapsedAsMilliseconds(stopwatch.elapsed).padLeft(5)}');
     }
   }
 }
diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart
index be3b2b6..6189f2c 100644
--- a/packages/flutter_tools/lib/src/commands/daemon.dart
+++ b/packages/flutter_tools/lib/src/commands/daemon.dart
@@ -772,6 +772,7 @@
     String message, {
     String progressId,
     bool expectSlowOperation = false,
+    bool multilineOutput,
     int progressIndicatorPadding = kDefaultStatusPadding,
   }) {
     printStatus(message);
@@ -928,6 +929,7 @@
     String message, {
     String progressId,
     bool expectSlowOperation = false,
+    bool multilineOutput,
     int progressIndicatorPadding = 52,
   }) {
     final int id = _nextProgressId++;
diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart
index 18c7666..c2b0352 100644
--- a/packages/flutter_tools/lib/src/compile.dart
+++ b/packages/flutter_tools/lib/src/compile.dart
@@ -32,30 +32,35 @@
     reset();
   }
 
+  bool compilerMessageReceived = false;
   final CompilerMessageConsumer consumer;
   String boundaryKey;
   Completer<CompilerOutput> compilerOutput;
 
   bool _suppressCompilerMessages;
 
-  void handler(String string) {
+  void handler(String message) {
     const String kResultPrefix = 'result ';
     if (boundaryKey == null) {
-      if (string.startsWith(kResultPrefix))
-        boundaryKey = string.substring(kResultPrefix.length);
-    } else if (string.startsWith(boundaryKey)) {
-      if (string.length <= boundaryKey.length) {
+      if (message.startsWith(kResultPrefix))
+        boundaryKey = message.substring(kResultPrefix.length);
+    } else if (message.startsWith(boundaryKey)) {
+      if (message.length <= boundaryKey.length) {
         compilerOutput.complete(null);
         return;
       }
-      final int spaceDelimiter = string.lastIndexOf(' ');
+      final int spaceDelimiter = message.lastIndexOf(' ');
       compilerOutput.complete(
         CompilerOutput(
-          string.substring(boundaryKey.length + 1, spaceDelimiter),
-          int.parse(string.substring(spaceDelimiter + 1).trim())));
+          message.substring(boundaryKey.length + 1, spaceDelimiter),
+          int.parse(message.substring(spaceDelimiter + 1).trim())));
     }
     else if (!_suppressCompilerMessages) {
-      consumer('compiler message: $string');
+      if (compilerMessageReceived == false) {
+        consumer('\nCompiler message:');
+        compilerMessageReceived = true;
+      }
+      consumer(message);
     }
   }
 
@@ -63,6 +68,7 @@
   // with its own boundary key and new completer.
   void reset({bool suppressCompilerMessages = false}) {
     boundaryKey = null;
+    compilerMessageReceived = false;
     compilerOutput = Completer<CompilerOutput>();
     _suppressCompilerMessages = suppressCompilerMessages;
   }
@@ -174,7 +180,7 @@
 
     server.stderr
       .transform(utf8.decoder)
-      .listen((String s) { printError('compiler message: $s'); });
+      .listen((String message) { printError(message); });
     server.stdout
       .transform(utf8.decoder)
       .transform(const LineSplitter())
@@ -242,7 +248,7 @@
 /// restarts the Flutter app.
 class ResidentCompiler {
   ResidentCompiler(this._sdkRoot, {bool trackWidgetCreation = false,
-      String packagesPath, List<String> fileSystemRoots, String fileSystemScheme ,
+      String packagesPath, List<String> fileSystemRoots, String fileSystemScheme,
       CompilerMessageConsumer compilerMessageConsumer = printError,
       String initializeFromDill})
     : assert(_sdkRoot != null),
@@ -381,7 +387,7 @@
     _server.stderr
       .transform(utf8.decoder)
       .transform(const LineSplitter())
-      .listen((String s) { printError('compiler message: $s'); });
+      .listen((String message) { printError(message); });
 
     _server.stdin.writeln('compile $scriptFilename');
 
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 5cbac19..53c822c 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -413,7 +413,7 @@
   Status initialBuildStatus;
   Directory tempDir;
 
-  if (logger.supportsColor) {
+  if (logger.hasTerminal) {
     tempDir = fs.systemTempDirectory.createTempSync('flutter_build_log_pipe.');
     final File scriptOutputPipeFile = tempDir.childFile('pipe_to_stdout');
     os.makePipe(scriptOutputPipeFile.path);
diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart
index 28572f7..8f45cbd 100644
--- a/packages/flutter_tools/lib/src/test/flutter_platform.dart
+++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart
@@ -217,7 +217,7 @@
       if (suppressOutput)
         return;
 
-      if (message.startsWith('compiler message: Error: Could not resolve the package \'test\'')) {
+      if (message.startsWith('Error: Could not resolve the package \'test\'')) {
         printTrace(message);
         printError(
           '\n\nFailed to load test harness. Are you missing a dependency on flutter_test?\n',
diff --git a/packages/flutter_tools/test/base/logger_test.dart b/packages/flutter_tools/test/base/logger_test.dart
index b9d19db..fef9a25 100644
--- a/packages/flutter_tools/test/base/logger_test.dart
+++ b/packages/flutter_tools/test/base/logger_test.dart
@@ -7,6 +7,7 @@
 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:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/base/terminal.dart';
 
 import '../src/common.dart';
@@ -60,6 +61,7 @@
     AnsiStatus ansiStatus;
     SummaryStatus summaryStatus;
     int called;
+    const List<String> testPlatforms = <String>['linux', 'macos', 'windows', 'fuchsia'];
     final RegExp secondDigits = RegExp(r'[^\b]\b\b\b\b\b[0-9]+[.][0-9]+(?:s|ms)');
 
     setUp(() {
@@ -91,22 +93,105 @@
       });
     }
 
-    testUsingContext('AnsiSpinner works', () async {
-      ansiSpinner.start();
-      await doWhileAsync(() => ansiSpinner.ticks < 10);
-      List<String> lines = outputStdout();
-      expect(lines[0], startsWith(' \b-\b\\\b|\b/\b-\b\\\b|\b/'));
-      expect(lines[0].endsWith('\n'), isFalse);
-      expect(lines.length, equals(1));
-      ansiSpinner.stop();
-      lines = outputStdout();
-      expect(lines[0], endsWith('\b \b'));
-      expect(lines.length, equals(1));
+    for (String testOs in testPlatforms) {
+      testUsingContext('AnsiSpinner works for $testOs', () async {
+        ansiSpinner.start();
+        await doWhileAsync(() => ansiSpinner.ticks < 10);
+        List<String> lines = outputStdout();
+        expect(lines[0], startsWith(platform.isWindows
+            ? ' \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.length, equals(1));
+        ansiSpinner.stop();
+        lines = outputStdout();
+        expect(lines[0], endsWith(platform.isWindows ? '\b \b' : '\b\b  \b\b'));
+        expect(lines.length, equals(1));
 
-      // Verify that stopping or canceling multiple times throws.
-      expect(() { ansiSpinner.stop(); }, throwsA(isInstanceOf<AssertionError>()));
-      expect(() { ansiSpinner.cancel(); }, throwsA(isInstanceOf<AssertionError>()));
-    }, overrides: <Type, Generator>{Stdio: () => mockStdio});
+        // Verify that stopping or canceling multiple times throws.
+        expect(() {
+          ansiSpinner.stop();
+        }, throwsA(isInstanceOf<AssertionError>()));
+        expect(() {
+          ansiSpinner.cancel();
+        }, throwsA(isInstanceOf<AssertionError>()));
+      }, overrides: <Type, Generator>{
+        Stdio: () => mockStdio,
+        Platform: () => FakePlatform(operatingSystem: testOs),
+      });
+
+      testUsingContext('Stdout startProgress handle null inputs on colored terminal for $testOs', () async {
+        context[Logger].startProgress(null, progressId: null,
+          expectSlowOperation: null,
+          progressIndicatorPadding: null,
+        );
+        final List<String> lines = outputStdout();
+        expect(outputStderr().length, equals(1));
+        expect(outputStderr().first, isEmpty);
+        expect(lines[0], matches(platform.isWindows ? r'[ ]{64} [\b]-' : r'[ ]{64} [\b][\b]🌕'));
+      }, overrides: <Type, Generator>{
+        Stdio: () => mockStdio,
+        Platform: () => FakePlatform(operatingSystem: testOs),
+        Logger: () => StdoutLogger()..supportsColor = true,
+      });
+
+      testUsingContext('AnsiStatus works when cancelled for $testOs', () async {
+        ansiStatus.start();
+        await doWhileAsync(() => ansiStatus.ticks < 10);
+        List<String> lines = outputStdout();
+        expect(lines[0], startsWith(platform.isWindows
+            ? 'Hello world               \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🌖'));
+        expect(lines.length, equals(1));
+        expect(lines[0].endsWith('\n'), isFalse);
+
+        // Verify a cancel does _not_ print the time and prints a newline.
+        ansiStatus.cancel();
+        lines = outputStdout();
+        final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
+        expect(matches, isEmpty);
+        expect(lines[0], endsWith(platform.isWindows ? '\b \b' : '\b\b  \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(isInstanceOf<AssertionError>()));
+        expect(() { ansiStatus.stop(); }, throwsA(isInstanceOf<AssertionError>()));
+      }, overrides: <Type, Generator>{
+        Stdio: () => mockStdio,
+        Platform: () => FakePlatform(operatingSystem: testOs),
+      });
+
+      testUsingContext('AnsiStatus works when stopped for $testOs', () async {
+        ansiStatus.start();
+        await doWhileAsync(() => ansiStatus.ticks < 10);
+        List<String> lines = outputStdout();
+        expect(lines[0], startsWith(platform.isWindows
+            ? 'Hello world               \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🌖'));
+        expect(lines.length, equals(1));
+
+        // Verify a stop prints the time.
+        ansiStatus.stop();
+        lines = outputStdout();
+        final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
+        expect(matches, isNotNull);
+        expect(matches, hasLength(1));
+        final Match match = matches.first;
+        expect(lines[0], endsWith(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(isInstanceOf<AssertionError>()));
+        expect(() { ansiStatus.cancel(); }, throwsA(isInstanceOf<AssertionError>()));
+      }, overrides: <Type, Generator>{
+        Stdio: () => mockStdio,
+        Platform: () => FakePlatform(operatingSystem: testOs),
+      });
+    }
 
     testUsingContext('Error logs are red', () async {
       context[Logger].printError('Pants on fire!');
@@ -144,20 +229,6 @@
       Logger: () => StdoutLogger()..supportsColor = true,
     });
 
-    testUsingContext('Stdout startProgress handle null inputs on colored terminal', () async {
-      context[Logger].startProgress(null, progressId: null,
-        expectSlowOperation: null,
-        progressIndicatorPadding: null,
-      );
-      final List<String> lines = outputStdout();
-      expect(outputStderr().length, equals(1));
-      expect(outputStderr().first, isEmpty);
-      expect(lines[0], equals('                                                                 \b-'));
-    }, overrides: <Type, Generator>{
-      Stdio: () => mockStdio,
-      Logger: () => StdoutLogger()..supportsColor = true,
-    });
-
     testUsingContext('Stdout printStatus handle null inputs on regular terminal', () async {
       context[Logger].printStatus(null, emphasis: null,
           color: null,
@@ -180,59 +251,12 @@
       final List<String> lines = outputStdout();
       expect(outputStderr().length, equals(1));
       expect(outputStderr().first, isEmpty);
-      expect(lines[0], equals('                                                                '));
+      expect(lines[0], matches('[ ]{64}'));
     }, overrides: <Type, Generator>{
       Stdio: () => mockStdio,
       Logger: () => StdoutLogger()..supportsColor = false,
     });
 
-    testUsingContext('AnsiStatus works when cancelled', () async {
-      ansiStatus.start();
-      await doWhileAsync(() => ansiStatus.ticks < 10);
-      List<String> lines = outputStdout();
-      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.
-      ansiStatus.cancel();
-      lines = outputStdout();
-      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(isInstanceOf<AssertionError>()));
-      expect(() { ansiStatus.stop(); }, throwsA(isInstanceOf<AssertionError>()));
-    }, overrides: <Type, Generator>{Stdio: () => mockStdio});
-
-    testUsingContext('AnsiStatus works when stopped', () async {
-      ansiStatus.start();
-      await doWhileAsync(() => ansiStatus.ticks < 10);
-      List<String> lines = outputStdout();
-      expect(lines[0], startsWith('Hello world               \b-\b\\\b|\b/\b-\b\\\b|\b/\b-'));
-      expect(lines.length, equals(1));
-
-      // Verify a stop prints the time.
-      ansiStatus.stop();
-      lines = outputStdout();
-      final List<Match> matches = secondDigits.allMatches(lines[0]).toList();
-      expect(matches, isNotNull);
-      expect(matches, hasLength(1));
-      final Match match = matches.first;
-      expect(lines[0], endsWith(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(isInstanceOf<AssertionError>()));
-      expect(() { ansiStatus.cancel(); }, throwsA(isInstanceOf<AssertionError>()));
-    }, overrides: <Type, Generator>{Stdio: () => mockStdio});
-
     testUsingContext('SummaryStatus works when cancelled', () async {
       summaryStatus.start();
       List<String> lines = outputStdout();
diff --git a/packages/flutter_tools/test/commands/test_test.dart b/packages/flutter_tools/test/commands/test_test.dart
index d576023..d0d3787 100644
--- a/packages/flutter_tools/test/commands/test_test.dart
+++ b/packages/flutter_tools/test/commands/test_test.dart
@@ -116,7 +116,11 @@
   int outputLineNumber = 0;
   bool haveSeenStdErrMarker = false;
   while (expectationLineNumber < expectations.length) {
-    expect(output, hasLength(greaterThan(outputLineNumber)));
+    expect(
+      output,
+      hasLength(greaterThan(outputLineNumber)),
+      reason: 'Failure in $testName to compare to $fullTestExpectation',
+    );
     final String expectationLine = expectations[expectationLineNumber];
     final String outputLine = output[outputLineNumber];
     if (expectationLine == '<<skip until matching line>>') {
diff --git a/packages/flutter_tools/test/compile_test.dart b/packages/flutter_tools/test/compile_test.dart
index 1565f74..2428559 100644
--- a/packages/flutter_tools/test/compile_test.dart
+++ b/packages/flutter_tools/test/compile_test.dart
@@ -50,10 +50,11 @@
         mainPath: '/path/to/main.dart'
       );
       expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
-      expect(logger.errorText, equals('compiler message: line1\ncompiler message: line2\n'));
+      expect(logger.errorText, equals('\nCompiler message:\nline1\nline2\n'));
       expect(output.outputFilename, equals('/path/to/main.dart.dill'));
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
+      Logger: () => BufferLogger()..supportsColor = false,
     });
 
     testUsingContext('single dart failed compilation', () async {
@@ -70,10 +71,11 @@
         mainPath: '/path/to/main.dart'
       );
       expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
-      expect(logger.errorText, equals('compiler message: line1\ncompiler message: line2\n'));
+      expect(logger.errorText, equals('\nCompiler message:\nline1\nline2\n'));
       expect(output, equals(null));
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
+      Logger: () => BufferLogger()..supportsColor = false,
     });
 
     testUsingContext('single dart abnormal compiler termination', () async {
@@ -92,10 +94,11 @@
           mainPath: '/path/to/main.dart'
       );
       expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
-      expect(logger.errorText, equals('compiler message: line1\ncompiler message: line2\n'));
+      expect(logger.errorText, equals('\nCompiler message:\nline1\nline2\n'));
       expect(output, equals(null));
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
+      Logger: () => BufferLogger()..supportsColor = false,
     });
   });
 
@@ -146,10 +149,11 @@
       );
       expect(mockFrontendServerStdIn.getAndClear(), 'compile /path/to/main.dart\n');
       verifyNoMoreInteractions(mockFrontendServerStdIn);
-      expect(logger.errorText, equals('compiler message: line1\ncompiler message: line2\n'));
+      expect(logger.errorText, equals('\nCompiler message:\nline1\nline2\n'));
       expect(output.outputFilename, equals('/path/to/main.dart.dill'));
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
+      Logger: () => BufferLogger()..supportsColor = false,
     });
 
     testUsingContext('single dart compile abnormally terminates', () async {
@@ -163,6 +167,7 @@
       expect(output, equals(null));
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
+      Logger: () => BufferLogger()..supportsColor = false,
     });
 
     testUsingContext('compile and recompile', () async {
@@ -181,11 +186,12 @@
       verifyNoMoreInteractions(mockFrontendServerStdIn);
       expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
       expect(logger.errorText, equals(
-        'compiler message: line0\ncompiler message: line1\n'
-        'compiler message: line1\ncompiler message: line2\n'
+        '\nCompiler message:\nline0\nline1\n'
+        '\nCompiler message:\nline1\nline2\n'
       ));
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
+      Logger: () => BufferLogger()..supportsColor = false,
     });
 
     testUsingContext('compile and recompile twice', () async {
@@ -208,12 +214,13 @@
       verifyNoMoreInteractions(mockFrontendServerStdIn);
       expect(mockFrontendServerStdIn.getAndClear(), isEmpty);
       expect(logger.errorText, equals(
-        'compiler message: line0\ncompiler message: line1\n'
-        'compiler message: line1\ncompiler message: line2\n'
-        'compiler message: line2\ncompiler message: line3\n'
+        '\nCompiler message:\nline0\nline1\n'
+        '\nCompiler message:\nline1\nline2\n'
+        '\nCompiler message:\nline2\nline3\n'
       ));
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
+      Logger: () => BufferLogger()..supportsColor = false,
     });
   });
 
@@ -283,7 +290,7 @@
             'compile /path/to/main.dart\n');
         verifyNoMoreInteractions(mockFrontendServerStdIn);
         expect(logger.errorText,
-            equals('compiler message: line1\ncompiler message: line2\n'));
+            equals('\nCompiler message:\nline1\nline2\n'));
         expect(output.outputFilename, equals('/path/to/main.dart.dill'));
 
         compileExpressionResponseCompleter.complete(
@@ -302,6 +309,7 @@
 
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
+      Logger: () => BufferLogger()..supportsColor = false,
     });
 
     testUsingContext('compile expressions without awaiting', () async {
@@ -325,7 +333,7 @@
           '/path/to/main.dart', null /* invalidatedFiles */
       ).then((CompilerOutput outputCompile) {
         expect(logger.errorText,
-            equals('compiler message: line1\ncompiler message: line2\n'));
+            equals('\nCompiler message:\nline1\nline2\n'));
         expect(outputCompile.outputFilename, equals('/path/to/main.dart.dill'));
 
         compileExpressionResponseCompleter1.complete(Future<List<int>>.value(utf8.encode(
@@ -363,6 +371,7 @@
       expect(await lastExpressionCompleted.future, isTrue);
     }, overrides: <Type, Generator>{
       ProcessManager: () => mockProcessManager,
+      Logger: () => BufferLogger()..supportsColor = false,
     });
   });
 }