Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(140)

Side by Side Diff: pkg/analysis_server/test/integration/integration_tests.dart

Issue 439313003: Add more matchers for use in analysis server integration testing. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698