| Index: pkg/analysis_server/test/stress/utilities/server.dart
|
| diff --git a/pkg/analysis_server/test/stress/utilities/server.dart b/pkg/analysis_server/test/stress/utilities/server.dart
|
| index ee52fdd6551ecd7369699b19ed6de443218225d6..cc96aca88af5f5aa892ee70b2ba7feb89b65ba3f 100644
|
| --- a/pkg/analysis_server/test/stress/utilities/server.dart
|
| +++ b/pkg/analysis_server/test/stress/utilities/server.dart
|
| @@ -3,18 +3,24 @@
|
| // BSD-style license that can be found in the LICENSE file.
|
|
|
| /**
|
| - * Support for interacting with an analysis server running in a separate
|
| + * Support for interacting with an analysis server that is running in a separate
|
| * process.
|
| */
|
| -library analysis_server.test.stress.utilities.server;
|
| -
|
| import 'dart:async';
|
| import 'dart:collection';
|
| +import 'dart:convert' hide JsonDecoder;
|
| +import 'dart:io';
|
| +import 'dart:math' as math;
|
|
|
| import 'package:analysis_server/plugin/protocol/protocol.dart';
|
| +import 'package:path/path.dart' as path;
|
| +
|
| +import 'logger.dart';
|
|
|
| -import '../../integration/integration_test_methods.dart';
|
| -import '../../integration/integration_tests.dart' as base;
|
| +/**
|
| + * Return the current time expressed as milliseconds since the epoch.
|
| + */
|
| +int get currentTime => new DateTime.now().millisecondsSinceEpoch;
|
|
|
| /**
|
| * ???
|
| @@ -59,36 +65,222 @@ class ErrorMap {
|
| }
|
|
|
| /**
|
| - * An interface for starting and communicating with an analysis server running
|
| - * in a separate process.
|
| + * Data that has been collected about a request sent to the server.
|
| + */
|
| +class RequestData {
|
| + /**
|
| + * The unique id of the request.
|
| + */
|
| + final String id;
|
| +
|
| + /**
|
| + * The method that was requested.
|
| + */
|
| + final String method;
|
| +
|
| + /**
|
| + * The request parameters.
|
| + */
|
| + final Map<String, dynamic> params;
|
| +
|
| + /**
|
| + * The time at which the request was sent.
|
| + */
|
| + final int requestTime;
|
| +
|
| + /**
|
| + * The time at which the response was received, or `null` if no response has
|
| + * been received.
|
| + */
|
| + int responseTime = null;
|
| +
|
| + /**
|
| + * The response that was received.
|
| + */
|
| + Response _response;
|
| +
|
| + /**
|
| + * The completer that will be completed when a response is received.
|
| + */
|
| + Completer<Response> _responseCompleter;
|
| +
|
| + /**
|
| + * Initialize a newly created set of request data.
|
| + */
|
| + RequestData(this.id, this.method, this.params, this.requestTime);
|
| +
|
| + /**
|
| + * Return the number of milliseconds that elapsed betwee the request and the
|
| + * response. This getter assumes that the response was received.
|
| + */
|
| + int get elapsedTime => responseTime - requestTime;
|
| +
|
| + /**
|
| + * Return a future that will complete when a response is received.
|
| + */
|
| + Future<Response> get respondedTo {
|
| + if (_response != null) {
|
| + return new Future.value(_response);
|
| + }
|
| + if (_responseCompleter == null) {
|
| + _responseCompleter = new Completer<Response>();
|
| + }
|
| + return _responseCompleter.future;
|
| + }
|
| +
|
| + /**
|
| + * Record that the given [response] was received.
|
| + */
|
| + void recordResponse(Response response) {
|
| + if (_response != null) {
|
| + stdout.writeln(
|
| + 'Received a second response to a $method request (id = $id)');
|
| + return;
|
| + }
|
| + responseTime = currentTime;
|
| + _response = response;
|
| + if (_responseCompleter != null) {
|
| + _responseCompleter.complete(response);
|
| + _responseCompleter = null;
|
| + }
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A utility for starting and communicating with an analysis server that is
|
| + * running in a separate process.
|
| */
|
| -class Server extends base.Server with IntegrationTestMixin {
|
| +class Server {
|
| + /**
|
| + * The label used for communications from the client.
|
| + */
|
| + static const String fromClient = 'client';
|
| +
|
| + /**
|
| + * The label used for normal communications from the server.
|
| + */
|
| + static const String fromServer = 'server';
|
| +
|
| + /**
|
| + * The label used for output written by the server on [fromStderr].
|
| + */
|
| + static const String fromStderr = 'stderr';
|
| +
|
| + /**
|
| + * The logger to which the communications log should be written, or `null` if
|
| + * the log should not be written.
|
| + */
|
| + final Logger logger;
|
| +
|
| + /**
|
| + * The process in which the server is running, or `null` if the server hasn't
|
| + * been started yet.
|
| + */
|
| + Process _process = null;
|
| +
|
| + /**
|
| + * Number that should be used to compute the 'id' to send in the next command
|
| + * sent to the server.
|
| + */
|
| + int _nextId = 0;
|
| +
|
| + /**
|
| + * The analysis roots that are included.
|
| + */
|
| + List<String> _analysisRootIncludes = <String>[];
|
| +
|
| + /**
|
| + * The analysis roots that are excluded.
|
| + */
|
| + List<String> _analysisRootExcludes = <String>[];
|
| +
|
| /**
|
| * A list containing the paths of files for which an overlay has been created.
|
| */
|
| List<String> filesWithOverlays = <String>[];
|
|
|
| /**
|
| + * The files that the server reported as being analyzed.
|
| + */
|
| + List<String> _analyzedFiles = <String>[];
|
| +
|
| + /**
|
| * A mapping from the absolute paths of files to the most recent set of errors
|
| * received for that file.
|
| */
|
| ErrorMap _errorMap = new ErrorMap();
|
|
|
| /**
|
| + * The completer that will be completed the next time a 'server.status'
|
| + * notification is received from the server with 'analyzing' set to false.
|
| + */
|
| + Completer<Null> _analysisFinishedCompleter;
|
| +
|
| + /**
|
| + * The completer that will be completed the next time a 'server.connected'
|
| + * notification is received from the server.
|
| + */
|
| + Completer<Null> _serverConnectedCompleter;
|
| +
|
| + /**
|
| + * A table mapping the ids of requests that have been sent to the server to
|
| + * data about those requests.
|
| + */
|
| + final Map<String, RequestData> _requestDataMap = <String, RequestData>{};
|
| +
|
| + /**
|
| + * A table mapping the number of times a request whose 'event' is equal to the
|
| + * key was sent to the server.
|
| + */
|
| + final Map<String, int> _notificationCountMap = <String, int>{};
|
| +
|
| + /**
|
| * Initialize a new analysis server. The analysis server is not running and
|
| * must be started using [start].
|
| + *
|
| + * If a [logger] is provided, the communications between the client (this
|
| + * test) and the server will be written to it.
|
| + */
|
| + Server({this.logger = null});
|
| +
|
| + /**
|
| + * Return a future that will complete when a 'server.status' notification is
|
| + * received from the server with 'analyzing' set to false.
|
| + *
|
| + * The future will only be completed by 'server.status' notifications that are
|
| + * received after this function call, so it is safe to use this getter
|
| + * multiple times in one test; each time it is used it will wait afresh for
|
| + * analysis to finish.
|
| */
|
| - Server() {
|
| - initializeInttestMixin();
|
| - onAnalysisErrors.listen(_recordErrors);
|
| + Future get analysisFinished {
|
| + if (_analysisFinishedCompleter == null) {
|
| + _analysisFinishedCompleter = new Completer();
|
| + }
|
| + return _analysisFinishedCompleter.future;
|
| }
|
|
|
| /**
|
| * Return a list of the paths of files that are currently being analyzed.
|
| */
|
| List<String> get analyzedDartFiles {
|
| - // TODO(brianwilkerson) Implement this.
|
| - return <String>[];
|
| + bool isAnalyzed(String filePath) {
|
| + // TODO(brianwilkerson) This should use the path package to determine
|
| + // inclusion, and needs to take exclusions into account.
|
| + for (String includedRoot in _analysisRootIncludes) {
|
| + if (filePath.startsWith(includedRoot)) {
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| + }
|
| +
|
| + List<String> analyzedFiles = <String>[];
|
| + for (String filePath in _analyzedFiles) {
|
| + if (filePath.endsWith('.dart') && isAnalyzed(filePath)) {
|
| + analyzedFiles.add(filePath);
|
| + }
|
| + }
|
| + return analyzedFiles;
|
| }
|
|
|
| /**
|
| @@ -98,9 +290,6 @@ class Server extends base.Server with IntegrationTestMixin {
|
| */
|
| ErrorMap get errorMap => new ErrorMap.from(_errorMap);
|
|
|
| - @override
|
| - base.Server get server => this;
|
| -
|
| /**
|
| * Compute a mapping from each of the file paths in the given list of
|
| * [filePaths] to the list of errors in the file at that path.
|
| @@ -109,9 +298,13 @@ class Server extends base.Server with IntegrationTestMixin {
|
| ErrorMap errorMap = new ErrorMap();
|
| List<Future> futures = <Future>[];
|
| for (String filePath in filePaths) {
|
| - futures.add(sendAnalysisGetErrors(filePath)
|
| - .then((AnalysisGetErrorsResult result) {
|
| - errorMap[filePath] = result.errors;
|
| + RequestData requestData = sendAnalysisGetErrors(filePath);
|
| + futures.add(requestData.respondedTo.then((Response response) {
|
| + if (response.result != null) {
|
| + AnalysisGetErrorsResult result =
|
| + new AnalysisGetErrorsResult.fromResponse(response);
|
| + errorMap[filePath] = result.errors;
|
| + }
|
| }));
|
| }
|
| await Future.wait(futures);
|
| @@ -119,19 +312,168 @@ class Server extends base.Server with IntegrationTestMixin {
|
| }
|
|
|
| /**
|
| + * Print information about the communications with the server.
|
| + */
|
| + void printStatistics() {
|
| + void writeSpaces(int count) {
|
| + for (int i = 0; i < count; i++) {
|
| + stdout.write(' ');
|
| + }
|
| + }
|
| +
|
| + //
|
| + // Print information about the requests that were sent.
|
| + //
|
| + stdout.writeln('Request Counts');
|
| + if (_requestDataMap.isEmpty) {
|
| + stdout.writeln(' none');
|
| + } else {
|
| + Map<String, List<RequestData>> requestsByMethod =
|
| + <String, List<RequestData>>{};
|
| + _requestDataMap.values.forEach((RequestData requestData) {
|
| + requestsByMethod
|
| + .putIfAbsent(requestData.method, () => <RequestData>[])
|
| + .add(requestData);
|
| + });
|
| + List<String> keys = requestsByMethod.keys.toList();
|
| + keys.sort();
|
| + int maxCount = requestsByMethod.values
|
| + .fold(0, (int count, List<RequestData> list) => count + list.length);
|
| + int countWidth = maxCount.toString().length;
|
| + for (String key in keys) {
|
| + List<RequestData> requests = requestsByMethod[key];
|
| + int noResponseCount = 0;
|
| + int responseCount = 0;
|
| + int minTime = -1;
|
| + int maxTime = -1;
|
| + int totalTime = 0;
|
| + requests.forEach((RequestData data) {
|
| + if (data.responseTime == null) {
|
| + noResponseCount++;
|
| + } else {
|
| + responseCount++;
|
| + int time = data.elapsedTime;
|
| + minTime = minTime < 0 ? time : math.min(minTime, time);
|
| + maxTime = math.max(maxTime, time);
|
| + totalTime += time;
|
| + }
|
| + });
|
| + String count = requests.length.toString();
|
| + writeSpaces(countWidth - count.length);
|
| + stdout.write(' ');
|
| + stdout.write(count);
|
| + stdout.write(' - ');
|
| + stdout.write(key);
|
| + if (noResponseCount > 0) {
|
| + stdout.write(', ');
|
| + stdout.write(noResponseCount);
|
| + stdout.write(' with no response');
|
| + }
|
| + if (maxTime >= 0) {
|
| + stdout.write(' (');
|
| + stdout.write(minTime);
|
| + stdout.write(', ');
|
| + stdout.write(totalTime / responseCount);
|
| + stdout.write(', ');
|
| + stdout.write(maxTime);
|
| + stdout.write(')');
|
| + }
|
| + stdout.writeln();
|
| + }
|
| + }
|
| + //
|
| + // Print information about the notifications that were received.
|
| + //
|
| + stdout.writeln();
|
| + stdout.writeln('Notification Counts');
|
| + if (_notificationCountMap.isEmpty) {
|
| + stdout.writeln(' none');
|
| + } else {
|
| + List<String> keys = _notificationCountMap.keys.toList();
|
| + keys.sort();
|
| + int maxCount = _notificationCountMap.values.fold(0, math.max);
|
| + int countWidth = maxCount.toString().length;
|
| + for (String key in keys) {
|
| + String count = _notificationCountMap[key].toString();
|
| + writeSpaces(countWidth - count.length);
|
| + stdout.write(' ');
|
| + stdout.write(count);
|
| + stdout.write(' - ');
|
| + stdout.writeln(key);
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| * Remove any existing overlays.
|
| */
|
| - Future<AnalysisUpdateContentResult> removeAllOverlays() {
|
| + void removeAllOverlays() {
|
| Map<String, dynamic> files = new HashMap<String, dynamic>();
|
| for (String path in filesWithOverlays) {
|
| files[path] = new RemoveContentOverlay();
|
| }
|
| - return sendAnalysisUpdateContent(files);
|
| + sendAnalysisUpdateContent(files);
|
| + }
|
| +
|
| + RequestData sendAnalysisGetErrors(String file) {
|
| + var params = new AnalysisGetErrorsParams(file).toJson();
|
| + return _send("analysis.getErrors", params);
|
| + }
|
| +
|
| + RequestData sendAnalysisGetHover(String file, int offset) {
|
| + var params = new AnalysisGetHoverParams(file, offset).toJson();
|
| + return _send("analysis.getHover", params);
|
| + }
|
| +
|
| + RequestData sendAnalysisGetLibraryDependencies() {
|
| + return _send("analysis.getLibraryDependencies", null);
|
| + }
|
| +
|
| + RequestData sendAnalysisGetNavigation(String file, int offset, int length) {
|
| + var params = new AnalysisGetNavigationParams(file, offset, length).toJson();
|
| + return _send("analysis.getNavigation", params);
|
| + }
|
| +
|
| + RequestData sendAnalysisGetReachableSources(String file) {
|
| + var params = new AnalysisGetReachableSourcesParams(file).toJson();
|
| + return _send("analysis.getReachableSources", params);
|
| + }
|
| +
|
| + void sendAnalysisReanalyze({List<String> roots}) {
|
| + var params = new AnalysisReanalyzeParams(roots: roots).toJson();
|
| + _send("analysis.reanalyze", params);
|
| + }
|
| +
|
| + void sendAnalysisSetAnalysisRoots(
|
| + List<String> included, List<String> excluded,
|
| + {Map<String, String> packageRoots}) {
|
| + _analysisRootIncludes = included;
|
| + _analysisRootExcludes = excluded;
|
| + var params = new AnalysisSetAnalysisRootsParams(included, excluded,
|
| + packageRoots: packageRoots)
|
| + .toJson();
|
| + _send("analysis.setAnalysisRoots", params);
|
| + }
|
| +
|
| + void sendAnalysisSetGeneralSubscriptions(
|
| + List<GeneralAnalysisService> subscriptions) {
|
| + var params =
|
| + new AnalysisSetGeneralSubscriptionsParams(subscriptions).toJson();
|
| + _send("analysis.setGeneralSubscriptions", params);
|
| + }
|
| +
|
| + void sendAnalysisSetPriorityFiles(List<String> files) {
|
| + var params = new AnalysisSetPriorityFilesParams(files).toJson();
|
| + _send("analysis.setPriorityFiles", params);
|
| + }
|
| +
|
| + void sendAnalysisSetSubscriptions(
|
| + Map<AnalysisService, List<String>> subscriptions) {
|
| + var params = new AnalysisSetSubscriptionsParams(subscriptions).toJson();
|
| + _send("analysis.setSubscriptions", params);
|
| }
|
|
|
| - @override
|
| - Future<AnalysisUpdateContentResult> sendAnalysisUpdateContent(
|
| - Map<String, dynamic> files) {
|
| + void sendAnalysisUpdateContent(Map<String, dynamic> files) {
|
| files.forEach((String path, dynamic overlay) {
|
| if (overlay is AddContentOverlay) {
|
| filesWithOverlays.add(path);
|
| @@ -139,14 +481,482 @@ class Server extends base.Server with IntegrationTestMixin {
|
| filesWithOverlays.remove(path);
|
| }
|
| });
|
| - return super.sendAnalysisUpdateContent(files);
|
| + var params = new AnalysisUpdateContentParams(files).toJson();
|
| + _send('analysis.updateContent', params);
|
| + }
|
| +
|
| + void sendAnalysisUpdateOptions(AnalysisOptions options) {
|
| + var params = new AnalysisUpdateOptionsParams(options).toJson();
|
| + _send("analysis.updateOptions", params);
|
| + }
|
| +
|
| + void sendCompletionGetSuggestions(String file, int offset) {
|
| + var params = new CompletionGetSuggestionsParams(file, offset).toJson();
|
| + _send("completion.getSuggestions", params);
|
| + }
|
| +
|
| + RequestData sendDiagnosticGetDiagnostics() {
|
| + return _send("diagnostic.getDiagnostics", null);
|
| + }
|
| +
|
| + RequestData sendEditFormat(
|
| + String file, int selectionOffset, int selectionLength,
|
| + {int lineLength}) {
|
| + var params = new EditFormatParams(file, selectionOffset, selectionLength,
|
| + lineLength: lineLength)
|
| + .toJson();
|
| + return _send("edit.format", params);
|
| + }
|
| +
|
| + RequestData sendEditGetAssists(String file, int offset, int length) {
|
| + var params = new EditGetAssistsParams(file, offset, length).toJson();
|
| + return _send("edit.getAssists", params);
|
| + }
|
| +
|
| + RequestData sendEditGetAvailableRefactorings(
|
| + String file, int offset, int length) {
|
| + var params =
|
| + new EditGetAvailableRefactoringsParams(file, offset, length).toJson();
|
| + return _send("edit.getAvailableRefactorings", params);
|
| + }
|
| +
|
| + RequestData sendEditGetFixes(String file, int offset) {
|
| + var params = new EditGetFixesParams(file, offset).toJson();
|
| + return _send("edit.getFixes", params);
|
| + }
|
| +
|
| + RequestData sendEditGetRefactoring(RefactoringKind kind, String file,
|
| + int offset, int length, bool validateOnly,
|
| + {RefactoringOptions options}) {
|
| + var params = new EditGetRefactoringParams(
|
| + kind, file, offset, length, validateOnly,
|
| + options: options)
|
| + .toJson();
|
| + return _send("edit.getRefactoring", params);
|
| + }
|
| +
|
| + RequestData sendEditOrganizeDirectives(String file) {
|
| + var params = new EditOrganizeDirectivesParams(file).toJson();
|
| + return _send("edit.organizeDirectives", params);
|
| + }
|
| +
|
| + RequestData sendEditSortMembers(String file) {
|
| + var params = new EditSortMembersParams(file).toJson();
|
| + return _send("edit.sortMembers", params);
|
| + }
|
| +
|
| + RequestData sendExecutionCreateContext(String contextRoot) {
|
| + var params = new ExecutionCreateContextParams(contextRoot).toJson();
|
| + return _send("execution.createContext", params);
|
| + }
|
| +
|
| + RequestData sendExecutionDeleteContext(String id) {
|
| + var params = new ExecutionDeleteContextParams(id).toJson();
|
| + return _send("execution.deleteContext", params);
|
| + }
|
| +
|
| + RequestData sendExecutionMapUri(String id, {String file, String uri}) {
|
| + var params = new ExecutionMapUriParams(id, file: file, uri: uri).toJson();
|
| + return _send("execution.mapUri", params);
|
| + }
|
| +
|
| + RequestData sendExecutionSetSubscriptions(
|
| + List<ExecutionService> subscriptions) {
|
| + var params = new ExecutionSetSubscriptionsParams(subscriptions).toJson();
|
| + return _send("execution.setSubscriptions", params);
|
| + }
|
| +
|
| + void sendSearchFindElementReferences(
|
| + String file, int offset, bool includePotential) {
|
| + var params =
|
| + new SearchFindElementReferencesParams(file, offset, includePotential)
|
| + .toJson();
|
| + _send("search.findElementReferences", params);
|
| + }
|
| +
|
| + void sendSearchFindMemberDeclarations(String name) {
|
| + var params = new SearchFindMemberDeclarationsParams(name).toJson();
|
| + _send("search.findMemberDeclarations", params);
|
| + }
|
| +
|
| + void sendSearchFindMemberReferences(String name) {
|
| + var params = new SearchFindMemberReferencesParams(name).toJson();
|
| + _send("search.findMemberReferences", params);
|
| + }
|
| +
|
| + void sendSearchFindTopLevelDeclarations(String pattern) {
|
| + var params = new SearchFindTopLevelDeclarationsParams(pattern).toJson();
|
| + _send("search.findTopLevelDeclarations", params);
|
| + }
|
| +
|
| + void sendSearchGetTypeHierarchy(String file, int offset, {bool superOnly}) {
|
| + var params =
|
| + new SearchGetTypeHierarchyParams(file, offset, superOnly: superOnly)
|
| + .toJson();
|
| + _send("search.getTypeHierarchy", params);
|
| + }
|
| +
|
| + RequestData sendServerGetVersion() {
|
| + return _send("server.getVersion", null);
|
| + }
|
| +
|
| + void sendServerSetSubscriptions(List<ServerService> subscriptions) {
|
| + var params = new ServerSetSubscriptionsParams(subscriptions).toJson();
|
| + _send("server.setSubscriptions", params);
|
| + }
|
| +
|
| + void sendServerShutdown() {
|
| + _send("server.shutdown", null);
|
| + }
|
| +
|
| + /**
|
| + * Start the server and listen for communications from it.
|
| + *
|
| + * If [checked] is `true`, the server's VM will be running in checked mode.
|
| + *
|
| + * If [debugServer] is `true`, the server will be started with "--debug",
|
| + * allowing a debugger to be attached.
|
| + *
|
| + * If [diagnosticPort] is not `null`, the server will serve status pages to
|
| + * the specified port.
|
| + *
|
| + * If [enableNewAnalysisDriver] is `true`, the server will use the new
|
| + * analysis driver.
|
| + *
|
| + * If [profileServer] is `true`, the server will be started with "--observe"
|
| + * and "--pause-isolates-on-exit", allowing the observatory to be used.
|
| + *
|
| + * If [useAnalysisHighlight2] is `true`, the server will use the new highlight
|
| + * APIs.
|
| + */
|
| + Future<Null> start(
|
| + {bool checked: true,
|
| + bool debugServer: false,
|
| + int diagnosticPort,
|
| + bool enableNewAnalysisDriver: false,
|
| + bool profileServer: false,
|
| + String sdkPath,
|
| + int servicesPort,
|
| + bool useAnalysisHighlight2: false}) async {
|
| + if (_process != null) {
|
| + throw new Exception('Process already started');
|
| + }
|
| + String dartBinary = Platform.executable;
|
| + String rootDir =
|
| + _findRoot(Platform.script.toFilePath(windows: Platform.isWindows));
|
| + String serverPath =
|
| + path.normalize(path.join(rootDir, 'bin', 'server.dart'));
|
| + List<String> arguments = [];
|
| + //
|
| + // Add VM arguments.
|
| + //
|
| + if (debugServer) {
|
| + arguments.add('--debug');
|
| + }
|
| + if (profileServer) {
|
| + if (servicesPort == null) {
|
| + arguments.add('--observe');
|
| + } else {
|
| + arguments.add('--observe=$servicesPort');
|
| + }
|
| + arguments.add('--pause-isolates-on-exit');
|
| + } else if (servicesPort != null) {
|
| + arguments.add('--enable-vm-service=$servicesPort');
|
| + }
|
| + if (Platform.packageRoot != null) {
|
| + arguments.add('--package-root=${Platform.packageRoot}');
|
| + }
|
| + if (Platform.packageConfig != null) {
|
| + arguments.add('--packages=${Platform.packageConfig}');
|
| + }
|
| + if (checked) {
|
| + arguments.add('--checked');
|
| + }
|
| + //
|
| + // Add the server executable.
|
| + //
|
| + arguments.add(serverPath);
|
| + //
|
| + // Add server arguments.
|
| + //
|
| + if (diagnosticPort != null) {
|
| + arguments.add('--port');
|
| + arguments.add(diagnosticPort.toString());
|
| + }
|
| + if (sdkPath != null) {
|
| + arguments.add('--sdk=$sdkPath');
|
| + }
|
| + if (useAnalysisHighlight2) {
|
| + arguments.add('--useAnalysisHighlight2');
|
| + }
|
| + if (enableNewAnalysisDriver) {
|
| + arguments.add('--enable-new-analysis-driver');
|
| + }
|
| +// stdout.writeln('Launching $serverPath');
|
| +// stdout.writeln('$dartBinary ${arguments.join(' ')}');
|
| + _process = await Process.start(dartBinary, arguments);
|
| + _process.exitCode.then((int code) {
|
| + if (code != 0) {
|
| + throw new StateError('Server terminated with exit code $code');
|
| + }
|
| + });
|
| + _listenToOutput();
|
| + _serverConnectedCompleter = new Completer();
|
| + return _serverConnectedCompleter.future;
|
| + }
|
| +
|
| + /**
|
| + * Find the root directory of the analysis_server package by proceeding
|
| + * upward to the 'test' dir, and then going up one more directory.
|
| + */
|
| + String _findRoot(String pathname) {
|
| + while (!['benchmark', 'test'].contains(path.basename(pathname))) {
|
| + String parent = path.dirname(pathname);
|
| + if (parent.length >= pathname.length) {
|
| + throw new Exception("Can't find root directory");
|
| + }
|
| + pathname = parent;
|
| + }
|
| + return path.dirname(pathname);
|
| }
|
|
|
| /**
|
| - * Record the errors in the given [params].
|
| + * Handle a [notification] received from the server.
|
| */
|
| - void _recordErrors(AnalysisErrorsParams params) {
|
| - _errorMap[params.file] = params.errors;
|
| + void _handleNotification(Notification notification) {
|
| + switch (notification.event) {
|
| + case "server.connected":
|
| +// new ServerConnectedParams.fromNotification(notification);
|
| + _serverConnectedCompleter.complete(null);
|
| + break;
|
| + case "server.error":
|
| +// new ServerErrorParams.fromNotification(notification);
|
| + throw new StateError('Server error: ${notification.toJson()}');
|
| + break;
|
| + case "server.status":
|
| + if (_analysisFinishedCompleter != null) {
|
| + ServerStatusParams params =
|
| + new ServerStatusParams.fromNotification(notification);
|
| + var analysis = params.analysis;
|
| + if (analysis != null && !analysis.isAnalyzing) {
|
| + _analysisFinishedCompleter.complete(null);
|
| + }
|
| + }
|
| + break;
|
| + case "analysis.analyzedFiles":
|
| + AnalysisAnalyzedFilesParams params =
|
| + new AnalysisAnalyzedFilesParams.fromNotification(notification);
|
| + _analyzedFiles = params.directories;
|
| + break;
|
| + case "analysis.errors":
|
| + AnalysisErrorsParams params =
|
| + new AnalysisErrorsParams.fromNotification(notification);
|
| + _errorMap.pathMap[params.file] = params.errors;
|
| + break;
|
| + case "analysis.flushResults":
|
| +// new AnalysisFlushResultsParams.fromNotification(notification);
|
| + _errorMap.pathMap.clear();
|
| + break;
|
| + case "analysis.folding":
|
| +// new AnalysisFoldingParams.fromNotification(notification);
|
| + break;
|
| + case "analysis.highlights":
|
| +// new AnalysisHighlightsParams.fromNotification(notification);
|
| + break;
|
| + case "analysis.implemented":
|
| +// new AnalysisImplementedParams.fromNotification(notification);
|
| + break;
|
| + case "analysis.invalidate":
|
| +// new AnalysisInvalidateParams.fromNotification(notification);
|
| + break;
|
| + case "analysis.navigation":
|
| +// new AnalysisNavigationParams.fromNotification(notification);
|
| + break;
|
| + case "analysis.occurrences":
|
| +// new AnalysisOccurrencesParams.fromNotification(notification);
|
| + break;
|
| + case "analysis.outline":
|
| +// new AnalysisOutlineParams.fromNotification(notification);
|
| + break;
|
| + case "analysis.overrides":
|
| +// new AnalysisOverridesParams.fromNotification(notification);
|
| + break;
|
| + case "completion.results":
|
| +// new CompletionResultsParams.fromNotification(notification);
|
| + break;
|
| + case "search.results":
|
| +// new SearchResultsParams.fromNotification(notification);
|
| + break;
|
| + case "execution.launchData":
|
| +// new ExecutionLaunchDataParams.fromNotification(notification);
|
| + break;
|
| + default:
|
| + throw new StateError(
|
| + 'Unhandled notification: ${notification.toJson()}');
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Handle a [response] received from the server.
|
| + */
|
| + void _handleResponse(Response response) {
|
| + String id = response.id.toString();
|
| + RequestData requestData = _requestDataMap[id];
|
| + requestData.recordResponse(response);
|
| +// switch (requestData.method) {
|
| +// case "analysis.getErrors":
|
| +// break;
|
| +// case "analysis.getHover":
|
| +// break;
|
| +// case "analysis.getLibraryDependencies":
|
| +// break;
|
| +// case "analysis.getNavigation":
|
| +// break;
|
| +// case "analysis.getReachableSources":
|
| +// break;
|
| +// case "analysis.reanalyze":
|
| +// break;
|
| +// case "analysis.setAnalysisRoots":
|
| +// break;
|
| +// case "analysis.setGeneralSubscriptions":
|
| +// break;
|
| +// case "analysis.setPriorityFiles":
|
| +// break;
|
| +// case "analysis.setSubscriptions":
|
| +// break;
|
| +// case 'analysis.updateContent':
|
| +// break;
|
| +// case "analysis.updateOptions":
|
| +// break;
|
| +// case "completion.getSuggestions":
|
| +// break;
|
| +// case "diagnostic.getDiagnostics":
|
| +// break;
|
| +// case "edit.format":
|
| +// break;
|
| +// case "edit.getAssists":
|
| +// break;
|
| +// case "edit.getAvailableRefactorings":
|
| +// break;
|
| +// case "edit.getFixes":
|
| +// break;
|
| +// case "edit.getRefactoring":
|
| +// break;
|
| +// case "edit.organizeDirectives":
|
| +// break;
|
| +// case "edit.sortMembers":
|
| +// break;
|
| +// case "execution.createContext":
|
| +// break;
|
| +// case "execution.deleteContext":
|
| +// break;
|
| +// case "execution.mapUri":
|
| +// break;
|
| +// case "execution.setSubscriptions":
|
| +// break;
|
| +// case "search.findElementReferences":
|
| +// break;
|
| +// case "search.findMemberDeclarations":
|
| +// break;
|
| +// case "search.findMemberReferences":
|
| +// break;
|
| +// case "search.findTopLevelDeclarations":
|
| +// break;
|
| +// case "search.getTypeHierarchy":
|
| +// break;
|
| +// case "server.getVersion":
|
| +// break;
|
| +// case "server.setSubscriptions":
|
| +// break;
|
| +// case "server.shutdown":
|
| +// break;
|
| +// default:
|
| +// throw new StateError('Unhandled response: ${response.toJson()}');
|
| +// }
|
| + }
|
| +
|
| + /**
|
| + * Handle a [line] of input read from stderr.
|
| + */
|
| + void _handleStdErr(String line) {
|
| + String trimmedLine = line.trim();
|
| + logger?.log(fromStderr, '$trimmedLine');
|
| + throw new StateError('Message received on stderr: "$trimmedLine"');
|
| + }
|
| +
|
| + /**
|
| + * Handle a [line] of input read from stdout.
|
| + */
|
| + void _handleStdOut(String line) {
|
| + /**
|
| + * Cast the given [value] to a Map, or throw an [ArgumentError] if the value
|
| + * cannot be cast.
|
| + */
|
| + Map asMap(Object value) {
|
| + if (value is Map) {
|
| + return value;
|
| + }
|
| + throw new ArgumentError('Expected a Map, found a ${value.runtimeType}');
|
| + }
|
| +
|
| + String trimmedLine = line.trim();
|
| + if (trimmedLine.isEmpty ||
|
| + trimmedLine.startsWith('Observatory listening on ')) {
|
| + return;
|
| + }
|
| + logger?.log(fromServer, '$trimmedLine');
|
| + Map message = asMap(JSON.decoder.convert(trimmedLine));
|
| + if (message.containsKey('id')) {
|
| + // The message is a response.
|
| + Response response = new Response.fromJson(message);
|
| + _handleResponse(response);
|
| + } else {
|
| + // The message is a notification.
|
| + Notification notification = new Notification.fromJson(message);
|
| + String event = notification.event;
|
| + _notificationCountMap[event] = (_notificationCountMap[event] ?? 0) + 1;
|
| + _handleNotification(notification);
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Start listening to output from the server.
|
| + */
|
| + void _listenToOutput() {
|
| + /**
|
| + * Install the given [handler] to listen to transformed output from the
|
| + * given [stream].
|
| + */
|
| + void installHandler(Stream<List<int>> stream, handler(String line)) {
|
| + stream
|
| + .transform((new Utf8Codec()).decoder)
|
| + .transform(new LineSplitter())
|
| + .listen(handler);
|
| + }
|
| +
|
| + installHandler(_process.stdout, _handleStdOut);
|
| + installHandler(_process.stderr, _handleStdErr);
|
| + }
|
| +
|
| + /**
|
| + * Send a command to the server. An 'id' will be automatically assigned.
|
| + */
|
| + RequestData _send(String method, Map<String, dynamic> params,
|
| + {void onResponse(Response response)}) {
|
| + String id = '${_nextId++}';
|
| + RequestData requestData = new RequestData(id, method, params, currentTime);
|
| + _requestDataMap[id] = requestData;
|
| + Map<String, dynamic> command = <String, dynamic>{
|
| + 'id': id,
|
| + 'method': method
|
| + };
|
| + if (params != null) {
|
| + command['params'] = params;
|
| + }
|
| + String line = JSON.encode(command);
|
| + _process.stdin.add(UTF8.encoder.convert('$line\n'));
|
| + logger?.log(fromClient, '$line');
|
| + return requestData;
|
| }
|
| }
|
|
|
| @@ -284,6 +1094,6 @@ class _ErrorComparator {
|
| buffer.writeln(filePath);
|
| _writeErrors(' Expected ', expectedErrors);
|
| buffer.writeln();
|
| - _writeErrors(' Found ', expectedErrors);
|
| + _writeErrors(' Found ', actualErrors);
|
| }
|
| }
|
|
|