Implement the legacy protocol for showing messages and URIs to users

There is a desire to unify the two subclasses of the analysis server,
but I didn't attempt to do that in this CL.

Change-Id: I17bbf4f6247fc547df1c82f02e74c50eabe7aadc
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/291000
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Jaime Wren <jwren@google.com>
This commit is contained in:
Brian Wilkerson 2023-03-24 18:10:17 +00:00 committed by Commit Queue
parent 24d210a18c
commit f4bbc427c3
19 changed files with 354 additions and 102 deletions

View file

@ -58,7 +58,7 @@ class Notification {
/// A request that was received from the client.
///
/// Clients may not extend, implement or mix-in this class.
class Request {
class Request extends RequestOrResponse {
/// The name of the JSON attribute containing the id of the request.
static const String ID = 'id';
@ -73,6 +73,7 @@ class Request {
static const String CLIENT_REQUEST_TIME = 'clientRequestTime';
/// The unique identifier used to identify this request.
@override
final String id;
/// The method being requested.
@ -271,10 +272,18 @@ abstract class RequestHandler {
Response? handleRequest(Request request, CancellationToken cancellationToken);
}
/// A request or response that was received from the client.
///
/// Clients may not extend, implement or mix-in this class.
abstract class RequestOrResponse {
/// The unique identifier associated with this request or response.
String get id;
}
/// A response to a request.
///
/// Clients may not extend, implement or mix-in this class.
class Response {
class Response extends RequestOrResponse {
/// The [Response] instance that is returned when a real [Response] cannot
/// be provided at the moment.
static final Response DELAYED_RESPONSE = Response('DELAYED_RESPONSE');
@ -291,6 +300,7 @@ class Response {
/// The unique identifier used to identify the request that this response is
/// associated with.
@override
final String id;
/// The error that was caused by attempting to handle the request, or `null`
@ -540,4 +550,30 @@ class Response {
return null;
}
}
/// Return a response parsed from the given [data], or `null` if the [data] is
/// not a valid json representation of a response. The [data] is expected to
/// have the following format:
///
/// {
/// 'id': String,
/// 'result': {
/// parameter_name: value
/// }
/// }
///
/// where the result is optional.
///
/// The result can contain any number of name/value pairs.
static Response? fromString(String data) {
try {
var result = json.decode(data);
if (result is Map<String, Object?>) {
return Response.fromJson(result);
}
return null;
} catch (exception) {
return null;
}
}
}

View file

@ -86,7 +86,7 @@ abstract class ByteStreamServerChannel implements ServerCommunicationChannel {
bool _closeRequested = false;
@override
late final Stream<Request> requests = _lines.transform(
late final Stream<RequestOrResponse> requests = _lines.transform(
StreamTransformer.fromHandlers(
handleData: _readRequest,
handleDone: (sink) {
@ -135,6 +135,18 @@ abstract class ByteStreamServerChannel implements ServerCommunicationChannel {
}
}
@override
void sendRequest(Request request) {
// Don't send any further requests after the communication channel is
// closed.
if (_closeRequested) {
return;
}
var jsonEncoding = json.encode(request.toJson());
_outputLine(jsonEncoding);
_instrumentationService.logRequest(jsonEncoding);
}
@override
void sendResponse(Response response) {
// Don't send any further responses after the communication channel is
@ -159,21 +171,26 @@ abstract class ByteStreamServerChannel implements ServerCommunicationChannel {
/// Read a request from the given [data] and use the given function to handle
/// the request.
void _readRequest(String data, Sink<Request> sink) {
void _readRequest(String data, Sink<RequestOrResponse> sink) {
// Ignore any further requests after the communication channel is closed.
if (_closed.isCompleted) {
return;
}
_instrumentationService.logRequest(data);
// Parse the string as a JSON descriptor and process the resulting
// structure as a request.
var request = Request.fromString(data);
if (request == null) {
// structure as either a request or a response.
var requestOrResponse =
Request.fromString(data) ?? Response.fromString(data);
if (requestOrResponse == null) {
// If the data isn't valid, then assume it was an invalid request so that
// clients won't be left waiting for a response.
sendResponse(Response.invalidRequestFormat());
return;
}
_requestStatistics?.addRequest(request);
sink.add(request);
if (requestOrResponse is Request) {
_requestStatistics?.addRequest(requestOrResponse);
}
sink.add(requestOrResponse);
}
}

View file

@ -99,8 +99,8 @@ class ResponseConverter extends Converter<Map<String, Object?>, Response?> {
/// objects that allow an [AnalysisServer] to receive [Request]s and to return
/// both [Response]s and [Notification]s.
abstract class ServerCommunicationChannel {
/// The single-subscription stream of requests.
Stream<Request> get requests;
/// The single-subscription stream of requests and responses.
Stream<RequestOrResponse> get requests;
/// Close the communication channel.
void close();
@ -108,6 +108,9 @@ abstract class ServerCommunicationChannel {
/// Send the given [notification] to the client.
void sendNotification(Notification notification);
/// Send the given [request] to the client.
void sendRequest(Request request);
/// Send the given [response] to the client.
void sendResponse(Response response);
}

View file

@ -0,0 +1,29 @@
// Copyright (c) 2023, 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 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
import 'package:analysis_server/src/handler/legacy/legacy_handler.dart';
/// The handler for the `server.setClientCapabilities` request.
class ServerSetClientCapabilitiesHandler extends LegacyHandler {
/// Initialize a newly created handler to be able to service requests for the
/// [server].
ServerSetClientCapabilitiesHandler(
super.server, super.request, super.cancellationToken, super.performance);
@override
Future<void> handle() async {
try {
server.clientCapabilities =
ServerSetClientCapabilitiesParams.fromRequest(request);
} on RequestFailure catch (exception) {
sendResponse(exception.response);
return;
}
sendResult(ServerSetClientCapabilitiesResult());
}
}

View file

@ -72,6 +72,7 @@ import 'package:analysis_server/src/handler/legacy/search_get_element_declaratio
import 'package:analysis_server/src/handler/legacy/search_get_type_hierarchy.dart';
import 'package:analysis_server/src/handler/legacy/server_cancel_request.dart';
import 'package:analysis_server/src/handler/legacy/server_get_version.dart';
import 'package:analysis_server/src/handler/legacy/server_set_client_capabilities.dart';
import 'package:analysis_server/src/handler/legacy/server_set_subscriptions.dart';
import 'package:analysis_server/src/handler/legacy/server_shutdown.dart';
import 'package:analysis_server/src/handler/legacy/unsupported_request.dart';
@ -170,7 +171,7 @@ class AnalysisServerOptions {
class LegacyAnalysisServer extends AnalysisServer {
/// A map from the name of a request to a function used to create a request
/// handler.
static final Map<String, HandlerGenerator> handlerGenerators = {
static final Map<String, HandlerGenerator> requestHandlerGenerators = {
ANALYSIS_REQUEST_GET_ERRORS: AnalysisGetErrorsHandler.new,
ANALYSIS_REQUEST_GET_HOVER: AnalysisGetHoverHandler.new,
ANALYSIS_REQUEST_GET_IMPORTED_ELEMENTS:
@ -247,10 +248,12 @@ class LegacyAnalysisServer extends AnalysisServer {
SearchGetElementDeclarationsHandler.new,
SEARCH_REQUEST_GET_TYPE_HIERARCHY: SearchGetTypeHierarchyHandler.new,
//
SERVER_REQUEST_CANCEL_REQUEST: ServerCancelRequestHandler.new,
SERVER_REQUEST_GET_VERSION: ServerGetVersionHandler.new,
SERVER_REQUEST_SET_CLIENT_CAPABILITIES:
ServerSetClientCapabilitiesHandler.new,
SERVER_REQUEST_SET_SUBSCRIPTIONS: ServerSetSubscriptionsHandler.new,
SERVER_REQUEST_SHUTDOWN: ServerShutdownHandler.new,
SERVER_REQUEST_CANCEL_REQUEST: ServerCancelRequestHandler.new,
};
/// The channel from which requests are received and to which responses should
@ -278,6 +281,11 @@ class LegacyAnalysisServer extends AnalysisServer {
/// notifications should be sent.
Map<AnalysisService, Set<String>> analysisServices = {};
/// The most recently registered set of client capabilities. The default is to
/// have no registered requests.
ServerSetClientCapabilitiesParams clientCapabilities =
ServerSetClientCapabilitiesParams([]);
/// A table mapping [FlutterService]s to the file paths for which these
/// notifications should be sent.
Map<FlutterService, Set<String>> flutterServices = {};
@ -328,6 +336,14 @@ class LegacyAnalysisServer extends AnalysisServer {
final StreamController<Request> discardedRequests =
StreamController.broadcast(sync: true);
/// The index of the next request from the server to the client.
int nextServerRequestId = 0;
/// A table mapping the ids of requests sent from the server to the client
/// that have not yet received a response, to the completer used to return the
/// response when it has been received.
Map<String, Completer<Response>> pendingServerRequests = {};
/// Initialize a newly created server to receive requests from and send
/// responses to the given [channel].
///
@ -383,7 +399,7 @@ class LegacyAnalysisServer extends AnalysisServer {
).toNotification(),
);
debounceRequests(channel, discardedRequests)
.listen(handleRequest, onDone: done, onError: error);
.listen(handleRequestOrResponse, onDone: done, onError: error);
refactoringWorkspace = RefactoringWorkspace(driverMap.values, searchEngine);
_newRefactoringManager();
}
@ -426,6 +442,12 @@ class LegacyAnalysisServer extends AnalysisServer {
return sdkManager.defaultSdkDirectory;
}
bool get supportsOpenUriNotification =>
clientCapabilities.requests.contains('openUrlRequest');
bool get supportsShowMessageRequest =>
clientCapabilities.requests.contains('showMessageRequest');
void cancelRequest(String id) {
cancellationTokens[id]?.cancel();
}
@ -473,7 +495,7 @@ class LegacyAnalysisServer extends AnalysisServer {
var cancellationToken = CancelableToken();
cancellationTokens[request.id] = cancellationToken;
var generator = handlerGenerators[request.method];
var generator = requestHandlerGenerators[request.method];
if (generator != null) {
var handler =
generator(this, request, cancellationToken, performance);
@ -513,6 +535,23 @@ class LegacyAnalysisServer extends AnalysisServer {
});
}
/// Handle a [request] that was read from the communication channel.
void handleRequestOrResponse(RequestOrResponse requestOrResponse) {
if (requestOrResponse is Request) {
handleRequest(requestOrResponse);
} else if (requestOrResponse is Response) {
handleResponse(requestOrResponse);
}
}
/// Handle a [response] that was read from the communication channel.
void handleResponse(Response response) {
var completer = pendingServerRequests.remove(response.id);
if (completer != null) {
completer.complete(response);
}
}
/// Return `true` if the [path] is both absolute and normalized.
bool isAbsoluteAndNormalized(String path) {
var pathContext = resourceProvider.pathContext;
@ -542,6 +581,22 @@ class LegacyAnalysisServer extends AnalysisServer {
channel.sendNotification(notification);
}
Future<void> sendOpenUriNotification(Uri uri) {
assert(supportsOpenUriNotification);
var requestId = (nextServerRequestId++).toString();
var request =
ServerOpenUrlRequestParams(uri.toString()).toRequest(requestId);
return sendRequest(request);
}
/// Send the given [request] to the client.
Future<Response> sendRequest(Request request) {
var completer = Completer<Response>();
pendingServerRequests[request.id] = completer;
channel.sendRequest(request);
return completer.future;
}
/// Send the given [response] to the client.
void sendResponse(Response response) {
channel.sendResponse(response);
@ -695,6 +750,20 @@ class LegacyAnalysisServer extends AnalysisServer {
}
}
Future<String?> showUserPrompt(
MessageType type,
String message,
List<String> actionLabels,
) async {
assert(supportsShowMessageRequest);
var requestId = (nextServerRequestId++).toString();
var actions = actionLabels.map((label) => MessageAction(label)).toList();
var request = ServerShowMessageRequestParams(type, message, actions)
.toRequest(requestId);
final response = await sendRequest(request);
return response.result?['action'] as String?;
}
@override
Future<void> shutdown() async {
await super.shutdown();

View file

@ -18,26 +18,26 @@ import 'package:analysis_server/src/channel/channel.dart';
/// empty response is enough.
///
/// Discarded requests are reported into [discardedRequests].
Stream<Request> debounceRequests(
Stream<RequestOrResponse> debounceRequests(
ServerCommunicationChannel channel,
StreamController<Request> discardedRequests,
StreamController<RequestOrResponse> discardedRequests,
) {
return _DebounceRequests(channel, discardedRequests).requests;
}
class _DebounceRequests {
final ServerCommunicationChannel channel;
final StreamController<Request> discardedRequests;
late final Stream<Request> requests;
final StreamController<RequestOrResponse> discardedRequests;
late final Stream<RequestOrResponse> requests;
_DebounceRequests(this.channel, this.discardedRequests) {
var buffer = <Request>[];
var buffer = <RequestOrResponse>[];
Timer? timer;
requests = channel.requests.transform(
StreamTransformer.fromHandlers(
handleData: (request, sink) {
buffer.add(request);
handleData: (requestOrResponse, sink) {
buffer.add(requestOrResponse);
// Accumulate requests for a short period of time.
// When we were busy processing a request, the client could put
// multiple requests into the event queue. So, when we look, we will
@ -55,28 +55,31 @@ class _DebounceRequests {
);
}
List<Request> _filterCompletion(List<Request> requests) {
var reversed = <Request>[];
List<RequestOrResponse> _filterCompletion(List<RequestOrResponse> requests) {
var reversed = <RequestOrResponse>[];
var abortCompletionRequests = false;
for (var request in requests.reversed) {
if (request.method == ANALYSIS_REQUEST_UPDATE_CONTENT) {
abortCompletionRequests = true;
}
if (request.method == COMPLETION_REQUEST_GET_SUGGESTIONS2) {
if (abortCompletionRequests) {
discardedRequests.add(request);
var params = CompletionGetSuggestions2Params.fromRequest(request);
var offset = params.offset;
channel.sendResponse(
CompletionGetSuggestions2Result(offset, 0, [], true)
.toResponse(request.id),
);
continue;
} else {
for (var requestOrResponse in requests.reversed) {
if (requestOrResponse is Request) {
if (requestOrResponse.method == ANALYSIS_REQUEST_UPDATE_CONTENT) {
abortCompletionRequests = true;
}
if (requestOrResponse.method == COMPLETION_REQUEST_GET_SUGGESTIONS2) {
if (abortCompletionRequests) {
discardedRequests.add(requestOrResponse);
var params =
CompletionGetSuggestions2Params.fromRequest(requestOrResponse);
var offset = params.offset;
channel.sendResponse(
CompletionGetSuggestions2Result(offset, 0, [], true)
.toResponse(requestOrResponse.id),
);
continue;
} else {
abortCompletionRequests = true;
}
}
}
reversed.add(request);
reversed.add(requestOrResponse);
}
return reversed.reversed.toList();
}

View file

@ -129,15 +129,15 @@ class DevAnalysisServer {
}
});
await _channel
.sendRequest(Request('${_nextId++}', 'server.setSubscriptions', {
await _channel.simulateRequestFromClient(
Request('${_nextId++}', 'server.setSubscriptions', {
'subscriptions': ['STATUS'],
}));
directories =
directories.map((dir) => path.normalize(path.absolute(dir))).toList();
await _channel.sendRequest(Request(
await _channel.simulateRequestFromClient(Request(
'${_nextId++}',
'analysis.setAnalysisRoots',
{'included': directories, 'excluded': []},
@ -146,7 +146,7 @@ class DevAnalysisServer {
return whenComplete.future.whenComplete(() {
notificationSubscriptions.cancel();
_channel.sendRequest(Request(
_channel.simulateRequestFromClient(Request(
'${_nextId++}',
'analysis.setAnalysisRoots',
{'included': [], 'excluded': []},
@ -156,7 +156,7 @@ class DevAnalysisServer {
}
class DevChannel implements ServerCommunicationChannel {
final StreamController<Request> _requestController =
final StreamController<RequestOrResponse> _requestController =
StreamController.broadcast();
final StreamController<Notification> _notificationController =
@ -167,7 +167,7 @@ class DevChannel implements ServerCommunicationChannel {
Stream<Notification> get onNotification => _notificationController.stream;
@override
Stream<Request> get requests => _requestController.stream;
Stream<RequestOrResponse> get requests => _requestController.stream;
@override
void close() {
@ -179,11 +179,10 @@ class DevChannel implements ServerCommunicationChannel {
_notificationController.add(notification);
}
Future<Response> sendRequest(Request request) {
var completer = Completer<Response>();
_responseCompleters[request.id] = completer;
_requestController.add(request);
return completer.future;
@override
void sendRequest(Request request) {
throw UnimplementedError(
'sendRequest (did you mean simulateRequestFromClient?)');
}
@override
@ -191,4 +190,11 @@ class DevChannel implements ServerCommunicationChannel {
var completer = _responseCompleters.remove(response.id);
completer?.complete(response);
}
Future<Response> simulateRequestFromClient(Request request) {
var completer = Completer<Response>();
_responseCompleters[request.id] = completer;
_requestController.add(request);
return completer.future;
}
}

View file

@ -73,8 +73,9 @@ class SocketServer implements AbstractSocketServer {
var error = RequestError(
RequestErrorCode.SERVER_ALREADY_STARTED, 'Server already started');
serverChannel.sendResponse(Response('', error: error));
serverChannel.requests.listen((Request request) {
serverChannel.sendResponse(Response(request.id, error: error));
serverChannel.requests.listen((RequestOrResponse requestOrResponse) {
serverChannel
.sendResponse(Response(requestOrResponse.id, error: error));
});
return;
}

View file

@ -19,7 +19,8 @@ import 'package:watcher/watcher.dart';
/// A mock [ServerCommunicationChannel] for testing [AnalysisServer].
class MockServerChannel implements ServerCommunicationChannel {
StreamController<Request> requestController = StreamController<Request>();
StreamController<RequestOrResponse> requestController =
StreamController<RequestOrResponse>();
StreamController<Response> responseController =
StreamController<Response>.broadcast();
StreamController<Notification> notificationController =
@ -28,6 +29,7 @@ class MockServerChannel implements ServerCommunicationChannel {
List<Response> responsesReceived = [];
List<Notification> notificationsReceived = [];
List<Request> serverRequestsSent = [];
bool _closed = false;
@ -41,7 +43,7 @@ class MockServerChannel implements ServerCommunicationChannel {
}
@override
Stream<Request> get requests => requestController.stream;
Stream<RequestOrResponse> get requests => requestController.stream;
@override
void close() {
@ -73,21 +75,9 @@ class MockServerChannel implements ServerCommunicationChannel {
notificationController.add(notification);
}
/// Send the given [request] to the server and return a future that will
/// complete when a response associated with the [request] has been received.
/// The value of the future will be the received response. If [throwOnError] is
/// `true` (the default) then the returned future will throw an exception if a
/// server error is reported before the response has been received.
Future<Response> sendRequest(Request request, {bool throwOnError = true}) {
// TODO(brianwilkerson) Attempt to remove the `throwOnError` parameter and
// have the default behavior be the only behavior.
// No further requests should be sent after the connection is closed.
if (_closed) {
throw Exception('sendRequest after connection closed');
}
// Wrap send request in future to simulate WebSocket.
Future(() => requestController.add(request));
return waitForResponse(request, throwOnError: throwOnError);
@override
void sendRequest(Request request) {
serverRequestsSent.add(request);
}
@override
@ -101,14 +91,45 @@ class MockServerChannel implements ServerCommunicationChannel {
Future(() => responseController.add(response));
}
/// Send the given [request] to the server as if it had been sent from the
/// client, and return a future that will complete when a response associated
/// with the [request] has been received.
///
/// The value of the future will be the received response. If [throwOnError]
/// is `true` (the default) then the returned future will throw an exception
/// if a server error is reported before the response has been received.
Future<Response> simulateRequestFromClient(Request request,
{bool throwOnError = true}) {
// TODO(brianwilkerson) Attempt to remove the `throwOnError` parameter and
// have the default behavior be the only behavior.
// No further requests should be sent after the connection is closed.
if (_closed) {
throw Exception('simulateRequestFromClient after connection closed');
}
// Wrap send request in future to simulate WebSocket.
Future(() => requestController.add(request));
return waitForResponse(request, throwOnError: throwOnError);
}
/// Send the given [response] to the server as if it had been sent from the
/// client.
Future<void> simulateResponseFromClient(Response response) {
// No further requests should be sent after the connection is closed.
if (_closed) {
throw Exception('simulateRequestFromClient after connection closed');
}
// Wrap send request in future to simulate WebSocket.
return Future(() => requestController.add(response));
}
/// Return a future that will complete when a response associated with the
/// given [request] has been received. The value of the future will be the
/// received response. If [throwOnError] is `true` (the default) then the
/// returned future will throw an exception if a server error is reported
/// before the response has been received.
///
/// Unlike [sendRequest], this method assumes that the [request] has already
/// been sent to the server.
/// Unlike [simulateRequestFromClient], this method assumes that the [request]
/// has already been sent to the server.
Future<Response> waitForResponse(Request request,
{bool throwOnError = true}) {
// TODO(brianwilkerson) Attempt to remove the `throwOnError` parameter and

View file

@ -64,7 +64,7 @@ class A {}
await setRoots(included: [], excluded: []);
var request = _createGetErrorsRequest(testFile.path);
var response = await serverChannel.sendRequest(request);
var response = await serverChannel.simulateRequestFromClient(request);
assertResponseFailure(
response,
requestId: _requestId,

View file

@ -84,7 +84,7 @@ String f() {
/// {@tool dartpad}
/// ** See code in $examplePath **
/// {@end-tool}
///
///
/// {@tool dartpad}
/// ** See code in $example2Path **
/// {@end-tool}
@ -158,7 +158,7 @@ class Bar {
Future<void> test_fileDoesNotExist() async {
var file = convertPath('$testPackageLibPath/doesNotExist.dart');
var request = _createGetNavigationRequest(file, 0, 100);
var response = await serverChannel.sendRequest(request);
var response = await serverChannel.simulateRequestFromClient(request);
expect(response.error, isNull);
var result = response.result!;
expect(result['files'], isEmpty);
@ -442,7 +442,7 @@ void f() {
}
var request = _createGetNavigationRequest(file.path, offset, length);
var response = await serverChannel.sendRequest(request);
var response = await serverChannel.simulateRequestFromClient(request);
var result = AnalysisGetNavigationResult.fromResponse(response);
targetFiles = result.files;
targets = result.targets;

View file

@ -125,7 +125,7 @@ class ContextResolutionTest with ResourceProviderMixin {
void createDefaultFiles() {}
Future<Response> handleRequest(Request request) async {
return await serverChannel.sendRequest(request);
return await serverChannel.simulateRequestFromClient(request);
}
/// Validates that the given [request] is handled successfully.

View file

@ -322,7 +322,7 @@ analyzer:
Future<void> test_shutdown() {
var request = Request('my28', SERVER_REQUEST_SHUTDOWN);
return channel.sendRequest(request).then((Response response) {
return channel.simulateRequestFromClient(request).then((Response response) {
expect(response.id, equals('my28'));
expect(response.error, isNull);
});
@ -330,7 +330,7 @@ analyzer:
Future<void> test_unknownRequest() {
var request = Request('my22', 'randomRequest');
return channel.sendRequest(request).then((Response response) {
return channel.simulateRequestFromClient(request).then((Response response) {
expect(response.id, equals('my22'));
expect(response.error, isNotNull);
});

View file

@ -111,7 +111,7 @@ class ByteStreamServerChannelTest {
late Stream<String> outputLineStream;
/// Stream of requests received from the channel via [listen()].
late Stream<Request> requestStream;
late Stream<RequestOrResponse> requestStream;
/// Stream of errors received from the channel via [listen()].
late Stream<Object?> errorStream;
@ -129,14 +129,14 @@ class ByteStreamServerChannelTest {
var outputSink = IOSink(outputStream);
channel = InputOutputByteStreamServerChannel(
inputStream.stream, outputSink, InstrumentationService.NULL_SERVICE);
var requestStreamController = StreamController<Request>();
var requestStreamController = StreamController<RequestOrResponse>();
requestStream = requestStreamController.stream;
var errorStreamController = StreamController<Object?>();
errorStream = errorStreamController.stream;
var doneCompleter = Completer();
doneFuture = doneCompleter.future;
channel.requests.listen((Request request) {
requestStreamController.add(request);
channel.requests.listen((RequestOrResponse requestOrResponse) {
requestStreamController.add(requestOrResponse);
}, onError: (error) {
errorStreamController.add(error);
}, onDone: () {
@ -164,7 +164,7 @@ class ByteStreamServerChannelTest {
}
Future<void> test_listen_invalidRequest() {
inputSink.writeln('{"id":"0"}');
inputSink.writeln('{"garbage":"true"}');
return inputSink
.flush()
.then((_) => outputLineStream.first.timeout(Duration(seconds: 1)))
@ -198,9 +198,12 @@ class ByteStreamServerChannelTest {
return inputSink
.flush()
.then((_) => requestStream.first.timeout(Duration(seconds: 1)))
.then((Request request) {
expect(request.id, equals('0'));
expect(request.method, equals('server.version'));
.then((RequestOrResponse requestOrResponse) {
if (requestOrResponse is! Request) {
fail('Expected a Request');
}
expect(requestOrResponse.id, equals('0'));
expect(requestOrResponse.method, equals('server.version'));
});
}

View file

@ -2361,13 +2361,13 @@ extension MyClassExtension on MyClass {
var request1 =
CompletionGetSuggestionsParams(testFile.path, completionOffset)
.toRequest('7');
var responseFuture1 = serverChannel.sendRequest(request1);
var responseFuture1 = serverChannel.simulateRequestFromClient(request1);
// Make another request before the first request completes
var request2 =
CompletionGetSuggestionsParams(testFile.path, completionOffset)
.toRequest('8');
var responseFuture2 = serverChannel.sendRequest(request2);
var responseFuture2 = serverChannel.simulateRequestFromClient(request2);
// Await first response
var response1 = await responseFuture1;

View file

@ -2,6 +2,8 @@
// 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 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_constants.dart';
import 'package:analysis_server/protocol/protocol_generated.dart';
@ -31,6 +33,52 @@ class ServerDomainTest extends PubPackageAnalysisServerTest {
}));
}
Future<void> test_openUrl() async {
server.clientCapabilities.requests = ['openUrlRequest'];
// Send the request.
var responseFuture =
server.sendOpenUriNotification(toUri('https://dart.dev'));
expect(serverChannel.serverRequestsSent, hasLength(1));
// Simulate the response.
var request = serverChannel.serverRequestsSent[0];
await serverChannel.simulateResponseFromClient(
ServerOpenUrlRequestResult().toResponse(request.id));
await responseFuture;
}
Future<void> test_setClientCapabilities() async {
var requestId = -1;
Future<void> setCapabilities(
{required bool openUrlRequest,
required bool showMessageRequest}) async {
var requests = [
if (openUrlRequest) 'openUrlRequest',
if (showMessageRequest) 'showMessageRequest',
];
if (requestId >= 0) {
// This is a bit of a kludge, but the first time this function is called
// we won't set the request, we'll just test the default state.
var request = ServerSetClientCapabilitiesParams(requests)
.toRequest(requestId.toString());
await handleSuccessfulRequest(request);
}
requestId++;
expect(server.clientCapabilities.requests, requests);
expect(server.supportsOpenUriNotification, openUrlRequest);
expect(server.supportsShowMessageRequest, showMessageRequest);
}
await setCapabilities(openUrlRequest: false, showMessageRequest: false);
await setCapabilities(openUrlRequest: true, showMessageRequest: false);
await setCapabilities(openUrlRequest: true, showMessageRequest: true);
await setCapabilities(openUrlRequest: false, showMessageRequest: true);
await setCapabilities(openUrlRequest: false, showMessageRequest: false);
}
Future<void> test_setSubscriptions_invalidServiceName() async {
var request = Request('0', SERVER_REQUEST_SET_SUBSCRIPTIONS, {
SUBSCRIPTIONS: ['noSuchService']
@ -49,6 +97,22 @@ class ServerDomainTest extends PubPackageAnalysisServerTest {
expect(server.serverServices, contains(ServerService.STATUS));
}
Future<void> test_showMessage() async {
server.clientCapabilities.requests = ['showMessageRequest'];
// Send the request.
var responseFuture =
server.showUserPrompt(MessageType.WARNING, 'message', ['a', 'b']);
expect(serverChannel.serverRequestsSent, hasLength(1));
// Simulate the response.
var request = serverChannel.serverRequestsSent[0];
await serverChannel.simulateResponseFromClient(
ServerShowMessageRequestResult('a').toResponse(request.id));
var response = await responseFuture;
expect(response, 'a');
}
Future<void> test_shutdown() async {
var request = ServerShutdownParams().toRequest('0');
await handleSuccessfulRequest(request);

View file

@ -121,7 +121,7 @@ void f(A a, B b, C c, D d) {
0,
false)
.toRequest('0');
return serverChannel.sendRequest(request);
return serverChannel.simulateRequestFromClient(request);
}
}
@ -233,7 +233,7 @@ void f(A a, B b, C c, D d) {
0,
false)
.toRequest('0');
return serverChannel.sendRequest(request);
return serverChannel.simulateRequestFromClient(request);
}
}
@ -879,7 +879,7 @@ class GetAvailableRefactoringsTest extends PubPackageAnalysisServerTest {
var request =
EditGetAvailableRefactoringsParams(testFile.path, offset, length)
.toRequest('0');
var response = await serverChannel.sendRequest(request);
var response = await serverChannel.simulateRequestFromClient(request);
var result = EditGetAvailableRefactoringsResult.fromResponse(response);
kinds = result.kinds;
}
@ -1191,7 +1191,7 @@ void f() {
0,
false)
.toRequest('0');
return serverChannel.sendRequest(request);
return serverChannel.simulateRequestFromClient(request);
}
}
@ -1313,7 +1313,7 @@ void f() {
testFile.path, findOffset(search), 0, false,
options: options)
.toRequest('0');
return serverChannel.sendRequest(request);
return serverChannel.simulateRequestFromClient(request);
}
}
@ -1369,7 +1369,7 @@ import 'new_folder/file.dart';
// 0 is the id from _sendMoveRequest
// 1 is another arbitrary id for the cancel request
var request = ServerCancelRequestParams('0').toRequest('1');
return serverChannel.sendRequest(request);
return serverChannel.simulateRequestFromClient(request);
}
Future<Response> _sendAndCancelMoveRequest(String item) async {
@ -1385,7 +1385,7 @@ import 'new_folder/file.dart';
RefactoringKind.MOVE_FILE, item, 0, 0, false,
options: options)
.toRequest('0');
return serverChannel.sendRequest(request);
return serverChannel.simulateRequestFromClient(request);
}
void _setOptions(String newFile) {
@ -1402,7 +1402,7 @@ class RenameTest extends _AbstractGetRefactoring_Test {
testFile.path, findOffset(search), 0, validateOnly,
options: options)
.toRequest(id);
return serverChannel.sendRequest(request);
return serverChannel.simulateRequestFromClient(request);
}
@override
@ -2589,7 +2589,7 @@ class _AbstractGetRefactoring_Test extends PubPackageAnalysisServerTest {
kind, testFile.path, offset, length, validateOnly,
options: options)
.toRequest('0');
return serverChannel.sendRequest(request);
return serverChannel.simulateRequestFromClient(request);
}
@override

View file

@ -1017,7 +1017,7 @@ class D extends C {}
convertPath('/does/not/exist.dart'), 0,
superOnly: true)
.toRequest(requestId);
var response = await serverChannel.sendRequest(request);
var response = await serverChannel.simulateRequestFromClient(request);
var items =
SearchGetTypeHierarchyResult.fromResponse(response).hierarchyItems;
expect(items, isNull);
@ -1373,7 +1373,7 @@ enum E with M {
{bool? superOnly}) async {
await waitForTasksFinished();
var request = _createGetTypeHierarchyRequest(search, superOnly: superOnly);
var response = await serverChannel.sendRequest(request);
var response = await serverChannel.simulateRequestFromClient(request);
return SearchGetTypeHierarchyResult.fromResponse(response).hierarchyItems;
}

View file

@ -40,7 +40,7 @@ class SocketServerTest {
expect(channel2.responsesReceived[0].error!.code,
equals(RequestErrorCode.SERVER_ALREADY_STARTED));
channel2
.sendRequest(ServerShutdownParams().toRequest('0'))
.simulateRequestFromClient(ServerShutdownParams().toRequest('0'))
.then((Response response) {
expect(response.id, equals('0'));
var error = response.error!;
@ -56,7 +56,7 @@ class SocketServerTest {
expect(
channel.notificationsReceived[0].event, SERVER_NOTIFICATION_CONNECTED);
return channel
.sendRequest(ServerShutdownParams().toRequest('0'))
.simulateRequestFromClient(ServerShutdownParams().toRequest('0'))
.then((Response response) {
expect(response.id, equals('0'));
expect(response.error, isNull);