| // Copyright 2014 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:fuchsia_remote_debug_protocol/fuchsia_remote_debug_protocol.dart'; |
| import 'package:test/fake.dart'; |
| import 'package:test/test.dart'; |
| import 'package:vm_service/vm_service.dart' as vms; |
| |
| void main() { |
| group('FuchsiaRemoteConnection.connect', () { |
| late List<FakePortForwarder> forwardedPorts; |
| List<FakeVmService> fakeVmServices; |
| late List<Uri> uriConnections; |
| |
| setUp(() { |
| final List<Map<String, dynamic>> flutterViewCannedResponses = |
| <Map<String, dynamic>>[ |
| <String, dynamic>{ |
| 'views': <Map<String, dynamic>>[ |
| <String, dynamic>{ |
| 'type': 'FlutterView', |
| 'id': 'flutterView0', |
| }, |
| ], |
| }, |
| <String, dynamic>{ |
| 'views': <Map<String, dynamic>>[ |
| <String, dynamic>{ |
| 'type': 'FlutterView', |
| 'id': 'flutterView1', |
| 'isolate': <String, dynamic>{ |
| 'type': '@Isolate', |
| 'fixedId': 'true', |
| 'id': 'isolates/1', |
| 'name': 'file://flutterBinary1', |
| 'number': '1', |
| }, |
| }, |
| ], |
| }, |
| <String, dynamic>{ |
| 'views': <Map<String, dynamic>>[ |
| <String, dynamic>{ |
| 'type': 'FlutterView', |
| 'id': 'flutterView2', |
| 'isolate': <String, dynamic>{ |
| 'type': '@Isolate', |
| 'fixedId': 'true', |
| 'id': 'isolates/2', |
| 'name': 'file://flutterBinary2', |
| 'number': '2', |
| }, |
| }, |
| ], |
| }, |
| ]; |
| |
| forwardedPorts = <FakePortForwarder>[]; |
| fakeVmServices = <FakeVmService>[]; |
| uriConnections = <Uri>[]; |
| Future<vms.VmService> fakeVmConnectionFunction( |
| Uri uri, { |
| Duration? timeout, |
| }) { |
| return Future<vms.VmService>(() async { |
| final FakeVmService service = FakeVmService(); |
| fakeVmServices.add(service); |
| uriConnections.add(uri); |
| service.flutterListViews = vms.Response.parse(flutterViewCannedResponses[uri.port]); |
| return service; |
| }); |
| } |
| |
| fuchsiaVmServiceConnectionFunction = fakeVmConnectionFunction; |
| }); |
| |
| tearDown(() { |
| /// Most tests will fake out the port forwarding and connection |
| /// functions. |
| restoreFuchsiaPortForwardingFunction(); |
| restoreVmServiceConnectionFunction(); |
| }); |
| |
| test('end-to-end with one vm connection and flutter view query', () async { |
| int port = 0; |
| Future<PortForwarder> fakePortForwardingFunction( |
| String address, |
| int remotePort, [ |
| String? interface = '', |
| String? configFile, |
| ]) { |
| return Future<PortForwarder>(() { |
| final FakePortForwarder pf = FakePortForwarder(); |
| forwardedPorts.add(pf); |
| pf.port = port++; |
| pf.remotePort = remotePort; |
| return pf; |
| }); |
| } |
| |
| fuchsiaPortForwardingFunction = fakePortForwardingFunction; |
| final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner(); |
| // Adds some extra junk to make sure the strings will be cleaned up. |
| fakeRunner.iqueryResponse = <String>[ |
| '[', |
| ' {', |
| ' "data_source": "Inspect",', |
| ' "metadata": {', |
| ' "filename": "fuchsia.inspect.Tree",', |
| ' "component_url": "fuchsia-pkg://fuchsia.com/flutter_runner#meta/flutter_runner.cm",', |
| ' "timestamp": 12345678901234', |
| ' },', |
| ' "moniker": "core/session-manager/session/flutter_runner",', |
| ' "payload": {', |
| ' "root": {', |
| ' "vm_service_port": "12345",', |
| ' "16859221": {', |
| ' "empty_tree": "this semantic tree is empty"', |
| ' },', |
| ' "build_info": {', |
| ' "dart_sdk_git_revision": "77e83fcc14fa94049f363d554579f48fbd6bb7a1",', |
| ' "dart_sdk_semantic_version": "2.19.0-317.0.dev",', |
| ' "flutter_engine_git_revision": "563b8e830c697a543bf0a8a9f4ae3edfad86ea86",', |
| ' "fuchsia_sdk_version": "10.20221018.0.1"', |
| ' },', |
| ' "vm": {', |
| ' "dst_status": 1,', |
| ' "get_profile_status": 0,', |
| ' "num_get_profile_calls": 1,', |
| ' "num_intl_provider_errors": 0,', |
| ' "num_on_change_calls": 0,', |
| ' "timezone_content_status": 0,', |
| ' "tz_data_close_status": -1,', |
| ' "tz_data_status": -1', |
| ' }', |
| ' }', |
| ' },', |
| ' "version": 1', |
| ' }', |
| ' ]' |
| ]; |
| fakeRunner.address = 'fe80::8eae:4cff:fef4:9247'; |
| fakeRunner.interface = 'eno1'; |
| |
| final FuchsiaRemoteConnection connection = |
| await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner); |
| |
| expect(forwardedPorts.length, 1); |
| expect(forwardedPorts[0].remotePort, 12345); |
| |
| // VMs should be accessed via localhost ports given by |
| // [fakePortForwardingFunction]. |
| expect(uriConnections[0], |
| Uri(scheme: 'ws', host: '[::1]', port: 0, path: '/ws')); |
| |
| final List<FlutterView> views = await connection.getFlutterViews(); |
| expect(views, isNot(null)); |
| expect(views.length, 1); |
| // Since name can be null, check for the ID on all of them. |
| expect(views[0].id, 'flutterView0'); |
| |
| expect(views[0].name, equals(null)); |
| |
| // Ensure the ports are all closed after stop was called. |
| await connection.stop(); |
| expect(forwardedPorts[0].stopped, true); |
| }); |
| |
| test('end-to-end with one vm and remote open port', () async { |
| int port = 0; |
| Future<PortForwarder> fakePortForwardingFunction( |
| String address, |
| int remotePort, [ |
| String? interface = '', |
| String? configFile, |
| ]) { |
| return Future<PortForwarder>(() { |
| final FakePortForwarder pf = FakePortForwarder(); |
| forwardedPorts.add(pf); |
| pf.port = port++; |
| pf.remotePort = remotePort; |
| pf.openPortAddress = 'fe80::1:2%eno2'; |
| return pf; |
| }); |
| } |
| |
| fuchsiaPortForwardingFunction = fakePortForwardingFunction; |
| final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner(); |
| // Adds some extra junk to make sure the strings will be cleaned up. |
| fakeRunner.iqueryResponse = <String>[ |
| '[', |
| ' {', |
| ' "data_source": "Inspect",', |
| ' "metadata": {', |
| ' "filename": "fuchsia.inspect.Tree",', |
| ' "component_url": "fuchsia-pkg://fuchsia.com/flutter_runner#meta/flutter_runner.cm",', |
| ' "timestamp": 12345678901234', |
| ' },', |
| ' "moniker": "core/session-manager/session/flutter_runner",', |
| ' "payload": {', |
| ' "root": {', |
| ' "vm_service_port": "12345",', |
| ' "16859221": {', |
| ' "empty_tree": "this semantic tree is empty"', |
| ' },', |
| ' "build_info": {', |
| ' "dart_sdk_git_revision": "77e83fcc14fa94049f363d554579f48fbd6bb7a1",', |
| ' "dart_sdk_semantic_version": "2.19.0-317.0.dev",', |
| ' "flutter_engine_git_revision": "563b8e830c697a543bf0a8a9f4ae3edfad86ea86",', |
| ' "fuchsia_sdk_version": "10.20221018.0.1"', |
| ' },', |
| ' "vm": {', |
| ' "dst_status": 1,', |
| ' "get_profile_status": 0,', |
| ' "num_get_profile_calls": 1,', |
| ' "num_intl_provider_errors": 0,', |
| ' "num_on_change_calls": 0,', |
| ' "timezone_content_status": 0,', |
| ' "tz_data_close_status": -1,', |
| ' "tz_data_status": -1', |
| ' }', |
| ' }', |
| ' },', |
| ' "version": 1', |
| ' }', |
| ' ]' |
| ]; |
| fakeRunner.address = 'fe80::8eae:4cff:fef4:9247'; |
| fakeRunner.interface = 'eno1'; |
| final FuchsiaRemoteConnection connection = |
| await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner); |
| |
| expect(forwardedPorts.length, 1); |
| expect(forwardedPorts[0].remotePort, 12345); |
| |
| // VMs should be accessed via the alternate address given by |
| // [fakePortForwardingFunction]. |
| expect(uriConnections[0], |
| Uri(scheme: 'ws', host: '[fe80::1:2%25eno2]', port: 0, path: '/ws')); |
| |
| final List<FlutterView> views = await connection.getFlutterViews(); |
| expect(views, isNot(null)); |
| expect(views.length, 1); |
| // Since name can be null, check for the ID on all of them. |
| expect(views[0].id, 'flutterView0'); |
| |
| expect(views[0].name, equals(null)); |
| |
| // Ensure the ports are all closed after stop was called. |
| await connection.stop(); |
| expect(forwardedPorts[0].stopped, true); |
| }); |
| |
| test('end-to-end with one vm and ipv4', () async { |
| int port = 0; |
| Future<PortForwarder> fakePortForwardingFunction( |
| String address, |
| int remotePort, [ |
| String? interface = '', |
| String? configFile, |
| ]) { |
| return Future<PortForwarder>(() { |
| final FakePortForwarder pf = FakePortForwarder(); |
| forwardedPorts.add(pf); |
| pf.port = port++; |
| pf.remotePort = remotePort; |
| return pf; |
| }); |
| } |
| |
| fuchsiaPortForwardingFunction = fakePortForwardingFunction; |
| final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner(); |
| // Adds some extra junk to make sure the strings will be cleaned up. |
| fakeRunner.iqueryResponse = <String>[ |
| '[', |
| ' {', |
| ' "data_source": "Inspect",', |
| ' "metadata": {', |
| ' "filename": "fuchsia.inspect.Tree",', |
| ' "component_url": "fuchsia-pkg://fuchsia.com/flutter_runner#meta/flutter_runner.cm",', |
| ' "timestamp": 12345678901234', |
| ' },', |
| ' "moniker": "core/session-manager/session/flutter_runner",', |
| ' "payload": {', |
| ' "root": {', |
| ' "vm_service_port": "12345",', |
| ' "16859221": {', |
| ' "empty_tree": "this semantic tree is empty"', |
| ' },', |
| ' "build_info": {', |
| ' "dart_sdk_git_revision": "77e83fcc14fa94049f363d554579f48fbd6bb7a1",', |
| ' "dart_sdk_semantic_version": "2.19.0-317.0.dev",', |
| ' "flutter_engine_git_revision": "563b8e830c697a543bf0a8a9f4ae3edfad86ea86",', |
| ' "fuchsia_sdk_version": "10.20221018.0.1"', |
| ' },', |
| ' "vm": {', |
| ' "dst_status": 1,', |
| ' "get_profile_status": 0,', |
| ' "num_get_profile_calls": 1,', |
| ' "num_intl_provider_errors": 0,', |
| ' "num_on_change_calls": 0,', |
| ' "timezone_content_status": 0,', |
| ' "tz_data_close_status": -1,', |
| ' "tz_data_status": -1', |
| ' }', |
| ' }', |
| ' },', |
| ' "version": 1', |
| ' }', |
| ' ]' |
| ]; |
| fakeRunner.address = '196.168.1.4'; |
| |
| final FuchsiaRemoteConnection connection = |
| await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner); |
| |
| expect(forwardedPorts.length, 1); |
| expect(forwardedPorts[0].remotePort, 12345); |
| |
| // VMs should be accessed via the ipv4 loopback. |
| expect(uriConnections[0], |
| Uri(scheme: 'ws', host: '127.0.0.1', port: 0, path: '/ws')); |
| |
| final List<FlutterView> views = await connection.getFlutterViews(); |
| expect(views, isNot(null)); |
| expect(views.length, 1); |
| // Since name can be null, check for the ID on all of them. |
| expect(views[0].id, 'flutterView0'); |
| |
| expect(views[0].name, equals(null)); |
| |
| // Ensure the ports are all closed after stop was called. |
| await connection.stop(); |
| expect(forwardedPorts[0].stopped, true); |
| }); |
| |
| test('env variable test without remote addr', () async { |
| Future<void> failingFunction() async { |
| await FuchsiaRemoteConnection.connect(); |
| } |
| |
| // Should fail as no env variable has been passed. |
| expect(failingFunction, |
| throwsA(isA<FuchsiaRemoteConnectionError>())); |
| }); |
| }); |
| } |
| |
| class FakeSshCommandRunner extends Fake implements SshCommandRunner { |
| List<String>? iqueryResponse; |
| @override |
| Future<List<String>> run(String command) async { |
| if (command.startsWith('iquery --format json show')) { |
| return iqueryResponse!; |
| } |
| throw UnimplementedError(command); |
| } |
| |
| @override |
| String interface = ''; |
| |
| @override |
| String address = ''; |
| |
| @override |
| String get sshConfigPath => '~/.ssh'; |
| } |
| |
| class FakePortForwarder extends Fake implements PortForwarder { |
| @override |
| int port = 0; |
| |
| @override |
| int remotePort = 0; |
| |
| @override |
| String? openPortAddress; |
| |
| bool stopped = false; |
| @override |
| Future<void> stop() async { |
| stopped = true; |
| } |
| } |
| |
| class FakeVmService extends Fake implements vms.VmService { |
| bool disposed = false; |
| vms.Response? flutterListViews; |
| |
| @override |
| Future<void> dispose() async { |
| disposed = true; |
| } |
| |
| @override |
| Future<vms.Response> callMethod(String method, {String? isolateId, Map<String, dynamic>? args}) async { |
| if (method == '_flutter.listViews') { |
| return flutterListViews!; |
| } |
| throw UnimplementedError(method); |
| } |
| |
| @override |
| Future<void> onDone = Future<void>.value(); |
| |
| @override |
| Future<vms.Version> getVersion() async { |
| return vms.Version(major: -1, minor: -1); |
| } |
| } |