Revert "[flutter_tools] update to package vm_service: electric boogaloo (#53809)" (#53951)

This reverts commit 66d7a6c266.
This commit is contained in:
Jonah Williams 2020-04-03 14:00:07 -07:00 committed by GitHub
parent 1e86c1fb12
commit 66f4907754
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 546 additions and 466 deletions

View file

@ -14,9 +14,9 @@ dependencies:
test_api: 0.2.15
test_core: 0.3.3
build_runner: 1.8.1
build_runner: 1.8.0
build_test: 1.0.0
build_runner_core: 5.0.0
build_runner_core: 4.5.3
dart_style: 1.3.3
code_builder: 3.2.1
build: 1.2.2
@ -95,4 +95,4 @@ dartdoc:
# Exclude this package from the hosted API docs.
nodoc: true
# PUBSPEC CHECKSUM: 7c61
# PUBSPEC CHECKSUM: e267

View file

@ -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';
@ -423,7 +423,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 rpc.RpcException catch (rpcException) {
// 1001 is kFileSystemAlreadyExists in //dart/runtime/vm/json_stream.h
if (rpcException.code != 1001) {
rethrow;

View file

@ -620,7 +620,7 @@ class FuchsiaDevice extends Device {
// loopback (::1).
final Uri uri = Uri.parse('http://[$_ipv6Loopback]:$port');
final VMService vmService = await VMService.connect(uri);
await vmService.getVMOld();
await vmService.getVM();
await vmService.refreshViews();
for (final FlutterView flutterView in vmService.vm.views) {
if (flutterView.uiIsolate == null) {
@ -717,7 +717,7 @@ class FuchsiaIsolateDiscoveryProtocol {
continue;
}
}
await service.getVMOld();
await service.getVM();
await service.refreshViews();
for (final FlutterView flutterView in service.vm.views) {
if (flutterView.uiIsolate == null) {

View file

@ -7,11 +7,11 @@ import 'dart:math' as math;
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 @@ class IOSDeviceLogReader extends DeviceLogReader {
// 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<void>>[];
_loggingSubscriptions = <StreamSubscription<ServiceEvent>>[];
}
/// Create a new [IOSDeviceLogReader].
@ -554,7 +554,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
RegExp _anyLineRegex;
StreamController<String> _linesController;
List<StreamSubscription<void>> _loggingSubscriptions;
List<StreamSubscription<ServiceEvent>> _loggingSubscriptions;
@override
Stream<String> get logLines => _linesController.stream;
@ -575,20 +575,18 @@ class IOSDeviceLogReader extends DeviceLogReader {
if (_majorSdkVersion < _minimumUniversalLoggingSdkVersion) {
return;
}
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);
// 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);
}
}));
}
void _listenToSysLog() {
void _listenToSysLog () {
// syslog is not written on iOS 13+.
if (_majorSdkVersion >= _minimumUniversalLoggingSdkVersion) {
return;
@ -643,7 +641,7 @@ class IOSDeviceLogReader extends DeviceLogReader {
@override
void dispose() {
for (final StreamSubscription<void> loggingSubscription in _loggingSubscriptions) {
for (final StreamSubscription<ServiceEvent> loggingSubscription in _loggingSubscriptions) {
loggingSubscription.cancel();
}
_idevicesyslogProcess?.kill();

View file

@ -232,7 +232,7 @@ class FlutterDevice {
: vmService.vm.views).toList();
}
Future<void> getVMs() => vmService.getVMOld();
Future<void> getVMs() => vmService.getVM();
Future<void> exitApps() async {
if (!device.supportsFlutterExit) {

View file

@ -3,8 +3,10 @@
// 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';
@ -108,10 +110,9 @@ class HotRunner extends ResidentRunner {
// TODO(cbernaschina): check that isolateId is the id of the UI isolate.
final OperationResult result = await restart(pause: pause);
if (!result.isOk) {
throw vm_service.RPCError(
throw rpc.RpcException(
rpc_error_code.INTERNAL_ERROR,
'Unable to reload sources',
RPCErrorCodes.kInternalError,
'',
);
}
}
@ -120,10 +121,9 @@ class HotRunner extends ResidentRunner {
final OperationResult result =
await restart(fullRestart: true, pause: pause);
if (!result.isOk) {
throw vm_service.RPCError(
throw rpc.RpcException(
rpc_error_code.INTERNAL_ERROR,
'Unable to restart',
RPCErrorCodes.kInternalError,
'',
);
}
}
@ -564,15 +564,16 @@ class HotRunner extends ResidentRunner {
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.firstWhere((vm_service.Event event) {
return event.kind == vm_service.EventKind.kServiceExtensionAdded;
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;
}
}
}),
);
}
@ -719,12 +720,9 @@ class HotRunner extends ResidentRunner {
if (!result.isOk) {
restartEvent = 'restart-failed';
}
} on vm_service.SentinelException catch (err, st) {
} on rpc.RpcException {
restartEvent = 'exception';
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);
return OperationResult(1, 'hot restart failed to complete', fatal: true);
} finally {
HotEvent(restartEvent,
targetPlatform: targetPlatform,
@ -766,7 +764,7 @@ class HotRunner extends ResidentRunner {
);
},
);
} on vm_service.RPCError {
} on rpc.RpcException {
HotEvent('exception',
targetPlatform: targetPlatform,
sdkName: sdkName,

View file

@ -189,7 +189,7 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool Function(String) libra
Future<VMService> Function(Uri) connector = _defaultConnect,
}) async {
final VMService vmService = await connector(serviceUri);
await vmService.getVMOld();
await vmService.getVM();
final Map<String, dynamic> result = await _getAllCoverage(
vmService, libraryPredicate);
await vmService.close();
@ -197,7 +197,7 @@ Future<Map<String, dynamic>> collect(Uri serviceUri, bool Function(String) libra
}
Future<Map<String, dynamic>> _getAllCoverage(VMService service, bool Function(String) libraryPredicate) async {
await service.getVMOld();
await service.getVM();
final List<Map<String, dynamic>> coverage = <Map<String, dynamic>>[];
for (final Isolate isolateRef in service.vm.isolates) {
await isolateRef.load();

View file

@ -3,7 +3,6 @@
// 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';
@ -46,8 +45,7 @@ class Tracing {
);
try {
final Completer<void> whenFirstFrameRendered = Completer<void>();
await vmService.streamListen('Extension');
vmService.onExtensionEvent.listen((vm_service.Event event) {
(await vmService.onExtensionEvent).listen((ServiceEvent event) {
if (event.extensionKind == 'Flutter.FirstFrame') {
whenFirstFrameRendered.complete();
}

View file

@ -5,14 +5,18 @@
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:vm_service/vm_service.dart' as vm_service;
import 'package:stream_channel/stream_channel.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'base/common.dart';
import 'base/context.dart';
import 'base/io.dart' as io;
import 'base/utils.dart';
import 'convert.dart' show base64, json, utf8;
import 'convert.dart' show base64, utf8;
import 'device.dart';
import 'globals.dart' as globals;
import 'version.dart';
@ -21,24 +25,10 @@ import 'version.dart';
/// for [WebSocket]s (used by tests).
typedef WebSocketConnector = Future<io.WebSocket> Function(String url, {io.CompressionOptions compression});
WebSocketConnector _openChannel = _defaultOpenChannel;
/// A function that opens a two-way communication channel to the specified [uri].
typedef _OpenChannel = Future<StreamChannel<String>> Function(Uri uri, {io.CompressionOptions compression});
/// 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;
}
_OpenChannel _openChannel = _defaultOpenChannel;
/// A function that reacts to the invocation of the 'reloadSources' service.
///
@ -74,9 +64,7 @@ typedef ReloadMethod = Future<void> Function({
String libraryId,
});
Future<io.WebSocket> _defaultOpenChannel(String url, {
io.CompressionOptions compression = io.CompressionOptions.compressionDefault
}) async {
Future<StreamChannel<String>> _defaultOpenChannel(Uri uri, {io.CompressionOptions compression = io.CompressionOptions.compressionDefault}) async {
Duration delay = const Duration(milliseconds: 100);
int attempts = 0;
io.WebSocket socket;
@ -102,14 +90,14 @@ Future<io.WebSocket> _defaultOpenChannel(String url, {
while (socket == null) {
attempts += 1;
try {
socket = await constructor(url, compression: compression);
socket = await constructor(uri.toString(), compression: compression);
} on io.WebSocketException catch (e) {
await handleError(e);
} on io.SocketException catch (e) {
await handleError(e);
}
}
return socket;
return IOWebSocketChannel(socket).cast<String>();
}
/// Override `VMServiceConnector` in [context] to return a different VMService
@ -124,10 +112,10 @@ typedef VMServiceConnector = Future<VMService> Function(Uri httpUri, {
});
/// 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 {
// TODO(mklim): Test this, https://github.com/flutter/flutter/issues/23031
class VMService {
VMService(
this._peer,
this.httpAddress,
this.wsAddress,
ReloadSources reloadSources,
@ -135,49 +123,38 @@ class VMService implements vm_service.VmService {
CompileExpression compileExpression,
Device device,
ReloadMethod reloadMethod,
this._delegateService,
this.streamClosedCompleter,
Stream<dynamic> secondary,
) {
_vm = VM._empty(this);
_peer.listen().catchError(_connectionError.completeError);
// 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>);
}
_peer.registerMethod('streamNotify', (rpc.Parameters event) {
_handleStreamNotify(event.asMap.cast<String, dynamic>());
});
if (reloadSources != null) {
_delegateService.registerServiceCallback('reloadSources', (Map<String, dynamic> params) async {
_peer.registerMethod('reloadSources', (rpc.Parameters 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;
final bool force = params.asMap['force'] as bool ?? false;
final bool pause = params.asMap['pause'] as bool ?? false;
if (isolateId.isEmpty) {
throw vm_service.RPCError(
"Invalid 'isolateId': $isolateId",
RPCErrorCodes.kInvalidParams,
'',
);
throw rpc.RpcException.invalidParams("Invalid 'isolateId': $isolateId");
}
try {
await reloadSources(isolateId, force: force, pause: pause);
return <String, String>{'type': 'Success'};
} on vm_service.RPCError {
} on rpc.RpcException {
rethrow;
} on Exception catch (e, st) {
throw vm_service.RPCError(
'Error during Sources Reload: $e\n$st',
RPCErrorCodes.kServerError,
'',
);
throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
'Error during Sources Reload: $e\n$st');
}
});
_delegateService.registerService('reloadSources', 'Flutter Tools');
_peer.sendNotification('registerService', <String, String>{
'service': 'reloadSources',
'alias': 'Flutter Tools',
});
}
@ -191,23 +168,15 @@ class VMService implements vm_service.VmService {
// 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<String, dynamic> params) async {
final String libraryId = params['library'] as String;
final String classId = params['class'] as String;
_peer.registerMethod('reloadMethod', (rpc.Parameters params) async {
final String libraryId = params['library'].value as String;
final String classId = params['class'].value as String;
if (libraryId.isEmpty) {
throw vm_service.RPCError(
"Invalid 'libraryId': $libraryId",
RPCErrorCodes.kInvalidParams,
'',
);
throw rpc.RpcException.invalidParams("Invalid 'libraryId': $libraryId");
}
if (classId.isEmpty) {
throw vm_service.RPCError(
"Invalid 'classId': $classId",
RPCErrorCodes.kInvalidParams,
'',
);
throw rpc.RpcException.invalidParams("Invalid 'classId': $classId");
}
globals.printTrace('reloadMethod not yet supported, falling back to hot reload');
@ -218,97 +187,113 @@ class VMService implements vm_service.VmService {
classId: classId,
);
return <String, String>{'type': 'Success'};
} on vm_service.RPCError {
} on rpc.RpcException {
rethrow;
} on Exception catch (e, st) {
throw vm_service.RPCError('Error during Sources Reload: $e\n$st', -32000, '');
throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
'Error during Sources Reload: $e\n$st');
}
});
_delegateService.registerService('reloadMethod', 'Flutter Tools');
_peer.sendNotification('registerService', <String, String>{
'service': 'reloadMethod',
'alias': 'Flutter Tools',
});
}
if (restart != null) {
_delegateService.registerServiceCallback('hotRestart', (Map<String, dynamic> params) async {
final bool pause = params['pause'] as bool ?? false;
_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");
}
try {
await restart(pause: pause);
return <String, String>{'type': 'Success'};
} on vm_service.RPCError {
} on rpc.RpcException {
rethrow;
} on Exception catch (e, st) {
throw vm_service.RPCError(
'Error during Hot Restart: $e\n$st',
RPCErrorCodes.kServerError,
'',
);
throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
'Error during Hot Restart: $e\n$st');
}
});
_delegateService.registerService('hotRestart', 'Flutter Tools');
_peer.sendNotification('registerService', <String, String>{
'service': 'hotRestart',
'alias': 'Flutter Tools',
});
}
_delegateService.registerServiceCallback('flutterVersion', (Map<String, dynamic> params) async {
_peer.registerMethod('flutterVersion', (rpc.Parameters 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;
});
_delegateService.registerService('flutterVersion', 'Flutter Tools');
_peer.sendNotification('registerService', <String, String>{
'service': 'flutterVersion',
'alias': 'Flutter Tools',
});
if (compileExpression != null) {
_delegateService.registerServiceCallback('compileExpression', (Map<String, dynamic> params) async {
final String isolateId = params['isolateId'] as String;
_peer.registerMethod('compileExpression', (rpc.Parameters params) async {
final String isolateId = params['isolateId'].asString;
if (isolateId is! String || isolateId.isEmpty) {
throw throw vm_service.RPCError(
"Invalid 'isolateId': $isolateId",
RPCErrorCodes.kInvalidParams,
'',
);
throw rpc.RpcException.invalidParams(
"Invalid 'isolateId': $isolateId");
}
final String expression = params['expression'] as String;
final String expression = params['expression'].asString;
if (expression is! String || expression.isEmpty) {
throw throw vm_service.RPCError(
"Invalid 'expression': $expression",
RPCErrorCodes.kInvalidParams,
'',
);
throw rpc.RpcException.invalidParams(
"Invalid 'expression': $expression");
}
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;
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);
try {
final String kernelBytesBase64 = await compileExpression(isolateId,
expression, definitions, typeDefinitions, libraryUri, klass,
isStatic);
return <String, dynamic>{
'type': 'Success',
'result': <String, dynamic>{
'result': <String, dynamic>{'kernelBytes': kernelBytesBase64},
},
};
} on vm_service.RPCError {
return <String, dynamic>{'type': 'Success',
'result': <String, dynamic> {'kernelBytes': kernelBytesBase64}};
} on rpc.RpcException {
rethrow;
} on Exception catch (e, st) {
throw vm_service.RPCError(
'Error during expression compilation: $e\n$st',
RPCErrorCodes.kServerError,
'',
);
throw rpc.RpcException(rpc_error_code.SERVER_ERROR,
'Error during expression compilation: $e\n$st');
}
});
_delegateService.registerService('compileExpression', 'Flutter Tools');
_peer.sendNotification('registerService', <String, String>{
'service': 'compileExpression',
'alias': 'Flutter Tools',
});
}
if (device != null) {
_delegateService.registerServiceCallback('flutterMemoryInfo', (Map<String, dynamic> params) async {
_peer.registerMethod('flutterMemoryInfo', (rpc.Parameters params) async {
final MemoryInfo result = await device.queryMemoryInfo();
return result.toJson();
});
_delegateService.registerService('flutterMemoryInfo', 'Flutter Tools');
_peer.sendNotification('registerService', <String, String>{
'service': 'flutterMemoryInfo',
'alias': '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
@ -347,35 +332,10 @@ class VMService implements vm_service.VmService {
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<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 {
streamClosedCompleter.complete();
},
);
final StreamChannel<String> channel = await _openChannel(wsUri, compression: compression);
final rpc.Peer peer = rpc.Peer.withoutJson(jsonDocument.bind(channel), onUnhandledError: _unhandledError);
final VMService service = VMService(
peer,
httpUri,
wsUri,
reloadSources,
@ -383,21 +343,17 @@ class VMService implements vm_service.VmService {
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();
await service._sendRequest('getVersion', const <String, dynamic>{});
return service;
}
final vm_service.VmService _delegateService;
final Uri httpAddress;
final Uri wsAddress;
final Completer<void> streamClosedCompleter;
final rpc.Peer _peer;
final Completer<Map<String, dynamic>> _connectionError = Completer<Map<String, dynamic>>();
VM _vm;
/// The singleton [VM] object. Owns [Isolate] and [FlutterView] objects.
@ -406,36 +362,46 @@ class VMService implements vm_service.VmService {
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 => streamClosedCompleter.isCompleted;
bool get isClosed => _peer.isClosed;
Future<void> get done async {
return streamClosedCompleter.future;
await _peer.done;
}
@override
Stream<vm_service.Event> get onDebugEvent => onEvent('Debug');
// 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 onExtensionEvent => onEvent('Extension');
// TODO(johnmccutchan): Add FlutterView events.
@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);
/// 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> onEvent(String streamId) {
return _delegateService.onEvent(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,
]);
}
StreamController<ServiceEvent> _getEventController(String eventName) {
@ -475,20 +441,19 @@ class VMService implements vm_service.VmService {
_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> getVMOld() async => await vm.reload();
Future<void> getVM() async => await vm.reload();
Future<void> refreshViews({ bool waitForViews = false }) => vm.refreshViews(waitForViews: waitForViews);
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');
}
Future<void> close() async => await _peer.close();
}
/// An error that is thrown when constructing/updating a service object.
@ -971,15 +936,36 @@ class VM extends ServiceObjectOwner {
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 {
final vm_service.Response response = await _vmService
._delegateService.callServiceExtension(method, args: params);
return response.json;
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;
}
}
/// Invoke the RPC and return a [ServiceObject] response.
@ -1278,17 +1264,13 @@ class Isolate extends ServiceObjectOwner {
}
final Map<String, dynamic> response = await invokeRpcRaw('_reloadSources', params: arguments);
return response;
} on vm_service.RPCError catch (e) {
} on rpc.RpcException 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) {
@ -1311,9 +1293,9 @@ class Isolate extends ServiceObjectOwner {
}) async {
try {
return await invokeRpcRaw(method, params: params);
} on vm_service.RPCError catch (err) {
} on rpc.RpcException catch (e) {
// If an application is not using the framework
if (err.code == RPCErrorCodes.kMethodNotFound) {
if (e.code == rpc_error_code.METHOD_NOT_FOUND) {
return null;
}
rethrow;
@ -1509,13 +1491,10 @@ class FlutterView extends ServiceObject {
final String viewId = id;
// When this completer completes the isolate is running.
final Completer<void> completer = Completer<void>();
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) {
final StreamSubscription<ServiceEvent> subscription =
(await owner.vm.vmService.onIsolateEvent).listen((ServiceEvent event) {
// TODO(johnmccutchan): Listen to the debug stream and catch initial
// launch errors.
if (event.kind == ServiceEvent.kIsolateRunnable) {
globals.printTrace('Isolate is runnable.');
if (!completer.isCompleted) {

View file

@ -19,6 +19,7 @@ dependencies:
flutter_template_images: 1.0.0
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
@ -27,8 +28,10 @@ dependencies:
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.6.1
yaml: 2.2.0
@ -85,7 +88,6 @@ dependencies:
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.1 # 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"
@ -93,7 +95,6 @@ dependencies:
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
@ -111,4 +112,4 @@ dartdoc:
# Exclude this package from the hosted API docs.
nodoc: true
# PUBSPEC CHECKSUM: a585
# PUBSPEC CHECKSUM: 53c2

View file

@ -14,8 +14,8 @@ import 'package:flutter_tools/src/base/net.dart';
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';
@ -389,7 +389,7 @@ class MockVM implements VM {
Future<Map<String, dynamic>> createDevFS(String fsName) async {
_service.messages.add('create $fsName');
if (_devFSExists) {
throw vm_service.RPCError('File system already exists', kFileSystemAlreadyExists, '');
throw rpc.RpcException(kFileSystemAlreadyExists, 'File system already exists');
}
_devFSExists = true;
return <String, dynamic>{'uri': '$_baseUri'};

View file

@ -596,7 +596,7 @@ void main() {
.thenAnswer((Invocation invocation) async => <int>[1]);
when(portForwarder.forward(1))
.thenAnswer((Invocation invocation) async => 2);
when(vmService.getVMOld())
when(vmService.getVM())
.thenAnswer((Invocation invocation) => Future<void>.value(null));
when(vmService.refreshViews())
.thenAnswer((Invocation invocation) => Future<void>.value(null));

View file

@ -2,18 +2,13 @@
// 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';
@ -142,39 +137,6 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt
' 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 {}

View file

@ -23,8 +23,8 @@ import 'package:flutter_tools/src/resident_runner.dart';
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';
@ -222,7 +222,7 @@ void main() {
pathToReload: anyNamed('pathToReload'),
invalidatedFiles: anyNamed('invalidatedFiles'),
dillOutputPath: anyNamed('dillOutputPath'),
)).thenThrow(vm_service.RPCError('something bad happened', 666, ''));
)).thenThrow(RpcException(666, 'something bad happened'));
final OperationResult result = await residentRunner.restart(fullRestart: false);
expect(result.fatal, true);
@ -327,7 +327,7 @@ void main() {
pathToReload: anyNamed('pathToReload'),
invalidatedFiles: anyNamed('invalidatedFiles'),
dillOutputPath: anyNamed('dillOutputPath'),
)).thenThrow(vm_service.RPCError('something bad happened', 666, ''));
)).thenThrow(RpcException(666, 'something bad happened'));
final OperationResult result = await residentRunner.restart(fullRestart: true);
expect(result.fatal, true);

View file

@ -3,218 +3,362 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:vm_service/vm_service.dart' as vm_service;
import 'package:mockito/mockito.dart';
import 'package:flutter_tools/src/base/io.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';
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,
},
],
};
class MockPeer implements rpc.Peer {
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,
},
},
Function _versionFn = (dynamic _) => null;
@override
rpc.ErrorCallback get onUnhandledError => null;
@override
Future<dynamic> get done async {
throw 'unexpected call to done';
}
);
final Map<String, Object> listViews = <String, dynamic>{
'type': 'FlutterViewList',
'views': <dynamic>[
<String, dynamic>{
'type': 'FlutterView',
'id': '_flutterView/0x4a4c1f8',
'isolate': <String, dynamic>{
'type': '@Isolate',
@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',
'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;
}
typedef ServiceCallback = Future<Map<String, dynamic>> Function(Map<String, Object>);
@override
dynamic withBatch(dynamic callback()) {
throw 'unexpected call to withBatch';
}
}
void main() {
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(),
);
MockStdio mockStdio;
final MockFlutterVersion mockVersion = MockFlutterVersion();
group('VMService', () {
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);
setUp(() {
mockStdio = MockStdio();
});
await vmService.getVMOld();
when(mockVmService.callServiceExtension('_flutter.listViews',
args: anyNamed('args'),
isolateId: anyNamed('isolateId')
)).thenAnswer((Invocation invocation) async {
return vm_service.Response.parse(listViews);
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'),
});
await vmService.refreshViews(waitForViews: true);
expect(vmService.vm.name, 'vm');
expect(vmService.vm.views.single.id, '_flutterView/0x4a4c1f8');
}, overrides: <Type, Generator>{
Logger: () => BufferLogger.test()
});
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));
});
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(),
);
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);
verify(mockVMService.registerService('reloadSources', 'Flutter Tools')).called(1);
}, overrides: <Type, Generator>{
Logger: () => BufferLogger.test()
});
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(),
),
});
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(),
);
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);
verify(mockVMService.registerService('reloadMethod', 'Flutter Tools')).called(1);
}, overrides: <Type, Generator>{
Logger: () => BufferLogger.test()
});
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(),
),
});
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(),
);
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);
verify(mockVMService.registerService('flutterMemoryInfo', 'Flutter Tools')).called(1);
}, overrides: <Type, Generator>{
Logger: () => BufferLogger.test()
});
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(),
),
});
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(),
);
testUsingContext('returns correct FlutterVersion', () {
FakeAsync().run((FakeAsync time) async {
final MockPeer mockPeer = MockPeer();
VMService(mockPeer, null, null, null, null, null, MockDevice(), null);
verify(mockVMService.registerService('flutterVersion', 'Flutter Tools')).called(1);
}, overrides: <Type, Generator>{
FlutterVersion: () => MockFlutterVersion(),
expect(mockPeer.registeredMethods, contains('flutterVersion'));
expect(await mockPeer.sendRequest('flutterVersion'), equals(mockVersion.toJson()));
});
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
});
}
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'};