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

[flutter_tools] Remove web specific vm_service handlers, move handler tests to...

[flutter_tools] Remove web specific vm_service handlers, move handler tests to single location (#80440)
parent bccacfed
......@@ -29,7 +29,6 @@ import '../cache.dart';
import '../dart/language_version.dart';
import '../devfs.dart';
import '../device.dart';
import '../features.dart';
import '../flutter_plugins.dart';
import '../platform_plugins.dart';
import '../plugins.dart';
......@@ -60,7 +59,6 @@ class DwdsWebRunnerFactory extends WebRunnerFactory {
@required FileSystem fileSystem,
@required SystemClock systemClock,
@required Usage usage,
@required FeatureFlags featureFlags,
bool machine = false,
}) {
return ResidentWebRunner(
......@@ -120,6 +118,12 @@ class ResidentWebRunner extends ResidentRunner {
final Usage _usage;
final UrlTunneller _urlTunneller;
@override
Logger get logger => _logger;
@override
FileSystem get fileSystem => _fileSystem;
FlutterDevice get device => flutterDevices.first;
final FlutterProject flutterProject;
DateTime firstBuildTime;
......@@ -162,9 +166,6 @@ class ResidentWebRunner extends ResidentRunner {
}
FlutterVmService _instance;
@override
bool get supportsRestart => true;
@override
Future<void> cleanupAfterSignal() async {
await _cleanup();
......@@ -219,242 +220,12 @@ class ResidentWebRunner extends ResidentRunner {
printDebuggerList();
}
@override
Future<bool> debugDumpApp() async {
if (!supportsServiceProtocol || _vmService == null) {
return false;
}
try {
final String data = await _vmService
.flutterDebugDumpApp(
isolateId: null,
);
_logger.printStatus(data);
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<bool> debugDumpRenderTree() async {
if (!supportsServiceProtocol || _vmService == null) {
return false;
}
try {
final String data = await _vmService
.flutterDebugDumpRenderTree(
isolateId: null,
);
_logger.printStatus(data);
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<bool> debugDumpLayerTree() async {
if (!supportsServiceProtocol || _vmService == null) {
return false;
}
try {
final String data = await _vmService
.flutterDebugDumpLayerTree(
isolateId: null,
);
_logger.printStatus(data);
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<bool> debugDumpSemanticsTreeInTraversalOrder() async {
if (!supportsServiceProtocol) {
return false;
}
try {
await _vmService
?.flutterDebugDumpSemanticsTreeInTraversalOrder(
isolateId: null,
);
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<bool> debugTogglePlatform() async {
if (!supportsServiceProtocol) {
return false;
}
try {
final String currentPlatform = await _vmService
?.flutterPlatformOverride(
isolateId: null,
);
final String platform = nextPlatform(currentPlatform);
await _vmService
?.flutterPlatformOverride(
platform: platform,
isolateId: null,
);
_logger.printStatus('Switched operating system to $platform');
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<bool> debugToggleBrightness() async {
if (!supportsServiceProtocol) {
return false;
}
try {
final Brightness currentBrightness = await _vmService
?.flutterBrightnessOverride(
isolateId: null,
);
Brightness next;
if (currentBrightness == Brightness.light) {
next = Brightness.dark;
} else if (currentBrightness == Brightness.dark) {
next = Brightness.light;
}
next = await _vmService
?.flutterBrightnessOverride(
brightness: next,
isolateId: null,
);
_logger.printStatus('Changed brightness to $next.');
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<void> stopEchoingDeviceLog() async {
// Do nothing for ResidentWebRunner
await device.stopEchoingDeviceLog();
}
@override
Future<bool> debugDumpSemanticsTreeInInverseHitTestOrder() async {
if (!supportsServiceProtocol) {
return false;
}
try {
await _vmService
?.flutterDebugDumpSemanticsTreeInInverseHitTestOrder(
isolateId: null,
);
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<bool> debugToggleDebugPaintSizeEnabled() async {
if (!supportsServiceProtocol) {
return false;
}
try {
await _vmService
?.flutterToggleDebugPaintSizeEnabled(
isolateId: null,
);
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<bool> debugToggleDebugCheckElevationsEnabled() async {
if (!supportsServiceProtocol) {
return false;
}
try {
await _vmService
?.flutterToggleDebugCheckElevationsEnabled(
isolateId: null,
);
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<bool> debugTogglePerformanceOverlayOverride() async {
if (!supportsServiceProtocol) {
return false;
}
try {
await _vmService
?.flutterTogglePerformanceOverlayOverride(
isolateId: null,
);
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<bool> debugToggleWidgetInspector() async {
if (!supportsServiceProtocol) {
return false;
}
try {
await _vmService
?.flutterToggleWidgetInspector(
isolateId: null,
);
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<bool> debugToggleInvertOversizedImages() async {
if (!supportsServiceProtocol) {
return false;
}
try {
await _vmService
?.flutterToggleInvertOversizedImages(
isolateId: null,
);
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<bool> debugToggleProfileWidgetBuilds() async {
if (!supportsServiceProtocol) {
return false;
}
try {
await _vmService
?.flutterToggleProfileWidgetBuilds(
isolateId: null,
);
} on vmservice.RPCError {
// do nothing.
}
return true;
}
@override
Future<int> run({
Completer<DebugConnectionInfo> connectionInfoCompleter,
......
......@@ -172,175 +172,6 @@ void main() {
fileSystem = MemoryFileSystem.test();
});
testUsingContext('Does not hot restart when device does not support it', () async {
fileSystem.file('.packages')
..createSync(recursive: true)
..writeAsStringSync('\n');
// Setup mocks
final MockDevice mockDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(false);
when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester);
// Trigger hot restart.
final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(mockDevice, generator: residentCompiler, buildInfo: BuildInfo.debug)..devFS = mockDevFs,
];
final OperationResult result = await HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
).restart(fullRestart: true);
// Expect hot restart failed.
expect(result.isOk, false);
expect(result.message, 'hotRestart not supported');
}, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Does not hot restart when one of many devices does not support it', () async {
fileSystem.file('.packages')
..createSync(recursive: true)
..writeAsStringSync('\n');
// Setup mocks
final MockDevice mockDevice = MockDevice();
final MockDevice mockHotDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(false);
when(mockHotDevice.supportsHotReload).thenReturn(true);
when(mockHotDevice.supportsHotRestart).thenReturn(true);
// Trigger hot restart.
final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(mockDevice, generator: residentCompiler, buildInfo: BuildInfo.debug)..devFS = mockDevFs,
FlutterDevice(mockHotDevice, generator: residentCompiler, buildInfo: BuildInfo.debug)..devFS = mockDevFs,
];
final OperationResult result = await HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
).restart(fullRestart: true);
// Expect hot restart failed.
expect(result.isOk, false);
expect(result.message, 'hotRestart not supported');
}, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('Does hot restarts when all devices support it', () async {
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson()
),
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson()
),
listViews,
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
}
),
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
}
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
)
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
)
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'main.dart.dill',
'assetDirectory': 'build/flutter_assets',
}
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'main.dart.dill',
'assetDirectory': 'build/flutter_assets',
}
),
]);
// Setup mocks
final MockDevice mockDevice = MockDevice();
final MockDevice mockHotDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(true);
when(mockHotDevice.supportsHotReload).thenReturn(true);
when(mockHotDevice.supportsHotRestart).thenReturn(true);
// Trigger a restart.
final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(mockDevice, generator: residentCompiler, buildInfo: BuildInfo.debug)
..vmService = fakeVmServiceHost.vmService
..devFS = mockDevFs,
FlutterDevice(mockHotDevice, generator: residentCompiler, buildInfo: BuildInfo.debug)
..vmService = fakeVmServiceHost.vmService
..devFS = mockDevFs,
];
final HotRunner hotRunner = HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
final OperationResult result = await hotRunner.restart(fullRestart: true);
// Expect hot restart was successful.
expect(hotRunner.uri, mockDevFs.baseUri);
expect(result.isOk, true);
expect(result.message, isNot('hotRestart not supported'));
}, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'),
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('setup function fails', () async {
fileSystem.file('.packages')
..createSync(recursive: true)
......@@ -368,76 +199,6 @@ void main() {
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('hot restart supported', () async {
fileSystem.file('.packages')
..createSync(recursive: true)
..writeAsStringSync('\n');
// Setup mocks
final FakeVmServiceHost fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
listViews,
FakeVmServiceRequest(
method: 'getIsolate',
args: <String, Object>{
'isolateId': fakeUnpausedIsolate.id,
},
jsonResponse: fakeUnpausedIsolate.toJson(),
),
FakeVmServiceRequest(
method: 'getVM',
jsonResponse: vm_service.VM.parse(<String, Object>{}).toJson(),
),
listViews,
const FakeVmServiceRequest(
method: 'streamListen',
args: <String, Object>{
'streamId': 'Isolate',
}
),
FakeVmServiceRequest(
method: kRunInViewMethod,
args: <String, Object>{
'viewId': fakeFlutterView.id,
'mainScript': 'main.dart.dill',
'assetDirectory': 'build/flutter_assets',
}
),
FakeVmServiceStreamResponse(
streamId: 'Isolate',
event: vm_service.Event(
timestamp: 0,
kind: vm_service.EventKind.kIsolateRunnable,
)
),
]);
final MockDevice mockDevice = MockDevice();
when(mockDevice.supportsHotReload).thenReturn(true);
when(mockDevice.supportsHotRestart).thenReturn(true);
when(mockDevice.targetPlatform).thenAnswer((Invocation _) async => TargetPlatform.tester);
// Trigger hot restart.
final List<FlutterDevice> devices = <FlutterDevice>[
FlutterDevice(mockDevice, generator: residentCompiler, buildInfo: BuildInfo.debug)
..vmService = fakeVmServiceHost.vmService
..devFS = mockDevFs,
];
final HotRunner hotRunner = HotRunner(
devices,
debuggingOptions: DebuggingOptions.disabled(BuildInfo.debug),
target: 'main.dart',
devtoolsHandler: createNoOpHandler,
);
final OperationResult result = await hotRunner.restart(fullRestart: true);
// Expect hot restart successful.
expect(hotRunner.uri, mockDevFs.baseUri);
expect(result.isOk, true);
expect(result.message, isNot('setupHotRestart failed'));
}, overrides: <Type, Generator>{
HotRunnerConfig: () => TestHotRunnerConfig(successfulSetup: true),
Artifacts: () => Artifacts.test(),
FileSystem: () => fileSystem,
Platform: () => FakePlatform(operatingSystem: 'linux'),
ProcessManager: () => FakeProcessManager.any(),
});
group('shutdown hook tests', () {
TestHotRunnerConfig shutdownTestingConfig;
......
......@@ -228,7 +228,6 @@ void main() {
fileSystem: fileSystem,
logger: BufferLogger.test(),
usage: globals.flutterUsage,
systemClock: globals.systemClock,
);
......@@ -908,412 +907,6 @@ void main() {
ProcessManager: () => processManager,
});
testUsingContext('debugDumpApp', () async {
final BufferLogger logger = BufferLogger.test();
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice, logger: logger);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{
'isolateId': null,
},
),
]);
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await residentWebRunner.debugDumpApp();
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debugDumpLayerTree', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpLayerTree',
args: <String, Object>{
'isolateId': null,
},
),
]);
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await residentWebRunner.debugDumpLayerTree();
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debugDumpRenderTree', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{
'isolateId': null,
},
),
]);
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await residentWebRunner.debugDumpRenderTree();
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debugDumpSemanticsTreeInTraversalOrder', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
args: <String, Object>{
'isolateId': null,
},
),
]);
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await residentWebRunner.debugDumpSemanticsTreeInTraversalOrder();
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debugDumpSemanticsTreeInInverseHitTestOrder', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
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();
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debugToggleDebugPaintSizeEnabled', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'ext.flutter.debugPaint',
args: <String, Object>{
'isolateId': null,
},
jsonResponse: <String, Object>{
'enabled': 'false'
},
),
const FakeVmServiceRequest(
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;
await residentWebRunner.debugToggleDebugPaintSizeEnabled();
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debugTogglePerformanceOverlayOverride', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'ext.flutter.showPerformanceOverlay',
args: <String, Object>{
'isolateId': null,
},
jsonResponse: <String, Object>{
'enabled': 'false'
},
),
const FakeVmServiceRequest(
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;
await residentWebRunner.debugTogglePerformanceOverlayOverride();
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debugToggleInvertOversizedImagesOverride', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'ext.flutter.invertOversizedImages',
args: <String, Object>{
'isolateId': null,
},
jsonResponse: <String, Object>{
'enabled': 'false'
},
),
const FakeVmServiceRequest(
method: 'ext.flutter.invertOversizedImages',
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;
await residentWebRunner.debugToggleInvertOversizedImages();
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debugToggleWidgetInspector', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'ext.flutter.inspector.show',
args: <String, Object>{
'isolateId': null,
},
jsonResponse: <String, Object>{
'enabled': 'false'
},
),
const FakeVmServiceRequest(
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;
await residentWebRunner.debugToggleWidgetInspector();
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debugToggleProfileWidgetBuilds', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{
'isolateId': null,
},
jsonResponse: <String, Object>{
'enabled': 'false'
},
),
const FakeVmServiceRequest(
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;
await residentWebRunner.debugToggleProfileWidgetBuilds();
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debugTogglePlatform', () async {
final BufferLogger logger = BufferLogger.test();
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice, logger: logger);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': null,
},
jsonResponse: <String, Object>{
'value': 'iOS'
},
),
const FakeVmServiceRequest(
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;
await residentWebRunner.debugTogglePlatform();
expect(logger.statusText,
contains('Switched operating system to fuchsia'));
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('debugToggleBrightness', () async {
final BufferLogger logger = BufferLogger.test();
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice, logger: logger);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
...kAttachExpectations,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{
'isolateId': null,
},
jsonResponse: <String, Object>{
'value': 'Brightness.light'
},
),
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{
'isolateId': null,
'value': 'Brightness.dark',
},
jsonResponse: <String, Object>{
'value': 'Brightness.dark'
},
),
]);
_setupMocks();
final Completer<DebugConnectionInfo> connectionInfoCompleter = Completer<DebugConnectionInfo>();
unawaited(residentWebRunner.run(
connectionInfoCompleter: connectionInfoCompleter,
));
await connectionInfoCompleter.future;
await residentWebRunner.debugToggleBrightness();
expect(logger.statusText,
contains('Changed brightness to Brightness.dark.'));
expect(fakeVmServiceHost.hasRemainingExpectations, false);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => processManager,
});
testUsingContext('cleanup of resources is safe to call multiple times', () async {
final ResidentRunner residentWebRunner = setUpResidentRunner(mockFlutterDevice);
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[
......
......@@ -6,17 +6,83 @@
import 'dart:async';
import 'package:file/file.dart';
import 'package:vm_service/vm_service.dart' as vm_service;
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/signals.dart';
import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/resident_runner.dart';
import 'package:mockito/mockito.dart';
import 'package:flutter_tools/src/vmservice.dart';
import 'package:test/fake.dart';
import '../src/common.dart';
import '../src/fake_vm_services.dart';
final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate(
id: '1',
pauseEvent: vm_service.Event(
kind: vm_service.EventKind.kResume,
timestamp: 0
),
breakpoints: <vm_service.Breakpoint>[],
exceptionPauseMode: null,
extensionRPCs: <String>[],
libraries: <vm_service.LibraryRef>[
vm_service.LibraryRef(
id: '1',
uri: 'file:///hello_world/main.dart',
name: '',
),
],
livePorts: 0,
name: 'test',
number: '1',
pauseOnExit: false,
runnable: true,
startTime: 0,
isSystemIsolate: false,
isolateFlags: <vm_service.IsolateFlag>[],
);
final vm_service.VM fakeVM = vm_service.VM(
isolates: <vm_service.IsolateRef>[fakeUnpausedIsolate],
pid: 1,
hostCPU: '',
isolateGroups: <vm_service.IsolateGroupRef>[],
targetCPU: '',
startTime: 0,
name: 'dart',
architectureBits: 64,
operatingSystem: '',
version: '',
systemIsolateGroups: <vm_service.IsolateGroupRef>[],
systemIsolates: <vm_service.IsolateRef>[],
);
final FlutterView fakeFlutterView = FlutterView(
id: 'a',
uiIsolate: fakeUnpausedIsolate,
);
final FakeVmServiceRequest listViews = FakeVmServiceRequest(
method: kListViewsMethod,
jsonResponse: <String, Object>{
'views': <Object>[
fakeFlutterView.toJson(),
],
},
);
final FakeVmServiceRequest getVM = FakeVmServiceRequest(
method: 'getVM',
jsonResponse: fakeVM.toJson(),
);
void main() {
testWithoutContext('keyboard input handling single help character', () async {
......@@ -62,293 +128,850 @@ void main() {
});
group('keycode verification, brought to you by the letter', () {
MockResidentRunner mockResidentRunner;
TerminalHandler terminalHandler;
BufferLogger testLogger;
setUp(() {
testLogger = BufferLogger.test();
final Signals signals = Signals.test();
final Terminal terminal = Terminal.test();
final MemoryFileSystem fs = MemoryFileSystem.test();
final ProcessInfo processInfo = ProcessInfo.test(fs);
mockResidentRunner = MockResidentRunner();
terminalHandler = TerminalHandler(
mockResidentRunner,
logger: testLogger,
signals: signals,
terminal: terminal,
processInfo: processInfo,
reportReady: false,
);
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true);
});
testWithoutContext('a, can handle trailing newlines', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('a\n');
expect(terminalHandler.lastReceivedCommand, 'a');
});
testWithoutContext('n, can handle trailing only newlines', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
await terminalHandler.processTerminalInput('\n\n');
expect(terminalHandler.lastReceivedCommand, '');
});
testWithoutContext('a - debugToggleProfileWidgetBuilds with service protocol', () async {
await terminalHandler.processTerminalInput('a');
testWithoutContext('a - debugToggleProfileWidgetBuilds', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'enabled': 'false'
},
),
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{
'isolateId': '1',
'enabled': 'true'
},
jsonResponse: <String, Object>{
'enabled': 'true'
},
),
]);
verify(mockResidentRunner.debugToggleProfileWidgetBuilds()).called(1);
await terminalHandler.processTerminalInput('a');
});
testWithoutContext('a - debugToggleProfileWidgetBuilds', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true);
testWithoutContext('a - debugToggleProfileWidgetBuilds with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'enabled': 'false'
},
),
const FakeVmServiceRequest(
method: 'ext.flutter.profileWidgetBuilds',
args: <String, Object>{
'isolateId': '1',
'enabled': 'true'
},
jsonResponse: <String, Object>{
'enabled': 'true'
},
),
], web: true);
await terminalHandler.processTerminalInput('a');
});
verify(mockResidentRunner.debugToggleProfileWidgetBuilds()).called(1);
testWithoutContext('a - debugToggleProfileWidgetBuilds without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('a');
});
testWithoutContext('b - debugToggleBrightness', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true);
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'Brightness.light',
}
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'Brightness.dark',
},
jsonResponse: <String, Object>{
'value': 'Brightness.dark',
}
),
]);
await terminalHandler.processTerminalInput('b');
verify(mockResidentRunner.debugToggleBrightness()).called(1);
expect(terminalHandler.logger.statusText, contains('Changed brightness to Brightness.dark'));
});
testWithoutContext('b - debugToggleBrightness with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'Brightness.light',
}
),
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.brightnessOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'Brightness.dark',
},
jsonResponse: <String, Object>{
'value': 'Brightness.dark',
}
),
], web: true);
await terminalHandler.processTerminalInput('b');
expect(terminalHandler.logger.statusText, contains('Changed brightness to Brightness.dark'));
});
testWithoutContext('b - debugToggleBrightness without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('b');
});
testWithoutContext('d,D - detach', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('d');
expect(runner.calledDetach, true);
runner.calledDetach = false;
await terminalHandler.processTerminalInput('D');
verify(mockResidentRunner.detach()).called(2);
expect(runner.calledDetach, true);
});
testWithoutContext('h,H,? - printHelp', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('h');
expect(runner.calledPrintWithDetails, true);
runner.calledPrintWithDetails = false;
await terminalHandler.processTerminalInput('H');
expect(runner.calledPrintWithDetails, true);
runner.calledPrintWithDetails = false;
await terminalHandler.processTerminalInput('?');
verify(mockResidentRunner.printHelp(details: true)).called(3);
expect(runner.calledPrintWithDetails, true);
});
testWithoutContext('i - debugToggleWidgetInspector with service protocol', () async {
testWithoutContext('i - debugToggleWidgetInspector', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.inspector.show',
args: <String, Object>{
'isolateId': '1',
},
),
]);
await terminalHandler.processTerminalInput('i');
});
verify(mockResidentRunner.debugToggleWidgetInspector()).called(1);
testWithoutContext('i - debugToggleWidgetInspector with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.inspector.show',
args: <String, Object>{
'isolateId': '1',
},
),
], web: true);
await terminalHandler.processTerminalInput('i');
});
testWithoutContext('I - debugToggleInvertOversizedImages with service protocol/debug', () async {
when(mockResidentRunner.isRunningDebug).thenReturn(true);
await terminalHandler.processTerminalInput('I');
testWithoutContext('i - debugToggleWidgetInspector without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
verify(mockResidentRunner.debugToggleInvertOversizedImages()).called(1);
await terminalHandler.processTerminalInput('i');
});
testWithoutContext('L - debugDumpLayerTree with service protocol', () async {
await terminalHandler.processTerminalInput('L');
testWithoutContext('I - debugToggleInvertOversizedImages', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.invertOversizedImages',
args: <String, Object>{
'isolateId': '1',
},
),
]);
await terminalHandler.processTerminalInput('I');
});
verify(mockResidentRunner.debugDumpLayerTree()).called(1);
testWithoutContext('I - debugToggleInvertOversizedImages with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.invertOversizedImages',
args: <String, Object>{
'isolateId': '1',
},
),
], web: true);
await terminalHandler.processTerminalInput('I');
});
testWithoutContext('o,O - debugTogglePlatform with service protocol and debug mode', () async {
when(mockResidentRunner.isRunningDebug).thenReturn(true);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
testWithoutContext('I - debugToggleInvertOversizedImages without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('I');
});
verify(mockResidentRunner.debugTogglePlatform()).called(2);
testWithoutContext('I - debugToggleInvertOversizedImages in profile mode is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], buildMode: BuildMode.profile);
await terminalHandler.processTerminalInput('I');
});
testWithoutContext('p - debugToggleDebugPaintSizeEnabled with service protocol and debug mode', () async {
when(mockResidentRunner.isRunningDebug).thenReturn(true);
await terminalHandler.processTerminalInput('p');
testWithoutContext('L - debugDumpLayerTree', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpLayerTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'LAYER TREE',
}
),
]);
await terminalHandler.processTerminalInput('L');
verify(mockResidentRunner.debugToggleDebugPaintSizeEnabled()).called(1);
expect(terminalHandler.logger.statusText, contains('LAYER TREE'));
});
testWithoutContext('p - debugToggleDebugPaintSizeEnabled with service protocol and debug mode', () async {
when(mockResidentRunner.isRunningDebug).thenReturn(true);
await terminalHandler.processTerminalInput('p');
testWithoutContext('L - debugDumpLayerTree with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpLayerTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'LAYER TREE',
}
),
], web: true);
await terminalHandler.processTerminalInput('L');
verify(mockResidentRunner.debugToggleDebugPaintSizeEnabled()).called(1);
expect(terminalHandler.logger.statusText, contains('LAYER TREE'));
});
testWithoutContext('P - debugTogglePerformanceOverlayOverride with service protocol', () async {
await terminalHandler.processTerminalInput('P');
testWithoutContext('L - debugDumpLayerTree with service protocol and profile mode is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], buildMode: BuildMode.profile);
await terminalHandler.processTerminalInput('L');
});
verify(mockResidentRunner.debugTogglePerformanceOverlayOverride()).called(1);
testWithoutContext('L - debugDumpLayerTree without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('L');
});
testWithoutContext('q,Q - exit', () async {
await terminalHandler.processTerminalInput('q');
await terminalHandler.processTerminalInput('Q');
testWithoutContext('o,O - debugTogglePlatform', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
// Request 1.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'iOS'
},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'fuchsia',
},
jsonResponse: <String, Object>{
'value': 'fuchsia'
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'android'
},
),
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'iOS',
},
jsonResponse: <String, Object>{
'value': 'iOS'
},
),
]);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
verify(mockResidentRunner.exit()).called(2);
expect(terminalHandler.logger.statusText, contains('Switched operating system to fuchsia'));
expect(terminalHandler.logger.statusText, contains('Switched operating system to iOS'));
});
testWithoutContext('s - screenshot', () async {
final MockDevice mockDevice = MockDevice();
final MockFlutterDevice mockFlutterDevice = MockFlutterDevice();
when(mockResidentRunner.isRunningDebug).thenReturn(true);
when(mockResidentRunner.flutterDevices).thenReturn(<FlutterDevice>[mockFlutterDevice]);
when(mockFlutterDevice.device).thenReturn(mockDevice);
when(mockDevice.supportsScreenshot).thenReturn(true);
testWithoutContext('o,O - debugTogglePlatform with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
// Request 1.
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'iOS'
},
),
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'fuchsia',
},
jsonResponse: <String, Object>{
'value': 'fuchsia'
},
),
// Request 2.
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'value': 'android'
},
),
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.platformOverride',
args: <String, Object>{
'isolateId': '1',
'value': 'iOS',
},
jsonResponse: <String, Object>{
'value': 'iOS'
},
),
], web: true);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
await terminalHandler.processTerminalInput('s');
expect(terminalHandler.logger.statusText, contains('Switched operating system to fuchsia'));
expect(terminalHandler.logger.statusText, contains('Switched operating system to iOS'));
});
verify(mockResidentRunner.screenshot(mockFlutterDevice)).called(1);
testWithoutContext('o,O - debugTogglePlatform without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('o');
await terminalHandler.processTerminalInput('O');
});
testWithoutContext('r - hotReload supported and succeeds', () async {
when(mockResidentRunner.canHotReload).thenReturn(true);
when(mockResidentRunner.restart(fullRestart: false))
.thenAnswer((Invocation invocation) async {
return OperationResult(0, '');
});
await terminalHandler.processTerminalInput('r');
testWithoutContext('p - debugToggleDebugPaintSizeEnabled', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugPaint',
args: <String, Object>{
'isolateId': '1',
},
),
]);
await terminalHandler.processTerminalInput('p');
});
verify(mockResidentRunner.restart(fullRestart: false)).called(1);
testWithoutContext('p - debugToggleDebugPaintSizeEnabled with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.debugPaint',
args: <String, Object>{
'isolateId': '1',
},
),
], web: true);
await terminalHandler.processTerminalInput('p');
});
testWithoutContext('r - hotReload supported and fails', () async {
when(mockResidentRunner.canHotReload).thenReturn(true);
when(mockResidentRunner.restart(fullRestart: false))
.thenAnswer((Invocation invocation) async {
return OperationResult(1, '');
});
await terminalHandler.processTerminalInput('r');
testWithoutContext('p - debugToggleDebugPaintSizeEnabled without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('p');
});
verify(mockResidentRunner.restart(fullRestart: false)).called(1);
testWithoutContext('P - debugTogglePerformanceOverlayOverride', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.showPerformanceOverlay',
args: <String, Object>{
'isolateId': '1',
},
),
]);
await terminalHandler.processTerminalInput('P');
});
expect(testLogger.statusText, contains('Try again after fixing the above error(s).'));
testWithoutContext('P - debugTogglePerformanceOverlayOverride with web target is skipped ', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], web: true);
await terminalHandler.processTerminalInput('P');
});
testWithoutContext('r - hotReload supported and fails fatally', () async {
when(mockResidentRunner.canHotReload).thenReturn(true);
when(mockResidentRunner.hotMode).thenReturn(true);
when(mockResidentRunner.restart(fullRestart: false))
.thenAnswer((Invocation invocation) async {
return OperationResult(1, 'fail', fatal: true);
});
expect(terminalHandler.processTerminalInput('r'), throwsToolExit());
testWithoutContext('P - debugTogglePerformanceOverlayOverride without service protocol is skipped ', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('P');
});
testWithoutContext('r - hotReload unsupported', () async {
when(mockResidentRunner.canHotReload).thenReturn(false);
await terminalHandler.processTerminalInput('r');
testWithoutContext('s - screenshot', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
verifyNever(mockResidentRunner.restart(fullRestart: false));
await terminalHandler.processTerminalInput('s');
});
testWithoutContext('R - hotRestart supported and succeeds', () async {
when(mockResidentRunner.supportsRestart).thenReturn(true);
when(mockResidentRunner.hotMode).thenReturn(true);
when(mockResidentRunner.restart(fullRestart: true))
.thenAnswer((Invocation invocation) async {
return OperationResult(0, '');
});
await terminalHandler.processTerminalInput('R');
testWithoutContext('S - debugDumpSemanticsTreeInTraversalOrder', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'SEMANTICS DATA',
},
),
]);
await terminalHandler.processTerminalInput('S');
verify(mockResidentRunner.restart(fullRestart: true)).called(1);
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
testWithoutContext('R - hotRestart supported and fails', () async {
when(mockResidentRunner.supportsRestart).thenReturn(true);
when(mockResidentRunner.hotMode).thenReturn(true);
when(mockResidentRunner.restart(fullRestart: true))
.thenAnswer((Invocation invocation) async {
return OperationResult(1, 'fail');
});
await terminalHandler.processTerminalInput('R');
verify(mockResidentRunner.restart(fullRestart: true)).called(1);
testWithoutContext('S - debugDumpSemanticsTreeInTraversalOrder with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInTraversalOrder',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'SEMANTICS DATA',
},
),
], web: true);
await terminalHandler.processTerminalInput('S');
expect(testLogger.statusText, contains('Try again after fixing the above error(s).'));
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
testWithoutContext('R - hotRestart supported and fails fatally', () async {
when(mockResidentRunner.supportsRestart).thenReturn(true);
when(mockResidentRunner.hotMode).thenReturn(true);
when(mockResidentRunner.restart(fullRestart: true))
.thenAnswer((Invocation invocation) async {
return OperationResult(1, 'fail', fatal: true);
});
expect(() => terminalHandler.processTerminalInput('R'), throwsToolExit());
testWithoutContext('S - debugDumpSemanticsTreeInTraversalOrder without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('S');
});
testWithoutContext('R - hot restart unsupported', () async {
when(mockResidentRunner.supportsRestart).thenReturn(false);
await terminalHandler.processTerminalInput('R');
testWithoutContext('U - debugDumpSemanticsTreeInInverseHitTestOrder', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'SEMANTICS DATA',
},
),
]);
await terminalHandler.processTerminalInput('U');
verifyNever(mockResidentRunner.restart(fullRestart: true));
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
testWithoutContext('ResidentRunner clears the screen when it should', () async {
const String message = 'This should be cleared';
testWithoutContext('U - debugDumpSemanticsTreeInInverseHitTestOrder with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpSemanticsTreeInInverseHitTestOrder',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'SEMANTICS DATA',
},
),
], web: true);
await terminalHandler.processTerminalInput('U');
expect(testLogger.statusText, equals(''));
testLogger.printStatus(message);
expect(testLogger.statusText, equals(message + '\n')); // printStatus makes a newline
expect(terminalHandler.logger.statusText, contains('SEMANTICS DATA'));
});
await terminalHandler.processTerminalInput('c');
expect(testLogger.statusText, equals(''));
testWithoutContext('U - debugDumpSemanticsTreeInInverseHitTestOrder without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('U');
});
testWithoutContext('S - debugDumpSemanticsTreeInTraversalOrder with service protocol', () async {
await terminalHandler.processTerminalInput('S');
testWithoutContext('t,T - debugDumpRenderTree', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'RENDER DATA 1',
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'RENDER DATA 2',
},
),
]);
await terminalHandler.processTerminalInput('t');
await terminalHandler.processTerminalInput('T');
verify(mockResidentRunner.debugDumpSemanticsTreeInTraversalOrder()).called(1);
expect(terminalHandler.logger.statusText, contains('RENDER DATA 1'));
expect(terminalHandler.logger.statusText, contains('RENDER DATA 2'));
});
testWithoutContext('t,T - debugDumpRenderTree with service protocol', () async {
testWithoutContext('t,T - debugDumpRenderTree with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'RENDER DATA 1',
},
),
// Request 2.
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpRenderTree',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'RENDER DATA 2',
},
),
], web: true);
await terminalHandler.processTerminalInput('t');
await terminalHandler.processTerminalInput('T');
verify(mockResidentRunner.debugDumpRenderTree()).called(2);
expect(terminalHandler.logger.statusText, contains('RENDER DATA 1'));
expect(terminalHandler.logger.statusText, contains('RENDER DATA 2'));
});
testWithoutContext('U - debugDumpRenderTree with service protocol', () async {
await terminalHandler.processTerminalInput('U');
testWithoutContext('t,T - debugDumpRenderTree without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('t');
await terminalHandler.processTerminalInput('T');
});
testWithoutContext('w,W - debugDumpApp', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'WIDGET DATA 1',
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'WIDGET DATA 2',
},
),
]);
await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W');
verify(mockResidentRunner.debugDumpSemanticsTreeInInverseHitTestOrder()).called(1);
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 1'));
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 2'));
});
testWithoutContext('w,W - debugDumpApp with service protocol', () async {
testWithoutContext('w,W - debugDumpApp with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'WIDGET DATA 1',
},
),
// Request 2.
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.debugDumpApp',
args: <String, Object>{
'isolateId': '1',
},
jsonResponse: <String, Object>{
'data': 'WIDGET DATA 2',
},
),
], web: true);
await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W');
verify(mockResidentRunner.debugDumpApp()).called(2);
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 1'));
expect(terminalHandler.logger.statusText, contains('WIDGET DATA 2'));
});
testWithoutContext('z,Z - debugToggleDebugCheckElevationsEnabled with service protocol', () async {
testWithoutContext('w,W - debugDumpApp without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('w');
await terminalHandler.processTerminalInput('W');
});
testWithoutContext('z,Z - debugToggleDebugCheckElevationsEnabled', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{
'isolateId': '1',
},
),
// Request 2.
listViews,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{
'isolateId': '1',
},
),
]);
await terminalHandler.processTerminalInput('z');
await terminalHandler.processTerminalInput('Z');
});
verify(mockResidentRunner.debugToggleDebugCheckElevationsEnabled()).called(2);
testWithoutContext('z,Z - debugToggleDebugCheckElevationsEnabled with web target', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{
'isolateId': '1',
},
),
// Request 2.
getVM,
const FakeVmServiceRequest(
method: 'ext.flutter.debugCheckElevationsEnabled',
args: <String, Object>{
'isolateId': '1',
},
),
], web: true);
await terminalHandler.processTerminalInput('z');
await terminalHandler.processTerminalInput('Z');
});
testWithoutContext('z,Z - debugToggleDebugCheckElevationsEnabled without service protocol', () async {
when(mockResidentRunner.supportsServiceProtocol).thenReturn(false);
testWithoutContext('z,Z - debugToggleDebugCheckElevationsEnabled without service protocol is skipped', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsServiceProtocol: false);
await terminalHandler.processTerminalInput('z');
await terminalHandler.processTerminalInput('Z');
});
testWithoutContext('q,Q - exit', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('q');
expect(runner.calledExit, true);
runner.calledExit = false;
await terminalHandler.processTerminalInput('Q');
expect(runner.calledExit, true);
});
testWithoutContext('r - hotReload unsupported', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsHotReload: false);
await terminalHandler.processTerminalInput('r');
});
testWithoutContext('R - hotRestart unsupported', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], supportsRestart: false);
await terminalHandler.processTerminalInput('R');
});
testWithoutContext('r - hotReload', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('r');
expect(runner.calledReload, true);
expect(runner.calledRestart, false);
});
testWithoutContext('R - hotRestart', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[]);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('R');
expect(runner.calledReload, false);
expect(runner.calledRestart, true);
});
testWithoutContext('r - hotReload with non-fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], reloadExitCode: 1);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('r');
// This should probably be disable when the service protocol is not enabled.
verify(mockResidentRunner.debugToggleDebugCheckElevationsEnabled()).called(2);
expect(runner.calledReload, true);
expect(runner.calledRestart, false);
expect(terminalHandler.logger.statusText, contains('Try again after fixing the above error(s).'));
});
testWithoutContext('R - hotRestart with non-fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], reloadExitCode: 1);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await terminalHandler.processTerminalInput('R');
expect(runner.calledReload, false);
expect(runner.calledRestart, true);
expect(terminalHandler.logger.statusText, contains('Try again after fixing the above error(s).'));
});
testWithoutContext('r - hotReload with fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], reloadExitCode: 1, fatalReloadError: true);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await expectLater(() => terminalHandler.processTerminalInput('r'), throwsToolExit());
expect(runner.calledReload, true);
expect(runner.calledRestart, false);
});
testWithoutContext('R - hotRestart with fatal error', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], reloadExitCode: 1, fatalReloadError: true);
final FakeResidentRunner runner = terminalHandler.residentRunner as FakeResidentRunner;
await expectLater(() => terminalHandler.processTerminalInput('R'), throwsToolExit());
expect(runner.calledReload, false);
expect(runner.calledRestart, true);
});
});
testWithoutContext('ResidentRunner clears the screen when it should', () async {
final TerminalHandler terminalHandler = setUpTerminalHandler(<FakeVmServiceRequest>[], reloadExitCode: 1, fatalReloadError: true);
const String message = 'This should be cleared';
expect(terminalHandler.logger.statusText, equals(''));
terminalHandler.logger.printStatus(message);
expect(terminalHandler.logger.statusText, equals(message + '\n')); // printStatus makes a newline
await terminalHandler.processTerminalInput('c');
expect(terminalHandler.logger.statusText, equals(''));
});
testWithoutContext('pidfile creation', () {
final BufferLogger testLogger = BufferLogger.test();
final Signals signals = _TestSignals(Signals.defaultExitSignals);
final Terminal terminal = Terminal.test();
final MemoryFileSystem fs = MemoryFileSystem.test();
final ProcessInfo processInfo = ProcessInfo.test(fs);
final ResidentRunner mockResidentRunner = MockResidentRunner();
when(mockResidentRunner.stayResident).thenReturn(true);
when(mockResidentRunner.supportsServiceProtocol).thenReturn(true);
when(mockResidentRunner.supportsRestart).thenReturn(true);
final FakeResidentRunner residentRunner = FakeResidentRunner(
FlutterDevice(FakeDevice(), buildInfo: BuildInfo.debug, generator: FakeResidentCompiler()),
testLogger,
fs,
);
residentRunner
..supportsRestart = true
..supportsServiceProtocol = true
..stayResident = true;
const String filename = 'test.pid';
final TerminalHandler terminalHandler = TerminalHandler(
mockResidentRunner,
residentRunner,
logger: testLogger,
signals: signals,
terminal: terminal,
......@@ -356,23 +979,183 @@ void main() {
reportReady: false,
pidFile: filename,
);
expect(fs.file(filename).existsSync(), isFalse);
expect(fs.file(filename), isNot(exists));
terminalHandler.setupTerminal();
terminalHandler.registerSignalHandlers();
expect(fs.file(filename).existsSync(), isTrue);
expect(fs.file(filename), exists);
terminalHandler.stop();
expect(fs.file(filename).existsSync(), isFalse);
expect(fs.file(filename), isNot(exists));
});
}
class MockDevice extends Mock implements Device {
MockDevice() {
when(isSupported()).thenReturn(true);
class FakeResidentRunner extends ResidentHandlers {
FakeResidentRunner(FlutterDevice device, this.logger, this.fileSystem) : flutterDevices = <FlutterDevice>[device];
bool calledDetach = false;
bool calledPrint = false;
bool calledExit = false;
bool calledPrintWithDetails = false;
bool calledReload = false;
bool calledRestart = false;
int reloadExitCode = 0;
bool fatalReloadError = false;
@override
final Logger logger;
@override
final FileSystem fileSystem;
@override
final List<FlutterDevice> flutterDevices;
@override
bool canHotReload = true;
@override
bool hotMode = true;
@override
bool isRunningDebug = true;
@override
bool isRunningProfile = false;
@override
bool isRunningRelease = false;
@override
bool stayResident = true;
@override
bool supportsRestart = true;
@override
bool supportsServiceProtocol = true;
@override
bool supportsWriteSkSL = true;
@override
Future<void> cleanupAfterSignal() async { }
@override
Future<void> detach() async {
calledDetach = true;
}
@override
Future<void> exit() async {
calledExit = true;
}
@override
void printHelp({bool details}) {
if (details) {
calledPrintWithDetails = true;
} else {
calledPrint = true;
}
}
@override
Future<void> runSourceGenerators() async { }
@override
Future<OperationResult> restart({bool fullRestart = false, bool pause = false, String reason}) async {
if (fullRestart && !supportsRestart) {
throw StateError('illegal restart');
}
if (!fullRestart && !canHotReload) {
throw StateError('illegal reload');
}
if (fullRestart) {
calledRestart = true;
} else {
calledReload = true;
}
return OperationResult(reloadExitCode, '', fatal: fatalReloadError);
}
}
class FakeDevice extends Fake implements Device {
@override
bool isSupported() => true;
@override
bool supportsScreenshot = false;
@override
Future<void> takeScreenshot(File file) async {
if (!supportsScreenshot) {
throw StateError('illegal screenshot attempt');
}
file.writeAsBytesSync(<int>[1, 2, 3, 4]);
}
}
TerminalHandler setUpTerminalHandler(List<FakeVmServiceRequest> requests, {
bool supportsRestart = true,
bool supportsServiceProtocol = true,
bool supportsHotReload = true,
bool web = false,
bool fatalReloadError = false,
int reloadExitCode = 0,
BuildMode buildMode = BuildMode.debug,
}) {
final BufferLogger testLogger = BufferLogger.test();
final Signals signals = Signals.test();
final Terminal terminal = Terminal.test();
final FileSystem fileSystem = MemoryFileSystem.test();
final ProcessInfo processInfo = ProcessInfo.test(MemoryFileSystem.test());
final FlutterDevice device = FlutterDevice(
FakeDevice(),
buildInfo: BuildInfo(buildMode, '', treeShakeIcons: false),
generator: FakeResidentCompiler(),
targetPlatform: web
? TargetPlatform.web_javascript
: TargetPlatform.android_arm,
);
device.vmService = FakeVmServiceHost(requests: requests).vmService;
final FakeResidentRunner residentRunner = FakeResidentRunner(device, testLogger, fileSystem)
..supportsServiceProtocol = supportsServiceProtocol
..supportsRestart = supportsRestart
..canHotReload = supportsHotReload
..fatalReloadError = fatalReloadError
..reloadExitCode = reloadExitCode;
switch (buildMode) {
case BuildMode.debug:
residentRunner
..isRunningDebug = true
..isRunningProfile = false
..isRunningRelease = false;
break;
case BuildMode.profile:
residentRunner
..isRunningDebug = false
..isRunningProfile = true
..isRunningRelease = false;
break;
case BuildMode.release:
residentRunner
..isRunningDebug = false
..isRunningProfile = false
..isRunningRelease = true;
break;
}
return TerminalHandler(
residentRunner,
logger: testLogger,
signals: signals,
terminal: terminal,
processInfo: processInfo,
reportReady: false,
);
}
class MockResidentRunner extends Mock implements ResidentRunner {}
class MockFlutterDevice extends Mock implements FlutterDevice {}
class FakeResidentCompiler extends Fake implements ResidentCompiler {}
class TestRunner extends Fake implements ResidentRunner {
......
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