30e4510f20
Change-Id: I1b43b0e61e5cc89261170ce13613fad756600d97 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/222201 Reviewed-by: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
287 lines
9.9 KiB
Dart
287 lines
9.9 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.
|
|
|
|
import 'package:analysis_server/protocol/protocol.dart';
|
|
import 'package:analysis_server/protocol/protocol_constants.dart';
|
|
import 'package:analysis_server/protocol/protocol_generated.dart';
|
|
import 'package:analysis_server/src/analysis_server.dart';
|
|
import 'package:analysis_server/src/domain_server.dart';
|
|
import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
|
|
import 'package:analysis_server/src/utilities/mocks.dart';
|
|
import 'package:analysis_server/src/utilities/progress.dart';
|
|
import 'package:analyzer/instrumentation/instrumentation.dart';
|
|
import 'package:analyzer/src/generated/sdk.dart';
|
|
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
|
|
import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(AnalysisServerTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class AnalysisServerTest with ResourceProviderMixin {
|
|
late MockServerChannel channel;
|
|
late AnalysisServer server;
|
|
|
|
/// Test that having multiple analysis contexts analyze the same file doesn't
|
|
/// cause that file to receive duplicate notifications when it's modified.
|
|
Future do_not_test_no_duplicate_notifications() async {
|
|
// Subscribe to STATUS so we'll know when analysis is done.
|
|
server.serverServices = {ServerService.STATUS};
|
|
newFolder('/foo');
|
|
newFolder('/bar');
|
|
newFile('/foo/foo.dart', content: 'import "../bar/bar.dart";');
|
|
var bar = newFile('/bar/bar.dart', content: 'library bar;');
|
|
server.setAnalysisRoots('0', ['/foo', '/bar'], []);
|
|
var subscriptions = <AnalysisService, Set<String>>{};
|
|
for (var service in AnalysisService.VALUES) {
|
|
subscriptions[service] = <String>{bar.path};
|
|
}
|
|
// The following line causes the isolate to continue running even though the
|
|
// test completes.
|
|
server.setAnalysisSubscriptions(subscriptions);
|
|
await server.onAnalysisComplete;
|
|
expect(server.statusAnalyzing, isFalse);
|
|
channel.notificationsReceived.clear();
|
|
server.updateContent(
|
|
'0', {bar.path: AddContentOverlay('library bar; void f() {}')});
|
|
await server.onAnalysisComplete;
|
|
expect(server.statusAnalyzing, isFalse);
|
|
expect(channel.notificationsReceived, isNotEmpty);
|
|
var notificationTypesReceived = <String>{};
|
|
for (var notification in channel.notificationsReceived) {
|
|
var 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
void setUp() {
|
|
channel = MockServerChannel();
|
|
|
|
// Create an SDK in the mock file system.
|
|
var sdkRoot = newFolder('/sdk');
|
|
createMockSdk(
|
|
resourceProvider: resourceProvider,
|
|
root: sdkRoot,
|
|
);
|
|
|
|
server = AnalysisServer(
|
|
channel,
|
|
resourceProvider,
|
|
AnalysisServerOptions(),
|
|
DartSdkManager(sdkRoot.path),
|
|
CrashReportingAttachmentsBuilder.empty,
|
|
InstrumentationService.NULL_SERVICE);
|
|
}
|
|
|
|
Future test_echo() {
|
|
server.handlers = [EchoHandler(server)];
|
|
var request = Request('my22', 'echo');
|
|
return channel.sendRequest(request).then((Response response) {
|
|
expect(response.id, equals('my22'));
|
|
expect(response.error, isNull);
|
|
});
|
|
}
|
|
|
|
Future test_serverStatusNotifications_hasFile() async {
|
|
server.serverServices.add(ServerService.STATUS);
|
|
|
|
newFile('/test/lib/a.dart', content: r'''
|
|
class A {}
|
|
''');
|
|
server.setAnalysisRoots('0', [convertPath('/test')], []);
|
|
|
|
// Pump the event queue, so that the server has finished any analysis.
|
|
await pumpEventQueue(times: 5000);
|
|
|
|
var notifications = channel.notificationsReceived;
|
|
expect(notifications, isNotEmpty);
|
|
|
|
// At least one notification indicating analysis is in progress.
|
|
expect(notifications.any((Notification notification) {
|
|
if (notification.event == SERVER_NOTIFICATION_STATUS) {
|
|
var params = ServerStatusParams.fromNotification(notification);
|
|
var analysis = params.analysis;
|
|
if (analysis != null) {
|
|
return analysis.isAnalyzing;
|
|
}
|
|
}
|
|
return false;
|
|
}), isTrue);
|
|
|
|
// The last notification should indicate that analysis is complete.
|
|
var notification = notifications[notifications.length - 1];
|
|
var params = ServerStatusParams.fromNotification(notification);
|
|
expect(params.analysis!.isAnalyzing, isFalse);
|
|
}
|
|
|
|
Future test_serverStatusNotifications_noFiles() async {
|
|
server.serverServices.add(ServerService.STATUS);
|
|
|
|
newFolder('/test');
|
|
server.setAnalysisRoots('0', [convertPath('/test')], []);
|
|
|
|
// Pump the event queue, so that the server has finished any analysis.
|
|
await pumpEventQueue(times: 5000);
|
|
|
|
var notifications = channel.notificationsReceived;
|
|
expect(notifications, isNotEmpty);
|
|
|
|
// At least one notification indicating analysis is in progress.
|
|
expect(notifications.any((Notification notification) {
|
|
if (notification.event == SERVER_NOTIFICATION_STATUS) {
|
|
var params = ServerStatusParams.fromNotification(notification);
|
|
var analysis = params.analysis;
|
|
if (analysis != null) {
|
|
return analysis.isAnalyzing;
|
|
}
|
|
}
|
|
return false;
|
|
}), isTrue);
|
|
|
|
// The last notification should indicate that analysis is complete.
|
|
var notification = notifications[notifications.length - 1];
|
|
var params = ServerStatusParams.fromNotification(notification);
|
|
expect(params.analysis!.isAnalyzing, isFalse);
|
|
}
|
|
|
|
Future<void>
|
|
test_setAnalysisSubscriptions_fileInIgnoredFolder_newOptions() async {
|
|
var path = convertPath('/project/samples/sample.dart');
|
|
newFile(path);
|
|
newAnalysisOptionsYamlFile('/project', content: r'''
|
|
analyzer:
|
|
exclude:
|
|
- 'samples/**'
|
|
''');
|
|
server.setAnalysisRoots('0', [convertPath('/project')], []);
|
|
server.setAnalysisSubscriptions(<AnalysisService, Set<String>>{
|
|
AnalysisService.NAVIGATION: <String>{path}
|
|
});
|
|
|
|
// We respect subscriptions, even for excluded files.
|
|
await pumpEventQueue();
|
|
expect(channel.notificationsReceived.any((notification) {
|
|
return notification.event == ANALYSIS_NOTIFICATION_NAVIGATION;
|
|
}), isTrue);
|
|
}
|
|
|
|
Future<void>
|
|
test_setAnalysisSubscriptions_fileInIgnoredFolder_oldOptions() async {
|
|
var path = convertPath('/project/samples/sample.dart');
|
|
newFile(path);
|
|
newAnalysisOptionsYamlFile('/project', content: r'''
|
|
analyzer:
|
|
exclude:
|
|
- 'samples/**'
|
|
''');
|
|
server.setAnalysisRoots('0', [convertPath('/project')], []);
|
|
server.setAnalysisSubscriptions(<AnalysisService, Set<String>>{
|
|
AnalysisService.NAVIGATION: <String>{path}
|
|
});
|
|
|
|
// We respect subscriptions, even for excluded files.
|
|
await pumpEventQueue();
|
|
expect(channel.notificationsReceived.any((notification) {
|
|
return notification.event == ANALYSIS_NOTIFICATION_NAVIGATION;
|
|
}), isTrue);
|
|
}
|
|
|
|
Future test_shutdown() {
|
|
server.handlers = [ServerDomainHandler(server)];
|
|
var request = Request('my28', SERVER_REQUEST_SHUTDOWN);
|
|
return channel.sendRequest(request).then((Response response) {
|
|
expect(response.id, equals('my28'));
|
|
expect(response.error, isNull);
|
|
});
|
|
}
|
|
|
|
Future test_slowEcho_cancelled() async {
|
|
server.handlers = [
|
|
ServerDomainHandler(server),
|
|
EchoHandler(server),
|
|
];
|
|
// Send the normal request.
|
|
var responseFuture = channel.sendRequest(Request('my22', 'slowEcho'));
|
|
// Send a cancellation for it for waiting for it to complete.
|
|
channel.sendRequest(
|
|
Request(
|
|
'my23',
|
|
'server.cancelRequest',
|
|
{'id': 'my22'},
|
|
),
|
|
);
|
|
var response = await responseFuture;
|
|
expect(response.id, equals('my22'));
|
|
expect(response.error, isNull);
|
|
expect(response.result!['cancelled'], isTrue);
|
|
}
|
|
|
|
Future test_slowEcho_notCancelled() {
|
|
server.handlers = [EchoHandler(server)];
|
|
var request = Request('my22', 'slowEcho');
|
|
return channel.sendRequest(request).then((Response response) {
|
|
expect(response.id, equals('my22'));
|
|
expect(response.error, isNull);
|
|
expect(response.result!['cancelled'], isFalse);
|
|
});
|
|
}
|
|
|
|
Future test_unknownRequest() {
|
|
server.handlers = [EchoHandler(server)];
|
|
var request = Request('my22', 'randomRequest');
|
|
return channel.sendRequest(request).then((Response response) {
|
|
expect(response.id, equals('my22'));
|
|
expect(response.error, isNotNull);
|
|
});
|
|
}
|
|
}
|
|
|
|
class EchoHandler implements RequestHandler {
|
|
final AnalysisServer server;
|
|
|
|
EchoHandler(this.server);
|
|
|
|
@override
|
|
Response? handleRequest(
|
|
Request request, CancellationToken cancellationToken) {
|
|
if (request.method == 'echo') {
|
|
return Response(request.id, result: {'echo': true});
|
|
} else if (request.method == 'slowEcho') {
|
|
_slowEcho(request, cancellationToken);
|
|
return Response.DELAYED_RESPONSE;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
void _slowEcho(Request request, CancellationToken cancellationToken) async {
|
|
for (var i = 0; i < 100; i++) {
|
|
if (cancellationToken.isCancellationRequested) {
|
|
server.sendResponse(Response(request.id, result: {'cancelled': true}));
|
|
}
|
|
await Future.delayed(const Duration(milliseconds: 10));
|
|
}
|
|
server.sendResponse(Response(request.id, result: {'cancelled': false}));
|
|
}
|
|
}
|