Turn on unawaited_futures in flutter_tools (#21048)

diff --git a/packages/flutter_tools/analysis_options.yaml b/packages/flutter_tools/analysis_options.yaml
index b8591ca..c02a5f6 100644
--- a/packages/flutter_tools/analysis_options.yaml
+++ b/packages/flutter_tools/analysis_options.yaml
@@ -2,3 +2,7 @@
 # the ones from above, which include the `public_member_api_docs` rule).
 
 include: ../../analysis_options.yaml
+
+linter:
+  rules:
+    - unawaited_futures
diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart
index f0ad58f..524dbd8 100644
--- a/packages/flutter_tools/lib/src/android/android_workflow.dart
+++ b/packages/flutter_tools/lib/src/android/android_workflow.dart
@@ -236,7 +236,11 @@
       environment: androidSdk.sdkManagerEnv,
     );
 
-    process.stdin.addStream(stdin);
+    // The real stdin will never finish streaming. Pipe until the child process
+    // finishes.
+    process.stdin.addStream(stdin); // ignore: unawaited_futures
+    // Wait for stdout and stderr to be fully processed, because process.exitCode
+    // may complete first.
     await waitGroup<void>(<Future<void>>[
       stdout.addStream(process.stdout),
       stderr.addStream(process.stderr),
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index 2105aa5..247e5ce 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -191,7 +191,9 @@
     allowReentrantFlutter: allowReentrantFlutter,
     environment: environment,
   );
-  process.stdin.addStream(stdin);
+  // The real stdin will never finish streaming. Pipe until the child process
+  // finishes.
+  process.stdin.addStream(stdin); // ignore: unawaited_futures
   // Wait for stdout and stderr to be fully processed, because process.exitCode
   // may complete first.
   await Future.wait<dynamic>(<Future<dynamic>>[
diff --git a/packages/flutter_tools/lib/src/commands/analyze_once.dart b/packages/flutter_tools/lib/src/commands/analyze_once.dart
index 610c319..06f2095 100644
--- a/packages/flutter_tools/lib/src/commands/analyze_once.dart
+++ b/packages/flutter_tools/lib/src/commands/analyze_once.dart
@@ -92,7 +92,8 @@
     });
 
     await server.start();
-    server.onExit.then((int exitCode) {
+    // Completing the future in the callback can't fail.
+    server.onExit.then((int exitCode) { // ignore: unawaited_futures
       if (!analysisCompleter.isCompleted) {
         analysisCompleter.completeError('analysis server exited: $exitCode');
       }
diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart
index 66ea962..9131f35 100644
--- a/packages/flutter_tools/lib/src/commands/attach.dart
+++ b/packages/flutter_tools/lib/src/commands/attach.dart
@@ -82,7 +82,7 @@
 
   @override
   Future<Null> validateCommand() async {
-    super.validateCommand();
+    await super.validateCommand();
     if (await findTargetDevice() == null)
       throwToolExit(null);
     observatoryPort;
diff --git a/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart b/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart
index 4650790..46419aa 100644
--- a/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart
+++ b/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart
@@ -444,7 +444,8 @@
         .transform(utf8.decoder)
         .transform(const LineSplitter())
         .listen((String data) { printTrace(data); });
-    process.exitCode.then((int c) {
+    // Best effort to print the exit code.
+    process.exitCode.then((int c) { // ignore: unawaited_futures
       printTrace("'${command.join(' ')}' exited with exit code $c");
     });
     printTrace('Set up forwarding from $localPort to $address:$remotePort');
diff --git a/packages/flutter_tools/lib/src/dart/analysis.dart b/packages/flutter_tools/lib/src/dart/analysis.dart
index 8c07f09..d4eabd7 100644
--- a/packages/flutter_tools/lib/src/dart/analysis.dart
+++ b/packages/flutter_tools/lib/src/dart/analysis.dart
@@ -45,8 +45,7 @@
     printTrace('dart ${command.skip(1).join(' ')}');
     _process = await processManager.start(command);
     // This callback hookup can't throw.
-    _process.exitCode
-        .whenComplete(() => _process = null); // ignore: unawaited_futures
+    _process.exitCode.whenComplete(() => _process = null); // ignore: unawaited_futures
 
     final Stream<String> errorStream =
         _process.stderr.transform(utf8.decoder).transform(const LineSplitter());
diff --git a/packages/flutter_tools/lib/src/ios/code_signing.dart b/packages/flutter_tools/lib/src/ios/code_signing.dart
index a337a75..a060eb1 100644
--- a/packages/flutter_tools/lib/src/ios/code_signing.dart
+++ b/packages/flutter_tools/lib/src/ios/code_signing.dart
@@ -153,9 +153,7 @@
   );
 
   final Process opensslProcess = await runCommand(const <String>['openssl', 'x509', '-subject']);
-  opensslProcess.stdin
-      ..write(signingCertificate)
-      ..close();
+  await (opensslProcess.stdin..write(signingCertificate)).close();
 
   final String opensslOutput = await utf8.decodeStream(opensslProcess.stdout);
   // Fire and forget discard of the stderr stream so we don't hold onto resources.
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart
index b5f93b3..9ba993b 100644
--- a/packages/flutter_tools/lib/src/resident_runner.dart
+++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -762,7 +762,7 @@
         await handleTerminalCommand(command);
     } catch (error, st) {
       printError('$error\n$st');
-      _cleanUpAndExit(null);
+      await _cleanUpAndExit(null);
     } finally {
       _processingUserRequest = false;
     }
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart
index b051daf..01c0e9c 100644
--- a/packages/flutter_tools/lib/src/run_hot.dart
+++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -590,21 +590,28 @@
           pause: pause
         );
         countExpectedReports += reports.length;
-        Future.wait(reports).catchError((dynamic error) {
-          return <Map<String, dynamic>>[error];
-        }).then((List<Map<String, dynamic>> list) {
-          // TODO(aam): Investigate why we are validating only first reload report,
-          // which seems to be current behavior
-          final Map<String, dynamic> firstReport = list.first;
-          // Don't print errors because they will be printed further down when
-          // `validateReloadReport` is called again.
-          device.updateReloadStatus(validateReloadReport(firstReport,
-            printErrors: false));
-          if (!retrieveFirstReloadReport.isCompleted)
-            retrieveFirstReloadReport.complete(firstReport);
-        }, onError: (dynamic error, StackTrace stack) {
-          retrieveFirstReloadReport.completeError(error, stack);
-        });
+        await Future
+            .wait(reports)
+            .catchError((dynamic error) {
+              return <Map<String, dynamic>>[error];
+            })
+            .then(
+              (List<Map<String, dynamic>> list) {
+                // TODO(aam): Investigate why we are validating only first reload report,
+                // which seems to be current behavior
+                final Map<String, dynamic> firstReport = list.first;
+                // Don't print errors because they will be printed further down when
+                // `validateReloadReport` is called again.
+                device.updateReloadStatus(
+                  validateReloadReport(firstReport, printErrors: false)
+                );
+                if (!retrieveFirstReloadReport.isCompleted)
+                  retrieveFirstReloadReport.complete(firstReport);
+              },
+              onError: (dynamic error, StackTrace stack) {
+                retrieveFirstReloadReport.completeError(error, stack);
+              },
+            );
       }
 
       if (countExpectedReports == 0) {
diff --git a/packages/flutter_tools/lib/src/tester/flutter_tester.dart b/packages/flutter_tools/lib/src/tester/flutter_tester.dart
index 7404b57..e6c2b24 100644
--- a/packages/flutter_tools/lib/src/tester/flutter_tester.dart
+++ b/packages/flutter_tools/lib/src/tester/flutter_tester.dart
@@ -157,7 +157,8 @@
           'FLUTTER_TEST': 'true',
         },
       );
-      _process.exitCode.then((_) => _isRunning = false);
+      // Setting a bool can't fail in the callback.
+      _process.exitCode.then((_) => _isRunning = false); // ignore: unawaited_futures
       _process.stdout
           .transform(utf8.decoder)
           .transform(const LineSplitter())
diff --git a/packages/flutter_tools/test/commands/daemon_test.dart b/packages/flutter_tools/test/commands/daemon_test.dart
index 8139187..f880e59 100644
--- a/packages/flutter_tools/test/commands/daemon_test.dart
+++ b/packages/flutter_tools/test/commands/daemon_test.dart
@@ -43,8 +43,8 @@
       expect(response['id'], 0);
       expect(response['result'], isNotEmpty);
       expect(response['result'] is String, true);
-      responses.close();
-      commands.close();
+      await responses.close();
+      await commands.close();
     });
 
     testUsingContext('printError should send daemon.logMessage event', () async {
@@ -64,8 +64,8 @@
       final Map<String, String> logMessage = response['params'].cast<String, String>();
       expect(logMessage['level'], 'error');
       expect(logMessage['message'], 'daemon.logMessage test');
-      responses.close();
-      commands.close();
+      await responses.close();
+      await commands.close();
     }, overrides: <Type, Generator>{
       Logger: () => notifyingLogger,
     });
@@ -103,9 +103,8 @@
         notifyingLogger: notifyingLogger
       );
       commands.add(<String, dynamic>{'id': 0, 'method': 'daemon.shutdown'});
-      return daemon.onExit.then<Null>((int code) {
-        responses.close();
-        commands.close();
+      return daemon.onExit.then<Null>((int code) async {
+        await commands.close();
         expect(code, 0);
       });
     });
@@ -127,8 +126,8 @@
       final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
       expect(response['id'], 0);
       expect(response['error'], contains('appId is required'));
-      responses.close();
-      commands.close();
+      await responses.close();
+      await commands.close();
     });
 
     testUsingContext('ext.flutter.debugPaint via service extension without an appId should report an error', () async {
@@ -154,8 +153,8 @@
       final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
       expect(response['id'], 0);
       expect(response['error'], contains('appId is required'));
-      responses.close();
-      commands.close();
+      await responses.close();
+      await commands.close();
     });
 
     testUsingContext('app.stop without appId should report an error', () async {
@@ -175,8 +174,8 @@
       final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
       expect(response['id'], 0);
       expect(response['error'], contains('appId is required'));
-      responses.close();
-      commands.close();
+      await responses.close();
+      await commands.close();
     });
 
     testUsingContext('daemon should send showMessage on startup if no Android devices are available', () async {
@@ -212,11 +211,11 @@
       final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
       expect(response['id'], 0);
       expect(response['result'], isList);
-      responses.close();
-      commands.close();
+      await responses.close();
+      await commands.close();
     });
 
-    testUsingContext('should send device.added event when device is discovered', () {
+    testUsingContext('should send device.added event when device is discovered', () async {
       final StreamController<Map<String, dynamic>> commands = new StreamController<Map<String, dynamic>>();
       final StreamController<Map<String, dynamic>> responses = new StreamController<Map<String, dynamic>>();
       daemon = new Daemon(
@@ -229,15 +228,15 @@
       daemon.deviceDomain.addDeviceDiscoverer(discoverer);
       discoverer.addDevice(new MockAndroidDevice());
 
-      return responses.stream.skipWhile(_isConnectedEvent).first.then((Map<String, dynamic> response) {
+      return await responses.stream.skipWhile(_isConnectedEvent).first.then((Map<String, dynamic> response) async {
         expect(response['event'], 'device.added');
         expect(response['params'], isMap);
 
         final Map<String, dynamic> params = response['params'];
         expect(params['platform'], isNotEmpty); // the mock device has a platform of 'android-arm'
 
-        responses.close();
-        commands.close();
+        await responses.close();
+        await commands.close();
       });
     }, overrides: <Type, Generator>{
       AndroidWorkflow: () => new MockAndroidWorkflow(),
@@ -261,8 +260,8 @@
       final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
       expect(response['id'], 0);
       expect(response['error'], contains('emulatorId is required'));
-      responses.close();
-      commands.close();
+      await responses.close();
+      await commands.close();
     });
 
     testUsingContext('emulator.getEmulators should respond with list', () async {
@@ -277,8 +276,8 @@
       final Map<String, dynamic> response = await responses.stream.firstWhere(_notEvent);
       expect(response['id'], 0);
       expect(response['result'], isList);
-      responses.close();
-      commands.close();
+      await responses.close();
+      await commands.close();
     });
   });
 
diff --git a/packages/flutter_tools/test/compile_test.dart b/packages/flutter_tools/test/compile_test.dart
index 096c797..3ddb5be 100644
--- a/packages/flutter_tools/test/compile_test.dart
+++ b/packages/flutter_tools/test/compile_test.dart
@@ -276,7 +276,7 @@
         'result abc\nline1\nline2\nabc /path/to/main.dart.dill 0\n'
       )));
 
-      generator.recompile(
+      await generator.recompile(
           '/path/to/main.dart', null /* invalidatedFiles */
       ).then((CompilerOutput output) {
         expect(mockFrontendServerStdIn.getAndClear(),
@@ -320,7 +320,8 @@
             compileExpressionResponseCompleter2.future,
           ]));
 
-      generator.recompile(
+      // The test manages timing via completers.
+      generator.recompile( // ignore: unawaited_futures
           '/path/to/main.dart', null /* invalidatedFiles */
       ).then((CompilerOutput outputCompile) {
         expect(logger.errorText,
@@ -332,8 +333,9 @@
         )));
       });
 
+      // The test manages timing via completers.
       final Completer<bool> lastExpressionCompleted = new Completer<bool>();
-      generator.compileExpression('0+1', null, null, null, null, false).then(
+      generator.compileExpression('0+1', null, null, null, null, false).then( // ignore: unawaited_futures
           (CompilerOutput outputExpression) {
             expect(outputExpression, isNotNull);
             expect(outputExpression.outputFilename,
@@ -344,7 +346,8 @@
             )));
           });
 
-      generator.compileExpression('1+1', null, null, null, null, false).then(
+      // The test manages timing via completers.
+      generator.compileExpression('1+1', null, null, null, null, false).then( // ignore: unawaited_futures
           (CompilerOutput outputExpression) {
             expect(outputExpression, isNotNull);
             expect(outputExpression.outputFilename,
diff --git a/packages/flutter_tools/test/device_test.dart b/packages/flutter_tools/test/device_test.dart
index 32a06a5..94cf23c 100644
--- a/packages/flutter_tools/test/device_test.dart
+++ b/packages/flutter_tools/test/device_test.dart
@@ -28,12 +28,12 @@
       Future<Null> expectDevice(String id, List<Device> expected) async {
         expect(await deviceManager.getDevicesById(id).toList(), expected);
       }
-      expectDevice('01abfc49119c410e', <Device>[device2]);
-      expectDevice('Nexus 5X', <Device>[device2]);
-      expectDevice('0553790d0a4e726f', <Device>[device1]);
-      expectDevice('Nexus 5', <Device>[device1]);
-      expectDevice('0553790', <Device>[device1]);
-      expectDevice('Nexus', <Device>[device1, device2]);
+      await expectDevice('01abfc49119c410e', <Device>[device2]);
+      await expectDevice('Nexus 5X', <Device>[device2]);
+      await expectDevice('0553790d0a4e726f', <Device>[device1]);
+      await expectDevice('Nexus 5', <Device>[device1]);
+      await expectDevice('0553790', <Device>[device1]);
+      await expectDevice('Nexus', <Device>[device1, device2]);
     });
   });
 }
diff --git a/packages/flutter_tools/test/emulator_test.dart b/packages/flutter_tools/test/emulator_test.dart
index ffdc7a7..64f04e2 100644
--- a/packages/flutter_tools/test/emulator_test.dart
+++ b/packages/flutter_tools/test/emulator_test.dart
@@ -62,12 +62,12 @@
         expect(await testEmulatorManager.getEmulatorsMatching(id), expected);
       }
 
-      expectEmulator('Nexus_5', <Emulator>[emulator1]);
-      expectEmulator('Nexus_5X', <Emulator>[emulator2]);
-      expectEmulator('Nexus_5X_API_27_x86', <Emulator>[emulator2]);
-      expectEmulator('Nexus', <Emulator>[emulator1, emulator2]);
-      expectEmulator('iOS Simulator', <Emulator>[emulator3]);
-      expectEmulator('ios', <Emulator>[emulator3]);
+      await expectEmulator('Nexus_5', <Emulator>[emulator1]);
+      await expectEmulator('Nexus_5X', <Emulator>[emulator2]);
+      await expectEmulator('Nexus_5X_API_27_x86', <Emulator>[emulator2]);
+      await expectEmulator('Nexus', <Emulator>[emulator1, emulator2]);
+      await expectEmulator('iOS Simulator', <Emulator>[emulator3]);
+      await expectEmulator('ios', <Emulator>[emulator3]);
     });
 
     testUsingContext('create emulator with an empty name does not fail',
diff --git a/packages/flutter_tools/test/integration/test_driver.dart b/packages/flutter_tools/test/integration/test_driver.dart
index 091df64..892c275 100644
--- a/packages/flutter_tools/test/integration/test_driver.dart
+++ b/packages/flutter_tools/test/integration/test_driver.dart
@@ -89,7 +89,9 @@
         workingDirectory: _projectFolder.path,
         environment: <String, String>{'FLUTTER_TEST': 'true'});
 
-    _proc.exitCode.then((int code) {
+    // This class doesn't use the result of the future. It's made available
+    // via a getter for external uses.
+    _proc.exitCode.then((int code) { // ignore: unawaited_futures
       _debugPrint('Process exited ($code)');
       _hasExited = true;
     });
diff --git a/packages/flutter_tools/test/project_test.dart b/packages/flutter_tools/test/project_test.dart
index 3e3ce86..dc19f23 100644
--- a/packages/flutter_tools/test/project_test.dart
+++ b/packages/flutter_tools/test/project_test.dart
@@ -97,7 +97,7 @@
       testInMemory('exits on already materialized module', () async {
         final FlutterProject project = await aModuleProject();
         await project.android.materialize();
-        expectToolExitLater(project.android.materialize(), contains('already materialized'));
+        return expectToolExitLater(project.android.materialize(), contains('already materialized'));
       });
       testInMemory('creates android/app folder in place of .android/app', () async {
         final FlutterProject project = await aModuleProject();
diff --git a/packages/flutter_tools/test/protocol_discovery_test.dart b/packages/flutter_tools/test/protocol_discovery_test.dart
index c89e19b..af80d01 100644
--- a/packages/flutter_tools/test/protocol_discovery_test.dart
+++ b/packages/flutter_tools/test/protocol_discovery_test.dart
@@ -1,3 +1,4 @@
+
 // Copyright 2016 The Chromium Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
@@ -134,7 +135,7 @@
         expect(uri.port, 99);
         expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/');
 
-        discoverer.cancel();
+        await discoverer.cancel();
         logReader.dispose();
       });
 
@@ -153,7 +154,7 @@
         expect(uri.port, 1243);
         expect('$uri', 'http://127.0.0.1:1243/PTwjm8Ii8qg=/');
 
-        discoverer.cancel();
+        await discoverer.cancel();
         logReader.dispose();
       });
 
@@ -172,7 +173,7 @@
         expect(uri.port, 99);
         expect('$uri', 'http://127.0.0.1:99/PTwjm8Ii8qg=/');
 
-        discoverer.cancel();
+        await discoverer.cancel();
         logReader.dispose();
       });
 
@@ -192,7 +193,7 @@
         expect(uri.port, 54777);
         expect('$uri', 'http://[::1]:54777/PTwjm8Ii8qg=/');
 
-        discoverer.cancel();
+        await discoverer.cancel();
         logReader.dispose();
       });
     });
diff --git a/packages/flutter_tools/tool/daemon_client.dart b/packages/flutter_tools/tool/daemon_client.dart
index 4700f8b..10f4895 100644
--- a/packages/flutter_tools/tool/daemon_client.dart
+++ b/packages/flutter_tools/tool/daemon_client.dart
@@ -81,7 +81,8 @@
     stdout.write('> ');
   });
 
-  daemon.exitCode.then<Null>((int code) {
+  // Print in the callback can't fail.
+  daemon.exitCode.then<Null>((int code) { // ignore: unawaited_futures
     print('daemon exiting ($code)');
     exit(code);
   });