blob: f92da208d4100c531d1c835cec50820409a4591c [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:dds/src/dap/logging.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/debug_adapters/server.dart';
import 'package:flutter_tools/src/globals.dart' as globals;
/// Enable to run from local source when running out-of-process (useful in
/// development to avoid having to keep rebuilding the flutter tool).
const bool _runFromSource = false;
abstract class DapTestServer {
Future<void> stop();
StreamSink<List<int>> get sink;
Stream<List<int>> get stream;
Function(String message)? onStderrOutput;
}
/// An instance of a DAP server running in-process (to aid debugging).
///
/// All communication still goes over the socket to ensure all messages are
/// serialized and deserialized but it's not quite the same running out of
/// process.
class InProcessDapTestServer extends DapTestServer {
InProcessDapTestServer._(List<String> args) {
_server = DapServer(
stdinController.stream,
stdoutController.sink,
fileSystem: globals.fs,
platform: globals.platform,
// Simulate flags based on the args to aid testing.
enableDds: !args.contains('--no-dds'),
ipv6: args.contains('--ipv6'),
test: args.contains('--test'),
);
}
late final DapServer _server;
final StreamController<List<int>> stdinController = StreamController<List<int>>();
final StreamController<List<int>> stdoutController = StreamController<List<int>>();
@override
StreamSink<List<int>> get sink => stdinController.sink;
@override
Stream<List<int>> get stream => stdoutController.stream;
@override
Future<void> stop() async {
_server.stop();
}
static Future<InProcessDapTestServer> create({
Logger? logger,
List<String>? additionalArgs,
}) async {
return InProcessDapTestServer._(additionalArgs ?? <String>[]);
}
}
/// An instance of a DAP server running out-of-process.
///
/// This is how an editor will usually consume DAP so is a more accurate test
/// but will be a little more difficult to debug tests as the debugger will not
/// be attached to the process.
class OutOfProcessDapTestServer extends DapTestServer {
OutOfProcessDapTestServer._(
this._process,
Logger? logger,
) {
// Unless we're given an error handler, treat anything written to stderr as
// the DAP crashing and fail the test unless it's "Waiting for another
// flutter command to release the startup lock" or we're tearing down.
_process.stderr
.transform(utf8.decoder)
.where((String error) => !error.contains('Waiting for another flutter command to release the startup lock'))
.listen((String error) {
logger?.call(error);
if (!_isShuttingDown) {
final Function(String message)? stderrHandler = onStderrOutput;
if (stderrHandler != null) {
stderrHandler(error);
} else {
throw Exception(error);
}
}
});
unawaited(_process.exitCode.then((int code) {
final String message = 'Out-of-process DAP server terminated with code $code';
logger?.call(message);
if (!_isShuttingDown && code != 0 && onStderrOutput == null) {
throw Exception(message);
}
}));
}
bool _isShuttingDown = false;
final Process _process;
Future<int> get exitCode => _process.exitCode;
@override
StreamSink<List<int>> get sink => _process.stdin;
@override
Stream<List<int>> get stream => _process.stdout;
@override
Future<void> stop() async {
_isShuttingDown = true;
_process.kill();
await _process.exitCode;
}
static Future<OutOfProcessDapTestServer> create({
Logger? logger,
List<String>? additionalArgs,
}) async {
// runFromSource=true will run "dart bin/flutter_tools.dart ..." to avoid
// having to rebuild the flutter_tools snapshot.
// runFromSource=false will run "flutter ..."
final String flutterToolPath = globals.fs.path.join(Cache.flutterRoot!, 'bin', Platform.isWindows ? 'flutter.bat' : 'flutter');
final String flutterToolsEntryScript = globals.fs.path.join(Cache.flutterRoot!, 'packages', 'flutter_tools', 'bin', 'flutter_tools.dart');
// When running from source, run "dart bin/flutter_tools.dart debug_adapter"
// instead of directly using "flutter debug_adapter".
final String executable = _runFromSource
? Platform.resolvedExecutable
: flutterToolPath;
final List<String> args = <String>[
if (_runFromSource) flutterToolsEntryScript,
'debug-adapter',
...?additionalArgs,
];
final Process process = await Process.start(executable, args);
return OutOfProcessDapTestServer._(process, logger);
}
}