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

Side by Side Diff: pkg/analysis_server/bin/fuzz/server_manager.dart

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

Powered by Google App Engine
This is Rietveld 408576698