Files
sdk/pkg/dartdev/test/commands/language_server_test.dart
T
Danny Tuppeny 027618092a [dartdev] Fix flakey LSP server test
This test was written to assume that the stdout stream would deliver LSP headers and body in separate "packets", but wasn't guaranteed to be the case. The failure at https://dart-ci.appspot.com/log/pkg-linux-release/unittest-asserts-release-linux/19779/pkg/dartdev/test/commands/language_server_test shows that both header+body arrived together which caused the test to fail.

These changes buffer the output until the complete message is available and then return the body of the message.

Change-Id: I5a5ec49fdca667ae351e602ca2d589fd2ab06ba3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/251548
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
2022-07-15 17:44:30 +00:00

137 lines
3.9 KiB
Dart

// 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;
}