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

Unified Diff: pkg/analysis_server/test/integration/integration_tests.dart

Issue 725143004: Format and sort analyzer and analysis_server packages. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 1 month 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 side-by-side diff with in-line comments
Download patch
Index: pkg/analysis_server/test/integration/integration_tests.dart
diff --git a/pkg/analysis_server/test/integration/integration_tests.dart b/pkg/analysis_server/test/integration/integration_tests.dart
index 65ca8bab1826edab35e5cd84e0c95446f4c043a0..a2bd8cc33fe8528196a26ff372b4306dbc29735a 100644
--- a/pkg/analysis_server/test/integration/integration_tests.dart
+++ b/pkg/analysis_server/test/integration/integration_tests.dart
@@ -17,6 +17,49 @@ import 'package:unittest/unittest.dart';
import 'integration_test_methods.dart';
import 'protocol_matchers.dart';
+const Matcher isBool = const isInstanceOf<bool>('bool');
+
+const Matcher isInt = const isInstanceOf<int>('int');
+
+const Matcher isNotification = const MatchesJsonObject('notification', const {
+ 'event': isString
+}, optionalFields: const {
+ 'params': isMap
+});
+
+const Matcher isObject = isMap;
+
+final Matcher isResponse = new MatchesJsonObject('response', {
+ 'id': isString
+}, optionalFields: {
+ 'result': anything,
+ 'error': isRequestError
+});
+
+const Matcher isString = const isInstanceOf<String>('String');
+
+Matcher isListOf(Matcher elementMatcher) => new _ListOf(elementMatcher);
+
+Matcher isMapOf(Matcher keyMatcher, Matcher valueMatcher) =>
+ new _MapOf(keyMatcher, valueMatcher);
+
+Matcher isOneOf(List<Matcher> choiceMatchers) => new _OneOf(choiceMatchers);
+
+/**
+ * Type of closures used by LazyMatcher.
+ */
+typedef Matcher MatcherCreator();
+
+/**
+ * Type of closures used by MatchesJsonObject to record field mismatches.
+ */
+typedef Description MismatchDescriber(Description mismatchDescription);
+
+/**
+ * Type of callbacks used to process notifications.
+ */
+typedef void NotificationProcessor(String event, params);
+
/**
* Base class for analysis server integration tests.
*/
@@ -61,47 +104,6 @@ abstract class AbstractAnalysisServerIntegrationTest extends
}
/**
- * Write a source file with the given absolute [pathname] and [contents].
- *
- * If the file didn't previously exist, it is created. If it did, it is
- * overwritten.
- *
- * Parent directories are created as necessary.
- *
- * Return a normalized path to the file (with symbolic links resolved).
- */
- String writeFile(String pathname, String contents) {
- new Directory(dirname(pathname)).createSync(recursive: true);
- File file = new File(pathname);
- file.writeAsStringSync(contents);
- return file.resolveSymbolicLinksSync();
- }
-
- /**
- * Convert the given [relativePath] to an absolute path, by interpreting it
- * relative to [sourceDirectory]. On Windows any forward slashes in
- * [relativePath] are converted to backslashes.
- */
- String sourcePath(String relativePath) {
- return join(sourceDirectory.path, relativePath.replaceAll('/', separator));
- }
-
- /**
- * Send the server an 'analysis.setAnalysisRoots' command directing it to
- * analyze [sourceDirectory]. If [subscribeStatus] is true (the default),
- * then also enable [SERVER_STATUS] notifications so that [analysisFinished]
- * can be used.
- */
- Future standardAnalysisSetup({bool subscribeStatus: true}) {
- List<Future> futures = <Future>[];
- if (subscribeStatus) {
- futures.add(sendServerSetSubscriptions([ServerService.STATUS]));
- }
- futures.add(sendAnalysisSetAnalysisRoots([sourceDirectory.path], []));
- return Future.wait(futures);
- }
-
- /**
* Return a future which will complete when a 'server.status' notification is
* received from the server with 'analyzing' set to false.
*
@@ -168,6 +170,30 @@ abstract class AbstractAnalysisServerIntegrationTest extends
}
/**
+ * Convert the given [relativePath] to an absolute path, by interpreting it
+ * relative to [sourceDirectory]. On Windows any forward slashes in
+ * [relativePath] are converted to backslashes.
+ */
+ String sourcePath(String relativePath) {
+ return join(sourceDirectory.path, relativePath.replaceAll('/', separator));
+ }
+
+ /**
+ * Send the server an 'analysis.setAnalysisRoots' command directing it to
+ * analyze [sourceDirectory]. If [subscribeStatus] is true (the default),
+ * then also enable [SERVER_STATUS] notifications so that [analysisFinished]
+ * can be used.
+ */
+ Future standardAnalysisSetup({bool subscribeStatus: true}) {
+ List<Future> futures = <Future>[];
+ if (subscribeStatus) {
+ futures.add(sendServerSetSubscriptions([ServerService.STATUS]));
+ }
+ futures.add(sendAnalysisSetAnalysisRoots([sourceDirectory.path], []));
+ return Future.wait(futures);
+ }
+
+ /**
* After every test, the server is stopped and [sourceDirectory] is deleted.
*/
Future tearDown() {
@@ -177,6 +203,23 @@ abstract class AbstractAnalysisServerIntegrationTest extends
}
/**
+ * Write a source file with the given absolute [pathname] and [contents].
+ *
+ * If the file didn't previously exist, it is created. If it did, it is
+ * overwritten.
+ *
+ * Parent directories are created as necessary.
+ *
+ * Return a normalized path to the file (with symbolic links resolved).
+ */
+ String writeFile(String pathname, String contents) {
+ new Directory(dirname(pathname)).createSync(recursive: true);
+ File file = new File(pathname);
+ file.writeAsStringSync(contents);
+ return file.resolveSymbolicLinksSync();
+ }
+
+ /**
* If [skipShutdown] is not set, shut down the server.
*/
Future _shutdownIfNeeded() {
@@ -192,149 +235,83 @@ abstract class AbstractAnalysisServerIntegrationTest extends
}
}
-final Matcher isResponse = new MatchesJsonObject('response', {
- 'id': isString
-}, optionalFields: {
- 'result': anything,
- 'error': isRequestError
-});
-
-const Matcher isNotification = const MatchesJsonObject('notification', const {
- 'event': isString
-}, optionalFields: const {
- 'params': isMap
-});
-
-const Matcher isString = const isInstanceOf<String>('String');
-
-const Matcher isInt = const isInstanceOf<int>('int');
-
-const Matcher isBool = const isInstanceOf<bool>('bool');
-
-const Matcher isObject = isMap;
-
/**
- * Type of closures used by MatchesJsonObject to record field mismatches.
+ * Wrapper class for Matcher which doesn't create the underlying Matcher object
+ * until it is needed. This is necessary in order to create matchers that can
+ * refer to themselves (so that recursive data structures can be represented).
*/
-typedef Description MismatchDescriber(Description mismatchDescription);
+class LazyMatcher implements Matcher {
+ /**
+ * Callback that will be used to create the matcher the first time it is
+ * needed.
+ */
+ final MatcherCreator _creator;
-/**
- * Base class for matchers that operate by recursing through the contents of
- * an object.
- */
-abstract class _RecursiveMatcher extends Matcher {
- const _RecursiveMatcher();
+ /**
+ * The matcher returned by [_creator], if it has already been called.
+ * Otherwise null.
+ */
+ Matcher _wrappedMatcher;
+
+ LazyMatcher(this._creator);
@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;
- }
+ Description describe(Description description) {
+ _createMatcher();
+ return _wrappedMatcher.describe(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);
- }
- return mismatchDescription;
- } else {
- return super.describeMismatch(
- item,
- mismatchDescription,
- matchState,
- verbose);
- }
+ _createMatcher();
+ return _wrappedMatcher.describeMismatch(
+ item,
+ mismatchDescription,
+ matchState,
+ verbose);
+ }
+
+ @override
+ bool matches(item, Map matchState) {
+ _createMatcher();
+ return _wrappedMatcher.matches(item, matchState);
}
/**
- * Populate [mismatches] with descriptions of all the ways in which [item]
- * does not match.
+ * Create the wrapped matcher object, if it hasn't been created already.
*/
- void populateMismatches(item, List<MismatchDescriber> mismatches);
+ void _createMatcher() {
+ if (_wrappedMatcher == null) {
+ _wrappedMatcher = _creator();
+ }
+ }
+}
+/**
+ * Matcher that matches a String drawn from a limited set.
+ */
+class MatchesEnum extends Matcher {
/**
- * Create a [MismatchDescriber] describing a mismatch with a simple string.
+ * Short description of the expected type.
*/
- MismatchDescriber simpleDescription(String description) =>
- (Description mismatchDescription) {
- mismatchDescription.add(description);
- };
+ final String 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 String drawn from a limited set.
- */
-class MatchesEnum extends Matcher {
- /**
- * Short description of the expected type.
- */
- final String description;
-
- /**
- * The set of enum values that are allowed.
+ * The set of enum values that are allowed.
*/
final List<String> allowedValues;
const MatchesEnum(this.description, this.allowedValues);
@override
+ Description describe(Description description) =>
+ description.add(this.description);
+
+ @override
bool matches(item, Map matchState) {
return allowedValues.contains(item);
}
-
- @override
- Description describe(Description description) =>
- description.add(this.description);
}
/**
@@ -363,6 +340,10 @@ class MatchesJsonObject extends _RecursiveMatcher {
{this.optionalFields});
@override
+ Description describe(Description description) =>
+ description.add(this.description);
+
+ @override
void populateMismatches(item, List<MismatchDescriber> mismatches) {
if (item is! Map) {
mismatches.add(simpleDescription('is not a map'));
@@ -394,10 +375,6 @@ class MatchesJsonObject extends _RecursiveMatcher {
});
}
- @override
- Description describe(Description description) =>
- description.add(this.description);
-
/**
* Check the type of a field called [key], having value [value], using
* [valueMatcher]. If it doesn't match, record a closure in [mismatches]
@@ -414,212 +391,6 @@ class MatchesJsonObject extends _RecursiveMatcher {
}
/**
- * Matcher that matches a list of objects, each of which satisfies the given
- * matcher.
- */
-class _ListOf extends Matcher {
- /**
- * Matcher which every element of the list must satisfy.
- */
- final Matcher elementMatcher;
-
- /**
- * Iterable matcher which we use to test the contents of the list.
- */
- final Matcher iterableMatcher;
-
- _ListOf(elementMatcher)
- : elementMatcher = elementMatcher,
- iterableMatcher = everyElement(elementMatcher);
-
- @override
- bool matches(item, Map matchState) {
- if (item is! List) {
- return false;
- }
- return iterableMatcher.matches(item, matchState);
- }
-
- @override
- Description describe(Description description) =>
- description.add('List of ').addDescriptionOf(elementMatcher);
-
- @override
- Description describeMismatch(item, Description mismatchDescription,
- Map matchState, bool verbose) {
- if (item is! List) {
- return super.describeMismatch(
- item,
- mismatchDescription,
- matchState,
- verbose);
- } else {
- return iterableMatcher.describeMismatch(
- item,
- mismatchDescription,
- matchState,
- verbose);
- }
- }
-}
-
-Matcher isListOf(Matcher elementMatcher) => new _ListOf(elementMatcher);
-
-/**
- * Type of closures used by LazyMatcher.
- */
-typedef Matcher MatcherCreator();
-
-/**
- * Wrapper class for Matcher which doesn't create the underlying Matcher object
- * until it is needed. This is necessary in order to create matchers that can
- * refer to themselves (so that recursive data structures can be represented).
- */
-class LazyMatcher implements Matcher {
- /**
- * Callback that will be used to create the matcher the first time it is
- * needed.
- */
- final MatcherCreator _creator;
-
- /**
- * The matcher returned by [_creator], if it has already been called.
- * Otherwise null.
- */
- Matcher _wrappedMatcher;
-
- LazyMatcher(this._creator);
-
- @override
- Description describe(Description description) {
- _createMatcher();
- return _wrappedMatcher.describe(description);
- }
-
- @override
- Description describeMismatch(item, Description mismatchDescription,
- Map matchState, bool verbose) {
- _createMatcher();
- return _wrappedMatcher.describeMismatch(
- item,
- mismatchDescription,
- matchState,
- verbose);
- }
-
- @override
- bool matches(item, Map matchState) {
- _createMatcher();
- return _wrappedMatcher.matches(item, matchState);
- }
-
- /**
- * Create the wrapped matcher object, if it hasn't been created already.
- */
- void _createMatcher() {
- if (_wrappedMatcher == null) {
- _wrappedMatcher = _creator();
- }
- }
-}
-
-/**
- * Matcher that matches a union of different types, each of which is described
- * by a matcher.
- */
-class _OneOf extends Matcher {
- /**
- * Matchers for the individual choices.
- */
- final List<Matcher> choiceMatchers;
-
- _OneOf(this.choiceMatchers);
-
- @override
- bool matches(item, Map matchState) {
- for (Matcher choiceMatcher in choiceMatchers) {
- Map subState = {};
- if (choiceMatcher.matches(item, subState)) {
- return true;
- }
- }
- return false;
- }
-
- @override
- Description describe(Description description) {
- for (int i = 0; i < choiceMatchers.length; i++) {
- if (i != 0) {
- if (choiceMatchers.length == 2) {
- description = description.add(' or ');
- } else {
- description = description.add(', ');
- if (i == choiceMatchers.length - 1) {
- description = description.add('or ');
- }
- }
- }
- description = description.addDescriptionOf(choiceMatchers[i]);
- }
- return description;
- }
-}
-
-Matcher isOneOf(List<Matcher> choiceMatchers) => new _OneOf(choiceMatchers);
-
-/**
- * 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);
-
-/**
- * Type of callbacks used to process notifications.
- */
-typedef void NotificationProcessor(String event, params);
-
-/**
* Instances of the class [Server] manage a connection to a server process, and
* facilitate communication to and from the server.
*/
@@ -666,57 +437,55 @@ class Server {
Stopwatch _time = new Stopwatch();
/**
- * Find the root directory of the analysis_server package by proceeding
- * upward to the 'test' dir, and then going up one more directory.
+ * Future that completes when the server process exits.
*/
- String findRoot(String pathname) {
- while (basename(pathname) != 'test') {
- String parent = dirname(pathname);
- if (parent.length >= pathname.length) {
- throw new Exception("Can't find root directory");
- }
+ Future<int> get exitCode => _process.exitCode;
+
+ /**
+ * Print out any messages exchanged with the server. If some messages have
+ * already been exchanged with the server, they are printed out immediately.
+ */
+ void debugStdio() {
+ if (_debuggingStdio) {
+ return;
+ }
+ _debuggingStdio = true;
+ for (String line in _recordedStdio) {
+ print(line);
+ }
+ }
+
+ /**
+ * Find the root directory of the analysis_server package by proceeding
+ * upward to the 'test' dir, and then going up one more directory.
+ */
+ String findRoot(String pathname) {
+ while (basename(pathname) != 'test') {
+ String parent = dirname(pathname);
+ if (parent.length >= pathname.length) {
+ throw new Exception("Can't find root directory");
+ }
pathname = parent;
}
return dirname(pathname);
}
/**
- * Start the server. If [debugServer] is `true`, the server will be started
- * with "--debug", allowing a debugger to be attached. If [profileServer] is
- * `true`, the server will be started with "--observe" and
- * "--pause-isolates-on-exit", allowing the observatory to be used.
+ * Return a future that will complete when all commands that have been sent
+ * to the server so far have been flushed to the OS buffer.
*/
- Future start({bool debugServer: false, bool profileServer: false}) {
- if (_process != null) {
- throw new Exception('Process already started');
- }
- _time.start();
- String dartBinary = Platform.executable;
- String rootDir =
- findRoot(Platform.script.toFilePath(windows: Platform.isWindows));
- String serverPath = normalize(join(rootDir, 'bin', 'server.dart'));
- List<String> arguments = [];
- if (debugServer) {
- arguments.add('--debug');
- }
- if (profileServer) {
- arguments.add('--observe');
- arguments.add('--pause-isolates-on-exit');
- }
- if (Platform.packageRoot.isNotEmpty) {
- arguments.add('--package-root=${Platform.packageRoot}');
- }
- arguments.add('--checked');
- arguments.add(serverPath);
- return Process.start(dartBinary, arguments).then((Process process) {
- _process = process;
- process.exitCode.then((int code) {
- _recordStdio('TERMINATED WITH EXIT CODE $code');
- if (code != 0) {
- _badDataFromServer();
- }
- });
- });
+ Future flushCommands() {
+ return _process.stdin.flush();
+ }
+
+ /**
+ * Stop the server.
+ */
+ Future kill() {
+ debugStdio();
+ _recordStdio('PROCESS FORCIBLY TERMINATED');
+ _process.kill();
+ return _process.exitCode;
}
/**
@@ -779,21 +548,6 @@ class Server {
}
/**
- * Future that completes when the server process exits.
- */
- Future<int> get exitCode => _process.exitCode;
-
- /**
- * Stop the server.
- */
- Future kill() {
- debugStdio();
- _recordStdio('PROCESS FORCIBLY TERMINATED');
- _process.kill();
- return _process.exitCode;
- }
-
- /**
* Send a command to the server. An 'id' will be automatically assigned.
* The returned [Future] will be completed when the server acknowledges the
* command with a response. If the server acknowledges the command with a
@@ -819,25 +573,42 @@ class Server {
}
/**
- * Print out any messages exchanged with the server. If some messages have
- * already been exchanged with the server, they are printed out immediately.
+ * Start the server. If [debugServer] is `true`, the server will be started
+ * with "--debug", allowing a debugger to be attached. If [profileServer] is
+ * `true`, the server will be started with "--observe" and
+ * "--pause-isolates-on-exit", allowing the observatory to be used.
*/
- void debugStdio() {
- if (_debuggingStdio) {
- return;
+ Future start({bool debugServer: false, bool profileServer: false}) {
+ if (_process != null) {
+ throw new Exception('Process already started');
}
- _debuggingStdio = true;
- for (String line in _recordedStdio) {
- print(line);
+ _time.start();
+ String dartBinary = Platform.executable;
+ String rootDir =
+ findRoot(Platform.script.toFilePath(windows: Platform.isWindows));
+ String serverPath = normalize(join(rootDir, 'bin', 'server.dart'));
+ List<String> arguments = [];
+ if (debugServer) {
+ arguments.add('--debug');
}
- }
-
- /**
- * Return a future that will complete when all commands that have been sent
- * to the server so far have been flushed to the OS buffer.
- */
- Future flushCommands() {
- return _process.stdin.flush();
+ if (profileServer) {
+ arguments.add('--observe');
+ arguments.add('--pause-isolates-on-exit');
+ }
+ if (Platform.packageRoot.isNotEmpty) {
+ arguments.add('--package-root=${Platform.packageRoot}');
+ }
+ arguments.add('--checked');
+ arguments.add(serverPath);
+ return Process.start(dartBinary, arguments).then((Process process) {
+ _process = process;
+ process.exitCode.then((int code) {
+ _recordStdio('TERMINATED WITH EXIT CODE $code');
+ if (code != 0) {
+ _badDataFromServer();
+ }
+ });
+ });
}
/**
@@ -873,3 +644,232 @@ class Server {
_recordedStdio.add(line);
}
}
+
+/**
+ * Matcher that matches a list of objects, each of which satisfies the given
+ * matcher.
+ */
+class _ListOf extends Matcher {
+ /**
+ * Matcher which every element of the list must satisfy.
+ */
+ final Matcher elementMatcher;
+
+ /**
+ * Iterable matcher which we use to test the contents of the list.
+ */
+ final Matcher iterableMatcher;
+
+ _ListOf(elementMatcher)
+ : elementMatcher = elementMatcher,
+ iterableMatcher = everyElement(elementMatcher);
+
+ @override
+ Description describe(Description description) =>
+ description.add('List of ').addDescriptionOf(elementMatcher);
+
+ @override
+ Description describeMismatch(item, Description mismatchDescription,
+ Map matchState, bool verbose) {
+ if (item is! List) {
+ return super.describeMismatch(
+ item,
+ mismatchDescription,
+ matchState,
+ verbose);
+ } else {
+ return iterableMatcher.describeMismatch(
+ item,
+ mismatchDescription,
+ matchState,
+ verbose);
+ }
+ }
+
+ @override
+ bool matches(item, Map matchState) {
+ if (item is! List) {
+ return false;
+ }
+ return iterableMatcher.matches(item, matchState);
+ }
+}
+
+/**
+ * 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
+ Description describe(Description description) =>
+ description.add(
+ 'Map from ').addDescriptionOf(
+ keyMatcher).add(' to ').addDescriptionOf(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));
+ });
+ }
+}
+
+/**
+ * Matcher that matches a union of different types, each of which is described
+ * by a matcher.
+ */
+class _OneOf extends Matcher {
+ /**
+ * Matchers for the individual choices.
+ */
+ final List<Matcher> choiceMatchers;
+
+ _OneOf(this.choiceMatchers);
+
+ @override
+ Description describe(Description description) {
+ for (int i = 0; i < choiceMatchers.length; i++) {
+ if (i != 0) {
+ if (choiceMatchers.length == 2) {
+ description = description.add(' or ');
+ } else {
+ description = description.add(', ');
+ if (i == choiceMatchers.length - 1) {
+ description = description.add('or ');
+ }
+ }
+ }
+ description = description.addDescriptionOf(choiceMatchers[i]);
+ }
+ return description;
+ }
+
+ @override
+ bool matches(item, Map matchState) {
+ for (Matcher choiceMatcher in choiceMatchers) {
+ Map subState = {};
+ if (choiceMatcher.matches(item, subState)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+
+/**
+ * Base class for matchers that operate by recursing through the contents of
+ * an object.
+ */
+abstract class _RecursiveMatcher extends Matcher {
+ const _RecursiveMatcher();
+
+ /**
+ * 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(')');
+ });
+ }
+ }
+
+ @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);
+ }
+ }
+
+ @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;
+ }
+ }
+
+ /**
+ * 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);
+ };
+}

Powered by Google App Engine
This is Rietveld 408576698