a1f0f1b700
Change-Id: I0fff9271ef796bc9b3f8316080068d667183f1b9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/195991 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
214 lines
6.6 KiB
Dart
214 lines
6.6 KiB
Dart
// Copyright (c) 2019, 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:analysis_server/lsp_protocol/protocol_generated.dart';
|
|
import 'package:analysis_server/src/lsp/channel/lsp_byte_stream_channel.dart';
|
|
import 'package:analyzer/instrumentation/instrumentation.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:path/path.dart';
|
|
|
|
import '../../lsp/server_abstract.dart';
|
|
|
|
abstract class AbstractLspAnalysisServerIntegrationTest
|
|
with ClientCapabilitiesHelperMixin, LspAnalysisServerTestMixin {
|
|
final List<String> vmArgs = [];
|
|
LspServerClient? client;
|
|
InstrumentationService? instrumentationService;
|
|
final Map<num, Completer<ResponseMessage>> _completers = {};
|
|
LspByteStreamServerChannel get channel => client!.channel!;
|
|
|
|
@override
|
|
Stream<Message> get serverToClient => client!.serverToClient;
|
|
|
|
/// Sends a request to the server and unwraps the result. Throws if the
|
|
/// response was not successful or returned an error.
|
|
@override
|
|
Future<T> expectSuccessfulResponseTo<T, R>(
|
|
RequestMessage request, T Function(R) fromJson) async {
|
|
final resp = await sendRequestToServer(request);
|
|
final error = resp.error;
|
|
if (error != null) {
|
|
throw error;
|
|
} else if (T == Null) {
|
|
return resp.result == null
|
|
? null as T
|
|
: throw 'Expected Null response but got ${resp.result}';
|
|
} else {
|
|
return fromJson(resp.result);
|
|
}
|
|
}
|
|
|
|
void newFile(String path, {String? content}) =>
|
|
File(path).writeAsStringSync(content ?? '');
|
|
|
|
void newFolder(String path) => Directory(path).createSync(recursive: true);
|
|
|
|
@override
|
|
void sendNotificationToServer(NotificationMessage notification) =>
|
|
channel.sendNotification(notification);
|
|
|
|
@override
|
|
Future<ResponseMessage> sendRequestToServer(RequestMessage request) {
|
|
final completer = Completer<ResponseMessage>();
|
|
final id = request.id.map((number) => number,
|
|
(string) => throw 'String IDs not supported in tests');
|
|
_completers[id] = completer;
|
|
|
|
channel.sendRequest(request);
|
|
|
|
return completer.future;
|
|
}
|
|
|
|
@override
|
|
void sendResponseToServer(ResponseMessage response) =>
|
|
channel.sendResponse(response);
|
|
|
|
@mustCallSuper
|
|
Future<void> setUp() async {
|
|
// Set up temporary folder for the test.
|
|
projectFolderPath = Directory.systemTemp
|
|
.createTempSync('analysisServer')
|
|
.resolveSymbolicLinksSync();
|
|
newFolder(projectFolderPath);
|
|
newFolder(join(projectFolderPath, 'lib'));
|
|
projectFolderUri = Uri.file(projectFolderPath);
|
|
mainFilePath = join(projectFolderPath, 'lib', 'main.dart');
|
|
mainFileUri = Uri.file(mainFilePath);
|
|
analysisOptionsPath = join(projectFolderPath, 'analysis_options.yaml');
|
|
analysisOptionsUri = Uri.file(analysisOptionsPath);
|
|
|
|
final client = LspServerClient(instrumentationService);
|
|
this.client = client;
|
|
await client.start(vmArgs: vmArgs);
|
|
client.serverToClient.listen((message) {
|
|
if (message is ResponseMessage) {
|
|
final id = message.id!.map((number) => number,
|
|
(string) => throw 'String IDs not supported in tests');
|
|
|
|
final completer = _completers[id];
|
|
if (completer == null) {
|
|
throw 'Response with ID $id was unexpected';
|
|
} else {
|
|
_completers.remove(id);
|
|
completer.complete(message);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void tearDown() {
|
|
// TODO(dantup): Graceful shutdown?
|
|
client?.close();
|
|
}
|
|
}
|
|
|
|
class LspServerClient {
|
|
final InstrumentationService? instrumentationService;
|
|
Process? _process;
|
|
LspByteStreamServerChannel? channel;
|
|
final StreamController<Message> _serverToClient =
|
|
StreamController<Message>.broadcast();
|
|
|
|
LspServerClient(this.instrumentationService);
|
|
|
|
Future<int> get exitCode => _process!.exitCode;
|
|
|
|
Stream<Message> get serverToClient => _serverToClient.stream;
|
|
|
|
void close() {
|
|
channel?.close();
|
|
_process?.kill();
|
|
}
|
|
|
|
/// Find the root directory of the analysis_server package by proceeding
|
|
/// upward to the 'test' dir, and then going up one more directory.
|
|
String findRoot(String pathname) {
|
|
while (!['benchmark', 'test'].contains(basename(pathname))) {
|
|
var parent = dirname(pathname);
|
|
if (parent.length >= pathname.length) {
|
|
throw Exception("Can't find root directory");
|
|
}
|
|
pathname = parent;
|
|
}
|
|
return dirname(pathname);
|
|
}
|
|
|
|
Future start({List<String>? vmArgs}) async {
|
|
if (_process != null) {
|
|
throw Exception('Process already started');
|
|
}
|
|
|
|
var dartBinary = Platform.executable;
|
|
|
|
var useSnapshot = true;
|
|
String serverPath;
|
|
|
|
if (useSnapshot) {
|
|
// Look for snapshots/analysis_server.dart.snapshot.
|
|
serverPath = normalize(join(dirname(Platform.resolvedExecutable),
|
|
'snapshots', 'analysis_server.dart.snapshot'));
|
|
|
|
if (!FileSystemEntity.isFileSync(serverPath)) {
|
|
// Look for dart-sdk/bin/snapshots/analysis_server.dart.snapshot.
|
|
serverPath = normalize(join(dirname(Platform.resolvedExecutable),
|
|
'dart-sdk', 'bin', 'snapshots', 'analysis_server.dart.snapshot'));
|
|
}
|
|
} else {
|
|
final rootDir =
|
|
findRoot(Platform.script.toFilePath(windows: Platform.isWindows));
|
|
serverPath = normalize(join(rootDir, 'bin', 'server.dart'));
|
|
}
|
|
|
|
final arguments = [...?vmArgs, serverPath, '--lsp', '--suppress-analytics'];
|
|
final process = await Process.start(dartBinary, arguments);
|
|
_process = process;
|
|
process.exitCode.then((int code) {
|
|
if (code != 0) {
|
|
// TODO(dantup): Log/fail tests...
|
|
}
|
|
});
|
|
|
|
// If the server writes to stderr, fail tests with a more useful message
|
|
// (rather than having the test just hang waiting for a response).
|
|
process.stderr.listen((data) {
|
|
final message = String.fromCharCodes(data);
|
|
throw 'Analysis Server wrote to stderr:\n\n$message';
|
|
});
|
|
|
|
channel = LspByteStreamServerChannel(process.stdout, process.stdin,
|
|
instrumentationService ?? InstrumentationService.NULL_SERVICE)
|
|
..listen(_serverToClient.add);
|
|
}
|
|
}
|
|
|
|
/// An [InstrumentationLogger] that buffers logs until [debugStdio()] is called.
|
|
class PrintableLogger extends InstrumentationLogger {
|
|
bool _printLogs = false;
|
|
final _buffer = StringBuffer();
|
|
|
|
void debugStdio() {
|
|
print(_buffer.toString());
|
|
_buffer.clear();
|
|
_printLogs = true;
|
|
}
|
|
|
|
@override
|
|
void log(String message) {
|
|
if (_printLogs) {
|
|
print(message);
|
|
} else {
|
|
_buffer.writeln(message);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Future<void> shutdown() async {
|
|
_printLogs = false;
|
|
_buffer.clear();
|
|
}
|
|
}
|