Files
sdk/pkg/analysis_server/test/analysis_server_test.dart
T
Brian Wilkerson 9f97ab9602 Restructure analytics support
I wanted to be able to plug in different kinds of analytics reporters,
and that's still a goal, but with the desire to make it possible for
users to see what data we are collecting I needed to change the level
at which reporters are plugged in.

This CL doesn't change the behavior of the code, but it does split the
classes out into separate files and collapse the AnalyticsManager class
and its subclasses into a single class, requiring that different
reporters be implemented by implementing the Analytics class instead.

Change-Id: Ia98ff225f97ae747d89c61fe98f520ce0b5e9961
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/249943
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
2022-06-27 21:19:32 +00:00

309 lines
11 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/analytics/analytics_manager.dart';
import 'package:analysis_server/src/analytics/noop_analytics.dart';
import 'package:analysis_server/src/server/crash_reporting_attachments.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/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/package_config_file_builder.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 {
@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 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', 'import "../bar/bar.dart";');
var bar = newFile('/bar/bar.dart', 'library bar;');
await 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),
AnalyticsManager(NoopAnalytics()),
CrashReportingAttachmentsBuilder.empty,
InstrumentationService.NULL_SERVICE);
}
/// 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 test_concurrentContextRebuilds() async {
// Subscribe to STATUS so we'll know when analysis is done.
server.serverServices = {ServerService.STATUS};
final projectRoot = convertPath('/foo');
final projectTestFile = convertPath('/foo/test.dart');
final 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', '');
final config = PackageConfigFileBuilder();
writePackageConfig(projectPackageConfigFile, config);
// Track diagnostics that arrive.
final errorsByFile = <String, List<AnalysisError>>{};
channel.notifications
.where((notification) => notification.event == 'analysis.errors')
.listen((notification) {
final params = AnalysisErrorsParams.fromNotification(notification);
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);
// Allow the server to catch up with everything.
await pumpEventQueue(times: 5000);
await server.onAnalysisComplete;
// Expect both errors are gone.
expect(await getUriNotExistErrors(), hasLength(0));
}
Future 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 == 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');
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 == 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', 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 == ANALYSIS_NOTIFICATION_NAVIGATION;
}), 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 == ANALYSIS_NOTIFICATION_NAVIGATION;
}), isTrue);
}
Future test_shutdown() {
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() {
var request = Request('my22', 'randomRequest');
return channel.sendRequest(request).then((Response response) {
expect(response.id, equals('my22'));
expect(response.error, isNotNull);
});
}
void writePackageConfig(String path, PackageConfigFileBuilder config) {
newFile(path, config.toContent(toUriStr: toUriStr));
}
/// 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) {
final packagePath = '/packages/$name';
final file = newFile('$packagePath/lib/$name.dart', content);
return file.parent;
}
}