[flutter_tools] Migrate to package:vm_service 4: trigonometric boogaloo (#54132)
diff --git a/packages/flutter_tools/lib/src/devfs.dart b/packages/flutter_tools/lib/src/devfs.dart index 7af81d8..db15800 100644 --- a/packages/flutter_tools/lib/src/devfs.dart +++ b/packages/flutter_tools/lib/src/devfs.dart
@@ -4,8 +4,8 @@ import 'dart:async'; -import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:meta/meta.dart'; +import 'package:vm_service/vm_service.dart' as vmservice; import 'asset.dart'; import 'base/context.dart'; @@ -439,7 +439,7 @@ globals.printTrace('DevFS: Creating new filesystem on the device ($_baseUri)'); try { _baseUri = await _operations.create(fsName); - } on rpc.RpcException catch (rpcException) { + } on vmservice.RPCError catch (rpcException) { // 1001 is kFileSystemAlreadyExists in //dart/runtime/vm/json_stream.h if (rpcException.code != 1001) { rethrow;
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart index b540cdf..11c2d32 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
@@ -620,7 +620,7 @@ // loopback (::1). final Uri uri = Uri.parse('http://[$_ipv6Loopback]:$port'); final VMService vmService = await VMService.connect(uri); - await vmService.getVM(); + await vmService.getVMOld(); await vmService.refreshViews(); for (final FlutterView flutterView in vmService.vm.views) { if (flutterView.uiIsolate == null) { @@ -717,7 +717,7 @@ continue; } } - await service.getVM(); + await service.getVMOld(); await service.refreshViews(); for (final FlutterView flutterView in service.vm.views) { if (flutterView.uiIsolate == null) {
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index f08c66a..5a34d94 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -7,11 +7,11 @@ import 'package:meta/meta.dart'; import 'package:platform/platform.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; import 'package:process/process.dart'; import '../application_package.dart'; import '../artifacts.dart'; -import '../base/common.dart'; import '../base/file_system.dart'; import '../base/io.dart'; import '../base/logger.dart'; @@ -514,7 +514,7 @@ // and "Flutter". The regex tries to strike a balance between not producing // false positives and not producing false negatives. _anyLineRegex = RegExp(r'\w+(\([^)]*\))?\[\d+\] <[A-Za-z]+>: '); - _loggingSubscriptions = <StreamSubscription<ServiceEvent>>[]; + _loggingSubscriptions = <StreamSubscription<void>>[]; } /// Create a new [IOSDeviceLogReader]. @@ -554,7 +554,7 @@ RegExp _anyLineRegex; StreamController<String> _linesController; - List<StreamSubscription<ServiceEvent>> _loggingSubscriptions; + List<StreamSubscription<void>> _loggingSubscriptions; @override Stream<String> get logLines => _linesController.stream; @@ -575,18 +575,20 @@ if (_majorSdkVersion < _minimumUniversalLoggingSdkVersion) { return; } - // The VM service will not publish logging events unless the debug stream is being listened to. - // onDebugEvent listens to this stream as a side effect. - unawaited(connectedVmService.onDebugEvent); - _loggingSubscriptions.add((await connectedVmService.onStdoutEvent).listen((ServiceEvent event) { - final String logMessage = event.message; - if (logMessage.isNotEmpty) { - _linesController.add(logMessage); + try { + await connectedVmService.streamListen('Stdout'); + } on vm_service.RPCError { + // Do nothing, since the tool is already subscribed. + } + _loggingSubscriptions.add(connectedVmService.onStdoutEvent.listen((vm_service.Event event) { + final String message = utf8.decode(base64.decode(event.bytes)); + if (message.isNotEmpty) { + _linesController.add(message); } })); } - void _listenToSysLog () { + void _listenToSysLog() { // syslog is not written on iOS 13+. if (_majorSdkVersion >= _minimumUniversalLoggingSdkVersion) { return; @@ -641,7 +643,7 @@ @override void dispose() { - for (final StreamSubscription<ServiceEvent> loggingSubscription in _loggingSubscriptions) { + for (final StreamSubscription<void> loggingSubscription in _loggingSubscriptions) { loggingSubscription.cancel(); } _idevicesyslogProcess?.kill();
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 92409dd..0fb3842 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -234,7 +234,7 @@ : vmService.vm.views).toList(); } - Future<void> getVMs() => vmService.getVM(); + Future<void> getVMs() => vmService.getVMOld(); Future<void> exitApps() async { if (!device.supportsFlutterExit) {
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 3df2c5b..64ac9c0 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -3,10 +3,8 @@ // found in the LICENSE file. import 'dart:async'; - +import 'package:vm_service/vm_service.dart' as vm_service; import 'package:platform/platform.dart'; -import 'package:json_rpc_2/error_code.dart' as rpc_error_code; -import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:meta/meta.dart'; import 'package:pool/pool.dart'; import 'base/async_guard.dart'; @@ -110,9 +108,10 @@ // TODO(cbernaschina): check that isolateId is the id of the UI isolate. final OperationResult result = await restart(pause: pause); if (!result.isOk) { - throw rpc.RpcException( - rpc_error_code.INTERNAL_ERROR, + throw vm_service.RPCError( 'Unable to reload sources', + RPCErrorCodes.kInternalError, + '', ); } } @@ -121,9 +120,10 @@ final OperationResult result = await restart(fullRestart: true, pause: pause); if (!result.isOk) { - throw rpc.RpcException( - rpc_error_code.INTERNAL_ERROR, + throw vm_service.RPCError( 'Unable to restart', + RPCErrorCodes.kInternalError, + '', ); } } @@ -564,16 +564,15 @@ if (benchmarkMode) { final List<Future<void>> isolateNotifications = <Future<void>>[]; for (final FlutterDevice device in flutterDevices) { + try { + await device.vmService.streamListen('Isolate'); + } on vm_service.RPCError { + // Do nothing, we're already subcribed. + } for (final FlutterView view in device.views) { isolateNotifications.add( - view.owner.vm.vmService.onIsolateEvent - .then((Stream<ServiceEvent> serviceEvents) async { - await for (final ServiceEvent serviceEvent in serviceEvents) { - if (serviceEvent.owner.name.contains('_spawn') - && serviceEvent.kind == ServiceEvent.kIsolateExit) { - return; - } - } + view.owner.vm.vmService.onIsolateEvent.firstWhere((vm_service.Event event) { + return event.kind == vm_service.EventKind.kServiceExtensionAdded; }), ); } @@ -720,9 +719,12 @@ if (!result.isOk) { restartEvent = 'restart-failed'; } - } on rpc.RpcException { + } on vm_service.SentinelException catch (err, st) { restartEvent = 'exception'; - return OperationResult(1, 'hot restart failed to complete', fatal: true); + return OperationResult(1, 'hot restart failed to complete: $err\n$st', fatal: true); + } on vm_service.RPCError catch (err, st) { + restartEvent = 'exception'; + return OperationResult(1, 'hot restart failed to complete: $err\n$st', fatal: true); } finally { HotEvent(restartEvent, targetPlatform: targetPlatform, @@ -764,7 +766,7 @@ ); }, ); - } on rpc.RpcException { + } on vm_service.RPCError { HotEvent('exception', targetPlatform: targetPlatform, sdkName: sdkName,
diff --git a/packages/flutter_tools/lib/src/test/coverage_collector.dart b/packages/flutter_tools/lib/src/test/coverage_collector.dart index e1fa2cf..9523d64 100644 --- a/packages/flutter_tools/lib/src/test/coverage_collector.dart +++ b/packages/flutter_tools/lib/src/test/coverage_collector.dart
@@ -189,7 +189,7 @@ Future<VMService> Function(Uri) connector = _defaultConnect, }) async { final VMService vmService = await connector(serviceUri); - await vmService.getVM(); + await vmService.getVMOld(); final Map<String, dynamic> result = await _getAllCoverage( vmService, libraryPredicate); await vmService.close(); @@ -197,7 +197,7 @@ } Future<Map<String, dynamic>> _getAllCoverage(VMService service, bool Function(String) libraryPredicate) async { - await service.getVM(); + await service.getVMOld(); final List<Map<String, dynamic>> coverage = <Map<String, dynamic>>[]; for (final Isolate isolateRef in service.vm.isolates) { await isolateRef.load();
diff --git a/packages/flutter_tools/lib/src/tracing.dart b/packages/flutter_tools/lib/src/tracing.dart index 25402b8..5ea6cfb 100644 --- a/packages/flutter_tools/lib/src/tracing.dart +++ b/packages/flutter_tools/lib/src/tracing.dart
@@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:async'; +import 'package:vm_service/vm_service.dart' as vm_service; import 'base/file_system.dart'; import 'base/logger.dart'; @@ -45,7 +46,8 @@ ); try { final Completer<void> whenFirstFrameRendered = Completer<void>(); - (await vmService.onExtensionEvent).listen((ServiceEvent event) { + await vmService.streamListen('Extension'); + vmService.onExtensionEvent.listen((vm_service.Event event) { if (event.extensionKind == 'Flutter.FirstFrame') { whenFirstFrameRendered.complete(); }
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 3db48d0..7f05b7a 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -5,18 +5,14 @@ import 'dart:async'; import 'dart:math' as math; -import 'package:json_rpc_2/error_code.dart' as rpc_error_code; -import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:meta/meta.dart' show required; -import 'package:stream_channel/stream_channel.dart'; -import 'package:web_socket_channel/io.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; import 'base/common.dart'; import 'base/context.dart'; import 'base/io.dart' as io; import 'base/utils.dart'; -import 'convert.dart' show base64, utf8; +import 'convert.dart' show base64, json, utf8; import 'device.dart'; import 'globals.dart' as globals; import 'version.dart'; @@ -25,10 +21,24 @@ /// for [WebSocket]s (used by tests). typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression}); -/// A function that opens a two-way communication channel to the specified [uri]. -typedef _OpenChannel = Future<StreamChannel<String>> Function(Uri uri, {io.CompressionOptions compression}); +WebSocketConnector _openChannel = _defaultOpenChannel; -_OpenChannel _openChannel = _defaultOpenChannel; +/// The error codes for the JSON-RPC standard. +/// +/// See also: https://www.jsonrpc.org/specification#error_object +abstract class RPCErrorCodes { + /// The method does not exist or is not available. + static const int kMethodNotFound = -32601; + + /// Invalid method parameter(s), such as a mismatched type. + static const int kInvalidParams = -32602; + + /// Internal JSON-RPC error. + static const int kInternalError = -32603; + + /// Application specific error codes.s + static const int kServerError = -32000; +} /// A function that reacts to the invocation of the 'reloadSources' service. /// @@ -64,7 +74,9 @@ String libraryId, }); -Future<StreamChannel<String>> _defaultOpenChannel(Uri uri, {io.CompressionOptions compression = io.CompressionOptions.compressionDefault}) async { +Future<io.WebSocket> _defaultOpenChannel(String url, { + io.CompressionOptions compression = io.CompressionOptions.compressionDefault +}) async { Duration delay = const Duration(milliseconds: 100); int attempts = 0; io.WebSocket socket; @@ -90,14 +102,14 @@ while (socket == null) { attempts += 1; try { - socket = await constructor(uri.toString(), compression: compression); + socket = await constructor(url, compression: compression); } on io.WebSocketException catch (e) { await handleError(e); } on io.SocketException catch (e) { await handleError(e); } } - return IOWebSocketChannel(socket).cast<String>(); + return socket; } /// Override `VMServiceConnector` in [context] to return a different VMService @@ -112,10 +124,10 @@ }); /// A connection to the Dart VM Service. -// TODO(mklim): Test this, https://github.com/flutter/flutter/issues/23031 -class VMService { +/// +/// This also implements the package:vm_service API to enable a gradual migration. +class VMService implements vm_service.VmService { VMService( - this._peer, this.httpAddress, this.wsAddress, ReloadSources reloadSources, @@ -123,38 +135,49 @@ CompileExpression compileExpression, Device device, ReloadMethod reloadMethod, + this._delegateService, + this.streamClosedCompleter, + Stream<dynamic> secondary, ) { _vm = VM._empty(this); - _peer.listen().catchError(_connectionError.completeError); - _peer.registerMethod('streamNotify', (rpc.Parameters event) { - _handleStreamNotify(event.asMap.cast<String, dynamic>()); + // 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<String, dynamic>); + } }); if (reloadSources != null) { - _peer.registerMethod('reloadSources', (rpc.Parameters params) async { + _delegateService.registerServiceCallback('reloadSources', (Map<String, dynamic> params) async { final String isolateId = params['isolateId'].value as String; - final bool force = params.asMap['force'] as bool ?? false; - final bool pause = params.asMap['pause'] as bool ?? false; + final bool force = params['force'] as bool ?? false; + final bool pause = params['pause'] as bool ?? false; if (isolateId.isEmpty) { - throw rpc.RpcException.invalidParams("Invalid 'isolateId': $isolateId"); + throw vm_service.RPCError( + "Invalid 'isolateId': $isolateId", + RPCErrorCodes.kInvalidParams, + '', + ); } try { await reloadSources(isolateId, force: force, pause: pause); return <String, String>{'type': 'Success'}; - } on rpc.RpcException { + } on vm_service.RPCError { rethrow; } on Exception catch (e, st) { - throw rpc.RpcException(rpc_error_code.SERVER_ERROR, - 'Error during Sources Reload: $e\n$st'); + throw vm_service.RPCError( + 'Error during Sources Reload: $e\n$st', + RPCErrorCodes.kServerError, + '', + ); } }); - - _peer.sendNotification('registerService', <String, String>{ - 'service': 'reloadSources', - 'alias': 'Flutter Tools', - }); + _delegateService.registerService('reloadSources', 'Flutter Tools'); } @@ -168,15 +191,23 @@ // 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. - _peer.registerMethod('reloadMethod', (rpc.Parameters params) async { - final String libraryId = params['library'].value as String; - final String classId = params['class'].value as String; + _delegateService.registerServiceCallback('reloadMethod', (Map<String, dynamic> params) async { + final String libraryId = params['library'] as String; + final String classId = params['class'] as String; if (libraryId.isEmpty) { - throw rpc.RpcException.invalidParams("Invalid 'libraryId': $libraryId"); + throw vm_service.RPCError( + "Invalid 'libraryId': $libraryId", + RPCErrorCodes.kInvalidParams, + '', + ); } if (classId.isEmpty) { - throw rpc.RpcException.invalidParams("Invalid 'classId': $classId"); + throw vm_service.RPCError( + "Invalid 'classId': $classId", + RPCErrorCodes.kInvalidParams, + '', + ); } globals.printTrace('reloadMethod not yet supported, falling back to hot reload'); @@ -187,113 +218,97 @@ classId: classId, ); return <String, String>{'type': 'Success'}; - } on rpc.RpcException { + } on vm_service.RPCError { rethrow; } on Exception catch (e, st) { - throw rpc.RpcException(rpc_error_code.SERVER_ERROR, - 'Error during Sources Reload: $e\n$st'); + throw vm_service.RPCError('Error during Sources Reload: $e\n$st', -32000, ''); } }); - _peer.sendNotification('registerService', <String, String>{ - 'service': 'reloadMethod', - 'alias': 'Flutter Tools', - }); + _delegateService.registerService('reloadMethod', 'Flutter Tools'); } if (restart != null) { - _peer.registerMethod('hotRestart', (rpc.Parameters params) async { - final bool pause = params.asMap['pause'] as bool ?? false; - - if (pause is! bool) { - throw rpc.RpcException.invalidParams("Invalid 'pause': $pause"); - } - + _delegateService.registerServiceCallback('hotRestart', (Map<String, dynamic> params) async { + final bool pause = params['pause'] as bool ?? false; try { await restart(pause: pause); return <String, String>{'type': 'Success'}; - } on rpc.RpcException { + } on vm_service.RPCError { rethrow; } on Exception catch (e, st) { - throw rpc.RpcException(rpc_error_code.SERVER_ERROR, - 'Error during Hot Restart: $e\n$st'); + throw vm_service.RPCError( + 'Error during Hot Restart: $e\n$st', + RPCErrorCodes.kServerError, + '', + ); } }); - - _peer.sendNotification('registerService', <String, String>{ - 'service': 'hotRestart', - 'alias': 'Flutter Tools', - }); + _delegateService.registerService('hotRestart', 'Flutter Tools'); } - _peer.registerMethod('flutterVersion', (rpc.Parameters params) async { + _delegateService.registerServiceCallback('flutterVersion', (Map<String, dynamic> params) async { final FlutterVersion version = context.get<FlutterVersion>() ?? FlutterVersion(); final Map<String, Object> versionJson = version.toJson(); versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort; versionJson['engineRevisionShort'] = version.engineRevisionShort; return versionJson; }); - - _peer.sendNotification('registerService', <String, String>{ - 'service': 'flutterVersion', - 'alias': 'Flutter Tools', - }); + _delegateService.registerService('flutterVersion', 'Flutter Tools'); if (compileExpression != null) { - _peer.registerMethod('compileExpression', (rpc.Parameters params) async { - final String isolateId = params['isolateId'].asString; + _delegateService.registerServiceCallback('compileExpression', (Map<String, dynamic> params) async { + final String isolateId = params['isolateId'] as String; if (isolateId is! String || isolateId.isEmpty) { - throw rpc.RpcException.invalidParams( - "Invalid 'isolateId': $isolateId"); + throw throw vm_service.RPCError( + "Invalid 'isolateId': $isolateId", + RPCErrorCodes.kInvalidParams, + '', + ); } - final String expression = params['expression'].asString; + final String expression = params['expression'] as String; if (expression is! String || expression.isEmpty) { - throw rpc.RpcException.invalidParams( - "Invalid 'expression': $expression"); + throw throw vm_service.RPCError( + "Invalid 'expression': $expression", + RPCErrorCodes.kInvalidParams, + '', + ); } - final List<String> definitions = - List<String>.from(params['definitions'].asList); - final List<String> typeDefinitions = - List<String>.from(params['typeDefinitions'].asList); - final String libraryUri = params['libraryUri'].asString; - final String klass = params['klass'].exists ? params['klass'].asString : null; - final bool isStatic = params['isStatic'].asBoolOr(false); - + final List<String> definitions = List<String>.from(params['definitions'] as List<dynamic>); + final List<String> typeDefinitions = List<String>.from(params['typeDefinitions'] as List<dynamic>); + 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 <String, dynamic>{'type': 'Success', - 'result': <String, dynamic> {'kernelBytes': kernelBytesBase64}}; - } on rpc.RpcException { + return <String, dynamic>{ + 'type': 'Success', + 'result': <String, dynamic>{ + 'result': <String, dynamic>{'kernelBytes': kernelBytesBase64}, + }, + }; + } on vm_service.RPCError { rethrow; } on Exception catch (e, st) { - throw rpc.RpcException(rpc_error_code.SERVER_ERROR, - 'Error during expression compilation: $e\n$st'); + throw vm_service.RPCError( + 'Error during expression compilation: $e\n$st', + RPCErrorCodes.kServerError, + '', + ); } }); - - _peer.sendNotification('registerService', <String, String>{ - 'service': 'compileExpression', - 'alias': 'Flutter Tools', - }); + _delegateService.registerService('compileExpression', 'Flutter Tools'); } if (device != null) { - _peer.registerMethod('flutterMemoryInfo', (rpc.Parameters params) async { + _delegateService.registerServiceCallback('flutterMemoryInfo', (Map<String, dynamic> params) async { final MemoryInfo result = await device.queryMemoryInfo(); return result.toJson(); }); - _peer.sendNotification('registerService', <String, String>{ - 'service': 'flutterMemoryInfo', - 'alias': 'Flutter Tools', - }); + _delegateService.registerService('flutterMemoryInfo', 'Flutter Tools'); } } - static void _unhandledError(dynamic error, dynamic stack) { - globals.logger.printTrace('Error in internal implementation of JSON RPC.\n$error\n$stack'); - assert(false); - } - /// Connect to a Dart VM Service at [httpUri]. /// /// If the [reloadSources] parameter is not null, the 'reloadSources' service @@ -332,10 +347,38 @@ Device device, }) async { final Uri wsUri = httpUri.replace(scheme: 'ws', path: globals.fs.path.join(httpUri.path, 'ws')); - final StreamChannel<String> channel = await _openChannel(wsUri, compression: compression); - final rpc.Peer peer = rpc.Peer.withoutJson(jsonDocument.bind(channel), onUnhandledError: _unhandledError); + final io.WebSocket channel = await _openChannel(wsUri.toString(), compression: compression); + final StreamController<dynamic> primary = StreamController<dynamic>(); + final StreamController<dynamic> secondary = StreamController<dynamic>(); + + channel.listen((dynamic data) { + primary.add(data); + secondary.add(data); + }, onDone: () { + primary.close(); + secondary.close(); + }, onError: (dynamic error, StackTrace stackTrace) { + primary.addError(error, stackTrace); + secondary.addError(error, stackTrace); + }); + + // Create an instance of the package:vm_service API in addition to the flutter + // tool's to allow gradual migration. + final Completer<void> streamClosedCompleter = Completer<void>(); + + 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( - peer, httpUri, wsUri, reloadSources, @@ -343,17 +386,21 @@ 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 service._sendRequest('getVersion', const <String, dynamic>{}); + await delegateService.getVersion(); return service; } + final vm_service.VmService _delegateService; final Uri httpAddress; final Uri wsAddress; - final rpc.Peer _peer; - final Completer<Map<String, dynamic>> _connectionError = Completer<Map<String, dynamic>>(); + final Completer<void> streamClosedCompleter; VM _vm; /// The singleton [VM] object. Owns [Isolate] and [FlutterView] objects. @@ -362,46 +409,36 @@ final Map<String, StreamController<ServiceEvent>> _eventControllers = <String, StreamController<ServiceEvent>>{}; - final Set<String> _listeningFor = <String>{}; - /// Whether our connection to the VM service has been closed; - bool get isClosed => _peer.isClosed; + bool get isClosed => streamClosedCompleter.isCompleted; Future<void> get done async { - await _peer.done; + return streamClosedCompleter.future; } - // Events - Future<Stream<ServiceEvent>> get onDebugEvent => onEvent('Debug'); - Future<Stream<ServiceEvent>> get onExtensionEvent => onEvent('Extension'); - // IsolateStart, IsolateRunnable, IsolateExit, IsolateUpdate, ServiceExtensionAdded - Future<Stream<ServiceEvent>> get onIsolateEvent => onEvent('Isolate'); - Future<Stream<ServiceEvent>> get onTimelineEvent => onEvent('Timeline'); - Future<Stream<ServiceEvent>> get onStdoutEvent => onEvent('Stdout'); // WriteEvent + @override + Stream<vm_service.Event> get onDebugEvent => onEvent('Debug'); - // TODO(johnmccutchan): Add FlutterView events. + @override + Stream<vm_service.Event> get onExtensionEvent => onEvent('Extension'); - /// Returns a stream of VM service events. - /// - /// This purposely returns a `Future<Stream<T>>` rather than a `Stream<T>` - /// because it first registers with the VM to receive events on the stream, - /// and only once the VM has acknowledged that the stream has started will - /// we return the associated stream. Any attempt to streamline this API into - /// returning `Stream<T>` should take that into account to avoid race - /// conditions. - Future<Stream<ServiceEvent>> onEvent(String streamId) async { - await _streamListen(streamId); - return _getEventController(streamId).stream; + @override + Stream<vm_service.Event> get onIsolateEvent => onEvent('Isolate'); + + @override + Stream<vm_service.Event> get onTimelineEvent => onEvent('Timeline'); + + @override + Stream<vm_service.Event> get onStdoutEvent => onEvent('Stdout'); + + @override + Future<vm_service.Success> streamListen(String streamId) { + return _delegateService.streamListen(streamId); } - Future<Map<String, dynamic>> _sendRequest( - String method, - Map<String, dynamic> params, - ) { - return Future.any<Map<String, dynamic>>(<Future<Map<String, dynamic>>>[ - _peer.sendRequest(method, params).then<Map<String, dynamic>>(castStringKeyedMap), - _connectionError.future, - ]); + @override + Stream<vm_service.Event> onEvent(String streamId) { + return _delegateService.onEvent(streamId); } StreamController<ServiceEvent> _getEventController(String eventName) { @@ -441,19 +478,20 @@ _getEventController(streamId).add(event); } - Future<void> _streamListen(String streamId) async { - if (!_listeningFor.contains(streamId)) { - _listeningFor.add(streamId); - await _sendRequest('streamListen', <String, dynamic>{'streamId': streamId}); - } - } - /// Reloads the VM. - Future<void> getVM() async => await vm.reload(); + Future<void> getVMOld() async => await vm.reload(); Future<void> refreshViews({ bool waitForViews = false }) => vm.refreshViews(waitForViews: waitForViews); - Future<void> close() async => await _peer.close(); + Future<void> close() async { + _delegateService?.dispose(); + } + + // 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. @@ -936,36 +974,15 @@ return Future<Isolate>.value(_isolateCache[isolateId]); } - static String _truncate(String message, int width, String ellipsis) { - assert(ellipsis.length < width); - if (message.length <= width) { - return message; - } - return message.substring(0, width - ellipsis.length) + ellipsis; - } - /// Invoke the RPC and return the raw response. Future<Map<String, dynamic>> invokeRpcRaw( String method, { Map<String, dynamic> params = const <String, dynamic>{}, bool truncateLogs = true, }) async { - globals.printTrace('Sending to VM service: $method($params)'); - assert(params != null); - try { - final Map<String, dynamic> result = await _vmService._sendRequest(method, params); - final String resultString = - truncateLogs ? _truncate(result.toString(), 250, '...') : result.toString(); - globals.printTrace('Result: $resultString'); - return result; - } on WebSocketChannelException catch (error) { - throwToolExit('Error connecting to observatory: $error'); - return null; - } on rpc.RpcException catch (error) { - globals.printError('Error ${error.code} received from application: ${error.message}'); - globals.printTrace('${error.data}'); - rethrow; - } + final vm_service.Response response = await _vmService + ._delegateService.callServiceExtension(method, args: params); + return response.json; } /// Invoke the RPC and return a [ServiceObject] response. @@ -1264,13 +1281,17 @@ } final Map<String, dynamic> response = await invokeRpcRaw('_reloadSources', params: arguments); return response; - } on rpc.RpcException catch (e) { + } on vm_service.RPCError catch (e) { return Future<Map<String, dynamic>>.value(<String, dynamic>{ 'code': e.code, 'message': e.message, 'data': e.data, }); + } on vm_service.SentinelException catch (e) { + throwToolExit('Unexpected Sentinel while reloading sources: $e'); } + assert(false); + return null; } Future<Map<String, dynamic>> getObject(Map<String, dynamic> objectRef) { @@ -1293,9 +1314,9 @@ }) async { try { return await invokeRpcRaw(method, params: params); - } on rpc.RpcException catch (e) { + } on vm_service.RPCError catch (err) { // If an application is not using the framework - if (e.code == rpc_error_code.METHOD_NOT_FOUND) { + if (err.code == RPCErrorCodes.kMethodNotFound) { return null; } rethrow; @@ -1491,10 +1512,13 @@ final String viewId = id; // When this completer completes the isolate is running. final Completer<void> completer = Completer<void>(); - final StreamSubscription<ServiceEvent> subscription = - (await owner.vm.vmService.onIsolateEvent).listen((ServiceEvent event) { - // TODO(johnmccutchan): Listen to the debug stream and catch initial - // launch errors. + try { + await owner.vm.vmService.streamListen('Isolate'); + } on vm_service.RPCError { + // Do nothing, since the tool is already subscribed. + } + final StreamSubscription<vm_service.Event> subscription = + owner.vm.vmService.onIsolateEvent.listen((vm_service.Event event) { if (event.kind == ServiceEvent.kIsolateRunnable) { globals.printTrace('Isolate is runnable.'); if (!completer.isCompleted) {
diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index d83fa20..57276ff 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml
@@ -19,7 +19,6 @@ flutter_template_images: 1.0.1 http: 0.12.0+4 intl: 0.16.1 - json_rpc_2: 2.1.0 meta: 1.1.8 multicast_dns: 0.2.2 mustache_template: 1.0.0+1 @@ -28,10 +27,8 @@ process: 3.0.12 quiver: 2.1.3 stack_trace: 1.9.3 - stream_channel: 2.0.0 usage: 3.4.1 webdriver: 2.1.2 - web_socket_channel: 1.1.0 webkit_inspection_protocol: 0.5.0+1 xml: 3.7.0 yaml: 2.2.0 @@ -88,6 +85,7 @@ source_maps: 0.10.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.7.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sse: 3.2.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_transform: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -95,6 +93,7 @@ typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" uuid: 2.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 0.9.7+14 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: collection: 1.14.12 @@ -112,4 +111,4 @@ # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 8ac6 +# PUBSPEC CHECKSUM: 7689
diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index 1b47fe5..f85f99e 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart
@@ -15,8 +15,8 @@ import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/vmservice.dart'; -import 'package:json_rpc_2/json_rpc_2.dart' as rpc; import 'package:mockito/mockito.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; import '../src/common.dart'; import '../src/context.dart'; @@ -403,7 +403,7 @@ Future<Map<String, dynamic>> createDevFS(String fsName) async { _service.messages.add('create $fsName'); if (_devFSExists) { - throw rpc.RpcException(kFileSystemAlreadyExists, 'File system already exists'); + throw vm_service.RPCError('File system already exists', kFileSystemAlreadyExists, ''); } _devFSExists = true; return <String, dynamic>{'uri': '$_baseUri'};
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 fd6e8a7..43e2854 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
@@ -596,7 +596,7 @@ .thenAnswer((Invocation invocation) async => <int>[1]); when(portForwarder.forward(1)) .thenAnswer((Invocation invocation) async => 2); - when(vmService.getVM()) + when(vmService.getVMOld()) .thenAnswer((Invocation invocation) => Future<void>.value(null)); when(vmService.refreshViews()) .thenAnswer((Invocation invocation) => Future<void>.value(null));
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 0510ef3..415a3eb 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
@@ -2,13 +2,18 @@ // 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/artifacts.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_info.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/ios/mac.dart'; +import 'package:flutter_tools/src/vmservice.dart'; import 'package:mockito/mockito.dart'; +import 'package:vm_service/vm_service.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -137,6 +142,39 @@ ' with a non-Flutter log message following it.', ]); }); + + testWithoutContext('IOSDeviceLogReader can listen to VM Service logs', () async { + final MockVmService vmService = MockVmService(); + final DeviceLogReader logReader = IOSDeviceLogReader.test( + useSyslog: false, + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + ); + final StreamController<Event> controller = StreamController<Event>(); + final Completer<Success> stdoutCompleter = Completer<Success>(); + when(vmService.streamListen('Stdout')).thenAnswer((Invocation invocation) { + return stdoutCompleter.future; + }); + when(vmService.onStdoutEvent).thenAnswer((Invocation invocation) { + return controller.stream; + }); + logReader.connectedVMService = vmService; + + stdoutCompleter.complete(Success()); + controller.add(Event( + kind: 'Stdout', + timestamp: 0, + bytes: base64.encode(utf8.encode(' This is a message ')), + )); + + // Wait for stream listeners to fire. + await expectLater(logReader.logLines, emits(' This is a message ')); + }); } class MockArtifacts extends Mock implements Artifacts {} +class MockVmService extends Mock implements VMService, 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 5e3d1e2..569ae98 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -24,8 +24,8 @@ import 'package:flutter_tools/src/run_cold.dart'; import 'package:flutter_tools/src/run_hot.dart'; import 'package:flutter_tools/src/vmservice.dart'; -import 'package:json_rpc_2/json_rpc_2.dart'; import 'package:mockito/mockito.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; import '../src/common.dart'; import '../src/context.dart'; @@ -224,7 +224,7 @@ pathToReload: anyNamed('pathToReload'), invalidatedFiles: anyNamed('invalidatedFiles'), dillOutputPath: anyNamed('dillOutputPath'), - )).thenThrow(RpcException(666, 'something bad happened')); + )).thenThrow(vm_service.RPCError('something bad happened', 666, '')); final OperationResult result = await residentRunner.restart(fullRestart: false); expect(result.fatal, true); @@ -329,7 +329,7 @@ pathToReload: anyNamed('pathToReload'), invalidatedFiles: anyNamed('invalidatedFiles'), dillOutputPath: anyNamed('dillOutputPath'), - )).thenThrow(RpcException(666, 'something bad happened')); + )).thenThrow(vm_service.RPCError('something bad happened', 666, '')); final OperationResult result = await residentRunner.restart(fullRestart: true); expect(result.fatal, true);
diff --git a/packages/flutter_tools/test/general.shard/vmservice_test.dart b/packages/flutter_tools/test/general.shard/vmservice_test.dart index 3af20ff..7320373 100644 --- a/packages/flutter_tools/test/general.shard/vmservice_test.dart +++ b/packages/flutter_tools/test/general.shard/vmservice_test.dart
@@ -3,362 +3,218 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:io'; -import 'package:flutter_tools/src/base/io.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; +import 'package:mockito/mockito.dart'; import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/device.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/version.dart'; import 'package:flutter_tools/src/vmservice.dart'; -import 'package:json_rpc_2/json_rpc_2.dart' as rpc; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; -import 'package:quiver/testing/async.dart'; import '../src/common.dart'; import '../src/context.dart'; -import '../src/mocks.dart'; -class MockPeer implements rpc.Peer { +final Map<String, Object> vm = <String, dynamic>{ + 'type': 'VM', + 'name': 'vm', + 'architectureBits': 64, + 'targetCPU': 'x64', + 'hostCPU': ' Intel(R) Xeon(R) CPU E5-1650 v2 @ 3.50GHz', + 'version': '2.1.0-dev.7.1.flutter-45f9462398 (Fri Oct 19 19:27:56 2018 +0000) on "linux_x64"', + '_profilerMode': 'Dart', + '_nativeZoneMemoryUsage': 0, + 'pid': 103707, + 'startTime': 1540426121876, + '_embedder': 'Flutter', + '_maxRSS': 312614912, + '_currentRSS': 33091584, + 'isolates': <dynamic>[ + <String, dynamic>{ + 'type': '@Isolate', + 'fixedId': true, + 'id': 'isolates/242098474', + 'name': 'main.dart:main()', + 'number': 242098474, + }, + ], +}; - Function _versionFn = (dynamic _) => null; - - @override - rpc.ErrorCallback get onUnhandledError => null; - - @override - Future<dynamic> get done async { - throw 'unexpected call to done'; +final vm_service.Isolate isolate = vm_service.Isolate.parse( + <String, dynamic>{ + 'type': 'Isolate', + 'fixedId': true, + 'id': 'isolates/242098474', + 'name': 'main.dart:main()', + 'number': 242098474, + '_originNumber': 242098474, + 'startTime': 1540488745340, + '_heaps': <String, dynamic>{ + 'new': <String, dynamic>{ + 'used': 0, + 'capacity': 0, + 'external': 0, + 'collections': 0, + 'time': 0.0, + 'avgCollectionPeriodMillis': 0.0, + }, + 'old': <String, dynamic>{ + 'used': 0, + 'capacity': 0, + 'external': 0, + 'collections': 0, + 'time': 0.0, + 'avgCollectionPeriodMillis': 0.0, + }, + }, } +); - @override - bool get isClosed => _isClosed; - - @override - Future<dynamic> close() async { - _isClosed = true; - } - - @override - Future<dynamic> listen() async { - // this does get called - } - - @override - void registerFallback(dynamic callback(rpc.Parameters parameters)) { - throw 'unexpected call to registerFallback'; - } - - @override - void registerMethod(String name, Function callback) { - registeredMethods.add(name); - if (name == 'flutterVersion') { - _versionFn = callback; - } - } - - @override - void sendNotification(String method, [ dynamic parameters ]) { - // this does get called - sentNotifications.putIfAbsent(method, () => <dynamic>[]).add(parameters); - } - - Map<String, List<dynamic>> sentNotifications = <String, List<dynamic>>{}; - List<String> registeredMethods = <String>[]; - - bool isolatesEnabled = false; - bool _isClosed = false; - - Future<void> _getVMLatch; - Completer<void> _currentGetVMLatchCompleter; - - void tripGetVMLatch() { - final Completer<void> lastCompleter = _currentGetVMLatchCompleter; - _currentGetVMLatchCompleter = Completer<void>(); - _getVMLatch = _currentGetVMLatchCompleter.future; - lastCompleter?.complete(); - } - - int returnedFromSendRequest = 0; - - @override - Future<dynamic> sendRequest(String method, [ dynamic parameters ]) async { - if (method == 'getVM') { - await _getVMLatch; - } - await Future<void>.delayed(Duration.zero); - returnedFromSendRequest += 1; - if (method == 'getVM') { - return <String, dynamic>{ - 'type': 'VM', - 'name': 'vm', - 'architectureBits': 64, - 'targetCPU': 'x64', - 'hostCPU': ' Intel(R) Xeon(R) CPU E5-1650 v2 @ 3.50GHz', - 'version': '2.1.0-dev.7.1.flutter-45f9462398 (Fri Oct 19 19:27:56 2018 +0000) on "linux_x64"', - '_profilerMode': 'Dart', - '_nativeZoneMemoryUsage': 0, - 'pid': 103707, - 'startTime': 1540426121876, - '_embedder': 'Flutter', - '_maxRSS': 312614912, - '_currentRSS': 33091584, - 'isolates': isolatesEnabled ? <dynamic>[ - <String, dynamic>{ - 'type': '@Isolate', - 'fixedId': true, - 'id': 'isolates/242098474', - 'name': 'main.dart:main()', - 'number': 242098474, - }, - ] : <dynamic>[], - }; - } - if (method == 'getIsolate') { - return <String, dynamic>{ - 'type': 'Isolate', +final Map<String, Object> listViews = <String, dynamic>{ + 'type': 'FlutterViewList', + 'views': <dynamic>[ + <String, dynamic>{ + 'type': 'FlutterView', + 'id': '_flutterView/0x4a4c1f8', + 'isolate': <String, dynamic>{ + 'type': '@Isolate', 'fixedId': true, 'id': 'isolates/242098474', 'name': 'main.dart:main()', 'number': 242098474, - '_originNumber': 242098474, - 'startTime': 1540488745340, - '_heaps': <String, dynamic>{ - 'new': <String, dynamic>{ - 'used': 0, - 'capacity': 0, - 'external': 0, - 'collections': 0, - 'time': 0.0, - 'avgCollectionPeriodMillis': 0.0, - }, - 'old': <String, dynamic>{ - 'used': 0, - 'capacity': 0, - 'external': 0, - 'collections': 0, - 'time': 0.0, - 'avgCollectionPeriodMillis': 0.0, - }, - }, - }; - } - if (method == '_flutter.listViews') { - return <String, dynamic>{ - 'type': 'FlutterViewList', - 'views': isolatesEnabled ? <dynamic>[ - <String, dynamic>{ - 'type': 'FlutterView', - 'id': '_flutterView/0x4a4c1f8', - 'isolate': <String, dynamic>{ - 'type': '@Isolate', - 'fixedId': true, - 'id': 'isolates/242098474', - 'name': 'main.dart:main()', - 'number': 242098474, - }, - }, - ] : <dynamic>[], - }; - } - if (method == 'flutterVersion') { - return _versionFn(parameters); - } - return null; - } + }, + }, + ] +}; - @override - dynamic withBatch(dynamic callback()) { - throw 'unexpected call to withBatch'; - } -} +typedef ServiceCallback = Future<Map<String, dynamic>> Function(Map<String, Object>); void main() { - MockStdio mockStdio; - final MockFlutterVersion mockVersion = MockFlutterVersion(); - group('VMService', () { + testUsingContext('VMService can refreshViews', () async { + final MockVMService mockVmService = MockVMService(); + final VMService vmService = VMService( + null, + null, + null, + null, + null, + null, + null, + mockVmService, + Completer<void>(), + const Stream<dynamic>.empty(), + ); - setUp(() { - mockStdio = MockStdio(); + verify(mockVmService.registerService('flutterVersion', 'Flutter Tools')).called(1); + + when(mockVmService.callServiceExtension('getVM', + args: anyNamed('args'), // Empty + isolateId: null + )).thenAnswer((Invocation invocation) async { + return vm_service.Response.parse(vm); }); + await vmService.getVMOld(); - testUsingContext('fails connection eagerly in the connect() method', () async { - FakeAsync().run((FakeAsync time) { - bool failed = false; - final Future<VMService> future = VMService.connect(Uri.parse('http://host.invalid:9999/')); - future.whenComplete(() { - failed = true; - }); - time.elapse(const Duration(seconds: 5)); - expect(failed, isFalse); - expect(mockStdio.writtenToStdout.join(''), ''); - expect(mockStdio.writtenToStderr.join(''), ''); - time.elapse(const Duration(seconds: 5)); - expect(failed, isFalse); - expect(mockStdio.writtenToStdout.join(''), 'This is taking longer than expected...\n'); - expect(mockStdio.writtenToStderr.join(''), ''); - }); - }, overrides: <Type, Generator>{ - Logger: () => StdoutLogger( - outputPreferences: OutputPreferences.test(), - stdio: mockStdio, - terminal: AnsiTerminal( - stdio: mockStdio, - platform: const LocalPlatform(), - ), - timeoutConfiguration: const TimeoutConfiguration(), - ), - WebSocketConnector: () => (String url, {CompressionOptions compression}) async => throw const SocketException('test'), + + when(mockVmService.callServiceExtension('_flutter.listViews', + args: anyNamed('args'), + isolateId: anyNamed('isolateId') + )).thenAnswer((Invocation invocation) async { + return vm_service.Response.parse(listViews); }); + await vmService.refreshViews(waitForViews: true); - testUsingContext('closing VMService closes Peer', () async { - final MockPeer mockPeer = MockPeer(); - final VMService vmService = VMService(mockPeer, null, null, null, null, null, MockDevice(), null); - expect(mockPeer.isClosed, equals(false)); - await vmService.close(); - expect(mockPeer.isClosed, equals(true)); - }); + expect(vmService.vm.name, 'vm'); + expect(vmService.vm.views.single.id, '_flutterView/0x4a4c1f8'); + }, overrides: <Type, Generator>{ + Logger: () => BufferLogger.test() + }); - testUsingContext('refreshViews', () { - FakeAsync().run((FakeAsync time) { - bool done = false; - final MockPeer mockPeer = MockPeer(); - expect(mockPeer.returnedFromSendRequest, 0); - final VMService vmService = VMService(mockPeer, null, null, null, null, null, null, null); - expect(mockPeer.sentNotifications, contains('registerService')); - final List<String> registeredServices = - mockPeer.sentNotifications['registerService'] - .map((dynamic service) => (service as Map<String, String>)['service']) - .toList(); - expect(registeredServices, contains('flutterVersion')); - vmService.getVM().then((void value) { done = true; }); - expect(done, isFalse); - expect(mockPeer.returnedFromSendRequest, 0); - time.elapse(Duration.zero); - expect(done, isTrue); - expect(mockPeer.returnedFromSendRequest, 1); + testUsingContext('VmService registers reloadSources', () { + Future<void> reloadSources(String isolateId, { bool pause, bool force}) async {} + final MockVMService mockVMService = MockVMService(); + VMService( + null, + null, + reloadSources, + null, + null, + null, + null, + mockVMService, + Completer<void>(), + const Stream<dynamic>.empty(), + ); - done = false; - mockPeer.tripGetVMLatch(); // this blocks the upcoming getVM call - final Future<void> ready = vmService.refreshViews(waitForViews: true); - ready.then((void value) { done = true; }); - expect(mockPeer.returnedFromSendRequest, 1); - time.elapse(Duration.zero); // this unblocks the listViews call which returns nothing - expect(mockPeer.returnedFromSendRequest, 2); - time.elapse(const Duration(milliseconds: 50)); // the last listViews had no views, so it waits 50ms, then calls getVM - expect(done, isFalse); - expect(mockPeer.returnedFromSendRequest, 2); - mockPeer.tripGetVMLatch(); // this unblocks the getVM call - expect(mockPeer.returnedFromSendRequest, 2); - time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views - expect(mockPeer.returnedFromSendRequest, 4); - time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms - expect(done, isFalse); - expect(mockPeer.returnedFromSendRequest, 4); - mockPeer.tripGetVMLatch(); // this unblocks the getVM call - expect(mockPeer.returnedFromSendRequest, 4); - time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views - expect(mockPeer.returnedFromSendRequest, 6); - time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms - expect(done, isFalse); - expect(mockPeer.returnedFromSendRequest, 6); - mockPeer.tripGetVMLatch(); // this unblocks the getVM call - expect(mockPeer.returnedFromSendRequest, 6); - time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views - expect(mockPeer.returnedFromSendRequest, 8); - time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms - expect(done, isFalse); - expect(mockPeer.returnedFromSendRequest, 8); - mockPeer.tripGetVMLatch(); // this unblocks the getVM call - expect(mockPeer.returnedFromSendRequest, 8); - time.elapse(Duration.zero); // here getVM returns with no isolates and listViews returns no views - expect(mockPeer.returnedFromSendRequest, 10); - const String message = 'Flutter is taking longer than expected to report its views. Still trying...\n'; - expect(mockStdio.writtenToStdout.join(''), message); - expect(mockStdio.writtenToStderr.join(''), ''); - time.elapse(const Duration(milliseconds: 50)); // so refreshViews waits another 50ms - expect(done, isFalse); - expect(mockPeer.returnedFromSendRequest, 10); - mockPeer.isolatesEnabled = true; - mockPeer.tripGetVMLatch(); // this unblocks the getVM call - expect(mockPeer.returnedFromSendRequest, 10); - time.elapse(Duration.zero); // now it returns an isolate and the listViews call returns views - expect(mockPeer.returnedFromSendRequest, 13); - expect(done, isTrue); - expect(mockStdio.writtenToStdout.join(''), message); - expect(mockStdio.writtenToStderr.join(''), ''); - }); - }, overrides: <Type, Generator>{ - Logger: () => StdoutLogger( - outputPreferences: globals.outputPreferences, - terminal: AnsiTerminal( - stdio: mockStdio, - platform: const LocalPlatform(), - ), - stdio: mockStdio, - timeoutConfiguration: const TimeoutConfiguration(), - ), - }); + verify(mockVMService.registerService('reloadSources', 'Flutter Tools')).called(1); + }, overrides: <Type, Generator>{ + Logger: () => BufferLogger.test() + }); - testUsingContext('registers hot UI method', () { - FakeAsync().run((FakeAsync time) { - final MockPeer mockPeer = MockPeer(); - Future<void> reloadMethod({ String classId, String libraryId }) async {} - VMService(mockPeer, null, null, null, null, null, null, reloadMethod); + testUsingContext('VmService registers reloadMethod', () { + Future<void> reloadMethod({ String classId, String libraryId,}) async {} + final MockVMService mockVMService = MockVMService(); + VMService( + null, + null, + null, + null, + null, + null, + reloadMethod, + mockVMService, + Completer<void>(), + const Stream<dynamic>.empty(), + ); - expect(mockPeer.registeredMethods, contains('reloadMethod')); - }); - }, overrides: <Type, Generator>{ - Logger: () => StdoutLogger( - outputPreferences: globals.outputPreferences, - terminal: AnsiTerminal( - stdio: mockStdio, - platform: const LocalPlatform(), - ), - stdio: mockStdio, - timeoutConfiguration: const TimeoutConfiguration(), - ), - }); + verify(mockVMService.registerService('reloadMethod', 'Flutter Tools')).called(1); + }, overrides: <Type, Generator>{ + Logger: () => BufferLogger.test() + }); - testUsingContext('registers flutterMemoryInfo service', () { - FakeAsync().run((FakeAsync time) { - final MockDevice mockDevice = MockDevice(); - final MockPeer mockPeer = MockPeer(); - Future<void> reloadSources(String isolateId, { bool pause, bool force}) async {} - VMService(mockPeer, null, null, reloadSources, null, null, mockDevice, null); + testUsingContext('VmService registers flutterMemoryInfo service', () { + final MockDevice mockDevice = MockDevice(); + final MockVMService mockVMService = MockVMService(); + VMService( + null, + null, + null, + null, + null, + mockDevice, + null, + mockVMService, + Completer<void>(), + const Stream<dynamic>.empty(), + ); - expect(mockPeer.registeredMethods, contains('flutterMemoryInfo')); - }); - }, overrides: <Type, Generator>{ - Logger: () => StdoutLogger( - outputPreferences: globals.outputPreferences, - terminal: AnsiTerminal( - stdio: mockStdio, - platform: const LocalPlatform(), - ), - stdio: mockStdio, - timeoutConfiguration: const TimeoutConfiguration(), - ), - }); + verify(mockVMService.registerService('flutterMemoryInfo', 'Flutter Tools')).called(1); + }, overrides: <Type, Generator>{ + Logger: () => BufferLogger.test() + }); - testUsingContext('returns correct FlutterVersion', () { - FakeAsync().run((FakeAsync time) async { - final MockPeer mockPeer = MockPeer(); - VMService(mockPeer, null, null, null, null, null, MockDevice(), null); + testUsingContext('VMService returns correct FlutterVersion', () async { + final MockVMService mockVMService = MockVMService(); + VMService( + null, + null, + null, + null, + null, + null, + null, + mockVMService, + Completer<void>(), + const Stream<dynamic>.empty(), + ); - expect(mockPeer.registeredMethods, contains('flutterVersion')); - expect(await mockPeer.sendRequest('flutterVersion'), equals(mockVersion.toJson())); - }); - }, overrides: <Type, Generator>{ - FlutterVersion: () => mockVersion, - }); + verify(mockVMService.registerService('flutterVersion', 'Flutter Tools')).called(1); + }, overrides: <Type, Generator>{ + FlutterVersion: () => MockFlutterVersion(), }); } class MockDevice extends Mock implements Device {} - +class MockVMService extends Mock implements vm_service.VmService {} class MockFlutterVersion extends Mock implements FlutterVersion { @override Map<String, Object> toJson() => const <String, Object>{'Mock': 'Version'};