Move test support logic out of the default Dart output. (#246)

This will allow us to move the mock logic out of the core framework.
diff --git a/packages/pigeon/README.md b/packages/pigeon/README.md
index e91ad4b..f597767 100644
--- a/packages/pigeon/README.md
+++ b/packages/pigeon/README.md
@@ -23,7 +23,8 @@
 
 1) Add Pigeon as a dev_dependency.
 1) Make a ".dart" file outside of your "lib" directory for defining the communication interface.
-1) Run pigeon on your ".dart" file to generate the required Dart and Objective-C code.
+1) Run pigeon on your ".dart" file to generate the required Dart and Objective-C code:
+   `flutter pub get` then `flutter pub run pigeon` with suitable arguments.
 1) Add the generated Dart code to `lib` for compilation.
 1) Add the generated Objective-C code to your Xcode project for compilation
    (e.g. `ios/Runner.xcworkspace` or `.podspec`).
@@ -36,6 +37,7 @@
 1) Add Pigeon as a dev_dependency.
 1) Make a ".dart" file outside of your "lib" directory for defining the communication interface.
 1) Run pigeon on your ".dart" file to generate the required Dart and Java code.
+   `flutter pub get` then `flutter pub run pigeon` with suitable arguments.
 1) Add the generated Dart code to `./lib` for compilation.
 1) Add the generated Java code to your `./android/app/src/main/java` directory for compilation.
 1) Implement the generated Java interface for handling the calls on Android, set it up
diff --git a/packages/pigeon/bin/pigeon.dart b/packages/pigeon/bin/pigeon.dart
index 25aba1f..1a424e0 100644
--- a/packages/pigeon/bin/pigeon.dart
+++ b/packages/pigeon/bin/pigeon.dart
@@ -1,7 +1,9 @@
 // Copyright 2020 The Flutter Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
+
 // @dart = 2.2
+
 import 'dart:async';
 import 'dart:io';
 import 'dart:isolate';
@@ -26,7 +28,9 @@
 
 Future<void> main(List<String> args) async {
   final PigeonOptions opts = Pigeon.parseArgs(args);
-  final Directory tempDir = Directory.systemTemp.createTempSync();
+  final Directory tempDir = Directory.systemTemp.createTempSync(
+    'flutter_pigeon.',
+  );
 
   String importLine = '';
   if (opts.input != null) {
@@ -43,7 +47,6 @@
   sendPort.send(await Pigeon.run(args));
 }
 """;
-
   final File tempFile = File(path.join(tempDir.path, '_pigeon_temp_.dart'));
   await tempFile.writeAsString(code);
   final ReceivePort receivePort = ReceivePort();
diff --git a/packages/pigeon/lib/ast.dart b/packages/pigeon/lib/ast.dart
index e253461..e3c3a96 100644
--- a/packages/pigeon/lib/ast.dart
+++ b/packages/pigeon/lib/ast.dart
@@ -32,7 +32,7 @@
   bool isAsynchronous;
 }
 
-/// Represents a collection of [Method]s that are hosted ona given [location].
+/// Represents a collection of [Method]s that are hosted on a given [location].
 class Api extends Node {
   /// Parametric constructor for [Api].
   Api({this.name, this.location, this.methods, this.dartHostTestHandler});
diff --git a/packages/pigeon/lib/dart_generator.dart b/packages/pigeon/lib/dart_generator.dart
index 1210568..80f7cb5 100644
--- a/packages/pigeon/lib/dart_generator.dart
+++ b/packages/pigeon/lib/dart_generator.dart
@@ -11,64 +11,81 @@
   bool isNullSafe = false;
 }
 
+String _escapeForDartSingleQuotedString(String raw) {
+  return raw
+      .replaceAll(r'\', r'\\')
+      .replaceAll(r'$', r'\$')
+      .replaceAll(r"'", r"\'");
+}
+
 void _writeHostApi(DartOptions opt, Indent indent, Api api) {
   assert(api.location == ApiLocation.host);
   final String nullTag = opt.isNullSafe ? '?' : '';
+  final String unwrapOperator = opt.isNullSafe ? '!' : '';
+  bool first = true;
   indent.write('class ${api.name} ');
   indent.scoped('{', '}', () {
     for (Method func in api.methods) {
+      if (!first) {
+        indent.writeln('');
+      } else {
+        first = false;
+      }
       String argSignature = '';
       String sendArgument = 'null';
-      String requestMapDeclaration;
+      String encodedDeclaration;
       if (func.argType != 'void') {
         argSignature = '${func.argType} arg';
-        sendArgument = 'requestMap';
-        requestMapDeclaration =
-            'final Map<dynamic, dynamic> requestMap = arg._toMap();';
+        sendArgument = 'encoded';
+        encodedDeclaration = 'final Object encoded = arg.encode();';
       }
       indent.write(
-          'Future<${func.returnType}> ${func.name}($argSignature) async ');
+        'Future<${func.returnType}> ${func.name}($argSignature) async ',
+      );
       indent.scoped('{', '}', () {
-        if (requestMapDeclaration != null) {
-          indent.writeln(requestMapDeclaration);
+        if (encodedDeclaration != null) {
+          indent.writeln(encodedDeclaration);
         }
         final String channelName = makeChannelName(api, func);
-        indent.writeln('const BasicMessageChannel<dynamic> channel =');
-        indent.inc();
-        indent.inc();
-        indent.writeln(
-            'BasicMessageChannel<dynamic>(\'$channelName\', StandardMessageCodec());');
-        indent.dec();
-        indent.dec();
-        indent.writeln('');
+        indent.writeln('const BasicMessageChannel<Object$nullTag> channel =');
+        indent.nest(2, () {
+          indent.writeln(
+            'BasicMessageChannel<Object$nullTag>(\'$channelName\', StandardMessageCodec());',
+          );
+        });
         final String returnStatement = func.returnType == 'void'
             ? '// noop'
-            : 'return ${func.returnType}._fromMap(replyMap[\'${Keys.result}\']);';
+            : 'return ${func.returnType}.decode(replyMap[\'${Keys.result}\']$unwrapOperator);';
         indent.format(
-            '''final Map<dynamic, dynamic>$nullTag replyMap = await channel.send($sendArgument);
+            '''final Map<Object$nullTag, Object$nullTag>$nullTag replyMap = await channel.send($sendArgument) as Map<Object$nullTag, Object$nullTag>$nullTag;
 if (replyMap == null) {
 \tthrow PlatformException(
 \t\tcode: 'channel-error',
 \t\tmessage: 'Unable to establish connection on channel.',
-\t\tdetails: null);
+\t\tdetails: null,
+\t);
 } else if (replyMap['error'] != null) {
-\tfinal Map<dynamic, dynamic> error = replyMap['${Keys.error}'];
+\tfinal Map<Object$nullTag, Object$nullTag> error = replyMap['${Keys.error}'] as Map<Object$nullTag, Object$nullTag>;
 \tthrow PlatformException(
-\t\t\tcode: error['${Keys.errorCode}'],
-\t\t\tmessage: error['${Keys.errorMessage}'],
-\t\t\tdetails: error['${Keys.errorDetails}']);
+\t\tcode: error['${Keys.errorCode}'] as String,
+\t\tmessage: error['${Keys.errorMessage}'] as String$nullTag,
+\t\tdetails: error['${Keys.errorDetails}'],
+\t);
 } else {
 \t$returnStatement
-}
-''');
+}''');
       });
     }
   });
-  indent.writeln('');
 }
 
-void _writeFlutterApi(DartOptions opt, Indent indent, Api api,
-    {String Function(Method) channelNameFunc, bool isMockHandler = false}) {
+void _writeFlutterApi(
+  DartOptions opt,
+  Indent indent,
+  Api api, {
+  String Function(Method) channelNameFunc,
+  bool isMockHandler = false,
+}) {
   assert(api.location == ApiLocation.flutter);
   final String nullTag = opt.isNullSafe ? '?' : '';
   indent.write('abstract class ${api.name} ');
@@ -86,53 +103,62 @@
       for (Method func in api.methods) {
         indent.write('');
         indent.scoped('{', '}', () {
-          indent.writeln('const BasicMessageChannel<dynamic> channel =');
-          indent.inc();
-          indent.inc();
-          final String channelName = channelNameFunc == null
-              ? makeChannelName(api, func)
-              : channelNameFunc(func);
           indent.writeln(
-              'BasicMessageChannel<dynamic>(\'$channelName\', StandardMessageCodec());');
-          indent.dec();
-          indent.dec();
+            'const BasicMessageChannel<Object$nullTag> channel =',
+          );
+          indent.nest(2, () {
+            final String channelName = channelNameFunc == null
+                ? makeChannelName(api, func)
+                : channelNameFunc(func);
+            indent.writeln(
+              'BasicMessageChannel<Object$nullTag>(\'$channelName\', StandardMessageCodec());',
+            );
+          });
           final String messageHandlerSetter =
               isMockHandler ? 'setMockMessageHandler' : 'setMessageHandler';
           indent.write('if (api == null) ');
-          indent.scoped('{', '} else {', () {
+          indent.scoped('{', '}', () {
             indent.writeln('channel.$messageHandlerSetter(null);');
-          });
-          indent.scoped('', '}', () {
+          }, addTrailingNewline: false);
+          indent.add(' else ');
+          indent.scoped('{', '}', () {
             indent.write(
-                'channel.$messageHandlerSetter((dynamic message) async ');
+              'channel.$messageHandlerSetter((Object$nullTag message) async ',
+            );
             indent.scoped('{', '});', () {
               final String argType = func.argType;
               final String returnType = func.returnType;
               final bool isAsync = func.isAsynchronous;
+              final String emptyReturnStatement = isMockHandler
+                  ? 'return <Object$nullTag, Object$nullTag>{};'
+                  : func.returnType == 'void'
+                      ? 'return;'
+                      : 'return null;';
+              indent.write('if (message == null) ');
+              indent.scoped('{', '}', () {
+                indent.writeln(emptyReturnStatement);
+              });
               String call;
               if (argType == 'void') {
                 call = 'api.${func.name}()';
               } else {
                 indent.writeln(
-                    'final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;');
-                indent.writeln(
-                    'final $argType input = $argType._fromMap(mapMessage);');
+                  'final $argType input = $argType.decode(message);',
+                );
                 call = 'api.${func.name}(input)';
               }
               if (returnType == 'void') {
                 indent.writeln('$call;');
-                if (isMockHandler) {
-                  indent.writeln('return <dynamic, dynamic>{};');
-                }
+                indent.writeln(emptyReturnStatement);
               } else {
                 if (isAsync) {
                   indent.writeln('final $returnType output = await $call;');
                 } else {
                   indent.writeln('final $returnType output = $call;');
                 }
-                const String returnExpresion = 'output._toMap()';
+                const String returnExpresion = 'output.encode()';
                 final String returnStatement = isMockHandler
-                    ? 'return <dynamic, dynamic>{\'${Keys.result}\': $returnExpresion};'
+                    ? 'return <Object$nullTag, Object$nullTag>{\'${Keys.result}\': $returnExpresion};'
                     : 'return $returnExpresion;';
                 indent.writeln(returnStatement);
               }
@@ -142,84 +168,148 @@
       }
     });
   });
-  indent.addln('');
+}
+
+String _addGenericTypes(String dataType, String nullTag) {
+  switch (dataType) {
+    case 'List':
+      return 'List<Object$nullTag>';
+    case 'Map':
+      return 'Map<Object$nullTag, Object$nullTag>';
+    default:
+      return dataType;
+  }
 }
 
 /// Generates Dart source code for the given AST represented by [root],
 /// outputting the code to [sink].
 void generateDart(DartOptions opt, Root root, StringSink sink) {
+  final String nullTag = opt.isNullSafe ? '?' : '';
+  final String unwrapOperator = opt.isNullSafe ? '!' : '';
   final List<String> customClassNames =
       root.classes.map((Class x) => x.name).toList();
   final Indent indent = Indent(sink);
   indent.writeln('// $generatedCodeWarning');
   indent.writeln('// $seeAlsoWarning');
   indent.writeln(
-      '// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import');
+    '// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import',
+  );
   indent.writeln('// @dart = ${opt.isNullSafe ? '2.10' : '2.8'}');
   indent.writeln('import \'dart:async\';');
-  indent.writeln('import \'package:flutter/services.dart\';');
   indent.writeln(
-      'import \'dart:typed_data\' show Uint8List, Int32List, Int64List, Float64List;');
+    'import \'dart:typed_data\' show Uint8List, Int32List, Int64List, Float64List;',
+  );
   indent.writeln('');
-
-  final String nullBang = opt.isNullSafe ? '!' : '';
+  indent.writeln('import \'package:flutter/services.dart\';');
   for (Class klass in root.classes) {
+    indent.writeln('');
     sink.write('class ${klass.name} ');
     indent.scoped('{', '}', () {
       for (Field field in klass.fields) {
         final String datatype =
-            opt.isNullSafe ? '${field.dataType}?' : field.dataType;
+            '${_addGenericTypes(field.dataType, nullTag)}$nullTag';
         indent.writeln('$datatype ${field.name};');
       }
+      if (klass.fields.isNotEmpty) {
+        indent.writeln('');
+      }
       indent.writeln('// ignore: unused_element');
-      indent.write('Map<dynamic, dynamic> _toMap() ');
+      indent.write('Object encode() ');
       indent.scoped('{', '}', () {
         indent.writeln(
-            'final Map<dynamic, dynamic> pigeonMap = <dynamic, dynamic>{};');
+          'final Map<Object$nullTag, Object$nullTag> pigeonMap = <Object$nullTag, Object$nullTag>{};',
+        );
         for (Field field in klass.fields) {
           indent.write('pigeonMap[\'${field.name}\'] = ');
           if (customClassNames.contains(field.dataType)) {
             indent.addln(
-                '${field.name} == null ? null : ${field.name}$nullBang._toMap();');
+              '${field.name} == null ? null : ${field.name}$unwrapOperator.encode();',
+            );
           } else {
             indent.addln('${field.name};');
           }
         }
         indent.writeln('return pigeonMap;');
       });
+      indent.writeln('');
       indent.writeln('// ignore: unused_element');
       indent.write(
-          'static ${klass.name} _fromMap(Map<dynamic, dynamic> pigeonMap) ');
+        'static ${klass.name} decode(Object message) ',
+      );
       indent.scoped('{', '}', () {
-        indent.writeln('final ${klass.name} result = ${klass.name}();');
-        for (Field field in klass.fields) {
-          indent.write('result.${field.name} = ');
-          if (customClassNames.contains(field.dataType)) {
-            indent.addln(
-                'pigeonMap[\'${field.name}\'] != null ? ${field.dataType}._fromMap(pigeonMap[\'${field.name}\']) : null;');
-          } else {
-            indent.addln('pigeonMap[\'${field.name}\'];');
+        indent.writeln(
+          'final Map<Object$nullTag, Object$nullTag> pigeonMap = message as Map<Object$nullTag, Object$nullTag>;',
+        );
+        indent.writeln('return ${klass.name}()');
+        indent.nest(1, () {
+          for (int index = 0; index < klass.fields.length; index += 1) {
+            final Field field = klass.fields[index];
+            indent.write('..${field.name} = ');
+            if (customClassNames.contains(field.dataType)) {
+              indent.add(
+                'pigeonMap[\'${field.name}\'] != null ? ${field.dataType}.decode(pigeonMap[\'${field.name}\']$unwrapOperator) : null',
+              );
+            } else {
+              indent.add(
+                'pigeonMap[\'${field.name}\'] as ${_addGenericTypes(field.dataType, nullTag)}',
+              );
+            }
+            indent.addln(index == klass.fields.length - 1 ? ';' : '');
           }
-        }
-        indent.writeln('return result;');
+        });
       });
     });
-    indent.writeln('');
   }
   for (Api api in root.apis) {
+    indent.writeln('');
     if (api.location == ApiLocation.host) {
       _writeHostApi(opt, indent, api);
-      if (api.dartHostTestHandler != null) {
-        final Api mockApi = Api(
-            name: api.dartHostTestHandler,
-            methods: api.methods,
-            location: ApiLocation.flutter);
-        _writeFlutterApi(opt, indent, mockApi,
-            channelNameFunc: (Method func) => makeChannelName(api, func),
-            isMockHandler: true);
-      }
     } else if (api.location == ApiLocation.flutter) {
       _writeFlutterApi(opt, indent, api);
     }
   }
 }
+
+/// Generates Dart source code for test support libraries based on the
+/// given AST represented by [root], outputting the code to [sink].
+void generateTestDart(
+  DartOptions opt,
+  Root root,
+  StringSink sink,
+  String mainDartFile,
+) {
+  final Indent indent = Indent(sink);
+  indent.writeln('// $generatedCodeWarning');
+  indent.writeln('// $seeAlsoWarning');
+  indent.writeln(
+    '// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import',
+  );
+  indent.writeln('// @dart = ${opt.isNullSafe ? '2.10' : '2.8'}');
+  indent.writeln('import \'dart:async\';');
+  indent.writeln(
+    'import \'dart:typed_data\' show Uint8List, Int32List, Int64List, Float64List;',
+  );
+  indent.writeln('import \'package:flutter/services.dart\';');
+  indent.writeln('import \'package:flutter_test/flutter_test.dart\';');
+  indent.writeln('');
+  indent.writeln(
+    'import \'${_escapeForDartSingleQuotedString(mainDartFile)}\';',
+  );
+  for (Api api in root.apis) {
+    if (api.location == ApiLocation.host && api.dartHostTestHandler != null) {
+      final Api mockApi = Api(
+        name: api.dartHostTestHandler,
+        methods: api.methods,
+        location: ApiLocation.flutter,
+      );
+      indent.writeln('');
+      _writeFlutterApi(
+        opt,
+        indent,
+        mockApi,
+        channelNameFunc: (Method func) => makeChannelName(api, func),
+        isMockHandler: true,
+      );
+    }
+  }
+}
diff --git a/packages/pigeon/lib/generator_tools.dart b/packages/pigeon/lib/generator_tools.dart
index 447c370..f79b16a 100644
--- a/packages/pigeon/lib/generator_tools.dart
+++ b/packages/pigeon/lib/generator_tools.dart
@@ -36,16 +36,16 @@
   final String tab = '  ';
 
   /// Increase the indentation level.
-  void inc() {
-    _count += 1;
+  void inc([int level = 1]) {
+    _count += level;
   }
 
   /// Decrement the indentation level.
-  void dec() {
-    _count -= 1;
+  void dec([int level = 1]) {
+    _count -= level;
   }
 
-  /// Returns the String represneting the current indentation.
+  /// Returns the String representing the current indentation.
   String str() {
     String result = '';
     for (int i = 0; i < _count; i++) {
@@ -63,7 +63,12 @@
 
   /// Scoped increase of the ident level.  For the execution of [func] the
   /// indentation will be incremented.
-  void scoped(String begin, String end, Function func) {
+  void scoped(
+    String begin,
+    String end,
+    Function func, {
+    bool addTrailingNewline = true,
+  }) {
     if (begin != null) {
       _sink.write(begin + newline);
     }
@@ -71,28 +76,43 @@
     func();
     dec();
     if (end != null) {
-      _sink.write(str() + end + newline);
+      _sink.write(str() + end);
+      if (addTrailingNewline) {
+        _sink.write(newline);
+      }
     }
   }
 
-  /// Add [str] with indentation and a newline.
-  void writeln(String str) {
-    _sink.write(this.str() + str + newline);
+  /// Scoped increase of the ident level.  For the execution of [func] the
+  /// indentation will be incremented by the given amount.
+  void nest(int count, Function func) {
+    inc(count);
+    func();
+    dec(count);
   }
 
-  /// Add [str] with indentation.
-  void write(String str) {
-    _sink.write(this.str() + str);
+  /// Add [text] with indentation and a newline.
+  void writeln(String text) {
+    if (text.isEmpty) {
+      _sink.write(newline);
+    } else {
+      _sink.write(str() + text + newline);
+    }
   }
 
-  /// Add [str] with a newline.
-  void addln(String str) {
-    _sink.write(str + newline);
+  /// Add [text] with indentation.
+  void write(String text) {
+    _sink.write(str() + text);
   }
 
-  /// Just adds [str].
-  void add(String str) {
-    _sink.write(str);
+  /// Add [text] with a newline.
+  void addln(String text) {
+    _sink.write(text + newline);
+  }
+
+  /// Just adds [text].
+  void add(String text) {
+    _sink.write(text);
   }
 }
 
diff --git a/packages/pigeon/lib/pigeon_lib.dart b/packages/pigeon/lib/pigeon_lib.dart
index 79de499..22d3871 100644
--- a/packages/pigeon/lib/pigeon_lib.dart
+++ b/packages/pigeon/lib/pigeon_lib.dart
@@ -6,6 +6,7 @@
 import 'dart:mirrors';
 
 import 'package:args/args.dart';
+import 'package:path/path.dart' as path;
 import 'package:path/path.dart';
 import 'package:pigeon/java_generator.dart';
 
@@ -44,10 +45,13 @@
   /// Parametric constructor for [HostApi].
   const HostApi({this.dartHostTestHandler});
 
-  /// The name of an interface generated next to the [HostApi] class.  Implement
-  /// this interface and invoke `[name of this handler].setup` to receive calls
-  /// from your real [HostApi] class in Dart instead of the host platform code,
-  /// as is typical.
+  /// The name of an interface generated for tests. Implement this
+  /// interface and invoke `[name of this handler].setup` to receive
+  /// calls from your real [HostApi] class in Dart instead of the host
+  /// platform code, as is typical.
+  ///
+  /// When using this, you must specify the `--out_test_dart` argument
+  /// to specify where to generate the test file.
   ///
   /// Prefer to use a mock of the real [HostApi] with a mocking library for unit
   /// tests.  Generating this Dart handler is sometimes useful in integration
@@ -119,6 +123,9 @@
   /// Path to the dart file that will be generated.
   String dartOut;
 
+  /// Path to the dart file that will be generated for test support classes.
+  String dartTestOut;
+
   /// Path to the ".h" Objective-C file will be generated.
   String objcHeaderOut;
 
@@ -279,7 +286,10 @@
   static final ArgParser _argParser = ArgParser()
     ..addOption('input', help: 'REQUIRED: Path to pigeon file.')
     ..addOption('dart_out',
-        help: 'REQUIRED: Path to generated dart source file (.dart).')
+        help: 'REQUIRED: Path to generated Dart source file (.dart).')
+    ..addOption('dart_test_out',
+        help: 'Path to generated library for Dart tests, when using '
+            '@HostApi(dartHostTestHandler:).')
     ..addOption('objc_source_out',
         help: 'Path to generated Objective-C source file (.m).')
     ..addOption('java_out', help: 'Path to generated Java file (.java).')
@@ -299,6 +309,7 @@
     final PigeonOptions opts = PigeonOptions();
     opts.input = results['input'];
     opts.dartOut = results['dart_out'];
+    opts.dartTestOut = results['dart_test_out'];
     opts.objcHeaderOut = results['objc_header_out'];
     opts.objcSourceOut = results['objc_source_out'];
     opts.objcOptions.prefix = results['objc_prefix'];
@@ -372,6 +383,11 @@
     }
   }
 
+  static String _posixify(String input) {
+    final path.Context context = path.Context(style: path.Style.posix);
+    return context.fromUri(path.toUri(path.absolute(input)));
+  }
+
   /// The 'main' entrypoint used by the command-line tool.  [args] are the
   /// command-line arguments.
   static Future<int> run(List<String> args) async {
@@ -413,6 +429,21 @@
             (StringSink sink) =>
                 generateDart(options.dartOptions, parseResults.root, sink));
       }
+      if (options.dartTestOut != null) {
+        final String mainPath = context.relative(
+          _posixify(options.dartOut),
+          from: _posixify(path.dirname(options.dartTestOut)),
+        );
+        await _runGenerator(
+          options.dartTestOut,
+          (StringSink sink) => generateTestDart(
+            options.dartOptions,
+            parseResults.root,
+            sink,
+            mainPath,
+          ),
+        );
+      }
       if (options.objcHeaderOut != null) {
         await _runGenerator(
             options.objcHeaderOut,
diff --git a/packages/pigeon/mock_handler_tester/test/message.dart b/packages/pigeon/mock_handler_tester/test/message.dart
index 1aa33c6..2b051a3 100644
--- a/packages/pigeon/mock_handler_tester/test/message.dart
+++ b/packages/pigeon/mock_handler_tester/test/message.dart
@@ -1,29 +1,29 @@
-// Autogenerated from Pigeon (v0.1.2), do not edit directly.
+// Autogenerated from Pigeon (v0.1.15), do not edit directly.
 // See also: https://pub.dev/packages/pigeon
 // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import
+// @dart = 2.8
 import 'dart:async';
+import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
 import 'package:flutter/services.dart';
 
 class SearchReply {
   String result;
   String error;
+
   // ignore: unused_element
-  Map<dynamic, dynamic> _toMap() {
-    final Map<dynamic, dynamic> pigeonMap = <dynamic, dynamic>{};
+  Object encode() {
+    final Map<Object, Object> pigeonMap = <Object, Object>{};
     pigeonMap['result'] = result;
     pigeonMap['error'] = error;
     return pigeonMap;
   }
 
   // ignore: unused_element
-  static SearchReply _fromMap(Map<dynamic, dynamic> pigeonMap) {
-    if (pigeonMap == null) {
-      return null;
-    }
-    final SearchReply result = SearchReply();
-    result.result = pigeonMap['result'];
-    result.error = pigeonMap['error'];
-    return result;
+  static SearchReply decode(Object message) {
+    final Map<Object, Object> pigeonMap = message as Map<Object, Object>;
+    return SearchReply()
+      ..result = pigeonMap['result'] as String
+      ..error = pigeonMap['error'] as String;
   }
 }
 
@@ -31,9 +31,10 @@
   String query;
   int anInt;
   bool aBool;
+
   // ignore: unused_element
-  Map<dynamic, dynamic> _toMap() {
-    final Map<dynamic, dynamic> pigeonMap = <dynamic, dynamic>{};
+  Object encode() {
+    final Map<Object, Object> pigeonMap = <Object, Object>{};
     pigeonMap['query'] = query;
     pigeonMap['anInt'] = anInt;
     pigeonMap['aBool'] = aBool;
@@ -41,35 +42,32 @@
   }
 
   // ignore: unused_element
-  static SearchRequest _fromMap(Map<dynamic, dynamic> pigeonMap) {
-    if (pigeonMap == null) {
-      return null;
-    }
-    final SearchRequest result = SearchRequest();
-    result.query = pigeonMap['query'];
-    result.anInt = pigeonMap['anInt'];
-    result.aBool = pigeonMap['aBool'];
-    return result;
+  static SearchRequest decode(Object message) {
+    final Map<Object, Object> pigeonMap = message as Map<Object, Object>;
+    return SearchRequest()
+      ..query = pigeonMap['query'] as String
+      ..anInt = pigeonMap['anInt'] as int
+      ..aBool = pigeonMap['aBool'] as bool;
   }
 }
 
 class Nested {
   SearchRequest request;
+
   // ignore: unused_element
-  Map<dynamic, dynamic> _toMap() {
-    final Map<dynamic, dynamic> pigeonMap = <dynamic, dynamic>{};
-    pigeonMap['request'] = request == null ? null : request._toMap();
+  Object encode() {
+    final Map<Object, Object> pigeonMap = <Object, Object>{};
+    pigeonMap['request'] = request == null ? null : request.encode();
     return pigeonMap;
   }
 
   // ignore: unused_element
-  static Nested _fromMap(Map<dynamic, dynamic> pigeonMap) {
-    if (pigeonMap == null) {
-      return null;
-    }
-    final Nested result = Nested();
-    result.request = SearchRequest._fromMap(pigeonMap['request']);
-    return result;
+  static Nested decode(Object message) {
+    final Map<Object, Object> pigeonMap = message as Map<Object, Object>;
+    return Nested()
+      ..request = pigeonMap['request'] != null
+          ? SearchRequest.decode(pigeonMap['request'])
+          : null;
   }
 }
 
@@ -77,97 +75,74 @@
   SearchReply search(SearchRequest arg);
   static void setup(FlutterSearchApi api) {
     {
-      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+      const BasicMessageChannel<Object> channel = BasicMessageChannel<Object>(
           'dev.flutter.pigeon.FlutterSearchApi.search', StandardMessageCodec());
-      channel.setMessageHandler((dynamic message) async {
-        final Map<dynamic, dynamic> mapMessage =
-            message as Map<dynamic, dynamic>;
-        final SearchRequest input = SearchRequest._fromMap(mapMessage);
-        final SearchReply output = api.search(input);
-        return output._toMap();
-      });
+      if (api == null) {
+        channel.setMessageHandler(null);
+      } else {
+        channel.setMessageHandler((Object message) async {
+          if (message == null) {
+            return null;
+          }
+          final SearchRequest input = SearchRequest.decode(message);
+          final SearchReply output = api.search(input);
+          return output.encode();
+        });
+      }
     }
   }
 }
 
 class NestedApi {
   Future<SearchReply> search(Nested arg) async {
-    final Map<dynamic, dynamic> requestMap = arg._toMap();
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+    final Object encoded = arg.encode();
+    const BasicMessageChannel<Object> channel = BasicMessageChannel<Object>(
         'dev.flutter.pigeon.NestedApi.search', StandardMessageCodec());
-
-    final Map<dynamic, dynamic> replyMap = await channel.send(requestMap);
+    final Map<Object, Object> replyMap =
+        await channel.send(encoded) as Map<Object, Object>;
     if (replyMap == null) {
       throw PlatformException(
-          code: 'channel-error',
-          message: 'Unable to establish connection on channel.',
-          details: null);
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
     } else if (replyMap['error'] != null) {
-      final Map<dynamic, dynamic> error = replyMap['error'];
+      final Map<Object, Object> error =
+          replyMap['error'] as Map<Object, Object>;
       throw PlatformException(
-          code: error['code'],
-          message: error['message'],
-          details: error['details']);
+        code: error['code'] as String,
+        message: error['message'] as String,
+        details: error['details'],
+      );
     } else {
-      return SearchReply._fromMap(replyMap['result']);
-    }
-  }
-}
-
-abstract class TestNestedApi {
-  SearchReply search(Nested arg);
-  static void setup(TestNestedApi api) {
-    {
-      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-          'dev.flutter.pigeon.NestedApi.search', StandardMessageCodec());
-      channel.setMockMessageHandler((dynamic message) async {
-        final Map<dynamic, dynamic> mapMessage =
-            message as Map<dynamic, dynamic>;
-        final Nested input = Nested._fromMap(mapMessage);
-        final SearchReply output = api.search(input);
-        return <dynamic, dynamic>{'result': output._toMap()};
-      });
+      return SearchReply.decode(replyMap['result']);
     }
   }
 }
 
 class Api {
   Future<SearchReply> search(SearchRequest arg) async {
-    final Map<dynamic, dynamic> requestMap = arg._toMap();
-    const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
+    final Object encoded = arg.encode();
+    const BasicMessageChannel<Object> channel = BasicMessageChannel<Object>(
         'dev.flutter.pigeon.Api.search', StandardMessageCodec());
-
-    final Map<dynamic, dynamic> replyMap = await channel.send(requestMap);
+    final Map<Object, Object> replyMap =
+        await channel.send(encoded) as Map<Object, Object>;
     if (replyMap == null) {
       throw PlatformException(
-          code: 'channel-error',
-          message: 'Unable to establish connection on channel.',
-          details: null);
+        code: 'channel-error',
+        message: 'Unable to establish connection on channel.',
+        details: null,
+      );
     } else if (replyMap['error'] != null) {
-      final Map<dynamic, dynamic> error = replyMap['error'];
+      final Map<Object, Object> error =
+          replyMap['error'] as Map<Object, Object>;
       throw PlatformException(
-          code: error['code'],
-          message: error['message'],
-          details: error['details']);
+        code: error['code'] as String,
+        message: error['message'] as String,
+        details: error['details'],
+      );
     } else {
-      return SearchReply._fromMap(replyMap['result']);
-    }
-  }
-}
-
-abstract class TestHostApi {
-  SearchReply search(SearchRequest arg);
-  static void setup(TestHostApi api) {
-    {
-      const BasicMessageChannel<dynamic> channel = BasicMessageChannel<dynamic>(
-          'dev.flutter.pigeon.Api.search', StandardMessageCodec());
-      channel.setMockMessageHandler((dynamic message) async {
-        final Map<dynamic, dynamic> mapMessage =
-            message as Map<dynamic, dynamic>;
-        final SearchRequest input = SearchRequest._fromMap(mapMessage);
-        final SearchReply output = api.search(input);
-        return <dynamic, dynamic>{'result': output._toMap()};
-      });
+      return SearchReply.decode(replyMap['result']);
     }
   }
 }
diff --git a/packages/pigeon/mock_handler_tester/test/test.dart b/packages/pigeon/mock_handler_tester/test/test.dart
new file mode 100644
index 0000000..c2dc28e
--- /dev/null
+++ b/packages/pigeon/mock_handler_tester/test/test.dart
@@ -0,0 +1,54 @@
+// Autogenerated from Pigeon (v0.1.15), do not edit directly.
+// See also: https://pub.dev/packages/pigeon
+// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import
+// @dart = 2.8
+import 'dart:async';
+import 'dart:typed_data' show Uint8List, Int32List, Int64List, Float64List;
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'message.dart';
+
+abstract class TestNestedApi {
+  SearchReply search(Nested arg);
+  static void setup(TestNestedApi api) {
+    {
+      const BasicMessageChannel<Object> channel = BasicMessageChannel<Object>(
+          'dev.flutter.pigeon.NestedApi.search', StandardMessageCodec());
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object message) async {
+          if (message == null) {
+            return <Object, Object>{};
+          }
+          final Nested input = Nested.decode(message);
+          final SearchReply output = api.search(input);
+          return <Object, Object>{'result': output.encode()};
+        });
+      }
+    }
+  }
+}
+
+abstract class TestHostApi {
+  SearchReply search(SearchRequest arg);
+  static void setup(TestHostApi api) {
+    {
+      const BasicMessageChannel<Object> channel = BasicMessageChannel<Object>(
+          'dev.flutter.pigeon.Api.search', StandardMessageCodec());
+      if (api == null) {
+        channel.setMockMessageHandler(null);
+      } else {
+        channel.setMockMessageHandler((Object message) async {
+          if (message == null) {
+            return <Object, Object>{};
+          }
+          final SearchRequest input = SearchRequest.decode(message);
+          final SearchReply output = api.search(input);
+          return <Object, Object>{'result': output.encode()};
+        });
+      }
+    }
+  }
+}
diff --git a/packages/pigeon/mock_handler_tester/test/widget_test.dart b/packages/pigeon/mock_handler_tester/test/widget_test.dart
index 41322b3..7fba290 100644
--- a/packages/pigeon/mock_handler_tester/test/widget_test.dart
+++ b/packages/pigeon/mock_handler_tester/test/widget_test.dart
@@ -3,7 +3,9 @@
 // found in the LICENSE file.
 
 import 'package:flutter_test/flutter_test.dart';
+
 import 'message.dart';
+import 'test.dart';
 
 class Mock implements TestHostApi {
   bool didCall = false;
diff --git a/packages/pigeon/run_tests.sh b/packages/pigeon/run_tests.sh
index 0ebfb4a..b740f5f 100755
--- a/packages/pigeon/run_tests.sh
+++ b/packages/pigeon/run_tests.sh
@@ -19,12 +19,19 @@
 # Functions
 ###############################################################################
 
+# Create a temporary directory in a way that works on both Linux and macOS.
+#
+# The mktemp commands have slighly semantics on the BSD systems vs GNU systems.
+mktmpdir() {
+  mktemp -d flutter_pigeon.XXXXXX 2>/dev/null || mktemp -d -t flutter_pigeon.
+}
+
 # test_pigeon_ios(<path to pigeon file>)
 #
 # Compiles the pigeon file to a temp directory and attempts to compile the code
 # and runs the dart analyzer on the generated dart code.
 test_pigeon_ios() {
-  temp_dir=$(mktemp -d -t pigeon)
+  temp_dir=$(mktmpdir)
 
   pub run pigeon \
     --input $1 \
@@ -41,8 +48,6 @@
     -c $temp_dir/pigeon.m \
     -o $temp_dir/pigeon.o
 
-  dartfmt -w $temp_dir/pigeon.dart
-  dartanalyzer $temp_dir/pigeon.dart --fatal-infos --fatal-warnings --packages ./e2e_tests/test_objc/.packages
   rm -rf $temp_dir
 }
 
@@ -50,7 +55,7 @@
 #
 # Compiles the pigeon file to a temp directory and attempts to compile the code.
 test_pigeon_android() {
-  temp_dir=$(mktemp -d -t pigeon)
+  temp_dir=$(mktmpdir)
 
   pub run pigeon \
     --input $1 \
@@ -64,6 +69,9 @@
     exit 1
   fi
 
+  dartfmt -w $temp_dir/pigeon.dart
+  dartanalyzer $temp_dir/pigeon.dart --fatal-infos --fatal-warnings --packages ./e2e_tests/test_objc/.packages
+
   rm -rf $temp_dir
 }
 
@@ -72,7 +80,7 @@
 # Compiles the pigeon file to a temp directory and attempts to run the dart
 # analyzer on it with null safety turned on.
 test_null_safe_dart() {
-  temp_dir=$(mktemp -d -t pigeon)
+  temp_dir=$(mktmpdir)
 
   pub run pigeon \
     --input $1 \
@@ -84,9 +92,10 @@
 }
 
 ###############################################################################
-# Dart unit tests
+# Dart analysis and unit tests
 ###############################################################################
 pub get
+dartanalyzer bin lib
 pub run test test/
 
 ###############################################################################
@@ -95,6 +104,20 @@
 pub run pigeon 1> /dev/null
 
 ###############################################################################
+# Mock handler flutter tests.
+###############################################################################
+pushd $PWD
+pub run pigeon \
+  --input pigeons/message.dart \
+  --dart_out mock_handler_tester/test/message.dart \
+  --dart_test_out mock_handler_tester/test/test.dart
+dartfmt -w mock_handler_tester/test/message.dart
+dartfmt -w mock_handler_tester/test/test.dart
+cd mock_handler_tester
+flutter test
+popd
+
+###############################################################################
 # Compilation tests (Code is generated and compiled)
 ###############################################################################
 # Make sure the artifacts are present.
@@ -126,18 +149,6 @@
 # test_pigeon_ios ./pigeons/async_handlers.dart
 
 ###############################################################################
-# Mock handler flutter tests.
-###############################################################################
-pushd $PWD
-pub run pigeon \
-  --input pigeons/message.dart \
-  --dart_out mock_handler_tester/test/message.dart
-dartfmt -w mock_handler_tester/test/message.dart
-cd mock_handler_tester
-flutter test
-popd
-
-###############################################################################
 # iOS unit tests on generated code.
 ###############################################################################
 pub run pigeon \
diff --git a/packages/pigeon/test/dart_generator_test.dart b/packages/pigeon/test/dart_generator_test.dart
index b90e60f..075e575 100644
--- a/packages/pigeon/test/dart_generator_test.dart
+++ b/packages/pigeon/test/dart_generator_test.dart
@@ -54,23 +54,29 @@
   test('nested class', () {
     final Root root = Root(apis: <Api>[], classes: <Class>[
       Class(
-          name: 'Input',
-          fields: <Field>[Field(name: 'input', dataType: 'String')]),
+        name: 'Input',
+        fields: <Field>[Field(name: 'input', dataType: 'String')],
+      ),
       Class(
-          name: 'Nested',
-          fields: <Field>[Field(name: 'nested', dataType: 'Input')])
+        name: 'Nested',
+        fields: <Field>[Field(name: 'nested', dataType: 'Input')],
+      )
     ]);
     final StringBuffer sink = StringBuffer();
     generateDart(DartOptions(), root, sink);
     final String code = sink.toString();
     expect(
-        code,
-        contains(
-            'pigeonMap[\'nested\'] = nested == null ? null : nested._toMap()'));
+      code,
+      contains(
+        'pigeonMap[\'nested\'] = nested == null ? null : nested.encode()',
+      ),
+    );
     expect(
-        code,
-        contains(
-            'result.nested = pigeonMap[\'nested\'] != null ? Input._fromMap(pigeonMap[\'nested\']) : null;'));
+      code,
+      contains(
+        '..nested = pigeonMap[\'nested\'] != null ? Input.decode(pigeonMap[\'nested\']) : null;',
+      ),
+    );
   });
 
   test('flutterapi', () {
@@ -140,7 +146,7 @@
     final String code = sink.toString();
     expect(code, isNot(matches('=.*doSomething')));
     expect(code, contains('doSomething('));
-    expect(code, isNot(contains('._toMap()')));
+    expect(code, isNot(contains('.encode()')));
   });
 
   test('flutter void argument', () {
@@ -214,13 +220,24 @@
           name: 'Output',
           fields: <Field>[Field(name: 'output', dataType: 'String')])
     ]);
-    final StringBuffer sink = StringBuffer();
-    generateDart(DartOptions(), root, sink);
-    final String code = sink.toString();
-    expect(code, matches('abstract class ApiMock'));
-    expect(code, isNot(matches('\.ApiMock\.doSomething')));
-    expect(code, matches('\'${Keys.result}\': output._toMap()'));
-    expect(code, contains('return <dynamic, dynamic>{};'));
+    final StringBuffer mainCodeSink = StringBuffer();
+    final StringBuffer testCodeSink = StringBuffer();
+    generateDart(DartOptions(), root, mainCodeSink);
+    final String mainCode = mainCodeSink.toString();
+    expect(mainCode, isNot(contains('import \'fo\\\'o.dart\';')));
+    expect(mainCode, contains('class Api {'));
+    expect(mainCode, isNot(contains('abstract class ApiMock')));
+    expect(mainCode, isNot(contains('\.ApiMock\.doSomething')));
+    expect(mainCode, isNot(contains('\'${Keys.result}\': output.encode()')));
+    expect(mainCode, isNot(contains('return <Object, Object>{};')));
+    generateTestDart(DartOptions(), root, testCodeSink, "fo'o.dart");
+    final String testCode = testCodeSink.toString();
+    expect(testCode, contains('import \'fo\\\'o.dart\';'));
+    expect(testCode, isNot(contains('class Api {')));
+    expect(testCode, contains('abstract class ApiMock'));
+    expect(testCode, isNot(contains('\.ApiMock\.doSomething')));
+    expect(testCode, contains('\'${Keys.result}\': output.encode()'));
+    expect(testCode, contains('return <Object, Object>{};'));
   });
 
   test('opt out of nndb', () {