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 |
deleted file mode 100644 |
index 2417cb12ae49017468c11cdd91b3b5b70f159938..0000000000000000000000000000000000000000 |
--- a/pkg/analysis_server/test/integration/integration_tests.dart |
+++ /dev/null |
@@ -1,988 +0,0 @@ |
-// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-library test.integration.analysis; |
- |
-import 'dart:async'; |
-import 'dart:collection'; |
-import 'dart:convert'; |
-import 'dart:io'; |
- |
-import 'package:analysis_server/plugin/protocol/protocol.dart'; |
-import 'package:analysis_server/src/constants.dart'; |
-import 'package:path/path.dart'; |
-import 'package:test/test.dart'; |
- |
-import 'integration_test_methods.dart'; |
-import 'protocol_matchers.dart'; |
- |
-const Matcher isBool = const isInstanceOf<bool>(); |
- |
-const Matcher isInt = const isInstanceOf<int>(); |
- |
-const Matcher isNotification = const MatchesJsonObject( |
- 'notification', const {'event': isString}, |
- optionalFields: const {'params': isMap}); |
- |
-const Matcher isObject = isMap; |
- |
-const Matcher isString = const isInstanceOf<String>(); |
- |
-final Matcher isResponse = new MatchesJsonObject('response', {'id': isString}, |
- optionalFields: {'result': anything, 'error': isRequestError}); |
- |
-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); |
- |
-/** |
- * Assert that [actual] matches [matcher]. |
- */ |
-void outOfTestExpect(actual, matcher, |
- {String reason, skip, bool verbose: false}) { |
- var matchState = {}; |
- try { |
- if (matcher.matches(actual, matchState)) return; |
- } catch (e, trace) { |
- if (reason == null) { |
- reason = '${(e is String) ? e : e.toString()} at $trace'; |
- } |
- } |
- fail(_defaultFailFormatter(actual, matcher, reason, matchState, verbose)); |
-} |
- |
-String _defaultFailFormatter( |
- actual, Matcher matcher, String reason, Map matchState, bool verbose) { |
- var description = new StringDescription(); |
- description.add('Expected: ').addDescriptionOf(matcher).add('\n'); |
- description.add(' Actual: ').addDescriptionOf(actual).add('\n'); |
- |
- var mismatchDescription = new StringDescription(); |
- matcher.describeMismatch(actual, mismatchDescription, matchState, verbose); |
- |
- if (mismatchDescription.length > 0) { |
- description.add(' Which: $mismatchDescription\n'); |
- } |
- if (reason != null) description.add(reason).add('\n'); |
- return description.toString(); |
-} |
- |
-/** |
- * 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. |
- */ |
-abstract class AbstractAnalysisServerIntegrationTest |
- extends IntegrationTestMixin { |
- /** |
- * Amount of time to give the server to respond to a shutdown request before |
- * forcibly terminating it. |
- */ |
- static const Duration SHUTDOWN_TIMEOUT = const Duration(seconds: 5); |
- |
- /** |
- * Connection to the analysis server. |
- */ |
- final Server server = new Server(); |
- |
- /** |
- * Temporary directory in which source files can be stored. |
- */ |
- Directory sourceDirectory; |
- |
- /** |
- * Map from file path to the list of analysis errors which have most recently |
- * been received for the file. |
- */ |
- HashMap<String, List<AnalysisError>> currentAnalysisErrors = |
- new HashMap<String, List<AnalysisError>>(); |
- |
- /** |
- * The last list of analyzed files received. |
- */ |
- List<String> lastAnalyzedFiles; |
- |
- /** |
- * True if the teardown process should skip sending a "server.shutdown" |
- * request (e.g. because the server is known to have already shutdown). |
- */ |
- bool skipShutdown = false; |
- |
- /** |
- * True if we are currently subscribed to [SERVER_STATUS] updates. |
- */ |
- bool _subscribedToServerStatus = false; |
- |
- AbstractAnalysisServerIntegrationTest() { |
- initializeInttestMixin(); |
- } |
- |
- /** |
- * Return a future which will complete when a 'server.status' notification is |
- * received from the server with 'analyzing' set to false. |
- * |
- * The future will only be completed by 'server.status' notifications that are |
- * received after this function call. So it is safe to use this getter |
- * multiple times in one test; each time it is used it will wait afresh for |
- * analysis to finish. |
- */ |
- Future<ServerStatusParams> get analysisFinished { |
- Completer completer = new Completer(); |
- StreamSubscription subscription; |
- // This will only work if the caller has already subscribed to |
- // SERVER_STATUS (e.g. using sendServerSetSubscriptions(['STATUS'])) |
- outOfTestExpect(_subscribedToServerStatus, isTrue); |
- subscription = onServerStatus.listen((ServerStatusParams params) { |
- if (params.analysis != null && !params.analysis.isAnalyzing) { |
- completer.complete(params); |
- subscription.cancel(); |
- } |
- }); |
- return completer.future; |
- } |
- |
- /** |
- * 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() { |
- server.debugStdio(); |
- } |
- |
- List<AnalysisError> getErrors(String pathname) => |
- currentAnalysisErrors[pathname]; |
- |
- /** |
- * Read a source file with the given absolute [pathname]. |
- */ |
- String readFile(String pathname) => new File(pathname).readAsStringSync(); |
- |
- @override |
- Future sendServerSetSubscriptions(List<ServerService> subscriptions) { |
- _subscribedToServerStatus = subscriptions.contains(ServerService.STATUS); |
- return super.sendServerSetSubscriptions(subscriptions); |
- } |
- |
- /** |
- * The server is automatically started before every test, and a temporary |
- * [sourceDirectory] is created. |
- */ |
- Future setUp() async { |
- sourceDirectory = new Directory(Directory.systemTemp |
- .createTempSync('analysisServer') |
- .resolveSymbolicLinksSync()); |
- |
- onAnalysisErrors.listen((AnalysisErrorsParams params) { |
- currentAnalysisErrors[params.file] = params.errors; |
- }); |
- onAnalysisAnalyzedFiles.listen((AnalysisAnalyzedFilesParams params) { |
- lastAnalyzedFiles = params.directories; |
- }); |
- Completer serverConnected = new Completer(); |
- onServerConnected.listen((_) { |
- outOfTestExpect(serverConnected.isCompleted, isFalse); |
- serverConnected.complete(); |
- }); |
- onServerError.listen((ServerErrorParams params) { |
- // A server error should never happen during an integration test. |
- fail('${params.message}\n${params.stackTrace}'); |
- }); |
- await startServer(); |
- server.listenToOutput(dispatchNotification); |
- server.exitCode.then((_) { |
- skipShutdown = true; |
- }); |
- return serverConnected.future; |
- } |
- |
- /** |
- * If [skipShutdown] is not set, shut down the server. |
- */ |
- Future shutdownIfNeeded() { |
- if (skipShutdown) { |
- return new Future.value(); |
- } |
- // Give the server a short time to comply with the shutdown request; if it |
- // doesn't exit, then forcibly terminate it. |
- sendServerShutdown(); |
- return server.exitCode.timeout(SHUTDOWN_TIMEOUT, onTimeout: () { |
- // The integer value of the exit code isn't used, but we have to return |
- // an integer to keep the typing correct. |
- return server.kill('server failed to exit').then((_) => -1); |
- }); |
- } |
- |
- /** |
- * 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); |
- } |
- |
- /** |
- * Start [server]. |
- */ |
- Future startServer( |
- {bool checked: true, int diagnosticPort, int servicesPort}) => |
- server.start( |
- checked: checked, |
- diagnosticPort: diagnosticPort, |
- servicesPort: servicesPort); |
- |
- /** |
- * After every test, the server is stopped and [sourceDirectory] is deleted. |
- */ |
- Future tearDown() { |
- return shutdownIfNeeded().then((_) { |
- sourceDirectory.deleteSync(recursive: true); |
- }); |
- } |
- |
- /** |
- * 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(); |
- } |
-} |
- |
-/** |
- * 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 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. |
- */ |
- 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); |
- } |
-} |
- |
-/** |
- * 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 _RecursiveMatcher { |
- /** |
- * Short description of the expected type. |
- */ |
- final String description; |
- |
- /** |
- * Fields that are required to be in the JSON object, and [Matcher]s describing |
- * their expected types. |
- */ |
- final Map<String, Matcher> requiredFields; |
- |
- /** |
- * Fields that are optional in the JSON object, and [Matcher]s describing |
- * their expected types. |
- */ |
- final Map<String, Matcher> optionalFields; |
- |
- const MatchesJsonObject(this.description, this.requiredFields, |
- {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')); |
- return; |
- } |
- if (requiredFields != null) { |
- requiredFields.forEach((String key, Matcher valueMatcher) { |
- if (!item.containsKey(key)) { |
- mismatches.add((Description mismatchDescription) => |
- mismatchDescription |
- .add('is missing field ') |
- .addDescriptionOf(key) |
- .add(' (') |
- .addDescriptionOf(valueMatcher) |
- .add(')')); |
- } else { |
- _checkField(key, item[key], valueMatcher, mismatches); |
- } |
- }); |
- } |
- item.forEach((key, value) { |
- if (requiredFields != null && requiredFields.containsKey(key)) { |
- // Already checked this field |
- } else if (optionalFields != null && optionalFields.containsKey(key)) { |
- _checkField(key, value, optionalFields[key], mismatches); |
- } else { |
- mismatches.add((Description mismatchDescription) => mismatchDescription |
- .add('has unexpected field ') |
- .addDescriptionOf(key)); |
- } |
- }); |
- } |
- |
- /** |
- * Check the type of a field called [key], having value [value], using |
- * [valueMatcher]. If it doesn't match, record a closure in [mismatches] |
- * which can describe the mismatch. |
- */ |
- void _checkField(String key, value, Matcher valueMatcher, |
- List<MismatchDescriber> mismatches) { |
- checkSubstructure( |
- value, |
- valueMatcher, |
- mismatches, |
- (Description description) => |
- description.add('field ').addDescriptionOf(key)); |
- } |
-} |
- |
-/** |
- * Instances of the class [Server] manage a connection to a server process, and |
- * facilitate communication to and from the server. |
- */ |
-class Server { |
- /** |
- * Server process object, or null if server hasn't been started yet. |
- */ |
- Process _process; |
- |
- /** |
- * Commands that have been sent to the server but not yet acknowledged, and |
- * the [Completer] objects which should be completed when acknowledgement is |
- * received. |
- */ |
- final Map<String, Completer> _pendingCommands = <String, Completer>{}; |
- |
- /** |
- * Number which should be used to compute the 'id' to send in the next command |
- * sent to the server. |
- */ |
- int _nextId = 0; |
- |
- /** |
- * Messages which have been exchanged with the server; we buffer these |
- * up until the test finishes, so that they can be examined in the debugger |
- * or printed out in response to a call to [debugStdio]. |
- */ |
- final List<String> _recordedStdio = <String>[]; |
- |
- /** |
- * True if we are currently printing out messages exchanged with the server. |
- */ |
- bool _debuggingStdio = false; |
- |
- /** |
- * True if we've received bad data from the server, and we are aborting the |
- * test. |
- */ |
- bool _receivedBadDataFromServer = false; |
- |
- /** |
- * Stopwatch that we use to generate timing information for debug output. |
- */ |
- Stopwatch _time = new Stopwatch(); |
- |
- /** |
- * The [currentElapseTime] at which the last communication was received from the server |
- * or `null` if no communication has been received. |
- */ |
- double lastCommunicationTime; |
- |
- /** |
- * The current elapse time (seconds) since the server was started. |
- */ |
- double get currentElapseTime => _time.elapsedTicks / _time.frequency; |
- |
- /** |
- * Future that completes when the server process exits. |
- */ |
- 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 (!['benchmark', 'test'].contains(basename(pathname))) { |
- String parent = dirname(pathname); |
- if (parent.length >= pathname.length) { |
- throw new Exception("Can't find root directory"); |
- } |
- pathname = parent; |
- } |
- return dirname(pathname); |
- } |
- |
- /** |
- * 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(); |
- } |
- |
- /** |
- * Stop the server. |
- */ |
- Future<int> kill(String reason) { |
- debugStdio(); |
- _recordStdio('FORCIBLY TERMINATING PROCESS: $reason'); |
- _process.kill(); |
- return _process.exitCode; |
- } |
- |
- /** |
- * Start listening to output from the server, and deliver notifications to |
- * [notificationProcessor]. |
- */ |
- void listenToOutput(NotificationProcessor notificationProcessor) { |
- _process.stdout |
- .transform((new Utf8Codec()).decoder) |
- .transform(new LineSplitter()) |
- .listen((String line) { |
- lastCommunicationTime = currentElapseTime; |
- String trimmedLine = line.trim(); |
- if (trimmedLine.startsWith('Observatory listening on ')) { |
- return; |
- } |
- _recordStdio('RECV: $trimmedLine'); |
- var message; |
- try { |
- message = JSON.decoder.convert(trimmedLine); |
- } catch (exception) { |
- _badDataFromServer('JSON decode failure: $exception'); |
- return; |
- } |
- outOfTestExpect(message, isMap); |
- Map messageAsMap = message; |
- if (messageAsMap.containsKey('id')) { |
- outOfTestExpect(messageAsMap['id'], isString); |
- String id = message['id']; |
- Completer completer = _pendingCommands[id]; |
- if (completer == null) { |
- fail('Unexpected response from server: id=$id'); |
- } else { |
- _pendingCommands.remove(id); |
- } |
- if (messageAsMap.containsKey('error')) { |
- completer.completeError(new ServerErrorMessage(messageAsMap)); |
- } else { |
- completer.complete(messageAsMap['result']); |
- } |
- // Check that the message is well-formed. We do this after calling |
- // completer.complete() or completer.completeError() so that we don't |
- // stall the test in the event of an error. |
- outOfTestExpect(message, isResponse); |
- } else { |
- // Message is a notification. It should have an event and possibly |
- // params. |
- outOfTestExpect(messageAsMap, contains('event')); |
- outOfTestExpect(messageAsMap['event'], isString); |
- notificationProcessor(messageAsMap['event'], messageAsMap['params']); |
- // Check that the message is well-formed. We do this after calling |
- // notificationController.add() so that we don't stall the test in the |
- // event of an error. |
- outOfTestExpect(message, isNotification); |
- } |
- }); |
- _process.stderr |
- .transform((new Utf8Codec()).decoder) |
- .transform(new LineSplitter()) |
- .listen((String line) { |
- String trimmedLine = line.trim(); |
- _recordStdio('ERR: $trimmedLine'); |
- _badDataFromServer('Message received on stderr', silent: true); |
- }); |
- } |
- |
- /** |
- * 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 |
- * normal (non-error) response, the future will be completed with the 'result' |
- * field from the response. If the server acknowledges the command with an |
- * error response, the future will be completed with an error. |
- */ |
- Future send(String method, Map<String, dynamic> params) { |
- String id = '${_nextId++}'; |
- Map<String, dynamic> command = <String, dynamic>{ |
- 'id': id, |
- 'method': method |
- }; |
- if (params != null) { |
- command['params'] = params; |
- } |
- Completer completer = new Completer(); |
- _pendingCommands[id] = completer; |
- String line = JSON.encode(command); |
- _recordStdio('SEND: $line'); |
- _process.stdin.add(UTF8.encoder.convert("$line\n")); |
- return completer.future; |
- } |
- |
- /** |
- * Start the server. If [profileServer] is `true`, the server will be started |
- * with "--observe" and "--pause-isolates-on-exit", allowing the observatory |
- * to be used. |
- */ |
- Future start({ |
- bool checked: true, |
- int diagnosticPort, |
- bool profileServer: false, |
- String sdkPath, |
- int servicesPort, |
- bool useAnalysisHighlight2: false, |
- }) async { |
- 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 = []; |
- // |
- // Add VM arguments. |
- // |
- if (profileServer) { |
- if (servicesPort == null) { |
- arguments.add('--observe'); |
- } else { |
- arguments.add('--observe=$servicesPort'); |
- } |
- arguments.add('--pause-isolates-on-exit'); |
- } else if (servicesPort != null) { |
- arguments.add('--enable-vm-service=$servicesPort'); |
- } |
- if (Platform.packageRoot != null) { |
- arguments.add('--package-root=${Platform.packageRoot}'); |
- } |
- if (Platform.packageConfig != null) { |
- arguments.add('--packages=${Platform.packageConfig}'); |
- } |
- if (checked) { |
- arguments.add('--checked'); |
- } |
- // |
- // Add the server executable. |
- // |
- arguments.add(serverPath); |
- // |
- // Add server arguments. |
- // |
- if (diagnosticPort != null) { |
- arguments.add('--port'); |
- arguments.add(diagnosticPort.toString()); |
- } |
- if (sdkPath != null) { |
- arguments.add('--sdk=$sdkPath'); |
- } |
- if (useAnalysisHighlight2) { |
- arguments.add('--useAnalysisHighlight2'); |
- } |
-// print('Launching $serverPath'); |
-// print('$dartBinary ${arguments.join(' ')}'); |
- // TODO(devoncarew): We could experiment with instead launching the analysis |
- // server in a separate isolate. This would make it easier to debug the |
- // integration tests, and would likely speed the tests up as well. |
- _process = await Process.start(dartBinary, arguments); |
- _process.exitCode.then((int code) { |
- if (code != 0) { |
- _badDataFromServer('server terminated with exit code $code'); |
- } |
- }); |
- } |
- |
- /** |
- * Deal with bad data received from the server. |
- */ |
- void _badDataFromServer(String details, {bool silent: false}) { |
- if (!silent) { |
- _recordStdio('BAD DATA FROM SERVER: $details'); |
- } |
- if (_receivedBadDataFromServer) { |
- // We're already dealing with it. |
- return; |
- } |
- _receivedBadDataFromServer = true; |
- debugStdio(); |
- // Give the server 1 second to continue outputting bad data before we kill |
- // the test. This is helpful if the server has had an unhandled exception |
- // and is outputting a stacktrace, because it ensures that we see the |
- // entire stacktrace. Use expectAsync() to prevent the test from |
- // ending during this 1 second. |
- new Future.delayed(new Duration(seconds: 1), expectAsync0(() { |
- fail('Bad data received from server: $details'); |
- })); |
- } |
- |
- /** |
- * Record a message that was exchanged with the server, and print it out if |
- * [debugStdio] has been called. |
- */ |
- void _recordStdio(String line) { |
- double elapsedTime = currentElapseTime; |
- line = "$elapsedTime: $line"; |
- if (_debuggingStdio) { |
- print(line); |
- } |
- _recordedStdio.add(line); |
- } |
-} |
- |
-/** |
- * An error result from a server request. |
- */ |
-class ServerErrorMessage { |
- final Map message; |
- |
- ServerErrorMessage(this.message); |
- |
- dynamic get error => message['error']; |
- |
- String toString() => message.toString(); |
-} |
- |
-/** |
- * 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'] as List<MismatchDescriber>; |
- 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); |
- }; |
-} |