blob: aac62315042adbd0dc6ba9db5c0ecb9a2d57914a [file] [log] [blame]
// 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_custom_generated.dart';
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:path/path.dart';
import '../../lsp/server_abstract.dart';
class AbstractLspAnalysisServerIntegrationTest
with ClientCapabilitiesHelperMixin, LspAnalysisServerTestMixin {
LspServerClient client;
final Map<int, Completer<ResponseMessage>> _completers = {};
@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.
Future<T> expectSuccessfulResponseTo<T>(RequestMessage request) async {
final resp = await sendRequestToServer(request);
if (resp.error != null) {
throw resp.error;
// TODO(dantup): It would be better if we had some code-gen'd way
// to be able to deserialise into the correct types. We could either
// code-gen this list, or code-gen the LSP client (used in tests) to
// give strongly typed sendXxx functions that return the correct types.
} else if (T == DartDiagnosticServer) {
return DartDiagnosticServer.fromJson(resp.result) as T;
} else if (T == Null) {
return resp.result == null
? null
: throw 'Expected Null response but got ${resp.result}';
} else {
throw 'Unable to deserialise ${resp.result.runtimeType} into $T.\n\n'
'You may need to extend expectSuccessfulResponseTo in '
'AbstractLspAnalysisServerIntegrationTest';
}
}
newFile(String path, {String content}) =>
File(path).writeAsStringSync(content ?? '');
newFolder(String path) => Directory(path).createSync(recursive: true);
@override
void sendNotificationToServer(NotificationMessage notification) =>
client.channel.sendNotification(notification);
@override
Future<ResponseMessage> sendRequestToServer(RequestMessage request) {
final completer = new Completer<ResponseMessage>();
final id = request.id.map(
(num) => num, (string) => throw 'String IDs not supported in tests');
_completers[id] = completer;
client.channel.sendRequest(request);
return completer.future;
}
@override
void sendResponseToServer(ResponseMessage response) =>
client.channel.sendResponse(response);
Future 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);
client = new LspServerClient();
await client.start();
client.serverToClient.listen((message) {
if (message is ResponseMessage) {
final id = message.id.map((num) => num,
(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);
}
}
});
}
tearDown() {
// TODO(dantup): Graceful shutdown?
client.close();
}
}
class LspServerClient {
Process _process;
LspByteStreamServerChannel channel;
final StreamController<Message> _serverToClient =
new StreamController<Message>.broadcast();
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))) {
String parent = dirname(pathname);
if (parent.length >= pathname.length) {
throw new Exception("Can't find root directory");
}
pathname = parent;
}
return dirname(pathname);
}
Future start() async {
if (_process != null) {
throw new Exception('Process already started');
}
String dartBinary = Platform.executable;
// TODO(dantup): The other servers integration tests can run with a snapshot
// which is much faster - we may wish to investigate doing the same here.
final rootDir =
findRoot(Platform.script.toFilePath(windows: Platform.isWindows));
final serverPath = normalize(join(rootDir, 'bin', 'server.dart'));
final arguments = [serverPath, '--lsp', '--suppress-analytics'];
_process = await Process.start(dartBinary, arguments);
_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 = new LspByteStreamServerChannel(
_process.stdout, _process.stdin, InstrumentationService.NULL_SERVICE);
channel.listen(_serverToClient.add);
}
}