| // 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: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. |
| MockSdk(resourceProvider: resourceProvider); |
| server = AnalysisServer( |
| channel, |
| resourceProvider, |
| AnalysisServerOptions(), |
| DartSdkManager(convertPath('/sdk')), |
| CrashReportingAttachmentsBuilder.empty, |
| InstrumentationService.NULL_SERVICE); |
| } |
| |
| Future test_echo() { |
| server.handlers = [EchoHandler()]; |
| 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_unknownRequest() { |
| server.handlers = [EchoHandler()]; |
| 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 { |
| @override |
| Response? handleRequest(Request request) { |
| if (request.method == 'echo') { |
| return Response(request.id, result: {'echo': true}); |
| } |
| return null; |
| } |
| } |