| 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'; |
| 11 | 11 |
| 12 import 'package:analysis_server/src/constants.dart'; | 12 import 'package:analysis_server/src/constants.dart'; |
| 13 import 'package:path/path.dart'; | 13 import 'package:path/path.dart'; |
| 14 import 'package:unittest/unittest.dart'; | 14 import 'package:unittest/unittest.dart'; |
| 15 | 15 |
| 16 import 'protocol_matchers.dart'; |
| 17 |
| 16 /** | 18 /** |
| 17 * Base class for analysis server integration tests. | 19 * Base class for analysis server integration tests. |
| 18 */ | 20 */ |
| 19 abstract class AbstractAnalysisServerIntegrationTest { | 21 abstract class AbstractAnalysisServerIntegrationTest { |
| 20 /** | 22 /** |
| 21 * Amount of time to give the server to respond to a shutdown request before | 23 * Amount of time to give the server to respond to a shutdown request before |
| 22 * forcibly terminating it. | 24 * forcibly terminating it. |
| 23 */ | 25 */ |
| 24 static const Duration SHUTDOWN_TIMEOUT = const Duration(seconds: 5); | 26 static const Duration SHUTDOWN_TIMEOUT = const Duration(seconds: 5); |
| 25 | 27 |
| (...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 143 expect(params['file'], isString); | 145 expect(params['file'], isString); |
| 144 currentAnalysisErrors[params['file']] = params['errors']; | 146 currentAnalysisErrors[params['file']] = params['errors']; |
| 145 }); | 147 }); |
| 146 Completer serverConnected = new Completer(); | 148 Completer serverConnected = new Completer(); |
| 147 server.onNotification(SERVER_CONNECTED).listen((_) { | 149 server.onNotification(SERVER_CONNECTED).listen((_) { |
| 148 expect(serverConnected.isCompleted, isFalse); | 150 expect(serverConnected.isCompleted, isFalse); |
| 149 serverConnected.complete(); | 151 serverConnected.complete(); |
| 150 }); | 152 }); |
| 151 return server.start().then((params) { | 153 return server.start().then((params) { |
| 152 serverConnectedParams = params; | 154 serverConnectedParams = params; |
| 153 server.exitCode.then((_) { skipShutdown = true; }); | 155 server.exitCode.then((_) { |
| 156 skipShutdown = true; |
| 157 }); |
| 154 return serverConnected.future; | 158 return serverConnected.future; |
| 155 }); | 159 }); |
| 156 } | 160 } |
| 157 | 161 |
| 158 /** | 162 /** |
| 159 * After every test, the server is stopped and [sourceDirectory] is deleted. | 163 * After every test, the server is stopped and [sourceDirectory] is deleted. |
| 160 */ | 164 */ |
| 161 Future tearDown() { | 165 Future tearDown() { |
| 162 return _shutdownIfNeeded().then((_) { | 166 return _shutdownIfNeeded().then((_) { |
| 163 sourceDirectory.deleteSync(recursive: true); | 167 sourceDirectory.deleteSync(recursive: true); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 174 // Give the server a short time to comply with the shutdown request; if it | 178 // Give the server a short time to comply with the shutdown request; if it |
| 175 // doesn't exit, then forcibly terminate it. | 179 // doesn't exit, then forcibly terminate it. |
| 176 Completer processExited = new Completer(); | 180 Completer processExited = new Completer(); |
| 177 server.send(SERVER_SHUTDOWN, null); | 181 server.send(SERVER_SHUTDOWN, null); |
| 178 return server.exitCode.timeout(SHUTDOWN_TIMEOUT, onTimeout: () { | 182 return server.exitCode.timeout(SHUTDOWN_TIMEOUT, onTimeout: () { |
| 179 return server.kill(); | 183 return server.kill(); |
| 180 }); | 184 }); |
| 181 } | 185 } |
| 182 } | 186 } |
| 183 | 187 |
| 184 // Matchers for data types defined in the analysis server API | 188 final Matcher isResponse = new MatchesJsonObject('response', { |
| 185 // ========================================================== | |
| 186 // TODO(paulberry): add more matchers. | |
| 187 | |
| 188 // Matchers common to all domains | |
| 189 // ------------------------------ | |
| 190 | |
| 191 const Matcher isResponse = const MatchesJsonObject('response', const { | |
| 192 'id': isString | 189 'id': isString |
| 193 }, optionalFields: const { | 190 }, optionalFields: { |
| 194 'result': anything, | 191 'result': anything, |
| 195 'error': isError | 192 'error': isError |
| 196 }); | 193 }); |
| 197 | 194 |
| 198 const Matcher isError = const MatchesJsonObject('Error', const { | |
| 199 // TODO(paulberry): once we decide what the set of permitted error codes are, | |
| 200 // add validation for 'code'. | |
| 201 'code': anything, | |
| 202 'message': isString | |
| 203 }, optionalFields: const { | |
| 204 // TODO(paulberry): API spec says that 'data' is required, but sometimes we | |
| 205 // don't see it (example: error "Expected parameter subscriptions to be a | |
| 206 // string list map" in response to a malformed "analysis.setSubscriptions" | |
| 207 // command). | |
| 208 'data': anything | |
| 209 }); | |
| 210 | |
| 211 const Matcher isNotification = const MatchesJsonObject('notification', const { | 195 const Matcher isNotification = const MatchesJsonObject('notification', const { |
| 212 'event': isString | 196 'event': isString |
| 213 }, optionalFields: const { | 197 }, optionalFields: const { |
| 214 'params': isMap | 198 'params': isMap |
| 215 }); | 199 }); |
| 216 | 200 |
| 217 // Matchers for specific responses and notifications | |
| 218 // ------------------------------------------------- | |
| 219 | |
| 220 // server.getVersion | |
| 221 const Matcher isServerGetVersionResult = const MatchesJsonObject( | |
| 222 'server.getVersion result', const { | |
| 223 'version': isString | |
| 224 }); | |
| 225 | |
| 226 // server.status | |
| 227 const Matcher isServerStatusParams = const MatchesJsonObject( | |
| 228 'server.status params', null, optionalFields: const { | |
| 229 'analysis': isAnalysisStatus | |
| 230 }); | |
| 231 | |
| 232 // analysis.getErrors | |
| 233 final Matcher isAnalysisGetErrorsResult = new MatchesJsonObject( | |
| 234 'analysis.getErrors result', { | |
| 235 'errors': isListOf(isAnalysisError) | |
| 236 }); | |
| 237 | |
| 238 // analysis.getHover | |
| 239 final Matcher isAnalysisGetHoverResult = new MatchesJsonObject( | |
| 240 'analysis.getHover result', { | |
| 241 'hovers': isListOf(isHoverInformation) | |
| 242 }); | |
| 243 | |
| 244 // Matchers for data types used in responses and notifications | |
| 245 // ----------------------------------------------------------- | |
| 246 | |
| 247 const Matcher isString = const isInstanceOf<String>('String'); | 201 const Matcher isString = const isInstanceOf<String>('String'); |
| 248 | 202 |
| 249 const Matcher isInt = const isInstanceOf<int>('int'); | 203 const Matcher isInt = const isInstanceOf<int>('int'); |
| 250 | 204 |
| 251 const Matcher isBool = const isInstanceOf<bool>('bool'); | 205 const Matcher isBool = const isInstanceOf<bool>('bool'); |
| 252 | 206 |
| 253 // AnalysisError | 207 const Matcher isObject = isMap; |
| 254 final Matcher isAnalysisError = new MatchesJsonObject('AnalysisError', { | |
| 255 'severity': isErrorSeverity, | |
| 256 'type': isErrorType, | |
| 257 'location': isLocation, | |
| 258 'message': isString, | |
| 259 }, optionalFields: { | |
| 260 'correction': isString | |
| 261 }); | |
| 262 | |
| 263 // AnalysisStatus | |
| 264 const Matcher isAnalysisStatus = const MatchesJsonObject('AnalysisStatus', const | |
| 265 { | |
| 266 'analyzing': isBool | |
| 267 }, optionalFields: const { | |
| 268 'analysisTarget': isString | |
| 269 }); | |
| 270 | |
| 271 // ErrorSeverity | |
| 272 final Matcher isErrorSeverity = isIn(['INFO', 'WARNING', 'ERROR']); | |
| 273 | |
| 274 // ErrorType | |
| 275 final Matcher isErrorType = isIn(['COMPILE_TIME_ERROR', 'HINT', | |
| 276 'STATIC_TYPE_WARNING', 'STATIC_WARNING', 'SYNTACTIC_ERROR', 'TODO']); | |
| 277 | |
| 278 // HoverInformation | |
| 279 const Matcher isHoverInformation = const MatchesJsonObject('HoverInformation', | |
| 280 const { | |
| 281 'offset': isInt, | |
| 282 'length': isInt | |
| 283 }, optionalFields: const { | |
| 284 'containingLibraryPath': isString, | |
| 285 'containingLibraryName': isString, | |
| 286 'dartdoc': isString, | |
| 287 'elementDescription': isString, | |
| 288 'elementKind': isString, | |
| 289 'parameter': isString, | |
| 290 'propagatedType': isString, | |
| 291 'staticType': isString | |
| 292 }); | |
| 293 | |
| 294 // Location | |
| 295 const Matcher isLocation = const MatchesJsonObject('Location', const { | |
| 296 'file': isString, | |
| 297 'offset': isInt, | |
| 298 'length': isInt, | |
| 299 'startLine': isInt, | |
| 300 'startColumn': isInt | |
| 301 }); | |
| 302 | |
| 303 | 208 |
| 304 /** | 209 /** |
| 305 * Type of closures used by MatchesJsonObject to record field mismatches. | 210 * Type of closures used by MatchesJsonObject to record field mismatches. |
| 306 */ | 211 */ |
| 307 typedef Description MismatchDescriber(Description mismatchDescription, bool | 212 typedef Description MismatchDescriber(Description mismatchDescription); |
| 308 verbose); | 213 |
| 214 /** |
| 215 * Base class for matchers that operate by recursing through the contents of |
| 216 * an object. |
| 217 */ |
| 218 abstract class _RecursiveMatcher extends Matcher { |
| 219 const _RecursiveMatcher(); |
| 220 |
| 221 @override |
| 222 bool matches(item, Map matchState) { |
| 223 List<MismatchDescriber> mismatches = <MismatchDescriber>[]; |
| 224 populateMismatches(item, mismatches); |
| 225 if (mismatches.isEmpty) { |
| 226 return true; |
| 227 } else { |
| 228 addStateInfo(matchState, { |
| 229 'mismatches': mismatches |
| 230 }); |
| 231 return false; |
| 232 } |
| 233 } |
| 234 |
| 235 @override |
| 236 Description describeMismatch(item, Description mismatchDescription, Map |
| 237 matchState, bool verbose) { |
| 238 List<MismatchDescriber> mismatches = matchState['mismatches']; |
| 239 if (mismatches != null) { |
| 240 for (int i = 0; i < mismatches.length; i++) { |
| 241 MismatchDescriber mismatch = mismatches[i]; |
| 242 if (i > 0) { |
| 243 if (mismatches.length == 2) { |
| 244 mismatchDescription = mismatchDescription.add(' and '); |
| 245 } else if (i == mismatches.length - 1) { |
| 246 mismatchDescription = mismatchDescription.add(', and '); |
| 247 } else { |
| 248 mismatchDescription = mismatchDescription.add(', '); |
| 249 } |
| 250 } |
| 251 mismatchDescription = mismatch(mismatchDescription); |
| 252 } |
| 253 return mismatchDescription; |
| 254 } else { |
| 255 return super.describeMismatch(item, mismatchDescription, matchState, |
| 256 verbose); |
| 257 } |
| 258 } |
| 259 |
| 260 /** |
| 261 * Populate [mismatches] with descriptions of all the ways in which [item] |
| 262 * does not match. |
| 263 */ |
| 264 void populateMismatches(item, List<MismatchDescriber> mismatches); |
| 265 |
| 266 /** |
| 267 * Create a [MismatchDescriber] describing a mismatch with a simple string. |
| 268 */ |
| 269 MismatchDescriber simpleDescription(String description) => (Description |
| 270 mismatchDescription) { |
| 271 mismatchDescription.add(description); |
| 272 }; |
| 273 |
| 274 /** |
| 275 * Check the type of a substructure whose value is [item], using [matcher]. |
| 276 * If it doesn't match, record a closure in [mismatches] which can describe |
| 277 * the mismatch. [describeSubstructure] is used to describe which |
| 278 * substructure did not match. |
| 279 */ |
| 280 checkSubstructure(item, Matcher matcher, List<MismatchDescriber> |
| 281 mismatches, Description describeSubstructure(Description)) { |
| 282 Map subState = {}; |
| 283 if (!matcher.matches(item, subState)) { |
| 284 mismatches.add((Description mismatchDescription) { |
| 285 mismatchDescription = mismatchDescription.add('contains malformed '); |
| 286 mismatchDescription = describeSubstructure(mismatchDescription); |
| 287 mismatchDescription = mismatchDescription.add(' (should be ' |
| 288 ).addDescriptionOf(matcher); |
| 289 String subDescription = matcher.describeMismatch(item, |
| 290 new StringDescription(), subState, false).toString(); |
| 291 if (subDescription.isNotEmpty) { |
| 292 mismatchDescription = mismatchDescription.add('; ').add(subDescription |
| 293 ); |
| 294 } |
| 295 return mismatchDescription.add(')'); |
| 296 }); |
| 297 } |
| 298 } |
| 299 } |
| 309 | 300 |
| 310 /** | 301 /** |
| 311 * Matcher that matches a JSON object, with a given set of required and | 302 * Matcher that matches a JSON object, with a given set of required and |
| 312 * optional fields, and their associated types (expressed as [Matcher]s). | 303 * optional fields, and their associated types (expressed as [Matcher]s). |
| 313 */ | 304 */ |
| 314 class MatchesJsonObject extends Matcher { | 305 class MatchesJsonObject extends _RecursiveMatcher { |
| 315 /** | 306 /** |
| 316 * Short description of the expected type. | 307 * Short description of the expected type. |
| 317 */ | 308 */ |
| 318 final String description; | 309 final String description; |
| 319 | 310 |
| 320 /** | 311 /** |
| 321 * Fields that are required to be in the JSON object, and [Matcher]s describin
g | 312 * Fields that are required to be in the JSON object, and [Matcher]s describin
g |
| 322 * their expected types. | 313 * their expected types. |
| 323 */ | 314 */ |
| 324 final Map<String, Matcher> requiredFields; | 315 final Map<String, Matcher> requiredFields; |
| 325 | 316 |
| 326 /** | 317 /** |
| 327 * Fields that are optional in the JSON object, and [Matcher]s describing | 318 * Fields that are optional in the JSON object, and [Matcher]s describing |
| 328 * their expected types. | 319 * their expected types. |
| 329 */ | 320 */ |
| 330 final Map<String, Matcher> optionalFields; | 321 final Map<String, Matcher> optionalFields; |
| 331 | 322 |
| 332 const | 323 const |
| 333 MatchesJsonObject(this.description, this.requiredFields, {this.optionalFie
lds}); | 324 MatchesJsonObject(this.description, this.requiredFields, {this.optionalFie
lds}); |
| 334 | 325 |
| 335 @override | 326 @override |
| 336 bool matches(item, Map matchState) { | 327 void populateMismatches(item, List<MismatchDescriber> mismatches) { |
| 337 if (item is! Map) { | 328 if (item is! Map) { |
| 338 return false; | 329 mismatches.add(simpleDescription('is not a map')); |
| 330 return; |
| 339 } | 331 } |
| 340 List<MismatchDescriber> mismatches = <MismatchDescriber>[]; | |
| 341 if (requiredFields != null) { | 332 if (requiredFields != null) { |
| 342 requiredFields.forEach((String key, Matcher valueMatcher) { | 333 requiredFields.forEach((String key, Matcher valueMatcher) { |
| 343 if (!item.containsKey(key)) { | 334 if (!item.containsKey(key)) { |
| 344 mismatches.add((Description mismatchDescription, bool verbose) => | 335 mismatches.add((Description mismatchDescription) => |
| 345 mismatchDescription.add('is missing field ').addDescriptionOf(key)
.add(' (' | 336 mismatchDescription.add('is missing field ').addDescriptionOf(key)
.add(' (' |
| 346 ).addDescriptionOf(valueMatcher).add(')')); | 337 ).addDescriptionOf(valueMatcher).add(')')); |
| 347 } else { | 338 } else { |
| 348 _checkField(key, item[key], valueMatcher, mismatches); | 339 _checkField(key, item[key], valueMatcher, mismatches); |
| 349 } | 340 } |
| 350 }); | 341 }); |
| 351 } | 342 } |
| 352 item.forEach((key, value) { | 343 item.forEach((key, value) { |
| 353 if (requiredFields != null && requiredFields.containsKey(key)) { | 344 if (requiredFields != null && requiredFields.containsKey(key)) { |
| 354 // Already checked this field | 345 // Already checked this field |
| 355 } else if (optionalFields != null && optionalFields.containsKey(key)) { | 346 } else if (optionalFields != null && optionalFields.containsKey(key)) { |
| 356 _checkField(key, value, optionalFields[key], mismatches); | 347 _checkField(key, value, optionalFields[key], mismatches); |
| 357 } else { | 348 } else { |
| 358 mismatches.add((Description mismatchDescription, bool verbose) => | 349 mismatches.add((Description mismatchDescription) => |
| 359 mismatchDescription.add('has unexpected field ').addDescriptionOf(ke
y)); | 350 mismatchDescription.add('has unexpected field ').addDescriptionOf(ke
y)); |
| 360 } | 351 } |
| 361 }); | 352 }); |
| 362 if (mismatches.isEmpty) { | |
| 363 return true; | |
| 364 } else { | |
| 365 addStateInfo(matchState, { | |
| 366 'mismatches': mismatches | |
| 367 }); | |
| 368 return false; | |
| 369 } | |
| 370 } | 353 } |
| 371 | 354 |
| 372 @override | 355 @override |
| 373 Description describe(Description description) => description.add( | 356 Description describe(Description description) => description.add( |
| 374 this.description); | 357 this.description); |
| 375 | 358 |
| 376 @override | |
| 377 Description describeMismatch(item, Description mismatchDescription, Map | |
| 378 matchState, bool verbose) { | |
| 379 List<MismatchDescriber> mismatches = matchState['mismatches']; | |
| 380 if (mismatches != null) { | |
| 381 for (int i = 0; i < mismatches.length; i++) { | |
| 382 MismatchDescriber mismatch = mismatches[i]; | |
| 383 if (i > 0) { | |
| 384 if (mismatches.length == 2) { | |
| 385 mismatchDescription = mismatchDescription.add(' and '); | |
| 386 } else if (i == mismatches.length - 1) { | |
| 387 mismatchDescription = mismatchDescription.add(', and '); | |
| 388 } else { | |
| 389 mismatchDescription = mismatchDescription.add(', '); | |
| 390 } | |
| 391 } | |
| 392 mismatchDescription = mismatch(mismatchDescription, verbose); | |
| 393 } | |
| 394 return mismatchDescription; | |
| 395 } else { | |
| 396 return super.describeMismatch(item, mismatchDescription, matchState, | |
| 397 verbose); | |
| 398 } | |
| 399 } | |
| 400 | |
| 401 /** | 359 /** |
| 402 * Check the type of a field called [key], having value [value], using | 360 * Check the type of a field called [key], having value [value], using |
| 403 * [valueMatcher]. If it doesn't match, record a closure in [mismatches] | 361 * [valueMatcher]. If it doesn't match, record a closure in [mismatches] |
| 404 * which can describe the mismatch. | 362 * which can describe the mismatch. |
| 405 */ | 363 */ |
| 406 void _checkField(String key, value, Matcher | 364 void _checkField(String key, value, Matcher |
| 407 valueMatcher, List<MismatchDescriber> mismatches) { | 365 valueMatcher, List<MismatchDescriber> mismatches) { |
| 408 Map subState = {}; | 366 checkSubstructure(value, valueMatcher, mismatches, (Description description) |
| 409 if (!valueMatcher.matches(value, subState)) { | 367 => description.add('field ').addDescriptionOf(key)); |
| 410 mismatches.add((Description mismatchDescription, bool verbose) { | |
| 411 mismatchDescription = mismatchDescription.add( | |
| 412 'contains malformed field ').addDescriptionOf(key).add(' (should be
' | |
| 413 ).addDescriptionOf(valueMatcher); | |
| 414 String subDescription = valueMatcher.describeMismatch(value, | |
| 415 new StringDescription(), subState, false).toString(); | |
| 416 if (subDescription.isNotEmpty) { | |
| 417 mismatchDescription = mismatchDescription.add('; ').add(subDescription | |
| 418 ); | |
| 419 } | |
| 420 return mismatchDescription.add(')'); | |
| 421 }); | |
| 422 } | |
| 423 } | 368 } |
| 424 } | 369 } |
| 425 | 370 |
| 426 /** | 371 /** |
| 427 * Matcher that matches a list of objects, each of which satisfies the given | 372 * Matcher that matches a list of objects, each of which satisfies the given |
| 428 * matcher. | 373 * matcher. |
| 429 */ | 374 */ |
| 430 class _ListOf extends Matcher { | 375 class _ListOf extends Matcher { |
| 431 /** | 376 /** |
| 432 * Matcher which every element of the list must satisfy. | 377 * Matcher which every element of the list must satisfy. |
| (...skipping 30 matching lines...) Expand all Loading... |
| 463 } else { | 408 } else { |
| 464 return iterableMatcher.describeMismatch(item, mismatchDescription, | 409 return iterableMatcher.describeMismatch(item, mismatchDescription, |
| 465 matchState, verbose); | 410 matchState, verbose); |
| 466 } | 411 } |
| 467 } | 412 } |
| 468 } | 413 } |
| 469 | 414 |
| 470 Matcher isListOf(Matcher elementMatcher) => new _ListOf(elementMatcher); | 415 Matcher isListOf(Matcher elementMatcher) => new _ListOf(elementMatcher); |
| 471 | 416 |
| 472 /** | 417 /** |
| 418 * Matcher that matches a map of objects, where each key/value pair in the |
| 419 * map satisies the given key and value matchers. |
| 420 */ |
| 421 class _MapOf extends _RecursiveMatcher { |
| 422 /** |
| 423 * Matcher which every key in the map must satisfy. |
| 424 */ |
| 425 final Matcher keyMatcher; |
| 426 |
| 427 /** |
| 428 * Matcher which every value in the map must satisfy. |
| 429 */ |
| 430 final Matcher valueMatcher; |
| 431 |
| 432 _MapOf(this.keyMatcher, this.valueMatcher); |
| 433 |
| 434 @override |
| 435 void populateMismatches(item, List<MismatchDescriber> mismatches) { |
| 436 if (item is! Map) { |
| 437 mismatches.add(simpleDescription('is not a map')); |
| 438 return; |
| 439 } |
| 440 item.forEach((key, value) { |
| 441 checkSubstructure(key, keyMatcher, mismatches, (Description description) |
| 442 => description.add('key ').addDescriptionOf(key)); |
| 443 checkSubstructure(value, valueMatcher, mismatches, (Description |
| 444 description) => description.add('field ').addDescriptionOf(key)); |
| 445 }); |
| 446 } |
| 447 |
| 448 @override |
| 449 Description describe(Description description) => description.add('Map from ' |
| 450 ).addDescriptionOf(keyMatcher).add(' to ').addDescriptionOf(valueMatcher); |
| 451 } |
| 452 |
| 453 Matcher isMapOf(Matcher keyMatcher, Matcher valueMatcher) => new _MapOf( |
| 454 keyMatcher, valueMatcher); |
| 455 |
| 456 /** |
| 473 * Instances of the class [Server] manage a connection to a server process, and | 457 * Instances of the class [Server] manage a connection to a server process, and |
| 474 * facilitate communication to and from the server. | 458 * facilitate communication to and from the server. |
| 475 */ | 459 */ |
| 476 class Server { | 460 class Server { |
| 477 /** | 461 /** |
| 478 * Server process object, or null if server hasn't been started yet. | 462 * Server process object, or null if server hasn't been started yet. |
| 479 */ | 463 */ |
| 480 Process _process = null; | 464 Process _process = null; |
| 481 | 465 |
| 482 /** | 466 /** |
| (...skipping 233 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 716 */ | 700 */ |
| 717 void _recordStdio(String line) { | 701 void _recordStdio(String line) { |
| 718 double elapsedTime = _time.elapsedTicks / _time.frequency; | 702 double elapsedTime = _time.elapsedTicks / _time.frequency; |
| 719 line = "$elapsedTime: $line"; | 703 line = "$elapsedTime: $line"; |
| 720 if (_debuggingStdio) { | 704 if (_debuggingStdio) { |
| 721 print(line); | 705 print(line); |
| 722 } | 706 } |
| 723 _recordedStdio.add(line); | 707 _recordedStdio.add(line); |
| 724 } | 708 } |
| 725 } | 709 } |
| OLD | NEW |