blob: e9a1663c536f20d5a79bced0e06f71a6628d0e89 [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.
library test.analysis_server;
import 'dart:async';
import 'package:analysis_server/plugin/protocol/protocol.dart';
import 'package:analysis_server/src/analysis_server.dart';
import 'package:analysis_server/src/constants.dart';
import 'package:analysis_server/src/context_manager.dart';
import 'package:analysis_server/src/domain_server.dart';
import 'package:analysis_server/src/operation/operation.dart';
import 'package:analysis_server/src/plugin/server_plugin.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/instrumentation/instrumentation.dart';
import 'package:analyzer/source/package_map_resolver.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/java_engine.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:plugin/manager.dart';
import 'package:plugin/plugin.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'package:typed_mock/typed_mock.dart';
import 'package:unittest/unittest.dart';
import 'mock_sdk.dart';
import 'mocks.dart';
import 'utils.dart';
main() {
initializeTestEnvironment();
defineReflectiveTests(AnalysisServerTest);
}
@reflectiveTest
class AnalysisServerTest {
MockServerChannel channel;
AnalysisServer server;
MemoryResourceProvider resourceProvider;
MockPackageMapProvider packageMapProvider;
/**
* Verify that getAnalysisContextForSource returns the correct contexts even
* for sources that are included by multiple contexts.
*
* See dartbug.com/21898
*/
Future fail_getAnalysisContextForSource_crossImports() {
// Subscribe to STATUS so we'll know when analysis is done.
server.serverServices = [ServerService.STATUS].toSet();
// Analyze project foo containing foo.dart and project bar containing
// bar.dart.
resourceProvider.newFolder('/foo');
resourceProvider.newFolder('/bar');
File foo = resourceProvider.newFile(
'/foo/foo.dart',
'''
libary foo;
import "../bar/bar.dart";
''');
Source fooSource = foo.createSource();
File bar = resourceProvider.newFile(
'/bar/bar.dart',
'''
library bar;
import "../foo/foo.dart";
''');
Source barSource = bar.createSource();
server.setAnalysisRoots('0', ['/foo', '/bar'], [], {});
return pumpEventQueue(40).then((_) {
expect(server.statusAnalyzing, isFalse);
// Make sure getAnalysisContext returns the proper context for each.
AnalysisContext fooContext =
server.getAnalysisContextForSource(fooSource);
expect(fooContext, isNotNull);
AnalysisContext barContext =
server.getAnalysisContextForSource(barSource);
expect(barContext, isNotNull);
expect(fooContext, isNot(same(barContext)));
expect(fooContext.getKindOf(fooSource), SourceKind.LIBRARY);
expect(fooContext.getKindOf(barSource), SourceKind.UNKNOWN);
expect(barContext.getKindOf(fooSource), SourceKind.UNKNOWN);
expect(barContext.getKindOf(barSource), SourceKind.LIBRARY);
});
}
/**
* Verify that getAnalysisContextForSource returns the correct contexts even
* for sources that haven't been analyzed yet.
*
* See dartbug.com/21898
*/
Future fail_getAnalysisContextForSource_unanalyzed() {
// Subscribe to STATUS so we'll know when analysis is done.
server.serverServices = [ServerService.STATUS].toSet();
// Analyze project foo containing foo.dart and project bar containing
// bar.dart.
resourceProvider.newFolder('/foo');
resourceProvider.newFolder('/bar');
File foo = resourceProvider.newFile('/foo/foo.dart', 'library lib;');
Source fooSource = foo.createSource();
File bar = resourceProvider.newFile('/bar/bar.dart', 'library lib;');
Source barSource = bar.createSource();
server.setAnalysisRoots('0', ['/foo', '/bar'], [], {});
AnalysisContext fooContext = server.getAnalysisContextForSource(fooSource);
expect(fooContext, isNotNull);
AnalysisContext barContext = server.getAnalysisContextForSource(barSource);
expect(barContext, isNotNull);
expect(fooContext, isNot(same(barContext)));
return pumpEventQueue(40).then((_) {
expect(server.statusAnalyzing, isFalse);
// Make sure getAnalysisContext returned the proper context for each.
expect(fooContext.getKindOf(fooSource), SourceKind.LIBRARY);
expect(fooContext.getKindOf(barSource), SourceKind.UNKNOWN);
expect(barContext.getKindOf(fooSource), SourceKind.UNKNOWN);
expect(barContext.getKindOf(barSource), SourceKind.LIBRARY);
});
}
void processRequiredPlugins() {
List<Plugin> plugins = <Plugin>[];
plugins.addAll(AnalysisEngine.instance.requiredPlugins);
plugins.add(AnalysisEngine.instance.optionsPlugin);
plugins.add(server.serverPlugin);
ExtensionManager manager = new ExtensionManager();
manager.processPlugins(plugins);
}
void setUp() {
channel = new MockServerChannel();
resourceProvider = new MemoryResourceProvider();
packageMapProvider = new MockPackageMapProvider();
server = new AnalysisServer(
channel,
resourceProvider,
packageMapProvider,
null,
new ServerPlugin(),
new AnalysisServerOptions(),
new MockSdk(),
InstrumentationService.NULL_SERVICE,
rethrowExceptions: true);
processRequiredPlugins();
}
Future test_contextDisposed() {
resourceProvider.newFolder('/foo');
resourceProvider.newFile('/foo/bar.dart', 'library lib;');
server.setAnalysisRoots('0', ['/foo'], [], {});
AnalysisContext context;
return pumpEventQueue().then((_) {
context = server.getAnalysisContext('/foo/bar.dart');
server.setAnalysisRoots('1', [], [], {});
}).then((_) => pumpEventQueue()).then((_) {
expect(context.isDisposed, isTrue);
});
}
Future test_contextsChangedEvent() {
resourceProvider.newFolder('/foo');
bool wasAdded = false;
bool wasChanged = false;
bool wasRemoved = false;
server.onContextsChanged.listen((ContextsChangedEvent event) {
wasAdded = event.added.length == 1;
if (wasAdded) {
expect(event.added[0], isNotNull);
}
wasChanged = event.changed.length == 1;
if (wasChanged) {
expect(event.changed[0], isNotNull);
}
wasRemoved = event.removed.length == 1;
if (wasRemoved) {
expect(event.removed[0], isNotNull);
}
});
server.setAnalysisRoots('0', ['/foo'], [], {});
return pumpEventQueue().then((_) {
expect(wasAdded, isTrue);
expect(wasChanged, isFalse);
expect(wasRemoved, isFalse);
wasAdded = false;
wasChanged = false;
wasRemoved = false;
server.setAnalysisRoots('0', ['/foo'], [], {'/foo': '/bar'});
return pumpEventQueue();
}).then((_) {
expect(wasAdded, isFalse);
expect(wasChanged, isTrue);
expect(wasRemoved, isFalse);
wasAdded = false;
wasChanged = false;
wasRemoved = false;
server.setAnalysisRoots('0', [], [], {});
return pumpEventQueue();
}).then((_) {
expect(wasAdded, isFalse);
expect(wasChanged, isFalse);
expect(wasRemoved, isTrue);
});
}
Future test_echo() {
server.handlers = [new EchoHandler()];
var request = new Request('my22', 'echo');
return channel.sendRequest(request).then((Response response) {
expect(response.id, equals('my22'));
expect(response.error, isNull);
});
}
Future test_getAnalysisContextForSource() {
// Subscribe to STATUS so we'll know when analysis is done.
server.serverServices = [ServerService.STATUS].toSet();
// Analyze project foo containing foo.dart and project bar containing
// bar.dart.
resourceProvider.newFolder('/foo');
resourceProvider.newFolder('/bar');
File foo = resourceProvider.newFile('/foo/foo.dart', 'library lib;');
Source fooSource = foo.createSource();
File bar = resourceProvider.newFile('/bar/bar.dart', 'library lib;');
Source barSource = bar.createSource();
server.setAnalysisRoots('0', ['/foo', '/bar'], [], {});
return pumpEventQueue(500).then((_) {
expect(server.statusAnalyzing, isFalse);
// Make sure getAnalysisContext returns the proper context for each.
AnalysisContext fooContext =
server.getAnalysisContextForSource(fooSource);
expect(fooContext, isNotNull);
AnalysisContext barContext =
server.getAnalysisContextForSource(barSource);
expect(barContext, isNotNull);
expect(fooContext, isNot(same(barContext)));
expect(fooContext.getKindOf(fooSource), SourceKind.LIBRARY);
expect(fooContext.getKindOf(barSource), SourceKind.UNKNOWN);
expect(barContext.getKindOf(fooSource), SourceKind.UNKNOWN);
expect(barContext.getKindOf(barSource), SourceKind.LIBRARY);
});
}
test_getContextSourcePair_nested() {
String dir1Path = '/dir1';
String dir2Path = dir1Path + '/dir2';
String filePath = dir2Path + '/file.dart';
resourceProvider.newFile('$dir1Path/.packages', '');
resourceProvider.newFile('$dir2Path/.packages', '');
resourceProvider.newFile(filePath, 'library lib;');
// create contexts
server.setAnalysisRoots('0', [dir1Path], [], {});
// get pair
ContextSourcePair pair = server.getContextSourcePair(filePath);
Source source = pair.source;
_assertContextOfFolder(pair.context, dir2Path);
expect(source, isNotNull);
expect(source.uri.scheme, 'file');
expect(source.fullName, filePath);
}
test_getContextSourcePair_nonFile() {
String dirPath = '/dir';
Folder dir = resourceProvider.newFolder(dirPath);
AnalysisContext context = AnalysisEngine.instance.createAnalysisContext();
_configureSourceFactory(context);
server.folderMap[dir] = context;
ContextSourcePair pair = server.getContextSourcePair(dirPath);
expect(pair, isNotNull);
expect(pair.context, isNull);
expect(pair.source, isNull);
}
test_getContextSourcePair_package_inRoot() {
String rootPath = '/my_package';
String filePath = rootPath + '/lib/file.dart';
Folder rootFolder = resourceProvider.newFolder(rootPath);
resourceProvider.newFile(filePath, 'library lib;');
packageMapProvider.packageMap = <String, List<Folder>>{
'my_package': <Folder>[rootFolder]
};
// create contexts
server.setAnalysisRoots('0', [rootPath], [], {});
// get pair
ContextSourcePair pair = server.getContextSourcePair(filePath);
Source source = pair.source;
_assertContextOfFolder(pair.context, rootPath);
expect(source, isNotNull);
expect(source.uri.scheme, 'package');
expect(source.fullName, filePath);
}
test_getContextSourcePair_simple() {
String dirPath = '/dir';
String filePath = dirPath + '/file.dart';
resourceProvider.newFile(filePath, 'library lib;');
// create contexts
server.setAnalysisRoots('0', [dirPath], [], {});
// get pair
ContextSourcePair pair = server.getContextSourcePair(filePath);
Source source = pair.source;
_assertContextOfFolder(pair.context, dirPath);
expect(source, isNotNull);
expect(source.uri.scheme, 'file');
expect(source.fullName, filePath);
}
/**
* Test that having multiple analysis contexts analyze the same file doesn't
* cause that file to receive duplicate notifications when it's modified.
*/
Future test_no_duplicate_notifications() async {
// Subscribe to STATUS so we'll know when analysis is done.
server.serverServices = [ServerService.STATUS].toSet();
resourceProvider.newFolder('/foo');
resourceProvider.newFolder('/bar');
resourceProvider.newFile('/foo/foo.dart', 'import "../bar/bar.dart";');
File bar = resourceProvider.newFile('/bar/bar.dart', 'library bar;');
server.setAnalysisRoots('0', ['/foo', '/bar'], [], {});
Map<AnalysisService, Set<String>> subscriptions =
<AnalysisService, Set<String>>{};
for (AnalysisService service in AnalysisService.VALUES) {
subscriptions[service] = <String>[bar.path].toSet();
}
server.setAnalysisSubscriptions(subscriptions);
await pumpEventQueue(1000);
expect(server.statusAnalyzing, isFalse);
channel.notificationsReceived.clear();
server.updateContent(
'0', {bar.path: new AddContentOverlay('library bar; void f() {}')});
await pumpEventQueue(500);
expect(server.statusAnalyzing, isFalse);
expect(channel.notificationsReceived, isNotEmpty);
Set<String> notificationTypesReceived = new Set<String>();
for (Notification notification in channel.notificationsReceived) {
String notificationType = notification.event;
switch (notificationType) {
case 'server.status':
case 'analysis.errors':
// It's normal for these notifications to be sent multiple times.
break;
case 'analysis.outline':
// It's normal for this notification to be sent twice.
// TODO(paulberry): why?
break;
default:
if (!notificationTypesReceived.add(notificationType)) {
fail('Notification type $notificationType received more than once');
}
break;
}
}
}
test_operationsRemovedOnContextDisposal() async {
resourceProvider.newFolder('/foo');
resourceProvider.newFile('/foo/baz.dart', 'library lib;');
resourceProvider.newFolder('/bar');
resourceProvider.newFile('/bar/baz.dart', 'library lib;');
server.setAnalysisRoots('0', ['/foo', '/bar'], [], {});
await pumpEventQueue();
AnalysisContext contextFoo = server.getAnalysisContext('/foo/baz.dart');
AnalysisContext contextBar = server.getAnalysisContext('/bar/baz.dart');
_MockServerOperation operationFoo = new _MockServerOperation(contextFoo);
_MockServerOperation operationBar = new _MockServerOperation(contextBar);
server.scheduleOperation(operationFoo);
server.scheduleOperation(operationBar);
server.setAnalysisRoots('1', ['/foo'], [], {});
await pumpEventQueue();
expect(operationFoo.isComplete, isTrue);
expect(operationBar.isComplete, isFalse);
}
Future test_prioritySourcesChangedEvent() {
resourceProvider.newFolder('/foo');
int eventCount = 0;
Source firstSource = null;
server.onPriorityChange.listen((PriorityChangeEvent event) {
++eventCount;
firstSource = event.firstSource;
});
server.setAnalysisRoots('0', ['/foo'], [], {});
return pumpEventQueue().then((_) {
expect(eventCount, 0);
server.setPriorityFiles('1', ['/foo/bar.dart']);
return pumpEventQueue();
}).then((_) {
expect(eventCount, 1);
expect(firstSource.fullName, '/foo/bar.dart');
server.setPriorityFiles('2', ['/foo/b1.dart', '/foo/b2.dart']);
return pumpEventQueue();
}).then((_) {
expect(eventCount, 2);
expect(firstSource.fullName, '/foo/b1.dart');
server.setPriorityFiles('17', []);
return pumpEventQueue();
}).then((_) {
expect(eventCount, 3);
expect(firstSource, isNull);
});
}
void test_rethrowExceptions() {
Exception exceptionToThrow = new Exception('test exception');
MockServerOperation operation =
new MockServerOperation(ServerOperationPriority.ANALYSIS, (_) {
throw exceptionToThrow;
});
server.operationQueue.add(operation);
server.performOperationPending = true;
try {
server.performOperation();
fail('exception not rethrown');
} on AnalysisException catch (exception) {
expect(exception.cause.exception, equals(exceptionToThrow));
}
}
Future test_serverStatusNotifications() {
MockAnalysisContext context = new MockAnalysisContext('context');
MockSource source = new MockSource('source');
when(source.fullName).thenReturn('foo.dart');
when(source.isInSystemLibrary).thenReturn(false);
ChangeNoticeImpl notice = new ChangeNoticeImpl(source);
notice.setErrors([], new LineInfo([0]));
AnalysisResult firstResult = new AnalysisResult([notice], 0, '', 0);
AnalysisResult lastResult = new AnalysisResult(null, 1, '', 1);
when(context.analysisOptions).thenReturn(new AnalysisOptionsImpl());
when(context.validateCacheConsistency()).thenReturn(false);
when(context.performAnalysisTask)
.thenReturnList([firstResult, firstResult, firstResult, lastResult]);
server.serverServices.add(ServerService.STATUS);
server.schedulePerformAnalysisOperation(context);
// Pump the event queue to make sure the server has finished any
// analysis.
return pumpEventQueue().then((_) {
List<Notification> notifications = channel.notificationsReceived;
expect(notifications, isNotEmpty);
// expect at least one notification indicating analysis is in progress
expect(notifications.any((Notification notification) {
if (notification.event == SERVER_STATUS) {
var params = new ServerStatusParams.fromNotification(notification);
return params.analysis.isAnalyzing;
}
return false;
}), isTrue);
// the last notification should indicate that analysis is complete
Notification notification = notifications[notifications.length - 1];
var params = new ServerStatusParams.fromNotification(notification);
expect(params.analysis.isAnalyzing, isFalse);
});
}
test_setAnalysisSubscriptions_fileInIgnoredFolder() async {
String path = '/project/samples/sample.dart';
resourceProvider.newFile(path, '');
resourceProvider.newFile(
'/project/.analysis_options',
r'''
analyzer:
exclude:
- 'samples/**'
''');
server.setAnalysisRoots('0', ['/project'], [], {});
server.setAnalysisSubscriptions(<AnalysisService, Set<String>>{
AnalysisService.NAVIGATION: new Set<String>.from([path])
});
// the file is excluded, so no navigation notification
await server.onAnalysisComplete;
expect(channel.notificationsReceived.any((notification) {
return notification.event == ANALYSIS_NAVIGATION;
}), isFalse);
}
Future test_shutdown() {
server.handlers = [new ServerDomainHandler(server)];
var request = new Request('my28', SERVER_SHUTDOWN);
return channel.sendRequest(request).then((Response response) {
expect(response.id, equals('my28'));
expect(response.error, isNull);
});
}
Future test_unknownRequest() {
server.handlers = [new EchoHandler()];
var request = new Request('my22', 'randomRequest');
return channel.sendRequest(request).then((Response response) {
expect(response.id, equals('my22'));
expect(response.error, isNotNull);
});
}
test_watch_modifyFile_hasOverlay() async {
server.serverServices.add(ServerService.STATUS);
// configure the project
String projectPath = '/root';
String filePath = '/root/test.dart';
resourceProvider.newFolder(projectPath);
resourceProvider.newFile(filePath, '// 111');
server.setAnalysisRoots('0', ['/root'], [], {});
await pumpEventQueue();
// add overlay
server.updateContent('1', {filePath: new AddContentOverlay('// 222')});
await pumpEventQueue();
// update the file
channel.notificationsReceived.clear();
resourceProvider.modifyFile(filePath, '// 333');
await pumpEventQueue();
// the file has an overlay, so the file-system change was ignored
expect(channel.notificationsReceived.any((notification) {
return notification.event == SERVER_STATUS;
}), isFalse);
}
void _assertContextOfFolder(
AnalysisContext context, String expectedFolderPath) {
Folder expectedFolder = resourceProvider.newFolder(expectedFolderPath);
ContextInfo expectedContextInfo = (server.contextManager
as ContextManagerImpl).getContextInfoFor(expectedFolder);
expect(expectedContextInfo, isNotNull);
expect(context, same(expectedContextInfo.context));
}
void _configureSourceFactory(AnalysisContext context) {
var resourceUriResolver = new ResourceUriResolver(resourceProvider);
var packageUriResolver = new PackageMapUriResolver(
resourceProvider, packageMapProvider.packageMap);
context.sourceFactory =
new SourceFactory([packageUriResolver, resourceUriResolver]);
}
}
class EchoHandler implements RequestHandler {
@override
Response handleRequest(Request request) {
if (request.method == 'echo') {
return new Response(request.id, result: {'echo': true});
}
return null;
}
}
/**
* A [ServerOperation] that does nothing but keep track of whether or not it
* has been performed.
*/
class _MockServerOperation implements ServerOperation {
final AnalysisContext context;
bool isComplete = false;
_MockServerOperation(this.context);
@override
ServerOperationPriority get priority => ServerOperationPriority.ANALYSIS;
@override
void perform(AnalysisServer server) {
isComplete = true;
}
}