Unverified Commit 09bd0f66 authored by Danny Tuppeny's avatar Danny Tuppeny Committed by GitHub

Support logging 'flutter run' communication to DAP clients (#118674)

* Support logging 'flutter run' communication to DAP clients

Fixes https://github.com/Dart-Code/Dart-Code/issues/4266.

* Fix test
parent 818bb4e6
......@@ -346,6 +346,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
final String messageString = jsonEncode(message);
// Flutter requests are always wrapped in brackets as an array.
final String payload = '[$messageString]\n';
_logTraffic('==> [Flutter] $payload');
process.stdin.writeln(payload);
}
......@@ -447,7 +448,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
@override
void handleExitCode(int code) {
final String codeSuffix = code == 0 ? '' : ' ($code)';
logger?.call('Process exited ($code)');
_logTraffic('<== [Flutter] Process exited ($code)');
handleSessionTerminate(codeSuffix);
}
......@@ -559,7 +560,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
@override
void handleStderr(List<int> data) {
logger?.call('stderr: $data');
_logTraffic('<== [Flutter] [stderr] $data');
sendOutput('stderr', utf8.decode(data));
}
......@@ -575,7 +576,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
// - the item has an "event" field that is a String
// - the item has a "params" field that is a Map<String, Object?>?
logger?.call('stdout: $data');
_logTraffic('<== [Flutter] $data');
// Output is sent as console (eg. output from tooling) until the app has
// started, then stdout (users output). This is so info like
......@@ -639,6 +640,22 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
}
}
/// Logs JSON traffic to aid debugging.
///
/// If `sendLogsToClient` was `true` in the launch/attach config, logs will
/// also be sent back to the client in a "dart.log" event to simplify
/// capturing logs from the IDE (such as using the **Dart: Capture Logs**
/// command in VS Code).
void _logTraffic(String message) {
logger?.call(message);
if (sendLogsToClient) {
sendEvent(
RawEventBody(<String, String>{'message': message}),
eventType: 'dart.log',
);
}
}
/// Performs a restart/reload by sending the `app.restart` message to the `flutter run --machine` process.
Future<void> _performRestart(
bool fullRestart, [
......
......@@ -57,6 +57,11 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
late List<String> processArgs;
late Map<String, String>? env;
/// Overrides base implementation of [sendLogsToClient] which requires valid
/// `args` to have been set which may not be the case for mocks.
@override
bool get sendLogsToClient => false;
final StreamController<Map<String, Object?>> _dapToClientMessagesController = StreamController<Map<String, Object?>>.broadcast();
/// A stream of all messages sent from the adapter back to the client.
......
......@@ -70,6 +70,39 @@ void main() {
]);
});
testWithoutContext('logs to client when sendLogsToClient=true', () async {
final BasicProject project = BasicProject();
await project.setUpIn(tempDir);
// Launch the app and wait for it to print "topLevelFunction".
await Future.wait(<Future<void>>[
dap.client.stdoutOutput.firstWhere((String output) => output.startsWith('topLevelFunction')),
dap.client.start(
launch: () => dap.client.launch(
cwd: project.dir.path,
noDebug: true,
toolArgs: <String>['-d', 'flutter-tester'],
sendLogsToClient: true,
),
),
], eagerError: true);
// Capture events while terminating.
final Future<List<Event>> logEventsFuture = dap.client.events('dart.log').toList();
await dap.client.terminate();
// Ensure logs contain both the app.stop request and the result.
final List<Event> logEvents = await logEventsFuture;
final List<String> logMessages = logEvents.map((Event l) => (l.body! as Map<String, Object?>)['message']! as String).toList();
expect(
logMessages,
containsAll(<Matcher>[
startsWith('==> [Flutter] [{"id":1,"method":"app.stop"'),
startsWith('<== [Flutter] [{"id":1,"result":true}]'),
]),
);
});
testWithoutContext('can run and terminate a Flutter app in noDebug mode', () async {
final BasicProject project = BasicProject();
await project.setUpIn(tempDir);
......
......@@ -153,6 +153,7 @@ class DapTestClient {
bool? debugExternalPackageLibraries,
bool? evaluateGettersInDebugViews,
bool? evaluateToStringInDebugViews,
bool sendLogsToClient = false,
}) {
return sendRequest(
FlutterLaunchRequestArguments(
......@@ -167,9 +168,9 @@ class DapTestClient {
evaluateGettersInDebugViews: evaluateGettersInDebugViews,
evaluateToStringInDebugViews: evaluateToStringInDebugViews,
// When running out of process, VM Service traffic won't be available
// to the client-side logger, so force logging on which sends VM Service
// traffic in a custom event.
sendLogsToClient: captureVmServiceTraffic,
// to the client-side logger, so force logging regardless of
// `sendLogsToClient` which sends VM Service traffic in a custom event.
sendLogsToClient: sendLogsToClient || captureVmServiceTraffic,
),
// We can't automatically pick the command when using a custom type
// (FlutterLaunchRequestArguments).
......
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