blob: 6a26f974ec5a772088a2faae9326dca82421978c [file] [log] [blame]
// 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<String, List<resource.Folder>> packageMap =
<String, List<resource.Folder>>{};
/**
* Package maps that will be returned by the next call to [computePackageMap].
*/
Map<String, Map<String, List<resource.Folder>>> packageMaps = null;
/**
* Dependency list that will be returned by the next call to [computePackageMap].
*/
Set<String> dependencies = new Set<String>();
/**
* 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<Request> requestController = new StreamController<Request>();
StreamController<Response> responseController =
new StreamController<Response>.broadcast();
StreamController<Notification> notificationController =
new StreamController<Notification>(sync: true);
List<Response> responsesReceived = [];
List<Notification> 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<Response> 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<Response> waitForResponse(Request request) {
String id = request.id;
return responseController.stream
.firstWhere((response) => response.id == id);
}
}
/**
* A mock [WebSocket] for testing.
*/
class MockSocket<T> implements WebSocket {
StreamController<T> controller = new StreamController<T>();
MockSocket<T> twin;
Stream<T> stream;
MockSocket();
factory MockSocket.pair() {
MockSocket<T> socket1 = new MockSocket<T>();
MockSocket<T> socket2 = new MockSocket<T>();
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<T> 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<T> where(bool test(dynamic t)) => stream.where((T data) => test(data));
}
class MockSource extends StringTypedMock implements Source {
@override
TimestampedData<String> 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;
}
}