From b187bc474e93aa9b0d58cd194b8d67b2bdf5eeec Mon Sep 17 00:00:00 2001 From: Naud Ghebre <30516046+naudzghebre@users.noreply.github.com> Date: Fri, 4 Nov 2022 16:39:53 -0400 Subject: [PATCH] Switch the way we retrieve the vm_service_port from /hub to iquery, on device. (#114637) * [fuchsia_remote_debug_protocol] Switch the way we retrieve the vm_service_port from /hub to iquery, on device. * [fuchsia_remote_debug_protocol] Switch the way we retrieve the vm_service_port from /hub to iquery, on device. --- .../lib/src/fuchsia_remote_connection.dart | 49 ++-- .../test/fuchsia_remote_connection_test.dart | 210 +++++++++++------- 2 files changed, 165 insertions(+), 94 deletions(-) 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 index 200e0fb829..7a85d0ebc0 100644 --- a/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart +++ b/packages/fuchsia_remote_debug_protocol/lib/src/fuchsia_remote_connection.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:process/process.dart'; @@ -506,6 +507,28 @@ class FuchsiaRemoteConnection { _pollDartVms = true; } + /// Helper for getDeviceServicePorts() to extract the vm_service_port from + /// json response. + List getVmServicePortFromInspectSnapshot(List inspectSnapshot) { + final ports = []; + for (final item in inspectSnapshot) { + if (item['payload'] == null || !(item as Map).containsKey('payload')) continue; + final payload = item['payload']; + + if (payload['root'] == null || !(payload as Map).containsKey('root')) continue; + final root = payload['root']; + + if (root['vm_service_port'] == null || + !(root as Map).containsKey('vm_service_port')) continue; + + final int? port = int.tryParse(root['vm_service_port']); + if (port != null) { + ports.add(port); + } + } + return ports; + } + /// Gets the open Dart VM service ports on a remote Fuchsia device. /// /// The method attempts to get service ports through an SSH connection. Upon @@ -514,24 +537,14 @@ class FuchsiaRemoteConnection { /// found. An exception is thrown in the event of an actual error when /// attempting to acquire the ports. Future> getDeviceServicePorts() async { - final List portPaths = await _sshCommandRunner - .run('/bin/find /hub -name vmservice-port'); - final List ports = []; - for (final String path in portPaths) { - if (path == '') { - continue; - } - final List lsOutput = - await _sshCommandRunner.run('/bin/ls $path'); - for (final String line in lsOutput) { - if (line == '') { - continue; - } - final int? port = int.tryParse(line); - if (port != null) { - ports.add(port); - } - } + final inspectResult = await _sshCommandRunner + .run('iquery --format json show \'**:root:vm_service_port\''); + final inspectOutputJson = jsonDecode(inspectResult.join('\n')); + final List ports = + getVmServicePortFromInspectSnapshot(inspectOutputJson); + + if (ports.length > 1) { + throw StateError('More than one Flutter observatory port found'); } return ports; } 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 index f67013e93d..a7e681f010 100644 --- a/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart +++ b/packages/fuchsia_remote_debug_protocol/test/fuchsia_remote_connection_test.dart @@ -82,7 +82,7 @@ void main() { restoreVmServiceConnectionFunction(); }); - test('end-to-end with three vm connections and flutter view query', () async { + test('end-to-end with one vm connection and flutter view query', () async { int port = 0; Future fakePortForwardingFunction( String address, @@ -102,54 +102,76 @@ void main() { fuchsiaPortForwardingFunction = fakePortForwardingFunction; final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner(); // Adds some extra junk to make sure the strings will be cleaned up. - fakeRunner.findResponse = ['/hub/blah/blah/blah/vmservice-port\n']; - fakeRunner.lsResponse = ['123\n\n\n', '456 ', '789']; + fakeRunner.iqueryResponse = [ + '[', + ' {', + ' "data_source": "Inspect",', + ' "metadata": {', + ' "filename": "fuchsia.inspect.Tree",', + ' "component_url": "fuchsia-pkg://fuchsia.com/flutter_aot_runner#meta/flutter_runner.cm",', + ' "timestamp": 12345678901234', + ' },', + ' "moniker": "core/session-manager/session\: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); - // [fakePortForwardingFunction] will have returned three different + // [fakePortForwardingFunction] will have returned one // 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.length, 1); + expect(forwardedPorts[0].remotePort, 12345); 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')); + Uri(scheme: 'ws', host: '[::1]', port: 0, path: '/ws')); final List views = await connection.getFlutterViews(); expect(views, isNot(null)); - expect(views.length, 3); + expect(views.length, 1); // 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 { + test('end-to-end with one vm and remote open port', () async { int port = 0; Future fakePortForwardingFunction( String address, @@ -170,53 +192,72 @@ void main() { fuchsiaPortForwardingFunction = fakePortForwardingFunction; final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner(); // Adds some extra junk to make sure the strings will be cleaned up. - fakeRunner.findResponse = ['/hub/blah/blah/blah/vmservice-port\n']; - fakeRunner.lsResponse = ['123\n\n\n', '456 ', '789']; + fakeRunner.iqueryResponse = [ + '[', + ' {', + ' "data_source": "Inspect",', + ' "metadata": {', + ' "filename": "fuchsia.inspect.Tree",', + ' "component_url": "fuchsia-pkg://fuchsia.com/flutter_aot_runner#meta/flutter_runner.cm",', + ' "timestamp": 12345678901234', + ' },', + ' "moniker": "core/session-manager/session\: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); - // [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.length, 1); + expect(forwardedPorts[0].remotePort, 12345); 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')); + Uri(scheme: 'ws', host: '[fe80::1:2%25eno2]', port: 0, path: '/ws')); final List views = await connection.getFlutterViews(); expect(views, isNot(null)); - expect(views.length, 3); + expect(views.length, 1); // 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 { + test('end-to-end with one vm and ipv4', () async { int port = 0; Future fakePortForwardingFunction( String address, @@ -236,8 +277,44 @@ void main() { fuchsiaPortForwardingFunction = fakePortForwardingFunction; final FakeSshCommandRunner fakeRunner = FakeSshCommandRunner(); // Adds some extra junk to make sure the strings will be cleaned up. - fakeRunner.findResponse = ['/hub/blah/blah/blah/vmservice-port\n']; - fakeRunner.lsResponse = ['123\n\n\n', '456 ', '789']; + fakeRunner.iqueryResponse = [ + '[', + ' {', + ' "data_source": "Inspect",', + ' "metadata": {', + ' "filename": "fuchsia.inspect.Tree",', + ' "component_url": "fuchsia-pkg://fuchsia.com/flutter_aot_runner#meta/flutter_runner.cm",', + ' "timestamp": 12345678901234', + ' },', + ' "moniker": "core/session-manager/session\: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 = @@ -246,39 +323,25 @@ void main() { // [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.length, 1); + expect(forwardedPorts[0].remotePort, 12345); 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')); + Uri(scheme: 'ws', host: '127.0.0.1', port: 0, path: '/ws')); final List views = await connection.getFlutterViews(); expect(views, isNot(null)); - expect(views.length, 3); + expect(views.length, 1); // 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 { @@ -287,22 +350,17 @@ void main() { } // Should fail as no env variable has been passed. - expect(failingFunction, - throwsA(isA())); + expect(failingFunction, throwsA(isA())); }); }); } class FakeSshCommandRunner extends Fake implements SshCommandRunner { - List? findResponse; - List? lsResponse; + List? iqueryResponse; @override Future> run(String command) async { - if (command.startsWith('/bin/find')) { - return findResponse!; - } - if (command.startsWith('/bin/ls')) { - return lsResponse!; + if (command.startsWith('iquery --format json show')) { + return iqueryResponse!; } throw UnimplementedError(command); }