68d44b88c1
Change-Id: Ibe3ae38d2afe00a125ed43034625924d4e989755 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/461241 Commit-Queue: Samuel Rawlins <srawlins@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
367 lines
12 KiB
Dart
367 lines
12 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/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/utilities/package_config_file_builder.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.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();
|
|
writePackageConfig(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', rootPath: fooLibFolder.parent.path);
|
|
writePackageConfig(projectPackageConfigFile, config);
|
|
await pumpEventQueue(times: 1); // Allow server to begin processing.
|
|
config.add(name: 'bar', rootPath: barLibFolder.parent.path);
|
|
writePackageConfig(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 writePackageConfig(String path, PackageConfigFileBuilder config) {
|
|
newFile(path, config.toContent(pathContext: pathContext));
|
|
}
|
|
|
|
/// 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;
|
|
}
|
|
}
|