Unverified Commit 9202e547 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] move service extensions off of deprecated vm service (#55012)

parent 96a3b2ae
......@@ -34,6 +34,7 @@ import '../project.dart';
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 @@ abstract class ResidentWebRunner extends ResidentRunner {
@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 @@ abstract class ResidentWebRunner extends ResidentRunner {
@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 @@ abstract class ResidentWebRunner extends ResidentRunner {
@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 @@ abstract class ResidentWebRunner extends ResidentRunner {
@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 @@ abstract class ResidentWebRunner extends ResidentRunner {
@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 @@ abstract class ResidentWebRunner extends ResidentRunner {
@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 @@ abstract class ResidentWebRunner extends ResidentRunner {
@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 @@ abstract class ResidentWebRunner extends ResidentRunner {
@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 @@ abstract class ResidentWebRunner extends ResidentRunner {
@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 @@ abstract class ResidentWebRunner extends ResidentRunner {
@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 @@ abstract class ResidentWebRunner extends ResidentRunner {
@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 @@ class _ResidentWebRunner extends ResidentWebRunner {
_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 @@ class _ResidentWebRunner extends ResidentWebRunner {
// 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;
......
......@@ -6,6 +6,7 @@ import 'dart:async';
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 'linux/linux_device.dart';
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 @@ abstract class DeviceLogReader {
/// 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 @@ class NoOpDeviceLogReader implements DeviceLogReader {
int appPid;
@override
VMService connectedVMService;
vm_service.VmService connectedVMService;
@override
Stream<String> get logLines => const Stream<String>.empty();
......
......@@ -24,7 +24,6 @@ import '../macos/xcode.dart';
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 @@ class IOSDeviceLogReader extends DeviceLogReader {
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;
}
......
......@@ -152,7 +152,7 @@ class FlutterDevice {
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 @@ class FlutterDevice {
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 @@ class FlutterDevice {
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 @@ class FlutterDevice {
}) {
// One devFS per device. Shared by all running instances.
devFS = DevFS(
vmService,
flutterDeprecatedVmService,
fsName,
rootDirectory,
osUtils: globals.os,
......@@ -301,7 +307,7 @@ class FlutterDevice {
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 @@ class FlutterDevice {
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 @@ class FlutterDevice {
}
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 @@ abstract class ResidentRunner {
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 @@ abstract class ResidentRunner {
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 @@ abstract class ResidentRunner {
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 @@ abstract class ResidentRunner {
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 @@ abstract class ResidentRunner {
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 @@ abstract class ResidentRunner {
// 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 @@ abstract class ResidentRunner {
<String, dynamic>{
'reuseWindows': true,
},
flutterDevices.first.vmService.httpAddress,
flutterDevices.first.flutterDeprecatedVmService.httpAddress,
'http://${_devtoolsServer.address.host}:${_devtoolsServer.port}',
false, // headless mode,
false, // machine mode
......
......@@ -83,8 +83,8 @@ class ColdRunner extends ResidentRunner {
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 @@ class ColdRunner extends ResidentRunner {
if (device.vmService != null) {
globals.printStatus('Tracing startup on ${device.device.name}.');
await downloadStartupTrace(
device.vmService,
device.flutterDeprecatedVmService,
awaitFirstFrame: awaitFirstFrameWhenTracing,
);
}
......@@ -197,7 +197,7 @@ class ColdRunner extends ResidentRunner {
// 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}',
);
}
}
......
......@@ -205,7 +205,10 @@ class HotRunner extends ResidentRunner {
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 @@ class HotRunner extends ResidentRunner {
// 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 @@ class HotRunner extends ResidentRunner {
// 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 @@ class HotRunner extends ResidentRunner {
}
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 @@ class HotRunner extends ResidentRunner {
}
} 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 @@ class HotRunner extends ResidentRunner {
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 @@ class HotRunner extends ResidentRunner {
// 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 @@ class HotRunner extends ResidentRunner {
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();
}
......
......@@ -54,7 +54,10 @@ class Tracing {
});
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;
}
......
......@@ -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 @@ class VMService implements vm_service.VmService {
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 @@ class VM extends ServiceObjectOwner {
_upgradeCollection(map, this);
_loaded = true;
_pid = map['pid'] as int;
if (map['_heapAllocatedMemoryUsage'] != null) {
_heapAllocatedMemoryUsage = map['_heapAllocatedMemoryUsage'] as int;
}
......@@ -910,10 +920,6 @@ class VM extends ServiceObjectOwner {
/// 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 VM extends ServiceObjectOwner {
}
}
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 @@ class Isolate extends ServiceObjectOwner {
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 @@ extension FlutterVmService on vm_service.VmService {
);
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;
}
}
}
......@@ -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/project.dart';
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 @@ void main() {
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 @@ VMServiceConnector getFakeVmServiceFactory({
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));
......
......@@ -41,6 +41,7 @@ void main() {
ResidentRunner residentRunner;
MockDevice mockDevice;
MockIsolate mockIsolate;
FakeVmServiceHost fakeVmServiceHost;
setUp(() {
testbed = Testbed(setup: () {
......@@ -89,31 +90,12 @@ void main() {
invalidatedSourcesCount: 0,
);
});
// TODO(jonahwilliams): replace mock with FakeVmServiceHost once all methods
// are moved to real vm service.
when(mockFlutterDevice.devFS).thenReturn(mockDevFS);
when(mockFlutterDevice.views).thenReturn(<FlutterView>[
mockFlutterView,
]);
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 @@ void main() {
.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 @@ void main() {
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 @@ void main() {
}));
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 @@ void main() {
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
test('ResidentRunner can send target platform to analytics from full restart', () => testbed.run(() async {
// Not all requests are present due to existing mocks
fakeVmServiceHost = FakeVmServiceHost(requests: <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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
test('ResidentRunner printHelpDetails', () => testbed.run(() {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
when(mockDevice.supportsHotRestart).thenReturn(true);
when(mockDevice.supportsScreenshot).thenReturn(true);
......@@ -436,39 +492,48 @@ void main() {
}));
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 @@ void main() {
'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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
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 @@ void main() {
}));
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 @@ void main() {
}));
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);
......
......@@ -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/common.dart';
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 @@ void main() {
MockWipDebugger mockWipDebugger;
MockWebServerDevice mockWebServerDevice;
MockDevice mockDevice;
FakeVmServiceHost fakeVmServiceHost;
setUp(() {
resetChromeForTesting();
mockDebugConnection = MockDebugConnection();
mockVmService = MockVmService();
mockDevice = MockDevice();
mockAppConnection = MockAppConnection();
mockFlutterDevice = MockFlutterDevice();
......@@ -110,24 +151,12 @@ void main() {
)).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 @@ void main() {
}
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 @@ void main() {
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
) 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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
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 @@ void main() {
}));
test('Exits when initial compile fails', () => testbed.run(() async {
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
_setupMocks();
when(mockWebDevFS.update(
mainUri: anyNamed('mainUri'),
......@@ -501,30 +560,34 @@ void main() {
}));
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 @@ void main() {
}));
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 {
_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);
expect(result.code, 0);
}));
test('Fails fatally on vmservice Exception', () => 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(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 @@ void main() {
}));
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
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 @@ void main() {
));
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 @@ void main() {
},
},
)));
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 @@ void main() {
},
},
)));
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);
});
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
_setupMocks();
final Future<void> expectation = expectLater(() => residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
), throwsToolExit());
when(mockWebDevFS.connect(any))
.thenThrow(ChromeDebugException(<String, dynamic>{}));
unhandledErrorCompleter.complete();
await expectation;
await expectLater(() => residentWebRunner.run(), throwsToolExit());
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
test('Rethrows Exception type', () => testbed.run(() async {
test('Rethrows unknown Exception type from dwds', () => 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);
});
when(mockWebDevFS.connect(any)).thenThrow(Exception());
final Future<void> expectation = expectLater(() => residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
), throwsException);
unhandledErrorCompleter.complete();
await expectation;
await expectLater(() => residentWebRunner.run(), throwsException);
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}));
test('Rethrows unknown exception type from web tooling', () => testbed.run(() async {
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(
......
......@@ -6,7 +6,6 @@ import 'dart:async';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:meta/meta.dart';
import 'package:vm_service/vm_service.dart' as vm_service;
import 'package:mockito/mockito.dart';
import 'package:flutter_tools/src/base/logger.dart';
......@@ -285,10 +284,10 @@ void main() {
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 @@ void main() {
});
}
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 {
......
......@@ -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 @@ class NoContext implements AppContext {
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;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment