diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart index 63b4c9a1ddb..fa131fc0b15 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart @@ -620,13 +620,12 @@ 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.refreshViews(); - for (final FlutterView flutterView in vmService.vm.views) { + final List flutterViews = await vmService.getFlutterViews(); + for (final FlutterView flutterView in flutterViews) { if (flutterView.uiIsolate == null) { continue; } - final Uri address = flutterView.owner.vmService.httpAddress; + final Uri address = vmService.httpAddress; if (flutterView.uiIsolate.name.contains(isolateName)) { return address.port; } @@ -717,13 +716,12 @@ class FuchsiaIsolateDiscoveryProtocol { continue; } } - await service.getVMOld(); - await service.refreshViews(); - for (final FlutterView flutterView in service.vm.views) { + final List flutterViews = await service.getFlutterViews(); + for (final FlutterView flutterView in flutterViews) { if (flutterView.uiIsolate == null) { continue; } - final Uri address = flutterView.owner.vmService.httpAddress; + final Uri address = service.httpAddress; if (flutterView.uiIsolate.name.contains(_isolateName)) { _foundUri.complete(_device.ipv6 ? Uri.parse('http://[$_ipv6Loopback]:${address.port}/') diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index c3b7beede94..8f767250c5d 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -233,18 +233,25 @@ class FlutterDevice { if (vmService == null) { return; } - await flutterDeprecatedVmService.vm.refreshViews(waitForViews: true); + final List updatedViews = await vmService.getFlutterViews(); + _views + ..clear() + ..addAll(updatedViews); } + final List _views = []; List get views { - if (vmService == null || flutterDeprecatedVmService.isClosed) { + if (vmService == null) { return []; } - - - return (viewFilter != null - ? flutterDeprecatedVmService.vm.allViewsWithName(viewFilter) - : flutterDeprecatedVmService.vm.views).toList(); + if (viewFilter != null) { + return [ + for (final FlutterView flutterView in views) + if (flutterView.uiIsolate.name.contains(viewFilter)) + flutterView + ]; + } + return _views; } Future getVMs() => flutterDeprecatedVmService.getVMOld(); @@ -254,35 +261,32 @@ class FlutterDevice { await device.stopApp(package); return; } - final List flutterViews = views; - if (flutterViews == null || flutterViews.isEmpty) { + await refreshViews(); + if (views == null || views.isEmpty) { return; } // If any of the flutter views are paused, we might not be able to // cleanly exit since the service extension may not have been registered. - if (flutterViews.any((FlutterView view) { - return view != null && - view.uiIsolate != null && - view.uiIsolate.pauseEvent != null && - view.uiIsolate.pauseEvent.isPauseEvent; + for (final FlutterView flutterView in views) { + final vm_service.Isolate isolate = await vmService + .getIsolateOrNull(flutterView.uiIsolate.id); + if (isolate == null) { + continue; + } + if (isPauseEvent(isolate.pauseEvent.kind)) { + await device.stopApp(package); + return; } - )) { - await device.stopApp(package); - return; } - final List> futures = >[]; - for (final FlutterView view in flutterViews) { + for (final FlutterView view in views) { if (view != null && view.uiIsolate != null) { - assert(!view.uiIsolate.pauseEvent.isPauseEvent); - futures.add(vmService.flutterExit( + // If successful, there will be no response from flutterExit. + unawaited(vmService.flutterExit( isolateId: view.uiIsolate.id, )); } } - // The flutterExit message only returns if it fails, so just wait a few - // seconds then assume it worked. - // TODO(ianh): We should make this return once the VM service disconnects. - await Future.wait(futures).timeout(const Duration(seconds: 2), onTimeout: () => []); + return vmService.onDone; } Future setupDevFS( diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 3bbdbbeb003..61f89c25e04 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -514,10 +514,8 @@ class HotRunner extends ResidentRunner { String reason, bool benchmarkMode = false, }) async { - if (!_isPaused()) { - globals.printTrace('Refreshing active FlutterViews before restarting.'); - await refreshViews(); - } + globals.printTrace('Refreshing active FlutterViews before restarting.'); + await refreshViews(); final Stopwatch restartTimer = Stopwatch()..start(); // TODO(aam): Add generator reset logic once we switch to using incremental @@ -542,31 +540,36 @@ class HotRunner extends ResidentRunner { // Check if the isolate is paused and resume it. final List> operations = >[]; for (final FlutterDevice device in flutterDevices) { - final Set uiIsolates = {}; + final Set uiIsolatesIds = {}; for (final FlutterView view in device.views) { if (view.uiIsolate == null) { continue; } - uiIsolates.add(view.uiIsolate); + uiIsolatesIds.add(view.uiIsolate.id); // Reload the isolate. - operations.add(view.uiIsolate.reload().then((ServiceObject _) { - final ServiceEvent pauseEvent = view.uiIsolate.pauseEvent; - if ((pauseEvent != null) && pauseEvent.isPauseEvent) { + final Future reloadIsolate = device.vmService + .getIsolateOrNull(view.uiIsolate.id); + operations.add(reloadIsolate.then((vm_service.Isolate isolate) async { + if ((isolate != null) && isPauseEvent(isolate.pauseEvent.kind)) { // Resume the isolate so that it can be killed by the embedder. - return device.vmService.resume(view.uiIsolate.id); + await device.vmService.resume(view.uiIsolate.id); } - return null; })); } + // The engine handles killing and recreating isolates that it has spawned // ("uiIsolates"). The isolates that were spawned from these uiIsolates // will not be restared, and so they must be manually killed. - for (final Isolate isolate in device?.flutterDeprecatedVmService?.vm?.isolates ?? []) { - if (!uiIsolates.contains(isolate)) { - operations.add(isolate.invokeRpcRaw('kill', params: { - 'isolateId': isolate.id, - })); + final vm_service.VM vm = await device.vmService.getVM(); + for (final vm_service.IsolateRef isolateRef in vm.isolates) { + if (uiIsolatesIds.contains(isolateRef.id)) { + continue; } + operations.add(device.vmService.kill(isolateRef.id) + .catchError((dynamic error, StackTrace stackTrace) { + // Do nothing on a SentinelException since it means the isolate + // has already been killed. + }, test: (dynamic error) => error is vm_service.SentinelException)); } } await Future.wait(operations); @@ -589,13 +592,11 @@ class HotRunner extends ResidentRunner { } 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.kIsolateRunnable; - }), - ); - } + isolateNotifications.add( + device.vmService.onIsolateEvent.firstWhere((vm_service.Event event) { + return event.kind == vm_service.EventKind.kIsolateRunnable; + }), + ); } await Future.wait(isolateNotifications); } @@ -818,11 +819,9 @@ class HotRunner extends ResidentRunner { final Stopwatch reloadTimer = Stopwatch()..start(); - if (!_isPaused()) { - globals.printTrace('Refreshing active FlutterViews before reloading.'); - await refreshVM(); - await refreshViews(); - } + globals.printTrace('Refreshing active FlutterViews before reloading.'); + await refreshVM(); + await refreshViews(); final Stopwatch devFSTimer = Stopwatch()..start(); final UpdateFSReport updatedDevFS = await _updateDevFS(); @@ -914,27 +913,19 @@ class HotRunner extends ResidentRunner { // Record time it took for the VM to reload the sources. _addBenchmarkData('hotReloadVMReloadMilliseconds', vmReloadTimer.elapsed.inMilliseconds); final Stopwatch reassembleTimer = Stopwatch()..start(); - // Reload the isolate. - final List> allDevices = >[]; - for (final FlutterDevice device in flutterDevices) { - globals.printTrace('Sending reload events to ${device.device.name}'); - final List> futuresViews = >[]; - for (final FlutterView view in device.views) { - globals.printTrace('Sending reload event to "${view.uiIsolate.name}"'); - futuresViews.add(view.uiIsolate.reload()); - } - allDevices.add(Future.wait(futuresViews).whenComplete(() { - return device.refreshViews(); - })); - } - await Future.wait(allDevices); + + // Reload the isolate data. + await Future.wait(>[ + for (final FlutterDevice device in flutterDevices) + device.refreshViews() + ]); globals.printTrace('Evicting dirty assets'); await _evictDirtyAssets(); // Check if any isolates are paused and reassemble those // that aren't. - final List reassembleViews = []; + final Map reassembleViews = {}; final List> reassembleFutures = >[]; String serviceEventKind; int pausedIsolatesFound = 0; @@ -943,8 +934,12 @@ class HotRunner extends ResidentRunner { for (final FlutterView view in device.views) { // Check if the isolate is paused, and if so, don't reassemble. Ignore the // PostPauseEvent event - the client requesting the pause will resume the app. - final ServiceEvent pauseEvent = view.uiIsolate.pauseEvent; - if (pauseEvent != null && pauseEvent.isPauseEvent && pauseEvent.kind != ServiceEvent.kPausePostRequest) { + final vm_service.Isolate isolate = await device.vmService + .getIsolateOrNull(view.uiIsolate.id); + final vm_service.Event pauseEvent = isolate?.pauseEvent; + if (pauseEvent != null + && isPauseEvent(pauseEvent.kind) + && pauseEvent.kind != vm_service.EventKind.kPausePostRequest) { pausedIsolatesFound += 1; if (serviceEventKind == null) { serviceEventKind = pauseEvent.kind; @@ -952,7 +947,7 @@ class HotRunner extends ResidentRunner { serviceEventKind = ''; // many kinds } } else { - reassembleViews.add(view); + reassembleViews[view] = device.vmService; reassembleFutures.add(device.vmService.flutterReassemble( isolateId: view.uiIsolate.id, ).catchError((dynamic error) { @@ -974,6 +969,7 @@ class HotRunner extends ResidentRunner { assert(reassembleViews.isNotEmpty); globals.printTrace('Reassembling application'); + final Future reassembleFuture = Future.wait(reassembleFutures); await reassembleFuture.timeout( const Duration(seconds: 2), @@ -986,14 +982,17 @@ class HotRunner extends ResidentRunner { globals.printTrace('This is taking a long time; will now check for paused isolates.'); int postReloadPausedIsolatesFound = 0; String serviceEventKind; - for (final FlutterView view in reassembleViews) { - await view.uiIsolate.reload(); - final ServiceEvent pauseEvent = view.uiIsolate.pauseEvent; - if (pauseEvent != null && pauseEvent.isPauseEvent) { + for (final FlutterView view in reassembleViews.keys) { + final vm_service.Isolate isolate = await reassembleViews[view] + .getIsolateOrNull(view.uiIsolate.id); + if (isolate == null) { + continue; + } + if (isolate.pauseEvent != null && isPauseEvent(isolate.pauseEvent.kind)) { postReloadPausedIsolatesFound += 1; if (serviceEventKind == null) { - serviceEventKind = pauseEvent.kind; - } else if (serviceEventKind != pauseEvent.kind) { + serviceEventKind = isolate.pauseEvent.kind; + } else if (serviceEventKind != isolate.pauseEvent.kind) { serviceEventKind = ''; // many kinds } } @@ -1069,32 +1068,33 @@ class HotRunner extends ResidentRunner { } assert(serviceEventKind != null); switch (serviceEventKind) { - case ServiceEvent.kPauseStart: message.write('paused (probably due to --start-paused)'); break; - case ServiceEvent.kPauseExit: message.write('paused because ${ plural ? 'they have' : 'it has' } terminated'); break; - case ServiceEvent.kPauseBreakpoint: message.write('paused in the debugger on a breakpoint'); break; - case ServiceEvent.kPauseInterrupted: message.write('paused due in the debugger'); break; - case ServiceEvent.kPauseException: message.write('paused in the debugger after an exception was thrown'); break; - case ServiceEvent.kPausePostRequest: message.write('paused'); break; - case '': message.write('paused for various reasons'); break; + case vm_service.EventKind.kPauseStart: + message.write('paused (probably due to --start-paused)'); + break; + case vm_service.EventKind.kPauseExit: + message.write('paused because ${ plural ? 'they have' : 'it has' } terminated'); + break; + case vm_service.EventKind.kPauseBreakpoint: + message.write('paused in the debugger on a breakpoint'); + break; + case vm_service.EventKind.kPauseInterrupted: + message.write('paused due in the debugger'); + break; + case vm_service.EventKind.kPauseException: + message.write('paused in the debugger after an exception was thrown'); + break; + case vm_service.EventKind.kPausePostRequest: + message.write('paused'); + break; + case '': + message.write('paused for various reasons'); + break; default: message.write('paused'); } return message.toString(); } - bool _isPaused() { - for (final FlutterDevice device in flutterDevices) { - for (final FlutterView view in device.views) { - if (view.uiIsolate != null) { - final ServiceEvent pauseEvent = view.uiIsolate.pauseEvent; - if (pauseEvent != null && pauseEvent.isPauseEvent) { - return true; - } - } - } - } - return false; - } @override void printHelp({ @required bool details }) { @@ -1134,7 +1134,7 @@ class HotRunner extends ResidentRunner { } for (final String assetPath in device.devFS.assetPathsToEvict) { futures.add( - device.views.first.uiIsolate.vmService + device.vmService .flutterEvictAsset( assetPath, isolateId: device.views.first.uiIsolate.id, diff --git a/packages/flutter_tools/lib/src/tracing.dart b/packages/flutter_tools/lib/src/tracing.dart index 6217717f156..b70850f211d 100644 --- a/packages/flutter_tools/lib/src/tracing.dart +++ b/packages/flutter_tools/lib/src/tracing.dart @@ -53,8 +53,9 @@ class Tracing { } }); bool done = false; - for (final FlutterView view in vmService.vm.views) { - if (await view.uiIsolate.vmService + final List views = await vmService.getFlutterViews(); + for (final FlutterView view in views) { + if (await vmService .flutterAlreadyPaintedFirstUsefulFrame( isolateId: view.uiIsolate.id, )) { diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 75b6f92c247..d9a1d9e9edb 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -19,6 +19,7 @@ const String kGetSkSLsMethod = '_flutter.getSkSLs'; const String kSetAssetBundlePathMethod = '_flutter.setAssetBundlePath'; const String kFlushUIThreadTasksMethod = '_flutter.flushUIThreadTasks'; const String kRunInViewMethod = '_flutter.runInView'; +const String kListViewsMethod = '_flutter.listViews'; /// The error response code from an unrecoverable compilation failure. const int kIsolateReloadBarred = 1005; @@ -509,10 +510,10 @@ class VMService implements vm_service.VmService { // getFromMap creates the Isolate if necessary. final Isolate isolate = vm.getFromMap(eventIsolate) as Isolate; event = ServiceObject._fromMap(isolate, eventData) as ServiceEvent; - if (event.kind == ServiceEvent.kIsolateExit) { + if (event.kind == vm_service.EventKind.kIsolateExit) { vm._isolateCache.remove(isolate.id); vm._buildIsolateList(); - } else if (event.kind == ServiceEvent.kIsolateRunnable) { + } else if (event.kind == vm_service.EventKind.kIsolateRunnable) { // Force reload once the isolate becomes runnable so that we // update the root library. isolate.reload(); @@ -532,8 +533,6 @@ class VMService implements vm_service.VmService { /// Reloads the VM. Future getVMOld() async => await vm.reload(); - Future refreshViews({ bool waitForViews = false }) => vm.refreshViews(waitForViews: waitForViews); - Future close() async { _delegateService?.dispose(); } @@ -555,6 +554,21 @@ class VMService implements vm_service.VmService { ); } + @override + Future getIsolate(String isolateId) { + return _delegateService.getIsolate(isolateId); + } + + @override + Future resume(String isolateId, {String step, int frameIndex}) { + return _delegateService.resume(isolateId, step: step, frameIndex: frameIndex); + } + + @override + Future kill(String isolateId) { + return _delegateService.kill(isolateId); + } + // To enable a gradual migration to package:vm_service @override dynamic noSuchMethod(Invocation invocation) { @@ -644,9 +658,6 @@ abstract class ServiceObject { case 'Event': serviceObject = ServiceEvent._empty(owner); break; - case 'FlutterView': - serviceObject = FlutterView._empty(owner.vm); - break; case 'Isolate': serviceObject = Isolate._empty(owner.vm); break; @@ -790,34 +801,6 @@ class ServiceEvent extends ServiceObject { String _message; String get message => _message; - // The possible 'kind' values. - static const String kVMUpdate = 'VMUpdate'; - static const String kIsolateStart = 'IsolateStart'; - static const String kIsolateRunnable = 'IsolateRunnable'; - static const String kIsolateExit = 'IsolateExit'; - static const String kIsolateUpdate = 'IsolateUpdate'; - static const String kIsolateReload = 'IsolateReload'; - static const String kIsolateSpawn = 'IsolateSpawn'; - static const String kServiceExtensionAdded = 'ServiceExtensionAdded'; - static const String kPauseStart = 'PauseStart'; - static const String kPauseExit = 'PauseExit'; - static const String kPauseBreakpoint = 'PauseBreakpoint'; - static const String kPauseInterrupted = 'PauseInterrupted'; - static const String kPauseException = 'PauseException'; - static const String kPausePostRequest = 'PausePostRequest'; - static const String kNone = 'None'; - static const String kResume = 'Resume'; - static const String kBreakpointAdded = 'BreakpointAdded'; - static const String kBreakpointResolved = 'BreakpointResolved'; - static const String kBreakpointRemoved = 'BreakpointRemoved'; - static const String kGraph = '_Graph'; - static const String kGC = 'GC'; - static const String kInspect = 'Inspect'; - static const String kDebuggerSettingsUpdate = '_DebuggerSettingsUpdate'; - static const String kConnectionClosed = 'ConnectionClosed'; - static const String kLogging = '_Logging'; - static const String kExtension = 'Extension'; - @override void _update(Map map, bool mapIsRef) { _loaded = true; @@ -842,16 +825,6 @@ class ServiceEvent extends ServiceObject { _message = utf8.decode(base64.decode(base64Bytes)).trim(); } } - - bool get isPauseEvent { - return kind == kPauseStart || - kind == kPauseExit || - kind == kPauseBreakpoint || - kind == kPauseInterrupted || - kind == kPauseException || - kind == kPausePostRequest || - kind == kNone; - } } /// A ServiceObjectOwner is either a [VM] or an [Isolate]. Owners can cache @@ -917,9 +890,6 @@ class VM extends ServiceObjectOwner { /// The list of live isolates, ordered by isolate start time. final List isolates = []; - /// The set of live views. - final Map _viewCache = {}; - /// The number of bytes allocated (e.g. by malloc) in the native heap. int _heapAllocatedMemoryUsage; int get heapAllocatedMemoryUsage => _heapAllocatedMemoryUsage ?? 0; @@ -1008,16 +978,6 @@ class VM extends ServiceObjectOwner { isolate.updateFromMap(map); } return isolate; - case 'FlutterView': - FlutterView view = _viewCache[mapId]; - if (view == null) { - // Add new view to the cache. - view = ServiceObject._fromMap(this, map) as FlutterView; - _viewCache[mapId] = view; - } else { - view.updateFromMap(map); - } - return view; default: // If we don't have a model object for this service object type, as a // fallback return a ServiceMap object. @@ -1094,47 +1054,6 @@ class VM extends ServiceObjectOwner { Future> getVMTimeline() { return invokeRpcRaw('getVMTimeline'); } - - Future refreshViews({ bool waitForViews = false }) async { - assert(waitForViews != null); - assert(loaded); - if (!isFlutterEngine) { - return; - } - int failCount = 0; - while (true) { - _viewCache.clear(); - // When the future returned by invokeRpc() below returns, - // the _viewCache will have been updated. - // This message updates all the views of every isolate. - await vmService.vm.invokeRpc( - '_flutter.listViews', truncateLogs: false); - if (_viewCache.values.isNotEmpty || !waitForViews) { - return; - } - failCount += 1; - if (failCount == 5) { // waited 200ms - globals.printStatus('Flutter is taking longer than expected to report its views. Still trying...'); - } - await Future.delayed(const Duration(milliseconds: 50)); - await reload(); - } - } - - Iterable get views => _viewCache.values; - - FlutterView get firstView { - return _viewCache.values.isEmpty ? null : _viewCache.values.first; - } - - List allViewsWithName(String isolateFilter) { - if (_viewCache.values.isEmpty) { - return null; - } - return _viewCache.values.where( - (FlutterView v) => v.uiIsolate.name.contains(isolateFilter) - ).toList(); - } } /// An isolate running inside the VM. Instances of the Isolate class are always @@ -1284,23 +1203,39 @@ class ServiceMap extends ServiceObject implements Map { } /// Peered to an Android/iOS FlutterView widget on a device. -class FlutterView extends ServiceObject { - FlutterView._empty(ServiceObjectOwner owner) : super._empty(owner); +class FlutterView { + FlutterView({ + @required this.id, + @required this.uiIsolate, + }); - Isolate _uiIsolate; - Isolate get uiIsolate => _uiIsolate; - - @override - void _update(Map map, bool mapIsRef) { - _loaded = !mapIsRef; - _upgradeCollection(map, owner); - _uiIsolate = map['isolate'] as Isolate; + factory FlutterView.parse(Map json) { + final Map rawIsolate = json['isolate'] as Map; + vm_service.IsolateRef isolate; + if (rawIsolate != null) { + rawIsolate['number'] = rawIsolate['number']?.toString(); + isolate = vm_service.IsolateRef.parse(rawIsolate); + } + return FlutterView( + id: json['id'] as String, + uiIsolate: isolate, + ); } - bool get hasIsolate => _uiIsolate != null; + final vm_service.IsolateRef uiIsolate; + final String id; + + bool get hasIsolate => uiIsolate != null; @override String toString() => id; + + Map toJson() { + return { + 'id': id, + 'isolate': uiIsolate?.toJson(), + }; + } } /// Flutter specific VM Service functionality. @@ -1532,13 +1467,15 @@ extension FlutterVmService on vm_service.VmService { /// /// This method is only supported by certain embedders. This is /// described by [Device.supportsFlutterExit]. - Future> flutterExit({ + Future flutterExit({ @required String isolateId, }) { return invokeFlutterExtensionRpcRaw( 'ext.flutter.exit', isolateId: isolateId, - ); + ).catchError((dynamic error, StackTrace stackTrace) { + // Do nothing on sentinel or exception, the isolate already exited. + }, test: (dynamic error) => error is vm_service.SentinelException || error is vm_service.RPCError); } /// Return the current platform override for the flutter view running with @@ -1588,4 +1525,37 @@ extension FlutterVmService on vm_service.VmService { rethrow; } } + + /// List all [FlutterView]s attached to the current VM. + Future> getFlutterViews() async { + final vm_service.Response response = await callMethod( + kListViewsMethod, + ); + final List rawViews = response.json['views'] as List; + return [ + for (final Object rawView in rawViews) + FlutterView.parse(rawView as Map) + ]; + } + + /// Attempt to retrieve the isolate with id [isolateId], or `null` if it has + /// been collected. + Future getIsolateOrNull(String isolateId) { + return getIsolate(isolateId) + .catchError((dynamic error, StackTrace stackTrace) { + return null; + }, test: (dynamic error) => error is vm_service.SentinelException); + } +} + +/// Whether the event attached to an [Isolate.pauseEvent] should be considered +/// a "pause" event. +bool isPauseEvent(String kind) { + return kind == vm_service.EventKind.kPauseStart || + kind == vm_service.EventKind.kPauseExit || + kind == vm_service.EventKind.kPauseBreakpoint || + kind == vm_service.EventKind.kPauseInterrupted || + kind == vm_service.EventKind.kPauseException || + kind == vm_service.EventKind.kPausePostRequest || + kind == vm_service.EventKind.kNone; } diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index 5bc54f06d9c..c571111f83c 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:file/memory.dart'; import 'package:meta/meta.dart'; import 'package:mockito/mockito.dart'; +import 'package:platform/platform.dart'; import 'package:process/process.dart'; import 'package:quiver/testing/async.dart'; import 'package:vm_service/vm_service.dart' as vm_service; @@ -34,6 +35,22 @@ import '../../src/context.dart'; import '../../src/fakes.dart'; import '../../src/mocks.dart'; +final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate( + id: '1', + pauseEvent: vm_service.Event( + kind: vm_service.EventKind.kResume, + timestamp: 0 + ), + breakpoints: [], + exceptionPauseMode: null, + libraries: [], + livePorts: 0, + name: 'test', + number: '1', + pauseOnExit: false, + runnable: true, + startTime: 0, +); void main() { group('attach', () { @@ -491,7 +508,7 @@ void main() { }, overrides: { FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), - }); + }, skip: const LocalPlatform().isWindows); // mDNS does not work on Windows. group('forwarding to given port', () { const int devicePort = 499; @@ -805,11 +822,21 @@ VMServiceConnector getFakeVmServiceFactory({ version: '', ); }); - - when(vm.refreshViews(waitForViews: anyNamed('waitForViews'))) - .thenAnswer((_) => Future.value(null)); - when(vm.views) - .thenReturn([FlutterViewMock()]); + when(vmService.getIsolate(any)) + .thenAnswer((Invocation invocation) async { + return fakeUnpausedIsolate; + }); + when(vmService.callMethod(kListViewsMethod)) + .thenAnswer((_) async { + return vm_service.Response.parse({ + 'views': [ + { + 'id': '1', + 'isolate': fakeUnpausedIsolate.toJson() + } + ] + }); + }); when(vm.createDevFS(any)) .thenAnswer((_) => Future>.value({'uri': '/',})); @@ -859,7 +886,6 @@ class TestHotRunnerFactory extends HotRunnerFactory { class VMMock extends Mock implements VM {} class VMServiceMock extends Mock implements VMService {} -class FlutterViewMock extends Mock implements FlutterView {} class MockProcessManager extends Mock implements ProcessManager {} class MockProcess extends Mock implements Process {} class MockHttpClientRequest extends Mock implements HttpClientRequest {} diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart index b9b23b4f695..768dc6bbea2 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 @@ -6,6 +6,12 @@ import 'dart:async'; import 'dart:convert'; import 'package:file/memory.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; +import 'package:meta/meta.dart'; +import 'package:mockito/mockito.dart'; +import 'package:platform/platform.dart'; +import 'package:process/process.dart'; + import 'package:flutter_tools/src/application_package.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/common.dart'; @@ -27,14 +33,27 @@ import 'package:flutter_tools/src/fuchsia/tiles_ctl.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/vmservice.dart'; -import 'package:meta/meta.dart'; -import 'package:mockito/mockito.dart'; -import 'package:platform/platform.dart'; -import 'package:process/process.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +final vm_service.Isolate fakeIsolate = vm_service.Isolate( + id: '1', + pauseEvent: vm_service.Event( + kind: vm_service.EventKind.kResume, + timestamp: 0 + ), + breakpoints: [], + exceptionPauseMode: null, + libraries: [], + livePorts: 0, + name: 'wrong name', + number: '1', + pauseOnExit: false, + runnable: true, + startTime: 0, +); + void main() { group('fuchsia device', () { MemoryFileSystem memoryFileSystem; @@ -607,71 +626,92 @@ void main() { }); - group(FuchsiaIsolateDiscoveryProtocol, () { + group('FuchsiaIsolateDiscoveryProtocol', () { MockPortForwarder portForwarder; MockVMService vmService; - MockVM vm; setUp(() { portForwarder = MockPortForwarder(); vmService = MockVMService(); - vm = MockVM(); - - when(vm.vmService).thenReturn(vmService); - when(vmService.vm).thenReturn(vm); }); - Future findUri(List views, String expectedIsolateName) async { - when(vm.views).thenReturn(views); - for (final MockFlutterView view in views) { - when(view.owner).thenReturn(vm); - } + Future findUri(List views, String expectedIsolateName) async { final MockFuchsiaDevice fuchsiaDevice = - MockFuchsiaDevice('123', portForwarder, false); + MockFuchsiaDevice('123', portForwarder, false); final FuchsiaIsolateDiscoveryProtocol discoveryProtocol = - FuchsiaIsolateDiscoveryProtocol( + FuchsiaIsolateDiscoveryProtocol( fuchsiaDevice, expectedIsolateName, (Uri uri) async => vmService, true, // only poll once. ); + when(fuchsiaDevice.servicePorts()) .thenAnswer((Invocation invocation) async => [1]); when(portForwarder.forward(1)) .thenAnswer((Invocation invocation) async => 2); when(vmService.getVMOld()) .thenAnswer((Invocation invocation) => Future.value(null)); - when(vmService.refreshViews()) - .thenAnswer((Invocation invocation) => Future.value(null)); when(vmService.httpAddress).thenReturn(Uri.parse('example')); + when(vmService.callMethod(kListViewsMethod)).thenAnswer((Invocation invocation) async { + return vm_service.Response.parse({ + 'views': [ + for (FlutterView view in views) + view.toJson() + ], + }); + }); return await discoveryProtocol.uri; } testUsingContext('can find flutter view with matching isolate name', () async { const String expectedIsolateName = 'foobar'; - final Uri uri = await findUri([ - MockFlutterView(null), // no ui isolate. - MockFlutterView(MockIsolate('wrong name')), // wrong name. - MockFlutterView(MockIsolate(expectedIsolateName)), // matching name. + final Uri uri = await findUri([ + // no ui isolate. + FlutterView(id: '1', uiIsolate: null), + // wrong name. + FlutterView( + id: '2', + uiIsolate: vm_service.Isolate.parse({ + ...fakeIsolate.toJson(), + 'name': 'Wrong name', + }), + ), + // matching name. + FlutterView( + id: '3', + uiIsolate: vm_service.Isolate.parse({ + ...fakeIsolate.toJson(), + 'name': expectedIsolateName, + }), + ), ], expectedIsolateName); + expect( uri.toString(), 'http://${InternetAddress.loopbackIPv4.address}:0/'); }); testUsingContext('can handle flutter view without matching isolate name', () async { const String expectedIsolateName = 'foobar'; - final Future uri = findUri([ - MockFlutterView(null), // no ui isolate. - MockFlutterView(MockIsolate('wrong name')), // wrong name. + final Future uri = findUri([ + // no ui isolate. + FlutterView(id: '1', uiIsolate: null), + // wrong name. + FlutterView(id: '2', uiIsolate: vm_service.Isolate.parse({ + ...fakeIsolate.toJson(), + 'name': 'wrong name', + })), ], expectedIsolateName); + expect(uri, throwsException); }); testUsingContext('can handle non flutter view', () async { const String expectedIsolateName = 'foobar'; - final Future uri = findUri([ - MockFlutterView(null), // no ui isolate. + final Future uri = findUri([ + FlutterView(id: '1', uiIsolate: null), // no ui isolate. ], expectedIsolateName); + expect(uri, throwsException); }); }); @@ -1083,22 +1123,6 @@ class MockPortForwarder extends Mock implements DevicePortForwarder {} class MockVMService extends Mock implements VMService {} -class MockVM extends Mock implements VM {} - -class MockFlutterView extends Mock implements FlutterView { - MockFlutterView(this.uiIsolate); - - @override - final Isolate uiIsolate; -} - -class MockIsolate extends Mock implements Isolate { - MockIsolate(this.name); - - @override - final String name; -} - class FuchsiaDeviceWithFakeDiscovery extends FuchsiaDevice { FuchsiaDeviceWithFakeDiscovery(String id, {String name}) : super(id, name: name); diff --git a/packages/flutter_tools/test/general.shard/hot_test.dart b/packages/flutter_tools/test/general.shard/hot_test.dart index 9a7d34f3725..25face5234e 100644 --- a/packages/flutter_tools/test/general.shard/hot_test.dart +++ b/packages/flutter_tools/test/general.shard/hot_test.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'package:vm_service/vm_service.dart' as vm_service; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/build_info.dart'; @@ -170,6 +171,52 @@ void main() { }); testUsingContext('Does hot restarts when all devices support it', () async { + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: [ + const FakeVmServiceRequest( + id: '1', + method: kListViewsMethod, + args: null, + jsonResponse: { + 'views': [], + } + ), + const FakeVmServiceRequest( + id: '2', + method: kListViewsMethod, + args: null, + jsonResponse: { + 'views': [], + } + ), + FakeVmServiceRequest( + id: '3', + method: 'getVM', + args: null, + jsonResponse: vm_service.VM.parse({}).toJson() + ), + FakeVmServiceRequest( + id: '4', + method: 'getVM', + args: null, + jsonResponse: vm_service.VM.parse({}).toJson() + ), + const FakeVmServiceRequest( + id: '5', + method: kListViewsMethod, + args: null, + jsonResponse: { + 'views': [], + } + ), + const FakeVmServiceRequest( + id: '6', + method: kListViewsMethod, + args: null, + jsonResponse: { + 'views': [], + } + ), + ]); // Setup mocks final MockDevice mockDevice = MockDevice(); final MockDevice mockHotDevice = MockDevice(); @@ -179,8 +226,12 @@ void main() { when(mockHotDevice.supportsHotRestart).thenReturn(true); // Trigger a restart. final List devices = [ - FlutterDevice(mockDevice, generator: residentCompiler, buildInfo: BuildInfo.debug)..devFS = mockDevFs, - FlutterDevice(mockHotDevice, generator: residentCompiler, buildInfo: BuildInfo.debug)..devFS = mockDevFs, + FlutterDevice(mockDevice, generator: residentCompiler, buildInfo: BuildInfo.debug) + ..vmService = fakeVmServiceHost.vmService + ..devFS = mockDevFs, + FlutterDevice(mockHotDevice, generator: residentCompiler, buildInfo: BuildInfo.debug) + ..vmService = fakeVmServiceHost.vmService + ..devFS = mockDevFs, ]; final HotRunner hotRunner = HotRunner(devices); final OperationResult result = await hotRunner.restart(fullRestart: true); @@ -211,13 +262,39 @@ void main() { testUsingContext('hot restart supported', () async { // Setup mocks + final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: [ + const FakeVmServiceRequest( + id: '1', + method: kListViewsMethod, + args: null, + jsonResponse: { + 'views': [], + } + ), + FakeVmServiceRequest( + id: '2', + method: 'getVM', + args: null, + jsonResponse: vm_service.VM.parse({}).toJson() + ), + const FakeVmServiceRequest( + id: '3', + method: kListViewsMethod, + args: null, + jsonResponse: { + 'views': [], + } + ), + ]); final MockDevice mockDevice = MockDevice(); when(mockDevice.supportsHotReload).thenReturn(true); when(mockDevice.supportsHotRestart).thenReturn(true); when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester); // Trigger hot restart. final List devices = [ - FlutterDevice(mockDevice, generator: residentCompiler, buildInfo: BuildInfo.debug)..devFS = mockDevFs, + FlutterDevice(mockDevice, generator: residentCompiler, buildInfo: BuildInfo.debug) + ..vmService = fakeVmServiceHost.vmService + ..devFS = mockDevFs, ]; final HotRunner hotRunner = HotRunner(devices); final OperationResult result = await hotRunner.restart(fullRestart: true); 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 93f1c7a332b..fb8ffba3560 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -31,16 +31,53 @@ import '../src/common.dart'; import '../src/context.dart'; import '../src/testbed.dart'; +final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate( + id: '1', + pauseEvent: vm_service.Event( + kind: vm_service.EventKind.kResume, + timestamp: 0 + ), + breakpoints: [], + exceptionPauseMode: null, + libraries: [], + livePorts: 0, + name: 'test', + number: '1', + pauseOnExit: false, + runnable: true, + startTime: 0, +); + +final vm_service.Isolate fakePausedIsolate = vm_service.Isolate( + id: '1', + pauseEvent: vm_service.Event( + kind: vm_service.EventKind.kPauseException, + timestamp: 0 + ), + breakpoints: [], + exceptionPauseMode: null, + libraries: [], + livePorts: 0, + name: 'test', + number: '1', + pauseOnExit: false, + runnable: true, + startTime: 0, +); + +final FlutterView fakeFlutterView = FlutterView( + id: 'a', + uiIsolate: fakeUnpausedIsolate, +); + void main() { final Uri testUri = Uri.parse('foo://bar'); Testbed testbed; MockFlutterDevice mockFlutterDevice; MockVMService mockVMService; MockDevFS mockDevFS; - MockFlutterView mockFlutterView; ResidentRunner residentRunner; MockDevice mockDevice; - MockIsolate mockIsolate; FakeVmServiceHost fakeVmServiceHost; setUp(() { @@ -61,8 +98,7 @@ void main() { mockDevice = MockDevice(); mockVMService = MockVMService(); mockDevFS = MockDevFS(); - mockFlutterView = MockFlutterView(); - mockIsolate = MockIsolate(); + // DevFS Mocks when(mockDevFS.lastCompiled).thenReturn(DateTime(2000)); when(mockDevFS.sources).thenReturn([]); @@ -92,10 +128,10 @@ void main() { }); when(mockFlutterDevice.devFS).thenReturn(mockDevFS); when(mockFlutterDevice.views).thenReturn([ - mockFlutterView, + // NB: still using mock FlutterDevice. + fakeFlutterView, ]); when(mockFlutterDevice.device).thenReturn(mockDevice); - when(mockFlutterView.uiIsolate).thenReturn(mockIsolate); when(mockFlutterDevice.stopEchoingDeviceLog()).thenAnswer((Invocation invocation) async { }); when(mockFlutterDevice.observatoryUris).thenAnswer((_) => Stream.value(testUri)); when(mockFlutterDevice.connect( @@ -134,9 +170,6 @@ void main() { final Completer result = Completer.sync(); return result.future; }); - when(mockIsolate.reload()).thenAnswer((Invocation invocation) { - return Future.value(null); - }); }); test('ResidentRunner can attach to device successfully', () => testbed.run(() async { @@ -160,18 +193,32 @@ void main() { test('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: [ - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '1', + method: 'getIsolate', + args: { + 'isolateId': fakeUnpausedIsolate.id, + }, + jsonResponse: fakeUnpausedIsolate.toJson(), + ), + FakeVmServiceRequest( + id: '2', + method: 'getVM', + args: null, + jsonResponse: vm_service.VM.parse({}).toJson(), + ), + const FakeVmServiceRequest( + id: '3', method: 'streamListen', args: { 'streamId': 'Isolate', } ), - const FakeVmServiceRequest( - id: '2', + FakeVmServiceRequest( + id: '4', method: kRunInViewMethod, args: { - 'viewId': null, + 'viewId': fakeFlutterView.id, 'mainScript': 'lib/main.dart.dill', 'assetDirectory': 'build/flutter_assets', } @@ -272,11 +319,19 @@ void main() { test('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: [ // Not all requests are present due to existing mocks - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '1', + method: 'getIsolate', + args: { + 'isolateId': '1', + }, + jsonResponse: fakeUnpausedIsolate.toJson(), + ), + FakeVmServiceRequest( + id: '2', method: 'ext.flutter.reassemble', args: { - 'isolateId': null, + 'isolateId': fakeUnpausedIsolate.id, }, ), ]); @@ -311,18 +366,32 @@ void main() { test('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async { // Not all requests are present due to existing mocks fakeVmServiceHost = FakeVmServiceHost(requests: [ - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '1', + method: 'getIsolate', + args: { + 'isolateId': fakeUnpausedIsolate.id, + }, + jsonResponse: fakeUnpausedIsolate.toJson(), + ), + FakeVmServiceRequest( + id: '2', + method: 'getVM', + args: null, + jsonResponse: vm_service.VM.parse({}).toJson(), + ), + const FakeVmServiceRequest( + id: '3', method: 'streamListen', args: { 'streamId': 'Isolate', }, ), - const FakeVmServiceRequest( - id: '2', + FakeVmServiceRequest( + id: '4', method: kRunInViewMethod, args: { - 'viewId': null, + 'viewId': fakeFlutterView.id, 'mainScript': 'lib/main.dart.dill', 'assetDirectory': 'build/flutter_assets', }, @@ -499,11 +568,11 @@ void main() { test('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: [ - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '1', method: kGetSkSLsMethod, args: { - 'viewId': null, + 'viewId': fakeFlutterView.id, }, jsonResponse: { 'SkSLs': {} @@ -517,11 +586,11 @@ void main() { test('ResidentRunner can write SkSL data to a unique file with engine revision, platform, and device name', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: [ - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '1', method: kGetSkSLsMethod, args: { - 'viewId': null, + 'viewId': fakeFlutterView.id, }, jsonResponse: { 'SkSLs': { @@ -549,19 +618,19 @@ void main() { test('ResidentRunner can take screenshot on debug device', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: [ - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '1', method: 'ext.flutter.debugAllowBanner', args: { - 'isolateId': null, + 'isolateId': fakeUnpausedIsolate.id, 'enabled': 'false', }, ), - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '2', method: 'ext.flutter.debugAllowBanner', args: { - 'isolateId': null, + 'isolateId': fakeUnpausedIsolate.id, 'enabled': 'true', }, ) @@ -591,11 +660,11 @@ void main() { test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws RpcError', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: [ - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '1', method: 'ext.flutter.debugAllowBanner', args: { - 'isolateId': null, + 'isolateId': fakeUnpausedIsolate.id, 'enabled': 'false', }, // Failed response, @@ -611,19 +680,19 @@ void main() { test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner during second request', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: [ - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '1', method: 'ext.flutter.debugAllowBanner', args: { - 'isolateId': null, + 'isolateId': fakeUnpausedIsolate.id, 'enabled': 'false', }, ), - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '2', method: 'ext.flutter.debugAllowBanner', args: { - 'isolateId': null, + 'isolateId': fakeUnpausedIsolate.id, 'enabled': 'true', }, // Failed response, @@ -638,19 +707,19 @@ void main() { test('ResidentRunner bails taking screenshot on debug device if takeScreenshot throws', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: [ - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '1', method: 'ext.flutter.debugAllowBanner', args: { - 'isolateId': null, + 'isolateId': fakeUnpausedIsolate.id, 'enabled': 'false', }, ), - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '2', method: 'ext.flutter.debugAllowBanner', args: { - 'isolateId': null, + 'isolateId': fakeUnpausedIsolate.id, 'enabled': 'true', }, ), @@ -694,15 +763,31 @@ void main() { })); test('FlutterDevice will not exit a paused isolate', () => testbed.run(() async { - fakeVmServiceHost = FakeVmServiceHost(requests: []); + fakeVmServiceHost = FakeVmServiceHost(requests: [ + FakeVmServiceRequest( + id: '1', + method: '_flutter.listViews', + args: null, + jsonResponse: { + 'views': [ + fakeFlutterView.toJson(), + ], + }, + ), + FakeVmServiceRequest( + id: '2', + method: 'getIsolate', + args: { + 'isolateId': fakeUnpausedIsolate.id, + }, + jsonResponse: fakePausedIsolate.toJson(), + ), + ]); final TestFlutterDevice flutterDevice = TestFlutterDevice( mockDevice, - [ mockFlutterView ], + [ fakeFlutterView ], ); flutterDevice.vmService = fakeVmServiceHost.vmService; - final MockServiceEvent mockServiceEvent = MockServiceEvent(); - when(mockServiceEvent.isPauseEvent).thenReturn(true); - when(mockIsolate.pauseEvent).thenReturn(mockServiceEvent); when(mockDevice.supportsFlutterExit).thenReturn(true); await flutterDevice.exitApps(); @@ -713,26 +798,44 @@ void main() { test('FlutterDevice will exit an un-paused isolate', () => testbed.run(() async { fakeVmServiceHost = FakeVmServiceHost(requests: [ - const FakeVmServiceRequest( + FakeVmServiceRequest( id: '1', + method: kListViewsMethod, + args: null, + jsonResponse: { + 'views': [ + fakeFlutterView.toJson(), + ], + }, + ), + FakeVmServiceRequest( + id: '2', + method: 'getIsolate', + args: { + 'isolateId': fakeUnpausedIsolate.id + }, + jsonResponse: fakeUnpausedIsolate.toJson(), + ), + FakeVmServiceRequest( + id: '3', method: 'ext.flutter.exit', args: { - 'isolateId': null, + 'isolateId': fakeUnpausedIsolate.id, }, + close: true, ) ]); final TestFlutterDevice flutterDevice = TestFlutterDevice( mockDevice, - [mockFlutterView], + [fakeFlutterView ], ); flutterDevice.vmService = fakeVmServiceHost.vmService; - final MockServiceEvent mockServiceEvent = MockServiceEvent(); - when(mockServiceEvent.isPauseEvent).thenReturn(false); - when(mockIsolate.pauseEvent).thenReturn(mockServiceEvent); when(mockDevice.supportsFlutterExit).thenReturn(true); - await flutterDevice.exitApps(); + final Future exitFuture = flutterDevice.exitApps(); + + await expectLater(exitFuture, completes); expect(fakeVmServiceHost.hasRemainingExpectations, false); })); @@ -981,17 +1084,14 @@ void main() { } class MockFlutterDevice extends Mock implements FlutterDevice {} -class MockFlutterView extends Mock implements FlutterView {} class MockVMService extends Mock implements VMService {} class MockDevFS extends Mock implements DevFS {} -class MockIsolate extends Mock implements Isolate {} class MockDevice extends Mock implements Device {} class MockDeviceLogReader extends Mock implements DeviceLogReader {} class MockDevicePortForwarder extends Mock implements DevicePortForwarder {} class MockUsage extends Mock implements Usage {} class MockProcessManager extends Mock implements ProcessManager {} -class MockServiceEvent extends Mock implements ServiceEvent {} -class MockVM extends Mock implements VM {} + class TestFlutterDevice extends FlutterDevice { TestFlutterDevice(Device device, this.views, { Stream observatoryUris }) : super(device, buildInfo: BuildInfo.debug) { diff --git a/packages/flutter_tools/test/general.shard/vmservice_test.dart b/packages/flutter_tools/test/general.shard/vmservice_test.dart index 88db3c1903b..12d1fb49ea9 100644 --- a/packages/flutter_tools/test/general.shard/vmservice_test.dart +++ b/packages/flutter_tools/test/general.shard/vmservice_test.dart @@ -91,46 +91,6 @@ final Map listViews = { typedef ServiceCallback = Future> Function(Map); void main() { - testUsingContext('VMService can refreshViews', () async { - final MockVMService mockVmService = MockVMService(); - final VMService vmService = VMService( - null, - null, - null, - null, - null, - null, - null, - mockVmService, - Completer(), - const Stream.empty(), - ); - - 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(); - - - 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); - - expect(vmService.vm.name, 'vm'); - expect(vmService.vm.views.single.id, '_flutterView/0x4a4c1f8'); - }, overrides: { - Logger: () => BufferLogger.test() - }); - testUsingContext('VmService registers reloadSources', () { Future reloadSources(String isolateId, { bool pause, bool force}) async {} final MockVMService mockVMService = MockVMService(); diff --git a/packages/flutter_tools/test/integration.shard/hot_reload_test.dart b/packages/flutter_tools/test/integration.shard/hot_reload_test.dart index 1370c5562bc..30b9fede6a0 100644 --- a/packages/flutter_tools/test/integration.shard/hot_reload_test.dart +++ b/packages/flutter_tools/test/integration.shard/hot_reload_test.dart @@ -97,6 +97,7 @@ void main() { _project.scheduledBreakpointUri, _project.scheduledBreakpointLine, ); + await Future.delayed(const Duration(seconds: 2)); await _flutter.hotReload(); // reload triggers code which eventually hits the breakpoint isolate = await _flutter.waitForPause(); expect(isolate.pauseEvent.kind, equals(EventKind.kPauseBreakpoint)); diff --git a/packages/flutter_tools/test/integration.shard/test_driver.dart b/packages/flutter_tools/test/integration.shard/test_driver.dart index e239ea46e02..0ee0c67604d 100644 --- a/packages/flutter_tools/test/integration.shard/test_driver.dart +++ b/packages/flutter_tools/test/integration.shard/test_driver.dart @@ -191,7 +191,7 @@ abstract class FlutterTestDriver { // ceases to be the case, this code will need changing. if (_flutterIsolateId == null) { final VM vm = await _vmService.getVM(); - _flutterIsolateId = vm.isolates.first.id; + _flutterIsolateId = vm.isolates.single.id; } return _flutterIsolateId; } diff --git a/packages/flutter_tools/test/src/common.dart b/packages/flutter_tools/test/src/common.dart index 298b5e7ce51..906708ef705 100644 --- a/packages/flutter_tools/test/src/common.dart +++ b/packages/flutter_tools/test/src/common.dart @@ -242,6 +242,11 @@ class FakeVmServiceHost { .having((Map request) => request['id'], 'id', fakeRequest.id) .having((Map request) => request['params'], 'args', fakeRequest.args) ); + if (fakeRequest.close) { + _vmService.dispose(); + expect(_requests, isEmpty); + return; + } if (fakeRequest.errorCode == null) { _input.add(json.encode({ 'jsonrpc': '2.0', @@ -298,11 +303,15 @@ class FakeVmServiceRequest implements VmServiceExpectation { @required this.args, this.jsonResponse, this.errorCode, + this.close = false, }); final String method; final String id; + /// When true, the vm service is automatically closed. + final bool close; + /// If non-null, the error code for a [vm_service.RPCError] in place of a /// standard response. final int errorCode;