| // 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/analytics/analytics_manager.dart'; | |
| import 'package:analysis_server/src/legacy_analysis_server.dart'; | |
| import 'package:analysis_server/src/server/crash_reporting_attachments.dart'; | |
| import 'package:analysis_server/src/server/error_notifier.dart'; | |
| import 'package:analysis_server/src/session_logger/session_logger.dart'; | |
| import 'package:analysis_server/src/utilities/mocks.dart'; | |
| import 'package:analyzer/file_system/file_system.dart'; | |
| import 'package:analyzer/file_system/memory_file_system.dart'; | |
| import 'package:analyzer/src/generated/sdk.dart'; | |
| import 'package:analyzer/src/test_utilities/mock_sdk.dart'; | |
| import 'package:analyzer_plugin/protocol/protocol_common.dart'; | |
| import 'package:analyzer_testing/package_config_file_builder.dart'; | |
| import 'package:analyzer_testing/resource_provider_mixin.dart'; | |
| import 'package:test/test.dart'; | |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; | |
| import 'package:unified_analytics/unified_analytics.dart'; | |
| import 'constants.dart'; | |
| void main() { | |
| defineReflectiveSuite(() { | |
| defineReflectiveTests(AnalysisServerTest); | |
| }); | |
| } | |
| @reflectiveTest | |
| class AnalysisServerTest with ResourceProviderMixin { | |
| @override | |
| MemoryResourceProvider resourceProvider = MemoryResourceProvider( | |
| // Force the in-memory file watchers to be slowly initialized to emulate | |
| // the physical watchers (for test_concurrentContextRebuilds). | |
| delayWatcherInitialization: Duration(milliseconds: 1), | |
| ); | |
| late MockServerChannel channel; | |
| late ErrorNotifier errorNotifier; | |
| late LegacyAnalysisServer server; | |
| void setUp() { | |
| channel = MockServerChannel(printMessages: debugPrintCommunication); | |
| // Create an SDK in the mock file system. | |
| var sdkRoot = newFolder('/sdk'); | |
| createMockSdk(resourceProvider: resourceProvider, root: sdkRoot); | |
| errorNotifier = ErrorNotifier(); | |
| server = LegacyAnalysisServer( | |
| channel, | |
| resourceProvider, | |
| AnalysisServerOptions(), | |
| DartSdkManager(sdkRoot.path), | |
| AnalyticsManager(NoOpAnalytics()), | |
| CrashReportingAttachmentsBuilder.empty, | |
| errorNotifier, | |
| SessionLogger(), | |
| ); | |
| errorNotifier.server = server; | |
| } | |
| /// See https://github.com/dart-lang/sdk/issues/50496 | |
| Future<void> test_caching_mixin_superInvokedNames_setter_change() async { | |
| var lib = convertPath('/lib'); | |
| newFolder(lib); | |
| var foo = newFile('/lib/foo.dart', ''' | |
| class A { | |
| set foo(int _) {} | |
| } | |
| mixin M on A { | |
| void bar() { | |
| super.boo = 0; | |
| } | |
| } | |
| class X extends A with M {} | |
| '''); | |
| await server.setAnalysisRoots('0', [lib], []); | |
| await server.onAnalysisComplete; | |
| expect(server.statusAnalyzing, isFalse); | |
| channel.notificationsReceived.clear(); | |
| server.updateContent('0', { | |
| foo.path: AddContentOverlay(''' | |
| class A { | |
| set foo(int _) {} | |
| } | |
| mixin M on A { | |
| void bar() { | |
| super.foo = 0; | |
| } | |
| } | |
| class X extends A with M {} | |
| '''), | |
| }); | |
| await server.onAnalysisComplete; | |
| expect(server.statusAnalyzing, isFalse); | |
| var notifications = channel.notificationsReceived; | |
| expect(notifications, hasLength(1)); | |
| var notification = notifications.first; | |
| expect(notification.event, 'analysis.errors'); | |
| var params = notification.params!; | |
| var errors = params['errors'] as List<Map<String, Object?>>; | |
| expect(errors, isEmpty); | |
| } | |
| /// Test that modifying package_config again while a context rebuild is in | |
| /// progress does not get lost due to a gap between creating a file watcher | |
| /// and it raising events. | |
| /// https://github.com/Dart-Code/Dart-Code/issues/3438 | |
| Future<void> test_concurrentContextRebuilds() async { | |
| // Subscribe to STATUS so we'll know when analysis is done. | |
| server.serverServices = {ServerService.STATUS}; | |
| var projectRoot = convertPath('/foo'); | |
| var projectTestFile = convertPath('/foo/lib/test.dart'); | |
| var projectPackageConfigFile = convertPath( | |
| '/foo/.dart_tool/package_config.json', | |
| ); | |
| // Create a file that references two packages, which will we write to | |
| // package_config.json individually. | |
| newFolder(projectRoot); | |
| newFile(projectTestFile, r''' | |
| import "package:foo/foo.dart";' | |
| import "package:bar/bar.dart";' | |
| '''); | |
| // Ensure the packages and package_config exist. | |
| var fooLibFolder = _addSimplePackage('foo', ''); | |
| var barLibFolder = _addSimplePackage('bar', ''); | |
| var config = PackageConfigFileBuilder(); | |
| writePackageConfig2(projectPackageConfigFile, config); | |
| // Track diagnostics that arrive. | |
| var errorsByFile = <String, List<AnalysisError>>{}; | |
| channel.notifications | |
| .where((notification) => notification.event == 'analysis.errors') | |
| .listen((notification) { | |
| var params = AnalysisErrorsParams.fromNotification( | |
| notification, | |
| clientUriConverter: server.uriConverter, | |
| ); | |
| errorsByFile[params.file] = params.errors; | |
| }); | |
| /// Helper that waits for analysis then returns the relevant errors. | |
| Future<List<AnalysisError>> getUriNotExistErrors() async { | |
| await server.onAnalysisComplete; | |
| expect(server.statusAnalyzing, isFalse); | |
| return errorsByFile[projectTestFile]! | |
| .where((error) => error.code == 'uri_does_not_exist') | |
| .toList(); | |
| } | |
| // Set roots and expect 2 uri_does_not_exist errors. | |
| await server.setAnalysisRoots('0', [projectRoot], []); | |
| expect(await getUriNotExistErrors(), hasLength(2)); | |
| // Write both packages, in two events so that the first one will trigger | |
| // a rebuild. | |
| config.add(name: 'foo', rootFolder: fooLibFolder.parent); | |
| writePackageConfig2(projectPackageConfigFile, config); | |
| await pumpEventQueue(times: 1); // Allow server to begin processing. | |
| config.add(name: 'bar', rootFolder: barLibFolder.parent); | |
| writePackageConfig2(projectPackageConfigFile, config); | |
| // Eventually the errors are gone. | |
| while (true) { | |
| var errors = await getUriNotExistErrors(); | |
| if (errors.isEmpty) { | |
| break; | |
| } | |
| await pumpEventQueue(times: 5000); | |
| } | |
| } | |
| Future<void> test_errorNotification_errorNotifier() async { | |
| errorNotifier.logException(Exception('dummy exception')); | |
| var errors = channel.notificationsReceived.where( | |
| (notification) => notification.event == serverNotificationError, | |
| ); | |
| expect( | |
| errors.single.params![serverNotificationErrorMessage], | |
| contains('dummy exception'), | |
| ); | |
| } | |
| Future<void> test_errorNotification_sendNotification() async { | |
| server.sendServerErrorNotification( | |
| 'message', | |
| Exception('dummy exception'), | |
| null, | |
| ); | |
| var errors = channel.notificationsReceived.where( | |
| (notification) => notification.event == serverNotificationError, | |
| ); | |
| expect( | |
| errors.single.params![serverNotificationErrorMessage], | |
| contains('dummy exception'), | |
| ); | |
| } | |
| Future<void> test_serverStatusNotifications_hasFile() async { | |
| server.serverServices.add(ServerService.STATUS); | |
| newFile('/test/lib/a.dart', r''' | |
| class A {} | |
| '''); | |
| await 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 == serverNotificationStatus) { | |
| var params = ServerStatusParams.fromNotification( | |
| notification, | |
| clientUriConverter: server.uriConverter, | |
| ); | |
| 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, | |
| clientUriConverter: server.uriConverter, | |
| ); | |
| expect(params.analysis!.isAnalyzing, isFalse); | |
| } | |
| Future<void> test_serverStatusNotifications_noFiles() async { | |
| server.serverServices.add(ServerService.STATUS); | |
| newFolder('/test'); | |
| await 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 == serverNotificationStatus) { | |
| var params = ServerStatusParams.fromNotification( | |
| notification, | |
| clientUriConverter: server.uriConverter, | |
| ); | |
| 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, | |
| clientUriConverter: server.uriConverter, | |
| ); | |
| expect(params.analysis!.isAnalyzing, isFalse); | |
| } | |
| Future<void> | |
| test_setAnalysisSubscriptions_fileInIgnoredFolder_newOptions() async { | |
| var path = convertPath('/project/samples/sample.dart'); | |
| newFile(path, ''); | |
| newAnalysisOptionsYamlFile('/project', r''' | |
| analyzer: | |
| exclude: | |
| - 'samples/**' | |
| '''); | |
| await server.setAnalysisRoots('0', [convertPath('/project')], []); | |
| server.setAnalysisSubscriptions(<AnalysisService, Set<String>>{ | |
| AnalysisService.NAVIGATION: <String>{path}, | |
| }); | |
| // We respect subscriptions, even for excluded files. | |
| await pumpEventQueue(times: 5000); | |
| expect( | |
| channel.notificationsReceived.any((notification) { | |
| return notification.event == analysisNotificationNavigation; | |
| }), | |
| isTrue, | |
| ); | |
| } | |
| Future<void> | |
| test_setAnalysisSubscriptions_fileInIgnoredFolder_oldOptions() async { | |
| var path = convertPath('/project/samples/sample.dart'); | |
| newFile(path, ''); | |
| newAnalysisOptionsYamlFile('/project', r''' | |
| analyzer: | |
| exclude: | |
| - 'samples/**' | |
| '''); | |
| await server.setAnalysisRoots('0', [convertPath('/project')], []); | |
| server.setAnalysisSubscriptions(<AnalysisService, Set<String>>{ | |
| AnalysisService.NAVIGATION: <String>{path}, | |
| }); | |
| // We respect subscriptions, even for excluded files. | |
| await pumpEventQueue(times: 5000); | |
| expect( | |
| channel.notificationsReceived.any((notification) { | |
| return notification.event == analysisNotificationNavigation; | |
| }), | |
| isTrue, | |
| ); | |
| } | |
| Future<void> test_shutdown() { | |
| var request = Request('my28', serverRequestShutdown); | |
| return channel.simulateRequestFromClient(request).then((Response response) { | |
| expect(response.id, equals('my28')); | |
| expect(response.error, isNull); | |
| }); | |
| } | |
| Future<void> test_unknownRequest() { | |
| var request = Request('my22', 'randomRequest'); | |
| return channel.simulateRequestFromClient(request).then((Response response) { | |
| expect(response.id, equals('my22')); | |
| expect(response.error, isNotNull); | |
| }); | |
| } | |
| void writePackageConfig2(String path, PackageConfigFileBuilder config) { | |
| newFile(path, config.toContent()); | |
| } | |
| /// Creates a simple package named [name] with [content] in the file at | |
| /// `package:$name/$name.dart`. | |
| /// | |
| /// Returns a [Folder] that represents the packages `lib` folder. | |
| Folder _addSimplePackage(String name, String content) { | |
| var packagePath = '/packages/$name'; | |
| var file = newFile('$packagePath/lib/$name.dart', content); | |
| return file.parent; | |
| } | |
| } |