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

Add a "flutter debug_adapter" command that runs a DAP server (#91802)

parent 8b88645b
......@@ -26,6 +26,7 @@ import 'src/commands/config.dart';
import 'src/commands/create.dart';
import 'src/commands/custom_devices.dart';
import 'src/commands/daemon.dart';
import 'src/commands/debug_adapter.dart';
import 'src/commands/devices.dart';
import 'src/commands/doctor.dart';
import 'src/commands/downgrade.dart';
......@@ -160,6 +161,7 @@ List<FlutterCommand> generateCommands({
),
CreateCommand(verboseHelp: verboseHelp),
DaemonCommand(hidden: !verboseHelp),
DebugAdapterCommand(verboseHelp: verboseHelp),
DevicesCommand(verboseHelp: verboseHelp),
DoctorCommand(verbose: verbose),
DowngradeCommand(verboseHelp: verboseHelp),
......
// 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.
// @dart = 2.8
import 'dart:async';
import '../debug_adapters/server.dart';
import '../globals.dart' as globals;
import '../runner/flutter_command.dart';
/// This command will start up a Debug Adapter that communicates using the Debug Adapter Protocol (DAP).
///
/// This is for use by editors and IDEs that have DAP clients to launch and
/// debug Flutter apps/tests. It extends the standard Dart DAP implementation
/// from DDS with Flutter-specific functionality (such as Hot Restart).
///
/// The server is intended to be single-use. It should live only for the
/// duration of a single debug session in the editor, and terminate when the
/// user stops debugging. If a user starts multiple debug sessions
/// simultaneously it is expected that the editor will start multiple debug
/// adapters.
///
/// The DAP specification can be found at
/// https://microsoft.github.io/debug-adapter-protocol/.
class DebugAdapterCommand extends FlutterCommand {
DebugAdapterCommand({ bool verboseHelp = false}) : hidden = !verboseHelp {
usesIpv6Flag(verboseHelp: verboseHelp);
addDdsOptions(verboseHelp: verboseHelp);
}
@override
final String name = 'debug-adapter';
@override
List<String> get aliases => const <String>['debug_adapter'];
@override
final String description = 'Run a Debug Adapter Protocol (DAP) server to communicate with the Flutter tool.';
@override
final String category = FlutterCommandCategory.tools;
@override
final bool hidden;
@override
Future<FlutterCommandResult> runCommand() async {
final DapServer server = DapServer(
globals.stdio.stdin,
globals.stdio.stdout.nonBlocking,
fileSystem: globals.fs,
platform: globals.platform,
ipv6: ipv6,
enableDds: enableDds,
);
await server.channel.closed;
return FlutterCommandResult.success();
}
}
# Debug Adapter Protocol (DAP)
This document is Flutter-specific. For information on the standard Dart DAP implementation, [see this document](https://github.com/dart-lang/sdk/blob/main/pkg/dds/tool/dap/README.md).
Flutter includes support for debugging using [the Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) as an alternative to using the [VM Service](https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md) directly, simplying the integration for new editors.
The debug adapter is started with the `flutter debug-adapter` command and is intended to be consumed by DAP-compliant tools such as Flutter-specific extensions for editors, or configured by users whose editors include generic configurable DAP clients.
Because in the DAP protocol the client speaks first, running this command from the terminal will result in no output (nor will the process terminate). This is expected behaviour.
For details on the standard DAP functionality, see [the Debug Adapter Protocol Overview](https://microsoft.github.io/debug-adapter-protocol/) and [the Debug Adapter Protocol Specification](https://microsoft.github.io/debug-adapter-protocol/specification). Custom extensions are detailed below.
## Launch/Attach Arguments
Arguments common to both `launchRequest` and `attachRequest` are:
- `bool? debugExternalPackageLibraries` - whether to enable debugging for packages that are not inside the current workspace (if not supplied, defaults to `true`)
- `bool? debugSdkLibraries` - whether to enable debugging for SDK libraries (if not supplied, defaults to `true`)
- `bool? evaluateGettersInDebugViews` - whether to evaluate getters in expression evaluation requests (inc. hovers/watch windows) (if not supplied, defaults to `false`)
- `bool? evaluateToStringInDebugViews` - whether to invoke `toString()` in expression evaluation requests (inc. hovers/watch windows) (if not supplied, defaults to `false`)
- `bool? sendLogsToClient` - used to proxy all VM Service traffic back to the client in custom `dart.log` events (has performance implications, intended for troubleshooting) (if not supplied, defaults to `false`)
- `List<String>? additionalProjectPaths` - paths of any projects (outside of `cwd`) that are open in the users workspace
- `String? cwd` - the working directory for the Dart process to be spawned in
Arguments specific to `launchRequest` are:
- `bool? noDebug` - whether to run in debug or noDebug mode (if not supplied, defaults to debug)
- `String program` - the path of the Flutter application to run
- `List<String>? args` - arguments to be passed to the Flutter program
- `List<String>? toolArgs` - arguments for the `flutter` tool
- `String? console` - if set to `"terminal"` or `"externalTerminal"` will be run using the `runInTerminal` reverse-request; otherwise the debug adapter spawns the Dart process
- `bool? enableAsserts` - whether to enable asserts (if not supplied, defaults to `true`)
`attachRequest` is not currently supported, but will be documented here when it is.
## Custom Requests
Some custom requests are available for clients to call. Below are the Flutter-specific custom requests, but the standard Dart DAP custom requests are also [documented here](https://github.com/dart-lang/sdk/blob/main/pkg/dds/tool/dap/README.md#custom-requests).
### `hotReload`
`hotReload` injects updated source code files into the running VM and then rebuilds the widget tree. An optional `reason` can be provided and should usually be `"manual"` or `"save"` to indicate what how the reload was triggered (for example by the user clicking a button, versus a hot-reload-on-save feature).
```
{
"reason": "manual"
}
```
### `hotRestart`
`hotRestart` updates the code on the device and performs a full restart (which does not preserve state). An optional `reason` can be provided and should usually be `"manual"` or `"save"` to indicate what how the reload was triggered (for example by the user clicking a button, versus a hot-reload-on-save feature).
```
{
"reason": "manual"
}
```
## Custom Events
The debug adapter may emit several custom events that are useful to clients. There are not currently any custom Flutter events, but the standard Dart DAP custom requests are [documented here](https://github.com/dart-lang/sdk/blob/main/pkg/dds/tool/dap/README.md#custom-events).
// 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 'package:dds/dap.dart';
/// An implementation of [AttachRequestArguments] that includes all fields used by the Flutter debug adapter.
///
/// This class represents the data passed from the client editor to the debug
/// adapter in attachRequest, which is a request to start debugging an
/// application.
class FlutterAttachRequestArguments
extends DartCommonLaunchAttachRequestArguments
implements AttachRequestArguments {
FlutterAttachRequestArguments({
Object? restart,
String? name,
String? cwd,
List<String>? additionalProjectPaths,
bool? debugSdkLibraries,
bool? debugExternalPackageLibraries,
bool? evaluateGettersInDebugViews,
bool? evaluateToStringInDebugViews,
bool? sendLogsToClient,
}) : super(
name: name,
cwd: cwd,
restart: restart,
additionalProjectPaths: additionalProjectPaths,
debugSdkLibraries: debugSdkLibraries,
debugExternalPackageLibraries: debugExternalPackageLibraries,
evaluateGettersInDebugViews: evaluateGettersInDebugViews,
evaluateToStringInDebugViews: evaluateToStringInDebugViews,
sendLogsToClient: sendLogsToClient,
);
FlutterAttachRequestArguments.fromMap(Map<String, Object?> obj):
super.fromMap(obj);
static FlutterAttachRequestArguments fromJson(Map<String, Object?> obj) =>
FlutterAttachRequestArguments.fromMap(obj);
}
/// An implementation of [LaunchRequestArguments] that includes all fields used by the Flutter debug adapter.
///
/// This class represents the data passed from the client editor to the debug
/// adapter in launchRequest, which is a request to start debugging an
/// application.
class FlutterLaunchRequestArguments
extends DartCommonLaunchAttachRequestArguments
implements LaunchRequestArguments {
FlutterLaunchRequestArguments({
this.noDebug,
required this.program,
this.args,
this.toolArgs,
Object? restart,
String? name,
String? cwd,
List<String>? additionalProjectPaths,
bool? debugSdkLibraries,
bool? debugExternalPackageLibraries,
bool? evaluateGettersInDebugViews,
bool? evaluateToStringInDebugViews,
bool? sendLogsToClient,
}) : super(
restart: restart,
name: name,
cwd: cwd,
additionalProjectPaths: additionalProjectPaths,
debugSdkLibraries: debugSdkLibraries,
debugExternalPackageLibraries: debugExternalPackageLibraries,
evaluateGettersInDebugViews: evaluateGettersInDebugViews,
evaluateToStringInDebugViews: evaluateToStringInDebugViews,
sendLogsToClient: sendLogsToClient,
);
FlutterLaunchRequestArguments.fromMap(Map<String, Object?> obj)
: noDebug = obj['noDebug'] as bool?,
program = obj['program'] as String?,
args = (obj['args'] as List<Object?>?)?.cast<String>(),
toolArgs = (obj['toolArgs'] as List<Object?>?)?.cast<String>(),
super.fromMap(obj);
/// If noDebug is true the launch request should launch the program without enabling debugging.
@override
final bool? noDebug;
/// The program/Flutter app to be run.
final String? program;
/// Arguments to be passed to [program].
final List<String>? args;
/// Arguments to be passed to the tool that will run [program] (for example, the VM or Flutter tool).
final List<String>? toolArgs;
@override
Map<String, Object?> toJson() => <String, Object?>{
...super.toJson(),
if (noDebug != null) 'noDebug': noDebug,
if (program != null) 'program': program,
if (args != null) 'args': args,
if (toolArgs != null) 'toolArgs': toolArgs,
};
static FlutterLaunchRequestArguments fromJson(Map<String, Object?> obj) =>
FlutterLaunchRequestArguments.fromMap(obj);
}
// 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 '../base/file_system.dart';
import '../base/io.dart';
/// A mixin providing some utility functions for locating/working with
/// package_config.json files.
///
/// Adapted from package:dds/src/dap/adapters/mixins.dart to use Flutter's
/// dart:io wrappers.
mixin PackageConfigUtils {
abstract FileSystem fileSystem;
/// Find the `package_config.json` file for the program being launched.
File? findPackageConfigFile(String possibleRoot) {
// TODO(dantup): Remove this once
// https://github.com/dart-lang/sdk/issues/45530 is done as it will not be
// necessary.
File? packageConfig;
while (true) {
packageConfig = fileSystem.file(
fileSystem.path.join(possibleRoot, '.dart_tool', 'package_config.json'),
);
// If this packageconfig exists, use it.
if (packageConfig.existsSync()) {
break;
}
final String parent = fileSystem.path.dirname(possibleRoot);
// If we can't go up anymore, the search failed.
if (parent == possibleRoot) {
packageConfig = null;
break;
}
possibleRoot = parent;
}
return packageConfig;
}
}
/// A mixin for tracking additional PIDs that can be shut down at the end of a debug session.
///
/// Adapted from package:dds/src/dap/adapters/mixins.dart to use Flutter's
/// dart:io wrappers.
mixin PidTracker {
/// Process IDs to terminate during shutdown.
///
/// This may be populated with pids from the VM Service to ensure we clean up
/// properly where signals may not be passed through the shell to the
/// underlying VM process.
/// https://github.com/Dart-Code/Dart-Code/issues/907
final Set<int> pidsToTerminate = <int>{};
/// Terminates all processes with the PIDs registered in [pidsToTerminate].
void terminatePids(ProcessSignal signal) {
pidsToTerminate.forEach(signal.send);
}
}
// 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 'package:dds/dap.dart' hide DapServer;
import '../base/file_system.dart';
import '../base/platform.dart';
import '../debug_adapters/flutter_adapter.dart';
import '../debug_adapters/flutter_adapter_args.dart';
/// A DAP server that communicates over a [ByteStreamServerChannel], usually constructed from the processes stdin/stdout streams.
///
/// The server is intended to be single-use. It should live only for the
/// duration of a single debug session in the editor, and terminate when the
/// user stops debugging. If a user starts multiple debug sessions
/// simultaneously it is expected that the editor will start multiple debug
/// adapters.
class DapServer {
DapServer(
Stream<List<int>> _input,
StreamSink<List<int>> _output, {
required FileSystem fileSystem,
required Platform platform,
this.ipv6 = false,
this.enableDds = true,
this.enableAuthCodes = true,
this.logger,
}) : channel = ByteStreamServerChannel(_input, _output, logger) {
adapter = FlutterDebugAdapter(channel,
fileSystem: fileSystem,
platform: platform,
ipv6: ipv6,
enableDds: enableDds,
enableAuthCodes: enableAuthCodes,
logger: logger);
}
final ByteStreamServerChannel channel;
late final DartDebugAdapter<FlutterLaunchRequestArguments, FlutterAttachRequestArguments> adapter;
final bool ipv6;
final bool enableDds;
final bool enableAuthCodes;
final Logger? logger;
void stop() {
channel.close();
}
}
// 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.
// @dart = 2.8
import 'dart:async';
import 'package:dds/src/dap/protocol_generated.dart';
import 'package:file/file.dart';
import 'package:flutter_tools/src/cache.dart';
import '../../src/common.dart';
import '../test_data/basic_project.dart';
import '../test_data/compile_error_project.dart';
import '../test_utils.dart';
import 'test_client.dart';
import 'test_support.dart';
void main() {
Directory tempDir;
/*late*/ DapTestSession dap;
final String relativeMainPath = 'lib${fileSystem.path.separator}main.dart';
setUpAll(() {
Cache.flutterRoot = getFlutterRoot();
});
setUp(() async {
tempDir = createResolvedTempDirectorySync('debug_adapter_test.');
dap = await DapTestSession.setUp();
});
tearDown(() async {
await dap.tearDown();
tryToDelete(tempDir);
});
testWithoutContext('can run and terminate a Flutter app in debug mode', () async {
final BasicProject _project = BasicProject();
await _project.setUpIn(tempDir);
// Once the "topLevelFunction" output arrives, we can terminate the app.
unawaited(
dap.client.outputEvents
.firstWhere((OutputEventBody output) => output.output.startsWith('topLevelFunction'))
.whenComplete(() => dap.client.terminate()),
);
final List<OutputEventBody> outputEvents = await dap.client.collectAllOutput(
launch: () => dap.client
.launch(
cwd: _project.dir.path,
toolArgs: <String>['-d', 'flutter-tester'],
),
);
final String output = _uniqueOutputLines(outputEvents);
expectLines(output, <Object>[
'Launching $relativeMainPath on Flutter test device in debug mode...',
startsWith('Connecting to VM Service at'),
'topLevelFunction',
'',
startsWith('Exited'),
]);
});
testWithoutContext('can run and terminate a Flutter app in noDebug mode', () async {
final BasicProject _project = BasicProject();
await _project.setUpIn(tempDir);
// Once the "topLevelFunction" output arrives, we can terminate the app.
unawaited(
dap.client.outputEvents
.firstWhere((OutputEventBody output) => output.output.startsWith('topLevelFunction'))
.whenComplete(() => dap.client.terminate()),
);
final List<OutputEventBody> outputEvents = await dap.client.collectAllOutput(
launch: () => dap.client
.launch(
cwd: _project.dir.path,
noDebug: true,
toolArgs: <String>['-d', 'flutter-tester'],
),
);
final String output = _uniqueOutputLines(outputEvents);
expectLines(output, <Object>[
'Launching $relativeMainPath on Flutter test device in debug mode...',
'topLevelFunction',
'',
startsWith('Exited'),
]);
});
testWithoutContext('correctly outputs launch errors and terminates', () async {
final CompileErrorProject _project = CompileErrorProject();
await _project.setUpIn(tempDir);
final List<OutputEventBody> outputEvents = await dap.client.collectAllOutput(
launch: () => dap.client
.launch(
cwd: _project.dir.path,
toolArgs: <String>['-d', 'flutter-tester'],
),
);
final String output = _uniqueOutputLines(outputEvents);
expect(output, contains('this code does not compile'));
expect(output, contains('Exception: Failed to build'));
expect(output, contains('Exited (1)'));
});
testWithoutContext('can hot reload', () async {
final BasicProject _project = BasicProject();
await _project.setUpIn(tempDir);
// Launch the app and wait for it to print "topLevelFunction".
await Future.wait(<Future<Object>>[
dap.client.outputEvents.firstWhere((OutputEventBody output) => output.output.startsWith('topLevelFunction')),
dap.client.start(
launch: () => dap.client.launch(
cwd: _project.dir.path,
noDebug: true,
toolArgs: <String>['-d', 'flutter-tester'],
),
),
], eagerError: true);
// Capture the next two output events that we expect to be the Reload
// notification and then topLevelFunction being printed again.
final Future<List<String>> outputEventsFuture = dap.client.output
// But skip any topLevelFunctions that come before the reload.
.skipWhile((String output) => output.startsWith('topLevelFunction'))
.take(2)
.toList();
await dap.client.hotReload();
expectLines(
(await outputEventsFuture).join(),
<Object>[
startsWith('Reloaded'),
'topLevelFunction',
],
);
await dap.client.terminate();
});
testWithoutContext('can hot restart', () async {
final BasicProject _project = BasicProject();
await _project.setUpIn(tempDir);
// Launch the app and wait for it to print "topLevelFunction".
await Future.wait(<Future<Object>>[
dap.client.outputEvents.firstWhere((OutputEventBody output) => output.output.startsWith('topLevelFunction')),
dap.client.start(
launch: () => dap.client.launch(
cwd: _project.dir.path,
noDebug: true,
toolArgs: <String>['-d', 'flutter-tester'],
),
),
], eagerError: true);
// Capture the next two output events that we expect to be the Restart
// notification and then topLevelFunction being printed again.
final Future<List<String>> outputEventsFuture = dap.client.output
// But skip any topLevelFunctions that come before the restart.
.skipWhile((String output) => output.startsWith('topLevelFunction'))
.take(2)
.toList();
await dap.client.hotRestart();
expectLines(
(await outputEventsFuture).join(),
<Object>[
startsWith('Restarted application'),
'topLevelFunction',
],
);
await dap.client.terminate();
});
}
/// Extracts the output from a set of [OutputEventBody], removing any
/// adjacent duplicates and combining into a single string.
String _uniqueOutputLines(List<OutputEventBody> outputEvents) {
String/*?*/ lastItem;
return outputEvents
.map((OutputEventBody e) => e.output)
.where((String output) {
// Skip the item if it's the same as the previous one.
final bool isDupe = output == lastItem;
lastItem = output;
return !isDupe;
})
.join();
}
// 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_null_migrated.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;
}
/// 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'),
);
}
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,
) {
// 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".
_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);
throw 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) {
throw message;
}
}));
}
bool _isShuttingDown = false;
final Process _process;
@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);
}
}
// 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:io';
import 'package:dds/src/dap/logging.dart';
import 'package:test/test.dart';
import 'test_client.dart';
import 'test_server.dart';
/// Whether to run the DAP server in-process with the tests, or externally in
/// another process.
///
/// By default tests will run the DAP server out-of-process to match the real
/// use from editors, but this complicates debugging the adapter. Set this env
/// variables to run the server in-process for easier debugging (this can be
/// simplified in VS Code by using a launch config with custom CodeLens links).
final bool useInProcessDap = Platform.environment['DAP_TEST_INTERNAL'] == 'true';
/// Whether to print all protocol traffic to stdout while running tests.
///
/// This is useful for debugging locally or on the bots and will include both
/// DAP traffic (between the test DAP client and the DAP server) and the VM
/// Service traffic (wrapped in a custom 'dart.log' event).
final bool verboseLogging = Platform.environment['DAP_TEST_VERBOSE'] == 'true';
/// Expects the lines in [actual] to match the relevant matcher in [expected],
/// ignoring differences in line endings and trailing whitespace.
void expectLines(String actual, List<Object> expected) {
expect(
actual.replaceAll('\r\n', '\n').trim().split('\n'),
equals(expected),
);
}
/// A helper class containing the DAP server/client for DAP integration tests.
class DapTestSession {
DapTestSession._(this.server, this.client);
DapTestServer server;
DapTestClient client;
Future<void> tearDown() async {
await client.stop();
await server.stop();
}
static Future<DapTestSession> setUp({List<String>? additionalArgs}) async {
final DapTestServer server = await _startServer(additionalArgs: additionalArgs);
final DapTestClient client = await DapTestClient.connect(
server,
captureVmServiceTraffic: verboseLogging,
logger: verboseLogging ? print : null,
);
return DapTestSession._(server, client);
}
/// Starts a DAP server that can be shared across tests.
static Future<DapTestServer> _startServer({
Logger? logger,
List<String>? additionalArgs,
}) async {
return useInProcessDap
? await InProcessDapTestServer.create(
logger: logger,
additionalArgs: additionalArgs,
)
: await OutOfProcessDapTestServer.create(
logger: logger,
additionalArgs: additionalArgs,
);
}
}
......@@ -33,7 +33,6 @@ import 'dart:convert';
import 'dart:io';
import 'package:meta/meta.dart';
import 'package:pedantic/pedantic.dart';
import 'package:process/process.dart';
import '../src/common.dart';
......
// 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.
// @dart = 2.8
import 'project.dart';
class CompileErrorProject extends Project {
@override
final String pubspec = '''
name: test
environment:
sdk: ">=2.12.0-0 <3.0.0"
dependencies:
flutter:
sdk: flutter
''';
@override
final String main = r'''
import 'dart:async';
import 'package:flutter/material.dart';
Future<void> main() async {
this code does not compile
}
''';
}
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