[instrumentation_adapter] Add support for running tests with Flutter driver (#2050)


diff --git a/packages/instrumentation_adapter/CHANGELOG.md b/packages/instrumentation_adapter/CHANGELOG.md
index 73557c5..e3e890b 100644
--- a/packages/instrumentation_adapter/CHANGELOG.md
+++ b/packages/instrumentation_adapter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.1.2
+
+* Added support for running tests using Flutter driver.
+
 ## 0.1.1
 
 * Updates about using *androidx* library.
diff --git a/packages/instrumentation_adapter/README.md b/packages/instrumentation_adapter/README.md
index 81f5155..b2dad97 100644
--- a/packages/instrumentation_adapter/README.md
+++ b/packages/instrumentation_adapter/README.md
@@ -90,3 +90,15 @@
   --results-bucket=<RESULTS_BUCKET> \
   --results-dir=<RESULTS_DIRECTORY>
 ```
+
+## Flutter driver support
+
+`InstrumentationAdapterFlutterBinding` also reports test results to `FlutterDriver`
+when run on the command line via `flutter drive`. 
+
+```dart
+  final FlutterDriver driver = await FlutterDriver.connect();
+  final String result = await driver.requestData(null, timeout: const Duration(minutes: 1));
+  driver.close();
+  exit(result == 'pass' ? 0 : 1);
+```  
diff --git a/packages/instrumentation_adapter/lib/instrumentation_adapter.dart b/packages/instrumentation_adapter/lib/instrumentation_adapter.dart
index 81f8187..2ec16c4 100644
--- a/packages/instrumentation_adapter/lib/instrumentation_adapter.dart
+++ b/packages/instrumentation_adapter/lib/instrumentation_adapter.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:async';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/services.dart';
@@ -16,9 +17,12 @@
     tearDownAll(() async {
       await _channel.invokeMethod<void>(
           'allTestsFinished', <String, dynamic>{'results': _results});
+      if (!_allTestsPassed.isCompleted) _allTestsPassed.complete(true);
     });
   }
 
+  final Completer<bool> _allTestsPassed = Completer<bool>();
+
   static WidgetsBinding ensureInitialized() {
     if (WidgetsBinding.instance == null) {
       InstrumentationAdapterFlutterBinding();
@@ -32,14 +36,46 @@
 
   static Map<String, String> _results = <String, String>{};
 
+  // Emulates the Flutter driver extension, returning 'pass' or 'fail'.
+  @override
+  void initServiceExtensions() {
+    super.initServiceExtensions();
+    Future<Map<String, dynamic>> callback(Map<String, String> params) async {
+      final String command = params['command'];
+      Map<String, String> response;
+      switch (command) {
+        case 'request_data':
+          final bool allTestsPassed = await _allTestsPassed.future;
+          response = <String, String>{
+            'message': allTestsPassed ? 'pass' : 'fail',
+          };
+          break;
+        case 'get_health':
+          response = <String, String>{'status': 'ok'};
+          break;
+        default:
+          throw UnimplementedError('$command is not implemented');
+      }
+      return <String, dynamic>{
+        'isError': false,
+        'response': response,
+      };
+    }
+
+    registerServiceExtension(name: 'driver', callback: callback);
+  }
+
   @override
   Future<void> runTest(Future<void> testBody(), VoidCallback invariantTester,
       {String description = '', Duration timeout}) async {
     // TODO(jackson): Report the results individually instead of all at once
     // See https://github.com/flutter/flutter/issues/38985
+    final TestExceptionReporter valueBeforeTest = reportTestException;
     reportTestException =
         (FlutterErrorDetails details, String testDescription) {
       _results[description] = 'failed';
+      _allTestsPassed.complete(false);
+      valueBeforeTest(details, testDescription);
     };
     await super.runTest(testBody, invariantTester,
         description: description, timeout: timeout);