[flutter_tools] move service extensions off of deprecated vm service (#55012)
diff --git a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart index e856412..26f1dbd 100644 --- a/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/build_runner/resident_web_runner.dart
@@ -34,6 +34,7 @@ import '../reporting/reporting.dart'; import '../resident_runner.dart'; import '../run_hot.dart'; +import '../vmservice.dart'; import '../web/chrome.dart'; import '../web/compile.dart'; import '../web/web_device.dart'; @@ -195,9 +196,10 @@ @override Future<void> debugDumpApp() async { try { - await _vmService?.callServiceExtension( - 'ext.flutter.debugDumpApp', - ); + await _vmService + ?.flutterDebugDumpApp( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -206,9 +208,10 @@ @override Future<void> debugDumpRenderTree() async { try { - await _vmService?.callServiceExtension( - 'ext.flutter.debugDumpRenderTree', - ); + await _vmService + ?.flutterDebugDumpRenderTree( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -217,9 +220,10 @@ @override Future<void> debugDumpLayerTree() async { try { - await _vmService?.callServiceExtension( - 'ext.flutter.debugDumpLayerTree', - ); + await _vmService + ?.flutterDebugDumpLayerTree( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -228,8 +232,10 @@ @override Future<void> debugDumpSemanticsTreeInTraversalOrder() async { try { - await _vmService?.callServiceExtension( - 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder'); + await _vmService + ?.flutterDebugDumpSemanticsTreeInTraversalOrder( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -238,14 +244,16 @@ @override Future<void> debugTogglePlatform() async { try { - final vmservice.Response response = await _vmService - ?.callServiceExtension('ext.flutter.platformOverride'); - final String currentPlatform = response.json['value'] as String; + final String currentPlatform = await _vmService + ?.flutterPlatformOverride( + isolateId: null, + ); final String platform = nextPlatform(currentPlatform, featureFlags); - await _vmService?.callServiceExtension('ext.flutter.platformOverride', - args: <String, Object>{ - 'value': platform, - }); + await _vmService + ?.flutterPlatformOverride( + platform: platform, + isolateId: null, + ); globals.printStatus('Switched operating system to $platform'); } on vmservice.RPCError { return; @@ -261,8 +269,10 @@ @override Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async { try { - await _vmService?.callServiceExtension( - 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder'); + await _vmService + ?.flutterDebugDumpSemanticsTreeInInverseHitTestOrder( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -271,16 +281,10 @@ @override Future<void> debugToggleDebugPaintSizeEnabled() async { try { - final vmservice.Response response = - await _vmService?.callServiceExtension( - 'ext.flutter.debugPaint', - ); - await _vmService?.callServiceExtension( - 'ext.flutter.debugPaint', - args: <dynamic, dynamic>{ - 'enabled': !(response.json['enabled'] == 'true') - }, - ); + await _vmService + ?.flutterToggleDebugPaintSizeEnabled( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -289,16 +293,10 @@ @override Future<void> debugToggleDebugCheckElevationsEnabled() async { try { - final vmservice.Response response = - await _vmService?.callServiceExtension( - 'ext.flutter.debugCheckElevationsEnabled', - ); - await _vmService?.callServiceExtension( - 'ext.flutter.debugCheckElevationsEnabled', - args: <dynamic, dynamic>{ - 'enabled': !(response.json['enabled'] == 'true') - }, - ); + await _vmService + ?.flutterToggleDebugCheckElevationsEnabled( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -307,14 +305,10 @@ @override Future<void> debugTogglePerformanceOverlayOverride() async { try { - final vmservice.Response response = await _vmService - ?.callServiceExtension('ext.flutter.showPerformanceOverlay'); - await _vmService?.callServiceExtension( - 'ext.flutter.showPerformanceOverlay', - args: <dynamic, dynamic>{ - 'enabled': !(response.json['enabled'] == 'true') - }, - ); + await _vmService + ?.flutterTogglePerformanceOverlayOverride( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -323,14 +317,10 @@ @override Future<void> debugToggleWidgetInspector() async { try { - final vmservice.Response response = await _vmService - ?.callServiceExtension('ext.flutter.debugToggleWidgetInspector'); - await _vmService?.callServiceExtension( - 'ext.flutter.debugToggleWidgetInspector', - args: <dynamic, dynamic>{ - 'enabled': !(response.json['enabled'] == 'true') - }, - ); + await _vmService + ?.flutterToggleWidgetInspector( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -339,14 +329,10 @@ @override Future<void> debugToggleProfileWidgetBuilds() async { try { - final vmservice.Response response = await _vmService - ?.callServiceExtension('ext.flutter.profileWidgetBuilds'); - await _vmService?.callServiceExtension( - 'ext.flutter.profileWidgetBuilds', - args: <dynamic, dynamic>{ - 'enabled': !(response.json['enabled'] == 'true') - }, - ); + await _vmService + ?.flutterToggleProfileWidgetBuilds( + isolateId: null, + ); } on vmservice.RPCError { return; } @@ -674,6 +660,14 @@ _connectionResult = await webDevFS.connect(useDebugExtension); unawaited(_connectionResult.debugConnection.onDone.whenComplete(_cleanupAndExit)); + _stdOutSub = _vmService.onStdoutEvent.listen((vmservice.Event log) { + final String message = utf8.decode(base64.decode(log.bytes)); + globals.printStatus(message, newline: false); + }); + _stdErrSub = _vmService.onStderrEvent.listen((vmservice.Event log) { + final String message = utf8.decode(base64.decode(log.bytes)); + globals.printStatus(message, newline: false); + }); try { await _vmService.streamListen(vmservice.EventStreams.kStdout); } on vmservice.RPCError { @@ -692,14 +686,6 @@ // It is safe to ignore this error because we expect an error to be // thrown if we're not already subscribed. } - _stdOutSub = _vmService.onStdoutEvent.listen((vmservice.Event log) { - final String message = utf8.decode(base64.decode(log.bytes)); - globals.printStatus(message, newline: false); - }); - _stdErrSub = _vmService.onStderrEvent.listen((vmservice.Event log) { - final String message = utf8.decode(base64.decode(log.bytes)); - globals.printStatus(message, newline: false); - }); unawaited(_vmService.registerService('reloadSources', 'FlutterTools')); _vmService.registerServiceCallback('reloadSources', (Map<String, Object> params) async { final bool pause = params['pause'] as bool ?? false;
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 3286a17..976e941 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart
@@ -6,6 +6,7 @@ import 'dart:math' as math; import 'package:meta/meta.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; import 'android/android_device_discovery.dart'; import 'android/android_workflow.dart'; @@ -25,7 +26,6 @@ import 'macos/macos_device.dart'; import 'project.dart'; import 'tester/flutter_tester.dart'; -import 'vmservice.dart'; import 'web/web_device.dart'; import 'windows/windows_device.dart'; @@ -755,7 +755,7 @@ /// Some logs can be obtained from a VM service stream. /// Set this after the VM services are connected. - VMService connectedVMService; + vm_service.VmService connectedVMService; @override String toString() => name; @@ -785,7 +785,7 @@ int appPid; @override - VMService connectedVMService; + vm_service.VmService connectedVMService; @override Stream<String> get logLines => const Stream<String>.empty();
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 4a35480..0bfd341 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -24,7 +24,6 @@ import '../mdns_discovery.dart'; import '../project.dart'; import '../protocol_discovery.dart'; -import '../vmservice.dart'; import 'fallback_discovery.dart'; import 'ios_deploy.dart'; import 'ios_workflow.dart'; @@ -561,18 +560,18 @@ Stream<String> get logLines => _linesController.stream; @override - VMService get connectedVMService => _connectedVMService; - VMService _connectedVMService; + vm_service.VmService get connectedVMService => _connectedVMService; + vm_service.VmService _connectedVMService; @override - set connectedVMService(VMService connectedVmService) { + set connectedVMService(vm_service.VmService connectedVmService) { _listenToUnifiedLoggingEvents(connectedVmService); _connectedVMService = connectedVmService; } static const int _minimumUniversalLoggingSdkVersion = 13; - Future<void> _listenToUnifiedLoggingEvents(VMService connectedVmService) async { + Future<void> _listenToUnifiedLoggingEvents(vm_service.VmService connectedVmService) async { if (_majorSdkVersion < _minimumUniversalLoggingSdkVersion) { return; }
diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 816e488..0650e27 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart
@@ -152,7 +152,7 @@ final ResidentCompiler generator; final BuildInfo buildInfo; Stream<Uri> observatoryUris; - VMService vmService; + vm_service.VmService vmService; DevFS devFS; ApplicationPackage package; List<String> fileSystemRoots; @@ -226,24 +226,28 @@ return completer.future; } + // TODO(jonahwilliams): remove once all callsites are updated. + VMService get flutterDeprecatedVmService => vmService as VMService; + Future<void> refreshViews() async { if (vmService == null) { return; } - await vmService.vm.refreshViews(waitForViews: true); + await flutterDeprecatedVmService.vm.refreshViews(waitForViews: true); } List<FlutterView> get views { - if (vmService == null || vmService.isClosed) { + if (vmService == null || flutterDeprecatedVmService.isClosed) { return <FlutterView>[]; } + return (viewFilter != null - ? vmService.vm.allViewsWithName(viewFilter) - : vmService.vm.views).toList(); + ? flutterDeprecatedVmService.vm.allViewsWithName(viewFilter) + : flutterDeprecatedVmService.vm.views).toList(); } - Future<void> getVMs() => vmService.getVMOld(); + Future<void> getVMs() => flutterDeprecatedVmService.getVMOld(); Future<void> exitApps() async { if (!device.supportsFlutterExit) { @@ -270,7 +274,9 @@ for (final FlutterView view in flutterViews) { if (view != null && view.uiIsolate != null) { assert(!view.uiIsolate.pauseEvent.isPauseEvent); - futures.add(view.uiIsolate.flutterExit()); + futures.add(vmService.flutterExit( + isolateId: view.uiIsolate.id, + )); } } // The flutterExit message only returns if it fails, so just wait a few @@ -286,7 +292,7 @@ }) { // One devFS per device. Shared by all running instances. devFS = DevFS( - vmService, + flutterDeprecatedVmService, fsName, rootDirectory, osUtils: globals.os, @@ -301,7 +307,7 @@ final String deviceEntryUri = devFS.baseUri .resolveUri(globals.fs.path.toUri(entryPath)).toString(); return <Future<vm_service.ReloadReport>>[ - for (final Isolate isolate in vmService.vm.isolates) + for (final Isolate isolate in flutterDeprecatedVmService.vm.isolates) vmService.reloadSources( isolate.id, pause: pause, @@ -325,68 +331,91 @@ Future<void> debugDumpApp() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterDebugDumpApp(); + await vmService.flutterDebugDumpApp( + isolateId: view.uiIsolate.id, + ); } } Future<void> debugDumpRenderTree() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterDebugDumpRenderTree(); + await vmService.flutterDebugDumpRenderTree( + isolateId: view.uiIsolate.id, + ); } } Future<void> debugDumpLayerTree() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterDebugDumpLayerTree(); + await vmService.flutterDebugDumpLayerTree( + isolateId: view.uiIsolate.id, + ); } } Future<void> debugDumpSemanticsTreeInTraversalOrder() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterDebugDumpSemanticsTreeInTraversalOrder(); + await vmService.flutterDebugDumpSemanticsTreeInTraversalOrder( + isolateId: view.uiIsolate.id, + ); } } Future<void> debugDumpSemanticsTreeInInverseHitTestOrder() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterDebugDumpSemanticsTreeInInverseHitTestOrder(); + await vmService.flutterDebugDumpSemanticsTreeInInverseHitTestOrder( + isolateId: view.uiIsolate.id, + ); } } Future<void> toggleDebugPaintSizeEnabled() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterToggleDebugPaintSizeEnabled(); + await vmService.flutterToggleDebugPaintSizeEnabled( + isolateId: view.uiIsolate.id, + ); } } Future<void> toggleDebugCheckElevationsEnabled() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterToggleDebugCheckElevationsEnabled(); + await vmService.flutterToggleDebugCheckElevationsEnabled( + isolateId: view.uiIsolate.id, + ); } } Future<void> debugTogglePerformanceOverlayOverride() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterTogglePerformanceOverlayOverride(); + await vmService.flutterTogglePerformanceOverlayOverride( + isolateId: view.uiIsolate.id, + ); } } Future<void> toggleWidgetInspector() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterToggleWidgetInspector(); + await vmService.flutterToggleWidgetInspector( + isolateId: view.uiIsolate.id, + ); } } Future<void> toggleProfileWidgetBuilds() async { for (final FlutterView view in views) { - await view.uiIsolate.flutterToggleProfileWidgetBuilds(); + await vmService.flutterToggleProfileWidgetBuilds( + isolateId: view.uiIsolate.id, + ); } } Future<String> togglePlatform({ String from }) async { final String to = nextPlatform(from, featureFlags); for (final FlutterView view in views) { - await view.uiIsolate.flutterPlatformOverride(to); + await vmService.flutterPlatformOverride( + platform: to, + isolateId: view.uiIsolate.id, + ); } return to; } @@ -416,7 +445,9 @@ } Future<void> initLogReader() async { - (await device.getLogReader(app: package)).appPid = vmService.vm.pid; + final vm_service.VM vm = await vmService.getVM(); + final DeviceLogReader logReader = await device.getLogReader(app: package); + logReader.appPid = vm.pid; } Future<int> runHot({ @@ -721,8 +752,16 @@ String method, { Map<String, dynamic> params, }) { - return flutterDevices.first.views.first.uiIsolate - .invokeFlutterExtensionRpcRaw(method, params: params); + return flutterDevices + .first + .vmService + .invokeFlutterExtensionRpcRaw( + method, + args: params, + isolateId: flutterDevices + .first.views + .first.uiIsolate.id + ); } /// Whether this runner can hot reload. @@ -812,7 +851,7 @@ void writeVmserviceFile() { if (debuggingOptions.vmserviceOutFile != null) { try { - final String address = flutterDevices.first.vmService.wsAddress.toString(); + final String address = flutterDevices.first.flutterDeprecatedVmService.wsAddress.toString(); final File vmserviceOutFile = globals.fs.file(debuggingOptions.vmserviceOutFile); vmserviceOutFile.createSync(recursive: true); vmserviceOutFile.writeAsStringSync(address); @@ -944,7 +983,10 @@ await device.refreshViews(); try { for (final FlutterView view in device.views) { - await view.uiIsolate.flutterDebugAllowBanner(false); + await device.vmService.flutterDebugAllowBanner( + false, + isolateId: view.uiIsolate.id, + ); } } on Exception catch (error) { status.cancel(); @@ -958,7 +1000,10 @@ if (supportsServiceProtocol && isRunningDebug) { try { for (final FlutterView view in device.views) { - await view.uiIsolate.flutterDebugAllowBanner(true); + await device.vmService.flutterDebugAllowBanner( + true, + isolateId: view.uiIsolate.id, + ); } } on Exception catch (error) { status.cancel(); @@ -980,7 +1025,12 @@ Future<void> debugTogglePlatform() async { await refreshViews(); - final String from = await flutterDevices[0].views[0].uiIsolate.flutterPlatformOverride(); + final String isolateId = flutterDevices + .first.views.first.uiIsolate.id; + final String from = await flutterDevices + .first.vmService.flutterPlatformOverride( + isolateId: isolateId, + ); String to; for (final FlutterDevice device in flutterDevices) { to = await device.togglePlatform(from: from); @@ -1039,7 +1089,7 @@ // This hooks up callbacks for when the connection stops in the future. // We don't want to wait for them. We don't handle errors in those callbacks' // futures either because they just print to logger and is not critical. - unawaited(device.vmService.done.then<void>( + unawaited(device.vmService.onDone.then<void>( _serviceProtocolDone, onError: _serviceProtocolError, ).whenComplete(_serviceDisconnected)); @@ -1056,7 +1106,7 @@ <String, dynamic>{ 'reuseWindows': true, }, - flutterDevices.first.vmService.httpAddress, + flutterDevices.first.flutterDeprecatedVmService.httpAddress, 'http://${_devtoolsServer.address.host}:${_devtoolsServer.port}', false, // headless mode, false, // machine mode
diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart index 1779426..1b84279 100644 --- a/packages/flutter_tools/lib/src/run_cold.dart +++ b/packages/flutter_tools/lib/src/run_cold.dart
@@ -83,8 +83,8 @@ if (flutterDevices.first.observatoryUris != null) { // For now, only support one debugger connection. connectionInfoCompleter?.complete(DebugConnectionInfo( - httpUri: flutterDevices.first.vmService.httpAddress, - wsUri: flutterDevices.first.vmService.wsAddress, + httpUri: flutterDevices.first.flutterDeprecatedVmService.httpAddress, + wsUri: flutterDevices.first.flutterDeprecatedVmService.wsAddress, )); } @@ -105,7 +105,7 @@ if (device.vmService != null) { globals.printStatus('Tracing startup on ${device.device.name}.'); await downloadStartupTrace( - device.vmService, + device.flutterDeprecatedVmService, awaitFirstFrame: awaitFirstFrameWhenTracing, ); } @@ -197,7 +197,7 @@ // Caution: This log line is parsed by device lab tests. globals.printStatus( 'An Observatory debugger and profiler on $dname is available at: ' - '${device.vmService.httpAddress}', + '${device.flutterDeprecatedVmService.httpAddress}', ); } }
diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 55094f4..9b11efe 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart
@@ -205,7 +205,10 @@ for (final FlutterDevice device in flutterDevices) { for (final FlutterView view in device.views) { - await view.uiIsolate.flutterFastReassemble(classId); + await device.vmService.flutterFastReassemble( + classId, + isolateId: view.uiIsolate.id, + ); } } @@ -260,8 +263,8 @@ // Only handle one debugger connection. connectionInfoCompleter.complete( DebugConnectionInfo( - httpUri: flutterDevices.first.vmService.httpAddress, - wsUri: flutterDevices.first.vmService.wsAddress, + httpUri: flutterDevices.first.flutterDeprecatedVmService.httpAddress, + wsUri: flutterDevices.first.flutterDeprecatedVmService.wsAddress, baseUri: baseUris.first.toString(), ), ); @@ -570,7 +573,7 @@ // 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?.vmService?.vm?.isolates ?? <Isolate>[]) { + for (final Isolate isolate in device?.flutterDeprecatedVmService?.vm?.isolates ?? <Isolate>[]) { if (!uiIsolates.contains(isolate)) { operations.add(isolate.invokeRpcRaw('kill', params: <String, dynamic>{ 'isolateId': isolate.id, @@ -938,10 +941,16 @@ } await Future.wait(allDevices); - // Check if any isolates are paused. + globals.printTrace('Evicting dirty assets'); + await _evictDirtyAssets(); + + // Check if any isolates are paused and reassemble those + // that aren't. final List<FlutterView> reassembleViews = <FlutterView>[]; + final List<Future<void>> reassembleFutures = <Future<void>>[]; String serviceEventKind; int pausedIsolatesFound = 0; + bool failedReassemble = false; for (final FlutterDevice device in flutterDevices) { for (final FlutterView view in device.views) { // Check if the isolate is paused, and if so, don't reassemble. Ignore the @@ -956,6 +965,12 @@ } } else { reassembleViews.add(view); + reassembleFutures.add(device.vmService.flutterReassemble( + isolateId: view.uiIsolate.id, + ).catchError((dynamic error) { + failedReassemble = true; + globals.printError('Reassembling ${view.uiIsolate.name} failed: $error'); + }, test: (dynamic error) => error is Exception)); } } } @@ -968,24 +983,10 @@ return OperationResult(OperationResult.ok.code, reloadMessage); } } - globals.printTrace('Evicting dirty assets'); - await _evictDirtyAssets(); assert(reassembleViews.isNotEmpty); + globals.printTrace('Reassembling application'); - bool failedReassemble = false; - final List<Future<void>> futures = <Future<void>>[ - for (final FlutterView view in reassembleViews) - () async { - try { - await view.uiIsolate.flutterReassemble(); - } on Exception catch (error) { - failedReassemble = true; - globals.printError('Reassembling ${view.uiIsolate.name} failed: $error'); - return; - } - }(), - ]; - final Future<void> reassembleFuture = Future.wait<void>(futures); + final Future<void> reassembleFuture = Future.wait<void>(reassembleFutures); await reassembleFuture.timeout( const Duration(seconds: 2), onTimeout: () async { @@ -1128,7 +1129,7 @@ // Caution: This log line is parsed by device lab tests. globals.printStatus( 'An Observatory debugger and profiler on $dname is available at: ' - '${device.vmService.httpAddress}', + '${device.flutterDeprecatedVmService.httpAddress}', ); } } @@ -1144,7 +1145,13 @@ continue; } for (final String assetPath in device.devFS.assetPathsToEvict) { - futures.add(device.views.first.uiIsolate.flutterEvictAsset(assetPath)); + futures.add( + device.views.first.uiIsolate.vmService + .flutterEvictAsset( + assetPath, + isolateId: device.views.first.uiIsolate.id, + ) + ); } device.devFS.assetPathsToEvict.clear(); }
diff --git a/packages/flutter_tools/lib/src/tracing.dart b/packages/flutter_tools/lib/src/tracing.dart index 5ea6cfb..aa41645 100644 --- a/packages/flutter_tools/lib/src/tracing.dart +++ b/packages/flutter_tools/lib/src/tracing.dart
@@ -54,7 +54,10 @@ }); bool done = false; for (final FlutterView view in vmService.vm.views) { - if (await view.uiIsolate.flutterAlreadyPaintedFirstUsefulFrame()) { + if (await view.uiIsolate.vmService + .flutterAlreadyPaintedFirstUsefulFrame( + isolateId: view.uiIsolate.id, + )) { done = true; break; }
diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 3de2238..75b6f92 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart
@@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:math' as math; import 'package:meta/meta.dart' show required; import 'package:vm_service/vm_service.dart' as vm_service; @@ -476,6 +475,18 @@ return _delegateService.callMethod(method, isolateId: isolateId, args: args); } + @override + Future<void> get onDone => _delegateService.onDone; + + @override + Future<vm_service.Response> callServiceExtension(String method, + {String isolateId, Map<Object, Object> args}) { + return _delegateService.callServiceExtension(method, isolateId: isolateId, args: args); + } + + @override + Future<vm_service.VM> getVM() => _delegateService.getVM(); + StreamController<ServiceEvent> _getEventController(String eventName) { StreamController<ServiceEvent> controller = _eventControllers[eventName]; if (controller == null) { @@ -890,7 +901,6 @@ _upgradeCollection(map, this); _loaded = true; - _pid = map['pid'] as int; if (map['_heapAllocatedMemoryUsage'] != null) { _heapAllocatedMemoryUsage = map['_heapAllocatedMemoryUsage'] as int; } @@ -910,10 +920,6 @@ /// The set of live views. final Map<String, FlutterView> _viewCache = <String, FlutterView>{}; - /// The pid of the VM's process. - int _pid; - int get pid => _pid; - /// The number of bytes allocated (e.g. by malloc) in the native heap. int _heapAllocatedMemoryUsage; int get heapAllocatedMemoryUsage => _heapAllocatedMemoryUsage ?? 0; @@ -1131,44 +1137,6 @@ } } -class HeapSpace extends ServiceObject { - HeapSpace._empty(ServiceObjectOwner owner) : super._empty(owner); - - int _used = 0; - int _capacity = 0; - int _external = 0; - int _collections = 0; - double _totalCollectionTimeInSeconds = 0.0; - double _averageCollectionPeriodInMillis = 0.0; - - int get used => _used; - int get capacity => _capacity; - int get external => _external; - - Duration get avgCollectionTime { - final double mcs = _totalCollectionTimeInSeconds * - Duration.microsecondsPerSecond / - math.max(_collections, 1); - return Duration(microseconds: mcs.ceil()); - } - - Duration get avgCollectionPeriod { - final double mcs = _averageCollectionPeriodInMillis * - Duration.microsecondsPerMillisecond; - return Duration(microseconds: mcs.ceil()); - } - - @override - void _update(Map<String, dynamic> map, bool mapIsRef) { - _used = map['used'] as int; - _capacity = map['capacity'] as int; - _external = map['external'] as int; - _collections = map['collections'] as int; - _totalCollectionTimeInSeconds = map['time'] as double; - _averageCollectionPeriodInMillis = map['avgCollectionPeriodMillis'] as double; - } -} - /// An isolate running inside the VM. Instances of the Isolate class are always /// canonicalized. class Isolate extends ServiceObjectOwner { @@ -1250,118 +1218,6 @@ pauseEvent = map['pauseEvent'] as ServiceEvent; } - // Flutter extension methods. - - // Invoke a flutter extension method, if the flutter extension is not - // available, returns null. - Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw( - String method, { - Map<String, dynamic> params, - }) async { - try { - return await invokeRpcRaw(method, params: params); - } on vm_service.RPCError catch (err) { - // If an application is not using the framework - if (err.code == RPCErrorCodes.kMethodNotFound) { - return null; - } - rethrow; - } - } - - Future<Map<String, dynamic>> flutterDebugDumpApp() { - return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpApp'); - } - - Future<Map<String, dynamic>> flutterDebugDumpRenderTree() { - return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpRenderTree'); - } - - Future<Map<String, dynamic>> flutterDebugDumpLayerTree() { - return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpLayerTree'); - } - - Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder() { - return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInTraversalOrder'); - } - - Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder() { - return invokeFlutterExtensionRpcRaw('ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder'); - } - - Future<Map<String, dynamic>> _flutterToggle(String name) async { - Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw('ext.flutter.$name'); - if (state != null && state.containsKey('enabled') && state['enabled'] is String) { - state = await invokeFlutterExtensionRpcRaw( - 'ext.flutter.$name', - params: <String, dynamic>{'enabled': state['enabled'] == 'true' ? 'false' : 'true'}, - ); - } - return state; - } - - Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled() => _flutterToggle('debugPaint'); - - Future<Map<String, dynamic>> flutterToggleDebugCheckElevationsEnabled() => _flutterToggle('debugCheckElevationsEnabled'); - - Future<Map<String, dynamic>> flutterTogglePerformanceOverlayOverride() => _flutterToggle('showPerformanceOverlay'); - - Future<Map<String, dynamic>> flutterToggleWidgetInspector() => _flutterToggle('inspector.show'); - - Future<Map<String, dynamic>> flutterToggleProfileWidgetBuilds() => _flutterToggle('profileWidgetBuilds'); - - Future<Map<String, dynamic>> flutterDebugAllowBanner(bool show) { - return invokeFlutterExtensionRpcRaw( - 'ext.flutter.debugAllowBanner', - params: <String, dynamic>{'enabled': show ? 'true' : 'false'}, - ); - } - - Future<Map<String, dynamic>> flutterReassemble() { - return invokeFlutterExtensionRpcRaw('ext.flutter.reassemble'); - } - - Future<Map<String, dynamic>> flutterFastReassemble(String classId) { - return invokeFlutterExtensionRpcRaw('ext.flutter.fastReassemble', params: <String, Object>{ - 'class': classId, - }); - } - - Future<bool> flutterAlreadyPaintedFirstUsefulFrame() async { - final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw('ext.flutter.didSendFirstFrameRasterizedEvent'); - // result might be null when the service extension is not initialized - return result != null && result['enabled'] == 'true'; - } - - Future<Map<String, dynamic>> uiWindowScheduleFrame() { - return invokeFlutterExtensionRpcRaw('ext.ui.window.scheduleFrame'); - } - - Future<Map<String, dynamic>> flutterEvictAsset(String assetPath) { - return invokeFlutterExtensionRpcRaw( - 'ext.flutter.evict', - params: <String, dynamic>{ - 'value': assetPath, - }, - ); - } - - // Application control extension methods. - Future<Map<String, dynamic>> flutterExit() { - return invokeFlutterExtensionRpcRaw('ext.flutter.exit'); - } - - Future<String> flutterPlatformOverride([ String platform ]) async { - final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw( - 'ext.flutter.platformOverride', - params: platform != null ? <String, dynamic>{'value': platform} : <String, String>{}, - ); - if (result != null && result['value'] is String) { - return result['value'] as String; - } - return 'unknown'; - } - @override String toString() => 'Isolate $id'; } @@ -1523,4 +1379,213 @@ ); await onRunnable; } + + Future<Map<String, dynamic>> flutterDebugDumpApp({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugDumpApp', + isolateId: isolateId, + ); + } + + Future<Map<String, dynamic>> flutterDebugDumpRenderTree({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugDumpRenderTree', + isolateId: isolateId, + ); + } + + Future<Map<String, dynamic>> flutterDebugDumpLayerTree({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugDumpLayerTree', + isolateId: isolateId, + ); + } + + Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInTraversalOrder({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder', + isolateId: isolateId, + ); + } + + Future<Map<String, dynamic>> flutterDebugDumpSemanticsTreeInInverseHitTestOrder({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder', + isolateId: isolateId, + ); + } + + Future<Map<String, dynamic>> _flutterToggle(String name, { + @required String isolateId, + }) async { + Map<String, dynamic> state = await invokeFlutterExtensionRpcRaw( + 'ext.flutter.$name', + isolateId: isolateId, + ); + if (state != null && state.containsKey('enabled') && state['enabled'] is String) { + state = await invokeFlutterExtensionRpcRaw( + 'ext.flutter.$name', + isolateId: isolateId, + args: <String, dynamic>{ + 'enabled': state['enabled'] == 'true' ? 'false' : 'true', + }, + ); + } + + return state; + } + + Future<Map<String, dynamic>> flutterToggleDebugPaintSizeEnabled({ + @required String isolateId, + }) => _flutterToggle('debugPaint', isolateId: isolateId); + + Future<Map<String, dynamic>> flutterToggleDebugCheckElevationsEnabled({ + @required String isolateId, + }) => _flutterToggle('debugCheckElevationsEnabled', isolateId: isolateId); + + Future<Map<String, dynamic>> flutterTogglePerformanceOverlayOverride({ + @required String isolateId, + }) => _flutterToggle('showPerformanceOverlay', isolateId: isolateId); + + Future<Map<String, dynamic>> flutterToggleWidgetInspector({ + @required String isolateId, + }) => _flutterToggle('inspector.show', isolateId: isolateId); + + Future<Map<String, dynamic>> flutterToggleProfileWidgetBuilds({ + @required String isolateId, + }) => _flutterToggle('profileWidgetBuilds', isolateId: isolateId); + + Future<Map<String, dynamic>> flutterDebugAllowBanner(bool show, { + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.debugAllowBanner', + isolateId: isolateId, + args: <String, dynamic>{'enabled': show ? 'true' : 'false'}, + ); + } + + Future<Map<String, dynamic>> flutterReassemble({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.reassemble', + isolateId: isolateId, + ); + } + + Future<Map<String, dynamic>> flutterFastReassemble(String classId, { + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.fastReassemble', + isolateId: isolateId, + args: <String, Object>{ + 'class': classId, + }, + ); + } + + Future<bool> flutterAlreadyPaintedFirstUsefulFrame({ + @required String isolateId, + }) async { + final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw( + 'ext.flutter.didSendFirstFrameRasterizedEvent', + isolateId: isolateId, + ); + // result might be null when the service extension is not initialized + return result != null && result['enabled'] == 'true'; + } + + Future<Map<String, dynamic>> uiWindowScheduleFrame({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.ui.window.scheduleFrame', + isolateId: isolateId, + ); + } + + Future<Map<String, dynamic>> flutterEvictAsset(String assetPath, { + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.evict', + isolateId: isolateId, + args: <String, dynamic>{ + 'value': assetPath, + }, + ); + } + + /// Exit the application by calling [exit] from `dart:io`. + /// + /// This method is only supported by certain embedders. This is + /// described by [Device.supportsFlutterExit]. + Future<Map<String, dynamic>> flutterExit({ + @required String isolateId, + }) { + return invokeFlutterExtensionRpcRaw( + 'ext.flutter.exit', + isolateId: isolateId, + ); + } + + /// Return the current platform override for the flutter view running with + /// the main isolate [isolateId]. + /// + /// If a non-null value is provided for [platform], the platform override + /// is updated with this value. + Future<String> flutterPlatformOverride({ + String platform, + @required String isolateId, + }) async { + final Map<String, dynamic> result = await invokeFlutterExtensionRpcRaw( + 'ext.flutter.platformOverride', + isolateId: isolateId, + args: platform != null + ? <String, dynamic>{'value': platform} + : <String, String>{}, + ); + if (result != null && result['value'] is String) { + return result['value'] as String; + } + return 'unknown'; + } + + /// Invoke a flutter extension method, if the flutter extension is not + /// available, returns null. + Future<Map<String, dynamic>> invokeFlutterExtensionRpcRaw( + String method, { + @required String isolateId, + Map<String, dynamic> args, + }) async { + try { + + final vm_service.Response response = await callServiceExtension( + method, + args: <String, Object>{ + 'isolateId': isolateId, + ...?args, + }, + ); + return response.json; + } on vm_service.RPCError catch (err) { + // If an application is not using the framework + if (err.code == RPCErrorCodes.kMethodNotFound) { + return null; + } + rethrow; + } + } }
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 edef46e..5bc54f0 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart
@@ -5,12 +5,17 @@ import 'dart:async'; import 'package:file/memory.dart'; +import 'package:meta/meta.dart'; +import 'package:mockito/mockito.dart'; +import 'package:process/process.dart'; +import 'package:quiver/testing/async.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; + import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/net.dart'; - import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/attach.dart'; @@ -22,10 +27,6 @@ import 'package:flutter_tools/src/resident_runner.dart'; import 'package:flutter_tools/src/run_hot.dart'; import 'package:flutter_tools/src/vmservice.dart'; -import 'package:meta/meta.dart'; -import 'package:mockito/mockito.dart'; -import 'package:process/process.dart'; -import 'package:quiver/testing/async.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import '../../src/common.dart'; @@ -142,7 +143,7 @@ final Process dartProcess = MockProcess(); final StreamController<List<int>> compilerStdoutController = StreamController<List<int>>(); - when(dartProcess.stdout).thenAnswer((_) => compilerStdoutController.stream); + when(dartProcess.stdout).thenAnswer((_) => compilerStdoutController.stream); when(dartProcess.stderr) .thenAnswer((_) => Stream<List<int>>.fromFuture(Future<List<int>>.value(const <int>[]))); @@ -787,6 +788,23 @@ when(vmService.done).thenAnswer((_) { return Future<void>.value(null); }); + when(vmService.onDone).thenAnswer((_) { + return Future<void>.value(null); + }); + when(vmService.getVM()).thenAnswer((_) async { + return vm_service.VM( + pid: 1, + architectureBits: 64, + hostCPU: '', + name: '', + isolates: <vm_service.IsolateRef>[], + isolateGroups: <vm_service.IsolateGroupRef>[], + startTime: 0, + targetCPU: '', + operatingSystem: '', + version: '', + ); + }); when(vm.refreshViews(waitForViews: anyNamed('waitForViews'))) .thenAnswer((_) => Future<void>.value(null));
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 5a33104..93f1c7a 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart
@@ -41,6 +41,7 @@ ResidentRunner residentRunner; MockDevice mockDevice; MockIsolate mockIsolate; + FakeVmServiceHost fakeVmServiceHost; setUp(() { testbed = Testbed(setup: () { @@ -89,31 +90,12 @@ 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, ]); when(mockFlutterDevice.device).thenReturn(mockDevice); when(mockFlutterView.uiIsolate).thenReturn(mockIsolate); - final MockVM mockVM = MockVM(); - when(mockVMService.vm).thenReturn(mockVM); - when(mockVM.isolates).thenReturn(<Isolate>[mockIsolate]); - 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( @@ -125,7 +107,12 @@ .thenAnswer((Invocation invocation) async { return testUri; }); - when(mockFlutterDevice.vmService).thenReturn(mockVMService); + when(mockFlutterDevice.vmService).thenAnswer((Invocation invocation) { + return fakeVmServiceHost.vmService; + }); + when(mockFlutterDevice.flutterDeprecatedVmService).thenAnswer((Invocation invocation) { + return mockVMService; + }); when(mockFlutterDevice.refreshViews()).thenAnswer((Invocation invocation) async { }); when(mockFlutterDevice.getVMs()).thenAnswer((Invocation invocation) async { }); when(mockFlutterDevice.reloadSources(any, pause: anyNamed('pause'))).thenReturn(<Future<vm_service.ReloadReport>>[ @@ -147,15 +134,13 @@ final Completer<void> result = Completer<void>.sync(); return result.future; }); - when(mockIsolate.flutterExit()).thenAnswer((Invocation invocation) { - return Future<Map<String, Object>>.value(null); - }); when(mockIsolate.reload()).thenAnswer((Invocation invocation) { return Future<ServiceObject>.value(null); }); }); test('ResidentRunner can attach to device successfully', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync(); final Completer<void> onAppStart = Completer<void>.sync(); final Future<int> result = residentRunner.attach( @@ -174,6 +159,31 @@ })); test('ResidentRunner can attach to device successfully with --fast-start', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest( + id: '1', + method: 'streamListen', + args: <String, Object>{ + 'streamId': 'Isolate', + } + ), + const FakeVmServiceRequest( + id: '2', + method: kRunInViewMethod, + args: <String, Object>{ + 'viewId': null, + 'mainScript': 'lib/main.dart.dill', + 'assetDirectory': 'build/flutter_assets', + } + ), + FakeVmServiceStreamResponse( + streamId: 'Isolate', + event: vm_service.Event( + timestamp: 0, + kind: vm_service.EventKind.kIsolateRunnable, + ) + ), + ]); when(mockDevice.supportsHotRestart).thenReturn(true); when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; @@ -189,7 +199,11 @@ mockFlutterDevice, ], stayResident: false, - debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, fastStart: true, startPaused: true), + debuggingOptions: DebuggingOptions.enabled( + BuildInfo.debug, + fastStart: true, + startPaused: true, + ), ); final Completer<DebugConnectionInfo> onConnectionInfo = Completer<DebugConnectionInfo>.sync(); final Completer<void> onAppStart = Completer<void>.sync(); @@ -209,6 +223,7 @@ })); test('ResidentRunner can handle an RPC exception from hot reload', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; }); @@ -255,6 +270,16 @@ })); test('ResidentRunner can send target platform to analytics from hot reload', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + // Not all requests are present due to existing mocks + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.reassemble', + args: <String, Object>{ + 'isolateId': null, + }, + ), + ]); when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; }); @@ -284,6 +309,32 @@ })); 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: <VmServiceExpectation>[ + const FakeVmServiceRequest( + id: '1', + method: 'streamListen', + args: <String, Object>{ + 'streamId': 'Isolate', + }, + ), + const FakeVmServiceRequest( + id: '2', + method: kRunInViewMethod, + args: <String, Object>{ + 'viewId': null, + 'mainScript': 'lib/main.dart.dill', + 'assetDirectory': 'build/flutter_assets', + }, + ), + FakeVmServiceStreamResponse( + streamId: 'Isolate', + event: vm_service.Event( + timestamp: 0, + kind: vm_service.EventKind.kIsolateRunnable, + ) + ) + ]); when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; }); @@ -314,6 +365,7 @@ })); test('ResidentRunner Can handle an RPC exception from hot restart', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); when(mockDevice.sdkNameAndVersion).thenAnswer((Invocation invocation) async { return 'Example'; }); @@ -361,6 +413,7 @@ })); test('ResidentRunner uses temp directory when there is no output dill path', () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); expect(residentRunner.artifactDirectory.path, contains('flutter_tool.')); final ResidentRunner otherRunner = HotRunner( @@ -375,6 +428,7 @@ })); test('ResidentRunner copies output dill to cache location during preExit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); residentRunner.artifactDirectory.childFile('app.dill').writeAsStringSync('hello'); await residentRunner.preExit(); final File cacheDill = globals.fs.file(globals.fs.path.join(getBuildDirectory(), 'cache.dill')); @@ -384,6 +438,7 @@ })); test('ResidentRunner handles output dill missing during preExit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.preExit(); final File cacheDill = globals.fs.file(globals.fs.path.join(getBuildDirectory(), 'cache.dill')); @@ -391,6 +446,7 @@ })); test('ResidentRunner printHelpDetails', () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); when(mockDevice.supportsHotRestart).thenReturn(true); when(mockDevice.supportsScreenshot).thenReturn(true); @@ -436,39 +492,48 @@ })); test('ResidentRunner does support CanvasKit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); expect(() => residentRunner.toggleCanvaskit(), throwsA(isA<Exception>())); })); test('ResidentRunner handles writeSkSL returning no data', () => testbed.run(() async { - when(mockVMService.callMethod( - kGetSkSLsMethod, - args: anyNamed('args'), - )).thenAnswer((Invocation invocation) async { - return vm_service.Response.parse(<String, Object>{ - 'SkSLs': <String, Object>{} - }); - }); + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest( + id: '1', + method: kGetSkSLsMethod, + args: <String, Object>{ + 'viewId': null, + }, + jsonResponse: <String, Object>{ + 'SkSLs': <String, Object>{} + } + ) + ]); await residentRunner.writeSkSL(); expect(testLogger.statusText, contains('No data was receieved')); })); test('ResidentRunner can write SkSL data to a unique file with engine revision, platform, and device name', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest( + id: '1', + method: kGetSkSLsMethod, + args: <String, Object>{ + 'viewId': null, + }, + jsonResponse: <String, Object>{ + 'SkSLs': <String, Object>{ + 'A': 'B', + } + } + ) + ]); when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async { return TargetPlatform.android_arm; }); when(mockDevice.name).thenReturn('test device'); - when(mockVMService.callMethod( - kGetSkSLsMethod, - args: anyNamed('args'), - )).thenAnswer((Invocation invocation) async { - return vm_service.Response.parse(<String, Object>{ - 'SkSLs': <String, Object>{ - 'A': 'B', - } - }); - }); await residentRunner.writeSkSL(); expect(testLogger.statusText, contains('flutter_01.sksl')); @@ -479,9 +544,28 @@ 'engineRevision': '42.2', // From FakeFlutterVersion 'data': <String, Object>{'A': 'B'} }); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('ResidentRunner can take screenshot on debug device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.debugAllowBanner', + args: <String, Object>{ + 'isolateId': null, + 'enabled': 'false', + }, + ), + const FakeVmServiceRequest( + id: '2', + method: 'ext.flutter.debugAllowBanner', + args: <String, Object>{ + 'isolateId': null, + 'enabled': 'true', + }, + ) + ]); when(mockDevice.supportsScreenshot).thenReturn(true); when(mockDevice.takeScreenshot(any)) .thenAnswer((Invocation invocation) async { @@ -491,23 +575,12 @@ await residentRunner.screenshot(mockFlutterDevice); - // disables debug banner. - verify(mockIsolate.flutterDebugAllowBanner(false)).called(1); - // Enables debug banner. - verify(mockIsolate.flutterDebugAllowBanner(true)).called(1); expect(testLogger.statusText, contains('1kB')); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); - test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws pre', () => testbed.run(() async { - when(mockDevice.supportsScreenshot).thenReturn(true); - when(mockIsolate.flutterDebugAllowBanner(false)).thenThrow(Exception()); - - await residentRunner.screenshot(mockFlutterDevice); - - expect(testLogger.errorText, contains('Error')); - })); - - test('ResidentTunner clears the screen when it should', () => testbed.run(() async { + test('ResidentRunner clears the screen when it should', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); const String message = 'This should be cleared'; expect(testLogger.statusText, equals('')); testLogger.printStatus(message); @@ -516,16 +589,72 @@ expect(testLogger.statusText, equals('')); })); - test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws post', () => testbed.run(() async { + test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner throws RpcError', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.debugAllowBanner', + args: <String, Object>{ + 'isolateId': null, + 'enabled': 'false', + }, + // Failed response, + errorCode: RPCErrorCodes.kInternalError, + ) + ]); when(mockDevice.supportsScreenshot).thenReturn(true); - when(mockIsolate.flutterDebugAllowBanner(true)).thenThrow(Exception()); + await residentRunner.screenshot(mockFlutterDevice); + expect(testLogger.errorText, contains('Error')); + expect(fakeVmServiceHost.hasRemainingExpectations, false); + })); + + test('ResidentRunner bails taking screenshot on debug device if debugAllowBanner during second request', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.debugAllowBanner', + args: <String, Object>{ + 'isolateId': null, + 'enabled': 'false', + }, + ), + const FakeVmServiceRequest( + id: '2', + method: 'ext.flutter.debugAllowBanner', + args: <String, Object>{ + 'isolateId': null, + 'enabled': 'true', + }, + // Failed response, + errorCode: RPCErrorCodes.kInternalError, + ) + ]); + when(mockDevice.supportsScreenshot).thenReturn(true); await residentRunner.screenshot(mockFlutterDevice); expect(testLogger.errorText, contains('Error')); })); test('ResidentRunner bails taking screenshot on debug device if takeScreenshot throws', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.debugAllowBanner', + args: <String, Object>{ + 'isolateId': null, + 'enabled': 'false', + }, + ), + const FakeVmServiceRequest( + id: '2', + method: 'ext.flutter.debugAllowBanner', + args: <String, Object>{ + 'isolateId': null, + 'enabled': 'true', + }, + ), + ]); when(mockDevice.supportsScreenshot).thenReturn(true); when(mockDevice.takeScreenshot(any)).thenThrow(Exception()); @@ -535,6 +664,7 @@ })); test("ResidentRunner can't take screenshot on device without support", () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); when(mockDevice.supportsScreenshot).thenReturn(false); expect(() => residentRunner.screenshot(mockFlutterDevice), @@ -542,6 +672,7 @@ })); test('ResidentRunner does not toggle banner in non-debug mode', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); residentRunner = HotRunner( <FlutterDevice>[ mockFlutterDevice, @@ -558,18 +689,17 @@ await residentRunner.screenshot(mockFlutterDevice); - // doesn't disabled debug banner. - verifyNever(mockIsolate.flutterDebugAllowBanner(false)); - // doesn't enable debug banner. - verifyNever(mockIsolate.flutterDebugAllowBanner(true)); expect(testLogger.statusText, contains('1kB')); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('FlutterDevice will not exit a paused isolate', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); final TestFlutterDevice flutterDevice = TestFlutterDevice( mockDevice, <FlutterView>[ mockFlutterView ], ); + flutterDevice.vmService = fakeVmServiceHost.vmService; final MockServiceEvent mockServiceEvent = MockServiceEvent(); when(mockServiceEvent.isPauseEvent).thenReturn(true); when(mockIsolate.pauseEvent).thenReturn(mockServiceEvent); @@ -577,15 +707,25 @@ await flutterDevice.exitApps(); - verifyNever(mockIsolate.flutterExit()); verify(mockDevice.stopApp(any)).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('FlutterDevice will exit an un-paused isolate', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest( + id: '1', + method: 'ext.flutter.exit', + args: <String, Object>{ + 'isolateId': null, + }, + ) + ]); final TestFlutterDevice flutterDevice = TestFlutterDevice( mockDevice, - <FlutterView> [mockFlutterView ], + <FlutterView> [mockFlutterView], ); + flutterDevice.vmService = fakeVmServiceHost.vmService; final MockServiceEvent mockServiceEvent = MockServiceEvent(); when(mockServiceEvent.isPauseEvent).thenReturn(false); @@ -593,17 +733,18 @@ when(mockDevice.supportsFlutterExit).thenReturn(true); await flutterDevice.exitApps(); - - verify(mockIsolate.flutterExit()).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('ResidentRunner refreshViews calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.refreshViews(); verify(mockFlutterDevice.refreshViews()).called(1); })); test('ResidentRunner debugDumpApp calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.debugDumpApp(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -611,6 +752,7 @@ })); test('ResidentRunner debugDumpRenderTree calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.debugDumpRenderTree(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -618,6 +760,7 @@ })); test('ResidentRunner debugDumpLayerTree calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.debugDumpLayerTree(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -625,6 +768,7 @@ })); test('ResidentRunner debugDumpSemanticsTreeInTraversalOrder calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.debugDumpSemanticsTreeInTraversalOrder(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -632,6 +776,7 @@ })); test('ResidentRunner debugDumpSemanticsTreeInInverseHitTestOrder calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.debugDumpSemanticsTreeInInverseHitTestOrder(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -639,6 +784,7 @@ })); test('ResidentRunner debugToggleDebugPaintSizeEnabled calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.debugToggleDebugPaintSizeEnabled(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -646,13 +792,15 @@ })); test('ResidentRunner debugToggleDebugCheckElevationsEnabled calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.debugToggleDebugCheckElevationsEnabled(); verify(mockFlutterDevice.refreshViews()).called(1); verify(mockFlutterDevice.toggleDebugCheckElevationsEnabled()).called(1); })); - test('ResidentRunner debugTogglePerformanceOverlayOverride calls flutter device', () => testbed.run(()async { + test('ResidentRunner debugTogglePerformanceOverlayOverride calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.debugTogglePerformanceOverlayOverride(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -660,6 +808,7 @@ })); test('ResidentRunner debugToggleWidgetInspector calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.debugToggleWidgetInspector(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -667,6 +816,7 @@ })); test('ResidentRunner debugToggleProfileWidgetBuilds calls flutter device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); await residentRunner.debugToggleProfileWidgetBuilds(); verify(mockFlutterDevice.refreshViews()).called(1); @@ -674,6 +824,7 @@ })); test('HotRunner writes vm service file when providing debugging option', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); residentRunner = HotRunner( <FlutterDevice>[ @@ -694,6 +845,7 @@ })); test('HotRunner unforwards device ports', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); final MockDevicePortForwarder mockPortForwarder = MockDevicePortForwarder(); when(mockDevice.portForwarder).thenReturn(mockPortForwarder); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); @@ -721,6 +873,7 @@ })); test('HotRunner handles failure to write vmservice file', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); residentRunner = HotRunner( <FlutterDevice>[ @@ -744,6 +897,7 @@ test('ColdRunner writes vm service file when providing debugging option', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); residentRunner = ColdRunner( <FlutterDevice>[ @@ -764,6 +918,7 @@ })); test('FlutterDevice uses dartdevc configuration when targeting web', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); final MockDevice mockDevice = MockDevice(); when(mockDevice.targetPlatform).thenAnswer((Invocation invocation) async { return TargetPlatform.web_javascript; @@ -792,6 +947,7 @@ })); test('connect sets up log reader', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); final MockDevice mockDevice = MockDevice(); final MockDeviceLogReader mockLogReader = MockDeviceLogReader(); when(mockDevice.getLogReader(app: anyNamed('app'))).thenReturn(mockLogReader);
diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index b018a46..30362a7 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart
@@ -5,6 +5,8 @@ import 'dart:async'; import 'dart:convert'; +import 'package:flutter_tools/src/vmservice.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; import 'package:dwds/dwds.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/io.dart'; @@ -32,11 +34,50 @@ import '../src/context.dart'; import '../src/testbed.dart'; +const List<VmServiceExpectation> kAttachLogExpectations = <VmServiceExpectation>[ + FakeVmServiceRequest( + id: '1', + method: 'streamListen', + args: <String, Object>{ + 'streamId': 'Stdout', + }, + ), + FakeVmServiceRequest( + id: '2', + method: 'streamListen', + args: <String, Object>{ + 'streamId': 'Stderr', + }, + ) +]; + +const List<VmServiceExpectation> kAttachIsolateExpectations = <VmServiceExpectation>[ + FakeVmServiceRequest( + id: '3', + method: 'streamListen', + args: <String, Object>{ + 'streamId': 'Isolate' + } + ), + FakeVmServiceRequest( + id: '4', + method: 'registerService', + args: <String, Object>{ + 'service': 'reloadSources', + 'alias': 'FlutterTools', + } + ) +]; + +const List<VmServiceExpectation> kAttachExpectations = <VmServiceExpectation>[ + ...kAttachLogExpectations, + ...kAttachIsolateExpectations, +]; + void main() { Testbed testbed; ResidentWebRunner residentWebRunner; MockDebugConnection mockDebugConnection; - MockVmService mockVmService; MockChromeDevice mockChromeDevice; MockAppConnection mockAppConnection; MockFlutterDevice mockFlutterDevice; @@ -49,11 +90,11 @@ MockWipDebugger mockWipDebugger; MockWebServerDevice mockWebServerDevice; MockDevice mockDevice; + FakeVmServiceHost fakeVmServiceHost; setUp(() { resetChromeForTesting(); mockDebugConnection = MockDebugConnection(); - mockVmService = MockVmService(); mockDevice = MockDevice(); mockAppConnection = MockAppConnection(); mockFlutterDevice = MockFlutterDevice(); @@ -110,24 +151,12 @@ )).thenAnswer((Invocation _) async { return UpdateFSReport(success: true, syncedBytes: 0)..invalidatedModules = <String>[]; }); - when(mockDebugConnection.vmService).thenReturn(mockVmService); + when(mockDebugConnection.vmService).thenAnswer((Invocation invocation) { + return fakeVmServiceHost.vmService; + }); when(mockDebugConnection.onDone).thenAnswer((Invocation invocation) { return Completer<void>().future; }); - when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) { - return const Stream<Event>.empty(); - }); - when(mockVmService.onStderrEvent).thenAnswer((Invocation _) { - return const Stream<Event>.empty(); - }); - when(mockVmService.onDebugEvent).thenAnswer((Invocation _) { - return const Stream<Event>.empty(); - }); - when(mockVmService.onIsolateEvent).thenAnswer((Invocation _) { - return Stream<Event>.fromIterable(<Event>[ - Event(kind: EventKind.kIsolateStart, timestamp: 1), - ]); - }); when(mockDebugConnection.uri).thenReturn('ws://127.0.0.1/abcd/'); when(mockFlutterDevice.devFS).thenReturn(mockWebDevFS); when(mockWebDevFS.sources).thenReturn(<Uri>[]); @@ -144,6 +173,7 @@ } test('runner with web server device does not support debugging without --start-paused', () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); when(mockFlutterDevice.device).thenReturn(WebServerDevice()); final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, @@ -158,9 +188,11 @@ when(mockFlutterDevice.device).thenReturn(MockChromeDevice()); expect(residentWebRunner.debuggingEnabled, true); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('runner with web server device supports debugging with --start-paused', () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); when(mockFlutterDevice.device).thenReturn(WebServerDevice()); final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( @@ -177,6 +209,7 @@ })); test('profile does not supportsServiceProtocol', () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); when(mockFlutterDevice.device).thenReturn(mockChromeDevice); final ResidentRunner profileResidentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, @@ -192,6 +225,7 @@ })); test('Exits on run if application does not support the web', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); globals.fs.file('pubspec.yaml').createSync(); expect(await residentWebRunner.run(), 1); @@ -199,6 +233,7 @@ })); test('Exits on run if target file does not exist', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file(globals.fs.path.join('web', 'index.html')).createSync(recursive: true); @@ -208,6 +243,7 @@ })); test('Can successfully run and connect to vmservice', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); _setupMocks(); final DelegateLogger delegateLogger = globals.logger as DelegateLogger; final BufferLogger bufferLogger = delegateLogger.delegate as BufferLogger; @@ -220,7 +256,6 @@ final DebugConnectionInfo debugConnectionInfo = await connectionInfoCompleter.future; verify(mockAppConnection.runMain()).called(1); - verify(mockVmService.registerService('reloadSources', 'FlutterTools')).called(1); verify(status.stop()).called(1); verify(pub.get( context: PubContext.pubGet, @@ -240,6 +275,7 @@ })); test('Can successfully run and disconnect with --no-resident', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); _setupMocks(); residentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, @@ -254,30 +290,28 @@ })); test('Listens to stdout and stderr streams before running main', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachLogExpectations, + FakeVmServiceStreamResponse( + streamId: 'Stdout', + event: vm_service.Event( + timestamp: 0, + kind: vm_service.EventStreams.kStdout, + bytes: base64.encode(utf8.encode('THIS MESSAGE IS IMPORTANT')) + ), + ), + FakeVmServiceStreamResponse( + streamId: 'Stderr', + event: vm_service.Event( + timestamp: 0, + kind: vm_service.EventStreams.kStderr, + bytes: base64.encode(utf8.encode('SO IS THIS')) + ), + ), + ...kAttachIsolateExpectations, + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); - final StreamController<Event> stdoutController = StreamController<Event>.broadcast(); - final StreamController<Event> stderrController = StreamController<Event>.broadcast(); - when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) { - return stdoutController.stream; - }); - when(mockVmService.onStderrEvent).thenAnswer((Invocation _) { - return stderrController.stream; - }); - when(mockAppConnection.runMain()).thenAnswer((Invocation invocation) { - stdoutController.add(Event.parse(<String, Object>{ - 'type': 'Event', - 'kind': 'WriteEvent', - 'timestamp': 1569473488296, - 'bytes': base64.encode('THIS MESSAGE IS IMPORTANT'.codeUnits), - })); - stderrController.add(Event.parse(<String, Object>{ - 'type': 'Event', - 'kind': 'WriteEvent', - 'timestamp': 1569473488296, - 'bytes': base64.encode('SO IS THIS'.codeUnits), - })); - }); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); @@ -288,6 +322,7 @@ })); test('Does not run main with --start-paused', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); residentWebRunner = DwdsWebRunnerFactory().createWebRunner( mockFlutterDevice, flutterProject: FlutterProject.current(), @@ -298,10 +333,7 @@ ) as ResidentWebRunner; _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); - final StreamController<Event> controller = StreamController<Event>.broadcast(); - when(mockVmService.onStdoutEvent).thenAnswer((Invocation _) { - return controller.stream; - }); + unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); @@ -311,6 +343,17 @@ })); test('Can hot reload after attaching', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + method: 'hotRestart', + id: '5', + args: null, + jsonResponse: <String, Object>{ + 'type': 'Success', + } + ), + ]); _setupMocks(); launchChromeInstance(mockChrome); when(mockWebDevFS.update( @@ -362,6 +405,17 @@ })); test('Can hot restart after attaching', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + method: 'hotRestart', + id: '5', + args: null, + jsonResponse: <String, Object>{ + 'type': 'Success', + } + ), + ]); _setupMocks(); launchChromeInstance(mockChrome); Uri entrypointFileUri; @@ -417,6 +471,7 @@ })); test('Can hot restart after attaching with web-server device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests :kAttachExpectations); _setupMocks(); when(mockFlutterDevice.device).thenReturn(mockWebServerDevice); when(mockWebDevFS.update( @@ -454,10 +509,13 @@ })); test('web resident runner is debuggable', () => testbed.run(() { + fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); + expect(residentWebRunner.debuggingEnabled, true); })); test('web resident runner can toggle CanvasKit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); final WebAssetServer webAssetServer = WebAssetServer(null, null, null, null, null); when(mockWebDevFS.webAssetServer).thenReturn(webAssetServer); @@ -471,6 +529,7 @@ })); test('Exits when initial compile fails', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); when(mockWebDevFS.update( mainUri: anyNamed('mainUri'), @@ -501,30 +560,34 @@ })); test('Faithfully displays stdout messages with leading/trailing spaces', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachLogExpectations, + FakeVmServiceStreamResponse( + streamId: 'Stdout', + event: vm_service.Event( + timestamp: 0, + kind: vm_service.EventStreams.kStdout, + bytes: base64.encode( + utf8.encode(' This is a message with 4 leading and trailing spaces '), + ), + ), + ), + ...kAttachIsolateExpectations, + ]); _setupMocks(); - final StreamController<Event> stdoutController = StreamController<Event>(); - when(mockVmService.onStdoutEvent).thenAnswer((Invocation invocation) { - return stdoutController.stream; - }); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - stdoutController.add(Event( - timestamp: 0, - kind: 'Stdout', - bytes: base64.encode(utf8.encode(' This is a message with 4 leading and trailing spaces '))), - ); - // Wait one event loop for the stream listener to fire. - await null; - expect(testLogger.statusText, contains(' This is a message with 4 leading and trailing spaces ')); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('Fails on compilation errors in hot restart', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: kAttachExpectations.toList()); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( @@ -559,62 +622,55 @@ })); test('Fails non-fatally on vmservice response error for hot restart', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'hotRestart', + args: null, + jsonResponse: <String, Object>{ + 'type': 'Failed', + } + ) + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callMethod('hotRestart')).thenAnswer((Invocation _) async { - return Response.parse(<String, Object>{'type': 'Failed'}); - }); + final OperationResult result = await residentWebRunner.restart(fullRestart: false); expect(result.code, 0); })); - test('Fails fatally on vmservice RpcError', () => testbed.run(() async { + test('Fails fatally on Vm Service error response', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'hotRestart', + args: null, + // Failed response, + errorCode: RPCErrorCodes.kInternalError, + ), + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callMethod('hotRestart')).thenThrow(RPCError('Something went wrong', 2, '123')); final OperationResult result = await residentWebRunner.restart(fullRestart: false); expect(result.code, 1); - expect(result.message, contains('Something went wrong')); - })); - - test('Fails fatally on vmservice WipError', () => testbed.run(() async { - _setupMocks(); - final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); - unawaited(residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - )); - await connectionInfoCompleter.future; - when(mockVmService.callMethod('hotRestart')).thenThrow(WipError(<String, String>{})); - final OperationResult result = await residentWebRunner.restart(fullRestart: false); - - expect(result.code, 1); - })); - - test('Fails fatally on vmservice Exception', () => testbed.run(() async { - _setupMocks(); - final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); - unawaited(residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - )); - await connectionInfoCompleter.future; - when(mockVmService.callMethod('hotRestart')).thenThrow(Exception('Something went wrong')); - final OperationResult result = await residentWebRunner.restart(fullRestart: false); - - expect(result.code, 1); - expect(result.message, contains('Something went wrong')); + expect(result.message, + contains(RPCErrorCodes.kInternalError.toString())); })); test('printHelp without details has web warning', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); residentWebRunner.printHelp(details: false); expect(testLogger.statusText, contains('Warning')); @@ -623,6 +679,16 @@ })); test('debugDumpApp', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugDumpApp', + args: <String, Object>{ + 'isolateId': null, + }, + ), + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( @@ -631,10 +697,20 @@ await connectionInfoCompleter.future; await residentWebRunner.debugDumpApp(); - verify(mockVmService.callServiceExtension('ext.flutter.debugDumpApp')).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugDumpLayerTree', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugDumpLayerTree', + args: <String, Object>{ + 'isolateId': null, + }, + ), + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( @@ -643,10 +719,20 @@ await connectionInfoCompleter.future; await residentWebRunner.debugDumpLayerTree(); - verify(mockVmService.callServiceExtension('ext.flutter.debugDumpLayerTree')).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugDumpRenderTree', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugDumpRenderTree', + args: <String, Object>{ + 'isolateId': null, + }, + ), + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( @@ -655,10 +741,20 @@ await connectionInfoCompleter.future; await residentWebRunner.debugDumpRenderTree(); - verify(mockVmService.callServiceExtension('ext.flutter.debugDumpRenderTree')).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugDumpSemanticsTreeInTraversalOrder', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder', + args: <String, Object>{ + 'isolateId': null, + }, + ), + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( @@ -667,113 +763,224 @@ await connectionInfoCompleter.future; await residentWebRunner.debugDumpSemanticsTreeInTraversalOrder(); - verify(mockVmService.callServiceExtension('ext.flutter.debugDumpSemanticsTreeInTraversalOrder')).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugDumpSemanticsTreeInInverseHitTestOrder', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder', + args: <String, Object>{ + 'isolateId': null, + }, + ), + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); + await connectionInfoCompleter.future; await residentWebRunner.debugDumpSemanticsTreeInInverseHitTestOrder(); - verify(mockVmService.callServiceExtension('ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder')).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugToggleDebugPaintSizeEnabled', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.debugPaint', + args: <String, Object>{ + 'isolateId': null, + }, + jsonResponse: <String, Object>{ + 'enabled': 'false' + }, + ), + const FakeVmServiceRequest( + id: '6', + method: 'ext.flutter.debugPaint', + args: <String, Object>{ + 'isolateId': null, + 'enabled': 'true', + }, + jsonResponse: <String, Object>{ + 'value': 'true' + }, + ) + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callServiceExtension('ext.flutter.debugPaint')) - .thenAnswer((Invocation _) async { - return Response.parse(<String, Object>{'enabled': false}); - }); + await residentWebRunner.debugToggleDebugPaintSizeEnabled(); - verify(mockVmService.callServiceExtension('ext.flutter.debugPaint', - args: <String, Object>{'enabled': true})).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugTogglePerformanceOverlayOverride', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.showPerformanceOverlay', + args: <String, Object>{ + 'isolateId': null, + }, + jsonResponse: <String, Object>{ + 'enabled': 'false' + }, + ), + const FakeVmServiceRequest( + id: '6', + method: 'ext.flutter.showPerformanceOverlay', + args: <String, Object>{ + 'isolateId': null, + 'enabled': 'true', + }, + jsonResponse: <String, Object>{ + 'enabled': 'true' + }, + ) + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callServiceExtension('ext.flutter.showPerformanceOverlay')) - .thenAnswer((Invocation _) async { - return Response.parse(<String, Object>{'enabled': false}); - }); await residentWebRunner.debugTogglePerformanceOverlayOverride(); - verify(mockVmService.callServiceExtension('ext.flutter.showPerformanceOverlay', - args: <String, Object>{'enabled': true})).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugToggleWidgetInspector', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.inspector.show', + args: <String, Object>{ + 'isolateId': null, + }, + jsonResponse: <String, Object>{ + 'enabled': 'false' + }, + ), + const FakeVmServiceRequest( + id: '6', + method: 'ext.flutter.inspector.show', + args: <String, Object>{ + 'isolateId': null, + 'enabled': 'true', + }, + jsonResponse: <String, Object>{ + 'enabled': 'true' + }, + ) + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callServiceExtension('ext.flutter.debugToggleWidgetInspector')) - .thenAnswer((Invocation _) async { - return Response.parse(<String, Object>{'enabled': false}); - }); await residentWebRunner.debugToggleWidgetInspector(); - verify(mockVmService.callServiceExtension('ext.flutter.debugToggleWidgetInspector', - args: <String, Object>{'enabled': true})).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugToggleProfileWidgetBuilds', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.profileWidgetBuilds', + args: <String, Object>{ + 'isolateId': null, + }, + jsonResponse: <String, Object>{ + 'enabled': 'false' + }, + ), + const FakeVmServiceRequest( + id: '6', + method: 'ext.flutter.profileWidgetBuilds', + args: <String, Object>{ + 'isolateId': null, + 'enabled': 'true', + }, + jsonResponse: <String, Object>{ + 'enabled': 'true' + }, + ) + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callServiceExtension('ext.flutter.profileWidgetBuilds')) - .thenAnswer((Invocation _) async { - return Response.parse(<String, Object>{'enabled': false}); - }); await residentWebRunner.debugToggleProfileWidgetBuilds(); - verify(mockVmService.callServiceExtension('ext.flutter.profileWidgetBuilds', - args: <String, Object>{'enabled': true})).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('debugTogglePlatform', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + const FakeVmServiceRequest( + id: '5', + method: 'ext.flutter.platformOverride', + args: <String, Object>{ + 'isolateId': null, + }, + jsonResponse: <String, Object>{ + 'value': 'iOS' + }, + ), + const FakeVmServiceRequest( + id: '6', + method: 'ext.flutter.platformOverride', + args: <String, Object>{ + 'isolateId': null, + 'value': 'fuchsia', + }, + jsonResponse: <String, Object>{ + 'value': 'fuchsia' + }, + ), + ]); _setupMocks(); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); unawaited(residentWebRunner.run( connectionInfoCompleter: connectionInfoCompleter, )); await connectionInfoCompleter.future; - when(mockVmService.callServiceExtension('ext.flutter.platformOverride')) - .thenAnswer((Invocation _) async { - return Response.parse(<String, Object>{'value': 'iOS'}); - }); await residentWebRunner.debugTogglePlatform(); - expect(testLogger.statusText, contains('Switched operating system to fuchsia')); - verify(mockVmService.callServiceExtension('ext.flutter.platformOverride', - args: <String, Object>{'value': 'fuchsia'})).called(1); + expect(testLogger.statusText, + contains('Switched operating system to fuchsia')); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('cleanup of resources is safe to call multiple times', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + ]); _setupMocks(); bool debugClosed = false; when(mockDevice.stopApp(any)).thenAnswer((Invocation invocation) async { @@ -793,9 +1000,13 @@ await residentWebRunner.exit(); verifyNever(mockDebugConnection.close()); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('cleans up Chrome if tab is closed', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + ]); _setupMocks(); final Completer<void> onDone = Completer<void>(); when(mockDebugConnection.onDone).thenAnswer((Invocation invocation) { @@ -809,9 +1020,13 @@ onDone.complete(); await result; + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('Prints target and device name on run', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachExpectations, + ]); _setupMocks(); when(mockDevice.name).thenReturn('Chromez'); final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); @@ -820,10 +1035,32 @@ )); await connectionInfoCompleter.future; - expect(testLogger.statusText, contains('Launching ${globals.fs.path.join('lib', 'main.dart')} on Chromez in debug mode')); + expect(testLogger.statusText, contains( + 'Launching ${globals.fs.path.join('lib', 'main.dart')} on ' + 'Chromez in debug mode', + )); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('Sends launched app.webLaunchUrl event for Chrome device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[ + ...kAttachLogExpectations, + const FakeVmServiceRequest( + id: '3', + method: 'streamListen', + args: <String, Object>{ + 'streamId': 'Isolate' + } + ), + const FakeVmServiceRequest( + id: '4', + method: 'registerService', + args: <String, Object>{ + 'service': 'reloadSources', + 'alias': 'FlutterTools', + } + ) + ]); _setupMocks(); when(mockFlutterDevice.device).thenReturn(ChromeDevice()); when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async { @@ -870,12 +1107,14 @@ }, }, ))); + expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ Logger: () => DelegateLogger(BufferLogger.test()), ChromeLauncher: () => MockChromeLauncher(), })); test('Sends unlaunched app.webLaunchUrl event for Web Server device', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); when(mockFlutterDevice.device).thenReturn(WebServerDevice()); when(mockWebDevFS.create()).thenAnswer((Invocation invocation) async { @@ -910,106 +1149,65 @@ }, }, ))); + expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ Logger: () => DelegateLogger(BufferLogger.test()) })); test('Successfully turns WebSocketException into ToolExit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); - final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); - final Completer<void> unhandledErrorCompleter = Completer<void>(); - when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw const WebSocketException(); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - final Future<void> expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); + when(mockWebDevFS.connect(any)) + .thenThrow(const WebSocketException()); - unhandledErrorCompleter.complete(); - await expectation; + await expectLater(() => residentWebRunner.run(), throwsToolExit()); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('Successfully turns AppConnectionException into ToolExit', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); - final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); - final Completer<void> unhandledErrorCompleter = Completer<void>(); - when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw AppConnectionException('Could not connect to application with appInstanceId: c0ae0750-ee91-11e9-cea6-35d95a968356'); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - final Future<void> expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); + when(mockWebDevFS.connect(any)) + .thenThrow(AppConnectionException('')); - unhandledErrorCompleter.complete(); - await expectation; + await expectLater(() => residentWebRunner.run(), throwsToolExit()); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); test('Successfully turns ChromeDebugError into ToolExit', () => testbed.run(() async { - _setupMocks(); - final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); - final Completer<void> unhandledErrorCompleter = Completer<void>(); - when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw ChromeDebugException(<String, dynamic>{}); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - - final Future<void> expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsToolExit()); - - unhandledErrorCompleter.complete(); - await expectation; - })); - - test('Rethrows Exception type', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); - final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); - final Completer<void> unhandledErrorCompleter = Completer<void>(); - when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw Exception('Something went wrong'); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - final Future<void> expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsException); + when(mockWebDevFS.connect(any)) + .thenThrow(ChromeDebugException(<String, dynamic>{})); - unhandledErrorCompleter.complete(); - await expectation; + await expectLater(() => residentWebRunner.run(), throwsToolExit()); + expect(fakeVmServiceHost.hasRemainingExpectations, false); })); - test('Rethrows unknown exception type from web tooling', () => testbed.run(() async { + + test('Rethrows unknown Exception type from dwds', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); + _setupMocks(); + when(mockWebDevFS.connect(any)).thenThrow(Exception()); + + await expectLater(() => residentWebRunner.run(), throwsException); + expect(fakeVmServiceHost.hasRemainingExpectations, false); + })); + + test('Rethrows unknown Error type from dwds tooling', () => testbed.run(() async { + fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]); _setupMocks(); final DelegateLogger delegateLogger = globals.logger as DelegateLogger; final MockStatus mockStatus = MockStatus(); delegateLogger.status = mockStatus; - final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>(); - final Completer<void> unhandledErrorCompleter = Completer<void>(); - when(mockWebDevFS.connect(any)).thenAnswer((Invocation _) async { - unawaited(unhandledErrorCompleter.future.then((void value) { - throw StateError('Something went wrong'); - })); - return ConnectionResult(mockAppConnection, mockDebugConnection); - }); - final Future<void> expectation = expectLater(() => residentWebRunner.run( - connectionInfoCompleter: connectionInfoCompleter, - ), throwsStateError); + when(mockWebDevFS.connect(any)).thenThrow(StateError('')); - unhandledErrorCompleter.complete(); - await expectation; + await expectLater(() => residentWebRunner.run(), throwsStateError); verify(mockStatus.stop()).called(1); + expect(fakeVmServiceHost.hasRemainingExpectations, false); }, overrides: <Type, Generator>{ Logger: () => DelegateLogger(BufferLogger( terminal: AnsiTerminal(
diff --git a/packages/flutter_tools/test/general.shard/vmservice_test.dart b/packages/flutter_tools/test/general.shard/vmservice_test.dart index 9439e85..88db3c1 100644 --- a/packages/flutter_tools/test/general.shard/vmservice_test.dart +++ b/packages/flutter_tools/test/general.shard/vmservice_test.dart
@@ -6,7 +6,6 @@ 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'; @@ -285,10 +284,10 @@ testWithoutContext('runInView forwards arguments correctly', () async { final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost( requests: <VmServiceExpectation>[ - const FakeVmServiceRequest(method: 'streamListen', id: '1', params: <String, Object>{ + const FakeVmServiceRequest(method: 'streamListen', id: '1', args: <String, Object>{ 'streamId': 'Isolate' }), - const FakeVmServiceRequest(method: kRunInViewMethod, id: '2', params: <String, Object>{ + const FakeVmServiceRequest(method: kRunInViewMethod, id: '2', args: <String, Object>{ 'viewId': '1234', 'mainScript': 'main.dart', 'assetDirectory': 'flutter_assets/', @@ -312,95 +311,6 @@ }); } -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 {} class MockVMService extends Mock implements vm_service.VmService {} class MockFlutterVersion extends Mock implements FlutterVersion {
diff --git a/packages/flutter_tools/test/src/common.dart b/packages/flutter_tools/test/src/common.dart index ecf413d..298b5e7 100644 --- a/packages/flutter_tools/test/src/common.dart +++ b/packages/flutter_tools/test/src/common.dart
@@ -5,10 +5,12 @@ import 'dart:async'; import 'package:args/command_runner.dart'; +import 'package:flutter_tools/src/convert.dart'; +import 'package:vm_service/vm_service.dart' as vm_service; + import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/context.dart'; import 'package:flutter_tools/src/base/file_system.dart'; - import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/commands/create.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; @@ -217,3 +219,109 @@ return body(); } } + +/// A fake implementation of a vm_service that mocks the JSON-RPC request +/// and response structure. +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(request, isA<Map<String, Object>>() + .having((Map<String, Object> request) => request['method'], 'method', fakeRequest.method) + .having((Map<String, Object> request) => request['id'], 'id', fakeRequest.id) + .having((Map<String, Object> request) => request['params'], 'args', fakeRequest.args) + ); + if (fakeRequest.errorCode == null) { + _input.add(json.encode(<String, Object>{ + 'jsonrpc': '2.0', + 'id': fakeRequest.id, + 'result': fakeRequest.jsonResponse ?? <String, Object>{'type': 'Success'}, + })); + } else { + _input.add(json.encode(<String, Object>{ + 'jsonrpc': '2.0', + 'id': fakeRequest.id, + 'error': <String, Object>{ + 'code': fakeRequest.errorCode, + } + })); + } + _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.args, + this.jsonResponse, + this.errorCode, + }); + + final String method; + final String id; + + /// If non-null, the error code for a [vm_service.RPCError] in place of a + /// standard response. + final int errorCode; + final Map<String, Object> args; + 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; +}