// 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:vm_service/vm_service.dart' as vms; import 'common.dart'; 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); } }