OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, 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.operation; | |
6 | |
7 import 'dart:async'; | |
8 | |
9 import 'package:analysis_server/src/protocol.dart'; | |
10 import 'package:logging/logging.dart'; | |
11 | |
12 import 'driver.dart'; | |
13 import 'input_converter.dart'; | |
14 | |
15 /** | |
16 * A [CompletionRequestOperation] tracks response time along with | |
17 * the first and last completion notifications. | |
18 */ | |
19 class CompletionRequestOperation extends RequestOperation { | |
20 Driver driver; | |
21 StreamSubscription<CompletionResultsParams> subscription; | |
22 String notificationId; | |
23 Stopwatch stopwatch; | |
24 bool firstNotification = true; | |
25 | |
26 CompletionRequestOperation( | |
27 CommonInputConverter converter, Map<String, dynamic> json) | |
28 : super(converter, json); | |
29 | |
30 @override | |
31 Future perform(Driver driver) { | |
32 this.driver = driver; | |
33 subscription = driver.onCompletionResults.listen(processNotification); | |
34 return super.perform(driver); | |
35 } | |
36 | |
37 void processNotification(CompletionResultsParams event) { | |
38 if (event.id == notificationId) { | |
39 Duration elapsed = stopwatch.elapsed; | |
40 if (firstNotification) { | |
41 firstNotification = false; | |
42 driver.results.record('completion notification first', elapsed, | |
43 notification: true); | |
44 } | |
45 if (event.isLast) { | |
46 subscription.cancel(); | |
47 driver.results.record('completion notification last', elapsed, | |
48 notification: true); | |
49 } | |
50 } | |
51 } | |
52 | |
53 @override | |
54 void processResult( | |
55 String id, Map<String, dynamic> result, Stopwatch stopwatch) { | |
56 notificationId = result['id']; | |
57 this.stopwatch = stopwatch; | |
58 super.processResult(id, result, stopwatch); | |
59 } | |
60 } | |
61 | |
62 /** | |
63 * An [Operation] represents an action such as sending a request to the server. | |
64 */ | |
65 abstract class Operation { | |
66 Future perform(Driver driver); | |
67 } | |
68 | |
69 /** | |
70 * A [RequestOperation] sends a [JSON] request to the server. | |
71 */ | |
72 class RequestOperation extends Operation { | |
73 final CommonInputConverter converter; | |
74 final Map<String, dynamic> json; | |
75 | |
76 RequestOperation(this.converter, this.json); | |
77 | |
78 @override | |
79 Future perform(Driver driver) { | |
80 Stopwatch stopwatch = new Stopwatch(); | |
81 String originalId = json['id']; | |
82 String method = json['method']; | |
83 json['clientRequestTime'] = new DateTime.now().millisecondsSinceEpoch; | |
84 driver.logger.log(Level.FINE, 'Sending request: $method\n $json'); | |
85 stopwatch.start(); | |
86 | |
87 void recordResult(bool success, result) { | |
88 Duration elapsed = stopwatch.elapsed; | |
89 driver.results.record(method, elapsed, success: success); | |
90 driver.logger.log( | |
91 Level.FINE, 'Response received: $method : $elapsed\n $result'); | |
92 } | |
93 | |
94 driver.send(method, json['params']).then((Map<String, dynamic> result) { | |
95 recordResult(true, result); | |
96 processResult(originalId, result, stopwatch); | |
97 }).catchError((exception) { | |
98 recordResult(false, exception); | |
99 converter.processErrorResponse(originalId, exception); | |
100 }); | |
101 return null; | |
102 } | |
103 | |
104 void processResult( | |
105 String id, Map<String, dynamic> result, Stopwatch stopwatch) { | |
106 converter.processResponseResult(id, result); | |
107 } | |
108 } | |
109 | |
110 /** | |
111 * A [ResponseOperation] waits for a [JSON] response from the server. | |
112 */ | |
113 class ResponseOperation extends Operation { | |
114 static final Duration responseTimeout = new Duration(seconds: 5); | |
115 final CommonInputConverter converter; | |
116 final Map<String, dynamic> requestJson; | |
117 final Map<String, dynamic> responseJson; | |
118 final Completer completer = new Completer(); | |
119 Driver driver; | |
120 | |
121 ResponseOperation(this.converter, this.requestJson, this.responseJson) { | |
122 completer.future.then(_processResult).timeout(responseTimeout); | |
123 } | |
124 | |
125 @override | |
126 Future perform(Driver driver) { | |
127 this.driver = driver; | |
128 return converter.processExpectedResponse(responseJson['id'], completer); | |
129 } | |
130 | |
131 bool _equal(expectedResult, actualResult) { | |
132 if (expectedResult is Map && actualResult is Map) { | |
133 if (expectedResult.length == actualResult.length) { | |
134 return expectedResult.keys.every((String key) { | |
135 return key == | |
136 'fileStamp' || // fileStamp values will not be the same across
runs | |
137 _equal(expectedResult[key], actualResult[key]); | |
138 }); | |
139 } | |
140 } else if (expectedResult is List && actualResult is List) { | |
141 if (expectedResult.length == actualResult.length) { | |
142 for (int i = 0; i < expectedResult.length; ++i) { | |
143 if (!_equal(expectedResult[i], actualResult[i])) { | |
144 return false; | |
145 } | |
146 } | |
147 return true; | |
148 } | |
149 } | |
150 return expectedResult == actualResult; | |
151 } | |
152 | |
153 /** | |
154 * Compare the expected and actual server response result. | |
155 */ | |
156 void _processResult(actualResult) { | |
157 var expectedResult = responseJson['result']; | |
158 if (!_equal(expectedResult, actualResult)) { | |
159 var expectedError = responseJson['error']; | |
160 String format(value) { | |
161 String text = '\n$value'; | |
162 if (text.endsWith('\n')) { | |
163 text = text.substring(0, text.length - 1); | |
164 } | |
165 return text.replaceAll('\n', '\n '); | |
166 } | |
167 String message = 'Request:${format(requestJson)}\n' | |
168 'expected result:${format(expectedResult)}\n' | |
169 'expected error:${format(expectedError)}\n' | |
170 'but received:${format(actualResult)}'; | |
171 driver.results.recordUnexpectedResults(requestJson['method']); | |
172 if (expectedError == null) { | |
173 converter.logger.log(Level.SEVERE, message); | |
174 } else { | |
175 throw message; | |
176 } | |
177 } | |
178 } | |
179 } | |
180 | |
181 class StartServerOperation extends Operation { | |
182 final int diagnosticPort; | |
183 | |
184 StartServerOperation({this.diagnosticPort}); | |
185 | |
186 @override | |
187 Future perform(Driver driver) { | |
188 return driver.startServer(diagnosticPort: diagnosticPort); | |
189 } | |
190 } | |
191 | |
192 class WaitForAnalysisCompleteOperation extends Operation { | |
193 @override | |
194 Future perform(Driver driver) { | |
195 DateTime start = new DateTime.now(); | |
196 driver.logger.log(Level.FINE, 'waiting for analysis to complete'); | |
197 StreamSubscription<ServerStatusParams> subscription; | |
198 Timer timer; | |
199 Completer completer = new Completer(); | |
200 bool isAnalyzing = false; | |
201 subscription = driver.onServerStatus.listen((ServerStatusParams params) { | |
202 if (params.analysis != null) { | |
203 if (params.analysis.isAnalyzing) { | |
204 isAnalyzing = true; | |
205 } else { | |
206 subscription.cancel(); | |
207 timer.cancel(); | |
208 DateTime end = new DateTime.now(); | |
209 Duration delta = end.difference(start); | |
210 driver.logger.log(Level.FINE, 'analysis complete after $delta'); | |
211 completer.complete(); | |
212 driver.results.record('analysis complete', delta, notification: true); | |
213 } | |
214 } | |
215 }); | |
216 timer = new Timer.periodic(new Duration(milliseconds: 20), (_) { | |
217 if (!isAnalyzing) { | |
218 // TODO (danrubel) revisit this once source change requests are implemen
ted | |
219 subscription.cancel(); | |
220 timer.cancel(); | |
221 driver.logger.log(Level.INFO, 'analysis never started'); | |
222 completer.complete(); | |
223 return; | |
224 } | |
225 // Timeout if no communcation received within the last 60 seconds. | |
226 double currentTime = driver.server.currentElapseTime; | |
227 double lastTime = driver.server.lastCommunicationTime; | |
228 if (currentTime - lastTime > 60) { | |
229 subscription.cancel(); | |
230 timer.cancel(); | |
231 String message = 'gave up waiting for analysis to complete'; | |
232 driver.logger.log(Level.WARNING, message); | |
233 completer.completeError(message); | |
234 } | |
235 }); | |
236 return completer.future; | |
237 } | |
238 } | |
OLD | NEW |