Add more matchers for use in analysis server integration testing.
R=jwren@google.com Review URL: https://codereview.chromium.org//439313003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@38868 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
@@ -12,6 +12,7 @@ import 'package:path/path.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
import 'integration_tests.dart';
|
||||
import 'protocol_matchers.dart';
|
||||
|
||||
@ReflectiveTestCase()
|
||||
class AnalysisDomainIntegrationTest extends
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:analysis_testing/reflective_tests.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
import 'integration_tests.dart';
|
||||
import 'protocol_matchers.dart';
|
||||
|
||||
@ReflectiveTestCase()
|
||||
class AnalysisErrorIntegrationTest extends AbstractAnalysisServerIntegrationTest
|
||||
|
||||
@@ -13,6 +13,8 @@ import 'package:analysis_server/src/constants.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
import 'protocol_matchers.dart';
|
||||
|
||||
/**
|
||||
* Base class for analysis server integration tests.
|
||||
*/
|
||||
@@ -150,7 +152,9 @@ abstract class AbstractAnalysisServerIntegrationTest {
|
||||
});
|
||||
return server.start().then((params) {
|
||||
serverConnectedParams = params;
|
||||
server.exitCode.then((_) { skipShutdown = true; });
|
||||
server.exitCode.then((_) {
|
||||
skipShutdown = true;
|
||||
});
|
||||
return serverConnected.future;
|
||||
});
|
||||
}
|
||||
@@ -181,137 +185,124 @@ abstract class AbstractAnalysisServerIntegrationTest {
|
||||
}
|
||||
}
|
||||
|
||||
// Matchers for data types defined in the analysis server API
|
||||
// ==========================================================
|
||||
// TODO(paulberry): add more matchers.
|
||||
|
||||
// Matchers common to all domains
|
||||
// ------------------------------
|
||||
|
||||
const Matcher isResponse = const MatchesJsonObject('response', const {
|
||||
final Matcher isResponse = new MatchesJsonObject('response', {
|
||||
'id': isString
|
||||
}, optionalFields: const {
|
||||
}, optionalFields: {
|
||||
'result': anything,
|
||||
'error': isError
|
||||
});
|
||||
|
||||
const Matcher isError = const MatchesJsonObject('Error', const {
|
||||
// TODO(paulberry): once we decide what the set of permitted error codes are,
|
||||
// add validation for 'code'.
|
||||
'code': anything,
|
||||
'message': isString
|
||||
}, optionalFields: const {
|
||||
// TODO(paulberry): API spec says that 'data' is required, but sometimes we
|
||||
// don't see it (example: error "Expected parameter subscriptions to be a
|
||||
// string list map" in response to a malformed "analysis.setSubscriptions"
|
||||
// command).
|
||||
'data': anything
|
||||
});
|
||||
|
||||
const Matcher isNotification = const MatchesJsonObject('notification', const {
|
||||
'event': isString
|
||||
}, optionalFields: const {
|
||||
'params': isMap
|
||||
});
|
||||
|
||||
// Matchers for specific responses and notifications
|
||||
// -------------------------------------------------
|
||||
|
||||
// server.getVersion
|
||||
const Matcher isServerGetVersionResult = const MatchesJsonObject(
|
||||
'server.getVersion result', const {
|
||||
'version': isString
|
||||
});
|
||||
|
||||
// server.status
|
||||
const Matcher isServerStatusParams = const MatchesJsonObject(
|
||||
'server.status params', null, optionalFields: const {
|
||||
'analysis': isAnalysisStatus
|
||||
});
|
||||
|
||||
// analysis.getErrors
|
||||
final Matcher isAnalysisGetErrorsResult = new MatchesJsonObject(
|
||||
'analysis.getErrors result', {
|
||||
'errors': isListOf(isAnalysisError)
|
||||
});
|
||||
|
||||
// analysis.getHover
|
||||
final Matcher isAnalysisGetHoverResult = new MatchesJsonObject(
|
||||
'analysis.getHover result', {
|
||||
'hovers': isListOf(isHoverInformation)
|
||||
});
|
||||
|
||||
// Matchers for data types used in responses and notifications
|
||||
// -----------------------------------------------------------
|
||||
|
||||
const Matcher isString = const isInstanceOf<String>('String');
|
||||
|
||||
const Matcher isInt = const isInstanceOf<int>('int');
|
||||
|
||||
const Matcher isBool = const isInstanceOf<bool>('bool');
|
||||
|
||||
// AnalysisError
|
||||
final Matcher isAnalysisError = new MatchesJsonObject('AnalysisError', {
|
||||
'severity': isErrorSeverity,
|
||||
'type': isErrorType,
|
||||
'location': isLocation,
|
||||
'message': isString,
|
||||
}, optionalFields: {
|
||||
'correction': isString
|
||||
});
|
||||
|
||||
// AnalysisStatus
|
||||
const Matcher isAnalysisStatus = const MatchesJsonObject('AnalysisStatus', const
|
||||
{
|
||||
'analyzing': isBool
|
||||
}, optionalFields: const {
|
||||
'analysisTarget': isString
|
||||
});
|
||||
|
||||
// ErrorSeverity
|
||||
final Matcher isErrorSeverity = isIn(['INFO', 'WARNING', 'ERROR']);
|
||||
|
||||
// ErrorType
|
||||
final Matcher isErrorType = isIn(['COMPILE_TIME_ERROR', 'HINT',
|
||||
'STATIC_TYPE_WARNING', 'STATIC_WARNING', 'SYNTACTIC_ERROR', 'TODO']);
|
||||
|
||||
// HoverInformation
|
||||
const Matcher isHoverInformation = const MatchesJsonObject('HoverInformation',
|
||||
const {
|
||||
'offset': isInt,
|
||||
'length': isInt
|
||||
}, optionalFields: const {
|
||||
'containingLibraryPath': isString,
|
||||
'containingLibraryName': isString,
|
||||
'dartdoc': isString,
|
||||
'elementDescription': isString,
|
||||
'elementKind': isString,
|
||||
'parameter': isString,
|
||||
'propagatedType': isString,
|
||||
'staticType': isString
|
||||
});
|
||||
|
||||
// Location
|
||||
const Matcher isLocation = const MatchesJsonObject('Location', const {
|
||||
'file': isString,
|
||||
'offset': isInt,
|
||||
'length': isInt,
|
||||
'startLine': isInt,
|
||||
'startColumn': isInt
|
||||
});
|
||||
|
||||
const Matcher isObject = isMap;
|
||||
|
||||
/**
|
||||
* Type of closures used by MatchesJsonObject to record field mismatches.
|
||||
*/
|
||||
typedef Description MismatchDescriber(Description mismatchDescription, bool
|
||||
verbose);
|
||||
typedef Description MismatchDescriber(Description mismatchDescription);
|
||||
|
||||
/**
|
||||
* Base class for matchers that operate by recursing through the contents of
|
||||
* an object.
|
||||
*/
|
||||
abstract class _RecursiveMatcher extends Matcher {
|
||||
const _RecursiveMatcher();
|
||||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
List<MismatchDescriber> mismatches = <MismatchDescriber>[];
|
||||
populateMismatches(item, mismatches);
|
||||
if (mismatches.isEmpty) {
|
||||
return true;
|
||||
} else {
|
||||
addStateInfo(matchState, {
|
||||
'mismatches': mismatches
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Description describeMismatch(item, Description mismatchDescription, Map
|
||||
matchState, bool verbose) {
|
||||
List<MismatchDescriber> mismatches = matchState['mismatches'];
|
||||
if (mismatches != null) {
|
||||
for (int i = 0; i < mismatches.length; i++) {
|
||||
MismatchDescriber mismatch = mismatches[i];
|
||||
if (i > 0) {
|
||||
if (mismatches.length == 2) {
|
||||
mismatchDescription = mismatchDescription.add(' and ');
|
||||
} else if (i == mismatches.length - 1) {
|
||||
mismatchDescription = mismatchDescription.add(', and ');
|
||||
} else {
|
||||
mismatchDescription = mismatchDescription.add(', ');
|
||||
}
|
||||
}
|
||||
mismatchDescription = mismatch(mismatchDescription);
|
||||
}
|
||||
return mismatchDescription;
|
||||
} else {
|
||||
return super.describeMismatch(item, mismatchDescription, matchState,
|
||||
verbose);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate [mismatches] with descriptions of all the ways in which [item]
|
||||
* does not match.
|
||||
*/
|
||||
void populateMismatches(item, List<MismatchDescriber> mismatches);
|
||||
|
||||
/**
|
||||
* Create a [MismatchDescriber] describing a mismatch with a simple string.
|
||||
*/
|
||||
MismatchDescriber simpleDescription(String description) => (Description
|
||||
mismatchDescription) {
|
||||
mismatchDescription.add(description);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check the type of a substructure whose value is [item], using [matcher].
|
||||
* If it doesn't match, record a closure in [mismatches] which can describe
|
||||
* the mismatch. [describeSubstructure] is used to describe which
|
||||
* substructure did not match.
|
||||
*/
|
||||
checkSubstructure(item, Matcher matcher, List<MismatchDescriber>
|
||||
mismatches, Description describeSubstructure(Description)) {
|
||||
Map subState = {};
|
||||
if (!matcher.matches(item, subState)) {
|
||||
mismatches.add((Description mismatchDescription) {
|
||||
mismatchDescription = mismatchDescription.add('contains malformed ');
|
||||
mismatchDescription = describeSubstructure(mismatchDescription);
|
||||
mismatchDescription = mismatchDescription.add(' (should be '
|
||||
).addDescriptionOf(matcher);
|
||||
String subDescription = matcher.describeMismatch(item,
|
||||
new StringDescription(), subState, false).toString();
|
||||
if (subDescription.isNotEmpty) {
|
||||
mismatchDescription = mismatchDescription.add('; ').add(subDescription
|
||||
);
|
||||
}
|
||||
return mismatchDescription.add(')');
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matcher that matches a JSON object, with a given set of required and
|
||||
* optional fields, and their associated types (expressed as [Matcher]s).
|
||||
*/
|
||||
class MatchesJsonObject extends Matcher {
|
||||
class MatchesJsonObject extends _RecursiveMatcher {
|
||||
/**
|
||||
* Short description of the expected type.
|
||||
*/
|
||||
@@ -333,15 +324,15 @@ class MatchesJsonObject extends Matcher {
|
||||
MatchesJsonObject(this.description, this.requiredFields, {this.optionalFields});
|
||||
|
||||
@override
|
||||
bool matches(item, Map matchState) {
|
||||
void populateMismatches(item, List<MismatchDescriber> mismatches) {
|
||||
if (item is! Map) {
|
||||
return false;
|
||||
mismatches.add(simpleDescription('is not a map'));
|
||||
return;
|
||||
}
|
||||
List<MismatchDescriber> mismatches = <MismatchDescriber>[];
|
||||
if (requiredFields != null) {
|
||||
requiredFields.forEach((String key, Matcher valueMatcher) {
|
||||
if (!item.containsKey(key)) {
|
||||
mismatches.add((Description mismatchDescription, bool verbose) =>
|
||||
mismatches.add((Description mismatchDescription) =>
|
||||
mismatchDescription.add('is missing field ').addDescriptionOf(key).add(' ('
|
||||
).addDescriptionOf(valueMatcher).add(')'));
|
||||
} else {
|
||||
@@ -355,49 +346,16 @@ class MatchesJsonObject extends Matcher {
|
||||
} else if (optionalFields != null && optionalFields.containsKey(key)) {
|
||||
_checkField(key, value, optionalFields[key], mismatches);
|
||||
} else {
|
||||
mismatches.add((Description mismatchDescription, bool verbose) =>
|
||||
mismatches.add((Description mismatchDescription) =>
|
||||
mismatchDescription.add('has unexpected field ').addDescriptionOf(key));
|
||||
}
|
||||
});
|
||||
if (mismatches.isEmpty) {
|
||||
return true;
|
||||
} else {
|
||||
addStateInfo(matchState, {
|
||||
'mismatches': mismatches
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Description describe(Description description) => description.add(
|
||||
this.description);
|
||||
|
||||
@override
|
||||
Description describeMismatch(item, Description mismatchDescription, Map
|
||||
matchState, bool verbose) {
|
||||
List<MismatchDescriber> mismatches = matchState['mismatches'];
|
||||
if (mismatches != null) {
|
||||
for (int i = 0; i < mismatches.length; i++) {
|
||||
MismatchDescriber mismatch = mismatches[i];
|
||||
if (i > 0) {
|
||||
if (mismatches.length == 2) {
|
||||
mismatchDescription = mismatchDescription.add(' and ');
|
||||
} else if (i == mismatches.length - 1) {
|
||||
mismatchDescription = mismatchDescription.add(', and ');
|
||||
} else {
|
||||
mismatchDescription = mismatchDescription.add(', ');
|
||||
}
|
||||
}
|
||||
mismatchDescription = mismatch(mismatchDescription, verbose);
|
||||
}
|
||||
return mismatchDescription;
|
||||
} else {
|
||||
return super.describeMismatch(item, mismatchDescription, matchState,
|
||||
verbose);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the type of a field called [key], having value [value], using
|
||||
* [valueMatcher]. If it doesn't match, record a closure in [mismatches]
|
||||
@@ -405,21 +363,8 @@ class MatchesJsonObject extends Matcher {
|
||||
*/
|
||||
void _checkField(String key, value, Matcher
|
||||
valueMatcher, List<MismatchDescriber> mismatches) {
|
||||
Map subState = {};
|
||||
if (!valueMatcher.matches(value, subState)) {
|
||||
mismatches.add((Description mismatchDescription, bool verbose) {
|
||||
mismatchDescription = mismatchDescription.add(
|
||||
'contains malformed field ').addDescriptionOf(key).add(' (should be '
|
||||
).addDescriptionOf(valueMatcher);
|
||||
String subDescription = valueMatcher.describeMismatch(value,
|
||||
new StringDescription(), subState, false).toString();
|
||||
if (subDescription.isNotEmpty) {
|
||||
mismatchDescription = mismatchDescription.add('; ').add(subDescription
|
||||
);
|
||||
}
|
||||
return mismatchDescription.add(')');
|
||||
});
|
||||
}
|
||||
checkSubstructure(value, valueMatcher, mismatches, (Description description)
|
||||
=> description.add('field ').addDescriptionOf(key));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -469,6 +414,45 @@ class _ListOf extends Matcher {
|
||||
|
||||
Matcher isListOf(Matcher elementMatcher) => new _ListOf(elementMatcher);
|
||||
|
||||
/**
|
||||
* Matcher that matches a map of objects, where each key/value pair in the
|
||||
* map satisies the given key and value matchers.
|
||||
*/
|
||||
class _MapOf extends _RecursiveMatcher {
|
||||
/**
|
||||
* Matcher which every key in the map must satisfy.
|
||||
*/
|
||||
final Matcher keyMatcher;
|
||||
|
||||
/**
|
||||
* Matcher which every value in the map must satisfy.
|
||||
*/
|
||||
final Matcher valueMatcher;
|
||||
|
||||
_MapOf(this.keyMatcher, this.valueMatcher);
|
||||
|
||||
@override
|
||||
void populateMismatches(item, List<MismatchDescriber> mismatches) {
|
||||
if (item is! Map) {
|
||||
mismatches.add(simpleDescription('is not a map'));
|
||||
return;
|
||||
}
|
||||
item.forEach((key, value) {
|
||||
checkSubstructure(key, keyMatcher, mismatches, (Description description)
|
||||
=> description.add('key ').addDescriptionOf(key));
|
||||
checkSubstructure(value, valueMatcher, mismatches, (Description
|
||||
description) => description.add('field ').addDescriptionOf(key));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Description describe(Description description) => description.add('Map from '
|
||||
).addDescriptionOf(keyMatcher).add(' to ').addDescriptionOf(valueMatcher);
|
||||
}
|
||||
|
||||
Matcher isMapOf(Matcher keyMatcher, Matcher valueMatcher) => new _MapOf(
|
||||
keyMatcher, valueMatcher);
|
||||
|
||||
/**
|
||||
* Instances of the class [Server] manage a connection to a server process, and
|
||||
* facilitate communication to and from the server.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ import 'package:analysis_testing/reflective_tests.dart';
|
||||
import 'package:unittest/unittest.dart';
|
||||
|
||||
import 'integration_tests.dart';
|
||||
import 'protocol_matchers.dart';
|
||||
|
||||
@ReflectiveTestCase()
|
||||
class ServerDomainIntegrationTest extends AbstractAnalysisServerIntegrationTest
|
||||
|
||||
Reference in New Issue
Block a user