|  | // Copyright (c) 2017, 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/src/channel/channel.dart'; | 
|  | import 'package:analysis_server/src/plugin/notification_manager.dart'; | 
|  | import 'package:analysis_server/src/plugin/plugin_manager.dart'; | 
|  | import 'package:analyzer/dart/analysis/context_root.dart' as analyzer; | 
|  | import 'package:analyzer/file_system/file_system.dart'; | 
|  | import 'package:analyzer/instrumentation/instrumentation.dart'; | 
|  | import 'package:analyzer_plugin/protocol/protocol.dart' as plugin; | 
|  | import 'package:analyzer_plugin/protocol/protocol_generated.dart' as plugin; | 
|  | import 'package:analyzer_plugin/src/protocol/protocol_internal.dart' as plugin; | 
|  | import 'package:test/test.dart'; | 
|  | import 'package:watcher/watcher.dart'; | 
|  |  | 
|  | /// A mock [ServerCommunicationChannel] for testing [AnalysisServer]. | 
|  | class MockServerChannel implements ServerCommunicationChannel { | 
|  | StreamController<RequestOrResponse> requestController = | 
|  | StreamController<RequestOrResponse>(); | 
|  | StreamController<Response> responseController = | 
|  | StreamController<Response>.broadcast(); | 
|  | StreamController<Notification> notificationController = | 
|  | StreamController<Notification>.broadcast(sync: true); | 
|  | Completer<Response>? errorCompleter; | 
|  |  | 
|  | List<Response> responsesReceived = []; | 
|  | List<Notification> notificationsReceived = []; | 
|  | List<Request> serverRequestsSent = []; | 
|  |  | 
|  | bool _closed = false; | 
|  |  | 
|  | String? name; | 
|  |  | 
|  | MockServerChannel(); | 
|  |  | 
|  | /// Return the broadcast stream of notifications. | 
|  | Stream<Notification> get notifications { | 
|  | return notificationController.stream; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Stream<RequestOrResponse> get requests => requestController.stream; | 
|  |  | 
|  | @override | 
|  | void close() { | 
|  | _closed = true; | 
|  | } | 
|  |  | 
|  | void expectMsgCount({responseCount = 0, notificationCount = 0}) { | 
|  | expect(responsesReceived, hasLength(responseCount)); | 
|  | expect(notificationsReceived, hasLength(notificationCount)); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void sendNotification(Notification notification) { | 
|  | // Don't deliver notifications after the connection is closed. | 
|  | if (_closed) { | 
|  | return; | 
|  | } | 
|  | notificationsReceived.add(notification); | 
|  | final errorCompleter = this.errorCompleter; | 
|  | if (errorCompleter != null && notification.event == 'server.error') { | 
|  | var params = notification.params!; | 
|  | print('[server.error] test: $name message: ${params['message']}'); | 
|  | errorCompleter.completeError(ServerError(params['message'] as String), | 
|  | StackTrace.fromString(params['stackTrace'] as String)); | 
|  | } | 
|  | // Wrap send notification in future to simulate websocket | 
|  | // TODO(scheglov): ask Dan why and decide what to do | 
|  | //    new Future(() => notificationController.add(notification)); | 
|  | notificationController.add(notification); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void sendRequest(Request request) { | 
|  | serverRequestsSent.add(request); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void sendResponse(Response response) { | 
|  | // Don't deliver responses after the connection is closed. | 
|  | if (_closed) { | 
|  | return; | 
|  | } | 
|  | responsesReceived.add(response); | 
|  | // Wrap send response in future to simulate WebSocket. | 
|  | 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. | 
|  | Future<Response> simulateRequestFromClient(Request request) { | 
|  | if (_closed) { | 
|  | throw Exception('simulateRequestFromClient after connection closed'); | 
|  | } | 
|  | // Wrap send request in future to simulate WebSocket. | 
|  | Future(() => requestController.add(request)); | 
|  | return waitForResponse(request); | 
|  | } | 
|  |  | 
|  | /// 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. | 
|  | /// | 
|  | /// Unlike [simulateRequestFromClient], this method assumes that the [request] | 
|  | /// has already been sent to the server. | 
|  | Future<Response> waitForResponse(Request request) { | 
|  | var id = request.id; | 
|  | return responseController.stream | 
|  | .firstWhere((response) => response.id == id); | 
|  | } | 
|  | } | 
|  |  | 
|  | class ServerError implements Exception { | 
|  | final String message; | 
|  |  | 
|  | ServerError(this.message); | 
|  |  | 
|  | @override | 
|  | String toString() { | 
|  | return 'Server Error: $message'; | 
|  | } | 
|  | } | 
|  |  | 
|  | /// A plugin manager that simulates broadcasting requests to plugins by | 
|  | /// hard-coding the responses. | 
|  | class TestPluginManager implements PluginManager { | 
|  | plugin.AnalysisSetPriorityFilesParams? analysisSetPriorityFilesParams; | 
|  | plugin.AnalysisSetSubscriptionsParams? analysisSetSubscriptionsParams; | 
|  | plugin.AnalysisUpdateContentParams? analysisUpdateContentParams; | 
|  | plugin.RequestParams? broadcastedRequest; | 
|  | Map<PluginInfo, Future<plugin.Response>>? broadcastResults; | 
|  | Map<PluginInfo, Future<plugin.Response>>? Function(plugin.RequestParams)? | 
|  | handleRequest; | 
|  |  | 
|  | @override | 
|  | List<PluginInfo> plugins = []; | 
|  |  | 
|  | StreamController<void> pluginsChangedController = | 
|  | StreamController.broadcast(); | 
|  |  | 
|  | @override | 
|  | String get byteStorePath { | 
|  | fail('Unexpected invocation of byteStorePath'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | InstrumentationService get instrumentationService { | 
|  | fail('Unexpected invocation of instrumentationService'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | AbstractNotificationManager get notificationManager { | 
|  | fail('Unexpected invocation of notificationManager'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Stream<void> get pluginsChanged => pluginsChangedController.stream; | 
|  |  | 
|  | @override | 
|  | ResourceProvider get resourceProvider { | 
|  | fail('Unexpected invocation of resourceProvider'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | String get sdkPath { | 
|  | fail('Unexpected invocation of sdkPath'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Future<void> addPluginToContextRoot( | 
|  | analyzer.ContextRoot contextRoot, String path) async { | 
|  | fail('Unexpected invocation of addPluginToContextRoot'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Map<PluginInfo, Future<plugin.Response>> broadcastRequest( | 
|  | plugin.RequestParams params, | 
|  | {analyzer.ContextRoot? contextRoot}) { | 
|  | broadcastedRequest = params; | 
|  | return handleRequest?.call(params) ?? | 
|  | broadcastResults ?? | 
|  | <PluginInfo, Future<plugin.Response>>{}; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Future<List<Future<plugin.Response>>> broadcastWatchEvent( | 
|  | WatchEvent watchEvent) async { | 
|  | return <Future<plugin.Response>>[]; | 
|  | } | 
|  |  | 
|  | @override | 
|  | PluginFiles filesFor(String pluginPath) { | 
|  | fail('Unexpected invocation of filesFor'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | List<PluginInfo> pluginsForContextRoot(analyzer.ContextRoot? contextRoot) { | 
|  | fail('Unexpected invocation of pluginsForContextRoot'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void recordPluginFailure(String hostPackageName, String message) { | 
|  | fail('Unexpected invocation of recordPluginFailure'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | void removedContextRoot(analyzer.ContextRoot contextRoot) { | 
|  | fail('Unexpected invocation of removedContextRoot'); | 
|  | } | 
|  |  | 
|  | @override | 
|  | Future<void> restartPlugins() async { | 
|  | // Nothing to restart. | 
|  | return; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void setAnalysisSetPriorityFilesParams( | 
|  | plugin.AnalysisSetPriorityFilesParams params) { | 
|  | analysisSetPriorityFilesParams = params; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void setAnalysisSetSubscriptionsParams( | 
|  | plugin.AnalysisSetSubscriptionsParams params) { | 
|  | analysisSetSubscriptionsParams = params; | 
|  | } | 
|  |  | 
|  | @override | 
|  | void setAnalysisUpdateContentParams( | 
|  | plugin.AnalysisUpdateContentParams params) { | 
|  | analysisUpdateContentParams = params; | 
|  | } | 
|  |  | 
|  | @override | 
|  | Future<List<void>> stopAll() async { | 
|  | fail('Unexpected invocation of stopAll'); | 
|  | } | 
|  | } |