blob: 842967751a6955e93fe60a52478c5dd2d66e7266 [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:convert';
import 'dart:io';
import 'package:test/test.dart';
import '../utils.dart' as utils;
void main() {
group(
'language-server',
defineLanguageServerTests,
timeout: utils.longTimeout,
onPlatform: {
'windows': Skip('https://github.com/dart-lang/sdk/issues/44679'),
},
);
}
void defineLanguageServerTests() {
late utils.TestProject project;
Process? process;
tearDown(() async => await project.dispose());
Future runWithLsp(List<String> args) async {
project = utils.project();
process = await project.start(args);
// Send an LSP init.
final String message = jsonEncode({
'jsonrpc': '2.0',
'id': 1,
'method': 'initialize',
'params': {
'processId': pid,
'clientInfo': {'name': 'dart-cli-tester'},
'capabilities': {},
'rootUri': project.dir.uri.toString(),
},
});
process!.stdin.write('Content-Length: ${message.length}\r\n');
process!.stdin.write('\r\n');
process!.stdin.write(message);
// Expect
final response = await _readLspMessage(process!.stdout);
final json = jsonDecode(response);
expect(json['id'], 1);
expect(json['result'], isNotNull);
final result = json['result'];
expect(result['capabilities'], isNotNull);
expect(result['serverInfo'], isNotNull);
final serverInfo = result['serverInfo'];
expect(serverInfo['name'], isNotEmpty);
process!.kill();
process = null;
}
test('protocol default', () async {
return runWithLsp(['language-server']);
});
test('protocol lsp', () async {
return runWithLsp(['language-server', '--protocol=lsp']);
});
test('protocol analyzer', () async {
project = utils.project();
process = await project.start(['language-server', '--protocol=analyzer']);
final Stream<String> inStream = process!.stdout
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter());
final line = await inStream.first;
final json = jsonDecode(line);
expect(json['event'], 'server.connected');
expect(json['params'], isNotNull);
final params = json['params'];
expect(params['version'], isNotEmpty);
expect(params['pid'], isNot(0));
process!.kill();
process = null;
});
}
/// Reads the first LSP message from [stream].
Future<String> _readLspMessage(Stream<List<int>> stream) {
// Headers are complete if there are 2x '\r\n\'. The '\r' is part of the LSP
// spec for headers and included on all platforms, not just Windows.
const lspHeaderBodySeperator = '\r\n\r\n';
final contentLengthRegExp = RegExp(r'Content-Length: (\d+)\r\n');
final completer = Completer<String>();
final buffer = StringBuffer();
late final StreamSubscription<String> inSubscription;
inSubscription = stream.transform<String>(utf8.decoder).listen(
(data) {
// Collect the output into the buffer.
buffer.write(data);
// Check whether the buffer has a complete message.
final bufferString = buffer.toString();
// To know if we have a complete message, we need to check we have the
// headers, extract the content-length, then check we have that many
// bytes in the body.
if (bufferString.contains(lspHeaderBodySeperator)) {
final parts = bufferString.split(lspHeaderBodySeperator);
final headers = parts[0];
final body = parts[1];
final length =
int.parse(contentLengthRegExp.firstMatch(headers)!.group(1)!);
// Check if we're already had the full payload.
if (body.length >= length) {
completer.complete(body.substring(0, length));
inSubscription.cancel();
}
}
},
);
return completer.future;
}