blob: dc8897409dcc6318b8532a72a8900aacdca155af [file] [log] [blame]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. 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:package_config/package_config.dart';
import 'package:path/path.dart' as path;
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 useInProcessDap = Platform.environment['DAP_TEST_INTERNAL'] == 'true';
/// A [RegExp] that matches the `path` part of a VM Service URI that contains
/// an authentication token.
final vmServiceAuthCodePathPattern = RegExp(r'^/[\w_\-=]{5,15}/ws$');
/// A [RegExp] that matches the "Connecting to VM Service" banner that is sent
/// as the first output event for a debug session.
final vmServiceUriPattern = RegExp(r'Connecting to VM Service at ([^\s]+)\s');
/// Expects [actual] to equal the lines [expected], ignoring differences in line
/// endings.
void expectLines(String actual, List<String> expected) {
expect(actual.replaceAll('\r\n', '\n'), equals(expected.join('\n')));
}
/// Returns the 1-base line in [file] that contains [searchText].
int lineWith(File file, String searchText) =>
file.readAsLinesSync().indexWhere((line) => line.contains(searchText)) + 1;
/// A helper class containing the DAP server/client for DAP integration tests.
class DapTestSession {
DapTestServer server;
DapTestClient client;
final Directory _testDir =
Directory.systemTemp.createTempSync('dart-sdk-dap-test');
late final Directory testAppDir;
late final Directory testPackageDir;
var _packageConfig = PackageConfig.empty;
DapTestSession._(this.server, this.client) {
testAppDir = _testDir.createTempSync('app');
testPackageDir = _testDir.createTempSync('packages');
}
/// Create a simple package named `foo` that has an empty `foo` function.
Future<Uri> createFooPackage() {
return createSimplePackage(
'foo',
'''
foo() {
// Does nothing.
}
''',
);
}
/// Creates a simple package script and adds the package to
/// .dart_tool/package_config.json
Future<Uri> createSimplePackage(
String name,
String content,
) async {
final dartToolDirectory =
Directory(path.join(testAppDir.path, '.dart_tool'))..createSync();
final packageConfigJsonFile =
File(path.join(dartToolDirectory.path, 'package_config.json'));
final packageConfigJsonUri = Uri.file(packageConfigJsonFile.path);
// Write the packages Dart implementation file.
final testPackageDirectory = Directory(path.join(testPackageDir.path, name))
..createSync(recursive: true);
final testFile = File(path.join(testPackageDirectory.path, '$name.dart'));
testFile.writeAsStringSync(content);
// Add this new package to the PackageConfig.
final newPackage = Package(name, Uri.file('${testPackageDirectory.path}/'));
_packageConfig = PackageConfig([..._packageConfig.packages, newPackage]);
// Write the PackageConfig to disk.
final sink = packageConfigJsonFile.openWrite();
PackageConfig.writeString(_packageConfig, sink, packageConfigJsonUri);
await sink.close();
return Uri.parse('package:$name/$name.dart');
}
/// Creates a file in a temporary folder to be used as an application for testing.
///
/// The file will be deleted at the end of the test run.
File createTestFile(String content) {
final testFile = File(path.join(testAppDir.path, 'test_file.dart'));
testFile.writeAsStringSync(content);
return testFile;
}
Future<void> tearDown() async {
await client.stop();
await server.stop();
// Clean up any temp folders created during the test runs.
_testDir.deleteSync(recursive: true);
}
static Future<DapTestSession> setUp({List<String>? additionalArgs}) async {
final server = await _startServer(additionalArgs: additionalArgs);
final client =
await DapTestClient.connect(server, captureVmServiceTraffic: true);
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,
);
}
}