e20704c3b0
Move PackageConfigFileBuilder into the analyzer_testing public API and deprecate the copy exposed from package:analyzer. The builder is only used by test infrastructure, so keeping it in analyzer_testing makes the ownership clearer and avoids exposing test-only utilities from analyzer. Update the builder API to accept a rootFolder instead of a rootPath. This lets callers pass the resource-provider folder directly, so the generated rootUri is derived from the same file-system abstraction that created the test files. This avoids accidentally passing POSIX paths where resource provider paths are required, such as on Windows. Update existing test utilities and callers to import the new library and pass Folder objects. Remove the production analysis server dependency on the builder by emitting the temporary plugin package config JSON directly. Change-Id: I46b14710626e0d6d5884afcdc5a05b23077acfc9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499081 Reviewed-by: Paul Berry <paulberry@google.com> Reviewed-by: Johnni Winther <johnniwinther@google.com> Reviewed-by: Samuel Rawlins <srawlins@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
302 lines
10 KiB
Dart
302 lines
10 KiB
Dart
// Copyright (c) 2015, 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:analyzer/file_system/file_system.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_common.dart';
|
|
import 'package:analyzer_testing/package_config_file_builder.dart';
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
|
|
import '../analysis_server_base.dart';
|
|
import '../mocks.dart';
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(UpdateContentTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class UpdateContentTest extends PubPackageAnalysisServerTest {
|
|
Map<File, List<String>> filesErrors = {};
|
|
int serverErrorCount = 0;
|
|
int navigationCount = 0;
|
|
|
|
@override
|
|
void processNotification(Notification notification) {
|
|
if (notification.event == analysisNotificationErrors) {
|
|
var decoded = AnalysisErrorsParams.fromNotification(
|
|
notification,
|
|
clientUriConverter: server.uriConverter,
|
|
);
|
|
String format(AnalysisError e) => '${e.location.startLine}: ${e.message}';
|
|
filesErrors[getFile(decoded.file)] = decoded.errors.map(format).toList();
|
|
}
|
|
if (notification.event == analysisNotificationNavigation) {
|
|
navigationCount++;
|
|
}
|
|
if (notification.event == serverNotificationError) {
|
|
serverErrorCount++;
|
|
}
|
|
}
|
|
|
|
void test_illegal_ChangeContentOverlay() async {
|
|
// It should be illegal to send a ChangeContentOverlay for a file that
|
|
// doesn't have an overlay yet.
|
|
addTestFile('');
|
|
await setRoots(included: [workspaceRootPath], excluded: []);
|
|
var response = await handleRequest(
|
|
AnalysisUpdateContentParams({
|
|
testFile.path: ChangeContentOverlay([SourceEdit(0, 0, '')]),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
assertResponseFailure(
|
|
response,
|
|
requestId: '0',
|
|
errorCode: RequestErrorCode.INVALID_OVERLAY_CHANGE,
|
|
);
|
|
}
|
|
|
|
Future<void> test_invalidFilePathFormat_notAbsolute() async {
|
|
var response = await handleRequest(
|
|
AnalysisUpdateContentParams({
|
|
'test.dart': AddContentOverlay(''),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
expect(
|
|
response,
|
|
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
|
|
);
|
|
}
|
|
|
|
Future<void> test_invalidFilePathFormat_notNormalized() async {
|
|
var response = await handleRequest(
|
|
AnalysisUpdateContentParams({
|
|
convertPath('/foo/../bar/test.dart'): AddContentOverlay(''),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
expect(
|
|
response,
|
|
isResponseFailure('0', RequestErrorCode.INVALID_FILE_PATH_FORMAT),
|
|
);
|
|
}
|
|
|
|
Future<void> test_multiple_contexts() async {
|
|
writePackageConfig(
|
|
workspaceRootPath,
|
|
config: PackageConfigFileBuilder()
|
|
..add(name: 'aaa', rootFolder: getFolder('$workspaceRootPath/aaa')),
|
|
);
|
|
|
|
var aaa = newFile('$workspaceRootPath/aaa/lib/aaa.dart', r'''
|
|
void f(int _) {}
|
|
''');
|
|
|
|
var foo = newFile('$workspaceRootPath/foo/lib/foo.dart', r'''
|
|
import 'package:aaa/aaa.dart';
|
|
void g() {
|
|
f();
|
|
}
|
|
''');
|
|
|
|
var bar = newFile('$workspaceRootPath/bar/lib/bar.dart', r'''
|
|
import 'package:aaa/aaa.dart';
|
|
void g() {
|
|
f();
|
|
}
|
|
''');
|
|
|
|
await setRoots(included: [foo.parent.path, bar.parent.path], excluded: []);
|
|
|
|
{
|
|
await waitForTasksFinished();
|
|
// Files foo.dart and bar.dart should both have errors, since they both
|
|
// call f() with the wrong number of arguments.
|
|
expect(filesErrors[foo], hasLength(1));
|
|
expect(filesErrors[bar], hasLength(1));
|
|
// Overlay the content of baz.dart to eliminate the errors.
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
aaa.path: AddContentOverlay('void f() {}'),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
}
|
|
|
|
{
|
|
await waitForTasksFinished();
|
|
// The overlay should have been propagated to both contexts, causing both
|
|
// foo.dart and bar.dart to be reanalyzed and found to be free of errors.
|
|
expect(filesErrors[foo], isEmpty);
|
|
expect(filesErrors[bar], isEmpty);
|
|
}
|
|
}
|
|
|
|
@failingTest
|
|
Future<void> test_overlay_addPreviouslyImported() async {
|
|
// The list of errors doesn't include errors for '/project/target.dart'.
|
|
var project = newFolder('/project');
|
|
await setRoots(included: [project.path], excluded: []);
|
|
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
'/project/main.dart': AddContentOverlay('import "target.dart";'),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
expect(filesErrors, {
|
|
'/project/main.dart': ["1: Target of URI doesn't exist: 'target.dart'."],
|
|
'/project/target.dart': [],
|
|
});
|
|
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
'/project/target.dart': AddContentOverlay('import "none.dart";'),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
expect(filesErrors, {
|
|
'/project/main.dart': ['1: Unused import.'],
|
|
'/project/target.dart': ["1: Target of URI doesn't exist: 'none.dart'."],
|
|
'/project/none.dart': [],
|
|
});
|
|
}
|
|
|
|
Future<void> test_overlayOnly() async {
|
|
var a = newFile('$testPackageLibPath/a.dart', '');
|
|
var b = getFile('$testPackageLibPath/b.dart');
|
|
|
|
await setRoots(included: [workspaceRootPath], excluded: []);
|
|
await waitForTasksFinished();
|
|
expect(filesErrors[a], isEmpty);
|
|
expect(filesErrors[b], isNull);
|
|
|
|
// Add `b.dart` overlay, analyzed.
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
b.path: AddContentOverlay(''),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
expect(filesErrors[a], isEmpty);
|
|
expect(filesErrors[b], isEmpty);
|
|
|
|
// Add `b.dart` overlay, analyzed.
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
b.path: RemoveContentOverlay(),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
expect(filesErrors[a], isEmpty);
|
|
// TODO(scheglov): We should get "flush" notification.
|
|
// expect(filesErrors[b], isNull);
|
|
}
|
|
|
|
Future<void> test_sendNoticesAfterNopChange() async {
|
|
// The errors are empty on the last line.
|
|
addTestFile('');
|
|
await setRoots(included: [workspaceRootPath], excluded: []);
|
|
await waitForTasksFinished();
|
|
// add an overlay
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
testFile.path: AddContentOverlay('void f() {} void f() {}'),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
// clear errors and make a no-op change
|
|
filesErrors.clear();
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
testFile.path: ChangeContentOverlay([SourceEdit(5, 1, 'f')]),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
// errors should have been resent
|
|
expect(filesErrors, isNotEmpty);
|
|
}
|
|
|
|
Future<void> test_sendNoticesAfterNopChange_flushedUnit() async {
|
|
// The list of errors is empty on the last line.
|
|
addTestFile('');
|
|
await setRoots(included: [workspaceRootPath], excluded: []);
|
|
await waitForTasksFinished();
|
|
// add an overlay
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
testFile.path: AddContentOverlay('void f() {} void f() {}'),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
// clear errors and make a no-op change
|
|
filesErrors.clear();
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams({
|
|
testFile.path: ChangeContentOverlay([SourceEdit(5, 1, 'f')]),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
await waitForTasksFinished();
|
|
// errors should have been resent
|
|
expect(filesErrors, isNotEmpty);
|
|
}
|
|
|
|
Future<void> test_sentToPlugins() async {
|
|
var filePath = convertPath('$testPackageLibPath/a.dart');
|
|
var fileContent = 'import "none.dart";';
|
|
//
|
|
// Add
|
|
//
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams(<String, Object>{
|
|
filePath: AddContentOverlay(fileContent),
|
|
}).toRequest('0', clientUriConverter: server.uriConverter),
|
|
);
|
|
var params = pluginManager.analysisUpdateContentParams!;
|
|
var files = params.files;
|
|
expect(files, hasLength(1));
|
|
var overlay = files[filePath];
|
|
expect(overlay, const TypeMatcher<AddContentOverlay>());
|
|
var addOverlay = overlay as AddContentOverlay;
|
|
expect(addOverlay.content, fileContent);
|
|
//
|
|
// Change
|
|
//
|
|
pluginManager.analysisUpdateContentParams = null;
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams(<String, Object>{
|
|
filePath: ChangeContentOverlay(<SourceEdit>[
|
|
SourceEdit(8, 1, "'"),
|
|
SourceEdit(18, 1, "'"),
|
|
]),
|
|
}).toRequest('1', clientUriConverter: server.uriConverter),
|
|
);
|
|
params = pluginManager.analysisUpdateContentParams!;
|
|
expect(params, isNotNull);
|
|
files = params.files;
|
|
expect(files, hasLength(1));
|
|
overlay = files[filePath];
|
|
expect(overlay, const TypeMatcher<ChangeContentOverlay>());
|
|
var changeOverlay = overlay as ChangeContentOverlay;
|
|
expect(changeOverlay.edits, hasLength(2));
|
|
//
|
|
// Remove
|
|
//
|
|
pluginManager.analysisUpdateContentParams = null;
|
|
await handleSuccessfulRequest(
|
|
AnalysisUpdateContentParams(<String, Object>{
|
|
filePath: RemoveContentOverlay(),
|
|
}).toRequest('2', clientUriConverter: server.uriConverter),
|
|
);
|
|
params = pluginManager.analysisUpdateContentParams!;
|
|
expect(params, isNotNull);
|
|
files = params.files;
|
|
expect(files, hasLength(1));
|
|
overlay = files[filePath];
|
|
expect(overlay, const TypeMatcher<RemoveContentOverlay>());
|
|
}
|
|
}
|