| 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 24 matching lines...) Expand all Loading... |
| 35 | 35 |
| 36 /** | 36 /** |
| 37 * Temporary directory in which source files can be stored. | 37 * Temporary directory in which source files can be stored. |
| 38 */ | 38 */ |
| 39 Directory sourceDirectory; | 39 Directory sourceDirectory; |
| 40 | 40 |
| 41 /** | 41 /** |
| 42 * Map from file path to the list of analysis errors which have most recently | 42 * Map from file path to the list of analysis errors which have most recently |
| 43 * been received for the file. | 43 * been received for the file. |
| 44 */ | 44 */ |
| 45 HashMap<String, dynamic> currentAnalysisErrors = new HashMap<String, dynamic>( | 45 HashMap<String, List<AnalysisError>> currentAnalysisErrors = |
| 46 ); | 46 new HashMap<String, List<AnalysisError>>(); |
| 47 | 47 |
| 48 /** | 48 /** |
| 49 * True if the teardown process should skip sending a "server.shutdown" | 49 * True if the teardown process should skip sending a "server.shutdown" |
| 50 * request (e.g. because the server is known to have already shutdown). | 50 * request (e.g. because the server is known to have already shutdown). |
| 51 */ | 51 */ |
| 52 bool skipShutdown = false; | 52 bool skipShutdown = false; |
| 53 | 53 |
| 54 /** | 54 /** |
| 55 * Data associated with the "server.connected" notification that was received | 55 * Data associated with the "server.connected" notification that was received |
| 56 * when the server started up. | 56 * when the server started up. |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 111 * received after this function call. So it is safe to use this getter | 111 * received after this function call. So it is safe to use this getter |
| 112 * multiple times in one test; each time it is used it will wait afresh for | 112 * multiple times in one test; each time it is used it will wait afresh for |
| 113 * analysis to finish. | 113 * analysis to finish. |
| 114 */ | 114 */ |
| 115 Future get analysisFinished { | 115 Future get analysisFinished { |
| 116 Completer completer = new Completer(); | 116 Completer completer = new Completer(); |
| 117 StreamSubscription subscription; | 117 StreamSubscription subscription; |
| 118 // This will only work if the caller has already subscribed to | 118 // This will only work if the caller has already subscribed to |
| 119 // SERVER_STATUS (e.g. using sendServerSetSubscriptions(['STATUS'])) | 119 // SERVER_STATUS (e.g. using sendServerSetSubscriptions(['STATUS'])) |
| 120 expect(_subscribedToServerStatus, isTrue); | 120 expect(_subscribedToServerStatus, isTrue); |
| 121 subscription = onServerStatus.listen((params) { | 121 subscription = onServerStatus.listen((ServerStatusParams params) { |
| 122 bool analysisComplete = false; | 122 if (!params.analysis.isAnalyzing) { |
| 123 try { | |
| 124 analysisComplete = !params['analysis']['isAnalyzing']; | |
| 125 } catch (_) { | |
| 126 // Status message was mal-formed or missing optional parameters. That's | |
| 127 // fine, since we'll detect a mal-formed status message below. | |
| 128 } | |
| 129 if (analysisComplete) { | |
| 130 completer.complete(params); | 123 completer.complete(params); |
| 131 subscription.cancel(); | 124 subscription.cancel(); |
| 132 } | 125 } |
| 133 expect(params, isServerStatusParams); | |
| 134 }); | 126 }); |
| 135 return completer.future; | 127 return completer.future; |
| 136 } | 128 } |
| 137 | 129 |
| 138 /** | 130 /** |
| 139 * Print out any messages exchanged with the server. If some messages have | 131 * Print out any messages exchanged with the server. If some messages have |
| 140 * already been exchanged with the server, they are printed out immediately. | 132 * already been exchanged with the server, they are printed out immediately. |
| 141 */ | 133 */ |
| 142 void debugStdio() { | 134 void debugStdio() { |
| 143 server.debugStdio(); | 135 server.debugStdio(); |
| 144 } | 136 } |
| 145 | 137 |
| 146 @override | 138 @override |
| 147 Future sendServerSetSubscriptions(List<ServerService> subscriptions) { | 139 Future sendServerSetSubscriptions(List<ServerService> subscriptions) { |
| 148 _subscribedToServerStatus = subscriptions.contains(ServerService.STATUS); | 140 _subscribedToServerStatus = subscriptions.contains(ServerService.STATUS); |
| 149 return super.sendServerSetSubscriptions(subscriptions); | 141 return super.sendServerSetSubscriptions(subscriptions); |
| 150 } | 142 } |
| 151 | 143 |
| 152 /** | 144 /** |
| 153 * The server is automatically started before every test, and a temporary | 145 * The server is automatically started before every test, and a temporary |
| 154 * [sourceDirectory] is created. | 146 * [sourceDirectory] is created. |
| 155 */ | 147 */ |
| 156 Future setUp() { | 148 Future setUp() { |
| 157 sourceDirectory = Directory.systemTemp.createTempSync('analysisServer'); | 149 sourceDirectory = Directory.systemTemp.createTempSync('analysisServer'); |
| 158 | 150 |
| 159 onAnalysisErrors.listen((params) { | 151 onAnalysisErrors.listen((AnalysisErrorsParams params) { |
| 160 currentAnalysisErrors[params['file']] = params['errors']; | 152 currentAnalysisErrors[params.file] = params.errors; |
| 161 }); | 153 }); |
| 162 Completer serverConnected = new Completer(); | 154 Completer serverConnected = new Completer(); |
| 163 onServerConnected.listen((_) { | 155 onServerConnected.listen((_) { |
| 164 expect(serverConnected.isCompleted, isFalse); | 156 expect(serverConnected.isCompleted, isFalse); |
| 165 serverConnected.complete(); | 157 serverConnected.complete(); |
| 166 }); | 158 }); |
| 167 return server.start(dispatchNotification).then((params) { | 159 return server.start(dispatchNotification).then((params) { |
| 168 serverConnectedParams = params; | 160 serverConnectedParams = params; |
| 169 server.exitCode.then((_) { | 161 server.exitCode.then((_) { |
| 170 skipShutdown = true; | 162 skipShutdown = true; |
| (...skipping 69 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 240 return true; | 232 return true; |
| 241 } else { | 233 } else { |
| 242 addStateInfo(matchState, { | 234 addStateInfo(matchState, { |
| 243 'mismatches': mismatches | 235 'mismatches': mismatches |
| 244 }); | 236 }); |
| 245 return false; | 237 return false; |
| 246 } | 238 } |
| 247 } | 239 } |
| 248 | 240 |
| 249 @override | 241 @override |
| 250 Description describeMismatch(item, Description mismatchDescription, Map | 242 Description describeMismatch(item, Description mismatchDescription, |
| 251 matchState, bool verbose) { | 243 Map matchState, bool verbose) { |
| 252 List<MismatchDescriber> mismatches = matchState['mismatches']; | 244 List<MismatchDescriber> mismatches = matchState['mismatches']; |
| 253 if (mismatches != null) { | 245 if (mismatches != null) { |
| 254 for (int i = 0; i < mismatches.length; i++) { | 246 for (int i = 0; i < mismatches.length; i++) { |
| 255 MismatchDescriber mismatch = mismatches[i]; | 247 MismatchDescriber mismatch = mismatches[i]; |
| 256 if (i > 0) { | 248 if (i > 0) { |
| 257 if (mismatches.length == 2) { | 249 if (mismatches.length == 2) { |
| 258 mismatchDescription = mismatchDescription.add(' and '); | 250 mismatchDescription = mismatchDescription.add(' and '); |
| 259 } else if (i == mismatches.length - 1) { | 251 } else if (i == mismatches.length - 1) { |
| 260 mismatchDescription = mismatchDescription.add(', and '); | 252 mismatchDescription = mismatchDescription.add(', and '); |
| 261 } else { | 253 } else { |
| 262 mismatchDescription = mismatchDescription.add(', '); | 254 mismatchDescription = mismatchDescription.add(', '); |
| 263 } | 255 } |
| 264 } | 256 } |
| 265 mismatchDescription = mismatch(mismatchDescription); | 257 mismatchDescription = mismatch(mismatchDescription); |
| 266 } | 258 } |
| 267 return mismatchDescription; | 259 return mismatchDescription; |
| 268 } else { | 260 } else { |
| 269 return super.describeMismatch(item, mismatchDescription, matchState, | 261 return super.describeMismatch( |
| 262 item, |
| 263 mismatchDescription, |
| 264 matchState, |
| 270 verbose); | 265 verbose); |
| 271 } | 266 } |
| 272 } | 267 } |
| 273 | 268 |
| 274 /** | 269 /** |
| 275 * Populate [mismatches] with descriptions of all the ways in which [item] | 270 * Populate [mismatches] with descriptions of all the ways in which [item] |
| 276 * does not match. | 271 * does not match. |
| 277 */ | 272 */ |
| 278 void populateMismatches(item, List<MismatchDescriber> mismatches); | 273 void populateMismatches(item, List<MismatchDescriber> mismatches); |
| 279 | 274 |
| 280 /** | 275 /** |
| 281 * Create a [MismatchDescriber] describing a mismatch with a simple string. | 276 * Create a [MismatchDescriber] describing a mismatch with a simple string. |
| 282 */ | 277 */ |
| 283 MismatchDescriber simpleDescription(String description) => (Description | 278 MismatchDescriber simpleDescription(String description) => |
| 284 mismatchDescription) { | 279 (Description mismatchDescription) { |
| 285 mismatchDescription.add(description); | 280 mismatchDescription.add(description); |
| 286 }; | 281 }; |
| 287 | 282 |
| 288 /** | 283 /** |
| 289 * Check the type of a substructure whose value is [item], using [matcher]. | 284 * Check the type of a substructure whose value is [item], using [matcher]. |
| 290 * If it doesn't match, record a closure in [mismatches] which can describe | 285 * If it doesn't match, record a closure in [mismatches] which can describe |
| 291 * the mismatch. [describeSubstructure] is used to describe which | 286 * the mismatch. [describeSubstructure] is used to describe which |
| 292 * substructure did not match. | 287 * substructure did not match. |
| 293 */ | 288 */ |
| 294 checkSubstructure(item, Matcher matcher, List<MismatchDescriber> | 289 checkSubstructure(item, Matcher matcher, List<MismatchDescriber> mismatches, |
| 295 mismatches, Description describeSubstructure(Description)) { | 290 Description describeSubstructure(Description)) { |
| 296 Map subState = {}; | 291 Map subState = {}; |
| 297 if (!matcher.matches(item, subState)) { | 292 if (!matcher.matches(item, subState)) { |
| 298 mismatches.add((Description mismatchDescription) { | 293 mismatches.add((Description mismatchDescription) { |
| 299 mismatchDescription = mismatchDescription.add('contains malformed '); | 294 mismatchDescription = mismatchDescription.add('contains malformed '); |
| 300 mismatchDescription = describeSubstructure(mismatchDescription); | 295 mismatchDescription = describeSubstructure(mismatchDescription); |
| 301 mismatchDescription = mismatchDescription.add(' (should be ' | 296 mismatchDescription = |
| 302 ).addDescriptionOf(matcher); | 297 mismatchDescription.add(' (should be ').addDescriptionOf(matcher); |
| 303 String subDescription = matcher.describeMismatch(item, | 298 String subDescription = matcher.describeMismatch( |
| 304 new StringDescription(), subState, false).toString(); | 299 item, |
| 300 new StringDescription(), |
| 301 subState, |
| 302 false).toString(); |
| 305 if (subDescription.isNotEmpty) { | 303 if (subDescription.isNotEmpty) { |
| 306 mismatchDescription = mismatchDescription.add('; ').add(subDescription | 304 mismatchDescription = |
| 307 ); | 305 mismatchDescription.add('; ').add(subDescription); |
| 308 } | 306 } |
| 309 return mismatchDescription.add(')'); | 307 return mismatchDescription.add(')'); |
| 310 }); | 308 }); |
| 311 } | 309 } |
| 312 } | 310 } |
| 313 } | 311 } |
| 314 | 312 |
| 315 /** | 313 /** |
| 316 * Matcher that matches a String drawn from a limited set. | 314 * Matcher that matches a String drawn from a limited set. |
| 317 */ | 315 */ |
| 318 class MatchesEnum extends Matcher { | 316 class MatchesEnum extends Matcher { |
| 319 /** | 317 /** |
| 320 * Short description of the expected type. | 318 * Short description of the expected type. |
| 321 */ | 319 */ |
| 322 final String description; | 320 final String description; |
| 323 | 321 |
| 324 /** | 322 /** |
| 325 * The set of enum values that are allowed. | 323 * The set of enum values that are allowed. |
| 326 */ | 324 */ |
| 327 final List<String> allowedValues; | 325 final List<String> allowedValues; |
| 328 | 326 |
| 329 const MatchesEnum(this.description, this.allowedValues); | 327 const MatchesEnum(this.description, this.allowedValues); |
| 330 | 328 |
| 331 @override | 329 @override |
| 332 bool matches(item, Map matchState) { | 330 bool matches(item, Map matchState) { |
| 333 return allowedValues.contains(item); | 331 return allowedValues.contains(item); |
| 334 } | 332 } |
| 335 | 333 |
| 336 @override | 334 @override |
| 337 Description describe(Description description) => description.add( | 335 Description describe(Description description) => |
| 338 this.description); | 336 description.add(this.description); |
| 339 } | 337 } |
| 340 | 338 |
| 341 /** | 339 /** |
| 342 * Matcher that matches a JSON object, with a given set of required and | 340 * Matcher that matches a JSON object, with a given set of required and |
| 343 * optional fields, and their associated types (expressed as [Matcher]s). | 341 * optional fields, and their associated types (expressed as [Matcher]s). |
| 344 */ | 342 */ |
| 345 class MatchesJsonObject extends _RecursiveMatcher { | 343 class MatchesJsonObject extends _RecursiveMatcher { |
| 346 /** | 344 /** |
| 347 * Short description of the expected type. | 345 * Short description of the expected type. |
| 348 */ | 346 */ |
| 349 final String description; | 347 final String description; |
| 350 | 348 |
| 351 /** | 349 /** |
| 352 * Fields that are required to be in the JSON object, and [Matcher]s describin
g | 350 * Fields that are required to be in the JSON object, and [Matcher]s describin
g |
| 353 * their expected types. | 351 * their expected types. |
| 354 */ | 352 */ |
| 355 final Map<String, Matcher> requiredFields; | 353 final Map<String, Matcher> requiredFields; |
| 356 | 354 |
| 357 /** | 355 /** |
| 358 * Fields that are optional in the JSON object, and [Matcher]s describing | 356 * Fields that are optional in the JSON object, and [Matcher]s describing |
| 359 * their expected types. | 357 * their expected types. |
| 360 */ | 358 */ |
| 361 final Map<String, Matcher> optionalFields; | 359 final Map<String, Matcher> optionalFields; |
| 362 | 360 |
| 363 const | 361 const MatchesJsonObject(this.description, this.requiredFields, |
| 364 MatchesJsonObject(this.description, this.requiredFields, {this.optionalFie
lds}); | 362 {this.optionalFields}); |
| 365 | 363 |
| 366 @override | 364 @override |
| 367 void populateMismatches(item, List<MismatchDescriber> mismatches) { | 365 void populateMismatches(item, List<MismatchDescriber> mismatches) { |
| 368 if (item is! Map) { | 366 if (item is! Map) { |
| 369 mismatches.add(simpleDescription('is not a map')); | 367 mismatches.add(simpleDescription('is not a map')); |
| 370 return; | 368 return; |
| 371 } | 369 } |
| 372 if (requiredFields != null) { | 370 if (requiredFields != null) { |
| 373 requiredFields.forEach((String key, Matcher valueMatcher) { | 371 requiredFields.forEach((String key, Matcher valueMatcher) { |
| 374 if (!item.containsKey(key)) { | 372 if (!item.containsKey(key)) { |
| 375 mismatches.add((Description mismatchDescription) => | 373 mismatches.add( |
| 376 mismatchDescription.add('is missing field ').addDescriptionOf(key)
.add(' (' | 374 (Description mismatchDescription) => |
| 377 ).addDescriptionOf(valueMatcher).add(')')); | 375 mismatchDescription.add( |
| 376 'is missing field ').addDescriptionOf( |
| 377 key).add(' (').addDescriptionOf(valueMatcher).add(')')
); |
| 378 } else { | 378 } else { |
| 379 _checkField(key, item[key], valueMatcher, mismatches); | 379 _checkField(key, item[key], valueMatcher, mismatches); |
| 380 } | 380 } |
| 381 }); | 381 }); |
| 382 } | 382 } |
| 383 item.forEach((key, value) { | 383 item.forEach((key, value) { |
| 384 if (requiredFields != null && requiredFields.containsKey(key)) { | 384 if (requiredFields != null && requiredFields.containsKey(key)) { |
| 385 // Already checked this field | 385 // Already checked this field |
| 386 } else if (optionalFields != null && optionalFields.containsKey(key)) { | 386 } else if (optionalFields != null && optionalFields.containsKey(key)) { |
| 387 _checkField(key, value, optionalFields[key], mismatches); | 387 _checkField(key, value, optionalFields[key], mismatches); |
| 388 } else { | 388 } else { |
| 389 mismatches.add((Description mismatchDescription) => | 389 mismatches.add( |
| 390 mismatchDescription.add('has unexpected field ').addDescriptionOf(ke
y)); | 390 (Description mismatchDescription) => |
| 391 mismatchDescription.add('has unexpected field ').addDescriptionO
f(key)); |
| 391 } | 392 } |
| 392 }); | 393 }); |
| 393 } | 394 } |
| 394 | 395 |
| 395 @override | 396 @override |
| 396 Description describe(Description description) => description.add( | 397 Description describe(Description description) => |
| 397 this.description); | 398 description.add(this.description); |
| 398 | 399 |
| 399 /** | 400 /** |
| 400 * Check the type of a field called [key], having value [value], using | 401 * Check the type of a field called [key], having value [value], using |
| 401 * [valueMatcher]. If it doesn't match, record a closure in [mismatches] | 402 * [valueMatcher]. If it doesn't match, record a closure in [mismatches] |
| 402 * which can describe the mismatch. | 403 * which can describe the mismatch. |
| 403 */ | 404 */ |
| 404 void _checkField(String key, value, Matcher | 405 void _checkField(String key, value, Matcher valueMatcher, |
| 405 valueMatcher, List<MismatchDescriber> mismatches) { | 406 List<MismatchDescriber> mismatches) { |
| 406 checkSubstructure(value, valueMatcher, mismatches, (Description description) | 407 checkSubstructure( |
| 407 => description.add('field ').addDescriptionOf(key)); | 408 value, |
| 409 valueMatcher, |
| 410 mismatches, |
| 411 (Description description) => description.add('field ').addDescriptionOf(
key)); |
| 408 } | 412 } |
| 409 } | 413 } |
| 410 | 414 |
| 411 /** | 415 /** |
| 412 * Matcher that matches a list of objects, each of which satisfies the given | 416 * Matcher that matches a list of objects, each of which satisfies the given |
| 413 * matcher. | 417 * matcher. |
| 414 */ | 418 */ |
| 415 class _ListOf extends Matcher { | 419 class _ListOf extends Matcher { |
| 416 /** | 420 /** |
| 417 * Matcher which every element of the list must satisfy. | 421 * Matcher which every element of the list must satisfy. |
| (...skipping 11 matching lines...) Expand all Loading... |
| 429 | 433 |
| 430 @override | 434 @override |
| 431 bool matches(item, Map matchState) { | 435 bool matches(item, Map matchState) { |
| 432 if (item is! List) { | 436 if (item is! List) { |
| 433 return false; | 437 return false; |
| 434 } | 438 } |
| 435 return iterableMatcher.matches(item, matchState); | 439 return iterableMatcher.matches(item, matchState); |
| 436 } | 440 } |
| 437 | 441 |
| 438 @override | 442 @override |
| 439 Description describe(Description description) => description.add('List of ' | 443 Description describe(Description description) => |
| 440 ).addDescriptionOf(elementMatcher); | 444 description.add('List of ').addDescriptionOf(elementMatcher); |
| 441 | 445 |
| 442 @override | 446 @override |
| 443 Description describeMismatch(item, Description mismatchDescription, Map | 447 Description describeMismatch(item, Description mismatchDescription, |
| 444 matchState, bool verbose) { | 448 Map matchState, bool verbose) { |
| 445 if (item is! List) { | 449 if (item is! List) { |
| 446 return super.describeMismatch(item, mismatchDescription, matchState, | 450 return super.describeMismatch( |
| 451 item, |
| 452 mismatchDescription, |
| 453 matchState, |
| 447 verbose); | 454 verbose); |
| 448 } else { | 455 } else { |
| 449 return iterableMatcher.describeMismatch(item, mismatchDescription, | 456 return iterableMatcher.describeMismatch( |
| 450 matchState, verbose); | 457 item, |
| 458 mismatchDescription, |
| 459 matchState, |
| 460 verbose); |
| 451 } | 461 } |
| 452 } | 462 } |
| 453 } | 463 } |
| 454 | 464 |
| 455 Matcher isListOf(Matcher elementMatcher) => new _ListOf(elementMatcher); | 465 Matcher isListOf(Matcher elementMatcher) => new _ListOf(elementMatcher); |
| 456 | 466 |
| 457 /** | 467 /** |
| 458 * Type of closures used by LazyMatcher. | 468 * Type of closures used by LazyMatcher. |
| 459 */ | 469 */ |
| 460 typedef Matcher MatcherCreator(); | 470 typedef Matcher MatcherCreator(); |
| (...skipping 18 matching lines...) Expand all Loading... |
| 479 | 489 |
| 480 LazyMatcher(this._creator); | 490 LazyMatcher(this._creator); |
| 481 | 491 |
| 482 @override | 492 @override |
| 483 Description describe(Description description) { | 493 Description describe(Description description) { |
| 484 _createMatcher(); | 494 _createMatcher(); |
| 485 return _wrappedMatcher.describe(description); | 495 return _wrappedMatcher.describe(description); |
| 486 } | 496 } |
| 487 | 497 |
| 488 @override | 498 @override |
| 489 Description describeMismatch(item, Description mismatchDescription, Map matchS
tate, bool verbose) { | 499 Description describeMismatch(item, Description mismatchDescription, |
| 500 Map matchState, bool verbose) { |
| 490 _createMatcher(); | 501 _createMatcher(); |
| 491 return _wrappedMatcher.describeMismatch(item, mismatchDescription, matchStat
e, verbose); | 502 return _wrappedMatcher.describeMismatch( |
| 503 item, |
| 504 mismatchDescription, |
| 505 matchState, |
| 506 verbose); |
| 492 } | 507 } |
| 493 | 508 |
| 494 @override | 509 @override |
| 495 bool matches(item, Map matchState) { | 510 bool matches(item, Map matchState) { |
| 496 _createMatcher(); | 511 _createMatcher(); |
| 497 return _wrappedMatcher.matches(item, matchState); | 512 return _wrappedMatcher.matches(item, matchState); |
| 498 } | 513 } |
| 499 | 514 |
| 500 /** | 515 /** |
| 501 * Create the wrapped matcher object, if it hasn't been created already. | 516 * Create the wrapped matcher object, if it hasn't been created already. |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 568 | 583 |
| 569 _MapOf(this.keyMatcher, this.valueMatcher); | 584 _MapOf(this.keyMatcher, this.valueMatcher); |
| 570 | 585 |
| 571 @override | 586 @override |
| 572 void populateMismatches(item, List<MismatchDescriber> mismatches) { | 587 void populateMismatches(item, List<MismatchDescriber> mismatches) { |
| 573 if (item is! Map) { | 588 if (item is! Map) { |
| 574 mismatches.add(simpleDescription('is not a map')); | 589 mismatches.add(simpleDescription('is not a map')); |
| 575 return; | 590 return; |
| 576 } | 591 } |
| 577 item.forEach((key, value) { | 592 item.forEach((key, value) { |
| 578 checkSubstructure(key, keyMatcher, mismatches, (Description description) | 593 checkSubstructure( |
| 579 => description.add('key ').addDescriptionOf(key)); | 594 key, |
| 580 checkSubstructure(value, valueMatcher, mismatches, (Description | 595 keyMatcher, |
| 581 description) => description.add('field ').addDescriptionOf(key)); | 596 mismatches, |
| 597 (Description description) => description.add('key ').addDescriptionOf(
key)); |
| 598 checkSubstructure( |
| 599 value, |
| 600 valueMatcher, |
| 601 mismatches, |
| 602 (Description description) => description.add('field ').addDescriptionO
f(key)); |
| 582 }); | 603 }); |
| 583 } | 604 } |
| 584 | 605 |
| 585 @override | 606 @override |
| 586 Description describe(Description description) => description.add('Map from ' | 607 Description describe(Description description) => |
| 587 ).addDescriptionOf(keyMatcher).add(' to ').addDescriptionOf(valueMatcher); | 608 description.add( |
| 609 'Map from ').addDescriptionOf( |
| 610 keyMatcher).add(' to ').addDescriptionOf(valueMatcher); |
| 588 } | 611 } |
| 589 | 612 |
| 590 Matcher isMapOf(Matcher keyMatcher, Matcher valueMatcher) => new _MapOf( | 613 Matcher isMapOf(Matcher keyMatcher, Matcher valueMatcher) => |
| 591 keyMatcher, valueMatcher); | 614 new _MapOf(keyMatcher, valueMatcher); |
| 592 | 615 |
| 593 /** | 616 /** |
| 594 * Type of callbacks used to process notifications. | 617 * Type of callbacks used to process notifications. |
| 595 */ | 618 */ |
| 596 typedef void NotificationProcessor(String event, params); | 619 typedef void NotificationProcessor(String event, params); |
| 597 | 620 |
| 598 /** | 621 /** |
| 599 * Instances of the class [Server] manage a connection to a server process, and | 622 * Instances of the class [Server] manage a connection to a server process, and |
| 600 * facilitate communication to and from the server. | 623 * facilitate communication to and from the server. |
| 601 */ | 624 */ |
| 602 class Server { | 625 class Server { |
| 603 /** | 626 /** |
| 604 * Server process object, or null if server hasn't been started yet. | 627 * Server process object, or null if server hasn't been started yet. |
| 605 */ | 628 */ |
| 606 Process _process = null; | 629 Process _process = null; |
| 607 | 630 |
| 608 /** | 631 /** |
| 609 * Commands that have been sent to the server but not yet acknowledged, and | 632 * Commands that have been sent to the server but not yet acknowledged, and |
| 610 * the [Completer] objects which should be completed when acknowledgement is | 633 * the [Completer] objects which should be completed when acknowledgement is |
| 611 * received. | 634 * received. |
| 612 */ | 635 */ |
| 613 final HashMap<String, Completer> _pendingCommands = <String, Completer> {}; | 636 final HashMap<String, Completer> _pendingCommands = <String, Completer>{}; |
| 614 | 637 |
| 615 /** | 638 /** |
| 616 * Number which should be used to compute the 'id' to send in the next command | 639 * Number which should be used to compute the 'id' to send in the next command |
| 617 * sent to the server. | 640 * sent to the server. |
| 618 */ | 641 */ |
| 619 int _nextId = 0; | 642 int _nextId = 0; |
| 620 | 643 |
| 621 /** | 644 /** |
| 622 * [StreamController] to which notifications will be sent. | 645 * [StreamController] to which notifications will be sent. |
| 623 */ | 646 */ |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 667 */ | 690 */ |
| 668 Future start(NotificationProcessor notificationProcessor, {bool debugServer: | 691 Future start(NotificationProcessor notificationProcessor, {bool debugServer: |
| 669 false}) { | 692 false}) { |
| 670 if (_process != null) { | 693 if (_process != null) { |
| 671 throw new Exception('Process already started'); | 694 throw new Exception('Process already started'); |
| 672 } | 695 } |
| 673 _time.start(); | 696 _time.start(); |
| 674 // TODO(paulberry): move the logic for finding the script, the dart | 697 // TODO(paulberry): move the logic for finding the script, the dart |
| 675 // executable, and the package root into a shell script. | 698 // executable, and the package root into a shell script. |
| 676 String dartBinary = Platform.executable; | 699 String dartBinary = Platform.executable; |
| 677 String rootDir = findRoot(Platform.script.toFilePath(windows: | 700 String rootDir = |
| 678 Platform.isWindows)); | 701 findRoot(Platform.script.toFilePath(windows: Platform.isWindows)); |
| 679 String serverPath = normalize(join(rootDir, 'bin', 'server.dart')); | 702 String serverPath = normalize(join(rootDir, 'bin', 'server.dart')); |
| 680 List<String> arguments = []; | 703 List<String> arguments = []; |
| 681 if (debugServer) { | 704 if (debugServer) { |
| 682 arguments.add('--debug'); | 705 arguments.add('--debug'); |
| 683 } | 706 } |
| 684 if (Platform.packageRoot.isNotEmpty) { | 707 if (Platform.packageRoot.isNotEmpty) { |
| 685 arguments.add('--package-root=${Platform.packageRoot}'); | 708 arguments.add('--package-root=${Platform.packageRoot}'); |
| 686 } | 709 } |
| 687 arguments.add('--checked'); | 710 arguments.add('--checked'); |
| 688 arguments.add(serverPath); | 711 arguments.add(serverPath); |
| 689 return Process.start(dartBinary, arguments).then((Process process) { | 712 return Process.start(dartBinary, arguments).then((Process process) { |
| 690 _process = process; | 713 _process = process; |
| 691 process.stdout.transform((new Utf8Codec()).decoder).transform( | 714 process.stdout.transform( |
| 692 new LineSplitter()).listen((String line) { | 715 (new Utf8Codec()).decoder).transform(new LineSplitter()).listen((Strin
g line) { |
| 693 String trimmedLine = line.trim(); | 716 String trimmedLine = line.trim(); |
| 694 _recordStdio('RECV: $trimmedLine'); | 717 _recordStdio('RECV: $trimmedLine'); |
| 695 var message; | 718 var message; |
| 696 try { | 719 try { |
| 697 message = JSON.decoder.convert(trimmedLine); | 720 message = JSON.decoder.convert(trimmedLine); |
| 698 } catch (exception) { | 721 } catch (exception) { |
| 699 _badDataFromServer(); | 722 _badDataFromServer(); |
| 700 return; | 723 return; |
| 701 } | 724 } |
| 702 expect(message, isMap); | 725 expect(message, isMap); |
| 703 Map messageAsMap = message; | 726 Map messageAsMap = message; |
| 704 if (messageAsMap.containsKey('id')) { | 727 if (messageAsMap.containsKey('id')) { |
| 705 expect(messageAsMap['id'], isString); | 728 expect(messageAsMap['id'], isString); |
| 706 String id = message['id']; | 729 String id = message['id']; |
| 707 Completer completer = _pendingCommands[id]; | 730 Completer completer = _pendingCommands[id]; |
| 708 if (completer == null) { | 731 if (completer == null) { |
| 709 fail('Unexpected response from server: id=$id'); | 732 fail('Unexpected response from server: id=$id'); |
| 710 } else { | 733 } else { |
| 711 _pendingCommands.remove(id); | 734 _pendingCommands.remove(id); |
| 712 } | 735 } |
| 713 if (messageAsMap.containsKey('error')) { | 736 if (messageAsMap.containsKey('error')) { |
| 714 // TODO(paulberry): propagate the error info to the completer. | 737 // TODO(paulberry): propagate the error info to the completer. |
| 715 completer.completeError(new UnimplementedError( | 738 completer.completeError( |
| 716 'Server responded with an error: ${JSON.encode(message)}')); | 739 new UnimplementedError( |
| 740 'Server responded with an error: ${JSON.encode(message)}')); |
| 717 } else { | 741 } else { |
| 718 completer.complete(messageAsMap['result']); | 742 completer.complete(messageAsMap['result']); |
| 719 } | 743 } |
| 720 // Check that the message is well-formed. We do this after calling | 744 // Check that the message is well-formed. We do this after calling |
| 721 // completer.complete() or completer.completeError() so that we don't | 745 // completer.complete() or completer.completeError() so that we don't |
| 722 // stall the test in the event of an error. | 746 // stall the test in the event of an error. |
| 723 expect(message, isResponse); | 747 expect(message, isResponse); |
| 724 } else { | 748 } else { |
| 725 // Message is a notification. It should have an event and possibly | 749 // Message is a notification. It should have an event and possibly |
| 726 // params. | 750 // params. |
| 727 expect(messageAsMap, contains('event')); | 751 expect(messageAsMap, contains('event')); |
| 728 expect(messageAsMap['event'], isString); | 752 expect(messageAsMap['event'], isString); |
| 729 notificationProcessor(messageAsMap['event'], messageAsMap['params']); | 753 notificationProcessor(messageAsMap['event'], messageAsMap['params']); |
| 730 // Check that the message is well-formed. We do this after calling | 754 // Check that the message is well-formed. We do this after calling |
| 731 // notificationController.add() so that we don't stall the test in the | 755 // notificationController.add() so that we don't stall the test in the |
| 732 // event of an error. | 756 // event of an error. |
| 733 expect(message, isNotification); | 757 expect(message, isNotification); |
| 734 } | 758 } |
| 735 }); | 759 }); |
| 736 process.stderr.transform((new Utf8Codec()).decoder).transform( | 760 process.stderr.transform( |
| 737 new LineSplitter()).listen((String line) { | 761 (new Utf8Codec()).decoder).transform(new LineSplitter()).listen((Strin
g line) { |
| 738 String trimmedLine = line.trim(); | 762 String trimmedLine = line.trim(); |
| 739 _recordStdio('ERR: $trimmedLine'); | 763 _recordStdio('ERR: $trimmedLine'); |
| 740 _badDataFromServer(); | 764 _badDataFromServer(); |
| 741 }); | 765 }); |
| 742 process.exitCode.then((int code) { | 766 process.exitCode.then((int code) { |
| 743 _recordStdio('TERMINATED WITH EXIT CODE $code'); | 767 _recordStdio('TERMINATED WITH EXIT CODE $code'); |
| 744 if (code != 0) { | 768 if (code != 0) { |
| 745 _badDataFromServer(); | 769 _badDataFromServer(); |
| 746 } | 770 } |
| 747 }); | 771 }); |
| (...skipping 18 matching lines...) Expand all Loading... |
| 766 /** | 790 /** |
| 767 * Send a command to the server. An 'id' will be automatically assigned. | 791 * Send a command to the server. An 'id' will be automatically assigned. |
| 768 * The returned [Future] will be completed when the server acknowledges the | 792 * The returned [Future] will be completed when the server acknowledges the |
| 769 * command with a response. If the server acknowledges the command with a | 793 * command with a response. If the server acknowledges the command with a |
| 770 * normal (non-error) response, the future will be completed with the 'result' | 794 * normal (non-error) response, the future will be completed with the 'result' |
| 771 * field from the response. If the server acknowledges the command with an | 795 * field from the response. If the server acknowledges the command with an |
| 772 * error response, the future will be completed with an error. | 796 * error response, the future will be completed with an error. |
| 773 */ | 797 */ |
| 774 Future send(String method, Map<String, dynamic> params) { | 798 Future send(String method, Map<String, dynamic> params) { |
| 775 String id = '${_nextId++}'; | 799 String id = '${_nextId++}'; |
| 776 Map<String, dynamic> command = <String, dynamic> { | 800 Map<String, dynamic> command = <String, dynamic>{ |
| 777 'id': id, | 801 'id': id, |
| 778 'method': method | 802 'method': method |
| 779 }; | 803 }; |
| 780 if (params != null) { | 804 if (params != null) { |
| 781 command['params'] = params; | 805 command['params'] = params; |
| 782 } | 806 } |
| 783 Completer completer = new Completer(); | 807 Completer completer = new Completer(); |
| 784 _pendingCommands[id] = completer; | 808 _pendingCommands[id] = completer; |
| 785 String line = JSON.encode(command); | 809 String line = JSON.encode(command); |
| 786 _recordStdio('SEND: $line'); | 810 _recordStdio('SEND: $line'); |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 828 */ | 852 */ |
| 829 void _recordStdio(String line) { | 853 void _recordStdio(String line) { |
| 830 double elapsedTime = _time.elapsedTicks / _time.frequency; | 854 double elapsedTime = _time.elapsedTicks / _time.frequency; |
| 831 line = "$elapsedTime: $line"; | 855 line = "$elapsedTime: $line"; |
| 832 if (_debuggingStdio) { | 856 if (_debuggingStdio) { |
| 833 print(line); | 857 print(line); |
| 834 } | 858 } |
| 835 _recordedStdio.add(line); | 859 _recordedStdio.add(line); |
| 836 } | 860 } |
| 837 } | 861 } |
| OLD | NEW |