diff --git a/dev/automated_tests/pubspec.yaml b/dev/automated_tests/pubspec.yaml index 2a24d66591..615f9addad 100644 --- a/dev/automated_tests/pubspec.yaml +++ b/dev/automated_tests/pubspec.yaml @@ -7,7 +7,7 @@ dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -58,4 +58,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 633a +# PUBSPEC CHECKSUM: d33b diff --git a/dev/benchmarks/complex_layout/pubspec.yaml b/dev/benchmarks/complex_layout/pubspec.yaml index 8e8f4c2033..7515444007 100644 --- a/dev/benchmarks/complex_layout/pubspec.yaml +++ b/dev/benchmarks/complex_layout/pubspec.yaml @@ -37,7 +37,7 @@ dev_dependencies: sdk: flutter args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.14.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -79,4 +79,4 @@ flutter: - packages/flutter_gallery_assets/ali_connors_sml.png - packages/flutter_gallery_assets/top_10_australian_beaches.png -# PUBSPEC CHECKSUM: e1fb +# PUBSPEC CHECKSUM: c6fc diff --git a/dev/benchmarks/microbenchmarks/pubspec.yaml b/dev/benchmarks/microbenchmarks/pubspec.yaml index e1d5f1d428..599cbb71a8 100644 --- a/dev/benchmarks/microbenchmarks/pubspec.yaml +++ b/dev/benchmarks/microbenchmarks/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -65,4 +65,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 4bb8 +# PUBSPEC CHECKSUM: 48b9 diff --git a/dev/bots/pubspec.yaml b/dev/bots/pubspec.yaml index d54cc6c737..29660bd595 100644 --- a/dev/bots/pubspec.yaml +++ b/dev/bots/pubspec.yaml @@ -23,7 +23,7 @@ dev_dependencies: test: 0.12.32+2 mockito: 2.2.3 - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -59,4 +59,4 @@ dev_dependencies: web_socket_channel: 1.0.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.1.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: f192 +# PUBSPEC CHECKSUM: 9a93 diff --git a/dev/bots/test.dart b/dev/bots/test.dart index 6a00621d56..14bf481bec 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -189,6 +189,8 @@ Future _runTests({List options: const []}) async { await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'), options: options); await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), options: options); await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), options: options); + await _runFlutterTest(path.join(flutterRoot, 'packages', + 'fuchsia_remote_debug_protocol'), options: options); await _pubRunTest(path.join(flutterRoot, 'packages', 'flutter_tools')); await _pubRunTest(path.join(flutterRoot, 'dev', 'bots')); diff --git a/dev/devicelab/pubspec.yaml b/dev/devicelab/pubspec.yaml index 5d6dc7acee..8ba4a96cc0 100644 --- a/dev/devicelab/pubspec.yaml +++ b/dev/devicelab/pubspec.yaml @@ -39,7 +39,7 @@ dev_dependencies: # See packages/flutter_test/pubspec.yaml for why we're pinning this version. test: 0.12.32+2 - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.14.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -71,4 +71,4 @@ dev_dependencies: watcher: 0.9.7+7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.1.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: a29e +# PUBSPEC CHECKSUM: e59f diff --git a/dev/integration_tests/ui/pubspec.yaml b/dev/integration_tests/ui/pubspec.yaml index 67f698b589..9bd4af8753 100644 --- a/dev/integration_tests/ui/pubspec.yaml +++ b/dev/integration_tests/ui/pubspec.yaml @@ -38,7 +38,7 @@ dev_dependencies: sdk: flutter test: 0.12.32+2 - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.14.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -74,4 +74,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: a8a4 +# PUBSPEC CHECKSUM: f1a5 diff --git a/dev/manual_tests/pubspec.yaml b/dev/manual_tests/pubspec.yaml index d6e751e53c..4c533e556e 100644 --- a/dev/manual_tests/pubspec.yaml +++ b/dev/manual_tests/pubspec.yaml @@ -17,7 +17,7 @@ dev_dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -64,4 +64,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 1427 +# PUBSPEC CHECKSUM: 9c28 diff --git a/dev/tools/pubspec.yaml b/dev/tools/pubspec.yaml index 7b36930020..2b5f504873 100644 --- a/dev/tools/pubspec.yaml +++ b/dev/tools/pubspec.yaml @@ -26,7 +26,7 @@ dev_dependencies: test: 0.12.32+2 mockito: 2.2.3 - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" csslib: 0.14.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -60,4 +60,4 @@ dev_dependencies: web_socket_channel: 1.0.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.1.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: d99b +# PUBSPEC CHECKSUM: 829c diff --git a/dev/tools/vitool/pubspec.yaml b/dev/tools/vitool/pubspec.yaml index 9dcc45f8b9..83d2192efd 100644 --- a/dev/tools/vitool/pubspec.yaml +++ b/dev/tools/vitool/pubspec.yaml @@ -22,7 +22,7 @@ dev_dependencies: test: 0.12.32+2 async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -63,4 +63,4 @@ dev_dependencies: web_socket_channel: 1.0.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.1.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: c269 +# PUBSPEC CHECKSUM: 4b6a diff --git a/examples/catalog/pubspec.yaml b/examples/catalog/pubspec.yaml index ba16f400f7..a3384a4222 100644 --- a/examples/catalog/pubspec.yaml +++ b/examples/catalog/pubspec.yaml @@ -18,7 +18,7 @@ dev_dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -68,4 +68,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: e1fb +# PUBSPEC CHECKSUM: c6fc diff --git a/examples/flutter_gallery/pubspec.yaml b/examples/flutter_gallery/pubspec.yaml index 38bc9ecfbb..8f99388246 100644 --- a/examples/flutter_gallery/pubspec.yaml +++ b/examples/flutter_gallery/pubspec.yaml @@ -32,7 +32,7 @@ dev_dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -166,4 +166,4 @@ flutter: fonts: - asset: packages/flutter_gallery_assets/shrine/fonts/abrilfatface/AbrilFatface-Regular.ttf -# PUBSPEC CHECKSUM: 5fb8 +# PUBSPEC CHECKSUM: f2b9 diff --git a/examples/hello_world/pubspec.yaml b/examples/hello_world/pubspec.yaml index 08a6552de8..f8cd918b6d 100644 --- a/examples/hello_world/pubspec.yaml +++ b/examples/hello_world/pubspec.yaml @@ -15,7 +15,7 @@ dev_dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -62,4 +62,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 633a +# PUBSPEC CHECKSUM: d33b diff --git a/examples/layers/pubspec.yaml b/examples/layers/pubspec.yaml index bfa96e5fcc..a9d16b279c 100644 --- a/examples/layers/pubspec.yaml +++ b/examples/layers/pubspec.yaml @@ -14,7 +14,7 @@ dev_dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -63,4 +63,4 @@ flutter: - services/data.json uses-material-design: true -# PUBSPEC CHECKSUM: 633a +# PUBSPEC CHECKSUM: d33b diff --git a/examples/platform_channel/pubspec.yaml b/examples/platform_channel/pubspec.yaml index a4e967dd0c..ad1d6dd708 100644 --- a/examples/platform_channel/pubspec.yaml +++ b/examples/platform_channel/pubspec.yaml @@ -17,7 +17,7 @@ dev_dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -68,4 +68,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: e1fb +# PUBSPEC CHECKSUM: c6fc diff --git a/examples/platform_channel_swift/pubspec.yaml b/examples/platform_channel_swift/pubspec.yaml index 01493d6ec5..33e9dc3232 100644 --- a/examples/platform_channel_swift/pubspec.yaml +++ b/examples/platform_channel_swift/pubspec.yaml @@ -17,7 +17,7 @@ dev_dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -68,4 +68,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: e1fb +# PUBSPEC CHECKSUM: c6fc diff --git a/examples/stocks/pubspec.yaml b/examples/stocks/pubspec.yaml index 038aca16c4..bdf15fea59 100644 --- a/examples/stocks/pubspec.yaml +++ b/examples/stocks/pubspec.yaml @@ -11,7 +11,7 @@ dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.14.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -72,4 +72,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: b601 +# PUBSPEC CHECKSUM: 802 diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index 48ccb8f91a..e2f5df354a 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -20,7 +20,7 @@ dev_dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -67,4 +67,4 @@ dev_dependencies: environment: sdk: '>=1.19.0 <2.0.0' -# PUBSPEC CHECKSUM: 1427 +# PUBSPEC CHECKSUM: 9c28 diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index c1a637765d..3dd98b7bda 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -69,4 +69,4 @@ dev_dependencies: mockito: 2.2.3 quiver: 0.28.0 -# PUBSPEC CHECKSUM: 77e8 +# PUBSPEC CHECKSUM: 74e9 diff --git a/packages/flutter_localizations/pubspec.yaml b/packages/flutter_localizations/pubspec.yaml index 65bb80ef0c..146e837205 100644 --- a/packages/flutter_localizations/pubspec.yaml +++ b/packages/flutter_localizations/pubspec.yaml @@ -19,7 +19,7 @@ dev_dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -62,4 +62,4 @@ dev_dependencies: web_socket_channel: 1.0.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.1.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 5707 +# PUBSPEC CHECKSUM: f308 diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index ee243c2021..9500c82d33 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -67,4 +67,4 @@ dependencies: web_socket_channel: 1.0.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 2.1.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 633a +# PUBSPEC CHECKSUM: d33b diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index 6f568e063e..8be98d16c1 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -44,7 +44,7 @@ dependencies: front_end: any async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - barback: 0.15.2+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -95,4 +95,4 @@ dependency_overrides: kernel: path: ../../bin/cache/dart-sdk/lib/kernel -# PUBSPEC CHECKSUM: 15a5 +# PUBSPEC CHECKSUM: d6a6 diff --git a/packages/fuchsia_remote_debug_protocol/examples/list_vms_and_flutter_views.dart b/packages/fuchsia_remote_debug_protocol/examples/list_vms_and_flutter_views.dart new file mode 100644 index 0000000000..987ec9dfd2 --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/examples/list_vms_and_flutter_views.dart @@ -0,0 +1,48 @@ +// Copyright 2018 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 'dart:async'; +import 'dart:core'; + +import 'package:fuchsia_remote_debug_protocol/fuchsia_remote_debug_protocol.dart'; +import 'package:fuchsia_remote_debug_protocol/logging.dart'; + +/// Runs through a simple usage of the fuchsia_remote_debug_protocol library: +/// connects to a remote machine at the address in argument 1 (interface +/// optional for argument 2) to list all active flutter views and Dart VMs +/// running on said device. This uses an SSH config file (optional, depending +/// on your setup). +/// +/// Example usage: +/// +/// $ dart examples/list_vms_and_flutter_views.dart \ +/// fe80::8eae:4cff:fef4:9247 eno1 +Future main(List args) async { + // Log only at info level within the library. If issues arise, this can be + // changed to [LoggingLevel.all] or [LoggingLevel.fine] to see more + // information. + Logger.globalLevel = LoggingLevel.info; + if (args.isEmpty) { + print('Expects an IP address and/or network interface'); + return; + } + final String address = args[0]; + final String interface = args.length > 1 ? args[1] : ''; + // Example ssh config path for the Fuchsia device after having made a local + // build. + const String sshConfigPath = '../../out/release-x86-64/ssh-keys/ssh_config'; + final FuchsiaRemoteConnection connection = + await FuchsiaRemoteConnection.connect(address, interface, sshConfigPath); + print('On $address, the following Dart VM ports are running:'); + for (int port in await connection.getDeviceServicePorts()) { + print('\t$port'); + } + print(''); + + print('The following Flutter views are running:'); + for (FlutterView view in await connection.getFlutterViews()) { + print('\t${view.name ?? view.id}'); + } + await connection.stop(); +} diff --git a/packages/fuchsia_remote_debug_protocol/lib/fuchsia_remote_debug_protocol.dart b/packages/fuchsia_remote_debug_protocol/lib/fuchsia_remote_debug_protocol.dart new file mode 100644 index 0000000000..6c08b9481b --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/lib/fuchsia_remote_debug_protocol.dart @@ -0,0 +1,22 @@ +// Copyright 2018 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. + +/// Provides an API to test/debug Flutter applications on remote Fuchsia devices +/// and emulators. +/// +/// The application typically runs in a separate process from the actual +/// test, wherein the user can supply the process with various events to test +/// the behavior of said application. +/// +/// The API will provide methods to connect to one or more instances of the +/// Dart VM and operate on Isolates and Flutter Views, including affordances to +/// subscribe to creation and destruction of Dart VM instances, Isolates, and +/// Flutter Views. Not all of these features are yet implemented, as this +/// library is a work in progress. +library fuchsia_remote_debug_protocol; + +export 'src/common/network.dart'; +export 'src/dart/dart_vm.dart'; +export 'src/fuchsia_remote_connection.dart'; +export 'src/runners/ssh_command_runner.dart'; diff --git a/packages/fuchsia_remote_debug_protocol/lib/logging.dart b/packages/fuchsia_remote_debug_protocol/lib/logging.dart new file mode 100644 index 0000000000..4b38acf73f --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/lib/logging.dart @@ -0,0 +1,12 @@ +// Copyright 2018 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. + +/// Library for logging the remote debug protocol internals. +/// +/// Useful for determining connection issues and the like. This is included as a +/// separate library so that it can be imported under a separate namespace in +/// the event that you are using a logging package with similar class names. +library logging; + +export 'src/common/logging.dart'; diff --git a/packages/fuchsia_remote_debug_protocol/lib/src/common/logging.dart b/packages/fuchsia_remote_debug_protocol/lib/src/common/logging.dart new file mode 100644 index 0000000000..6e72822a7c --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/lib/src/common/logging.dart @@ -0,0 +1,150 @@ +// Copyright 2018 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 'dart:core'; +import 'dart:io'; + +/// Determines the level of logging. +/// +/// Verbosity is increasing from one (none) to five (fine). The sixth level +/// (all) logs everything. +enum LoggingLevel { + /// Logs no logs. + none, + + /// Logs severe messages at the most (note that severe messages are always + /// logged). + /// + /// Severe means that the process has encountered a critical level of failure + /// in which it cannot recover and will terminate as a result. + severe, + + /// Logs warning messages at the most. + /// + /// Warning implies that an error was encountered, but the process will + /// attempt to continue, and may/may not succeed. + warning, + + /// Logs info messages at the most. + /// + /// An info message is for determining information about the state of the + /// application as it runs through execution. + info, + + /// Logs fine logs at the most. + /// + /// A fine message is one that is not important for logging outside of + /// debugging potential issues in the application. + fine, + + /// Logs everything. + all, +} + +/// Signature of a function that logs a [LogMessage]. +typedef void LoggingFunction(LogMessage log); + +/// The default logging function. +/// +/// Runs the [print] function using the format string: +/// '[${log.levelName}]::${log.tag}--${log.time}: ${log.message}' +/// +/// Exits with status code 1 if the `log` is [LoggingLevel.severe]. +LoggingFunction defaultLoggingFunction = (LogMessage log) { + print('[${log.levelName}]::${log.tag}--${log.time}: ${log.message}'); + if (log.level == LoggingLevel.severe) { + exit(1); + } +}; + +/// Represents a logging message created by the logger. +/// +/// Includes a message, the time the message was created, the level of the log +/// as an enum, the name of the level as a string, and a tag. This class is used +/// to print from the global logging function defined in +/// [Logger.loggingFunction] (a function that can be user-defined). +class LogMessage { + /// Creates a log, including the level of the log, the time it was created, + /// and the actual log message. + /// + /// When this message is created, it sets its [time] to [DateTime.now]. + LogMessage(this.message, this.tag, this.level) + : this.levelName = + level.toString().substring(level.toString().indexOf('.') + 1), + this.time = new DateTime.now(); + + /// The actual log message. + final String message; + + /// The time the log message was created. + final DateTime time; + + /// The level of this log. + final LoggingLevel level; + + /// The human readable level of this log. + final String levelName; + + /// The tag associated with the message. This is set to [Logger.tag] when + /// emitted by a [Logger] object. + final String tag; +} + +/// Logs messages using the global [LoggingFunction] and logging level. +/// +/// Example of setting log level to [LoggingLevel.warning] and creating a +/// logging function: +/// +/// ```dart +/// Logger.globalLevel = LoggingLevel.warning; +/// ``` +class Logger { + /// Creates a logger with the given [tag]. + Logger(this.tag); + + /// The tag associated with the log message (usable in the logging function). + /// [LogMessage] objects emitted by this class will have [LogMessage.tag] set + /// to this value. + final String tag; + + /// Determines what to do when the [Logger] creates and attempts to log a + /// [LogMessage] object. + /// + /// This function can be reassigned to whatever functionality of your + /// choosing, so long as it has the same signature of [LoggingFunction] (it + /// can also be an asynchronous function, if doing file I/O, for + /// example). + static LoggingFunction loggingFunction = defaultLoggingFunction; + + /// Determines the logging level all [Logger] instances use. + static LoggingLevel globalLevel = LoggingLevel.none; + + /// Logs a [LoggingLevel.severe] level `message`. + /// + /// Severe messages are always logged, regardless of what level is set. + void severe(String message) { + loggingFunction(new LogMessage(message, tag, LoggingLevel.severe)); + } + + /// Logs a [LoggingLevel.warning] level `message`. + void warning(String message) { + if (globalLevel.index >= LoggingLevel.warning.index) { + loggingFunction(new LogMessage(message, tag, LoggingLevel.warning)); + } + } + + /// Logs a [LoggingLevel.info] level `message`. + void info(String message) { + if (globalLevel.index >= LoggingLevel.info.index) { + loggingFunction(new LogMessage(message, tag, LoggingLevel.info)); + } + } + + /// Logs a [LoggingLevel.fine] level `message`. + void fine(String message) { + if (globalLevel.index >= LoggingLevel.fine.index) { + loggingFunction(new LogMessage(message, tag, LoggingLevel.fine)); + } + } +} diff --git a/packages/fuchsia_remote_debug_protocol/lib/src/common/network.dart b/packages/fuchsia_remote_debug_protocol/lib/src/common/network.dart new file mode 100644 index 0000000000..ecd411743c --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/lib/src/common/network.dart @@ -0,0 +1,35 @@ +// Copyright 2018 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 'dart:core'; + +/// Determines whether `address` is a valid IPv6 or IPv4 address. +/// +/// Throws an [ArgumentError] if the address is neither. +void validateAddress(String address) { + if (!(isIpV4Address(address) || isIpV6Address(address))) { + throw new ArgumentError( + '"$address" is neither a valid IPv4 nor IPv6 address'); + } +} + +/// Returns true if `address` is a valid IPv6 address. +bool isIpV6Address(String address) { + try { + Uri.parseIPv6Address(address); + return true; + } on FormatException { + return false; + } +} + +/// Returns true if `address` is a valid IPv4 address. +bool isIpV4Address(String address) { + try { + Uri.parseIPv4Address(address); + return true; + } on FormatException { + return false; + } +} diff --git a/packages/fuchsia_remote_debug_protocol/lib/src/dart/dart_vm.dart b/packages/fuchsia_remote_debug_protocol/lib/src/dart/dart_vm.dart new file mode 100644 index 0000000000..5709adeaa0 --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/lib/src/dart/dart_vm.dart @@ -0,0 +1,203 @@ +// Copyright 2018 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 'dart:async'; +import 'dart:io'; + +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'package:web_socket_channel/io.dart'; + +import '../common/logging.dart'; + +const Duration _kConnectTimeout = const Duration(seconds: 30); + +const Duration _kReconnectAttemptInterval = const Duration(seconds: 3); + +const Duration _kRpcTimeout = const Duration(seconds: 5); + +final Logger _log = new Logger('DartVm'); + +/// Signature of an asynchronous function for astablishing a JSON RPC-2 +/// connection to a [Uri]. +typedef Future RpcPeerConnectionFunction(Uri uri); + +/// [DartVm] uses this function to connect to the Dart VM on Fuchsia. +/// +/// This function can be assigned to a different one in the event that a +/// custom connection function is needed. +RpcPeerConnectionFunction fuchsiaVmServiceConnectionFunction = _waitAndConnect; + +/// Attempts to connect to a Dart VM service. +/// +/// Gives up after `_kConnectTimeout` has elapsed. +Future _waitAndConnect(Uri uri) async { + final Stopwatch timer = new Stopwatch()..start(); + + Future attemptConnection(Uri uri) async { + WebSocket socket; + json_rpc.Peer peer; + try { + socket = await WebSocket.connect(uri.toString()); + peer = new json_rpc.Peer(new IOWebSocketChannel(socket).cast())..listen(); + return peer; + } catch (e) { + await peer?.close(); + await socket?.close(); + if (timer.elapsed < _kConnectTimeout) { + _log.info('Attempting to reconnect'); + await new Future.delayed(_kReconnectAttemptInterval); + return attemptConnection(uri); + } else { + _log.severe('Connection to Fuchsia\'s Dart VM timed out at ' + '${uri.toString()}'); + rethrow; + } + } + } + + return attemptConnection(uri); +} + +/// Restores the VM service connection function to the default implementation. +void restoreVmServiceConnectionFunction() { + fuchsiaVmServiceConnectionFunction = _waitAndConnect; +} + +/// An error raised when a malformed RPC response is received from the Dart VM. +/// +/// A more detailed description of the error is found within the [message] +/// field. +class RpcFormatError extends Error { + /// Basic constructor outlining the reason for the format error. + RpcFormatError(this.message); + + /// The reason for format error. + final String message; + + @override + String toString() { + return '$RpcFormatError: $message\n${super.stackTrace}'; + } +} + +/// Handles JSON RPC-2 communication with a Dart VM service. +/// +/// Either wraps existing RPC calls to the Dart VM service, or runs raw RPC +/// function calls via [invokeRpc]. +class DartVm { + DartVm._(this._peer); + + final json_rpc.Peer _peer; + + /// Attempts to connect to the given [Uri]. + /// + /// Throws an error if unable to connect. + static Future connect(Uri uri) async { + if (uri.scheme == 'http') { + uri = uri.replace(scheme: 'ws', path: '/ws'); + } + final json_rpc.Peer peer = await fuchsiaVmServiceConnectionFunction(uri); + if (peer == null) { + return null; + } + return new DartVm._(peer); + } + + /// Invokes a raw JSON RPC command with the VM service. + /// + /// When `timeout` is set and reached, throws a [TimeoutException]. + /// + /// If the function returns, it is with a parsed JSON response. + Future> invokeRpc( + String function, { + Map params, + Duration timeout, + }) async { + final Future> future = _peer.sendRequest( + function, + params ?? {}, + ); + if (timeout == null) { + return future; + } + return future.timeout(timeout, onTimeout: () { + throw new TimeoutException( + 'Peer connection timed out during RPC call', + timeout, + ); + }); + } + + /// Returns a list of [FlutterView] objects running across all Dart VM's. + /// + /// If there is no associated isolate with the flutter view (used to determine + /// the flutter view's name), then the flutter view's ID will be added + /// instead. If none of these things can be found (isolate has no name or the + /// flutter view has no ID), then the result will not be added to the list. + Future> getAllFlutterViews() async { + final List views = []; + final Map rpcResponse = + await invokeRpc('_flutter.listViews', timeout: _kRpcTimeout); + final List> flutterViewsJson = rpcResponse['views']; + for (Map jsonView in flutterViewsJson) { + final FlutterView flutterView = new FlutterView._fromJson(jsonView); + if (flutterView != null) { + views.add(flutterView); + } + } + return views; + } + + /// Disconnects from the Dart VM Service. + /// + /// After this function completes this object is no longer usable. + Future stop() async { + await _peer?.close(); + } +} + +/// Represents an instance of a Flutter view running on a Fuchsia device. +class FlutterView { + FlutterView._(this._name, this._id); + + /// Attempts to construct a [FlutterView] from a json representation. + /// + /// If there is no isolate and no ID for the view, throws an [RpcFormatError]. + /// If there is an associated isolate, and there is no name for said isolate, + /// also throws an [RpcFormatError]. + /// + /// All other cases return a [FlutterView] instance. The name of the + /// view may be null, but the id will always be set. + factory FlutterView._fromJson(Map json) { + final Map isolate = json['isolate']; + final String id = json['id']; + String name; + if (isolate != null) { + name = isolate['name']; + if (name == null) { + throw new RpcFormatError('Unable to find name for isolate "$isolate"'); + } + } + if (id == null) { + throw new RpcFormatError( + 'Unable to find view name for the following JSON structure "$json"'); + } + return new FlutterView._(name, id); + } + + /// Determines the name of the isolate associated with this view. If there is + /// no associated isolate, this will be set to the view's ID. + final String _name; + + /// The ID of the Flutter view. + final String _id; + + /// The ID of the [FlutterView]. + String get id => _id; + + /// Returns the name of the [FlutterView]. + /// + /// May be null if there is no associated isolate. + String get name => _name; +} diff --git a/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart b/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart new file mode 100644 index 0000000000..ce2d51d237 --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart @@ -0,0 +1,348 @@ +// Copyright 2018 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 'dart:async'; +import 'dart:io'; + +import 'package:meta/meta.dart'; +import 'package:process/process.dart'; + +import 'common/logging.dart'; +import 'common/network.dart'; +import 'dart/dart_vm.dart'; +import 'runners/ssh_command_runner.dart'; + +final String _ipv4Loopback = InternetAddress.LOOPBACK_IP_V4.address; + +final String _ipv6Loopback = InternetAddress.LOOPBACK_IP_V6.address; + +const ProcessManager _processManager = const LocalProcessManager(); + +final Logger _log = new Logger('FuchsiaRemoteConnection'); + +/// A function for forwarding ports on the local machine to a remote device. +/// +/// Takes a remote `address`, the target device's port, and an optional +/// `interface` and `configFile`. The config file is used primarily for the +/// default SSH port forwarding configuration. +typedef Future PortForwardingFunction( + String address, int remotePort, + [String interface, String configFile]); + +/// The function for forwarding the local machine's ports to a remote Fuchsia +/// device. +/// +/// Can be overwritten in the event that a different method is required. +/// Defaults to using SSH port forwarding. +PortForwardingFunction fuchsiaPortForwardingFunction = _SshPortForwarder.start; + +/// Sets [fuchsiaPortForwardingFunction] back to the default SSH port forwarding +/// implementation. +void restoreFuchsiaPortForwardingFunction() { + fuchsiaPortForwardingFunction = _SshPortForwarder.start; +} + +/// Manages a remote connection to a Fuchsia Device. +/// +/// Provides affordances to observe and connect to Flutter views, isolates, and +/// perform actions on the Fuchsia device's various VM services. +/// +/// Note that this class can be connected to several instances of the Fuchsia +/// device's Dart VM at any given time. +class FuchsiaRemoteConnection { + final List _forwardedVmServicePorts = []; + final SshCommandRunner _sshCommandRunner; + final bool _useIpV6Loopback; + + /// VM service cache to avoid repeating handshakes across function + /// calls. Keys a forwarded port to a DartVm connection instance. + final Map _dartVmCache = {}; + + FuchsiaRemoteConnection._(this._useIpV6Loopback, this._sshCommandRunner); + + /// Same as [FuchsiaRemoteConnection.connect] albeit with a provided + /// [SshCommandRunner] instance. + @visibleForTesting + static Future connectWithSshCommandRunner( + SshCommandRunner commandRunner) async { + final FuchsiaRemoteConnection connection = new FuchsiaRemoteConnection._( + isIpV6Address(commandRunner.address), commandRunner); + await connection._forwardLocalPortsToDeviceServicePorts(); + return connection; + } + + /// Opens a connection to a Fuchsia device. + /// + /// Accepts an `address` to a Fuchsia device, and optionally a `sshConfigPath` + /// in order to open the associated ssh_config for port forwarding. + /// + /// Will throw an [ArgumentError] if `address` is malformed. + /// + /// Once this function is called, the instance of [FuchsiaRemoteConnection] + /// returned will keep all associated DartVM connections opened over the + /// lifetime of the object. + /// + /// At its current state Dart VM connections will not be added or removed over + /// the lifetime of this object. + /// + /// Throws an [ArgumentError] if the supplied `address` is not valid IPv6 or + /// IPv4. + /// + /// Note that if `address` is ipv6 link local (usually starts with fe80::), + /// then `interface` will probably need to be set in order to connect + /// successfully (that being the outgoing interface of your machine, not the + /// interface on the target machine). + static Future connect( + String address, [ + String interface = '', + String sshConfigPath, + ]) async { + return await FuchsiaRemoteConnection.connectWithSshCommandRunner( + new SshCommandRunner( + address: address, + interface: interface, + sshConfigPath: sshConfigPath, + ), + ); + } + + /// Closes all open connections. + /// + /// Any objects that this class returns (including any child objects from + /// those objects) will subsequently have its connection closed as well, so + /// behavior for them will be undefined. + Future stop() async { + for (PortForwarder fp in _forwardedVmServicePorts) { + // Closes VM service first to ensure that the connection is closed cleanly + // on the target before shutting down the forwarding itself. + final DartVm vmService = _dartVmCache[fp.port]; + _dartVmCache[fp.port] = null; + await vmService?.stop(); + await fp.stop(); + } + _dartVmCache.clear(); + _forwardedVmServicePorts.clear(); + } + + /// Returns a list of [FlutterView] objects. + /// + /// This is run across all connected DartVM connections that this class is + /// managing. + Future> getFlutterViews() async { + final List views = []; + if (_forwardedVmServicePorts.isEmpty) { + return views; + } + for (PortForwarder fp in _forwardedVmServicePorts) { + final DartVm vmService = await _getDartVm(fp.port); + views.addAll(await vmService.getAllFlutterViews()); + } + return new List.unmodifiable(views); + } + + Future _getDartVm(int port) async { + if (!_dartVmCache.containsKey(port)) { + // While the IPv4 loopback can be used for the initial port forwarding + // (see [PortForwarder.start]), the address is actually bound to the IPv6 + // loopback device, so connecting to the IPv4 loopback would fail when the + // target address is IPv6 link-local. + final String addr = _useIpV6Loopback + ? 'http://\[$_ipv6Loopback\]:$port' + : 'http://$_ipv4Loopback:$port'; + final Uri uri = Uri.parse(addr); + final DartVm dartVm = await DartVm.connect(uri); + _dartVmCache[port] = dartVm; + } + return _dartVmCache[port]; + } + + /// Forwards a series of local device ports to the remote device. + /// + /// When this function is run, all existing forwarded ports and connections + /// are reset by way of [stop]. + Future _forwardLocalPortsToDeviceServicePorts() async { + await stop(); + final List servicePorts = await getDeviceServicePorts(); + _forwardedVmServicePorts + .addAll(await Future.wait(servicePorts.map((int deviceServicePort) { + return fuchsiaPortForwardingFunction( + _sshCommandRunner.address, + deviceServicePort, + _sshCommandRunner.interface, + _sshCommandRunner.sshConfigPath); + }))); + } + + /// Gets the open Dart VM service ports on a remote Fuchsia device. + /// + /// The method attempts to get service ports through an SSH connection. Upon + /// successfully getting the VM service ports, returns them as a list of + /// integers. If an empty list is returned, then no Dart VM instances could be + /// found. An exception is thrown in the event of an actual error when + /// attempting to acquire the ports. + Future> getDeviceServicePorts() async { + // TODO(awdavies): This is using a temporary workaround rather than a + // well-defined service, and will be deprecated in the near future. + final List lsOutput = + await _sshCommandRunner.run('ls /tmp/dart.services'); + final List ports = []; + + // The output of lsOutput is a list of available ports as the Fuchsia dart + // service advertises. An example lsOutput would look like: + // + // [ '31782\n', '1234\n', '11967' ] + for (String s in lsOutput) { + final String trimmed = s.trim(); + final int lastSpace = trimmed.lastIndexOf(' '); + final String lastWord = trimmed.substring(lastSpace + 1); + if ((lastWord != '.') && (lastWord != '..')) { + final int value = int.parse(lastWord, onError: (_) => null); + if (value != null) { + ports.add(value); + } + } + } + return ports; + } +} + +/// Defines an interface for port forwarding. +/// +/// When a port forwarder is initialized, it is intended to save a port through +/// which a connection is persisted along the lifetime of this object. +/// +/// To shut down a port forwarder you must call the [stop] function. +abstract class PortForwarder { + /// Determines the port which is being forwarded from the local machine. + int get port; + + /// The destination port on the other end of the port forwarding tunnel. + int get remotePort; + + /// Shuts down and cleans up port forwarding. + Future stop(); +} + +/// Instances of this class represent a running SSH tunnel. +/// +/// The SSH tunnel is from the host to a VM service running on a Fuchsia device. +class _SshPortForwarder implements PortForwarder { + _SshPortForwarder._( + this._remoteAddress, + this._remotePort, + this._localSocket, + this._process, + this._interface, + this._sshConfigPath, + this._ipV6, + ); + + final String _remoteAddress; + final int _remotePort; + final ServerSocket _localSocket; + final Process _process; + final String _sshConfigPath; + final String _interface; + final bool _ipV6; + + @override + int get port => _localSocket.port; + + @override + int get remotePort => _remotePort; + + /// Starts SSH forwarding through a subprocess, and returns an instance of + /// [_SshPortForwarder]. + static Future<_SshPortForwarder> start(String address, int remotePort, + [String interface, String sshConfigPath]) async { + final bool isIpV6 = isIpV6Address(address); + final ServerSocket localSocket = await _createLocalSocket(); + if (localSocket == null || localSocket.port == 0) { + _log.warning('_SshPortForwarder failed to find a local port for ' + '$address:$remotePort'); + return null; + } + // TODO(awdavies): The square-bracket enclosure for using the IPv6 loopback + // didn't appear to work, but when assigning to the IPv4 loopback device, + // netstat shows that the local port is actually being used on the IPv6 + // loopback (::1). While this can be used for forwarding to the destination + // IPv6 interface, it cannot be used to connect to a websocket. + final String formattedForwardingUrl = + '${localSocket.port}:$_ipv4Loopback:$remotePort'; + final List command = ['ssh']; + if (isIpV6) { + command.add('-6'); + } + if (sshConfigPath != null) { + command.addAll(['-F', sshConfigPath]); + } + final String targetAddress = + isIpV6 && interface.isNotEmpty ? '$address%$interface' : address; + command.addAll([ + '-nNT', + '-L', + formattedForwardingUrl, + targetAddress, + ]); + _log.fine("_SshPortForwarder running '${command.join(' ')}'"); + final Process process = await _processManager.start(command); + process.exitCode.then((int c) { + _log.fine("'${command.join(' ')}' exited with exit code $c"); + }); + _log.fine( + 'Set up forwarding from ${localSocket.port} to $address port $remotePort'); + return new _SshPortForwarder._(address, remotePort, localSocket, process, + interface, sshConfigPath, isIpV6); + } + + /// Kills the SSH forwarding command, then to ensure no ports are forwarded, + /// runs the SSH 'cancel' command to shut down port forwarding completely. + @override + Future stop() async { + // Kill the original SSH process if it is still around. + _process.kill(); + // Cancel the forwarding request. See [start] for commentary about why this + // uses the IPv4 loopback. + final String formattedForwardingUrl = + '${_localSocket.port}:$_ipv4Loopback:$_remotePort'; + final List command = ['ssh']; + final String targetAddress = _ipV6 && _interface.isNotEmpty + ? '$_remoteAddress%$_interface' + : _remoteAddress; + if (_sshConfigPath != null) { + command.addAll(['-F', _sshConfigPath]); + } + command.addAll([ + '-O', + 'cancel', + '-L', + formattedForwardingUrl, + targetAddress, + ]); + _log.fine( + 'Shutting down SSH forwarding with command: ${command.join(' ')}'); + final ProcessResult result = await _processManager.run(command); + if (result.exitCode != 0) { + _log.warning( + 'Command failed:\nstdout: ${result.stdout}\nstderr: ${result.stderr}'); + } + _localSocket.close(); + } + + /// Attempts to find an available port. + /// + /// If successful returns a valid [ServerSocket] (which must be disconnected + /// later). + static Future _createLocalSocket() async { + ServerSocket s; + try { + s = await ServerSocket.bind(_ipv4Loopback, 0); + } catch (e) { + // Failures are signaled by a return value of 0 from this function. + _log.warning('_createLocalSocket failed: $e'); + return null; + } + return s; + } +} diff --git a/packages/fuchsia_remote_debug_protocol/lib/src/runners/ssh_command_runner.dart b/packages/fuchsia_remote_debug_protocol/lib/src/runners/ssh_command_runner.dart new file mode 100644 index 0000000000..d57518cc86 --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/lib/src/runners/ssh_command_runner.dart @@ -0,0 +1,107 @@ +// Copyright 2018 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 'dart:async'; +import 'dart:io' show ProcessResult; + +import 'package:meta/meta.dart'; +import 'package:process/process.dart'; + +import '../common/logging.dart'; +import '../common/network.dart'; + +/// An error raised when a command fails to run within the [SshCommandRunner]. +/// +/// This occurs for both connection failures, and for failure to +/// run the command on the remote device. This error is raised when the +/// subprocess running the SSH command returns a nonzero exit code. +class SshCommandError extends Error { + /// Basic constructor outlining the reason for the SSH command failure through + /// the message string. + SshCommandError(this.message); + + /// The reason for the command failure. + final String message; + + @override + String toString() { + return '$SshCommandError: $message\n${super.stackTrace}'; + } +} + +/// Runs commands remotely on a Fuchsia device. +/// +/// Requires a Fuchsia root and build type (to load the ssh config), +/// and the address of the Fuchsia device. +class SshCommandRunner { + /// Instantiates the command runner, pointing to an `address` as well as + /// an optional SSH config file path. + /// + /// If the SSH config path is supplied as an empty string, behavior is + /// undefined. + /// + /// [ArgumentError] is thrown in the event that `address` is neither valid + /// IPv4 nor IPv6. Note that when connecting to a link local address (fe80:: + /// is usually at the start of the address), then an interface should be + /// supplied. + SshCommandRunner({ + this.address, + this.interface = '', + this.sshConfigPath, + }) : _processManager = const LocalProcessManager() { + validateAddress(address); + } + + /// Private constructor for dependency injection of the process manager. + @visibleForTesting + SshCommandRunner.withProcessManager( + this._processManager, { + this.address, + this.interface = '', + this.sshConfigPath, + }) { + validateAddress(address); + } + + final Logger _log = new Logger('SshCommandRunner'); + + final ProcessManager _processManager; + + /// The IPv4 address to access the Fuchsia machine over SSH. + final String address; + + /// The path to the SSH config (optional). + final String sshConfigPath; + + /// The name of the machine's network interface (for use with IPv6 + /// connections. Ignored otherwise). + final String interface; + + /// Runs a command on a Fuchsia device through an SSH tunnel. + /// + /// If the subprocess creating the SSH tunnel returns a nonzero exit status, + /// then an [SshCommandError] is raised. + Future> run(String command) async { + final List args = ['ssh']; + if (sshConfigPath != null) { + args.addAll(['-F', sshConfigPath]); + } + if (isIpV6Address(address)) { + final String fullAddress = + interface.isEmpty ? address : '$address%$interface'; + args.addAll(['-6', fullAddress]); + } else { + args.add(address); + } + args.add(command); + _log.fine('Running command through SSH: ${args.join(' ')}'); + final ProcessResult result = await _processManager.run(args); + if (result.exitCode != 0) { + throw new SshCommandError( + 'Command failed: $command\nstdout: ${result.stdout}\nstderr: ${result.stderr}'); + } + _log.fine('SSH command stdout in brackets:[${result.stdout}]'); + return result.stdout.split('\n'); + } +} diff --git a/packages/fuchsia_remote_debug_protocol/pubspec.yaml b/packages/fuchsia_remote_debug_protocol/pubspec.yaml new file mode 100644 index 0000000000..eb8f47f0a3 --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/pubspec.yaml @@ -0,0 +1,68 @@ +name: fuchsia_remote_debug_protocol +description: Provides an API to test/debug Flutter applications on remote Fuchsia devices and emulators. +homepage: http://flutter.io +author: Flutter Authors + +dependencies: + json_rpc_2: 2.0.7 + process: 2.0.9 + meta: 1.1.2 + web_socket_channel: 1.0.7 + flutter_test: + sdk: flutter + + args: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + async: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + barback: 0.15.2+15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + charcode: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + cli_util: 0.1.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + collection: 1.14.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + convert: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + crypto: 2.0.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + csslib: 0.14.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + file: 3.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + html: 0.13.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http: 0.11.3+16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_multi_server: 2.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_parser: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + intl: 0.15.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + io: 0.3.2+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + isolate: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 0.11.3+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.1+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + mime: 0.9.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + multi_server_socket: 1.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + node_preamble: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_resolver: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + platform: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + plugin: 0.2.0+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pool: 1.3.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pub_semver: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + quiver: 0.28.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf: 0.7.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_packages_handler: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_static: 0.2.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 0.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_map_stack_trace: 1.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_maps: 0.10.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stack_trace: 1.9.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 1.6.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + string_scanner: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + utf: 0.9.0+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vector_math: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 0.9.7+7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + yaml: 2.1.13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +dev_dependencies: + mockito: 2.2.3 + test: 0.12.32+2 + +# PUBSPEC CHECKSUM: 97d6 diff --git a/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart b/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart new file mode 100644 index 0000000000..7d7bab6f6f --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart @@ -0,0 +1,150 @@ +// Copyright 2018 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 'dart:async'; + +import 'package:test/test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; + +import 'package:fuchsia_remote_debug_protocol/fuchsia_remote_debug_protocol.dart'; + +void main() { + group('FuchsiaRemoteConnection.connect', () { + MockSshCommandRunner mockRunner; + + setUp(() { + mockRunner = new MockSshCommandRunner(); + }); + + tearDown(() { + /// Most tests will mock out the port forwarding and connection + /// functions. + restoreFuchsiaPortForwardingFunction(); + restoreVmServiceConnectionFunction(); + }); + + test('end-to-end with three vm connections and flutter view query', + () async { + const String address = 'fe80::8eae:4cff:fef4:9247'; + const String interface = 'eno1'; + // Adds some extra junk to make sure the strings will be cleaned up. + when(mockRunner.run(typed(any))) + .thenReturn(['123\n\n\n', '456 ', '789']); + when(mockRunner.address).thenReturn(address); + when(mockRunner.interface).thenReturn(interface); + int port = 0; + final List forwardedPorts = []; + Future mockPortForwardingFunction( + String address, int remotePort, + [String interface = '', String configFile]) { + return new Future(() { + final MockPortForwarder pf = new MockPortForwarder(); + forwardedPorts.add(pf); + when(pf.port).thenReturn(port++); + when(pf.remotePort).thenReturn(remotePort); + return pf; + }); + } + + int flutterViewIndex = 0; + final List> flutterViewCannedResponses = + >[ + { + 'views': >[ + { + 'type': 'FlutterView', + 'id': 'flutterView0', + }, + ], + }, + { + 'views': >[ + { + 'type': 'FlutterView', + 'id': 'flutterView1', + 'isolate': { + 'type': '@Isolate', + 'fixedId': 'true', + 'id': 'isolates/1', + 'name': 'file://flutterBinary1', + 'number': '1', + }, + } + ], + }, + { + 'views': >[ + { + 'type': 'FlutterView', + 'id': 'flutterView2', + 'isolate': { + 'type': '@Isolate', + 'fixedId': 'true', + 'id': 'isolates/2', + 'name': 'file://flutterBinary2', + 'number': '2', + }, + } + ], + }, + ]; + + final List mockPeerConnections = []; + final List uriConnections = []; + Future mockVmConnectionFunction(Uri uri) { + return new Future(() async { + final MockPeer mp = new MockPeer(); + mockPeerConnections.add(mp); + uriConnections.add(uri); + when(mp.sendRequest(typed(any), typed(any))) + .thenReturn(new Future>( + () => flutterViewCannedResponses[flutterViewIndex++])); + return mp; + }); + } + + fuchsiaPortForwardingFunction = mockPortForwardingFunction; + fuchsiaVmServiceConnectionFunction = mockVmConnectionFunction; + + final FuchsiaRemoteConnection connection = + await FuchsiaRemoteConnection.connectWithSshCommandRunner(mockRunner); + + // [mockPortForwardingFunction] will have returned three different + // forwarded ports, incrementing the port each time by one. (Just a sanity + // check that the forwarding port was called). + expect(forwardedPorts.length, 3); + expect(forwardedPorts[0].remotePort, 123); + expect(forwardedPorts[1].remotePort, 456); + expect(forwardedPorts[2].remotePort, 789); + expect(forwardedPorts[0].port, 0); + expect(forwardedPorts[1].port, 1); + expect(forwardedPorts[2].port, 2); + + final List views = await connection.getFlutterViews(); + expect(views, isNot(null)); + expect(views.length, 3); + // Since name can be null, check for the ID on all of them. + expect(views[0].id, 'flutterView0'); + expect(views[1].id, 'flutterView1'); + expect(views[2].id, 'flutterView2'); + + expect(views[0].name, equals(null)); + expect(views[1].name, 'file://flutterBinary1'); + expect(views[2].name, 'file://flutterBinary2'); + + // Ensure the ports are all closed after stop was called. + await connection.stop(); + verify(forwardedPorts[0].stop()); + verify(forwardedPorts[1].stop()); + verify(forwardedPorts[2].stop()); + }); + }); +} + +class MockSshCommandRunner extends Mock implements SshCommandRunner {} + +class MockPortForwarder extends Mock implements PortForwarder {} + +class MockPeer extends Mock implements json_rpc.Peer {} diff --git a/packages/fuchsia_remote_debug_protocol/test/src/dart/dart_vm_test.dart b/packages/fuchsia_remote_debug_protocol/test/src/dart/dart_vm_test.dart new file mode 100644 index 0000000000..aec9690888 --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/test/src/dart/dart_vm_test.dart @@ -0,0 +1,228 @@ +// Copyright 2018 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 'dart:async'; + +import 'package:fuchsia_remote_debug_protocol/src/dart/dart_vm.dart'; +import 'package:json_rpc_2/json_rpc_2.dart' as json_rpc; +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; + +void main() { + group('DartVm.connect', () { + tearDown(() { + restoreVmServiceConnectionFunction(); + }); + + test('null connector', () async { + Future mockServiceFunction(Uri uri) { + return new Future(() => null); + } + + fuchsiaVmServiceConnectionFunction = mockServiceFunction; + expect(await DartVm.connect(Uri.parse('http://this.whatever/ws')), + equals(null)); + }); + + test('disconnect closes peer', () async { + final MockPeer peer = new MockPeer(); + Future mockServiceFunction(Uri uri) { + return new Future(() => peer); + } + + fuchsiaVmServiceConnectionFunction = mockServiceFunction; + final DartVm vm = + await DartVm.connect(Uri.parse('http://this.whatever/ws')); + expect(vm, isNot(null)); + await vm.stop(); + verify(peer.close()); + }); + }); + + group('DartVm.getAllFlutterViews', () { + MockPeer mockPeer; + + setUp(() { + mockPeer = new MockPeer(); + }); + + tearDown(() { + restoreVmServiceConnectionFunction(); + }); + + test('basic flutter view parsing', () async { + final Map flutterViewCannedResponses = { + 'views': >[ + { + 'type': 'FlutterView', + 'id': 'flutterView0', + }, + { + 'type': 'FlutterView', + 'id': 'flutterView1', + 'isolate': { + 'type': '@Isolate', + 'fixedId': 'true', + 'id': 'isolates/1', + 'name': 'file://flutterBinary1', + 'number': '1', + }, + }, + { + 'type': 'FlutterView', + 'id': 'flutterView2', + 'isolate': { + 'type': '@Isolate', + 'fixedId': 'true', + 'id': 'isolates/2', + 'name': 'file://flutterBinary2', + 'number': '2', + }, + }, + ], + }; + + Future mockVmConnectionFunction(Uri uri) { + when(mockPeer.sendRequest( + typed(any), typed>(any))) + .thenReturn(new Future>( + () => flutterViewCannedResponses)); + return new Future(() => mockPeer); + } + + fuchsiaVmServiceConnectionFunction = mockVmConnectionFunction; + final DartVm vm = + await DartVm.connect(Uri.parse('http://whatever.com/ws')); + expect(vm, isNot(null)); + final List views = await vm.getAllFlutterViews(); + expect(views.length, 3); + // Check ID's as they cannot be null. + expect(views[0].id, 'flutterView0'); + expect(views[1].id, 'flutterView1'); + expect(views[2].id, 'flutterView2'); + + // Verify names. + expect(views[0].name, equals(null)); + expect(views[1].name, 'file://flutterBinary1'); + expect(views[2].name, 'file://flutterBinary2'); + }); + + test('invalid flutter view missing ID', () async { + final Map flutterViewCannedResponseMissingId = + { + 'views': >[ + // Valid flutter view. + { + 'type': 'FlutterView', + 'id': 'flutterView1', + 'isolate': { + 'type': '@Isolate', + 'name': 'IsolateThing', + 'fixedId': 'true', + 'id': 'isolates/1', + 'number': '1', + }, + }, + + // Missing ID. + { + 'type': 'FlutterView', + }, + ] + }; + + Future mockVmConnectionFunction(Uri uri) { + when(mockPeer.sendRequest( + typed(any), typed>(any))) + .thenReturn(new Future>( + () => flutterViewCannedResponseMissingId)); + return new Future(() => mockPeer); + } + + fuchsiaVmServiceConnectionFunction = mockVmConnectionFunction; + final DartVm vm = + await DartVm.connect(Uri.parse('http://whatever.com/ws')); + expect(vm, isNot(null)); + Future failingFunction() async { + await vm.getAllFlutterViews(); + } + + // Both views should be invalid as they were missing required fields. + expect(failingFunction, throwsA(const isInstanceOf())); + }); + + test('invalid flutter view missing ID', () async { + final Map flutterViewCannedResponseMissingIsolateName = + { + 'views': >[ + // Missing isolate name. + { + 'type': 'FlutterView', + 'id': 'flutterView1', + 'isolate': { + 'type': '@Isolate', + 'fixedId': 'true', + 'id': 'isolates/1', + 'number': '1', + }, + }, + ], + }; + + Future mockVmConnectionFunction(Uri uri) { + when(mockPeer.sendRequest( + typed(any), typed>(any))) + .thenReturn(new Future>( + () => flutterViewCannedResponseMissingIsolateName)); + return new Future(() => mockPeer); + } + + fuchsiaVmServiceConnectionFunction = mockVmConnectionFunction; + final DartVm vm = + await DartVm.connect(Uri.parse('http://whatever.com/ws')); + expect(vm, isNot(null)); + Future failingFunction() async { + await vm.getAllFlutterViews(); + } + + // Both views should be invalid as they were missing required fields. + expect(failingFunction, throwsA(const isInstanceOf())); + }); + }); + + group('DartVm.invokeRpc', () { + MockPeer mockPeer; + + setUp(() { + mockPeer = new MockPeer(); + }); + + tearDown(() { + restoreVmServiceConnectionFunction(); + }); + + test('verify timeout fires', () async { + const Duration timeoutTime = const Duration(milliseconds: 100); + Future mockVmConnectionFunction(Uri uri) { + // Return a command that will never complete. + when(mockPeer.sendRequest( + typed(any), typed>(any))) + .thenReturn(new Completer>().future); + return new Future(() => mockPeer); + } + + fuchsiaVmServiceConnectionFunction = mockVmConnectionFunction; + final DartVm vm = + await DartVm.connect(Uri.parse('http://whatever.com/ws')); + expect(vm, isNot(null)); + Future failingFunction() async { + await vm.invokeRpc('somesillyfunction', timeout: timeoutTime); + } + + expect(failingFunction, throwsA(const isInstanceOf())); + }); + }); +} + +class MockPeer extends Mock implements json_rpc.Peer {} diff --git a/packages/fuchsia_remote_debug_protocol/test/src/runners/ssh_command_runner_test.dart b/packages/fuchsia_remote_debug_protocol/test/src/runners/ssh_command_runner_test.dart new file mode 100644 index 0000000000..068ce16481 --- /dev/null +++ b/packages/fuchsia_remote_debug_protocol/test/src/runners/ssh_command_runner_test.dart @@ -0,0 +1,142 @@ +// Copyright 2018 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 'dart:async'; +import 'dart:io' show ProcessResult; + +import 'package:test/test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:process/process.dart'; +import 'package:fuchsia_remote_debug_protocol/src/runners/ssh_command_runner.dart'; + +void main() { + group('SshCommandRunner.constructors', () { + test('throws exception with invalid address', () async { + SshCommandRunner newCommandRunner() { + return new SshCommandRunner(address: 'sillyaddress.what'); + } + + expect(newCommandRunner, throwsArgumentError); + }); + + test('throws exception from injection constructor with invalid addr', + () async { + SshCommandRunner newCommandRunner() { + return new SshCommandRunner.withProcessManager( + const LocalProcessManager(), + address: '192.168.1.1.1'); + } + + expect(newCommandRunner, throwsArgumentError); + }); + }); + + group('SshCommandRunner.run', () { + MockProcessManager mockProcessManager; + MockProcessResult mockProcessResult; + SshCommandRunner runner; + + setUp(() { + mockProcessManager = new MockProcessManager(); + mockProcessResult = new MockProcessResult(); + when(mockProcessManager.run(typed(any))).thenReturn(mockProcessResult); + }); + + test('verify interface is appended to ipv6 address', () async { + const String ipV6Addr = 'fe80::8eae:4cff:fef4:9247'; + const String interface = 'eno1'; + runner = new SshCommandRunner.withProcessManager( + mockProcessManager, + address: ipV6Addr, + interface: interface, + sshConfigPath: '/whatever', + ); + when(mockProcessResult.stdout).thenReturn('somestuff'); + when(mockProcessResult.exitCode).thenReturn(0); + await runner.run('ls /whatever'); + final List passedCommand = + verify(mockProcessManager.run(typed(captureAny))).captured.single; + expect(passedCommand, contains('$ipV6Addr%$interface')); + }); + + test('verify no percentage symbol is added when no ipv6 interface', + () async { + const String ipV6Addr = 'fe80::8eae:4cff:fef4:9247'; + runner = new SshCommandRunner.withProcessManager( + mockProcessManager, + address: ipV6Addr, + ); + when(mockProcessResult.stdout).thenReturn('somestuff'); + when(mockProcessResult.exitCode).thenReturn(0); + await runner.run('ls /whatever'); + final List passedCommand = + verify(mockProcessManager.run(typed(captureAny))).captured.single; + expect(passedCommand, contains(ipV6Addr)); + }); + + test('verify commands are split into multiple lines', () async { + const String addr = '192.168.1.1'; + runner = new SshCommandRunner.withProcessManager(mockProcessManager, + address: addr); + when(mockProcessResult.stdout).thenReturn('''this + has + four + lines'''); + when(mockProcessResult.exitCode).thenReturn(0); + final List result = await runner.run('oihaw'); + expect(result, hasLength(4)); + }); + + test('verify exception on nonzero process result exit code', () async { + const String addr = '192.168.1.1'; + runner = new SshCommandRunner.withProcessManager(mockProcessManager, + address: addr); + when(mockProcessResult.stdout).thenReturn('whatever'); + when(mockProcessResult.exitCode).thenReturn(1); + Future failingFunction() async { + await runner.run('oihaw'); + } + + expect(failingFunction, throwsA(const isInstanceOf())); + }); + + test('verify correct args with config', () async { + const String addr = 'fe80::8eae:4cff:fef4:9247'; + const String config = '/this/that/this/and/uh'; + runner = new SshCommandRunner.withProcessManager( + mockProcessManager, + address: addr, + sshConfigPath: config, + ); + when(mockProcessResult.stdout).thenReturn('somestuff'); + when(mockProcessResult.exitCode).thenReturn(0); + await runner.run('ls /whatever'); + final List passedCommand = + verify(mockProcessManager.run(typed(captureAny))).captured.single; + expect(passedCommand, contains('-F')); + final int indexOfFlag = passedCommand.indexOf('-F'); + final String passedConfig = passedCommand[indexOfFlag + 1]; + expect(passedConfig, config); + }); + + test('verify config is excluded correctly', () async { + const String addr = 'fe80::8eae:4cff:fef4:9247'; + runner = new SshCommandRunner.withProcessManager( + mockProcessManager, + address: addr, + ); + when(mockProcessResult.stdout).thenReturn('somestuff'); + when(mockProcessResult.exitCode).thenReturn(0); + await runner.run('ls /whatever'); + final List passedCommand = + verify(mockProcessManager.run(typed(captureAny))).captured.single; + final int indexOfFlag = passedCommand.indexOf('-F'); + expect(indexOfFlag, equals(-1)); + }); + }); +} + +class MockProcessManager extends Mock implements ProcessManager {} + +class MockProcessResult extends Mock implements ProcessResult {}