[pigeon] Add local-only Java integration tests (#2854)

* Move generated code to a new shared bundle.

* Consolidate the example app code

* Replace stale TODOs with explanatory comments

* Move integration tests

* Add Java implementation

* Add test script commands

* Autoformat
This commit is contained in:
stuartmorgan
2022-11-28 21:24:04 -05:00
committed by GitHub
parent e7c3837f00
commit f57e62cc70
23 changed files with 303 additions and 201 deletions

View File

@ -5,16 +5,39 @@
package com.example.alternate_language_test_plugin;
import androidx.annotation.NonNull;
import com.example.alternate_language_test_plugin.AllDatatypes.Everything;
import com.example.alternate_language_test_plugin.AllDatatypes.HostEverything;
import com.example.alternate_language_test_plugin.AllVoid.AllVoidHostApi;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
/**
* This plugin is currently a no-op since only unit tests have been set up. In the future, this will
* register Pigeon APIs used in integration tests.
*/
public class AlternateLanguageTestPlugin implements FlutterPlugin {
/** This plugin handles the native side of the integration tests in example/integration_test/. */
public class AlternateLanguageTestPlugin implements FlutterPlugin, AllVoidHostApi, HostEverything {
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {}
public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
AllVoidHostApi.setup(binding.getBinaryMessenger(), this);
HostEverything.setup(binding.getBinaryMessenger(), this);
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
// AllVoidHostApi
@Override
public void doit() {
// No-op.
}
// HostEverything
@Override
public @NonNull Everything giveMeEverything() {
// Currently unused in integration tests, so just return an empty object.
return new Everything();
}
@Override
public @NonNull Everything echo(@NonNull Everything everything) {
return everything;
}
}

View File

@ -0,0 +1,7 @@
// Copyright 2013 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.
import 'package:shared_test_plugin_code/integration_tests.dart';
void main() => runPigeonIntegrationTests();

View File

@ -2,51 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: public_member_api_docs
import 'package:alternate_language_test_plugin/alternate_language_test_plugin.dart';
import 'package:flutter/material.dart';
import 'package:shared_test_plugin_code/example_app.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// ignore: unused_field
final AlternateLanguageTestPlugin _alternateLanguageTestPlugin =
AlternateLanguageTestPlugin();
@override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
// TODO(tarrinneal): Call TestPlugin methods here for manual integration
// testing, once they exist. See
// https://github.com/flutter/flutter/issues/111505
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Pigeon integration tests'),
),
body: const Center(
child: Text(
'TODO, see https://github.com/flutter/flutter/issues/111505'),
),
),
);
}
runApp(const ExampleApp());
}

View File

@ -10,10 +10,14 @@ dependencies:
path: ../
flutter:
sdk: flutter
shared_test_plugin_code:
path: ../../shared_test_plugin_code
dev_dependencies:
flutter_test:
sdk: flutter
integration_test:
sdk: flutter
flutter:
uses-material-design: true

View File

@ -1,9 +0,0 @@
# TODO(stuartmorgan): Remove this, so that review will show the effects of
# changes on generated files. This will need a way to avoid unnecessary churn,
# such as a flag to suppress version stamp generation.
*.gen.dart
# TODO(stuartmorgan): Add exceptions for specific files that are used in
# integration tests, as they will need to be checked in to avoid analysis
# failures. The exclusion of other files is to prevent having multiple
# copies of all of the Dart output until more tests are restructured to
# minimize duplication.

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(tarrinneal): Add integration tests.
/// A host plugin for Pigeon tests.
class AlternateLanguageTestPlugin {}
// There is intentionally no code here; tests use generated Pigeon APIs
// directly, as wrapping them in a plugin would just add maintenance burden
// when changing tests.

View File

@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

View File

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: c940f318226901f7ba9ccb68f216756ced2fe3c7
channel: main
project_type: package

View File

@ -0,0 +1,7 @@
This package contains the shared code (generated Pigeon output, example app,
integration tests) for `test_plugin` and `alternate_language_shared_plugin`.
Since those two plugin projects are intended to be identical, and only exist
as separate projects because of the Java/Kotlin and Obj-C/Swift overlap that
prevents combining them, almost all Dart code should be in this package rather
than in the plugins themselves.

View File

@ -0,0 +1,66 @@
// Copyright 2013 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.
import 'package:flutter/material.dart';
import 'generated.dart';
void main() {
runApp(const ExampleApp());
}
/// A trivial example that validates that Pigeon is able to successfully call
/// into native code.
///
/// Actual testing is all done in the integration tests, which run in the
/// context of the example app but don't actually rely on this class.
class ExampleApp extends StatefulWidget {
/// Creates a new example app.
const ExampleApp({super.key});
@override
State<ExampleApp> createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
// This just uses a single API since it's intended only a minimal check
// that the basics are wired up correctly.
late final AllVoidHostApi api;
String status = 'Calling...';
@override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
api = AllVoidHostApi();
try {
await api.doit();
} catch (e) {
setState(() {
status = 'Failed: $e';
});
return;
}
setState(() {
status = 'Success!';
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Pigeon integration tests'),
),
body: Center(
child: Text(status),
),
),
);
}
}

View File

@ -0,0 +1,6 @@
// Copyright 2013 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.
export 'src/generated/all_datatypes.gen.dart';
export 'src/generated/all_void.gen.dart';

View File

@ -0,0 +1,80 @@
// Copyright 2013 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.
// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231)
// ignore: unnecessary_import
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'generated.dart';
/// Sets up and runs the integration tests.
void runPigeonIntegrationTests() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Host API tests', () {
testWidgets('voidCallVoidReturn', (WidgetTester _) async {
final AllVoidHostApi api = AllVoidHostApi();
expect(api.doit(), completes);
});
testWidgets('allDataTypesEcho', (WidgetTester _) async {
final HostEverything api = HostEverything();
final Everything sentObject = Everything(
aBool: true,
anInt: 42,
aDouble: 3.14159,
aString: 'Hello host!',
aByteArray: Uint8List.fromList(<int>[1, 2, 3]),
a4ByteArray: Int32List.fromList(<int>[4, 5, 6]),
a8ByteArray: Int64List.fromList(<int>[7, 8, 9]),
aFloatArray: Float64List.fromList(<double>[2.71828, 3.14159]),
aList: <Object?>['Thing 1', 2],
aMap: <Object?, Object?>{'a': 1, 'b': 2.0},
nestedList: <List<bool>>[
<bool>[true, false],
<bool>[false, true]
],
);
final Everything echoObject = await api.echo(sentObject);
expect(echoObject.aBool, sentObject.aBool);
expect(echoObject.anInt, sentObject.anInt);
expect(echoObject.aDouble, sentObject.aDouble);
expect(echoObject.aString, sentObject.aString);
// TODO(stuartmorgan): Enable these once they work for all generators;
// currently at least Swift is broken.
// See https://github.com/flutter/flutter/issues/115906
//expect(echoObject.aByteArray, sentObject.aByteArray);
//expect(echoObject.a4ByteArray, sentObject.a4ByteArray);
//expect(echoObject.a8ByteArray, sentObject.a8ByteArray);
//expect(echoObject.aFloatArray, sentObject.aFloatArray);
expect(listEquals(echoObject.aList, sentObject.aList), true);
expect(mapEquals(echoObject.aMap, sentObject.aMap), true);
expect(echoObject.nestedList?.length, sentObject.nestedList?.length);
// TODO(stuartmorgan): Enable this once the Dart types are fixed; see
// https://github.com/flutter/flutter/issues/116117
//for (int i = 0; i < echoObject.nestedList!.length; i++) {
// expect(listEquals(echoObject.nestedList![i], sentObject.nestedList![i]),
// true);
//}
expect(
mapEquals(
echoObject.mapWithAnnotations, sentObject.mapWithAnnotations),
true);
expect(
mapEquals(echoObject.mapWithObject, sentObject.mapWithObject), true);
});
});
group('Flutter API tests', () {
// TODO(stuartmorgan): Add Flutter API tests, driven by wrapper host APIs
// that forward the arguments and return values in the opposite direction.
});
}

View File

@ -0,0 +1,19 @@
name: shared_test_plugin_code
description: Common code for test_plugin and altenate_language_test_plugin
version: 0.0.1
publish_to: none
environment:
sdk: '>=2.18.0 <3.0.0'
flutter: ">=3.0.0"
dependencies:
flutter:
sdk: flutter
# These are normal dependencies rather than dev_dependencies because the
# package exports the integration test code to be shared by the integration
# tests for the two plugins.
flutter_test:
sdk: flutter
integration_test:
sdk: flutter

View File

@ -2,78 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231)
// ignore: unnecessary_import
import 'dart:typed_data';
import 'package:shared_test_plugin_code/integration_tests.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:test_plugin/all_datatypes.gen.dart';
import 'package:test_plugin/all_void.gen.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Host API tests', () {
testWidgets('voidCallVoidReturn', (WidgetTester _) async {
final AllVoidHostApi api = AllVoidHostApi();
expect(api.doit(), completes);
});
testWidgets('allDataTypesEcho', (WidgetTester _) async {
final HostEverything api = HostEverything();
final Everything sentObject = Everything(
aBool: true,
anInt: 42,
aDouble: 3.14159,
aString: 'Hello host!',
aByteArray: Uint8List.fromList(<int>[1, 2, 3]),
a4ByteArray: Int32List.fromList(<int>[4, 5, 6]),
a8ByteArray: Int64List.fromList(<int>[7, 8, 9]),
aFloatArray: Float64List.fromList(<double>[2.71828, 3.14159]),
aList: <Object?>['Thing 1', 2],
aMap: <Object?, Object?>{'a': 1, 'b': 2.0},
nestedList: <List<bool>>[
<bool>[true, false],
<bool>[false, true]
],
);
final Everything echoObject = await api.echo(sentObject);
expect(echoObject.aBool, sentObject.aBool);
expect(echoObject.anInt, sentObject.anInt);
expect(echoObject.aDouble, sentObject.aDouble);
expect(echoObject.aString, sentObject.aString);
// TODO(stuartmorgan): Enable these once they work for all generators;
// currently at least Swift is broken.
// See https://github.com/flutter/flutter/issues/115906
//expect(echoObject.aByteArray, sentObject.aByteArray);
//expect(echoObject.a4ByteArray, sentObject.a4ByteArray);
//expect(echoObject.a8ByteArray, sentObject.a8ByteArray);
//expect(echoObject.aFloatArray, sentObject.aFloatArray);
expect(listEquals(echoObject.aList, sentObject.aList), true);
expect(mapEquals(echoObject.aMap, sentObject.aMap), true);
expect(echoObject.nestedList?.length, sentObject.nestedList?.length);
// TODO(stuartmorgan): Enable this once the Dart types are fixed; see
// https://github.com/flutter/flutter/issues/116117
//for (int i = 0; i < echoObject.nestedList!.length; i++) {
// expect(listEquals(echoObject.nestedList![i], sentObject.nestedList![i]),
// true);
//}
expect(
mapEquals(
echoObject.mapWithAnnotations, sentObject.mapWithAnnotations),
true);
expect(
mapEquals(echoObject.mapWithObject, sentObject.mapWithObject), true);
});
});
group('Flutter API tests', () {
// TODO(stuartmorgan): Add Flutter API tests, driven by wrapper host APIs
// that forward the arguments and return values in the opposite direction.
});
}
void main() => runPigeonIntegrationTests();

View File

@ -2,61 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// ignore_for_file: public_member_api_docs
import 'package:flutter/material.dart';
import 'package:test_plugin/all_void.gen.dart';
import 'package:test_plugin/test_plugin.dart';
import 'package:shared_test_plugin_code/example_app.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// ignore: unused_field
final TestPlugin _testPlugin = TestPlugin();
late final AllVoidHostApi api;
String status = 'Calling...';
@override
void initState() {
super.initState();
initPlatformState();
}
Future<void> initPlatformState() async {
api = AllVoidHostApi();
try {
await api.doit();
} catch (e) {
setState(() {
status = 'Failed: $e';
});
return;
}
setState(() {
status = 'Success!';
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Pigeon integration tests'),
),
body: Center(
child: Text(status),
),
),
);
}
runApp(const ExampleApp());
}

View File

@ -8,6 +8,8 @@ environment:
dependencies:
flutter:
sdk: flutter
shared_test_plugin_code:
path: ../../shared_test_plugin_code
test_plugin:
path: ../

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(tarrinneal): Add integration tests.
/// A host plugin for Pigeon tests.
class TestPlugin {}
// There is intentionally no code here; tests use generated Pigeon APIs
// directly, as wrapping them in a plugin would just add maintenance burden
// when changing tests.

View File

@ -251,6 +251,10 @@ run_android_unittests() {
popd
}
run_android_java_e2e_tests() {
dart run tool/run_tests.dart -t android_java_integration_tests --skip-generation
}
###############################################################################
# main
###############################################################################
@ -267,6 +271,7 @@ should_run_macos_swift_e2e_tests=true
should_run_android_kotlin_unittests=true
# Default to false until there is CI support. See
# https://github.com/flutter/flutter/issues/111505
should_run_android_java_e2e_tests=false
should_run_android_kotlin_e2e_tests=false
while getopts "t:l?h" opt; do
case $opt in
@ -282,10 +287,12 @@ while getopts "t:l?h" opt; do
should_run_macos_swift_unittests=false
should_run_macos_swift_e2e_tests=false
should_run_android_kotlin_unittests=false
should_run_android_java_e2e_tests=false
should_run_android_kotlin_e2e_tests=false
case $OPTARG in
# TODO(stuartmorgan): Rename to include "java".
android_unittests) should_run_android_unittests=true ;;
android_java_e2e_tests) should_run_android_java_e2e_tests=true ;;
dart_compilation_tests) should_run_dart_compilation_tests=true ;;
dart_unittests) should_run_dart_unittests=true ;;
flutter_unittests) should_run_flutter_unittests=true ;;
@ -307,6 +314,7 @@ while getopts "t:l?h" opt; do
l)
echo "available tests for -t:
android_unittests - Unit tests on generated Java code.
android_java_e2e_tests - Integration tests on generated Java code on Android.
android_kotlin_unittests - Unit tests on generated Kotlin code on Android.
android_kotlin_e2e_tests - Integration tests on generated Kotlin code on Android.
dart_compilation_tests - Compilation tests on generated Dart code.
@ -371,6 +379,9 @@ fi
if [ "$should_run_android_unittests" = true ]; then
run_android_unittests
fi
if [ "$should_run_android_java_e2e_tests" = true ]; then
run_android_java_e2e_tests
fi
if [ "$should_run_macos_swift_unittests" = true ]; then
run_macos_swift_unittests
fi

View File

@ -28,6 +28,8 @@ const String _skipGenerationFlag = 'skip-generation';
const int _noDeviceAvailableExitCode = 100;
const String _testPluginRelativePath = 'platform_tests/test_plugin';
const String _alternateLanguageTestPluginRelativePath =
'platform_tests/test_plugin';
const String _integrationTestFileRelativePath = 'integration_test/test.dart';
@immutable
@ -44,9 +46,12 @@ const Map<String, _TestInfo> _tests = <String, _TestInfo>{
'windows_integration_tests': _TestInfo(
function: _runWindowsIntegrationTests,
description: 'Integration tests on generated Windows C++ code.'),
'android_unittests': _TestInfo(
function: _runAndroidUnitTests,
'android_java_unittests': _TestInfo(
function: _runAndroidJavaUnitTests,
description: 'Unit tests on generated Java code.'),
'android_java_integration_tests': _TestInfo(
function: _runAndroidJavaIntegrationTests,
description: 'Integration tests on generated Java code.'),
'android_kotlin_unittests': _TestInfo(
function: _runAndroidKotlinUnitTests,
description: 'Unit tests on generated Kotlin code.'),
@ -79,10 +84,14 @@ const Map<String, _TestInfo> _tests = <String, _TestInfo>{
description: 'Unit tests on generated Dart mock handler code.'),
};
Future<int> _runAndroidUnitTests() async {
Future<int> _runAndroidJavaUnitTests() async {
throw UnimplementedError('See run_tests.sh.');
}
Future<int> _runAndroidJavaIntegrationTests() async {
return _runAndroidIntegrationTests(_alternateLanguageTestPluginRelativePath);
}
Future<int> _runAndroidKotlinUnitTests() async {
const String examplePath = './$_testPluginRelativePath/example';
const String androidProjectPath = '$examplePath/android';
@ -98,6 +107,10 @@ Future<int> _runAndroidKotlinUnitTests() async {
}
Future<int> _runAndroidKotlinIntegrationTests() async {
return _runAndroidIntegrationTests(_testPluginRelativePath);
}
Future<int> _runAndroidIntegrationTests(String testPluginPath) async {
final String? device = await getDeviceForPlatform('android');
if (device == null) {
print('No Android device available. Attach an Android device or start '
@ -105,7 +118,7 @@ Future<int> _runAndroidKotlinIntegrationTests() async {
return _noDeviceAvailableExitCode;
}
const String examplePath = './$_testPluginRelativePath/example';
final String examplePath = './$testPluginPath/example';
return runFlutterCommand(
examplePath,
'test',

View File

@ -77,6 +77,8 @@ Future<int> generatePigeons({required String baseDir}) async {
final String outputBase = p.join(baseDir, 'platform_tests', 'test_plugin');
final String alternateOutputBase =
p.join(baseDir, 'platform_tests', 'alternate_language_test_plugin');
final String sharedDartOutputBase =
p.join(baseDir, 'platform_tests', 'shared_test_plugin_code');
// TODO(stuartmorgan): Eliminate this and use alternateOutputBase.
// See https://github.com/flutter/packages/pull/2816.
final String iosObjCBase =
@ -90,7 +92,7 @@ Future<int> generatePigeons({required String baseDir}) async {
// Generate the default language test plugin output.
int generateCode = await runPigeon(
input: './pigeons/$input.dart',
dartOut: '$outputBase/lib/$input.gen.dart',
dartOut: '$sharedDartOutputBase/lib/src/generated/$input.gen.dart',
// Android
kotlinOut: skipLanguages.contains(GeneratorLanguages.kotlin)
? null
@ -129,7 +131,6 @@ Future<int> generatePigeons({required String baseDir}) async {
// Generate the alternate language test plugin output.
generateCode = await runPigeon(
input: './pigeons/$input.dart',
dartOut: '$alternateOutputBase/lib/$input.gen.dart',
// Android
// This doesn't use the '.gen' suffix since Java has strict file naming
// rules.