de4d8b3a78
I'm changing DEPS in the corresponding analyzer CL. R=brianwilkerson@google.com, paulberry@google.com BUG= Review URL: https://codereview.chromium.org/2394683006 .
441 lines
13 KiB
Dart
441 lines
13 KiB
Dart
// 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 mocks;
|
|
|
|
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:analysis_server/plugin/protocol/protocol.dart'
|
|
hide Element, ElementKind;
|
|
import 'package:analysis_server/src/analysis_server.dart';
|
|
import 'package:analysis_server/src/channel/channel.dart';
|
|
import 'package:analysis_server/src/operation/operation.dart';
|
|
import 'package:analysis_server/src/operation/operation_analysis.dart';
|
|
import 'package:analyzer/dart/element/element.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/source/package_map_provider.dart';
|
|
import 'package:analyzer/source/pub_package_map_provider.dart';
|
|
import 'package:analyzer/src/generated/engine.dart';
|
|
import 'package:analyzer/src/generated/source.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:typed_mock/typed_mock.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);
|
|
|
|
/**
|
|
* Returns a [Future] that completes after pumping the event queue [times]
|
|
* times. By default, this should pump the event queue enough times to allow
|
|
* any code to run, as long as it's not waiting on some external event.
|
|
*/
|
|
Future pumpEventQueue([int times = 5000]) {
|
|
if (times == 0) return new Future.value();
|
|
// We use a delayed future to allow microtask events to finish. The
|
|
// Future.value or Future() constructors use scheduleMicrotask themselves and
|
|
// would therefore not wait for microtask callbacks that are scheduled after
|
|
// invoking this method.
|
|
return new Future.delayed(Duration.ZERO, () => pumpEventQueue(times - 1));
|
|
}
|
|
|
|
typedef void MockServerOperationPerformFunction(AnalysisServer server);
|
|
|
|
class MockAnalysisContext extends StringTypedMock implements AnalysisContext {
|
|
MockAnalysisContext(String name) : super(name);
|
|
}
|
|
|
|
class MockClassElement extends TypedMock implements ClassElement {
|
|
final ElementKind kind = ElementKind.CLASS;
|
|
}
|
|
|
|
class MockCompilationUnitElement extends TypedMock
|
|
implements CompilationUnitElement {
|
|
final ElementKind kind = ElementKind.COMPILATION_UNIT;
|
|
}
|
|
|
|
class MockConstructorElement extends TypedMock implements ConstructorElement {
|
|
final kind = ElementKind.CONSTRUCTOR;
|
|
}
|
|
|
|
class MockElement extends StringTypedMock implements Element {
|
|
MockElement([String name = '<element>']) : super(name);
|
|
|
|
@override
|
|
String get displayName => _toString;
|
|
|
|
@override
|
|
String get name => _toString;
|
|
}
|
|
|
|
class MockFieldElement extends TypedMock implements FieldElement {
|
|
final ElementKind kind = ElementKind.FIELD;
|
|
}
|
|
|
|
class MockFunctionElement extends TypedMock implements FunctionElement {
|
|
final ElementKind kind = ElementKind.FUNCTION;
|
|
}
|
|
|
|
class MockFunctionTypeAliasElement extends TypedMock
|
|
implements FunctionTypeAliasElement {
|
|
final ElementKind kind = ElementKind.FUNCTION_TYPE_ALIAS;
|
|
}
|
|
|
|
class MockImportElement extends TypedMock implements ImportElement {
|
|
final ElementKind kind = ElementKind.IMPORT;
|
|
}
|
|
|
|
class MockLibraryElement extends TypedMock implements LibraryElement {
|
|
final ElementKind kind = ElementKind.LIBRARY;
|
|
}
|
|
|
|
class MockLocalVariableElement extends TypedMock
|
|
implements LocalVariableElement {
|
|
final ElementKind kind = ElementKind.LOCAL_VARIABLE;
|
|
}
|
|
|
|
class MockLogger extends TypedMock implements Logger {}
|
|
|
|
class MockMethodElement extends StringTypedMock implements MethodElement {
|
|
final kind = ElementKind.METHOD;
|
|
MockMethodElement([String name = 'method']) : super(name);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
class MockParameterElement extends TypedMock implements ParameterElement {
|
|
final ElementKind kind = ElementKind.PARAMETER;
|
|
}
|
|
|
|
class MockPropertyAccessorElement extends TypedMock
|
|
implements PropertyAccessorElement {
|
|
final ElementKind kind;
|
|
MockPropertyAccessorElement(this.kind);
|
|
}
|
|
|
|
/**
|
|
* 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 new Future<Response>(() =>
|
|
responseController.stream.firstWhere((response) => response.id == id));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A mock [ServerOperation] for testing [AnalysisServer].
|
|
*/
|
|
class MockServerOperation implements PerformAnalysisOperation {
|
|
final ServerOperationPriority priority;
|
|
final MockServerOperationPerformFunction _perform;
|
|
|
|
MockServerOperation(this.priority, this._perform);
|
|
|
|
@override
|
|
AnalysisContext get context => null;
|
|
|
|
@override
|
|
bool get isContinue => false;
|
|
|
|
@override
|
|
void perform(AnalysisServer server) => this._perform(server);
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
MockSource([String name = 'mocked.dart']) : super(name);
|
|
}
|
|
|
|
class MockTopLevelVariableElement extends TypedMock
|
|
implements TopLevelVariableElement {
|
|
final ElementKind kind = ElementKind.TOP_LEVEL_VARIABLE;
|
|
}
|
|
|
|
class MockTypeParameterElement extends TypedMock
|
|
implements TypeParameterElement {
|
|
final ElementKind kind = ElementKind.TYPE_PARAMETER;
|
|
}
|
|
|
|
class NoResponseException implements Exception {
|
|
/**
|
|
* The request that was not responded to.
|
|
*/
|
|
final Request request;
|
|
|
|
NoResponseException(this.request);
|
|
|
|
String toString() {
|
|
return "NoResponseException after request ${request.toJson()}";
|
|
}
|
|
}
|
|
|
|
class StringTypedMock extends TypedMock {
|
|
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;
|
|
}
|
|
}
|