// 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. import 'dart:async'; import 'dart:io'; import 'package:analysis_server/protocol/protocol.dart'; import 'package:analysis_server/protocol/protocol_generated.dart'; import 'package:analysis_server/src/analysis_server.dart'; import 'package:analysis_server/src/channel/channel.dart'; import 'package:analyzer/file_system/file_system.dart' as resource; import 'package:analyzer/file_system/memory_file_system.dart' as resource; import 'package:analyzer/src/generated/source.dart'; import 'package:analyzer/src/source/package_map_provider.dart'; import 'package:analyzer/src/source/pub_package_map_provider.dart'; import 'package:front_end/src/base/timestamped_data.dart'; import 'package:test/test.dart'; /** * Answer the absolute path the SDK relative to the currently running * script or throw an exception if it cannot be found. */ String get sdkPath { Uri sdkUri = Platform.script.resolve('../../../sdk/'); // Verify the directory exists Directory sdkDir = new Directory.fromUri(sdkUri); if (!sdkDir.existsSync()) { throw 'Specified Dart SDK does not exist: $sdkDir'; } return sdkDir.path; } /** * A [Matcher] that check that the given [Response] has an expected identifier * and has an error. The error code may optionally be checked. */ Matcher isResponseFailure(String id, [RequestErrorCode code]) => new _IsResponseFailure(id, code); /** * A [Matcher] that check that the given [Response] has an expected identifier * and no error. */ Matcher isResponseSuccess(String id) => new _IsResponseSuccess(id); /** * A mock [PackageMapProvider]. */ class MockPackageMapProvider implements PubPackageMapProvider { /** * Package map that will be returned by the next call to [computePackageMap]. */ Map> packageMap = >{}; /** * Package maps that will be returned by the next call to [computePackageMap]. */ Map>> packageMaps = null; /** * Dependency list that will be returned by the next call to [computePackageMap]. */ Set dependencies = new Set(); /** * Number of times [computePackageMap] has been called. */ int computeCount = 0; @override PackageMapInfo computePackageMap(resource.Folder folder) { ++computeCount; if (packageMaps != null) { return new PackageMapInfo(packageMaps[folder.path], dependencies); } return new PackageMapInfo(packageMap, dependencies); } noSuchMethod(Invocation invocation) { // No other methods should be called. return super.noSuchMethod(invocation); } } /** * A mock [ServerCommunicationChannel] for testing [AnalysisServer]. */ class MockServerChannel implements ServerCommunicationChannel { StreamController requestController = new StreamController(); StreamController responseController = new StreamController.broadcast(); StreamController notificationController = new StreamController(sync: true); List responsesReceived = []; List notificationsReceived = []; bool _closed = false; MockServerChannel(); @override void close() { _closed = true; } void expectMsgCount({responseCount: 0, notificationCount: 0}) { expect(responsesReceived, hasLength(responseCount)); expect(notificationsReceived, hasLength(notificationCount)); } @override void listen(void onRequest(Request request), {Function onError, void onDone()}) { requestController.stream .listen(onRequest, onError: onError, onDone: onDone); } @override void sendNotification(Notification notification) { // Don't deliver notifications after the connection is closed. if (_closed) { return; } notificationsReceived.add(notification); // Wrap send notification in future to simulate websocket // TODO(scheglov) ask Dan why and decide what to do // new Future(() => notificationController.add(notification)); notificationController.add(notification); } /** * Simulate request/response pair. */ Future sendRequest(Request request) { // No further requests should be sent after the connection is closed. if (_closed) { throw new Exception('sendRequest after connection closed'); } // Wrap send request in future to simulate WebSocket. new Future(() => requestController.add(request)); return waitForResponse(request); } @override void sendResponse(Response response) { // Don't deliver responses after the connection is closed. if (_closed) { return; } responsesReceived.add(response); // Wrap send response in future to simulate WebSocket. new Future(() => responseController.add(response)); } Future waitForResponse(Request request) { String id = request.id; return responseController.stream .firstWhere((response) => response.id == id); } } /** * A mock [WebSocket] for testing. */ class MockSocket implements WebSocket { StreamController controller = new StreamController(); MockSocket twin; Stream stream; MockSocket(); factory MockSocket.pair() { MockSocket socket1 = new MockSocket(); MockSocket socket2 = new MockSocket(); socket1.twin = socket2; socket2.twin = socket1; socket1.stream = socket2.controller.stream; socket2.stream = socket1.controller.stream; return socket1; } void add(dynamic text) => controller.add(text as T); void allowMultipleListeners() { stream = stream.asBroadcastStream(); } Future close([int code, String reason]) => controller.close().then((_) => twin.controller.close()); StreamSubscription listen(void onData(dynamic event), {Function onError, void onDone(), bool cancelOnError}) => stream.listen((T data) => onData(data), onError: onError, onDone: onDone, cancelOnError: cancelOnError); noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); Stream where(bool test(dynamic t)) => stream.where((T data) => test(data)); } class MockSource extends StringTypedMock implements Source { @override TimestampedData contents = null; @override String encoding = null; @override String fullName = null; @override bool isInSystemLibrary = null; @override Source librarySource = null; @override int modificationStamp = null; @override String shortName = null; @override Source source = null; @override Uri uri = null; @override UriKind uriKind = null; MockSource([String name = 'mocked.dart']) : super(name); @override bool exists() => null; } class StringTypedMock { String _toString; StringTypedMock(this._toString); @override String toString() { if (_toString != null) { return _toString; } return super.toString(); } } /** * A [Matcher] that check that there are no `error` in a given [Response]. */ class _IsResponseFailure extends Matcher { final String _id; final RequestErrorCode _code; _IsResponseFailure(this._id, this._code); @override Description describe(Description description) { description = description.add('response with identifier "$_id" and an error'); if (_code != null) { description = description.add(' with code ${this._code.name}'); } return description; } @override Description describeMismatch( item, Description mismatchDescription, Map matchState, bool verbose) { Response response = item; var id = response.id; RequestError error = response.error; mismatchDescription.add('has identifier "$id"'); if (error == null) { mismatchDescription.add(' and has no error'); } else { mismatchDescription .add(' and has error code ${response.error.code.name}'); } return mismatchDescription; } @override bool matches(item, Map matchState) { Response response = item; if (response.id != _id || response.error == null) { return false; } if (_code != null && response.error.code != _code) { return false; } return true; } } /** * A [Matcher] that check that there are no `error` in a given [Response]. */ class _IsResponseSuccess extends Matcher { final String _id; _IsResponseSuccess(this._id); @override Description describe(Description description) { return description .addDescriptionOf('response with identifier "$_id" and without error'); } @override Description describeMismatch( item, Description mismatchDescription, Map matchState, bool verbose) { Response response = item; if (response == null) { mismatchDescription.add('is null response'); } else { var id = response.id; RequestError error = response.error; mismatchDescription.add('has identifier "$id"'); if (error != null) { mismatchDescription.add(' and has error $error'); } } return mismatchDescription; } @override bool matches(item, Map matchState) { Response response = item; return response != null && response.id == _id && response.error == null; } }