mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Provide more useful error message if a non-compliant DAP tool (or user) sends bad input to DAP server (#107827)
This commit is contained in:
parent
0d40e3dd54
commit
c67d2587d5
@ -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
|
|||||||
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
|
|||||||
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 {
|
|||||||
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';
|
|||||||
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';
|
|||||||
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() {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user