Skip to content

Commit d5b82a3

Browse files
DanTupCommit Queue
authored andcommitted
[analysis_server] Create a wrapper to allow LSP helpers to be used over DTD
A minor refactor towards supporting CodeActions over DTD. The (existing) `LspRequestHelpersMixin` is a collection of helper methods like `getHover()` that allow sharing strongly-typed access to LSP methods across different kinds of tests (LSP, LSP-over-Legacy, and in-process vs out-of-process). This adds a class (`DtdHelper`) that allows those same helpers to be used to call LSP over DTD (it has to be a separate instance because these helpers already exist on the base test classes to communicate with the server directly without DTD). As part of this, the mixin was split up to separate request, reverse-requests and notifications, since only outbound requests apply over DTD and it cannot provide an implementation for the others. This change also adds `textDocument/hover` to the allow-list so that some of these tests can be unskipped (since in an upcoming change the allow-list will be removed entirely anyway). Change-Id: Id7bf40020cb5397e4cb8bbdefeed71745f58c4cc Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/428520 Reviewed-by: Samuel Rawlins <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]>
1 parent f581940 commit d5b82a3

File tree

8 files changed

+220
-177
lines changed

8 files changed

+220
-177
lines changed

pkg/analysis_server/lib/src/services/dart_tooling_daemon/dtd_services.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,12 @@ class DtdServices {
4343
/// particular, those with well defined results that are not affected by
4444
/// differences in client capabilities).
4545
static final allowedLspMethods = <Method>{
46-
// When removing this allowlist or adding simple methods like
47-
// textDocument/hover, skipped tests in `SharedDtdTests` can be unskipped.
48-
// TODO(dantup): Enable this but add a flag so we can opt-in to experimental
49-
// handlers being exposed over DTD while in dev.
46+
// TODO(dantup): Remove this allow list so that all shared methods are
47+
// available over DTD.
5048
CustomMethods.experimentalEcho,
5149
CustomMethods.dartTextDocumentEditableArguments,
5250
CustomMethods.dartTextDocumentEditArgument,
51+
Method.textDocument_hover,
5352
};
5453

5554
/// The name of the DTD service that methods will be registered under.

pkg/analysis_server/test/integration/lsp/abstract_lsp_over_legacy.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ import '../support/integration_tests.dart';
1313

1414
abstract class AbstractLspOverLegacyTest
1515
extends AbstractAnalysisServerIntegrationTest
16-
with LspRequestHelpersMixin, LspEditHelpersMixin {
16+
with
17+
LspRequestHelpersMixin,
18+
LspReverseRequestHelpersMixin,
19+
LspNotificationHelpersMixin,
20+
LspEditHelpersMixin {
1721
late final testFile = sourcePath('lib/test.dart');
1822

1923
/// A stream of LSP [NotificationMessage]s from the server.

pkg/analysis_server/test/integration/lsp_server/integration_tests.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ abstract class AbstractLspAnalysisServerIntegrationTest
2222
with
2323
ClientCapabilitiesHelperMixin,
2424
LspRequestHelpersMixin,
25+
LspReverseRequestHelpersMixin,
26+
LspNotificationHelpersMixin,
2527
LspEditHelpersMixin,
2628
LspAnalysisServerTestMixin {
2729
final List<String> vmArgs = [];

pkg/analysis_server/test/lsp/request_helpers_mixin.dart

Lines changed: 134 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@ mixin LspEditHelpersMixin {
7474
}
7575
}
7676

77+
/// Helpers to simplify handling LSP notifications.
78+
mixin LspNotificationHelpersMixin {
79+
/// A stream of [DartTextDocumentContentDidChangeParams] for any
80+
/// `dart/textDocumentContentDidChange` notifications.
81+
Stream<DartTextDocumentContentDidChangeParams>
82+
get dartTextDocumentContentDidChangeNotifications => notificationsFromServer
83+
.where(
84+
(notification) =>
85+
notification.method ==
86+
CustomMethods.dartTextDocumentContentDidChange,
87+
)
88+
.map(
89+
(message) => DartTextDocumentContentDidChangeParams.fromJson(
90+
message.params as Map<String, Object?>,
91+
),
92+
);
93+
94+
Stream<NotificationMessage> get notificationsFromServer;
95+
}
96+
7797
mixin LspProgressNotificationsMixin {
7898
Stream<NotificationMessage> get notificationsFromServer;
7999

@@ -143,25 +163,6 @@ mixin LspRequestHelpersMixin {
143163
/// should not be validated as being created by the server first.
144164
final clientProvidedTestWorkDoneToken = ProgressToken.t2('client-test');
145165

146-
/// A stream of [DartTextDocumentContentDidChangeParams] for any
147-
/// `dart/textDocumentContentDidChange` notifications.
148-
Stream<DartTextDocumentContentDidChangeParams>
149-
get dartTextDocumentContentDidChangeNotifications => notificationsFromServer
150-
.where(
151-
(notification) =>
152-
notification.method ==
153-
CustomMethods.dartTextDocumentContentDidChange,
154-
)
155-
.map(
156-
(message) => DartTextDocumentContentDidChangeParams.fromJson(
157-
message.params as Map<String, Object?>,
158-
),
159-
);
160-
161-
Stream<NotificationMessage> get notificationsFromServer;
162-
163-
Stream<RequestMessage> get requestsFromServer;
164-
165166
Future<List<CallHierarchyIncomingCall>?> callHierarchyIncoming(
166167
CallHierarchyItem item,
167168
) {
@@ -237,29 +238,6 @@ mixin LspRequestHelpersMixin {
237238
void expect(Object? actual, Matcher matcher, {String? reason}) =>
238239
test.expect(actual, matcher, reason: reason);
239240

240-
/// Expects a [method] request from the server after executing [f].
241-
Future<RequestMessage> expectRequest(
242-
Method method,
243-
FutureOr<void> Function() f, {
244-
Duration timeout = const Duration(seconds: 5),
245-
}) async {
246-
var firstRequest = requestsFromServer.firstWhere((n) => n.method == method);
247-
await f();
248-
249-
var requestFromServer = await firstRequest.timeout(
250-
timeout,
251-
onTimeout:
252-
() =>
253-
throw TimeoutException(
254-
'Did not receive the expected $method request from the server in the timeout period',
255-
timeout,
256-
),
257-
);
258-
259-
expect(requestFromServer, isNotNull);
260-
return requestFromServer;
261-
}
262-
263241
Future<T> expectSuccessfulResponseTo<T, R>(
264242
RequestMessage request,
265243
T Function(R) fromJson,
@@ -875,71 +853,6 @@ mixin LspRequestHelpersMixin {
875853
);
876854
}
877855

878-
/// Executes [f] then waits for a request of type [method] from the server which
879-
/// is passed to [handler] to process, then waits for (and returns) the
880-
/// response to the original request.
881-
///
882-
/// This is used for testing things like code actions, where the client initiates
883-
/// a request but the server does not respond to it until it's sent its own
884-
/// request to the client and it received a response.
885-
///
886-
/// Client Server
887-
/// 1. |- Req: textDocument/codeAction ->
888-
/// 1. <- Resp: textDocument/codeAction -|
889-
///
890-
/// 2. |- Req: workspace/executeCommand ->
891-
/// 3. <- Req: textDocument/applyEdits -|
892-
/// 3. |- Resp: textDocument/applyEdits ->
893-
/// 2. <- Resp: workspace/executeCommand -|
894-
///
895-
/// Request 2 from the client is not responded to until the server has its own
896-
/// response to the request it sends (3).
897-
Future<T> handleExpectedRequest<T, R, RR>(
898-
Method method,
899-
R Function(Map<String, dynamic>) fromJson,
900-
Future<T> Function() f, {
901-
required FutureOr<RR> Function(R) handler,
902-
Duration timeout = const Duration(seconds: 5),
903-
}) async {
904-
late Future<T> outboundRequest;
905-
Object? outboundRequestError;
906-
907-
// Run [f] and wait for the incoming request from the server.
908-
var incomingRequest = await expectRequest(method, () {
909-
// Don't return/await the response yet, as this may not complete until
910-
// after we have handled the request that comes from the server.
911-
outboundRequest = f();
912-
913-
// Because we don't await this future until "later", if it throws the
914-
// error is treated as unhandled and will fail the test even if expected.
915-
// Instead, capture the error and suppress it. But if we time out (in
916-
// which case we will never return outboundRequest), then we'll raise this
917-
// error.
918-
outboundRequest.then(
919-
(_) {},
920-
onError: (e) {
921-
outboundRequestError = e;
922-
return null;
923-
},
924-
);
925-
}, timeout: timeout).catchError((Object timeoutException) {
926-
// We timed out waiting for the request from the server. Probably this is
927-
// because our outbound request for some reason, so if we have an error
928-
// for that, then throw it. Otherwise, propogate the timeout.
929-
throw outboundRequestError ?? timeoutException;
930-
}, test: (e) => e is TimeoutException);
931-
932-
// Handle the request from the server and send the response back.
933-
var clientsResponse = await handler(
934-
fromJson(incomingRequest.params as Map<String, Object?>),
935-
);
936-
respondTo(incomingRequest, clientsResponse);
937-
938-
// Return a future that completes when the response to the original request
939-
// (from [f]) returns.
940-
return outboundRequest;
941-
}
942-
943856
RequestMessage makeRequest(Method method, Object? params) {
944857
var id = Either2<int, String>.t1(_id++);
945858
return RequestMessage(
@@ -1014,24 +927,8 @@ mixin LspRequestHelpersMixin {
1014927
return expectSuccessfulResponseTo(request, CompletionItem.fromJson);
1015928
}
1016929

1017-
/// Sends [responseParams] to the server as a successful response to
1018-
/// a server-initiated [request].
1019-
void respondTo<T>(RequestMessage request, T responseParams) {
1020-
sendResponseToServer(
1021-
ResponseMessage(
1022-
id: request.id,
1023-
result: responseParams,
1024-
jsonrpc: jsonRpcVersion,
1025-
),
1026-
);
1027-
}
1028-
1029930
Future<ResponseMessage> sendRequestToServer(RequestMessage request);
1030931

1031-
/// Sends a ResponseMessage to the server, completing a reverse
1032-
/// (server-to-client) request.
1033-
void sendResponseToServer(ResponseMessage response);
1034-
1035932
Future<List<TypeHierarchyItem>?> typeHierarchySubtypes(
1036933
TypeHierarchyItem item,
1037934
) {
@@ -1263,6 +1160,120 @@ mixin LspRequestHelpersMixin {
12631160
}
12641161
}
12651162

1163+
/// Helpers to simplify handling LSP reverse-requests.
1164+
///
1165+
/// The sending of responses must be supplied by the implementing class
1166+
/// via [sendResponseToServer].
1167+
mixin LspReverseRequestHelpersMixin {
1168+
/// A stream of reverse-requests from the server that can be responded to via
1169+
/// [sendResponseToServer].
1170+
Stream<RequestMessage> get requestsFromServer;
1171+
1172+
/// Expects a [method] request from the server after executing [f].
1173+
Future<RequestMessage> expectRequest(
1174+
Method method,
1175+
FutureOr<void> Function() f, {
1176+
Duration timeout = const Duration(seconds: 5),
1177+
}) async {
1178+
var firstRequest = requestsFromServer.firstWhere((n) => n.method == method);
1179+
await f();
1180+
1181+
var requestFromServer = await firstRequest.timeout(
1182+
timeout,
1183+
onTimeout:
1184+
() =>
1185+
throw TimeoutException(
1186+
'Did not receive the expected $method request from the server in the timeout period',
1187+
timeout,
1188+
),
1189+
);
1190+
1191+
expect(requestFromServer, isNotNull);
1192+
return requestFromServer;
1193+
}
1194+
1195+
/// Executes [f] then waits for a request of type [method] from the server which
1196+
/// is passed to [handler] to process, then waits for (and returns) the
1197+
/// response to the original request.
1198+
///
1199+
/// This is used for testing things like code actions, where the client initiates
1200+
/// a request but the server does not respond to it until it's sent its own
1201+
/// request to the client and it received a response.
1202+
///
1203+
/// Client Server
1204+
/// 1. |- Req: textDocument/codeAction ->
1205+
/// 1. <- Resp: textDocument/codeAction -|
1206+
///
1207+
/// 2. |- Req: workspace/executeCommand ->
1208+
/// 3. <- Req: textDocument/applyEdits -|
1209+
/// 3. |- Resp: textDocument/applyEdits ->
1210+
/// 2. <- Resp: workspace/executeCommand -|
1211+
///
1212+
/// Request 2 from the client is not responded to until the server has its own
1213+
/// response to the request it sends (3).
1214+
Future<T> handleExpectedRequest<T, R, RR>(
1215+
Method method,
1216+
R Function(Map<String, dynamic>) fromJson,
1217+
Future<T> Function() f, {
1218+
required FutureOr<RR> Function(R) handler,
1219+
Duration timeout = const Duration(seconds: 5),
1220+
}) async {
1221+
late Future<T> outboundRequest;
1222+
Object? outboundRequestError;
1223+
1224+
// Run [f] and wait for the incoming request from the server.
1225+
var incomingRequest = await expectRequest(method, () {
1226+
// Don't return/await the response yet, as this may not complete until
1227+
// after we have handled the request that comes from the server.
1228+
outboundRequest = f();
1229+
1230+
// Because we don't await this future until "later", if it throws the
1231+
// error is treated as unhandled and will fail the test even if expected.
1232+
// Instead, capture the error and suppress it. But if we time out (in
1233+
// which case we will never return outboundRequest), then we'll raise this
1234+
// error.
1235+
outboundRequest.then(
1236+
(_) {},
1237+
onError: (e) {
1238+
outboundRequestError = e;
1239+
return null;
1240+
},
1241+
);
1242+
}, timeout: timeout).catchError((Object timeoutException) {
1243+
// We timed out waiting for the request from the server. Probably this is
1244+
// because our outbound request for some reason, so if we have an error
1245+
// for that, then throw it. Otherwise, propogate the timeout.
1246+
throw outboundRequestError ?? timeoutException;
1247+
}, test: (e) => e is TimeoutException);
1248+
1249+
// Handle the request from the server and send the response back.
1250+
var clientsResponse = await handler(
1251+
fromJson(incomingRequest.params as Map<String, Object?>),
1252+
);
1253+
respondTo(incomingRequest, clientsResponse);
1254+
1255+
// Return a future that completes when the response to the original request
1256+
// (from [f]) returns.
1257+
return outboundRequest;
1258+
}
1259+
1260+
/// Sends [responseParams] to the server as a successful response to
1261+
/// a server-initiated [request].
1262+
void respondTo<T>(RequestMessage request, T responseParams) {
1263+
sendResponseToServer(
1264+
ResponseMessage(
1265+
id: request.id,
1266+
result: responseParams,
1267+
jsonrpc: jsonRpcVersion,
1268+
),
1269+
);
1270+
}
1271+
1272+
/// Sends a ResponseMessage to the server, completing a reverse
1273+
/// (server-to-client) request.
1274+
void sendResponseToServer(ResponseMessage response);
1275+
}
1276+
12661277
/// A mixin with helpers for verifying LSP edits in a given project.
12671278
///
12681279
/// Extends [LspEditHelpersMixin] with methods for accessing file state and

pkg/analysis_server/test/lsp/server_abstract.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ abstract class AbstractLspAnalysisServerTest
4949
ResourceProviderMixin,
5050
ClientCapabilitiesHelperMixin,
5151
LspRequestHelpersMixin,
52+
LspReverseRequestHelpersMixin,
53+
LspNotificationHelpersMixin,
5254
LspEditHelpersMixin,
5355
LspVerifyEditHelpersMixin,
5456
LspAnalysisServerTestMixin,
@@ -784,7 +786,12 @@ mixin ClientCapabilitiesHelperMixin {
784786
}
785787
}
786788

787-
mixin LspAnalysisServerTestMixin on LspRequestHelpersMixin, LspEditHelpersMixin
789+
mixin LspAnalysisServerTestMixin
790+
on
791+
LspRequestHelpersMixin,
792+
LspReverseRequestHelpersMixin,
793+
LspNotificationHelpersMixin,
794+
LspEditHelpersMixin
788795
implements ClientCapabilitiesHelperMixin {
789796
late String projectFolderPath,
790797
mainFilePath,

pkg/analysis_server/test/lsp_over_legacy/abstract_lsp_over_legacy.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ class EventsPrinterConfiguration {}
148148
abstract class LspOverLegacyTest extends PubPackageAnalysisServerTest
149149
with
150150
LspRequestHelpersMixin,
151+
LspReverseRequestHelpersMixin,
152+
LspNotificationHelpersMixin,
151153
LspEditHelpersMixin,
152154
ClientCapabilitiesHelperMixin,
153155
LspVerifyEditHelpersMixin,

pkg/analysis_server/test/shared/shared_code_actions_refactor_tests.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ mixin SharedConvertMethodToGetterRefactorCodeActionsTests
6969
SharedTestInterface,
7070
CodeActionsTestMixin,
7171
LspRequestHelpersMixin,
72+
LspReverseRequestHelpersMixin,
7273
LspEditHelpersMixin,
7374
LspVerifyEditHelpersMixin,
7475
ClientCapabilitiesHelperMixin {
@@ -118,6 +119,7 @@ mixin SharedExtractMethodRefactorCodeActionsTests
118119
SharedTestInterface,
119120
CodeActionsTestMixin,
120121
LspRequestHelpersMixin,
122+
LspReverseRequestHelpersMixin,
121123
LspEditHelpersMixin,
122124
LspVerifyEditHelpersMixin,
123125
ClientCapabilitiesHelperMixin,

0 commit comments

Comments
 (0)