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

Side by Side 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: 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library server.manager;
6
7 import 'dart:async';
8 import 'dart:convert';
9 import 'dart:io';
10
11 import 'package:matcher/matcher.dart';
12
13 import 'byte_stream_channel.dart';
14 import 'channel.dart';
15 import 'protocol.dart';
16
17 part 'logging_client_channel.dart';
18
19 /**
20 * The results returned by [ServerManager].analyze(...) once analysis
21 * has finished.
22 */
23 class AnalysisResults {
24 Duration elapsed;
25 int errorCount = 0;
26 int hintCount = 0;
27 int warningCount = 0;
28 }
29
30
31 /**
32 * [CompletionResults] contains the completion results returned by the server
33 * along with the elapse time to receive those completions.
34 */
35 class CompletionResults {
36 final Duration elapsed;
37 final CompletionResultsParams params;
38
39 CompletionResults(this.elapsed, this.params);
40
41 int get suggestionCount => params.results.length;
42 }
43
44 /**
45 * [Editor] is a virtual editor for inspecting and modifying a file's content
46 * and updating the server with those modifications.
47 */
48 class Editor {
49 final ServerManager manager;
50 final File file;
51 int offset = 0;
52 String _content = null;
53
54 Editor(this.manager, this.file);
55
56 /// Return a future that returns the file content
57 Future<String> get content {
58 if (_content != null) {
59 return new Future.value(_content);
60 }
61 return file.readAsString().then((String content) {
62 _content = content;
63 return _content;
64 });
65 }
66
67 /**
68 * Request completion suggestions from the server.
69 * Return a future that completes with the completions sent.
70 */
71 Future<List<CompletionResults>> getSuggestions() {
72 Request request = new CompletionGetSuggestionsParams(
73 file.path,
74 offset).toRequest(manager._nextId);
75 Stopwatch stopwatch = new Stopwatch()..start();
76 return manager.channel.sendRequest(request).then((Response response) {
77 var id = new CompletionGetSuggestionsResult.fromResponse(response).id;
lukechurch 2014/09/21 05:36:11 It's not quite obvious from just reading the code
danrubel 2014/09/22 18:27:25 Good point. This is the completionId not the respo
78 var completer = new Completer<List<CompletionResults>>();
79 List<CompletionResults> results = [];
80
81 // Listen for completion suggestions
82 StreamSubscription<Notification> subscription;
83 subscription =
84 manager.channel.notificationStream.listen((Notification notification) {
85 if (notification.event == 'completion.results') {
86 CompletionResultsParams params =
87 new CompletionResultsParams.fromNotification(notification);
88 results.add(new CompletionResults(stopwatch.elapsed, params));
89 if (params.isLast) {
90 stopwatch.stop();
91 subscription.cancel();
92 completer.complete(results);
93 }
94 }
95 });
96
97 return completer.future;
98 });
99 }
100
101 /**
102 * Move the virtual cursor after the given pattern in the source.
103 * Return a future that completes once the cursor has been moved.
104 */
105 Future<Editor> moveAfter(String pattern) {
106 return content.then((String content) {
107 offset = content.indexOf(pattern);
108 return this;
109 });
110 }
111
112 /**
113 * Replace the specified number of characters at the current cursor location
114 * with the given text, but do not save that content to disk.
115 * Return a future that completes once the server has been notified.
116 */
117 Future<Editor> replace(int replacementLength, String text) {
118 return content.then((String oldContent) {
119 StringBuffer sb = new StringBuffer();
120 sb.write(oldContent.substring(0, offset));
121 sb.write(text);
122 sb.write(oldContent.substring(offset));
123 _content = sb.toString();
124 SourceEdit sourceEdit = new SourceEdit(offset, replacementLength, text);
125 Request request = new AnalysisUpdateContentParams({
126 file.path: new ChangeContentOverlay([sourceEdit])
127 }).toRequest(manager._nextId);
128 offset += text.length;
129 return manager.channel.sendRequest(request).then((Response response) {
130 return this;
131 });
132 });
133 }
134 }
135
136 /**
137 * [ServerManager] is used to launch and manage an analysis server
138 * running in a separate process.
139 */
140 class ServerManager {
141
142 /**
143 * The analysis server process being managed or `null` if not started.
144 */
145 Process process;
146
147 /**
148 * The channel used to communicate with the analysis server.
149 */
150 LoggingClientChannel _channel;
151
152 bool _unreportedServerException = false;
153
154 bool _stopRequested = false;
lukechurch 2014/09/21 05:36:11 whitespace is a bit whacky
danrubel 2014/09/22 18:27:25 Added comments. Hopefully not so whacky now.
155
156 Directory appDir;
157
158 int _id = 0;
159
160 /**
161 * Return the channel used to communicate with the analysis server.
162 */
163 ClientCommunicationChannel get channel => _channel;
164
165 /**
166 * Return `true` if a server error occurred.
167 */
168 bool get errorOccurred =>
169 _unreportedServerException || (_channel.serverErrorCount > 0);
170
171 String get _nextId => (++_id).toString();
172
173 /**
174 * Direct the server to analyze all sources in the given directory.
175 * Return a future that completes when the analysis is finished.
lukechurch 2014/09/21 05:36:11 Does this recurse into children? Given the ambigui
danrubel 2014/09/22 18:27:26 It analyzes all of the files in the given director
176 */
177 Future<AnalysisResults> analyze(Directory appDir) {
178 this.appDir = appDir;
179 Stopwatch stopwatch = new Stopwatch()..start();
180 Request request =
181 new AnalysisSetAnalysisRootsParams([appDir.path], []).toRequest(_nextId) ;
182
183 // Request analysis
184 return channel.sendRequest(request).then((Response response) {
185 AnalysisResults results = new AnalysisResults();
186 StreamSubscription<Notification> subscription;
187 Completer<AnalysisResults> completer = new Completer<AnalysisResults>();
188 subscription =
189 channel.notificationStream.listen((Notification notification) {
190
191 // Gather analysis results
192 if (notification.event == 'analysis.errors') {
193 AnalysisErrorsParams params =
194 new AnalysisErrorsParams.fromNotification(notification);
195 params.errors.forEach((AnalysisError error) {
196 AnalysisErrorSeverity severity = error.severity;
197 if (severity == AnalysisErrorSeverity.ERROR) {
198 results.errorCount += 1;
199 } else if (severity == AnalysisErrorSeverity.WARNING) {
200 results.warningCount += 1;
201 } else if (severity == AnalysisErrorSeverity.INFO) {
202 results.hintCount += 1;
203 } else {
204 print('Unknown error severity: ${severity.name}');
205 }
206 });
207 }
208
209 // Stop gathering once analysis is complete
210 if (notification.event == 'server.status') {
211 ServerStatusParams status =
212 new ServerStatusParams.fromNotification(notification);
213 AnalysisStatus analysis = status.analysis;
214 if (analysis != null && !analysis.isAnalyzing) {
215 stopwatch.stop();
216 results.elapsed = stopwatch.elapsed;
217 subscription.cancel();
218 completer.complete(results);
219 }
220 }
221 });
222 return completer.future;
223 });
224 }
225
226 /**
227 * Send a request to the server for its version information
228 * and return a future that completes with the result.
229 */
230 Future<ServerGetVersionResult> getVersion() {
231 Request request = new ServerGetVersionParams().toRequest(_nextId);
232 return channel.sendRequest(request).then((Response response) {
233 return new ServerGetVersionResult.fromResponse(response);
234 });
235 }
236
237 /**
238 * Notify the server that the given file will be edited.
239 * Return a virtual editor for inspecting and modifying the file's content.
240 */
241 Future<Editor> openFileNamed(String fileName) {
242 return _findFile(fileName, appDir).then((File file) {
243 if (file == null) {
244 throw 'Failed to find file named $fileName in ${appDir.path}';
245 }
246 file = file.absolute;
247 Request request =
248 new AnalysisSetPriorityFilesParams([file.path]).toRequest(_nextId);
249 return channel.sendRequest(request).then((Response response) {
250 return new Editor(this, file);
251 });
252 });
253 }
254
255 /**
256 * Send a request for notifications.
257 * Return when the server has acknowledged that request.
258 */
259 Future setSubscriptions() {
260 Request request =
261 new ServerSetSubscriptionsParams([ServerService.STATUS]).toRequest(_next Id);
262 return channel.sendRequest(request);
263 }
264
265 /**
266 * Stop the analysis server.
267 * Return a future that completes when the server is terminated.
268 */
269 Future stop([_]) {
270 _stopRequested = true;
271 print("Requesting server shutdown");
272 Request request = new ServerShutdownParams().toRequest(_nextId);
273 Duration waitTime = new Duration(seconds: 5);
274 return channel.sendRequest(request).timeout(waitTime, onTimeout: () {
275 print('Expected shutdown response');
276 }).then((Response response) {
277 return channel.close().then((_) => process.exitCode);
278 }).timeout(new Duration(seconds: 2), onTimeout: () {
279 print('Expected server to shutdown');
280 process.kill();
281 });
282 }
283
284 /**
285 * Locate the given file in the directory tree.
286 */
287 Future<File> _findFile(String fileName, Directory appDir) {
288 return appDir.list(recursive: true).firstWhere((FileSystemEntity entity) {
289 return entity is File && entity.path.endsWith(fileName);
290 });
291 }
292
293 /**
294 * Launch an analysis server and open a connection to that server.
295 */
296 Future<ServerManager> _launchServer(String pathToServer) {
297 List<String> serverArgs = [pathToServer];
298 return Process.start(Platform.executable, serverArgs).catchError((error) {
299 exitCode = 21;
300 throw 'Failed to launch analysis server: $error';
301 }).then((Process process) {
302 this.process = process;
303 _channel = new LoggingClientChannel(
304 new ByteStreamClientChannel(process.stdout, process.stdin));
305
306 // simple out of band exception handling
307 process.stderr.transform(
308 new Utf8Codec().decoder).transform(new LineSplitter()).listen((String line) {
309 if (!_unreportedServerException) {
310 _unreportedServerException = true;
311 stderr.writeln('>>> Unreported server exception');
312 }
313 stderr.writeln('server.stderr: $line');
314 });
315
316 // watch for unexpected process termination and catch the exit code
317 process.exitCode.then((int code) {
318 if (!_stopRequested) {
319 fail('Unexpected server termination: $code');
320 }
321 if (code != null && code != 0) {
322 exitCode = code;
323 }
324 print('Server stopped: $code');
325 });
326
327 return channel.notificationStream.first.then((Notification notification) {
328 print('Server connection established');
329 return setSubscriptions().then((_) {
330 return getVersion().then((ServerGetVersionResult result) {
331 print('Server version ${result.version}');
332 return this;
333 });
334 });
335 });
336 });
337 }
338
339 /**
340 * Launch analysis server in a separate process
341 * and return a future with a manager for that analysis server.
342 */
343 static Future<ServerManager> start(String serverPath) {
344 return new ServerManager()._launchServer(serverPath);
345 }
346 }
OLDNEW
« pkg/analysis_server/bin/fuzz.dart ('K') | « 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