| 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 |