Unverified Commit c67d2587 authored by Danny Tuppeny's avatar Danny Tuppeny Committed by GitHub

Provide more useful error message if a non-compliant DAP tool (or user) sends...

Provide more useful error message if a non-compliant DAP tool (or user) sends bad input to DAP server (#107827)
parent 0d40e3dd
...@@ -59,6 +59,14 @@ class DebugAdapterCommand extends FlutterCommand { ...@@ -59,6 +59,14 @@ class DebugAdapterCommand extends FlutterCommand {
ipv6: ipv6 ?? false, ipv6: ipv6 ?? false,
enableDds: enableDds, enableDds: enableDds,
test: boolArgDeprecated('test'), test: boolArgDeprecated('test'),
onError: (Object? e) {
globals.printError(
'Input could not be parsed as a Debug Adapter Protocol message.\n'
'The "flutter debug-adapter" command is intended for use by tooling '
'that communicates using the Debug Adapter Protocol.\n\n'
'$e',
);
},
); );
await server.channel.closed; await server.channel.closed;
......
...@@ -28,6 +28,7 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments ...@@ -28,6 +28,7 @@ class FlutterDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArguments
bool enableDds = true, bool enableDds = true,
super.enableAuthCodes, super.enableAuthCodes,
super.logger, super.logger,
super.onError,
}) : _enableDds = enableDds, }) : _enableDds = enableDds,
// Always disable in the DAP layer as it's handled in the spawned // Always disable in the DAP layer as it's handled in the spawned
// 'flutter' process. // 'flutter' process.
......
...@@ -28,6 +28,7 @@ class FlutterTestDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArgum ...@@ -28,6 +28,7 @@ class FlutterTestDebugAdapter extends DartDebugAdapter<FlutterLaunchRequestArgum
bool enableDds = true, bool enableDds = true,
super.enableAuthCodes, super.enableAuthCodes,
super.logger, super.logger,
super.onError,
}) : _enableDds = enableDds, }) : _enableDds = enableDds,
// Always disable in the DAP layer as it's handled in the spawned // Always disable in the DAP layer as it's handled in the spawned
// 'flutter' process. // 'flutter' process.
......
...@@ -30,21 +30,28 @@ class DapServer { ...@@ -30,21 +30,28 @@ class DapServer {
this.enableAuthCodes = true, this.enableAuthCodes = true,
bool test = false, bool test = false,
this.logger, this.logger,
void Function(Object? e)? onError,
}) : channel = ByteStreamServerChannel(input, output, logger) { }) : channel = ByteStreamServerChannel(input, output, logger) {
adapter = test adapter = test
? FlutterTestDebugAdapter(channel, ? FlutterTestDebugAdapter(
channel,
fileSystem: fileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
ipv6: ipv6, ipv6: ipv6,
enableDds: enableDds, enableDds: enableDds,
enableAuthCodes: enableAuthCodes, enableAuthCodes: enableAuthCodes,
logger: logger) logger: logger,
: FlutterDebugAdapter(channel, onError: onError,
)
: FlutterDebugAdapter(
channel,
fileSystem: fileSystem, fileSystem: fileSystem,
platform: platform, platform: platform,
enableDds: enableDds, enableDds: enableDds,
enableAuthCodes: enableAuthCodes, enableAuthCodes: enableAuthCodes,
logger: logger); logger: logger,
onError: onError,
);
} }
final ByteStreamServerChannel channel; final ByteStreamServerChannel channel;
......
...@@ -8,6 +8,7 @@ import 'package:dds/dap.dart'; ...@@ -8,6 +8,7 @@ import 'package:dds/dap.dart';
import 'package:dds/src/dap/protocol_generated.dart'; import 'package:dds/src/dap/protocol_generated.dart';
import 'package:file/file.dart'; import 'package:file/file.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import '../../src/common.dart'; import '../../src/common.dart';
...@@ -15,6 +16,7 @@ import '../test_data/basic_project.dart'; ...@@ -15,6 +16,7 @@ import '../test_data/basic_project.dart';
import '../test_data/compile_error_project.dart'; import '../test_data/compile_error_project.dart';
import '../test_utils.dart'; import '../test_utils.dart';
import 'test_client.dart'; import 'test_client.dart';
import 'test_server.dart';
import 'test_support.dart'; import 'test_support.dart';
void main() { void main() {
...@@ -97,6 +99,27 @@ void main() { ...@@ -97,6 +99,27 @@ void main() {
]); ]);
}); });
testWithoutContext('outputs useful message on invalid DAP protocol messages', () async {
final OutOfProcessDapTestServer server = dap.server as OutOfProcessDapTestServer;
final CompileErrorProject project = CompileErrorProject();
await project.setUpIn(tempDir);
final StringBuffer stderrOutput = StringBuffer();
dap.server.onStderrOutput = stderrOutput.write;
// Write invalid headers and await the error.
dap.server.sink.add(utf8.encode('foo\r\nbar\r\n\r\n'));
await server.exitCode;
// Verify the user-friendly message was included in the output.
final String error = stderrOutput.toString();
expect(error, contains('Input could not be parsed as a Debug Adapter Protocol message'));
expect(error, contains('The "flutter debug-adapter" command is intended for use by tooling'));
// This test only runs with out-of-process DAP as it's testing _actual_
// stderr output and that the debug-adapter process terminates, which is
// not possible when running the DAP Server in-process.
}, skip: useInProcessDap); // [intended] See above.
testWithoutContext('correctly outputs launch errors and terminates', () async { testWithoutContext('correctly outputs launch errors and terminates', () async {
final CompileErrorProject project = CompileErrorProject(); final CompileErrorProject project = CompileErrorProject();
await project.setUpIn(tempDir); await project.setUpIn(tempDir);
......
...@@ -325,7 +325,7 @@ extension DapTestClientExtension on DapTestClient { ...@@ -325,7 +325,7 @@ extension DapTestClientExtension on DapTestClient {
Future<List<OutputEventBody>> collectAllOutput({ Future<List<OutputEventBody>> collectAllOutput({
String? program, String? program,
String? cwd, String? cwd,
Future<Response> Function()? start, Future<void> Function()? start,
Future<Response> Function()? launch, Future<Response> Function()? launch,
bool skipInitialPubGetOutput = true bool skipInitialPubGetOutput = true
}) async { }) async {
......
...@@ -19,6 +19,7 @@ abstract class DapTestServer { ...@@ -19,6 +19,7 @@ abstract class DapTestServer {
Future<void> stop(); Future<void> stop();
StreamSink<List<int>> get sink; StreamSink<List<int>> get sink;
Stream<List<int>> get stream; Stream<List<int>> get stream;
Function(String message)? onStderrOutput;
} }
/// An instance of a DAP server running in-process (to aid debugging). /// An instance of a DAP server running in-process (to aid debugging).
...@@ -73,22 +74,27 @@ class OutOfProcessDapTestServer extends DapTestServer { ...@@ -73,22 +74,27 @@ class OutOfProcessDapTestServer extends DapTestServer {
this._process, this._process,
Logger? logger, Logger? logger,
) { ) {
// Treat anything written to stderr as the DAP crashing and fail the test // Unless we're given an error handler, treat anything written to stderr as
// unless it's "Waiting for another flutter command to release the startup // the DAP crashing and fail the test unless it's "Waiting for another
// lock" or we're tearing down. // flutter command to release the startup lock" or we're tearing down.
_process.stderr _process.stderr
.transform(utf8.decoder) .transform(utf8.decoder)
.where((String error) => !error.contains('Waiting for another flutter command to release the startup lock')) .where((String error) => !error.contains('Waiting for another flutter command to release the startup lock'))
.listen((String error) { .listen((String error) {
logger?.call(error); logger?.call(error);
if (!_isShuttingDown) { if (!_isShuttingDown) {
throw Exception(error); final Function(String message)? stderrHandler = onStderrOutput;
if (stderrHandler != null) {
stderrHandler(error);
} else {
throw Exception(error);
}
} }
}); });
unawaited(_process.exitCode.then((int code) { unawaited(_process.exitCode.then((int code) {
final String message = 'Out-of-process DAP server terminated with code $code'; final String message = 'Out-of-process DAP server terminated with code $code';
logger?.call(message); logger?.call(message);
if (!_isShuttingDown && code != 0) { if (!_isShuttingDown && code != 0 && onStderrOutput == null) {
throw Exception(message); throw Exception(message);
} }
})); }));
...@@ -97,6 +103,8 @@ class OutOfProcessDapTestServer extends DapTestServer { ...@@ -97,6 +103,8 @@ class OutOfProcessDapTestServer extends DapTestServer {
bool _isShuttingDown = false; bool _isShuttingDown = false;
final Process _process; final Process _process;
Future<int> get exitCode => _process.exitCode;
@override @override
StreamSink<List<int>> get sink => _process.stdin; StreamSink<List<int>> get sink => _process.stdin;
......
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