| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library test.integration.analysis; | 5 library test.integration.analysis; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 import 'dart:convert'; | 9 import 'dart:convert'; |
| 10 import 'dart:io'; | 10 import 'dart:io'; |
| (...skipping 16 matching lines...) Expand all Loading... |
| 27 Directory sourceDirectory; | 27 Directory sourceDirectory; |
| 28 | 28 |
| 29 /** | 29 /** |
| 30 * Map from file path to the list of analysis errors which have most recently | 30 * Map from file path to the list of analysis errors which have most recently |
| 31 * been received for the file. | 31 * been received for the file. |
| 32 */ | 32 */ |
| 33 HashMap<String, dynamic> currentAnalysisErrors = new HashMap<String, dynamic>( | 33 HashMap<String, dynamic> currentAnalysisErrors = new HashMap<String, dynamic>( |
| 34 ); | 34 ); |
| 35 | 35 |
| 36 /** | 36 /** |
| 37 * Create a source file with the given contents. [relativePath] | 37 * Write a source file with the given contents. [relativePath] |
| 38 * is relative to [sourceDirectory]; on Windows any forward slashes it | 38 * is relative to [sourceDirectory]; on Windows any forward slashes it |
| 39 * contains are converted to backslashes. | 39 * contains are converted to backslashes. |
| 40 * |
| 41 * If the file didn't previously exist, it is created. If it did, it is |
| 42 * overwritten. |
| 40 */ | 43 */ |
| 41 void createFile(String relativePath, String contents) { | 44 void writeFile(String relativePath, String contents) { |
| 42 String absolutePath = normalizePath(relativePath); | 45 String absolutePath = normalizePath(relativePath); |
| 43 new Directory(dirname(absolutePath)).createSync(recursive: true); | 46 new Directory(dirname(absolutePath)).createSync(recursive: true); |
| 44 new File(absolutePath).writeAsStringSync(contents); | 47 new File(absolutePath).writeAsStringSync(contents); |
| 45 } | 48 } |
| 46 | 49 |
| 47 /** | 50 /** |
| 48 * Convert the given [relativePath] to an absolute path, by interpreting it | 51 * Convert the given [relativePath] to an absolute path, by interpreting it |
| 49 * relative to [sourceDirectory]. On Windows any forward slashes in | 52 * relative to [sourceDirectory]. On Windows any forward slashes in |
| 50 * [relativePath] are converted to backslashes. | 53 * [relativePath] are converted to backslashes. |
| 51 */ | 54 */ |
| 52 String normalizePath(String relativePath) { | 55 String normalizePath(String relativePath) { |
| 53 return join(sourceDirectory.path, relativePath.replaceAll('/', separator)); | 56 return join(sourceDirectory.path, relativePath.replaceAll('/', separator)); |
| 54 } | 57 } |
| 55 | 58 |
| 56 /** | 59 /** |
| 57 * Send the server an 'analysis.setAnalysisRoots' command. | 60 * Send the server an 'analysis.setAnalysisRoots' command. |
| 58 */ | 61 */ |
| 59 Future setAnalysisRoots(List<String> relativeRoots) { | 62 Future setAnalysisRoots(List<String> relativeRoots) { |
| 60 return server.send('analysis.setAnalysisRoots', { | 63 return server.send('analysis.setAnalysisRoots', { |
| 61 'included': relativeRoots.map(normalizePath).toList(), | 64 'included': relativeRoots.map(normalizePath).toList(), |
| 62 'excluded': [] | 65 'excluded': [] |
| 63 }); | 66 }); |
| 64 } | 67 } |
| 65 | 68 |
| 66 /** | 69 /** |
| 70 * Send the server a 'server.setSubscriptions' command. |
| 71 */ |
| 72 Future server_setSubscriptions(List<String> subscriptions) { |
| 73 return server.send('server.setSubscriptions', { |
| 74 'subscriptions': subscriptions |
| 75 }); |
| 76 } |
| 77 |
| 78 /** |
| 67 * Return a future which will complete when a 'server.status' notification is | 79 * Return a future which will complete when a 'server.status' notification is |
| 68 * received from the server with 'analyzing' set to false. | 80 * received from the server with 'analyzing' set to false. |
| 69 * | 81 * |
| 70 * The future will only be completed by 'server.status' notifications that are | 82 * The future will only be completed by 'server.status' notifications that are |
| 71 * received after this function call. So it is safe to use this getter | 83 * received after this function call. So it is safe to use this getter |
| 72 * multiple times in one test; each time it is used it will wait afresh for | 84 * multiple times in one test; each time it is used it will wait afresh for |
| 73 * analysis to finish. | 85 * analysis to finish. |
| 74 */ | 86 */ |
| 75 Future get analysisFinished { | 87 Future get analysisFinished { |
| 76 Completer completer = new Completer(); | 88 Completer completer = new Completer(); |
| 77 StreamSubscription subscription; | 89 StreamSubscription subscription; |
| 78 subscription = server.onNotification('server.status').listen((params) { | 90 subscription = server.onNotification('server.status').listen((params) { |
| 79 if (!params['analysis']['analyzing']) { | 91 bool analysisComplete = false; |
| 92 try { |
| 93 analysisComplete = !params['analysis']['analyzing']; |
| 94 } catch (_) { |
| 95 // Status message was mal-formed or missing optional parameters. That's |
| 96 // fine, since we'll detect a mal-formed status message below. |
| 97 } |
| 98 if (analysisComplete) { |
| 80 completer.complete(params); | 99 completer.complete(params); |
| 81 subscription.cancel(); | 100 subscription.cancel(); |
| 82 } | 101 } |
| 102 expect(params, isServerStatusParams); |
| 83 }); | 103 }); |
| 84 return completer.future; | 104 return completer.future; |
| 85 } | 105 } |
| 86 | 106 |
| 87 /** | 107 /** |
| 88 * Print out any messages exchanged with the server. If some messages have | 108 * Print out any messages exchanged with the server. If some messages have |
| 89 * already been exchanged with the server, they are printed out immediately. | 109 * already been exchanged with the server, they are printed out immediately. |
| 90 */ | 110 */ |
| 91 void debugStdio() { | 111 void debugStdio() { |
| 92 server.debugStdio(); | 112 server.debugStdio(); |
| (...skipping 24 matching lines...) Expand all Loading... |
| 117 }); | 137 }); |
| 118 } | 138 } |
| 119 } | 139 } |
| 120 | 140 |
| 121 // Matchers for data types defined in the analysis server API | 141 // Matchers for data types defined in the analysis server API |
| 122 // ========================================================== | 142 // ========================================================== |
| 123 // TODO(paulberry): add more matchers. | 143 // TODO(paulberry): add more matchers. |
| 124 | 144 |
| 125 const Matcher isString = const isInstanceOf<String>('String'); | 145 const Matcher isString = const isInstanceOf<String>('String'); |
| 126 | 146 |
| 127 const Matcher isInt = const isInstanceOf<int>('Int'); | 147 const Matcher isInt = const isInstanceOf<int>('int'); |
| 128 | 148 |
| 129 const Matcher isResultResponse = const MatchesJsonObject('result response', cons
t { | 149 const Matcher isBool = const isInstanceOf<bool>('bool'); |
| 150 |
| 151 const Matcher isResultResponse = const MatchesJsonObject('result response', |
| 152 const { |
| 130 'id': isString | 153 'id': isString |
| 131 }, optionalFields: const { | 154 }, optionalFields: const { |
| 132 'result': anything | 155 'result': anything |
| 133 }); | 156 }); |
| 134 | 157 |
| 135 const Matcher isError = const MatchesJsonObject('Error', const { | 158 const Matcher isError = const MatchesJsonObject('Error', const { |
| 136 // TODO(paulberry): once we decide what the set of permitted error codes are, | 159 // TODO(paulberry): once we decide what the set of permitted error codes are, |
| 137 // add validation for 'code'. | 160 // add validation for 'code'. |
| 138 'code': anything, | 161 'code': anything, |
| 139 'message': isString, | 162 'message': isString |
| 163 }, optionalFields: const { |
| 164 // TODO(paulberry): API spec says that 'data' is required, but sometimes we |
| 165 // don't see it (example: error "Expected parameter subscriptions to be a |
| 166 // string list map" in response to a malformed "analysis.setSubscriptions" |
| 167 // command). |
| 140 'data': anything | 168 'data': anything |
| 141 }); | 169 }); |
| 142 | 170 |
| 143 const Matcher isErrorResponse = const MatchesJsonObject('error response', const
{ | 171 const Matcher isErrorResponse = const MatchesJsonObject('error response', const |
| 172 { |
| 144 'id': isString, | 173 'id': isString, |
| 145 'error': isError | 174 'error': isError |
| 146 }); | 175 }); |
| 147 | 176 |
| 148 const Matcher isNotification = const MatchesJsonObject('notification', const { | 177 const Matcher isNotification = const MatchesJsonObject('notification', const { |
| 149 'event': isString | 178 'event': isString |
| 150 }, optionalFields: const { | 179 }, optionalFields: const { |
| 151 'params': isMap | 180 'params': isMap |
| 152 }); | 181 }); |
| 153 | 182 |
| 183 const Matcher isServerGetVersionResult = const MatchesJsonObject( |
| 184 'server.getVersion result', const { |
| 185 'version': isString |
| 186 }); |
| 187 |
| 188 const Matcher isServerStatusParams = const MatchesJsonObject( |
| 189 'server.status params', null, optionalFields: const { |
| 190 'analysis': isAnalysisStatus |
| 191 }); |
| 192 |
| 154 final Matcher isErrorSeverity = isIn(['INFO', 'WARNING', 'ERROR']); | 193 final Matcher isErrorSeverity = isIn(['INFO', 'WARNING', 'ERROR']); |
| 155 | 194 |
| 156 final Matcher isErrorType = isIn(['COMPILE_TIME_ERROR', 'HINT', | 195 final Matcher isErrorType = isIn(['COMPILE_TIME_ERROR', 'HINT', |
| 157 'STATIC_TYPE_WARNING', 'STATIC_WARNING', 'SYNTACTIC_ERROR', 'TODO']); | 196 'STATIC_TYPE_WARNING', 'STATIC_WARNING', 'SYNTACTIC_ERROR', 'TODO']); |
| 158 | 197 |
| 159 const Matcher isLocation = const MatchesJsonObject('Location', const { | 198 const Matcher isLocation = const MatchesJsonObject('Location', const { |
| 160 'file': isString, | 199 'file': isString, |
| 161 'offset': isInt, | 200 'offset': isInt, |
| 162 'length': isInt, | 201 'length': isInt, |
| 163 'startLine': isInt, | 202 'startLine': isInt, |
| 164 'startColumn': isInt | 203 'startColumn': isInt |
| 165 }); | 204 }); |
| 166 | 205 |
| 167 final Matcher isAnalysisError = new MatchesJsonObject('AnalysisError', { | 206 final Matcher isAnalysisError = new MatchesJsonObject('AnalysisError', { |
| 168 'severity': isErrorSeverity, | 207 'severity': isErrorSeverity, |
| 169 'type': isErrorType, | 208 'type': isErrorType, |
| 170 'location': isLocation, | 209 'location': isLocation, |
| 171 'message': isString, | 210 'message': isString, |
| 172 }, optionalFields: { | 211 }, optionalFields: { |
| 173 'correction': isString, | 212 'correction': isString, |
| 174 // TODO(paulberry): remove 'errorCode' once server stops sending it | 213 // TODO(paulberry): remove 'errorCode' once server stops sending it |
| 175 'errorCode': anything | 214 'errorCode': anything |
| 176 }); | 215 }); |
| 177 | 216 |
| 217 const Matcher isAnalysisStatus = const MatchesJsonObject('AnalysisStatus', const |
| 218 { |
| 219 'analyzing': isBool |
| 220 }, optionalFields: const { |
| 221 'analysisTarget': isString |
| 222 }); |
| 223 |
| 178 | 224 |
| 179 /** | 225 /** |
| 180 * Type of closures used by MatchesJsonObject to record field mismatches. | 226 * Type of closures used by MatchesJsonObject to record field mismatches. |
| 181 */ | 227 */ |
| 182 typedef Description MismatchDescriber(Description mismatchDescription, bool | 228 typedef Description MismatchDescriber(Description mismatchDescription, bool |
| 183 verbose); | 229 verbose); |
| 184 | 230 |
| 185 /** | 231 /** |
| 186 * Matcher that matches a JSON object, with a given set of required and | 232 * Matcher that matches a JSON object, with a given set of required and |
| 187 * optional fields, and their associated types (expressed as [Matcher]s). | 233 * optional fields, and their associated types (expressed as [Matcher]s). |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 278 * [valueMatcher]. If it doesn't match, record a closure in [mismatches] | 324 * [valueMatcher]. If it doesn't match, record a closure in [mismatches] |
| 279 * which can describe the mismatch. | 325 * which can describe the mismatch. |
| 280 */ | 326 */ |
| 281 void _checkField(String key, value, Matcher | 327 void _checkField(String key, value, Matcher |
| 282 valueMatcher, List<MismatchDescriber> mismatches) { | 328 valueMatcher, List<MismatchDescriber> mismatches) { |
| 283 Map subState = {}; | 329 Map subState = {}; |
| 284 if (!valueMatcher.matches(value, subState)) { | 330 if (!valueMatcher.matches(value, subState)) { |
| 285 mismatches.add((Description mismatchDescription, bool verbose) { | 331 mismatches.add((Description mismatchDescription, bool verbose) { |
| 286 mismatchDescription = mismatchDescription.add( | 332 mismatchDescription = mismatchDescription.add( |
| 287 'contains malformed field ').addDescriptionOf(key).add(' (should be
' | 333 'contains malformed field ').addDescriptionOf(key).add(' (should be
' |
| 288 ).addDescriptionOf(valueMatcher).add('; '); | 334 ).addDescriptionOf(valueMatcher); |
| 289 mismatchDescription = valueMatcher.describeMismatch(value, | 335 String subDescription = valueMatcher.describeMismatch(value, |
| 290 mismatchDescription, subState, verbose); | 336 new StringDescription(), subState, false).toString(); |
| 337 if (subDescription.isNotEmpty) { |
| 338 mismatchDescription = mismatchDescription.add('; ').add(subDescription |
| 339 ); |
| 340 } |
| 291 return mismatchDescription.add(')'); | 341 return mismatchDescription.add(')'); |
| 292 }); | 342 }); |
| 293 } | 343 } |
| 294 } | 344 } |
| 295 } | 345 } |
| 296 | 346 |
| 297 /** | 347 /** |
| 298 * Instances of the class [Server] manage a connection to a server process, and | 348 * Instances of the class [Server] manage a connection to a server process, and |
| 299 * facilitate communication to and from the server. | 349 * facilitate communication to and from the server. |
| 300 */ | 350 */ |
| (...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 387 expect(messageAsMap['id'], isString); | 437 expect(messageAsMap['id'], isString); |
| 388 String id = message['id']; | 438 String id = message['id']; |
| 389 Completer completer = server._pendingCommands[id]; | 439 Completer completer = server._pendingCommands[id]; |
| 390 if (completer == null) { | 440 if (completer == null) { |
| 391 fail('Unexpected response from server: id=$id'); | 441 fail('Unexpected response from server: id=$id'); |
| 392 } else { | 442 } else { |
| 393 server._pendingCommands.remove(id); | 443 server._pendingCommands.remove(id); |
| 394 } | 444 } |
| 395 if (messageAsMap.containsKey('error')) { | 445 if (messageAsMap.containsKey('error')) { |
| 396 // TODO(paulberry): propagate the error info to the completer. | 446 // TODO(paulberry): propagate the error info to the completer. |
| 397 completer.completeError(null); | 447 completer.completeError(new UnimplementedError( |
| 448 'Server responded with an error')); |
| 398 // Check that the message is well-formed. We do this after calling | 449 // Check that the message is well-formed. We do this after calling |
| 399 // completer.completeError() so that we don't stall the test in the | 450 // completer.completeError() so that we don't stall the test in the |
| 400 // event of an error. | 451 // event of an error. |
| 401 expect(message, isErrorResponse); | 452 expect(message, isErrorResponse); |
| 402 } else { | 453 } else { |
| 403 completer.complete(messageAsMap['result']); | 454 completer.complete(messageAsMap['result']); |
| 404 // Check that the message is well-formed. We do this after calling | 455 // Check that the message is well-formed. We do this after calling |
| 405 // completer.complete() so that we don't stall the test in the | 456 // completer.complete() so that we don't stall the test in the |
| 406 // event of an error. | 457 // event of an error. |
| 407 expect(message, isResultResponse); | 458 expect(message, isResultResponse); |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 481 * Record a message that was exchanged with the server, and print it out if | 532 * Record a message that was exchanged with the server, and print it out if |
| 482 * [debugStdio] has been called. | 533 * [debugStdio] has been called. |
| 483 */ | 534 */ |
| 484 void _recordStdio(String line) { | 535 void _recordStdio(String line) { |
| 485 if (_debuggingStdio) { | 536 if (_debuggingStdio) { |
| 486 print(line); | 537 print(line); |
| 487 } | 538 } |
| 488 _recordedStdio.add(line); | 539 _recordedStdio.add(line); |
| 489 } | 540 } |
| 490 } | 541 } |
| OLD | NEW |