Unverified Commit b6262e7c authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] remove runFromSource, move runInView to vm_service extension (#54700)

parent 846e8fe8
......@@ -457,14 +457,18 @@ class HotRunner extends ResidentRunner {
Future<void> _launchInView(
FlutterDevice device,
Uri entryUri,
Uri packagesUri,
Uri assetsDirectoryUri,
) {
return Future.wait(<Future<void>>[
Uri main,
Uri assetsDirectory,
) async {
await Future.wait(<Future<void>>[
for (final FlutterView view in device.views)
view.runFromSource(entryUri, assetsDirectoryUri),
device.vmService.runInView(
viewId: view.id,
main: main,
assetsDirectory: assetsDirectory,
),
]);
await device.refreshViews();
}
Future<void> _launchFromDevFS(String mainScript) async {
......@@ -473,12 +477,10 @@ class HotRunner extends ResidentRunner {
for (final FlutterDevice device in flutterDevices) {
final Uri deviceEntryUri = device.devFS.baseUri.resolveUri(
globals.fs.path.toUri(entryUri));
final Uri devicePackagesUri = device.devFS.baseUri.resolve('.packages');
final Uri deviceAssetsDirectoryUri = device.devFS.baseUri.resolveUri(
globals.fs.path.toUri(getAssetBuildDirectory()));
futures.add(_launchInView(device,
deviceEntryUri,
devicePackagesUri,
deviceAssetsDirectoryUri));
}
await Future.wait(futures);
......
......@@ -20,6 +20,7 @@ import 'version.dart';
const String kGetSkSLsMethod = '_flutter.getSkSLs';
const String kSetAssetBundlePathMethod = '_flutter.setAssetBundlePath';
const String kFlushUIThreadTasksMethod = '_flutter.flushUIThreadTasks';
const String kRunInViewMethod = '_flutter.runInView';
/// Override `WebSocketConnector` in [context] to use a different constructor
/// for [WebSocket]s (used by tests).
......@@ -1091,19 +1092,6 @@ class VM extends ServiceObjectOwner {
return invokeRpcRaw('_deleteDevFS', params: <String, dynamic>{'fsName': fsName});
}
Future<ServiceMap> runInView(
String viewId,
Uri main,
Uri assetsDirectory,
) {
return invokeRpc<ServiceMap>('_flutter.runInView',
params: <String, dynamic>{
'viewId': viewId,
'mainScript': main.toString(),
'assetDirectory': assetsDirectory.toString(),
});
}
Future<Map<String, dynamic>> clearVMTimeline() {
return invokeRpcRaw('clearVMTimeline');
}
......@@ -1541,36 +1529,6 @@ class FlutterView extends ServiceObject {
_uiIsolate = map['isolate'] as Isolate;
}
// TODO(johnmccutchan): Report errors when running failed.
Future<void> runFromSource(
Uri entryUri,
Uri assetsDirectoryUri,
) async {
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) {
if (event.kind == ServiceEvent.kIsolateRunnable) {
globals.printTrace('Isolate is runnable.');
if (!completer.isCompleted) {
completer.complete();
}
}
});
await owner.vm.runInView(viewId,
entryUri,
assetsDirectoryUri);
await completer.future;
await owner.vm.refreshViews(waitForViews: true);
await subscription.cancel();
}
bool get hasIsolate => _uiIsolate != null;
@override
......@@ -1579,6 +1537,7 @@ class FlutterView extends ServiceObject {
/// Flutter specific VM Service functionality.
extension FlutterVmService on vm_service.VmService {
/// Set the asset directory for the an attached Flutter view.
Future<void> setAssetDirectory({
@required Uri assetsDirectory,
......@@ -1623,4 +1582,33 @@ extension FlutterVmService on vm_service.VmService {
},
);
}
/// Launch the Dart isolate with entrypoint [main] in the Flutter engine [viewId]
/// with [assetsDirectory] as the devFS.
///
/// This method is used by the tool to hot restart an already running Flutter
/// engine.
Future<void> runInView({
@required String viewId,
@required Uri main,
@required Uri assetsDirectory,
}) async {
try {
await streamListen('Isolate');
} on vm_service.RPCError {
// Do nothing, since the tool is already subscribed.
}
final Future<void> onRunnable = onIsolateEvent.firstWhere((vm_service.Event event) {
return event.kind == vm_service.EventKind.kIsolateRunnable;
});
await callMethod(
kRunInViewMethod,
args: <String, Object>{
'viewId': viewId,
'mainScript': main.toString(),
'assetDirectory': assetsDirectory.toString(),
},
);
await onRunnable;
}
}
......@@ -88,6 +88,8 @@ void main() {
invalidatedSourcesCount: 0,
);
});
// TODO(jonahwilliams): replace mock with FakeVmServiceHost once all methods
// are moved to real vm service.
when(mockFlutterDevice.devFS).thenReturn(mockDevFS);
when(mockFlutterDevice.views).thenReturn(<FlutterView>[
mockFlutterView,
......@@ -97,7 +99,20 @@ void main() {
final MockVM mockVM = MockVM();
when(mockVMService.vm).thenReturn(mockVM);
when(mockVM.isolates).thenReturn(<Isolate>[mockIsolate]);
when(mockFlutterView.runFromSource(any, any)).thenAnswer((Invocation invocation) async {});
when(mockVMService.streamListen('Isolate')).thenAnswer((Invocation invocation) async {
return vm_service.Success();
});
when(mockVMService.onIsolateEvent).thenAnswer((Invocation invocation) {
return Stream<vm_service.Event>.fromIterable(<vm_service.Event>[
vm_service.Event(kind: vm_service.EventKind.kIsolateRunnable, timestamp: 0),
]);
});
when(mockVMService.callMethod(
kRunInViewMethod,
args: anyNamed('args'),
)).thenAnswer((Invocation invocation) async {
return vm_service.Success();
});
when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) async { });
when(mockFlutterDevice.observatoryUris).thenAnswer((_) => Stream<Uri>.value(testUri));
when(mockFlutterDevice.connect(
......
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart' as vm_service;
import 'package:mockito/mockito.dart';
import 'package:flutter_tools/src/base/logger.dart';
......@@ -280,6 +281,124 @@ void main() {
]))
]));
});
testWithoutContext('runInView forwards arguments correctly', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(
requests: <VmServiceExpectation>[
const FakeVmServiceRequest(method: 'streamListen', id: '1', params: <String, Object>{
'streamId': 'Isolate'
}),
const FakeVmServiceRequest(method: kRunInViewMethod, id: '2', params: <String, Object>{
'viewId': '1234',
'mainScript': 'main.dart',
'assetDirectory': 'flutter_assets/',
}),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
kind: vm_service.EventKind.kIsolateRunnable,
timestamp: 1,
)
),
]
);
await fakeVmServiceHost.vmService.runInView(
viewId: '1234',
main: Uri.file('main.dart'),
assetsDirectory: Uri.file('flutter_assets/'),
);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
});
}
class FakeVmServiceHost {
FakeVmServiceHost({
@required List<VmServiceExpectation> requests,
}) : _requests = requests {
_vmService = vm_service.VmService(
_input.stream,
_output.add,
);
_applyStreamListen();
_output.stream.listen((String data) {
final Map<String, Object> request = json.decode(data) as Map<String, Object>;
if (_requests.isEmpty) {
throw Exception('Unexpected request: $request');
}
final FakeVmServiceRequest fakeRequest = _requests.removeAt(0) as FakeVmServiceRequest;
expect(fakeRequest, isA<FakeVmServiceRequest>()
.having((FakeVmServiceRequest request) => request.method, 'method', request['method'])
.having((FakeVmServiceRequest request) => request.id, 'id', request['id'])
.having((FakeVmServiceRequest request) => request.params, 'params', request['params'])
);
_input.add(json.encode(<String, Object>{
'jsonrpc': '2.0',
'id': fakeRequest.id,
'result': fakeRequest.jsonResponse ?? <String, Object>{'type': 'Success'},
}));
_applyStreamListen();
});
}
final List<VmServiceExpectation> _requests;
final StreamController<String> _input = StreamController<String>();
final StreamController<String> _output = StreamController<String>();
vm_service.VmService get vmService => _vmService;
vm_service.VmService _vmService;
bool get hasRemainingExpectations => _requests.isNotEmpty;
// remove FakeStreamResponse objects from _requests until it is empty
// or until we hit a FakeRequest
void _applyStreamListen() {
while (_requests.isNotEmpty && !_requests.first.isRequest) {
final FakeVmServiceStreamResponse response = _requests.removeAt(0) as FakeVmServiceStreamResponse;
_input.add(json.encode(<String, Object>{
'jsonrpc': '2.0',
'method': 'streamNotify',
'params': <String, Object>{
'streamId': response.streamId,
'event': response.event.toJson(),
},
}));
}
}
}
abstract class VmServiceExpectation {
bool get isRequest;
}
class FakeVmServiceRequest implements VmServiceExpectation {
const FakeVmServiceRequest({
@required this.method,
@required this.id,
@required this.params,
this.jsonResponse,
});
final String method;
final String id;
final Map<String, Object> params;
final Map<String, Object> jsonResponse;
@override
bool get isRequest => true;
}
class FakeVmServiceStreamResponse implements VmServiceExpectation {
const FakeVmServiceStreamResponse({
@required this.event,
@required this.streamId,
});
final vm_service.Event event;
final String streamId;
@override
bool get isRequest => false;
}
class MockDevice extends Mock implements Device {}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment