Be more verbose when pub fails (#42187)

diff --git a/packages/flutter_tools/lib/src/dart/pub.dart b/packages/flutter_tools/lib/src/dart/pub.dart
index 6ffdbc0..a01367f 100644
--- a/packages/flutter_tools/lib/src/dart/pub.dart
+++ b/packages/flutter_tools/lib/src/dart/pub.dart
@@ -114,13 +114,13 @@
   /// understand usage.
   Future<void> batch(
     List<String> arguments, {
-      @required PubContext context,
-      String directory,
-      MessageFilter filter,
-      String failureMessage = 'pub failed',
-      @required bool retry,
-      bool showTraceForErrors,
-    });
+    @required PubContext context,
+    String directory,
+    MessageFilter filter,
+    String failureMessage = 'pub failed',
+    @required bool retry,
+    bool showTraceForErrors,
+  });
 
 
   /// Runs pub in 'interactive' mode.
@@ -129,8 +129,8 @@
   /// stdout/stderr stream of pub to the corresponding streams of this process.
   Future<void> interactively(
     List<String> arguments, {
-      String directory,
-    });
+    String directory,
+  });
 }
 
 class _DefaultPub implements Pub {
@@ -201,17 +201,19 @@
   @override
   Future<void> batch(
     List<String> arguments, {
-      @required PubContext context,
-      String directory,
-      MessageFilter filter,
-      String failureMessage = 'pub failed',
-      @required bool retry,
-      bool showTraceForErrors,
-    }) async {
+    @required PubContext context,
+    String directory,
+    MessageFilter filter,
+    String failureMessage = 'pub failed',
+    @required bool retry,
+    bool showTraceForErrors,
+  }) async {
     showTraceForErrors ??= isRunningOnBot;
 
+    String lastPubMessage = 'no message';
     bool versionSolvingFailed = false;
     String filterWrapper(String line) {
+      lastPubMessage = line;
       if (line.contains('version solving failed')) {
         versionSolvingFailed = true;
       }
@@ -227,19 +229,25 @@
     int attempts = 0;
     int duration = 1;
     int code;
-    while (true) {
+    loop: while (true) {
       attempts += 1;
       code = await processUtils.stream(
         _pubCommand(arguments),
         workingDirectory: directory,
-        mapFunction: filterWrapper,
+        mapFunction: filterWrapper, // may set versionSolvingFailed, lastPubMessage
         environment: _createPubEnvironment(context),
       );
-      if (code != 69) { // UNAVAILABLE in https://github.com/dart-lang/pub/blob/master/lib/src/exit_codes.dart
-        break;
+      String message;
+      switch (code) {
+        case 69: // UNAVAILABLE in https://github.com/dart-lang/pub/blob/master/lib/src/exit_codes.dart
+          message = 'server unavailable';
+          break;
+        default:
+          break loop;
       }
+      assert(message != null);
       versionSolvingFailed = false;
-      printStatus('$failureMessage ($code) -- attempting retry $attempts in $duration second${ duration == 1 ? "" : "s"}...');
+      printStatus('$failureMessage ($message) -- attempting retry $attempts in $duration second${ duration == 1 ? "" : "s"}...');
       await Future<void>.delayed(Duration(seconds: duration));
       if (duration < 64) {
         duration *= 2;
@@ -259,14 +267,14 @@
     ).send();
 
     if (code != 0) {
-      throwToolExit('$failureMessage ($code)', exitCode: code);
+      throwToolExit('$failureMessage ($code; $lastPubMessage)', exitCode: code);
     }
   }
 
   @override
   Future<void> interactively(
     List<String> arguments, {
-      String directory,
+    String directory,
   }) async {
     Cache.releaseLockEarly();
     final io.Process process = await processUtils.start(
diff --git a/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart b/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart
index ecd7f34..41970b4 100644
--- a/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart
+++ b/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart
@@ -44,46 +44,46 @@
       time.elapse(const Duration(milliseconds: 500));
       expect(testLogger.statusText,
         'Running "flutter pub get" in /...\n'
-        'pub get failed (69) -- attempting retry 1 in 1 second...\n',
+        'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n',
       );
       expect(processMock.lastPubEnvironment, contains('flutter_cli:flutter_tests'));
       expect(processMock.lastPubCache, isNull);
       time.elapse(const Duration(milliseconds: 500));
       expect(testLogger.statusText,
         'Running "flutter pub get" in /...\n'
-        'pub get failed (69) -- attempting retry 1 in 1 second...\n'
-        'pub get failed (69) -- attempting retry 2 in 2 seconds...\n',
+        'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n'
+        'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n',
       );
       time.elapse(const Duration(seconds: 1));
       expect(testLogger.statusText,
         'Running "flutter pub get" in /...\n'
-        'pub get failed (69) -- attempting retry 1 in 1 second...\n'
-        'pub get failed (69) -- attempting retry 2 in 2 seconds...\n',
+        'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n'
+        'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n',
       );
       time.elapse(const Duration(seconds: 100)); // from t=0 to t=100
       expect(testLogger.statusText,
         'Running "flutter pub get" in /...\n'
-        'pub get failed (69) -- attempting retry 1 in 1 second...\n'
-        'pub get failed (69) -- attempting retry 2 in 2 seconds...\n'
-        'pub get failed (69) -- attempting retry 3 in 4 seconds...\n' // at t=1
-        'pub get failed (69) -- attempting retry 4 in 8 seconds...\n' // at t=5
-        'pub get failed (69) -- attempting retry 5 in 16 seconds...\n' // at t=13
-        'pub get failed (69) -- attempting retry 6 in 32 seconds...\n' // at t=29
-        'pub get failed (69) -- attempting retry 7 in 64 seconds...\n', // at t=61
+        'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n'
+        'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n'
+        'pub get failed (server unavailable) -- attempting retry 3 in 4 seconds...\n' // at t=1
+        'pub get failed (server unavailable) -- attempting retry 4 in 8 seconds...\n' // at t=5
+        'pub get failed (server unavailable) -- attempting retry 5 in 16 seconds...\n' // at t=13
+        'pub get failed (server unavailable) -- attempting retry 6 in 32 seconds...\n' // at t=29
+        'pub get failed (server unavailable) -- attempting retry 7 in 64 seconds...\n', // at t=61
       );
       time.elapse(const Duration(seconds: 200)); // from t=0 to t=200
       expect(testLogger.statusText,
         'Running "flutter pub get" in /...\n'
-        'pub get failed (69) -- attempting retry 1 in 1 second...\n'
-        'pub get failed (69) -- attempting retry 2 in 2 seconds...\n'
-        'pub get failed (69) -- attempting retry 3 in 4 seconds...\n'
-        'pub get failed (69) -- attempting retry 4 in 8 seconds...\n'
-        'pub get failed (69) -- attempting retry 5 in 16 seconds...\n'
-        'pub get failed (69) -- attempting retry 6 in 32 seconds...\n'
-        'pub get failed (69) -- attempting retry 7 in 64 seconds...\n'
-        'pub get failed (69) -- attempting retry 8 in 64 seconds...\n' // at t=39
-        'pub get failed (69) -- attempting retry 9 in 64 seconds...\n' // at t=103
-        'pub get failed (69) -- attempting retry 10 in 64 seconds...\n', // at t=167
+        'pub get failed (server unavailable) -- attempting retry 1 in 1 second...\n'
+        'pub get failed (server unavailable) -- attempting retry 2 in 2 seconds...\n'
+        'pub get failed (server unavailable) -- attempting retry 3 in 4 seconds...\n'
+        'pub get failed (server unavailable) -- attempting retry 4 in 8 seconds...\n'
+        'pub get failed (server unavailable) -- attempting retry 5 in 16 seconds...\n'
+        'pub get failed (server unavailable) -- attempting retry 6 in 32 seconds...\n'
+        'pub get failed (server unavailable) -- attempting retry 7 in 64 seconds...\n'
+        'pub get failed (server unavailable) -- attempting retry 8 in 64 seconds...\n' // at t=39
+        'pub get failed (server unavailable) -- attempting retry 9 in 64 seconds...\n' // at t=103
+        'pub get failed (server unavailable) -- attempting retry 10 in 64 seconds...\n', // at t=167
       );
     });
     expect(testLogger.errorText, isEmpty);
@@ -97,6 +97,33 @@
     Pub: () => const Pub(),
   });
 
+  testUsingContext('pub get 66 shows message from pub', () async {
+    try {
+      await pub.get(context: PubContext.flutterTests, checkLastModified: false);
+      throw AssertionError('pubGet did not fail');
+    } on ToolExit catch (error) {
+      expect(error.message, 'pub get failed (66; err3)');
+    }
+    expect(testLogger.statusText,
+      'Running "flutter pub get" in /...\n'
+      'out1\n'
+      'out2\n'
+      'out3\n'
+    );
+    expect(testLogger.errorText,
+      'err1\n'
+      'err2\n'
+      'err3\n'
+    );
+  }, overrides: <Type, Generator>{
+    ProcessManager: () => MockProcessManager(66, stderr: 'err1\nerr2\nerr3\n', stdout: 'out1\nout2\nout3\n'),
+    FileSystem: () => MockFileSystem(),
+    Platform: () => FakePlatform(
+      environment: UnmodifiableMapView<String, String>(<String, String>{}),
+    ),
+    Pub: () => const Pub(),
+  });
+
   testUsingContext('pub cache in root is used', () async {
     String error;
 
@@ -218,10 +245,12 @@
 
 class MockProcessManager implements ProcessManager {
   MockProcessManager(this.fakeExitCode, {
+    this.stdout = '',
     this.stderr = '',
   });
 
   final int fakeExitCode;
+  final String stdout;
   final String stderr;
 
   String lastPubEnvironment;
@@ -240,6 +269,7 @@
     lastPubCache = environment['PUB_CACHE'];
     return Future<Process>.value(mocks.createMockProcess(
       exitCode: fakeExitCode,
+      stdout: stdout,
       stderr: stderr,
     ));
   }