Files
sdk/pkg/analysis_server/test/analysis_server_base.dart
T
Danny Tuppeny 6be105bb24 [analysis_server] Normalize test sources in base class
This adds code to normalize file contents in `PubPackageAnalysisServerTest.newFile()` so all tests using this base class normalize their source newlines by default (this ensures we run with \r\n on Windows, regardless of the git settings always using \n in this repo).

A flag `useLineEndingsForPlatform` allows option out of this, and any tests that currently fail in this mode set this in their setUp - with the exception of a few that were just trivial fixes.

This will make it easier to fix the remaining tests (by looking at code that does `useLineEndingsForPlatform = false`, deleting it, then fixing those tests).

See https://github.com/dart-lang/sdk/issues/60234

Change-Id: If05bc5f7fe3007151c1290831157b6a62d6bf651
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/449880
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
2025-09-16 19:37:19 -07:00

352 lines
11 KiB
Dart

// Copyright (c) 2022, 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 'dart:async';
import 'package:analysis_server/src/analytics/analytics_manager.dart';
import 'package:analysis_server/src/legacy_analysis_server.dart';
import 'package:analysis_server/src/protocol_server.dart';
import 'package:analysis_server/src/server/crash_reporting_attachments.dart';
import 'package:analysis_server/src/services/user_prompts/dart_fix_prompt_manager.dart';
import 'package:analysis_server/src/utilities/mocks.dart';
import 'package:analyzer/dart/analysis/analysis_options.dart' as analysis;
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/instrumentation/service.dart';
import 'package:analyzer/source/source_range.dart';
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
import 'package:analyzer/src/test_utilities/platform.dart';
import 'package:analyzer/src/test_utilities/test_code_format.dart';
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
import 'package:analyzer_testing/experiments/experiments.dart';
import 'package:analyzer_testing/mock_packages/mock_packages.dart';
import 'package:analyzer_testing/resource_provider_mixin.dart';
import 'package:analyzer_testing/utilities/extensions/resource_provider.dart';
import 'package:analyzer_testing/utilities/utilities.dart';
import 'package:meta/meta.dart';
import 'package:test/test.dart';
import 'package:unified_analytics/unified_analytics.dart';
import 'constants.dart';
import 'mocks.dart';
import 'support/configuration_files.dart';
import 'utils/message_scheduler_test_view.dart';
class BlazeWorkspaceAnalysisServerTest extends ContextResolutionTest {
String get myPackageLibPath => '$myPackageRootPath/lib';
String get myPackageRootPath => '$workspaceRootPath/dart/my';
Folder get workspaceRoot => getFolder(workspaceRootPath);
String get workspaceRootPath => '/workspace';
@override
void createDefaultFiles() {
newFile('$workspaceRootPath/${file_paths.blazeWorkspaceMarker}', '');
}
}
abstract class ContextResolutionTest with ResourceProviderMixin {
/// The byte store that is reused between tests. This allows reusing all
/// unlinked and linked summaries for SDK, so that tests run much faster.
/// However nothing is preserved between Dart VM runs, so changes to the
/// implementation are still fully verified.
static final MemoryByteStore _sharedByteStore = MemoryByteStore();
/// The next ID to use in a request to the server.
var nextRequestId = 0;
MemoryByteStore _byteStore = _sharedByteStore;
final TestPluginManager pluginManager = TestPluginManager();
late final MockServerChannel serverChannel;
MessageSchedulerTestView? testView;
late final LegacyAnalysisServer server;
DartFixPromptManager? dartFixPromptManager;
final List<GeneralAnalysisService> _analysisGeneralServices = [];
final Map<AnalysisService, List<String>> _analysisFileSubscriptions = {};
void Function(Notification)? notificationListener;
bool get retainDataForTesting => false;
Folder get sdkRoot => newFolder('/sdk');
Future<void> addGeneralAnalysisSubscription(
GeneralAnalysisService service,
) async {
_analysisGeneralServices.add(service);
await _setGeneralAnalysisSubscriptions();
}
void assertResponseFailure(
Response response, {
required String requestId,
required RequestErrorCode errorCode,
}) {
expect(response, isResponseFailure(requestId, errorCode));
}
void createDefaultFiles() {}
Future<Response> handleRequest(Request request) async {
return await serverChannel.simulateRequestFromClient(request);
}
/// Validates that the given [request] is handled successfully.
Future<Response> handleSuccessfulRequest(Request request) async {
var response = await handleRequest(request);
expect(response, isResponseSuccess(request.id));
return response;
}
void processNotification(Notification notification) {
notificationListener?.call(notification);
}
Future<void> removeGeneralAnalysisSubscription(
GeneralAnalysisService service,
) async {
_analysisGeneralServices.remove(service);
await _setGeneralAnalysisSubscriptions();
}
void setPriorityFiles(List<File> files) {
handleSuccessfulRequest(
AnalysisSetPriorityFilesParams(
files.map((e) => e.path).toList(),
).toRequest(
'${nextRequestId++}',
clientUriConverter: server.uriConverter,
),
);
}
Future<void> setPriorityFiles2(List<File> files) async {
await handleSuccessfulRequest(
AnalysisSetPriorityFilesParams(
files.map((e) => e.path).toList(),
).toRequest(
'${nextRequestId++}',
clientUriConverter: server.uriConverter,
),
);
}
Future<void> setRoots({
required List<String> included,
required List<String> excluded,
}) async {
var includedConverted = included.map(convertPath).toList();
var excludedConverted = excluded.map(convertPath).toList();
await handleSuccessfulRequest(
AnalysisSetAnalysisRootsParams(
includedConverted,
excludedConverted,
packageRoots: {},
).toRequest(
'${nextRequestId++}',
clientUriConverter: server.uriConverter,
),
);
}
@mustCallSuper
void setUp() {
serverChannel = MockServerChannel(printMessages: debugPrintCommunication);
createMockSdk(resourceProvider: resourceProvider, root: sdkRoot);
createDefaultFiles();
serverChannel.notifications.listen(processNotification);
testView = retainDataForTesting ? MessageSchedulerTestView() : null;
server = LegacyAnalysisServer(
serverChannel,
resourceProvider,
AnalysisServerOptions(),
DartSdkManager(sdkRoot.path),
AnalyticsManager(NoOpAnalytics()),
CrashReportingAttachmentsBuilder.empty,
InstrumentationService.NULL_SERVICE,
dartFixPromptManager: dartFixPromptManager,
providedByteStore: _byteStore,
pluginManager: pluginManager,
messageSchedulerListener: testView,
);
server.completionState.budgetDuration = const Duration(seconds: 30);
}
Future<void> tearDown() async {
await server.shutdown();
}
/// Returns a [Future] that completes when the server's analysis is complete.
Future<void> waitForTasksFinished() async {
await pumpEventQueue(times: 1 << 10);
await server.onAnalysisComplete;
}
Future<void> _setGeneralAnalysisSubscriptions() async {
await handleSuccessfulRequest(
AnalysisSetGeneralSubscriptionsParams(_analysisGeneralServices).toRequest(
'${nextRequestId++}',
clientUriConverter: server.uriConverter,
),
);
}
}
class PubPackageAnalysisServerTest extends ContextResolutionTest
with MockPackagesMixin, ConfigurationFilesMixin {
// TODO(scheglov): Consider turning it back into a getter.
late String testFilePath = resourceProvider.convertPath(
'$testPackageLibPath/test.dart',
);
late String pubspecFilePath = pathContext.normalize(
resourceProvider.convertPath('$testPackageRootPath/pubspec.yaml'),
);
late TestCode parsedTestCode;
final String testPackageName = 'test';
/// Whether to automatically normalize line endings for the current platform.
bool useLineEndingsForPlatform = true;
/// Return a list of the experiments that are to be enabled for tests in this
/// class, an empty list if there are no experiments that should be enabled.
List<String> get experiments => experimentsForTests;
/// The path that is not in [workspaceRootPath], contains external packages.
@override
String get packagesRootPath => resourceProvider.convertPath('/packages');
List<TestCodePosition> get parsedPositions => parsedTestCode.positions;
TestCodeRange get parsedRange => parsedTestCode.range;
List<TestCodeRange> get parsedRanges => parsedTestCode.ranges;
SourceRange get parsedSourceRange => parsedTestCode.range.sourceRange;
File get testFile => getFile(testFilePath);
analysis.AnalysisOptions get testFileAnalysisOptions {
var analysisDriver = server.getAnalysisDriver(testFile.path)!;
return analysisDriver.getAnalysisOptionsForFile(testFile);
}
String get testFileContent => testFile.readAsStringSync();
String get testPackageLibPath => '$testPackageRootPath/lib';
Folder get testPackageRoot => getFolder(testPackageRootPath);
@override
String get testPackageRootPath => '$workspaceRootPath/$testPackageName';
String get testPackageTestPath => '$testPackageRootPath/test';
Folder get workspaceRoot => getFolder(workspaceRootPath);
String get workspaceRootPath => '/home';
Future<void> addAnalysisSubscription(
AnalysisService service,
File file,
) async {
(_analysisFileSubscriptions[service] ??= []).add(file.path);
await handleSuccessfulRequest(
AnalysisSetSubscriptionsParams(_analysisFileSubscriptions).toRequest(
'${nextRequestId++}',
clientUriConverter: server.uriConverter,
),
);
}
// TODO(scheglov): rename
void addTestFile(String content) {
parsedTestCode = useLineEndingsForPlatform
? TestCode.parseNormalized(content)
: TestCode.parse(content);
newFile(testFilePath, parsedTestCode.code);
}
@override
void createDefaultFiles() {
writeTestPackageConfig();
writeTestPackagePubspecYamlFile('name: $testPackageName');
writeTestPackageAnalysisOptionsFile(
analysisOptionsContent(experiments: experiments),
);
}
/// Deletes the analysis options YAML file at [testPackageRootPath].
void deleteTestPackageAnalysisOptionsFile() {
var path = join(testPackageRootPath, file_paths.analysisOptionsYaml);
deleteFile(path);
}
/// Deletes the `package_config.json` file at [testPackageRootPath].
void deleteTestPackageConfigJsonFile() {
var filePath = join(
testPackageRootPath,
file_paths.dotDartTool,
file_paths.packageConfigJson,
);
deleteFile(filePath);
}
/// Returns the offset of [search] in [testFileContent].
/// Fails if not found.
// TODO(scheglov): Rename it.
int findOffset(String search) {
return offsetInFile(testFile, search);
}
void modifyTestFile(String content) {
modifyFile2(testFile, content);
}
@override
File newFile(String path, String content) {
if (useLineEndingsForPlatform) {
content = normalizeNewlinesForPlatform(content);
}
return super.newFile(path, content);
}
/// Returns the offset of [search] in [file].
/// Fails if not found.
int offsetInFile(File file, String search) {
var content = file.readAsStringSync();
var offset = content.indexOf(search);
expect(offset, isNot(-1));
return offset;
}
/// Call this method if the test needs to use the empty byte store, without
/// any information cached.
void useEmptyByteStore() {
_byteStore = MemoryByteStore();
}
void writeTestPackageAnalysisOptionsFile(String content) {
newAnalysisOptionsYamlFile(testPackageRootPath, content);
}
void writeTestPackagePubspecYamlFile(String content) {
newPubspecYamlFile(testPackageRootPath, content);
}
}