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', () {