Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Unified Diff: pkg/analysis_server/bin/fuzz/server_manager.dart

Issue 584963002: first cut fuzz test for analysis server (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: merge and address comments Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/analysis_server/bin/fuzz/protocol.dart ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/analysis_server/bin/fuzz/server_manager.dart
diff --git a/pkg/analysis_server/bin/fuzz/server_manager.dart b/pkg/analysis_server/bin/fuzz/server_manager.dart
new file mode 100644
index 0000000000000000000000000000000000000000..c59e18cf76c9ab1518296b5f1aa2446f0b654937
--- /dev/null
+++ b/pkg/analysis_server/bin/fuzz/server_manager.dart
@@ -0,0 +1,365 @@
+// Copyright (c) 2014, 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.
+
+library server.manager;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:matcher/matcher.dart';
+
+import 'byte_stream_channel.dart';
+import 'channel.dart';
+import 'protocol.dart';
+
+part 'logging_client_channel.dart';
+
+/**
+ * The results returned by [ServerManager].analyze(...) once analysis
+ * has finished.
+ */
+class AnalysisResults {
+ Duration elapsed;
+ int errorCount = 0;
+ int hintCount = 0;
+ int warningCount = 0;
+}
+
+
+/**
+ * [CompletionResults] contains the completion results returned by the server
+ * along with the elapse time to receive those completions.
+ */
+class CompletionResults {
+ final Duration elapsed;
+ final CompletionResultsParams params;
+
+ CompletionResults(this.elapsed, this.params);
+
+ int get suggestionCount => params.results.length;
+}
+
+/**
+ * [Editor] is a virtual editor for inspecting and modifying a file's content
+ * and updating the server with those modifications.
+ */
+class Editor {
+ final ServerManager manager;
+ final File file;
+ int offset = 0;
+ String _content = null;
+
+ Editor(this.manager, this.file);
+
+ /// Return a future that returns the file content
+ Future<String> get content {
+ if (_content != null) {
+ return new Future.value(_content);
+ }
+ return file.readAsString().then((String content) {
+ _content = content;
+ return _content;
+ });
+ }
+
+ /**
+ * Request completion suggestions from the server.
+ * Return a future that completes with the completions sent.
+ */
+ Future<List<CompletionResults>> getSuggestions() {
+ Request request = new CompletionGetSuggestionsParams(
+ file.path,
+ offset).toRequest(manager._nextRequestId);
+ Stopwatch stopwatch = new Stopwatch()..start();
+ return manager.channel.sendRequest(request).then((Response response) {
+ String completionId =
+ new CompletionGetSuggestionsResult.fromResponse(response).id;
+ var completer = new Completer<List<CompletionResults>>();
+ List<CompletionResults> results = [];
+
+ // Listen for completion suggestions
+ StreamSubscription<Notification> subscription;
+ subscription =
+ manager.channel.notificationStream.listen((Notification notification) {
+ if (notification.event == 'completion.results') {
+ CompletionResultsParams params =
+ new CompletionResultsParams.fromNotification(notification);
+ if (params.id == completionId) {
+ results.add(new CompletionResults(stopwatch.elapsed, params));
+ if (params.isLast) {
+ stopwatch.stop();
+ subscription.cancel();
+ completer.complete(results);
+ }
+ }
+ }
+ });
+
+ return completer.future;
+ });
+ }
+
+ /**
+ * Move the virtual cursor after the given pattern in the source.
+ * Return a future that completes once the cursor has been moved.
+ */
+ Future<Editor> moveAfter(String pattern) {
+ return content.then((String content) {
+ offset = content.indexOf(pattern);
+ return this;
+ });
+ }
+
+ /**
+ * Replace the specified number of characters at the current cursor location
+ * with the given text, but do not save that content to disk.
+ * Return a future that completes once the server has been notified.
+ */
+ Future<Editor> replace(int replacementLength, String text) {
+ return content.then((String oldContent) {
+ StringBuffer sb = new StringBuffer();
+ sb.write(oldContent.substring(0, offset));
+ sb.write(text);
+ sb.write(oldContent.substring(offset));
+ _content = sb.toString();
+ SourceEdit sourceEdit = new SourceEdit(offset, replacementLength, text);
+ Request request = new AnalysisUpdateContentParams({
+ file.path: new ChangeContentOverlay([sourceEdit])
+ }).toRequest(manager._nextRequestId);
+ offset += text.length;
+ return manager.channel.sendRequest(request).then((Response response) {
+ return this;
+ });
+ });
+ }
+}
+
+/**
+ * [ServerManager] is used to launch and manage an analysis server
+ * running in a separate process.
+ */
+class ServerManager {
+
+ /**
+ * The analysis server process being managed or `null` if not started.
+ */
+ Process process;
+
+ /**
+ * The root directory containing the Dart source files to be analyzed.
+ */
+ Directory appDir;
+
+ /**
+ * The channel used to communicate with the analysis server.
+ */
+ LoggingClientChannel _channel;
+
+ /**
+ * The identifier used in the most recent request to the server.
+ * See [_nextRequestId].
+ */
+ int _lastRequestId = 0;
+
+ /**
+ * `true` if a server exception was detected on stderr as opposed to an
+ * exception that the server reported via the server.error notification.
+ */
+ bool _unreportedServerException = false;
+
+ /**
+ * `true` if the [stop] method has been called.
+ */
+ bool _stopRequested = false;
+
+ /**
+ * Return the channel used to communicate with the analysis server.
+ */
+ ClientCommunicationChannel get channel => _channel;
+
+ /**
+ * Return `true` if a server error occurred.
+ */
+ bool get errorOccurred =>
+ _unreportedServerException || (_channel.serverErrorCount > 0);
+
+ String get _nextRequestId => (++_lastRequestId).toString();
+
+ /**
+ * Direct the server to analyze all sources in the given directory,
+ * all sub directories recursively, and any source referenced sources
+ * outside this directory hierarch such as referenced packages.
+ * Return a future that completes when the analysis is finished.
+ */
+ Future<AnalysisResults> analyze(Directory appDir) {
+ this.appDir = appDir;
+ Stopwatch stopwatch = new Stopwatch()..start();
+ Request request =
+ new AnalysisSetAnalysisRootsParams([appDir.path], []).toRequest(_nextRequestId);
+
+ // Request analysis
+ return channel.sendRequest(request).then((Response response) {
+ AnalysisResults results = new AnalysisResults();
+ StreamSubscription<Notification> subscription;
+ Completer<AnalysisResults> completer = new Completer<AnalysisResults>();
+ subscription =
+ channel.notificationStream.listen((Notification notification) {
+
+ // Gather analysis results
+ if (notification.event == 'analysis.errors') {
+ AnalysisErrorsParams params =
+ new AnalysisErrorsParams.fromNotification(notification);
+ params.errors.forEach((AnalysisError error) {
+ AnalysisErrorSeverity severity = error.severity;
+ if (severity == AnalysisErrorSeverity.ERROR) {
+ results.errorCount += 1;
+ } else if (severity == AnalysisErrorSeverity.WARNING) {
+ results.warningCount += 1;
+ } else if (severity == AnalysisErrorSeverity.INFO) {
+ results.hintCount += 1;
+ } else {
+ print('Unknown error severity: ${severity.name}');
+ }
+ });
+ }
+
+ // Stop gathering once analysis is complete
+ if (notification.event == 'server.status') {
+ ServerStatusParams status =
+ new ServerStatusParams.fromNotification(notification);
+ AnalysisStatus analysis = status.analysis;
+ if (analysis != null && !analysis.isAnalyzing) {
+ stopwatch.stop();
+ results.elapsed = stopwatch.elapsed;
+ subscription.cancel();
+ completer.complete(results);
+ }
+ }
+ });
+ return completer.future;
+ });
+ }
+
+ /**
+ * Send a request to the server for its version information
+ * and return a future that completes with the result.
+ */
+ Future<ServerGetVersionResult> getVersion() {
+ Request request = new ServerGetVersionParams().toRequest(_nextRequestId);
+ return channel.sendRequest(request).then((Response response) {
+ return new ServerGetVersionResult.fromResponse(response);
+ });
+ }
+
+ /**
+ * Notify the server that the given file will be edited.
+ * Return a virtual editor for inspecting and modifying the file's content.
+ */
+ Future<Editor> openFileNamed(String fileName) {
+ return _findFile(fileName, appDir).then((File file) {
+ if (file == null) {
+ throw 'Failed to find file named $fileName in ${appDir.path}';
+ }
+ file = file.absolute;
+ Request request =
+ new AnalysisSetPriorityFilesParams([file.path]).toRequest(_nextRequestId);
+ return channel.sendRequest(request).then((Response response) {
+ return new Editor(this, file);
+ });
+ });
+ }
+
+ /**
+ * Send a request for notifications.
+ * Return when the server has acknowledged that request.
+ */
+ Future setSubscriptions() {
+ Request request =
+ new ServerSetSubscriptionsParams([ServerService.STATUS]).toRequest(_nextRequestId);
+ return channel.sendRequest(request);
+ }
+
+ /**
+ * Stop the analysis server.
+ * Return a future that completes when the server is terminated.
+ */
+ Future stop([_]) {
+ _stopRequested = true;
+ print("Requesting server shutdown");
+ Request request = new ServerShutdownParams().toRequest(_nextRequestId);
+ Duration waitTime = new Duration(seconds: 5);
+ return channel.sendRequest(request).timeout(waitTime, onTimeout: () {
+ print('Expected shutdown response');
+ }).then((Response response) {
+ return channel.close().then((_) => process.exitCode);
+ }).timeout(new Duration(seconds: 2), onTimeout: () {
+ print('Expected server to shutdown');
+ process.kill();
+ });
+ }
+
+ /**
+ * Locate the given file in the directory tree.
+ */
+ Future<File> _findFile(String fileName, Directory appDir) {
+ return appDir.list(recursive: true).firstWhere((FileSystemEntity entity) {
+ return entity is File && entity.path.endsWith(fileName);
+ });
+ }
+
+ /**
+ * Launch an analysis server and open a connection to that server.
+ */
+ Future<ServerManager> _launchServer(String pathToServer) {
+ List<String> serverArgs = [pathToServer];
+ return Process.start(Platform.executable, serverArgs).catchError((error) {
+ exitCode = 21;
+ throw 'Failed to launch analysis server: $error';
+ }).then((Process process) {
+ this.process = process;
+ _channel = new LoggingClientChannel(
+ new ByteStreamClientChannel(process.stdout, process.stdin));
+
+ // simple out of band exception handling
+ process.stderr.transform(
+ new Utf8Codec().decoder).transform(new LineSplitter()).listen((String line) {
+ if (!_unreportedServerException) {
+ _unreportedServerException = true;
+ stderr.writeln('>>> Unreported server exception');
+ }
+ stderr.writeln('server.stderr: $line');
+ });
+
+ // watch for unexpected process termination and catch the exit code
+ process.exitCode.then((int code) {
+ if (!_stopRequested) {
+ fail('Unexpected server termination: $code');
+ }
+ if (code != null && code != 0) {
+ exitCode = code;
+ }
+ print('Server stopped: $code');
+ });
+
+ return channel.notificationStream.first.then((Notification notification) {
+ print('Server connection established');
+ return setSubscriptions().then((_) {
+ return getVersion().then((ServerGetVersionResult result) {
+ print('Server version ${result.version}');
+ return this;
+ });
+ });
+ });
+ });
+ }
+
+ /**
+ * Launch analysis server in a separate process
+ * and return a future with a manager for that analysis server.
+ */
+ static Future<ServerManager> start(String serverPath) {
+ return new ServerManager()._launchServer(serverPath);
+ }
+}
« no previous file with comments | « pkg/analysis_server/bin/fuzz/protocol.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698