bc34c295ae
Change-Id: I0e7cd26dcb0c7de1b4f7e19b66719d5a73670ce7 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/345824 Commit-Queue: Phil Quitslund <pquitslund@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com> Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
560 lines
18 KiB
Dart
560 lines
18 KiB
Dart
// Copyright (c) 2017, 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:convert';
|
|
import 'dart:isolate';
|
|
|
|
import 'package:analyzer/dart/analysis/analysis_context.dart';
|
|
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
|
|
import 'package:analyzer/file_system/file_system.dart';
|
|
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
|
|
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
|
|
import 'package:analyzer_plugin/plugin/plugin.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_generated.dart';
|
|
import 'package:analyzer_plugin/src/protocol/protocol_internal.dart';
|
|
import 'package:meta/meta.dart';
|
|
import 'package:pub_semver/pub_semver.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import 'mocks.dart';
|
|
|
|
void main() {
|
|
defineReflectiveTests(ServerPluginTest);
|
|
}
|
|
|
|
abstract class AbstractPluginTest with ResourceProviderMixin {
|
|
final MockChannel channel = MockChannel();
|
|
late final ServerPlugin plugin;
|
|
|
|
Folder get byteStoreRoot => getFolder('/byteStore');
|
|
|
|
Version get pluginSpecificationVersion => Version(0, 1, 0);
|
|
|
|
Folder get sdkRoot => getFolder('/sdk');
|
|
|
|
ServerPlugin createPlugin();
|
|
|
|
@mustCallSuper
|
|
Future<void> setUp() async {
|
|
createMockSdk(
|
|
resourceProvider: resourceProvider,
|
|
root: sdkRoot,
|
|
);
|
|
|
|
plugin = createPlugin();
|
|
plugin.start(channel);
|
|
|
|
await plugin.handlePluginVersionCheck(
|
|
PluginVersionCheckParams(
|
|
byteStoreRoot.path,
|
|
sdkRoot.path,
|
|
pluginSpecificationVersion.canonicalizedVersion,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class ServerPluginTest extends AbstractPluginTest {
|
|
late String packagePath1;
|
|
late String filePath1;
|
|
late ContextRoot contextRoot1;
|
|
|
|
late String packagePath2;
|
|
late String filePath2;
|
|
late ContextRoot contextRoot2;
|
|
|
|
/// Asserts that [params] is valid to send to an [Isolate] started with
|
|
/// [Isolate.spawnUri].
|
|
Future<void> assertValidForIsolateSend(RequestParams params) async {
|
|
const isolateSource = r'''
|
|
import 'dart:isolate';
|
|
|
|
void main(List<String> args, SendPort sendPort) {
|
|
final receivePort = ReceivePort();
|
|
sendPort.send(receivePort.sendPort);
|
|
receivePort.listen((msg) => sendPort.send('ECHO: $msg'));
|
|
}
|
|
''';
|
|
var isolateUri = Uri.dataFromString(isolateSource, encoding: utf8);
|
|
var receivePort = ReceivePort();
|
|
var isolate = await Isolate.spawnUri(isolateUri, [], receivePort.sendPort);
|
|
var sendPort = (await receivePort.first) as SendPort;
|
|
try {
|
|
sendPort.send(params.toJson());
|
|
} catch (e) {
|
|
fail('Failed to send ${params.runtimeType} across Isolate: $e');
|
|
}
|
|
isolate.kill();
|
|
}
|
|
|
|
@override
|
|
ServerPlugin createPlugin() {
|
|
return _TestServerPlugin(resourceProvider);
|
|
}
|
|
|
|
@override
|
|
Future<void> setUp() async {
|
|
await super.setUp();
|
|
packagePath1 = convertPath('/package1');
|
|
filePath1 = join(packagePath1, 'lib', 'test.dart');
|
|
newFile(filePath1, '');
|
|
contextRoot1 = ContextRoot(packagePath1, <String>[]);
|
|
|
|
packagePath2 = convertPath('/package2');
|
|
filePath2 = join(packagePath2, 'lib', 'test.dart');
|
|
newFile(filePath2, '');
|
|
contextRoot2 = ContextRoot(packagePath2, <String>[]);
|
|
}
|
|
|
|
Future<void> test_handleAnalysisGetNavigation() async {
|
|
var result = await plugin
|
|
.handleAnalysisGetNavigation(AnalysisGetNavigationParams('', 1, 2));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_handleAnalysisHandleWatchEvents() async {
|
|
var result = await plugin
|
|
.handleAnalysisHandleWatchEvents(AnalysisHandleWatchEventsParams([]));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_handleAnalysisSetPriorityFiles() async {
|
|
await plugin.handleAnalysisSetContextRoots(
|
|
AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result = await plugin.handleAnalysisSetPriorityFiles(
|
|
AnalysisSetPriorityFilesParams([filePath1]));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_handleAnalysisSetSubscriptions() async {
|
|
await plugin.handleAnalysisSetContextRoots(
|
|
AnalysisSetContextRootsParams([contextRoot1]));
|
|
expect(plugin.subscriptionManager.servicesForFile(filePath1), isEmpty);
|
|
|
|
var result = await plugin
|
|
.handleAnalysisSetSubscriptions(AnalysisSetSubscriptionsParams({
|
|
AnalysisService.OUTLINE: [filePath1]
|
|
}));
|
|
expect(result, isNotNull);
|
|
expect(plugin.subscriptionManager.servicesForFile(filePath1),
|
|
[AnalysisService.OUTLINE]);
|
|
}
|
|
|
|
Future<void> test_handleAnalysisUpdateContent_addChangeRemove() async {
|
|
await plugin.handleAnalysisSetContextRoots(
|
|
AnalysisSetContextRootsParams([contextRoot1]));
|
|
var addResult = await plugin.handleAnalysisUpdateContent(
|
|
AnalysisUpdateContentParams(
|
|
{filePath1: AddContentOverlay('class C {}')}));
|
|
expect(addResult, isNotNull);
|
|
var changeResult =
|
|
await plugin.handleAnalysisUpdateContent(AnalysisUpdateContentParams({
|
|
filePath1: ChangeContentOverlay([SourceEdit(7, 0, ' extends Object')])
|
|
}));
|
|
expect(changeResult, isNotNull);
|
|
var removeResult = await plugin.handleAnalysisUpdateContent(
|
|
AnalysisUpdateContentParams({filePath1: RemoveContentOverlay()}));
|
|
expect(removeResult, isNotNull);
|
|
}
|
|
|
|
Future<void> test_handleAnalysisUpdateContent_changeNoAdd() async {
|
|
await plugin.handleAnalysisSetContextRoots(
|
|
AnalysisSetContextRootsParams([contextRoot1]));
|
|
try {
|
|
await plugin.handleAnalysisUpdateContent(AnalysisUpdateContentParams({
|
|
filePath1: ChangeContentOverlay([SourceEdit(7, 0, ' extends Object')])
|
|
}));
|
|
fail('Expected RequestFailure');
|
|
} on RequestFailure {
|
|
// Expected
|
|
}
|
|
}
|
|
|
|
Future<void> test_handleAnalysisUpdateContent_invalidChange() async {
|
|
await plugin.handleAnalysisSetContextRoots(
|
|
AnalysisSetContextRootsParams([contextRoot1]));
|
|
await plugin.handleAnalysisUpdateContent(AnalysisUpdateContentParams(
|
|
{filePath1: AddContentOverlay('class C {}')}));
|
|
try {
|
|
await plugin.handleAnalysisUpdateContent(AnalysisUpdateContentParams({
|
|
filePath1: ChangeContentOverlay([SourceEdit(20, 5, 'class D {}')])
|
|
}));
|
|
fail('Expected RequestFailure');
|
|
} on RequestFailure {
|
|
// Expected
|
|
}
|
|
}
|
|
|
|
Future<void> test_handleCompletionGetSuggestions() async {
|
|
await plugin.handleAnalysisSetContextRoots(
|
|
AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result = await plugin.handleCompletionGetSuggestions(
|
|
CompletionGetSuggestionsParams(filePath1, 12));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_handleEditGetAssists() async {
|
|
await plugin.handleAnalysisSetContextRoots(
|
|
AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result = await plugin
|
|
.handleEditGetAssists(EditGetAssistsParams(filePath1, 10, 0));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_handleEditGetAvailableRefactorings() async {
|
|
await plugin.handleAnalysisSetContextRoots(
|
|
AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result = await plugin.handleEditGetAvailableRefactorings(
|
|
EditGetAvailableRefactoringsParams(filePath1, 10, 0));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_handleEditGetFixes() async {
|
|
await plugin.handleAnalysisSetContextRoots(
|
|
AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result =
|
|
await plugin.handleEditGetFixes(EditGetFixesParams(filePath1, 13));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
@failingTest
|
|
Future<void> test_handleEditGetRefactoring() async {
|
|
await plugin.handleAnalysisSetContextRoots(
|
|
AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result = await plugin.handleEditGetRefactoring(EditGetRefactoringParams(
|
|
RefactoringKind.RENAME, filePath1, 7, 0, false));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_handlePluginShutdown() async {
|
|
var result = await plugin.handlePluginShutdown(PluginShutdownParams());
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_handlePluginVersionCheck() async {
|
|
var result = await plugin.handlePluginVersionCheck(
|
|
PluginVersionCheckParams('byteStorePath', 'sdkPath', '0.1.0'));
|
|
expect(result, isNotNull);
|
|
expect(result.interestingFiles, ['*.dart']);
|
|
expect(result.isCompatible, isTrue);
|
|
expect(result.name, 'Test Plugin');
|
|
expect(result.version, '0.1.0');
|
|
}
|
|
|
|
@failingTest
|
|
void test_isCompatibleWith() {
|
|
fail('Not yet implemented.');
|
|
}
|
|
|
|
Future<void> test_isolateSend_analysisGetNavigation() async {
|
|
await assertValidForIsolateSend(AnalysisGetNavigationParams('', 1, 2));
|
|
}
|
|
|
|
Future<void> test_isolateSend_analysisHandleWatchEvents() async {
|
|
await assertValidForIsolateSend(AnalysisHandleWatchEventsParams([]));
|
|
}
|
|
|
|
Future<void> test_isolateSend_analysisSetPriorityFiles() async {
|
|
await assertValidForIsolateSend(
|
|
AnalysisSetPriorityFilesParams([filePath1]));
|
|
}
|
|
|
|
Future<void> test_isolateSend_analysisSetSubscriptions() async {
|
|
await assertValidForIsolateSend(AnalysisSetSubscriptionsParams({
|
|
AnalysisService.OUTLINE: [filePath1]
|
|
}));
|
|
}
|
|
|
|
Future<void> test_isolateSend_analysisUpdateContent_add() async {
|
|
await assertValidForIsolateSend(AnalysisUpdateContentParams(
|
|
{filePath1: AddContentOverlay('class C {}')}));
|
|
}
|
|
|
|
Future<void> test_isolateSend_analysisUpdateContent_change() async {
|
|
await assertValidForIsolateSend(AnalysisUpdateContentParams({
|
|
filePath1: ChangeContentOverlay([SourceEdit(7, 0, ' extends Object')])
|
|
}));
|
|
}
|
|
|
|
Future<void> test_isolateSend_analysisUpdateContent_remove() async {
|
|
await assertValidForIsolateSend(
|
|
AnalysisUpdateContentParams({filePath1: RemoveContentOverlay()}));
|
|
}
|
|
|
|
Future<void> test_isolateSend_completionGetSuggestions() async {
|
|
await assertValidForIsolateSend(
|
|
CompletionGetSuggestionsParams(filePath1, 12));
|
|
}
|
|
|
|
Future<void> test_isolateSend_editGetAssists() async {
|
|
await assertValidForIsolateSend(EditGetAssistsParams(filePath1, 10, 0));
|
|
}
|
|
|
|
Future<void> test_isolateSend_editGetAvailableRefactorings() async {
|
|
await assertValidForIsolateSend(
|
|
EditGetAvailableRefactoringsParams(filePath1, 10, 0));
|
|
}
|
|
|
|
Future<void> test_isolateSend_editGetFixes() async {
|
|
await assertValidForIsolateSend(EditGetFixesParams(filePath1, 13));
|
|
}
|
|
|
|
Future<void> test_isolateSend_editGetRefactoring() async {
|
|
await assertValidForIsolateSend(EditGetRefactoringParams(
|
|
RefactoringKind.RENAME, filePath1, 7, 0, false));
|
|
}
|
|
|
|
Future<void> test_isolateSend_pluginShutdown() async {
|
|
await assertValidForIsolateSend(PluginShutdownParams());
|
|
}
|
|
|
|
Future<void> test_isolateSend_pluginVersionCheck() async {
|
|
await assertValidForIsolateSend(
|
|
PluginVersionCheckParams('byteStorePath', 'sdkPath', '0.1.0'));
|
|
}
|
|
|
|
Future<void> test_isolateSend_setContextRoots() async {
|
|
await assertValidForIsolateSend(
|
|
AnalysisSetContextRootsParams([contextRoot1]));
|
|
}
|
|
|
|
void test_onDone() {
|
|
channel.sendDone();
|
|
}
|
|
|
|
void test_onError() {
|
|
channel.sendError(ArgumentError(), StackTrace.fromString(''));
|
|
}
|
|
|
|
Future<void> test_onRequest_analysisGetNavigation() async {
|
|
var result =
|
|
await channel.sendRequest(AnalysisGetNavigationParams('', 1, 2));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_onRequest_analysisHandleWatchEvents() async {
|
|
var result = await channel.sendRequest(AnalysisHandleWatchEventsParams([]));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_onRequest_analysisSetContextRoots() async {
|
|
var plugin = this.plugin as _TestServerPlugin;
|
|
|
|
var analyzedPaths = <String>[];
|
|
plugin.analyzeFileHandler = ({
|
|
required AnalysisContext analysisContext,
|
|
required String path,
|
|
}) {
|
|
analyzedPaths.add(path);
|
|
};
|
|
|
|
var result = await channel
|
|
.sendRequest(AnalysisSetContextRootsParams([contextRoot1]));
|
|
expect(result, isNotNull);
|
|
|
|
expect(plugin.invoked_afterNewContextCollection, isTrue);
|
|
expect(analyzedPaths, [getFile('/package1/lib/test.dart').path]);
|
|
}
|
|
|
|
Future<void> test_onRequest_analysisSetPriorityFiles() async {
|
|
await channel.sendRequest(AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result =
|
|
await channel.sendRequest(AnalysisSetPriorityFilesParams([filePath1]));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_onRequest_analysisSetSubscriptions() async {
|
|
await channel.sendRequest(AnalysisSetContextRootsParams([contextRoot1]));
|
|
expect(plugin.subscriptionManager.servicesForFile(filePath1), isEmpty);
|
|
|
|
var result = await channel.sendRequest(AnalysisSetSubscriptionsParams({
|
|
AnalysisService.OUTLINE: [filePath1]
|
|
}));
|
|
expect(result, isNotNull);
|
|
expect(plugin.subscriptionManager.servicesForFile(filePath1),
|
|
[AnalysisService.OUTLINE]);
|
|
}
|
|
|
|
Future<void> test_onRequest_analysisUpdateContent_addChangeRemove() async {
|
|
await channel.sendRequest(AnalysisSetContextRootsParams([contextRoot1]));
|
|
var addResult = await channel.sendRequest(AnalysisUpdateContentParams(
|
|
{filePath1: AddContentOverlay('class C {}')}));
|
|
expect(addResult, isNotNull);
|
|
var changeResult = await channel.sendRequest(AnalysisUpdateContentParams({
|
|
filePath1: ChangeContentOverlay([SourceEdit(7, 0, ' extends Object')])
|
|
}));
|
|
expect(changeResult, isNotNull);
|
|
var removeResult = await channel.sendRequest(
|
|
AnalysisUpdateContentParams({filePath1: RemoveContentOverlay()}));
|
|
expect(removeResult, isNotNull);
|
|
}
|
|
|
|
Future<void> test_onRequest_completionGetSuggestions() async {
|
|
await channel.sendRequest(AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result = await channel
|
|
.sendRequest(CompletionGetSuggestionsParams(filePath1, 12));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_onRequest_editGetAssists() async {
|
|
await channel.sendRequest(AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result =
|
|
await channel.sendRequest(EditGetAssistsParams(filePath1, 10, 0));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_onRequest_editGetAvailableRefactorings() async {
|
|
await channel.sendRequest(AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result = await channel
|
|
.sendRequest(EditGetAvailableRefactoringsParams(filePath1, 10, 0));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_onRequest_editGetFixes() async {
|
|
await channel.sendRequest(AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result = await channel.sendRequest(EditGetFixesParams(filePath1, 13));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_onRequest_editGetRefactoring() async {
|
|
await channel.sendRequest(AnalysisSetContextRootsParams([contextRoot1]));
|
|
|
|
var result = await channel.sendRequest(EditGetRefactoringParams(
|
|
RefactoringKind.RENAME, filePath1, 7, 0, false));
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_onRequest_pluginShutdown() async {
|
|
var result = await channel.sendRequest(PluginShutdownParams());
|
|
expect(result, isNotNull);
|
|
}
|
|
|
|
Future<void> test_onRequest_pluginVersionCheck() async {
|
|
var response = await channel.sendRequest(
|
|
PluginVersionCheckParams('byteStorePath', 'sdkPath', '0.1.0'));
|
|
var result = PluginVersionCheckResult.fromResponse(response);
|
|
expect(result, isNotNull);
|
|
expect(result.interestingFiles, ['*.dart']);
|
|
expect(result.isCompatible, isTrue);
|
|
expect(result.name, 'Test Plugin');
|
|
expect(result.version, '0.1.0');
|
|
}
|
|
|
|
void test_sendNotificationsForFile() {
|
|
var service1 = AnalysisService.FOLDING;
|
|
var service2 = AnalysisService.NAVIGATION;
|
|
var service3 = AnalysisService.OUTLINE;
|
|
plugin.subscriptionManager.setSubscriptions({
|
|
service1: [filePath1, filePath2],
|
|
service2: [filePath1],
|
|
service3: [filePath2]
|
|
});
|
|
plugin.sendNotificationsForFile(filePath1);
|
|
var notifications = (plugin as _TestServerPlugin).sentNotifications;
|
|
expect(notifications, hasLength(1));
|
|
var services = notifications[filePath1];
|
|
expect(services, unorderedEquals([service1, service2]));
|
|
}
|
|
|
|
void test_sendNotificationsForSubscriptions() {
|
|
var subscriptions = <String, List<AnalysisService>>{};
|
|
|
|
plugin.sendNotificationsForSubscriptions(subscriptions);
|
|
var notifications = (plugin as _TestServerPlugin).sentNotifications;
|
|
expect(notifications, hasLength(subscriptions.length));
|
|
for (var path in subscriptions.keys) {
|
|
var subscribedServices = subscriptions[path];
|
|
var notifiedServices = notifications[path];
|
|
expect(notifiedServices, isNotNull,
|
|
reason: 'Not notified for file $path');
|
|
expect(notifiedServices, unorderedEquals(subscribedServices!),
|
|
reason: 'Wrong notifications for file $path');
|
|
}
|
|
}
|
|
}
|
|
|
|
class _TestServerPlugin extends MockServerPlugin {
|
|
Map<String, List<AnalysisService>> sentNotifications =
|
|
<String, List<AnalysisService>>{};
|
|
|
|
bool invoked_afterNewContextCollection = false;
|
|
|
|
void Function({
|
|
required AnalysisContext analysisContext,
|
|
required String path,
|
|
})? analyzeFileHandler;
|
|
|
|
_TestServerPlugin(super.resourceProvider);
|
|
|
|
@override
|
|
Future<void> afterNewContextCollection({
|
|
required AnalysisContextCollection contextCollection,
|
|
}) async {
|
|
invoked_afterNewContextCollection = true;
|
|
return super.afterNewContextCollection(
|
|
contextCollection: contextCollection,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> analyzeFile({
|
|
required AnalysisContext analysisContext,
|
|
required String path,
|
|
}) async {
|
|
analyzeFileHandler?.call(
|
|
analysisContext: analysisContext,
|
|
path: path,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<void> sendFoldingNotification(String path) {
|
|
_sent(path, AnalysisService.FOLDING);
|
|
return Future.value();
|
|
}
|
|
|
|
@override
|
|
Future<void> sendHighlightsNotification(String path) {
|
|
_sent(path, AnalysisService.HIGHLIGHTS);
|
|
return Future.value();
|
|
}
|
|
|
|
@override
|
|
Future<void> sendNavigationNotification(String path) {
|
|
_sent(path, AnalysisService.NAVIGATION);
|
|
return Future.value();
|
|
}
|
|
|
|
@override
|
|
Future<void> sendOccurrencesNotification(String path) {
|
|
_sent(path, AnalysisService.OCCURRENCES);
|
|
return Future.value();
|
|
}
|
|
|
|
@override
|
|
Future<void> sendOutlineNotification(String path) {
|
|
_sent(path, AnalysisService.OUTLINE);
|
|
return Future.value();
|
|
}
|
|
|
|
void _sent(String path, AnalysisService service) {
|
|
sentNotifications.putIfAbsent(path, () => <AnalysisService>[]).add(service);
|
|
}
|
|
}
|