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 { ...@@ -346,6 +346,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
final String messageString = jsonEncode(message); final String messageString = jsonEncode(message);
// Flutter requests are always wrapped in brackets as an array. // Flutter requests are always wrapped in brackets as an array.
final String payload = '[$messageString]\n'; final String payload = '[$messageString]\n';
_logTraffic('==> [Flutter] $payload');
process.stdin.writeln(payload); process.stdin.writeln(payload);
} }
...@@ -447,7 +448,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { ...@@ -447,7 +448,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
@override @override
void handleExitCode(int code) { void handleExitCode(int code) {
final String codeSuffix = code == 0 ? '' : ' ($code)'; final String codeSuffix = code == 0 ? '' : ' ($code)';
logger?.call('Process exited ($code)'); _logTraffic('<== [Flutter] Process exited ($code)');
handleSessionTerminate(codeSuffix); handleSessionTerminate(codeSuffix);
} }
...@@ -559,7 +560,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { ...@@ -559,7 +560,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
@override @override
void handleStderr(List<int> data) { void handleStderr(List<int> data) {
logger?.call('stderr: $data'); _logTraffic('<== [Flutter] [stderr] $data');
sendOutput('stderr', utf8.decode(data)); sendOutput('stderr', utf8.decode(data));
} }
...@@ -575,7 +576,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { ...@@ -575,7 +576,7 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter {
// - the item has an "event" field that is a String // - the item has an "event" field that is a String
// - the item has a "params" field that is a Map<String, Object?>? // - 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 // Output is sent as console (eg. output from tooling) until the app has
// started, then stdout (users output). This is so info like // started, then stdout (users output). This is so info like
...@@ -639,6 +640,22 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { ...@@ -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. /// Performs a restart/reload by sending the `app.restart` message to the `flutter run --machine` process.
Future<void> _performRestart( Future<void> _performRestart(
bool fullRestart, [ bool fullRestart, [
......
...@@ -57,6 +57,11 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter { ...@@ -57,6 +57,11 @@ class MockFlutterDebugAdapter extends FlutterDebugAdapter {
late List<String> processArgs; late List<String> processArgs;
late Map<String, String>? env; 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(); final StreamController<Map<String, Object?>> _dapToClientMessagesController = StreamController<Map<String, Object?>>.broadcast();
/// A stream of all messages sent from the adapter back to the client. /// A stream of all messages sent from the adapter back to the client.
......
...@@ -70,6 +70,39 @@ void main() { ...@@ -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 { testWithoutContext('can run and terminate a Flutter app in noDebug mode', () async {
final BasicProject project = BasicProject(); final BasicProject project = BasicProject();
await project.setUpIn(tempDir); await project.setUpIn(tempDir);
......
...@@ -153,6 +153,7 @@ class DapTestClient { ...@@ -153,6 +153,7 @@ class DapTestClient {
bool? debugExternalPackageLibraries, bool? debugExternalPackageLibraries,
bool? evaluateGettersInDebugViews, bool? evaluateGettersInDebugViews,
bool? evaluateToStringInDebugViews, bool? evaluateToStringInDebugViews,
bool sendLogsToClient = false,
}) { }) {
return sendRequest( return sendRequest(
FlutterLaunchRequestArguments( FlutterLaunchRequestArguments(
...@@ -167,9 +168,9 @@ class DapTestClient { ...@@ -167,9 +168,9 @@ class DapTestClient {
evaluateGettersInDebugViews: evaluateGettersInDebugViews, evaluateGettersInDebugViews: evaluateGettersInDebugViews,
evaluateToStringInDebugViews: evaluateToStringInDebugViews, evaluateToStringInDebugViews: evaluateToStringInDebugViews,
// When running out of process, VM Service traffic won't be available // 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 // to the client-side logger, so force logging regardless of
// traffic in a custom event. // `sendLogsToClient` which sends VM Service traffic in a custom event.
sendLogsToClient: captureVmServiceTraffic, sendLogsToClient: sendLogsToClient || captureVmServiceTraffic,
), ),
// We can't automatically pick the command when using a custom type // We can't automatically pick the command when using a custom type
// (FlutterLaunchRequestArguments). // (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