| // 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 three vm connections 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.findResponse = <String>['/hub/blah/blah/blah/vmservice-port\n']; |
| fakeRunner.lsResponse = <String>['123\n\n\n', '456 ', '789']; |
| fakeRunner.address = 'fe80::8eae:4cff:fef4:9247'; |
| fakeRunner.interface = 'eno1'; |
| |
| final FuchsiaRemoteConnection connection = |
| await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner); |
| |
| // [fakePortForwardingFunction] 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); |
| |
| // VMs should be accessed via localhost ports given by |
| // [fakePortForwardingFunction]. |
| expect(uriConnections[0], |
| Uri(scheme:'ws', host:'[::1]', port:0, path:'/ws')); |
| expect(uriConnections[1], |
| Uri(scheme:'ws', host:'[::1]', port:1, path:'/ws')); |
| expect(uriConnections[2], |
| Uri(scheme:'ws', host:'[::1]', port:2, path:'/ws')); |
| |
| final List<FlutterView> 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(); |
| expect(forwardedPorts[0].stopped, true); |
| expect(forwardedPorts[1].stopped, true); |
| expect(forwardedPorts[2].stopped, true); |
| }); |
| |
| test('end-to-end with three vms 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.findResponse = <String>['/hub/blah/blah/blah/vmservice-port\n']; |
| fakeRunner.lsResponse = <String>['123\n\n\n', '456 ', '789']; |
| fakeRunner.address = 'fe80::8eae:4cff:fef4:9247'; |
| fakeRunner.interface = 'eno1'; |
| final FuchsiaRemoteConnection connection = |
| await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner); |
| |
| // [fakePortForwardingFunction] 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); |
| |
| // 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')); |
| expect(uriConnections[1], |
| Uri(scheme:'ws', host:'[fe80::1:2%25eno2]', port:1, path:'/ws')); |
| expect(uriConnections[2], |
| Uri(scheme:'ws', host:'[fe80::1:2%25eno2]', port:2, path:'/ws')); |
| |
| final List<FlutterView> 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(); |
| expect(forwardedPorts[0].stopped, true); |
| expect(forwardedPorts[1].stopped, true); |
| expect(forwardedPorts[2].stopped, true); |
| }); |
| |
| test('end-to-end with three vms 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.findResponse = <String>['/hub/blah/blah/blah/vmservice-port\n']; |
| fakeRunner.lsResponse = <String>['123\n\n\n', '456 ', '789']; |
| fakeRunner.address = '196.168.1.4'; |
| |
| final FuchsiaRemoteConnection connection = |
| await FuchsiaRemoteConnection.connectWithSshCommandRunner(fakeRunner); |
| |
| // [fakePortForwardingFunction] 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); |
| |
| // VMs should be accessed via the ipv4 loopback. |
| expect(uriConnections[0], |
| Uri(scheme:'ws', host:'127.0.0.1', port:0, path:'/ws')); |
| expect(uriConnections[1], |
| Uri(scheme:'ws', host:'127.0.0.1', port:1, path:'/ws')); |
| expect(uriConnections[2], |
| Uri(scheme:'ws', host:'127.0.0.1', port:2, path:'/ws')); |
| |
| final List<FlutterView> 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(); |
| expect(forwardedPorts[0].stopped, true); |
| expect(forwardedPorts[1].stopped, true); |
| expect(forwardedPorts[2].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>? findResponse; |
| List<String>? lsResponse; |
| @override |
| Future<List<String>> run(String command) async { |
| if (command.startsWith('/bin/find')) { |
| return findResponse!; |
| } |
| if (command.startsWith('/bin/ls')) { |
| return lsResponse!; |
| } |
| 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); |
| } |
| } |