From 534b0608ce38a30b1ed5eb085f0ff64fb1c4c8b0 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Wed, 29 Apr 2020 11:52:46 -0700 Subject: [PATCH] [flutter_tools] remove vm service (#55794) Finishes the gradual vm service migration by deleting the flutter tooling's vm_service --- .../lib/src/commands/screenshot.dart | 20 +- packages/flutter_tools/lib/src/devfs.dart | 48 +- .../lib/src/fuchsia/fuchsia_device.dart | 21 +- .../lib/src/resident_runner.dart | 32 +- packages/flutter_tools/lib/src/run_cold.dart | 8 +- packages/flutter_tools/lib/src/run_hot.dart | 11 +- .../lib/src/test/coverage_collector.dart | 46 +- .../lib/src/test/flutter_platform.dart | 5 +- packages/flutter_tools/lib/src/tracing.dart | 18 +- packages/flutter_tools/lib/src/vmservice.dart | 1335 ++++------------- .../commands.shard/hermetic/attach_test.dart | 209 +-- .../coverage_collector_test.dart | 70 +- .../test/general.shard/devfs_test.dart | 219 +-- .../fuchsia/fuchsia_device_test.dart | 33 +- .../ios/ios_device_logger_test.dart | 3 +- .../general.shard/resident_runner_test.dart | 40 +- .../test/general.shard/vmservice_test.dart | 33 +- 17 files changed, 538 insertions(+), 1613 deletions(-) diff --git a/packages/flutter_tools/lib/src/commands/screenshot.dart b/packages/flutter_tools/lib/src/commands/screenshot.dart index 5d830e7fb3..3c32de782b 100644 --- a/packages/flutter_tools/lib/src/commands/screenshot.dart +++ b/packages/flutter_tools/lib/src/commands/screenshot.dart @@ -4,6 +4,8 @@ import 'dart:async'; +import 'package:vm_service/vm_service.dart' as vm_service; + import '../base/common.dart'; import '../base/file_system.dart'; import '../convert.dart'; @@ -123,39 +125,37 @@ class ScreenshotCommand extends FlutterCommand { } Future runSkia(File outputFile) async { - final Map skp = await _invokeVmServiceRpc('_flutter.screenshotSkp'); + final Uri observatoryUri = Uri.parse(stringArg(_kObservatoryUri)); + final vm_service.VmService vmService = await connectToVmService(observatoryUri); + final vm_service.Response skp = await vmService.screenshotSkp(); outputFile ??= globals.fsUtils.getUniqueFile( globals.fs.currentDirectory, 'flutter', 'skp', ); final IOSink sink = outputFile.openWrite(); - sink.add(base64.decode(skp['skp'] as String)); + sink.add(base64.decode(skp.json['skp'] as String)); await sink.close(); _showOutputFileInfo(outputFile); _ensureOutputIsNotJsonRpcError(outputFile); } Future runRasterizer(File outputFile) async { - final Map response = await _invokeVmServiceRpc('_flutter.screenshot'); + final Uri observatoryUri = Uri.parse(stringArg(_kObservatoryUri)); + final vm_service.VmService vmService = await connectToVmService(observatoryUri); + final vm_service.Response response = await vmService.screenshot(); outputFile ??= globals.fsUtils.getUniqueFile( globals.fs.currentDirectory, 'flutter', 'png', ); final IOSink sink = outputFile.openWrite(); - sink.add(base64.decode(response['screenshot'] as String)); + sink.add(base64.decode(response.json['screenshot'] as String)); await sink.close(); _showOutputFileInfo(outputFile); _ensureOutputIsNotJsonRpcError(outputFile); } - Future> _invokeVmServiceRpc(String method) async { - final Uri observatoryUri = Uri.parse(stringArg(_kObservatoryUri)); - final VMService vmService = await VMService.connect(observatoryUri); - return await vmService.vm.invokeRpcRaw(method); - } - void _ensureOutputIsNotJsonRpcError(File outputFile) { if (outputFile.lengthSync() >= 1000) { return; diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index 59fc388242..a3456aed09 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart @@ -6,7 +6,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:package_config/package_config.dart'; -import 'package:vm_service/vm_service.dart' as vmservice; +import 'package:vm_service/vm_service.dart' as vm_service; import 'asset.dart'; import 'base/context.dart'; @@ -223,40 +223,22 @@ abstract class DevFSOperations { class ServiceProtocolDevFSOperations implements DevFSOperations { ServiceProtocolDevFSOperations(this.vmService); - final VMService vmService; + final vm_service.VmService vmService; @override Future create(String fsName) async { - final Map response = await vmService.vm.createDevFS(fsName); - return Uri.parse(response['uri'] as String); + final vm_service.Response response = await vmService.createDevFS(fsName); + return Uri.parse(response.json['uri'] as String); } @override Future destroy(String fsName) async { - await vmService.vm.deleteDevFS(fsName); + await vmService.deleteDevFS(fsName); } @override Future writeFile(String fsName, Uri deviceUri, DevFSContent content) async { - List bytes; - try { - bytes = await content.contentsAsBytes(); - } on Exception catch (e) { - return e; - } - final String fileContents = base64.encode(bytes); - try { - return await vmService.vm.invokeRpcRaw( - '_writeDevFSFile', - params: { - 'fsName': fsName, - 'uri': deviceUri.toString(), - 'fileContents': fileContents, - }, - ); - } on Exception catch (error) { - globals.printTrace('DevFS: Failed to write $deviceUri: $error'); - } + throw UnsupportedError('Use the HTTP devFS api.'); } } @@ -270,7 +252,7 @@ class DevFSException implements Exception { class _DevFSHttpWriter { _DevFSHttpWriter( this.fsName, - VMService serviceProtocol, { + vm_service.VmService serviceProtocol, { @required OperatingSystemUtils osUtils, }) : httpAddress = serviceProtocol.httpAddress, @@ -392,22 +374,25 @@ class UpdateFSReport { class DevFS { /// Create a [DevFS] named [fsName] for the local files in [rootDirectory]. DevFS( - VMService serviceProtocol, + vm_service.VmService serviceProtocol, this.fsName, this.rootDirectory, { @required OperatingSystemUtils osUtils, + @visibleForTesting bool disableUpload = false, }) : _operations = ServiceProtocolDevFSOperations(serviceProtocol), _httpWriter = _DevFSHttpWriter( fsName, serviceProtocol, osUtils: osUtils, - ); + ), + _disableUpload = disableUpload; DevFS.operations( this._operations, this.fsName, this.rootDirectory, - ) : _httpWriter = null; + ) : _httpWriter = null, + _disableUpload = false; final DevFSOperations _operations; final _DevFSHttpWriter _httpWriter; @@ -417,6 +402,7 @@ class DevFS { List sources = []; DateTime lastCompiled; PackageConfig lastPackageConfig; + final bool _disableUpload; Uri _baseUri; Uri get baseUri => _baseUri; @@ -435,7 +421,7 @@ class DevFS { globals.printTrace('DevFS: Creating new filesystem on the device ($_baseUri)'); try { _baseUri = await _operations.create(fsName); - } on vmservice.RPCError catch (rpcException) { + } on vm_service.RPCError catch (rpcException) { // 1001 is kFileSystemAlreadyExists in //dart/runtime/vm/json_stream.h if (rpcException.code != 1001) { rethrow; @@ -542,7 +528,9 @@ class DevFS { globals.printTrace('Updating files'); if (dirtyEntries.isNotEmpty) { try { - await _httpWriter.write(dirtyEntries); + if (!_disableUpload) { + await _httpWriter.write(dirtyEntries); + } } on SocketException catch (socketException, stackTrace) { globals.printTrace('DevFS sync failed. Lost connection to device: $socketException'); throw DevFSException('Lost connection to device.', socketException, stackTrace); diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart index fa131fc0b1..0ec719a145 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; import '../application_package.dart'; import '../artifacts.dart'; @@ -46,8 +47,8 @@ final String _ipv4Loopback = InternetAddress.loopbackIPv4.address; final String _ipv6Loopback = InternetAddress.loopbackIPv6.address; // Enables testing the fuchsia isolate discovery -Future _kDefaultFuchsiaIsolateDiscoveryConnector(Uri uri) { - return VMService.connect(uri); +Future _kDefaultFuchsiaIsolateDiscoveryConnector(Uri uri) { + return connectToVmService(uri); } /// Read the log for a particular device. @@ -619,15 +620,14 @@ class FuchsiaDevice extends Device { // netstat shows that the local port is actually being used on the IPv6 // loopback (::1). final Uri uri = Uri.parse('http://[$_ipv6Loopback]:$port'); - final VMService vmService = await VMService.connect(uri); + final vm_service.VmService vmService = await connectToVmService(uri); final List flutterViews = await vmService.getFlutterViews(); for (final FlutterView flutterView in flutterViews) { if (flutterView.uiIsolate == null) { continue; } - final Uri address = vmService.httpAddress; if (flutterView.uiIsolate.name.contains(isolateName)) { - return address.port; + return vmService.httpAddress.port; } } } on SocketException catch (err) { @@ -662,11 +662,11 @@ class FuchsiaIsolateDiscoveryProtocol { ]); static const Duration _pollDuration = Duration(seconds: 10); - final Map _ports = {}; + final Map _ports = {}; final FuchsiaDevice _device; final String _isolateName; final Completer _foundUri = Completer(); - final Future Function(Uri) _vmServiceConnector; + final Future Function(Uri) _vmServiceConnector; // whether to only poll once. final bool _pollOnce; Timer _pollingTimer; @@ -702,7 +702,7 @@ class FuchsiaIsolateDiscoveryProtocol { Future _findIsolate() async { final List ports = await _device.servicePorts(); for (final int port in ports) { - VMService service; + vm_service.VmService service; if (_ports.containsKey(port)) { service = _ports[port]; } else { @@ -721,11 +721,10 @@ class FuchsiaIsolateDiscoveryProtocol { if (flutterView.uiIsolate == null) { continue; } - final Uri address = service.httpAddress; if (flutterView.uiIsolate.name.contains(_isolateName)) { _foundUri.complete(_device.ipv6 - ? Uri.parse('http://[$_ipv6Loopback]:${address.port}/') - : Uri.parse('http://$_ipv4Loopback:${address.port}/')); + ? Uri.parse('http://[$_ipv6Loopback]:${service.httpAddress.port}/') + : Uri.parse('http://$_ipv4Loopback:${service.httpAddress.port}/')); _status.stop(); return; } diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 0f328b2bfc..971c74f676 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -187,10 +187,10 @@ class FlutterDevice { // FYI, this message is used as a sentinel in tests. globals.printTrace('Connecting to service protocol: $observatoryUri'); isWaitingForVm = true; - VMService service; + vm_service.VmService service; try { - service = await VMService.connect( + service = await connectToVmService( observatoryUri, reloadSources: reloadSources, restart: restart, @@ -226,9 +226,6 @@ class FlutterDevice { return completer.future; } - // TODO(jonahwilliams): remove once all callsites are updated. - VMService get flutterDeprecatedVmService => vmService as VMService; - Future refreshViews() async { if (vmService == null) { return; @@ -254,8 +251,6 @@ class FlutterDevice { return _views; } - Future getVMs() => flutterDeprecatedVmService.getVMOld(); - Future exitApps() async { if (!device.supportsFlutterExit) { await device.stopApp(package); @@ -308,7 +303,7 @@ class FlutterDevice { }) { // One devFS per device. Shared by all running instances. devFS = DevFS( - flutterDeprecatedVmService, + vmService, fsName, rootDirectory, osUtils: globals.os, @@ -316,16 +311,17 @@ class FlutterDevice { return devFS.create(); } - List> reloadSources( + Future>> reloadSources( String entryPath, { bool pause = false, - }) { + }) async { final String deviceEntryUri = devFS.baseUri .resolveUri(globals.fs.path.toUri(entryPath)).toString(); + final vm_service.VM vm = await vmService.getVM(); return >[ - for (final Isolate isolate in flutterDeprecatedVmService.vm.isolates) + for (final vm_service.IsolateRef isolateRef in vm.isolates) vmService.reloadSources( - isolate.id, + isolateRef.id, pause: pause, rootLibUri: deviceEntryUri, ) @@ -867,7 +863,7 @@ abstract class ResidentRunner { void writeVmserviceFile() { if (debuggingOptions.vmserviceOutFile != null) { try { - final String address = flutterDevices.first.flutterDeprecatedVmService.wsAddress.toString(); + final String address = flutterDevices.first.vmService.wsAddress.toString(); final File vmserviceOutFile = globals.fs.file(debuggingOptions.vmserviceOutFile); vmserviceOutFile.createSync(recursive: true); vmserviceOutFile.writeAsStringSync(address); @@ -899,13 +895,6 @@ abstract class ResidentRunner { await Future.wait(futures); } - Future refreshVM() async { - final List> futures = >[ - for (final FlutterDevice device in flutterDevices) device.getVMs(), - ]; - await Future.wait(futures); - } - Future debugDumpApp() async { await refreshViews(); for (final FlutterDevice device in flutterDevices) { @@ -1086,7 +1075,6 @@ abstract class ResidentRunner { compileExpression: compileExpression, reloadMethod: reloadMethod, ); - await device.getVMs(); await device.refreshViews(); if (device.views.isNotEmpty) { viewFound = true; @@ -1122,7 +1110,7 @@ abstract class ResidentRunner { { 'reuseWindows': true, }, - flutterDevices.first.flutterDeprecatedVmService.httpAddress, + flutterDevices.first.vmService.httpAddress, 'http://${_devtoolsServer.address.host}:${_devtoolsServer.port}', false, // headless mode, false, // machine mode diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart index 1b8427979e..177942604f 100644 --- a/packages/flutter_tools/lib/src/run_cold.dart +++ b/packages/flutter_tools/lib/src/run_cold.dart @@ -83,8 +83,8 @@ class ColdRunner extends ResidentRunner { if (flutterDevices.first.observatoryUris != null) { // For now, only support one debugger connection. connectionInfoCompleter?.complete(DebugConnectionInfo( - httpUri: flutterDevices.first.flutterDeprecatedVmService.httpAddress, - wsUri: flutterDevices.first.flutterDeprecatedVmService.wsAddress, + httpUri: flutterDevices.first.vmService.httpAddress, + wsUri: flutterDevices.first.vmService.wsAddress, )); } @@ -105,7 +105,7 @@ class ColdRunner extends ResidentRunner { if (device.vmService != null) { globals.printStatus('Tracing startup on ${device.device.name}.'); await downloadStartupTrace( - device.flutterDeprecatedVmService, + device.vmService, awaitFirstFrame: awaitFirstFrameWhenTracing, ); } @@ -197,7 +197,7 @@ class ColdRunner extends ResidentRunner { // Caution: This log line is parsed by device lab tests. globals.printStatus( 'An Observatory debugger and profiler on $dname is available at: ' - '${device.flutterDeprecatedVmService.httpAddress}', + '${device.vmService.httpAddress}', ); } } diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 9f0a6ea5d7..38b36fb0cb 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -186,7 +186,7 @@ class HotRunner extends ResidentRunner { from: projectRootPath, ); for (final FlutterDevice device in flutterDevices) { - final List> reportFutures = device.reloadSources( + final List> reportFutures = await device.reloadSources( entryPath, pause: false, ); final List reports = await Future.wait(reportFutures); @@ -257,8 +257,8 @@ class HotRunner extends ResidentRunner { // Only handle one debugger connection. connectionInfoCompleter.complete( DebugConnectionInfo( - httpUri: flutterDevices.first.flutterDeprecatedVmService.httpAddress, - wsUri: flutterDevices.first.flutterDeprecatedVmService.wsAddress, + httpUri: flutterDevices.first.vmService.httpAddress, + wsUri: flutterDevices.first.vmService.wsAddress, baseUri: baseUris.first.toString(), ), ); @@ -820,7 +820,6 @@ class HotRunner extends ResidentRunner { final Stopwatch reloadTimer = Stopwatch()..start(); globals.printTrace('Refreshing active FlutterViews before reloading.'); - await refreshVM(); await refreshViews(); final Stopwatch devFSTimer = Stopwatch()..start(); @@ -847,7 +846,7 @@ class HotRunner extends ResidentRunner { await device.resetAssetDirectory(); _shouldResetAssetDirectory = false; } - final List> reportFutures = device.reloadSources( + final List> reportFutures = await device.reloadSources( entryPath, pause: pause, ); allReportsFutures.add(Future.wait(reportFutures).then( @@ -1117,7 +1116,7 @@ class HotRunner extends ResidentRunner { // Caution: This log line is parsed by device lab tests. globals.printStatus( 'An Observatory debugger and profiler on $dname is available at: ' - '${device.flutterDeprecatedVmService.httpAddress}', + '${device.vmService.httpAddress}', ); } } diff --git a/packages/flutter_tools/lib/src/test/coverage_collector.dart b/packages/flutter_tools/lib/src/test/coverage_collector.dart index cb18c6ccc3..22d11559eb 100644 --- a/packages/flutter_tools/lib/src/test/coverage_collector.dart +++ b/packages/flutter_tools/lib/src/test/coverage_collector.dart @@ -185,29 +185,27 @@ class CoverageCollector extends TestWatcher { Future handleTestTimedOut(ProcessEvent event) async { } } -Future _defaultConnect(Uri serviceUri) { - return VMService.connect( +Future _defaultConnect(Uri serviceUri) { + return connectToVmService( serviceUri, compression: CompressionOptions.compressionOff); } Future> collect(Uri serviceUri, bool Function(String) libraryPredicate, { bool waitPaused = false, String debugName, - Future Function(Uri) connector = _defaultConnect, + Future Function(Uri) connector = _defaultConnect, }) async { - final VMService vmService = await connector(serviceUri); - await vmService.getVMOld(); + final vm_service.VmService vmService = await connector(serviceUri); final Map result = await _getAllCoverage( vmService, libraryPredicate); - await vmService.close(); + vmService.dispose(); return result; } -Future> _getAllCoverage(VMService service, bool Function(String) libraryPredicate) async { - await service.getVMOld(); +Future> _getAllCoverage(vm_service.VmService service, bool Function(String) libraryPredicate) async { + final vm_service.VM vm = await service.getVM(); final List> coverage = >[]; - for (final Isolate isolateRef in service.vm.isolates) { - await isolateRef.load(); + for (final vm_service.IsolateRef isolateRef in vm.isolates) { Map scriptList; try { final vm_service.ScriptList actualScriptList = await service.getScripts(isolateRef.id); @@ -228,24 +226,22 @@ Future> _getAllCoverage(VMService service, bool Function(St } final String scriptId = script['id'] as String; futures.add( - isolateRef.invokeRpcRaw('getSourceReport', params: { - 'forceCompile': true, - 'scriptId': scriptId, - 'isolateId': isolateRef.id, - 'reports': ['Coverage'], - }) - .then((Map report) { - sourceReports[scriptId] = report; + service.getSourceReport( + isolateRef.id, + ['Coverage'], + scriptId: scriptId, + forceCompile: true, + ) + .then((vm_service.SourceReport report) { + sourceReports[scriptId] = report.json; }) ); futures.add( - isolateRef.invokeRpcRaw('getObject', params: { - 'isolateId': isolateRef.id, - 'objectId': scriptId, - }) - .then((Map script) { - scripts[scriptId] = script; - }) + service + .getObject(isolateRef.id, scriptId) + .then((vm_service.Obj script) { + scripts[scriptId] = script.json; + }) ); } await Future.wait(futures); diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart index c612b2a990..b4cd0dad4f 100644 --- a/packages/flutter_tools/lib/src/test/flutter_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:stream_channel/stream_channel.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; import 'package:test_api/src/backend/suite_platform.dart'; // ignore: implementation_imports import 'package:test_core/src/runner/runner_suite.dart'; // ignore: implementation_imports @@ -521,9 +522,9 @@ class FlutterPlatform extends PlatformPlugin { processObservatoryUri = detectedUri; { globals.printTrace('Connecting to service protocol: $processObservatoryUri'); - final Future localVmService = VMService.connect(processObservatoryUri, + final Future localVmService = connectToVmService(processObservatoryUri, compileExpression: _compileExpressionService); - localVmService.then((VMService vmservice) { + localVmService.then((vm_service.VmService vmservice) { globals.printTrace('Successfully connected to service protocol: $processObservatoryUri'); }); } diff --git a/packages/flutter_tools/lib/src/tracing.dart b/packages/flutter_tools/lib/src/tracing.dart index b70850f211..1a32c45566 100644 --- a/packages/flutter_tools/lib/src/tracing.dart +++ b/packages/flutter_tools/lib/src/tracing.dart @@ -24,15 +24,15 @@ class Tracing { static const String firstUsefulFrameEventName = kFirstFrameRasterizedEventName; static Future connect(Uri uri) async { - final VMService observatory = await VMService.connect(uri); + final vm_service.VmService observatory = await connectToVmService(uri); return Tracing(observatory); } - final VMService vmService; + final vm_service.VmService vmService; Future startTracing() async { - await vmService.vm.setVMTimelineFlags(['Compiler', 'Dart', 'Embedder', 'GC']); - await vmService.vm.clearVMTimeline(); + await vmService.setVMTimelineFlags(['Compiler', 'Dart', 'Embedder', 'GC']); + await vmService.clearVMTimeline(); } /// Stops tracing; optionally wait for first frame. @@ -73,15 +73,15 @@ class Tracing { } status.stop(); } - final Map timeline = await vmService.vm.getVMTimeline(); - await vmService.vm.setVMTimelineFlags([]); - return timeline; + final vm_service.Timeline timeline = await vmService.getVMTimeline(); + await vmService.setVMTimelineFlags([]); + return timeline.json; } } /// Download the startup trace information from the given observatory client and /// store it to build/start_up_info.json. -Future downloadStartupTrace(VMService observatory, { bool awaitFirstFrame = true }) async { +Future downloadStartupTrace(vm_service.VmService vmService, { bool awaitFirstFrame = true }) async { final String traceInfoFilePath = globals.fs.path.join(getBuildDirectory(), 'start_up_info.json'); final File traceInfoFile = globals.fs.file(traceInfoFilePath); @@ -95,7 +95,7 @@ Future downloadStartupTrace(VMService observatory, { bool awaitFirstFrame traceInfoFile.parent.createSync(); } - final Tracing tracing = Tracing(observatory); + final Tracing tracing = Tracing(vmService); final Map timeline = await tracing.stopTracingAndDownloadTimeline( awaitFirstFrame: awaitFirstFrame, diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index d9a1d9e9ed..8a30d1c771 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -4,13 +4,11 @@ import 'dart:async'; -import 'package:meta/meta.dart' show required; +import 'package:meta/meta.dart' show required, visibleForTesting; import 'package:vm_service/vm_service.dart' as vm_service; import 'base/context.dart'; import 'base/io.dart' as io; -import 'base/utils.dart'; -import 'convert.dart' show base64, json, utf8; import 'device.dart'; import 'globals.dart' as globals; import 'version.dart'; @@ -20,6 +18,8 @@ const String kSetAssetBundlePathMethod = '_flutter.setAssetBundlePath'; const String kFlushUIThreadTasksMethod = '_flutter.flushUIThreadTasks'; const String kRunInViewMethod = '_flutter.runInView'; const String kListViewsMethod = '_flutter.listViews'; +const String kScreenshotSkpMethod = '_flutter.screenshotSkp'; +const String kScreenshotMethod = '_flutter.screenshot'; /// The error response code from an unrecoverable compilation failure. const int kIsolateReloadBarred = 1005; @@ -121,7 +121,7 @@ Future _defaultOpenChannel(String url, { /// Override `VMServiceConnector` in [context] to return a different VMService /// from [VMService.connect] (used by tests). -typedef VMServiceConnector = Future Function(Uri httpUri, { +typedef VMServiceConnector = Future Function(Uri httpUri, { ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, @@ -130,240 +130,216 @@ typedef VMServiceConnector = Future Function(Uri httpUri, { Device device, }); -/// A connection to the Dart VM Service. -/// -/// This also implements the package:vm_service API to enable a gradual migration. -class VMService implements vm_service.VmService { - VMService( - this.httpAddress, - this.wsAddress, - ReloadSources reloadSources, - Restart restart, - CompileExpression compileExpression, - Device device, - ReloadMethod reloadMethod, - this._delegateService, - this.streamClosedCompleter, - Stream secondary, - ) { - _vm = VM._empty(this); +final Expando _httpAddressExpando = Expando(); - // TODO(jonahwilliams): this is temporary to support the current vm_service - // semantics of update-in-place. - secondary.listen((dynamic rawData) { - final String message = rawData as String; - final dynamic map = json.decode(message); - if (map != null && map['method'] == 'streamNotify') { - _handleStreamNotify(map['params'] as Map); +final Expando _wsAddressExpando = Expando(); + +@visibleForTesting +void setHttpAddress(Uri uri, vm_service.VmService vmService) { + _httpAddressExpando[vmService] = uri; +} + +@visibleForTesting +void setWsAddress(Uri uri, vm_service.VmService vmService) { + _wsAddressExpando[vmService] = uri; +} + +/// A connection to the Dart VM Service. +vm_service.VmService setUpVmService( + ReloadSources reloadSources, + Restart restart, + CompileExpression compileExpression, + Device device, + ReloadMethod reloadMethod, + vm_service.VmService vmService +) { + if (reloadSources != null) { + vmService.registerServiceCallback('reloadSources', (Map params) async { + final String isolateId = params['isolateId'].value as String; + final bool force = params['force'] as bool ?? false; + final bool pause = params['pause'] as bool ?? false; + + if (isolateId.isEmpty) { + throw vm_service.RPCError( + "Invalid 'isolateId': $isolateId", + RPCErrorCodes.kInvalidParams, + '', + ); + } + try { + await reloadSources(isolateId, force: force, pause: pause); + return {'type': 'Success'}; + } on vm_service.RPCError { + rethrow; + } on Exception catch (e, st) { + throw vm_service.RPCError( + 'Error during Sources Reload: $e\n$st', + RPCErrorCodes.kServerError, + '', + ); } }); + vmService.registerService('reloadSources', 'Flutter Tools'); - if (reloadSources != null) { - _delegateService.registerServiceCallback('reloadSources', (Map params) async { - final String isolateId = params['isolateId'].value as String; - final bool force = params['force'] as bool ?? false; - final bool pause = params['pause'] as bool ?? false; + } - if (isolateId.isEmpty) { - throw vm_service.RPCError( - "Invalid 'isolateId': $isolateId", - RPCErrorCodes.kInvalidParams, - '', - ); - } - try { - await reloadSources(isolateId, force: force, pause: pause); - return {'type': 'Success'}; - } on vm_service.RPCError { - rethrow; - } on Exception catch (e, st) { - throw vm_service.RPCError( - 'Error during Sources Reload: $e\n$st', - RPCErrorCodes.kServerError, - '', - ); - } - }); - _delegateService.registerService('reloadSources', 'Flutter Tools'); + if (reloadMethod != null) { + // Register a special method for hot UI. while this is implemented + // currently in the same way as hot reload, it leaves the tool free + // to change to a more efficient implementation in the future. + // + // `library` should be the file URI of the updated code. + // `class` should be the name of the Widget subclass to be marked dirty. For example, + // if the build method of a StatelessWidget is updated, this is the name of class. + // If the build method of a StatefulWidget is updated, then this is the name + // of the Widget class that created the State object. + vmService.registerServiceCallback('reloadMethod', (Map params) async { + final String libraryId = params['library'] as String; + final String classId = params['class'] as String; - } + if (libraryId.isEmpty) { + throw vm_service.RPCError( + "Invalid 'libraryId': $libraryId", + RPCErrorCodes.kInvalidParams, + '', + ); + } + if (classId.isEmpty) { + throw vm_service.RPCError( + "Invalid 'classId': $classId", + RPCErrorCodes.kInvalidParams, + '', + ); + } - if (reloadMethod != null) { - // Register a special method for hot UI. while this is implemented - // currently in the same way as hot reload, it leaves the tool free - // to change to a more efficient implementation in the future. - // - // `library` should be the file URI of the updated code. - // `class` should be the name of the Widget subclass to be marked dirty. For example, - // if the build method of a StatelessWidget is updated, this is the name of class. - // If the build method of a StatefulWidget is updated, then this is the name - // of the Widget class that created the State object. - _delegateService.registerServiceCallback('reloadMethod', (Map params) async { - final String libraryId = params['library'] as String; - final String classId = params['class'] as String; + globals.printTrace('reloadMethod not yet supported, falling back to hot reload'); - if (libraryId.isEmpty) { - throw vm_service.RPCError( - "Invalid 'libraryId': $libraryId", - RPCErrorCodes.kInvalidParams, - '', - ); - } - if (classId.isEmpty) { - throw vm_service.RPCError( - "Invalid 'classId': $classId", - RPCErrorCodes.kInvalidParams, - '', - ); - } - - globals.printTrace('reloadMethod not yet supported, falling back to hot reload'); - - try { - await reloadMethod( - libraryId: libraryId, - classId: classId, - ); - return {'type': 'Success'}; - } on vm_service.RPCError { - rethrow; - } on Exception catch (e, st) { - throw vm_service.RPCError('Error during Sources Reload: $e\n$st', -32000, ''); - } - }); - _delegateService.registerService('reloadMethod', 'Flutter Tools'); - } - - if (restart != null) { - _delegateService.registerServiceCallback('hotRestart', (Map params) async { - final bool pause = params['pause'] as bool ?? false; - try { - await restart(pause: pause); - return {'type': 'Success'}; - } on vm_service.RPCError { - rethrow; - } on Exception catch (e, st) { - throw vm_service.RPCError( - 'Error during Hot Restart: $e\n$st', - RPCErrorCodes.kServerError, - '', - ); - } - }); - _delegateService.registerService('hotRestart', 'Flutter Tools'); - } - - _delegateService.registerServiceCallback('flutterVersion', (Map params) async { - final FlutterVersion version = context.get() ?? FlutterVersion(); - final Map versionJson = version.toJson(); - versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort; - versionJson['engineRevisionShort'] = version.engineRevisionShort; - return { - 'result': { - 'type': 'Success', - ...versionJson, - } - }; + try { + await reloadMethod( + libraryId: libraryId, + classId: classId, + ); + return {'type': 'Success'}; + } on vm_service.RPCError { + rethrow; + } on Exception catch (e, st) { + throw vm_service.RPCError('Error during Sources Reload: $e\n$st', -32000, ''); + } }); - _delegateService.registerService('flutterVersion', 'Flutter Tools'); + vmService.registerService('reloadMethod', 'Flutter Tools'); + } - if (compileExpression != null) { - _delegateService.registerServiceCallback('compileExpression', (Map params) async { - final String isolateId = params['isolateId'] as String; - if (isolateId is! String || isolateId.isEmpty) { - throw throw vm_service.RPCError( - "Invalid 'isolateId': $isolateId", - RPCErrorCodes.kInvalidParams, - '', - ); - } - final String expression = params['expression'] as String; - if (expression is! String || expression.isEmpty) { - throw throw vm_service.RPCError( - "Invalid 'expression': $expression", - RPCErrorCodes.kInvalidParams, - '', - ); - } - final List definitions = List.from(params['definitions'] as List); - final List typeDefinitions = List.from(params['typeDefinitions'] as List); - final String libraryUri = params['libraryUri'] as String; - final String klass = params['klass'] as String; - final bool isStatic = params['isStatic'] as bool ?? false; - try { - final String kernelBytesBase64 = await compileExpression(isolateId, - expression, definitions, typeDefinitions, libraryUri, klass, - isStatic); - return { + if (restart != null) { + vmService.registerServiceCallback('hotRestart', (Map params) async { + final bool pause = params['pause'] as bool ?? false; + try { + await restart(pause: pause); + return {'type': 'Success'}; + } on vm_service.RPCError { + rethrow; + } on Exception catch (e, st) { + throw vm_service.RPCError( + 'Error during Hot Restart: $e\n$st', + RPCErrorCodes.kServerError, + '', + ); + } + }); + vmService.registerService('hotRestart', 'Flutter Tools'); + } + + vmService.registerServiceCallback('flutterVersion', (Map params) async { + final FlutterVersion version = context.get() ?? FlutterVersion(); + final Map versionJson = version.toJson(); + versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort; + versionJson['engineRevisionShort'] = version.engineRevisionShort; + return { + 'result': { + 'type': 'Success', + ...versionJson, + } + }; + }); + vmService.registerService('flutterVersion', 'Flutter Tools'); + + if (compileExpression != null) { + vmService.registerServiceCallback('compileExpression', (Map params) async { + final String isolateId = params['isolateId'] as String; + if (isolateId is! String || isolateId.isEmpty) { + throw throw vm_service.RPCError( + "Invalid 'isolateId': $isolateId", + RPCErrorCodes.kInvalidParams, + '', + ); + } + final String expression = params['expression'] as String; + if (expression is! String || expression.isEmpty) { + throw throw vm_service.RPCError( + "Invalid 'expression': $expression", + RPCErrorCodes.kInvalidParams, + '', + ); + } + final List definitions = List.from(params['definitions'] as List); + final List typeDefinitions = List.from(params['typeDefinitions'] as List); + final String libraryUri = params['libraryUri'] as String; + final String klass = params['klass'] as String; + final bool isStatic = params['isStatic'] as bool ?? false; + try { + final String kernelBytesBase64 = await compileExpression(isolateId, + expression, definitions, typeDefinitions, libraryUri, klass, + isStatic); + return { + 'type': 'Success', + 'result': { + 'result': {'kernelBytes': kernelBytesBase64}, + }, + }; + } on vm_service.RPCError { + rethrow; + } on Exception catch (e, st) { + throw vm_service.RPCError( + 'Error during expression compilation: $e\n$st', + RPCErrorCodes.kServerError, + '', + ); + } + }); + vmService.registerService('compileExpression', 'Flutter Tools'); + } + if (device != null) { + vmService.registerServiceCallback('flutterMemoryInfo', (Map params) async { + try { + final MemoryInfo result = await device.queryMemoryInfo(); + return { + 'result': { 'type': 'Success', - 'result': { - 'result': {'kernelBytes': kernelBytesBase64}, - }, - }; - } on vm_service.RPCError { - rethrow; - } on Exception catch (e, st) { - throw vm_service.RPCError( - 'Error during expression compilation: $e\n$st', - RPCErrorCodes.kServerError, - '', - ); - } - }); - _delegateService.registerService('compileExpression', 'Flutter Tools'); - } - if (device != null) { - _delegateService.registerServiceCallback('flutterMemoryInfo', (Map params) async { - try { - final MemoryInfo result = await device.queryMemoryInfo(); - return { - 'result': { - 'type': 'Success', - ...result.toJson(), - } - }; - } on Exception catch (e, st) { - throw vm_service.RPCError( - 'Error during memory info query $e\n$st', - RPCErrorCodes.kServerError, - '', - ); - } - }); - _delegateService.registerService('flutterMemoryInfo', 'Flutter Tools'); - } + ...result.toJson(), + } + }; + } on Exception catch (e, st) { + throw vm_service.RPCError( + 'Error during memory info query $e\n$st', + RPCErrorCodes.kServerError, + '', + ); + } + }); + vmService.registerService('flutterMemoryInfo', 'Flutter Tools'); } + return vmService; +} - /// Connect to a Dart VM Service at [httpUri]. - /// - /// If the [reloadSources] parameter is not null, the 'reloadSources' service - /// will be registered. The VM Service Protocol allows clients to register - /// custom services that can be invoked by other clients through the service - /// protocol itself. - /// - /// See: https://github.com/dart-lang/sdk/commit/df8bf384eb815cf38450cb50a0f4b62230fba217 - static Future connect( - Uri httpUri, { - ReloadSources reloadSources, - Restart restart, - CompileExpression compileExpression, - ReloadMethod reloadMethod, - io.CompressionOptions compression = io.CompressionOptions.compressionDefault, - Device device, - }) async { - final VMServiceConnector connector = context.get() ?? VMService._connect; - return connector(httpUri, - reloadSources: reloadSources, - restart: restart, - compileExpression: compileExpression, - compression: compression, - device: device, - reloadMethod: reloadMethod, - ); - } - - static Future _connect( - Uri httpUri, { +/// Connect to a Dart VM Service at [httpUri]. +/// +/// If the [reloadSources] parameter is not null, the 'reloadSources' service +/// will be registered. The VM Service Protocol allows clients to register +/// custom services that can be invoked by other clients through the service +/// protocol itself. +/// +/// See: https://github.com/dart-lang/sdk/commit/df8bf384eb815cf38450cb50a0f4b62230fba217 +Future connectToVmService( + Uri httpUri, { ReloadSources reloadSources, Restart restart, CompileExpression compileExpression, @@ -371,835 +347,58 @@ class VMService implements vm_service.VmService { io.CompressionOptions compression = io.CompressionOptions.compressionDefault, Device device, }) async { - final Uri wsUri = httpUri.replace(scheme: 'ws', path: globals.fs.path.join(httpUri.path, 'ws')); - final io.WebSocket channel = await _openChannel(wsUri.toString(), compression: compression); - final StreamController primary = StreamController(); - final StreamController secondary = StreamController(); + final VMServiceConnector connector = context.get() ?? _connect; + return connector(httpUri, + reloadSources: reloadSources, + restart: restart, + compileExpression: compileExpression, + compression: compression, + device: device, + reloadMethod: reloadMethod, + ); +} - // Create an instance of the package:vm_service API in addition to the flutter - // tool's to allow gradual migration. - final Completer streamClosedCompleter = Completer(); - - channel.listen((dynamic data) { - primary.add(data); - secondary.add(data); - }, onDone: () { - primary.close(); - secondary.close(); +Future _connect( + Uri httpUri, { + ReloadSources reloadSources, + Restart restart, + CompileExpression compileExpression, + ReloadMethod reloadMethod, + io.CompressionOptions compression = io.CompressionOptions.compressionDefault, + Device device, +}) async { + final Uri wsUri = httpUri.replace(scheme: 'ws', path: globals.fs.path.join(httpUri.path, 'ws')); + final io.WebSocket channel = await _openChannel(wsUri.toString(), compression: compression); + // Create an instance of the package:vm_service API in addition to the flutter + // tool's to allow gradual migration. + final Completer streamClosedCompleter = Completer(); + final vm_service.VmService delegateService = vm_service.VmService( + channel, + channel.add, + log: null, + disposeHandler: () async { if (!streamClosedCompleter.isCompleted) { streamClosedCompleter.complete(); } - }, onError: (dynamic error, StackTrace stackTrace) { - primary.addError(error, stackTrace); - secondary.addError(error, stackTrace); - }); - final vm_service.VmService delegateService = vm_service.VmService( - primary.stream, - channel.add, - log: null, - disposeHandler: () async { - if (!streamClosedCompleter.isCompleted) { - streamClosedCompleter.complete(); - } - await channel.close(); - }, - ); - - final VMService service = VMService( - httpUri, - wsUri, - reloadSources, - restart, - compileExpression, - device, - reloadMethod, - delegateService, - streamClosedCompleter, - secondary.stream, - ); - - // This call is to ensure we are able to establish a connection instead of - // keeping on trucking and failing farther down the process. - await delegateService.getVersion(); - return service; - } - - final vm_service.VmService _delegateService; - final Uri httpAddress; - final Uri wsAddress; - final Completer streamClosedCompleter; - - VM _vm; - /// The singleton [VM] object. Owns [Isolate] and [FlutterView] objects. - VM get vm => _vm; - - final Map> _eventControllers = - >{}; - - /// Whether our connection to the VM service has been closed; - bool get isClosed => streamClosedCompleter.isCompleted; - - Future get done async { - return streamClosedCompleter.future; - } - - @override - Stream get onDebugEvent => onEvent('Debug'); - - @override - Stream get onExtensionEvent => onEvent('Extension'); - - @override - Stream get onIsolateEvent => onEvent('Isolate'); - - @override - Stream get onTimelineEvent => onEvent('Timeline'); - - @override - Stream get onStdoutEvent => onEvent('Stdout'); - - @override - Future streamListen(String streamId) { - return _delegateService.streamListen(streamId); - } - - @override - Stream onEvent(String streamId) { - return _delegateService.onEvent(streamId); - } - - @override - Future callMethod(String method, { - String isolateId, - Map args, - }) { - return _delegateService.callMethod(method, isolateId: isolateId, args: args); - } - - @override - Future get onDone => _delegateService.onDone; - - @override - Future callServiceExtension(String method, - {String isolateId, Map args}) { - return _delegateService.callServiceExtension(method, isolateId: isolateId, args: args); - } - - @override - Future getVM() => _delegateService.getVM(); - - StreamController _getEventController(String eventName) { - StreamController controller = _eventControllers[eventName]; - if (controller == null) { - controller = StreamController.broadcast(); - _eventControllers[eventName] = controller; - } - return controller; - } - - void _handleStreamNotify(Map data) { - final String streamId = data['streamId'] as String; - final Map eventData = castStringKeyedMap(data['event']); - final Map eventIsolate = castStringKeyedMap(eventData['isolate']); - - // Log event information. - globals.printTrace('Notification from VM: $data'); - - ServiceEvent event; - if (eventIsolate != null) { - // getFromMap creates the Isolate if necessary. - final Isolate isolate = vm.getFromMap(eventIsolate) as Isolate; - event = ServiceObject._fromMap(isolate, eventData) as ServiceEvent; - if (event.kind == vm_service.EventKind.kIsolateExit) { - vm._isolateCache.remove(isolate.id); - vm._buildIsolateList(); - } else if (event.kind == vm_service.EventKind.kIsolateRunnable) { - // Force reload once the isolate becomes runnable so that we - // update the root library. - isolate.reload(); - } - } else { - // The event doesn't have an isolate, so it is owned by the VM. - event = ServiceObject._fromMap(vm, eventData) as ServiceEvent; - } - _getEventController(streamId).add(event); - } - - @override - Future getScripts(String isolateId) { - return _delegateService.getScripts(isolateId); - } - - /// Reloads the VM. - Future getVMOld() async => await vm.reload(); - - Future close() async { - _delegateService?.dispose(); - } - - @override - Future reloadSources( - String isolateId, { - bool force, - bool pause, - String rootLibUri, - String packagesUri, - }) { - return _delegateService.reloadSources( - isolateId, - force: force, - pause: pause, - rootLibUri: rootLibUri, - packagesUri: packagesUri, - ); - } - - @override - Future getIsolate(String isolateId) { - return _delegateService.getIsolate(isolateId); - } - - @override - Future resume(String isolateId, {String step, int frameIndex}) { - return _delegateService.resume(isolateId, step: step, frameIndex: frameIndex); - } - - @override - Future kill(String isolateId) { - return _delegateService.kill(isolateId); - } - - // To enable a gradual migration to package:vm_service - @override - dynamic noSuchMethod(Invocation invocation) { - throw UnsupportedError('${invocation.memberName} is not currently supported'); - } -} - -/// An error that is thrown when constructing/updating a service object. -class VMServiceObjectLoadError { - VMServiceObjectLoadError(this.message, this.map); - final String message; - final Map map; -} - -bool _isServiceMap(Map m) { - return (m != null) && (m['type'] != null); -} -bool _hasRef(String type) => (type != null) && type.startsWith('@'); -String _stripRef(String type) => _hasRef(type) ? type.substring(1) : type; - -/// Given a raw response from the service protocol and a [ServiceObjectOwner], -/// recursively walk the response and replace values that are service maps with -/// actual [ServiceObject]s. During the upgrade the owner is given a chance -/// to return a cached / canonicalized object. -void _upgradeCollection( - dynamic collection, - ServiceObjectOwner owner, -) { - if (collection is ServiceMap) { - return; - } - if (collection is Map) { - _upgradeMap(collection, owner); - } else if (collection is List) { - _upgradeList(collection, owner); - } -} - -void _upgradeMap(Map map, ServiceObjectOwner owner) { - map.forEach((String k, Object v) { - if ((v is Map) && _isServiceMap(v)) { - map[k] = owner.getFromMap(v); - } else if (v is List) { - _upgradeList(v, owner); - } else if (v is Map) { - _upgradeMap(v, owner); - } - }); -} - -void _upgradeList(List list, ServiceObjectOwner owner) { - for (int i = 0; i < list.length; i += 1) { - final Object v = list[i]; - if ((v is Map) && _isServiceMap(v)) { - list[i] = owner.getFromMap(v); - } else if (v is List) { - _upgradeList(v, owner); - } else if (v is Map) { - _upgradeMap(v, owner); - } - } -} - -/// Base class of all objects received over the service protocol. -abstract class ServiceObject { - ServiceObject._empty(this._owner); - - /// Factory constructor given a [ServiceObjectOwner] and a service map, - /// upgrade the map into a proper [ServiceObject]. This function always - /// returns a new instance and does not interact with caches. - factory ServiceObject._fromMap( - ServiceObjectOwner owner, - Map map, - ) { - if (map == null) { - return null; - } - - if (!_isServiceMap(map)) { - throw VMServiceObjectLoadError('Expected a service map', map); - } - - final String type = _stripRef(map['type'] as String); - - ServiceObject serviceObject; - switch (type) { - case 'Event': - serviceObject = ServiceEvent._empty(owner); - break; - case 'Isolate': - serviceObject = Isolate._empty(owner.vm); - break; - } - // If we don't have a model object for this service object type, as a - // fallback return a ServiceMap object. - serviceObject ??= ServiceMap._empty(owner); - // We have now constructed an empty service object, call update to populate it. - serviceObject.updateFromMap(map); - return serviceObject; - } - - final ServiceObjectOwner _owner; - ServiceObjectOwner get owner => _owner; - - /// The id of this object. - String get id => _id; - String _id; - - /// The user-level type of this object. - String get type => _type; - String _type; - - /// The vm-level type of this object. Usually the same as [type]. - String get vmType => _vmType; - String _vmType; - - /// Is it safe to cache this object? - bool _canCache = false; - bool get canCache => _canCache; - - /// Has this object been fully loaded? - bool get loaded => _loaded; - bool _loaded = false; - - /// Is this object immutable after it is [loaded]? - bool get immutable => false; - - String get name => _name; - String _name; - - String get vmName => _vmName; - String _vmName; - - /// If this is not already loaded, load it. Otherwise reload. - Future load() async { - if (loaded) { - return this; - } - return reload(); - } - - /// Fetch this object from vmService and return the response directly. - Future> _fetchDirect() { - final Map params = { - 'objectId': id, - }; - return _owner.isolate.invokeRpcRaw('getObject', params: params); - } - - Future _inProgressReload; - /// Reload the service object (if possible). - Future reload() async { - final bool hasId = (id != null) && (id != ''); - final bool isVM = this is VM; - // We should always reload the VM. - // We can't reload objects without an id. - // We shouldn't reload an immutable and already loaded object. - if (!isVM && (!hasId || (immutable && loaded))) { - return this; - } - - if (_inProgressReload == null) { - final Completer completer = Completer(); - _inProgressReload = completer.future; - try { - final Map response = await _fetchDirect(); - if (_stripRef(response['type'] as String) == 'Sentinel') { - // An object may have been collected. - completer.complete(ServiceObject._fromMap(owner, response)); - } else { - updateFromMap(response); - completer.complete(this); - } - // Catches all exceptions to propagate to the completer. - } catch (e, st) { // ignore: avoid_catches_without_on_clauses - completer.completeError(e, st); - } - _inProgressReload = null; - return await completer.future; - } - - return await _inProgressReload; - } - - /// Update [this] using [map] as a source. [map] can be a service reference. - void updateFromMap(Map map) { - // Don't allow the type to change on an object update. - final bool mapIsRef = _hasRef(map['type'] as String); - final String mapType = _stripRef(map['type'] as String); - - if ((_type != null) && (_type != mapType)) { - throw VMServiceObjectLoadError('ServiceObject types must not change', - map); - } - _type = mapType; - _vmType = map.containsKey('_vmType') ? _stripRef(map['_vmType'] as String) : _type; - - _canCache = map['fixedId'] == true; - if ((_id != null) && (_id != map['id']) && _canCache) { - throw VMServiceObjectLoadError('ServiceObject id changed', map); - } - _id = map['id'] as String; - - // Copy name properties. - _name = map['name'] as String; - _vmName = map.containsKey('_vmName') ? map['_vmName'] as String : _name; - - // We have now updated all common properties, let the subclasses update - // their specific properties. - _update(map, mapIsRef); - } - - /// Implemented by subclasses to populate their model. - void _update(Map map, bool mapIsRef); -} - -class ServiceEvent extends ServiceObject { - ServiceEvent._empty(ServiceObjectOwner owner) : super._empty(owner); - - String _kind; - String get kind => _kind; - DateTime _timestamp; - DateTime get timestamp => _timestamp; - String _extensionKind; - String get extensionKind => _extensionKind; - Map _extensionData; - Map get extensionData => _extensionData; - List> _timelineEvents; - List> get timelineEvents => _timelineEvents; - String _message; - String get message => _message; - - @override - void _update(Map map, bool mapIsRef) { - _loaded = true; - _upgradeCollection(map, owner); - _kind = map['kind'] as String; - assert(map['isolate'] == null || owner == map['isolate']); - _timestamp = - DateTime.fromMillisecondsSinceEpoch(map['timestamp'] as int); - if (map['extensionKind'] != null) { - _extensionKind = map['extensionKind'] as String; - _extensionData = castStringKeyedMap(map['extensionData']); - } - // map['timelineEvents'] is List which can't be assigned to - // List> directly. Unfortunately, we previously didn't - // catch this exception because json_rpc_2 is hiding all these exceptions - // on a Stream. - final List dynamicList = map['timelineEvents'] as List; - _timelineEvents = dynamicList?.cast>(); - - final String base64Bytes = map['bytes'] as String; - if (base64Bytes != null) { - _message = utf8.decode(base64.decode(base64Bytes)).trim(); - } - } -} - -/// A ServiceObjectOwner is either a [VM] or an [Isolate]. Owners can cache -/// and/or canonicalize service objects received over the wire. -abstract class ServiceObjectOwner extends ServiceObject { - ServiceObjectOwner._empty(ServiceObjectOwner owner) : super._empty(owner); - - /// Returns the owning VM. - VM get vm => null; - - /// Returns the owning isolate (if any). - Isolate get isolate => null; - - /// Returns the vmService connection. - VMService get vmService => null; - - /// Builds a [ServiceObject] corresponding to the [id] from [map]. - /// The result may come from the cache. The result will not necessarily - /// be [loaded]. - ServiceObject getFromMap(Map map); -} - -/// There is only one instance of the VM class. The VM class owns [Isolate] -/// and [FlutterView] objects. -class VM extends ServiceObjectOwner { - VM._empty(this._vmService) : super._empty(null); - - /// Connection to the VMService. - final VMService _vmService; - @override - VMService get vmService => _vmService; - - @override - VM get vm => this; - - @override - Future> _fetchDirect() => invokeRpcRaw('getVM'); - - @override - void _update(Map map, bool mapIsRef) { - if (mapIsRef) { - return; - } - - // Upgrade the collection. A side effect of this call is that any new - // isolates in the map are created and added to the isolate cache. - _upgradeCollection(map, this); - _loaded = true; - - if (map['_heapAllocatedMemoryUsage'] != null) { - _heapAllocatedMemoryUsage = map['_heapAllocatedMemoryUsage'] as int; - } - _maxRSS = map['_maxRSS'] as int; - _embedder = map['_embedder'] as String; - - // Remove any isolates which are now dead from the isolate cache. - _removeDeadIsolates((map['isolates'] as List).cast()); - } - - final Map _cache = {}; - final Map _isolateCache = {}; - - /// The list of live isolates, ordered by isolate start time. - final List isolates = []; - - /// The number of bytes allocated (e.g. by malloc) in the native heap. - int _heapAllocatedMemoryUsage; - int get heapAllocatedMemoryUsage => _heapAllocatedMemoryUsage ?? 0; - - /// The peak resident set size for the process. - int _maxRSS; - int get maxRSS => _maxRSS ?? 0; - - // The embedder's name, Flutter or dart_runner. - String _embedder; - String get embedder => _embedder; - bool get isFlutterEngine => embedder == 'Flutter'; - bool get isDartRunner => embedder == 'dart_runner'; - - int _compareIsolates(Isolate a, Isolate b) { - final DateTime aStart = a.startTime; - final DateTime bStart = b.startTime; - if (aStart == null) { - if (bStart == null) { - return 0; - } else { - return 1; - } - } - if (bStart == null) { - return -1; - } - return aStart.compareTo(bStart); - } - - void _buildIsolateList() { - final List isolateList = _isolateCache.values.toList(); - isolateList.sort(_compareIsolates); - isolates.clear(); - isolates.addAll(isolateList); - } - - void _removeDeadIsolates(List newIsolates) { - // Build a set of new isolates. - final Set newIsolateSet = {}; - for (final Isolate iso in newIsolates) { - newIsolateSet.add(iso.id); - } - - // Remove any old isolates which no longer exist. - final List toRemove = []; - _isolateCache.forEach((String id, _) { - if (!newIsolateSet.contains(id)) { - toRemove.add(id); - } - }); - toRemove.forEach(_isolateCache.remove); - _buildIsolateList(); - } - - @override - ServiceObject getFromMap(Map map) { - if (map == null) { - return null; - } - final String type = _stripRef(map['type'] as String); - if (type == 'VM') { - // Update this VM object. - updateFromMap(map); - return this; - } - - final String mapId = map['id'] as String; - - switch (type) { - case 'Isolate': - // Check cache. - Isolate isolate = _isolateCache[mapId]; - if (isolate == null) { - // Add new isolate to the cache. - isolate = ServiceObject._fromMap(this, map) as Isolate; - _isolateCache[mapId] = isolate; - _buildIsolateList(); - - // Eagerly load the isolate. - isolate.load().catchError((dynamic e, StackTrace stack) { - globals.printTrace('Eagerly loading an isolate failed: $e\n$stack'); - }); - } else { - // Existing isolate, update data. - isolate.updateFromMap(map); - } - return isolate; - default: - // If we don't have a model object for this service object type, as a - // fallback return a ServiceMap object. - final ServiceObject serviceObject = ServiceMap._empty(owner); - // We have now constructed an empty service object, call update to populate it. - serviceObject.updateFromMap(map); - return serviceObject; - } - } - - // This function does not reload the isolate if it's found in the cache. - Future getIsolate(String isolateId) { - if (!loaded) { - // Trigger a VM load, then get the isolate. Ignore any errors. - return load().then((ServiceObject serviceObject) => getIsolate(isolateId)).catchError((dynamic error) => null); - } - return Future.value(_isolateCache[isolateId]); - } - - /// Invoke the RPC and return the raw response. - Future> invokeRpcRaw( - String method, { - Map params = const {}, - bool truncateLogs = true, - }) async { - final vm_service.Response response = await _vmService - ._delegateService.callServiceExtension(method, args: params); - return response.json; - } - - /// Invoke the RPC and return a [ServiceObject] response. - Future invokeRpc( - String method, { - Map params = const {}, - bool truncateLogs = true, - }) async { - final Map response = await invokeRpcRaw( - method, - params: params, - truncateLogs: truncateLogs, - ); - final T serviceObject = ServiceObject._fromMap(this, response) as T; - if ((serviceObject != null) && (serviceObject._canCache)) { - final String serviceObjectId = serviceObject.id; - _cache.putIfAbsent(serviceObjectId, () => serviceObject); - } - return serviceObject; - } - - /// Create a new development file system on the device. - Future> createDevFS(String fsName) { - return invokeRpcRaw('_createDevFS', params: {'fsName': fsName}); - } - - /// Delete an existing file system. - Future> deleteDevFS(String fsName) { - return invokeRpcRaw('_deleteDevFS', params: {'fsName': fsName}); - } - - Future> clearVMTimeline() { - return invokeRpcRaw('clearVMTimeline'); - } - - Future> setVMTimelineFlags(List recordedStreams) { - assert(recordedStreams != null); - return invokeRpcRaw( - 'setVMTimelineFlags', - params: { - 'recordedStreams': recordedStreams, - }, - ); - } - - Future> getVMTimeline() { - return invokeRpcRaw('getVMTimeline'); - } -} - -/// An isolate running inside the VM. Instances of the Isolate class are always -/// canonicalized. -class Isolate extends ServiceObjectOwner { - Isolate._empty(VM owner) : super._empty(owner); - - @override - VM get vm => owner as VM; - - @override - VMService get vmService => vm.vmService; - - @override - Isolate get isolate => this; - - DateTime startTime; - - /// The last pause event delivered to the isolate. If the isolate is running, - /// this will be a resume event. - ServiceEvent pauseEvent; - - final Map _cache = {}; - - @override - ServiceObject getFromMap(Map map) { - if (map == null) { - return null; - } - final String mapType = _stripRef(map['type'] as String); - if (mapType == 'Isolate') { - // There are sometimes isolate refs in ServiceEvents. - return vm.getFromMap(map); - } - - final String mapId = map['id'] as String; - ServiceObject serviceObject = (mapId != null) ? _cache[mapId] : null; - if (serviceObject != null) { - serviceObject.updateFromMap(map); - return serviceObject; - } - // Build the object from the map directly. - serviceObject = ServiceObject._fromMap(this, map); - if ((serviceObject != null) && serviceObject.canCache) { - _cache[mapId] = serviceObject; - } - return serviceObject; - } - - @override - Future> _fetchDirect() => invokeRpcRaw('getIsolate'); - - /// Invoke the RPC and return the raw response. - Future> invokeRpcRaw( - String method, { - Map params, - }) { - // Inject the 'isolateId' parameter. - if (params == null) { - params = { - 'isolateId': id, - }; - } else { - params['isolateId'] = id; - } - return vm.invokeRpcRaw(method, params: params); - } - - @override - void _update(Map map, bool mapIsRef) { - if (mapIsRef) { - return; - } - _loaded = true; - - final int startTimeMillis = map['startTime'] as int; - startTime = DateTime.fromMillisecondsSinceEpoch(startTimeMillis); - - _upgradeCollection(map, this); - - pauseEvent = map['pauseEvent'] as ServiceEvent; - } - - @override - String toString() => 'Isolate $id'; -} - -class ServiceMap extends ServiceObject implements Map { - ServiceMap._empty(ServiceObjectOwner owner) : super._empty(owner); - - final Map _map = {}; - - @override - void _update(Map map, bool mapIsRef) { - _loaded = !mapIsRef; - _upgradeCollection(map, owner); - _map.clear(); - _map.addAll(map); - } - - // Forward Map interface calls. - @override - void addAll(Map other) => _map.addAll(other); - @override - void clear() => _map.clear(); - @override - bool containsValue(dynamic v) => _map.containsValue(v); - @override - bool containsKey(Object k) => _map.containsKey(k); - @override - void forEach(void f(String key, dynamic value)) => _map.forEach(f); - @override - dynamic putIfAbsent(String key, dynamic ifAbsent()) => _map.putIfAbsent(key, ifAbsent); - @override - void remove(Object key) => _map.remove(key); - @override - dynamic operator [](Object k) => _map[k]; - @override - void operator []=(String k, dynamic v) => _map[k] = v; - @override - bool get isEmpty => _map.isEmpty; - @override - bool get isNotEmpty => _map.isNotEmpty; - @override - Iterable get keys => _map.keys; - @override - Iterable get values => _map.values; - @override - int get length => _map.length; - @override - String toString() => _map.toString(); - @override - void addEntries(Iterable> entries) => _map.addEntries(entries); - @override - Map cast() => _map.cast(); - @override - void removeWhere(bool test(String key, dynamic value)) => _map.removeWhere(test); - @override - Map map(MapEntry transform(String key, dynamic value)) => _map.map(transform); - @override - Iterable> get entries => _map.entries; - @override - void updateAll(dynamic update(String key, dynamic value)) => _map.updateAll(update); - Map retype() => _map.cast(); - @override - dynamic update(String key, dynamic update(dynamic value), { dynamic ifAbsent() }) => _map.update(key, update, ifAbsent: ifAbsent); + await channel.close(); + }, + ); + + final vm_service.VmService service = setUpVmService( + reloadSources, + restart, + compileExpression, + device, + reloadMethod, + delegateService, + ); + _httpAddressExpando[service] = httpUri; + _wsAddressExpando[service] = wsUri; + + // This call is to ensure we are able to establish a connection instead of + // keeping on trucking and failing farther down the process. + await delegateService.getVersion(); + return service; } /// Peered to an Android/iOS FlutterView widget on a device. @@ -1240,6 +439,9 @@ class FlutterView { /// Flutter specific VM Service functionality. extension FlutterVmService on vm_service.VmService { + Uri get wsAddress => _wsAddressExpando[this]; + + Uri get httpAddress => _httpAddressExpando[this]; /// Set the asset directory for the an attached Flutter view. Future setAssetDirectory({ @@ -1546,6 +748,39 @@ extension FlutterVmService on vm_service.VmService { return null; }, test: (dynamic error) => error is vm_service.SentinelException); } + + /// Create a new development file system on the device. + Future createDevFS(String fsName) { + return callServiceExtension('_createDevFS', args: {'fsName': fsName}); + } + + /// Delete an existing file system. + Future deleteDevFS(String fsName) { + return callServiceExtension('_deleteDevFS', args: {'fsName': fsName}); + } + + Future screenshot() { + return callServiceExtension(kScreenshotMethod); + } + + Future screenshotSkp() { + return callServiceExtension(kScreenshotSkpMethod); + } + + /// Set the VM timeline flags + Future setVMTimelineFlags(List recordedStreams) { + assert(recordedStreams != null); + return callServiceExtension( + 'setVMTimelineFlags', + args: { + 'recordedStreams': recordedStreams, + }, + ); + } + + Future getVMTimeline() { + return callServiceExtension('getVMTimeline'); + } } /// Whether the event attached to an [Isolate.pauseEvent] should be considered diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index 98e11e3437..b79f653a90 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -9,18 +9,15 @@ import 'package:meta/meta.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; import 'package:process/process.dart'; -import 'package:quiver/testing/async.dart'; import 'package:vm_service/vm_service.dart' as vm_service; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/net.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/attach.dart'; -import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/mdns_discovery.dart'; @@ -76,16 +73,12 @@ void main() { FakeDeviceLogReader mockLogReader; MockPortForwarder portForwarder; MockAndroidDevice device; - MockProcessManager mockProcessManager; MockHttpClient httpClient; - Completer vmServiceDoneCompleter; setUp(() { - mockProcessManager = MockProcessManager(); mockLogReader = FakeDeviceLogReader(); portForwarder = MockPortForwarder(); device = MockAndroidDevice(); - vmServiceDoneCompleter = Completer(); when(device.portForwarder) .thenReturn(portForwarder); when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort'))) @@ -144,114 +137,6 @@ void main() { Logger: () => logger, }); - testUsingContext('finds all observatory ports and forwards them', () async { - testFileSystem.file(testFileSystem.path.join('.packages')).createSync(); - testFileSystem.file(testFileSystem.path.join('lib', 'main.dart')).createSync(); - testFileSystem - .file(testFileSystem.path.join('build', 'flutter_assets', 'AssetManifest.json')) - ..createSync(recursive: true) - ..writeAsStringSync('{}'); - - when(device.name).thenReturn('MockAndroidDevice'); - when(device.getLogReader(includePastLogs: anyNamed('includePastLogs'))) - - .thenReturn(mockLogReader); - - final Process dartProcess = MockProcess(); - final StreamController> compilerStdoutController = StreamController>(); - - when(dartProcess.stdout).thenAnswer((_) => compilerStdoutController.stream); - when(dartProcess.stderr) - .thenAnswer((_) => Stream>.fromFuture(Future>.value(const []))); - - when(dartProcess.stdin).thenAnswer((_) => MockStdIn()); - - final Completer dartProcessExitCode = Completer(); - when(dartProcess.exitCode).thenAnswer((_) => dartProcessExitCode.future); - when(mockProcessManager.start(any)).thenAnswer((_) => Future.value(dartProcess)); - - testDeviceManager.addDevice(device); - - final List observatoryLogs = []; - - await FakeAsync().run((FakeAsync time) { - unawaited(runZoned(() async { - final StreamSubscription loggerSubscription = logger.stream.listen((String message) { - // The "Observatory URL on device" message is output by the ProtocolDiscovery when it found the observatory. - if (message.startsWith('[verbose] Observatory URL on device')) { - observatoryLogs.add(message); - } - if (message == '[stdout] Waiting for a connection from Flutter on MockAndroidDevice...') { - observatoryLogs.add(message); - } - if (message == '[stdout] Lost connection to device.') { - observatoryLogs.add(message); - } - if (message.contains('Hot reload.')) { - observatoryLogs.add(message); - } - if (message.contains('Hot restart.')) { - observatoryLogs.add(message); - } - }); - - final TestHotRunnerFactory testHotRunnerFactory = TestHotRunnerFactory(); - final Future task = createTestCommandRunner( - AttachCommand(hotRunnerFactory: testHotRunnerFactory) - ).run(['attach']); - - // First iteration of the attach loop. - mockLogReader.addLine('Observatory listening on http://127.0.0.1:0001'); - mockLogReader.addLine('Observatory listening on http://127.0.0.1:1234'); - - time.elapse(const Duration(milliseconds: 200)); - - compilerStdoutController - .add(utf8.encode('result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0\n')); - time.flushMicrotasks(); - - // Second iteration of the attach loop. - mockLogReader.addLine('Observatory listening on http://127.0.0.1:0002'); - mockLogReader.addLine('Observatory listening on http://127.0.0.1:1235'); - - time.elapse(const Duration(milliseconds: 200)); - - compilerStdoutController - .add(utf8.encode('result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0\n')); - time.flushMicrotasks(); - - dartProcessExitCode.complete(0); - - await loggerSubscription.cancel(); - await testHotRunnerFactory.exitApp(); - await task; - })); - }); - - expect(observatoryLogs.length, 9); - expect(observatoryLogs[0], '[stdout] Waiting for a connection from Flutter on MockAndroidDevice...'); - expect(observatoryLogs[1], '[verbose] Observatory URL on device: http://127.0.0.1:1234'); - expect(observatoryLogs[2], '[stdout] Lost connection to device.'); - expect(observatoryLogs[3].contains('Hot reload.'), isTrue); - expect(observatoryLogs[4].contains('Hot restart.'), isTrue); - expect(observatoryLogs[5], '[verbose] Observatory URL on device: http://127.0.0.1:1235'); - expect(observatoryLogs[6], '[stdout] Lost connection to device.'); - expect(observatoryLogs[7].contains('Hot reload.'), isTrue); - expect(observatoryLogs[8].contains('Hot restart.'), isTrue); - - verify(portForwarder.forward(1234, hostPort: anyNamed('hostPort'))).called(1); - verify(portForwarder.forward(1235, hostPort: anyNamed('hostPort'))).called(1); - - }, overrides: { - FileSystem: () => testFileSystem, - HttpClientFactory: () => () => httpClient, - ProcessManager: () => mockProcessManager, - Logger: () => logger, - VMServiceConnector: () => getFakeVmServiceFactory( - vmServiceDoneCompleter: vmServiceDoneCompleter, - ), - }); - testUsingContext('Fails with tool exit on bad Observatory uri', () async { when(device.getLogReader(includePastLogs: anyNamed('includePastLogs'))) .thenAnswer((_) { @@ -797,50 +682,54 @@ VMServiceConnector getFakeVmServiceFactory({ CompressionOptions compression, Device device, }) async { - final VMService vmService = VMServiceMock(); - final VM vm = VMMock(); - - when(vmService.vm).thenReturn(vm); - when(vmService.isClosed).thenReturn(false); - when(vmService.done).thenAnswer((_) { - return Future.value(null); - }); - when(vmService.onDone).thenAnswer((_) { - return Future.value(null); - }); - when(vmService.getVM()).thenAnswer((_) async { - return vm_service.VM( - pid: 1, - architectureBits: 64, - hostCPU: '', - name: '', - isolates: [], - isolateGroups: [], - startTime: 0, - targetCPU: '', - operatingSystem: '', - version: '', - ); - }); - when(vmService.getIsolate(any)) - .thenAnswer((Invocation invocation) async { - return fakeUnpausedIsolate; - }); - when(vmService.callMethod(kListViewsMethod)) - .thenAnswer((_) async { - return vm_service.Response.parse({ - 'views': [ - { - 'id': '1', - 'isolate': fakeUnpausedIsolate.toJson() - } - ] - }); - }); - when(vm.createDevFS(any)) - .thenAnswer((_) => Future>.value({'uri': '/',})); - - return vmService; + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( + requests: [ + FakeVmServiceRequest( + id: '1', + method: kListViewsMethod, + args: null, + jsonResponse: { + 'views': [ + { + 'id': '1', + 'isolate': fakeUnpausedIsolate.toJson() + }, + ], + }, + ), + FakeVmServiceRequest( + id: '2', + method: 'getVM', + args: null, + jsonResponse: vm_service.VM.parse({}) + .toJson(), + ), + FakeVmServiceRequest( + id: '3', + method: '_createDevFS', + args: { + 'fsName': globals.fs.currentDirectory.absolute.path, + }, + jsonResponse: { + 'uri': globals.fs.currentDirectory.absolute.path, + }, + ), + FakeVmServiceRequest( + id: '4', + method: kListViewsMethod, + args: null, + jsonResponse: { + 'views': [ + { + 'id': '1', + 'isolate': fakeUnpausedIsolate.toJson() + }, + ], + }, + ), + ], + ); + return fakeVmServiceHost.vmService; }; } @@ -884,8 +773,6 @@ class TestHotRunnerFactory extends HotRunnerFactory { } } -class VMMock extends Mock implements VM {} -class VMServiceMock extends Mock implements VMService {} class MockProcessManager extends Mock implements ProcessManager {} class MockProcess extends Mock implements Process {} class MockHttpClientRequest extends Mock implements HttpClientRequest {} diff --git a/packages/flutter_tools/test/general.shard/coverage_collector_test.dart b/packages/flutter_tools/test/general.shard/coverage_collector_test.dart index 9861dd6a94..357a3bc1e6 100644 --- a/packages/flutter_tools/test/general.shard/coverage_collector_test.dart +++ b/packages/flutter_tools/test/general.shard/coverage_collector_test.dart @@ -2,49 +2,49 @@ // 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:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/test/coverage_collector.dart'; -import 'package:flutter_tools/src/vmservice.dart'; -import 'package:mockito/mockito.dart'; import 'package:vm_service/vm_service.dart' as vm_service; import '../src/common.dart'; void main() { - MockVMService mockVMService; + testWithoutContext('Coverage collector Can handle coverage SentinelException', () async { + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( + requests: [ + FakeVmServiceRequest( + id: '1', + method: 'getVM', + args: null, + jsonResponse: (vm_service.VM.parse({}) + ..isolates = [ + vm_service.IsolateRef.parse({ + 'id': '1' + }), + ] + ).toJson(), + ), + const FakeVmServiceRequest( + id: '2', + method: 'getScripts', + args: { + 'isolateId': '1', + }, + jsonResponse: { + 'type': 'Sentinel' + } + ) + ], + ); - setUp(() { - mockVMService = MockVMService(); - }); - - test('Coverage collector Can handle coverage sentinenl data', () async { - when(mockVMService.getScripts(any)) - .thenThrow(vm_service.SentinelException.parse('getScripts', {})); - final Map result = await collect(null, (String predicate) => true, connector: (Uri uri) async { - return mockVMService; - }); + final Map result = await collect( + null, + (String predicate) => true, + connector: (Uri uri) async { + return fakeVmServiceHost.vmService; + }, + ); expect(result, {'type': 'CodeCoverage', 'coverage': []}); + expect(fakeVmServiceHost.hasRemainingExpectations, false); }); } - -class MockVMService extends Mock implements VMService { - @override - final MockVM vm = MockVM(); -} - -class MockVM extends Mock implements VM { - @override - final List isolates = [ MockIsolate() ]; -} - -class MockIsolate extends Mock implements Isolate {} - -class MockProcess extends Mock implements Process { - final Completercompleter = Completer(); - - @override - Future get exitCode => completer.future; -} diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index 2970ceccb6..80e9963335 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart @@ -126,13 +126,21 @@ void main() { // simulate package await _createPackage(fs, 'somepkg', 'somefile.txt'); - final RealMockVMService vmService = RealMockVMService(); - final RealMockVM vm = RealMockVM(); - final Map response = { 'uri': 'file://abc' }; - when(vm.createDevFS(any)).thenAnswer((Invocation invocation) { - return Future>.value(response); - }); - when(vmService.vm).thenReturn(vm); + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( + requests: [ + FakeVmServiceRequest( + id: '1', + method: '_createDevFS', + args: { + 'fsName': 'test', + }, + jsonResponse: { + 'uri': Uri.parse('test').toString(), + } + ) + ], + ); + setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService); reset(httpClient); @@ -152,7 +160,7 @@ void main() { }); final DevFS devFS = DevFS( - vmService, + fakeVmServiceHost.vmService, 'test', tempDir, osUtils: osUtils, @@ -183,101 +191,43 @@ void main() { }); group('devfs remote', () { - MockVMService vmService; - final MockResidentCompiler residentCompiler = MockResidentCompiler(); DevFS devFS; setUpAll(() async { tempDir = _newTempDir(fs); basePath = tempDir.path; - vmService = MockVMService(); - await vmService.setUp(); }); setUp(() { - vmService.resetState(); + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( + requests: [ + FakeVmServiceRequest( + id: '1', + method: '_createDevFS', + args: { + 'fsName': 'test', + }, + jsonResponse: { + 'uri': Uri.parse('test').toString(), + } + ) + ], + ); + setHttpAddress(Uri.parse('http://localhost'), fakeVmServiceHost.vmService); devFS = DevFS( - vmService, + fakeVmServiceHost.vmService, 'test', tempDir, osUtils: FakeOperatingSystemUtils(), + // TODO(jonahwilliams): remove and prevent usage of http writer. + disableUpload: true, ); }); tearDownAll(() async { - await vmService.tearDown(); _cleanupTempDirs(); }); - testUsingContext('create dev file system', () async { - // simulate workspace - final File file = fs.file(fs.path.join(basePath, filePath)); - await file.parent.create(recursive: true); - file.writeAsBytesSync([1, 2, 3]); - - // simulate package - await _createPackage(fs, 'somepkg', 'somefile.txt'); - - await devFS.create(); - vmService.expectMessages(['create test']); - expect(devFS.assetPathsToEvict, isEmpty); - - final UpdateFSReport report = await devFS.update( - mainUri: Uri.parse('lib/foo.txt'), - generator: residentCompiler, - pathToReload: 'lib/foo.txt.dill', - trackWidgetCreation: false, - invalidatedFiles: [], - packageConfig: PackageConfig.empty, - ); - vmService.expectMessages([ - 'writeFile test lib/foo.txt.dill', - ]); - expect(devFS.assetPathsToEvict, isEmpty); - expect(report.syncedBytes, 22); - expect(report.success, true); - }, overrides: { - FileSystem: () => fs, - HttpClient: () => () => HttpClient(), - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('delete dev file system', () async { - expect(vmService.messages, isEmpty, reason: 'prior test timeout'); - await devFS.destroy(); - vmService.expectMessages(['destroy test']); - expect(devFS.assetPathsToEvict, isEmpty); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - - testUsingContext('cleanup preexisting file system', () async { - // simulate workspace - final File file = fs.file(fs.path.join(basePath, filePath)); - await file.parent.create(recursive: true); - file.writeAsBytesSync([1, 2, 3]); - - // simulate package - await _createPackage(fs, 'somepkg', 'somefile.txt'); - await devFS.create(); - vmService.expectMessages(['create test']); - expect(devFS.assetPathsToEvict, isEmpty); - - // Try to create again. - await devFS.create(); - vmService.expectMessages(['create test', 'destroy test', 'create test']); - expect(devFS.assetPathsToEvict, isEmpty); - - // Really destroy. - await devFS.destroy(); - vmService.expectMessages(['destroy test']); - expect(devFS.assetPathsToEvict, isEmpty); - }, overrides: { - FileSystem: () => fs, - ProcessManager: () => FakeProcessManager.any(), - }); - testUsingContext('reports unsuccessful compile when errors are returned', () async { await devFS.create(); final DateTime previousCompile = devFS.lastCompiled; @@ -345,98 +295,6 @@ void main() { }); } -class MockVMService extends BasicMock implements VMService { - MockVMService() { - _vm = MockVM(this); - } - - Uri _httpAddress; - HttpServer _server; - MockVM _vm; - - @override - Uri get httpAddress => _httpAddress; - - @override - VM get vm => _vm; - - Future setUp() async { - try { - _server = await HttpServer.bind(InternetAddress.loopbackIPv6, 0); - _httpAddress = Uri.parse('http://[::1]:${_server.port}'); - } on SocketException { - // Fall back to IPv4 if the host doesn't support binding to IPv6 localhost - _server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0); - _httpAddress = Uri.parse('http://127.0.0.1:${_server.port}'); - } - _server.listen((HttpRequest request) { - final String fsName = request.headers.value('dev_fs_name'); - final String devicePath = utf8.decode(base64.decode(request.headers.value('dev_fs_uri_b64'))); - messages.add('writeFile $fsName $devicePath'); - request.drain>().then((List value) { - request.response - ..write('Got it') - ..close(); - }); - }); - } - - Future tearDown() async { - await _server?.close(); - } - - void resetState() { - _vm = MockVM(this); - messages.clear(); - } - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); - -} - -class MockVM implements VM { - MockVM(this._service); - - final MockVMService _service; - final Uri _baseUri = Uri.parse('file:///tmp/devfs/test'); - bool _devFSExists = false; - - static const int kFileSystemAlreadyExists = 1001; - - @override - Future> createDevFS(String fsName) async { - _service.messages.add('create $fsName'); - if (_devFSExists) { - throw vm_service.RPCError('File system already exists', kFileSystemAlreadyExists, ''); - } - _devFSExists = true; - return {'uri': '$_baseUri'}; - } - - @override - Future> deleteDevFS(String fsName) async { - _service.messages.add('destroy $fsName'); - _devFSExists = false; - return {'type': 'Success'}; - } - - @override - Future> invokeRpcRaw( - String method, { - Map params = const {}, - Duration timeout, - bool timeoutFatal = true, - bool truncateLogs = true, - }) async { - _service.messages.add('$method $params'); - return {'success': true}; - } - - @override - dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -} - class RealMockResidentCompiler extends Mock implements ResidentCompiler {} final List _tempDirs = []; @@ -474,14 +332,6 @@ Future _createPackage(FileSystem fs, String pkgName, String pkgFileName, { ..writeAsStringSync(sb.toString()); } -class RealMockVM extends Mock implements VM { - -} - -class RealMockVMService extends Mock implements VMService { - -} - class MyHttpOverrides extends HttpOverrides { MyHttpOverrides(this._httpClient); @override @@ -497,3 +347,4 @@ class MockHttpClientRequest extends Mock implements HttpClientRequest {} class MockHttpHeaders extends Mock implements HttpHeaders {} class MockHttpClientResponse extends Mock implements HttpClientResponse {} class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {} +class MockVMService extends Mock implements vm_service.VmService {} diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart index 768dc6bbea..0e0e7eb2cb 100644 --- a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart +++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart @@ -628,21 +628,34 @@ void main() { group('FuchsiaIsolateDiscoveryProtocol', () { MockPortForwarder portForwarder; - MockVMService vmService; setUp(() { portForwarder = MockPortForwarder(); - vmService = MockVMService(); }); Future findUri(List views, String expectedIsolateName) async { + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( + requests: [ + FakeVmServiceRequest( + id: '1', + method: kListViewsMethod, + args: null, + jsonResponse: { + 'views': [ + for (FlutterView view in views) + view.toJson() + ], + }, + ), + ], + ); final MockFuchsiaDevice fuchsiaDevice = MockFuchsiaDevice('123', portForwarder, false); final FuchsiaIsolateDiscoveryProtocol discoveryProtocol = FuchsiaIsolateDiscoveryProtocol( fuchsiaDevice, expectedIsolateName, - (Uri uri) async => vmService, + (Uri uri) async => fakeVmServiceHost.vmService, true, // only poll once. ); @@ -650,17 +663,7 @@ void main() { .thenAnswer((Invocation invocation) async => [1]); when(portForwarder.forward(1)) .thenAnswer((Invocation invocation) async => 2); - when(vmService.getVMOld()) - .thenAnswer((Invocation invocation) => Future.value(null)); - when(vmService.httpAddress).thenReturn(Uri.parse('example')); - when(vmService.callMethod(kListViewsMethod)).thenAnswer((Invocation invocation) async { - return vm_service.Response.parse({ - 'views': [ - for (FlutterView view in views) - view.toJson() - ], - }); - }); + setHttpAddress(Uri.parse('example'), fakeVmServiceHost.vmService); return await discoveryProtocol.uri; } @@ -1121,8 +1124,6 @@ class MockFuchsiaDevice extends Mock implements FuchsiaDevice { class MockPortForwarder extends Mock implements DevicePortForwarder {} -class MockVMService extends Mock implements VMService {} - class FuchsiaDeviceWithFakeDiscovery extends FuchsiaDevice { FuchsiaDeviceWithFakeDiscovery(String id, {String name}) : super(id, name: name); diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart index 415a3eb5e5..bf2d789c9a 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart @@ -11,7 +11,6 @@ import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/mac.dart'; -import 'package:flutter_tools/src/vmservice.dart'; import 'package:mockito/mockito.dart'; import 'package:vm_service/vm_service.dart'; @@ -177,4 +176,4 @@ Runner(libsystem_asl.dylib)[297] : libMobileGestalt } class MockArtifacts extends Mock implements Artifacts {} -class MockVmService extends Mock implements VMService, VmService {} +class MockVmService extends Mock implements VmService {} diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index a9f32d9558..dc664fdd28 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -146,29 +146,21 @@ void main() { when(mockFlutterDevice.vmService).thenAnswer((Invocation invocation) { return fakeVmServiceHost.vmService; }); - when(mockFlutterDevice.flutterDeprecatedVmService).thenAnswer((Invocation invocation) { - return mockVMService; - }); when(mockFlutterDevice.refreshViews()).thenAnswer((Invocation invocation) async { }); - when(mockFlutterDevice.getVMs()).thenAnswer((Invocation invocation) async { }); - when(mockFlutterDevice.reloadSources(any, pause: anyNamed('pause'))).thenReturn(>[ - Future.value(vm_service.ReloadReport.parse({ - 'type': 'ReloadReport', - 'success': true, - 'details': { - 'loadedLibraryCount': 1, - 'finalLibraryCount': 1, - 'receivedLibraryCount': 1, - 'receivedClassesCount': 1, - 'receivedProceduresCount': 1, - }, - })), - ]); - // VMService mocks. - when(mockVMService.wsAddress).thenReturn(testUri); - when(mockVMService.done).thenAnswer((Invocation invocation) { - final Completer result = Completer.sync(); - return result.future; + when(mockFlutterDevice.reloadSources(any, pause: anyNamed('pause'))).thenAnswer((Invocation invocation) async { + return >[ + Future.value(vm_service.ReloadReport.parse({ + 'type': 'ReloadReport', + 'success': true, + 'details': { + 'loadedLibraryCount': 1, + 'finalLibraryCount': 1, + 'receivedLibraryCount': 1, + 'receivedClassesCount': 1, + 'receivedProceduresCount': 1, + }, + })), + ]; }); }); @@ -955,6 +947,7 @@ void main() { test('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: []); + setWsAddress(testUri, fakeVmServiceHost.vmService); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); residentRunner = HotRunner( [ @@ -1029,6 +1022,7 @@ void main() { test('ColdRunner writes vm service file when providing debugging option', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: []); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); + setWsAddress(testUri, fakeVmServiceHost.vmService); residentRunner = ColdRunner( [ mockFlutterDevice, @@ -1111,7 +1105,7 @@ void main() { } class MockFlutterDevice extends Mock implements FlutterDevice {} -class MockVMService extends Mock implements VMService {} +class MockVMService extends Mock implements vm_service.VmService {} class MockDevFS extends Mock implements DevFS {} class MockDevice extends Mock implements Device {} class MockDeviceLogReader extends Mock implements DeviceLogReader {} diff --git a/packages/flutter_tools/test/general.shard/vmservice_test.dart b/packages/flutter_tools/test/general.shard/vmservice_test.dart index 12d1fb49ea..61007faa60 100644 --- a/packages/flutter_tools/test/general.shard/vmservice_test.dart +++ b/packages/flutter_tools/test/general.shard/vmservice_test.dart @@ -91,20 +91,17 @@ final Map listViews = { typedef ServiceCallback = Future> Function(Map); void main() { - testUsingContext('VmService registers reloadSources', () { + testUsingContext('VmService registers reloadSources', () async { Future reloadSources(String isolateId, { bool pause, bool force}) async {} + final MockVMService mockVMService = MockVMService(); - VMService( - null, - null, + setUpVmService( reloadSources, null, null, null, null, mockVMService, - Completer(), - const Stream.empty(), ); verify(mockVMService.registerService('reloadSources', 'Flutter Tools')).called(1); @@ -112,20 +109,17 @@ void main() { Logger: () => BufferLogger.test() }); - testUsingContext('VmService registers reloadMethod', () { + testUsingContext('VmService registers reloadMethod', () async { Future reloadMethod({ String classId, String libraryId,}) async {} + final MockVMService mockVMService = MockVMService(); - VMService( - null, - null, + setUpVmService( null, null, null, null, reloadMethod, mockVMService, - Completer(), - const Stream.empty(), ); verify(mockVMService.registerService('reloadMethod', 'Flutter Tools')).called(1); @@ -133,20 +127,17 @@ void main() { Logger: () => BufferLogger.test() }); - testUsingContext('VmService registers flutterMemoryInfo service', () { + testUsingContext('VmService registers flutterMemoryInfo service', () async { final MockDevice mockDevice = MockDevice(); + final MockVMService mockVMService = MockVMService(); - VMService( - null, - null, + setUpVmService( null, null, null, mockDevice, null, mockVMService, - Completer(), - const Stream.empty(), ); verify(mockVMService.registerService('flutterMemoryInfo', 'Flutter Tools')).called(1); @@ -156,17 +147,13 @@ void main() { testUsingContext('VMService returns correct FlutterVersion', () async { final MockVMService mockVMService = MockVMService(); - VMService( - null, - null, + setUpVmService( null, null, null, null, null, mockVMService, - Completer(), - const Stream.empty(), ); verify(mockVMService.registerService('flutterVersion', 'Flutter Tools')).called(1);