| 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);
|
| + };
|
| +}
|
|
|