blob: 7f2d470d3e8664b1336f2f92135197cd0f481eb2 [file] [log] [blame]
// 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 'dart:async';
import 'package:analysis_server/lsp_protocol/protocol.dart' as lsp;
import 'package:analysis_server/protocol/protocol.dart';
import 'package:analysis_server/protocol/protocol_constants.dart';
import 'package:analysis_server/protocol/protocol_generated.dart'
hide MessageType;
import 'package:analysis_server/src/analysis_server.dart' show MessageType;
import 'package:analysis_server/src/services/user_prompts/dart_fix_prompt_manager.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'analysis_server_base.dart';
import 'constants.dart';
import 'mocks.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(ServerDomainTest);
defineReflectiveTests(ServerDartFixPromptTest);
});
}
/// Checks server interacts with [DartFixPromptManager] correctly.
///
/// Tests for [DartFixPromptManager]'s behaviour are in
/// test/services/user_prompts/dart_fix_prompt_manager_test.dart.
@reflectiveTest
class ServerDartFixPromptTest extends PubPackageAnalysisServerTest {
late TestDartFixPromptManager promptManager;
@override
DartFixPromptManager? get dartFixPromptManager => promptManager;
@override
void setUp() {
promptManager = TestDartFixPromptManager();
super.setUp();
}
Future<void> test_trigger_afterInitialAnalysis() async {
await setRoots(included: [workspaceRootPath], excluded: []);
await pumpEventQueue(times: 5000);
expect(promptManager.checksTriggered, 1);
}
Future<void> test_trigger_afterPackageConfigChange() async {
// Ensure there's a file to analyze otherwise writing the package_config
// won't trigger any additional analysis.
newFile('$testPackageLibPath/test.dart', 'void f() {}');
// Set up and let initial analysis complete.
await setRoots(included: [workspaceRootPath], excluded: []);
await pumpEventQueue(times: 5000);
expect(promptManager.checksTriggered, 1);
// Expect that writing package config attempts to trigger another check.
writeTestPackageConfig();
await pumpEventQueue(times: 5000);
expect(promptManager.checksTriggered, 2);
}
}
@reflectiveTest
class ServerDomainTest extends PubPackageAnalysisServerTest {
Future<void> test_getVersion() async {
var request = ServerGetVersionParams().toRequest(
'0',
clientUriConverter: server.uriConverter,
);
var response = await handleSuccessfulRequest(request);
expect(
response.toJson(),
equals({
Response.ID: '0',
Response.RESULT: {VERSION: PROTOCOL_VERSION},
}),
);
}
Future<void> test_openUrl() async {
server.clientCapabilities.requests = ['openUrlRequest'];
// Send the request.
var uri = toUri('https://dart.dev');
var responseFuture = server.openUriNotificationSender!.call(uri);
expect(serverChannel.serverRequestsSent, hasLength(1));
// Simulate the response.
var request = serverChannel.serverRequestsSent[0];
serverChannel.simulateResponseFromClient(
ServerOpenUrlRequestResult().toResponse(
request.id,
clientUriConverter: server.uriConverter,
),
);
await responseFuture;
}
Future<void> test_setClientCapabilities_lspCapabilities() async {
// Test an arbitrary set of capabilities.
var capabilities = lsp.ClientCapabilities(
textDocument: lsp.TextDocumentClientCapabilities(
hover: lsp.HoverClientCapabilities(
contentFormat: [lsp.MarkupKind.PlainText],
),
),
workspace: lsp.WorkspaceClientCapabilities(
applyEdit: true,
workspaceEdit: lsp.WorkspaceEditClientCapabilities(
documentChanges: true,
resourceOperations: [lsp.ResourceOperationKind.Create],
),
),
);
var request = ServerSetClientCapabilitiesParams(
[],
lspCapabilities: capabilities.toJson(),
).toRequest('1', clientUriConverter: server.uriConverter);
await handleSuccessfulRequest(request);
var effectiveCapabilities = server.editorClientCapabilities;
expect(
effectiveCapabilities.hoverContentFormats,
equals([lsp.MarkupKind.PlainText]),
);
expect(effectiveCapabilities.applyEdit, isTrue);
expect(effectiveCapabilities.documentChanges, isTrue);
expect(effectiveCapabilities.createResourceOperations, isTrue);
}
Future<void> test_setClientCapabilities_lspCapabilities_invalid() async {
var request = ServerSetClientCapabilitiesParams(
[],
lspCapabilities: {
'textDocument': 1, // Not valid
},
).toRequest('1', clientUriConverter: server.uriConverter);
var response = await handleRequest(request);
expect(
response,
isResponseFailure('1', RequestErrorCode.INVALID_PARAMETER),
);
expect(
response.error!.message,
"The 'lspCapabilities' parameter was invalid:"
' textDocument must be of type TextDocumentClientCapabilities',
);
}
Future<void> test_setClientCapabilities_requests() async {
var requestId = -1;
Future<void> setCapabilities({
required bool openUrlRequest,
required bool showMessageRequest,
}) async {
var requests = [
if (openUrlRequest) 'openUrlRequest',
if (showMessageRequest) 'showMessageRequest',
];
if (requestId >= 0) {
// This is a bit of a kludge, but the first time this function is called
// we won't set the request, we'll just test the default state.
var request = ServerSetClientCapabilitiesParams(requests).toRequest(
requestId.toString(),
clientUriConverter: server.uriConverter,
);
await handleSuccessfulRequest(request);
}
requestId++;
expect(server.clientCapabilities.requests, requests);
expect(
server.openUriNotificationSender,
openUrlRequest ? isNotNull : isNull,
);
expect(server.userPromptSender, showMessageRequest ? isNotNull : isNull);
}
await setCapabilities(openUrlRequest: false, showMessageRequest: false);
await setCapabilities(openUrlRequest: true, showMessageRequest: false);
await setCapabilities(openUrlRequest: true, showMessageRequest: true);
await setCapabilities(openUrlRequest: false, showMessageRequest: true);
await setCapabilities(openUrlRequest: false, showMessageRequest: false);
}
/// Verify that the server handles URIs once we've enabled the supportsUris
/// client capability.
Future<void>
test_setClientCapabilities_supportsUris_clientToServer_request() async {
// Tell the server we support URIs.
await handleSuccessfulRequest(
ServerSetClientCapabilitiesParams(
[],
supportsUris: true,
).toRequest('-1', clientUriConverter: server.uriConverter),
);
// Set the roots using a URI. Since the helper methods will to through
// toJson() (which will convert paths to URIs) we need to pass the JSON
// manually here.
await handleSuccessfulRequest(
Request('1', 'analysis.setAnalysisRoots', {
'included': [toUri(workspaceRootPath).toString()],
'excluded': [],
}),
);
await pumpEventQueue(times: 5000);
// Ensure the roots were recorded correctly.
expect(server.contextManager.includedPaths, [
convertPath(workspaceRootPath),
]);
}
Future<void> test_setClientCapabilities_supportsUris_defaults() async {
// Before request.
expect(server.clientCapabilities.supportsUris, isNull);
expect(server.uriConverter.supportedNonFileSchemes, isEmpty);
// If not supplied.
await handleSuccessfulRequest(
ServerSetClientCapabilitiesParams(
[],
).toRequest('-1', clientUriConverter: server.uriConverter),
);
expect(server.clientCapabilities.supportsUris, isNull);
expect(server.uriConverter.supportedNonFileSchemes, isEmpty);
// If set explicitly to false.
await handleSuccessfulRequest(
ServerSetClientCapabilitiesParams(
[],
supportsUris: false,
).toRequest('-1', clientUriConverter: server.uriConverter),
);
expect(server.clientCapabilities.supportsUris, isFalse);
expect(server.uriConverter.supportedNonFileSchemes, isEmpty);
}
Future<void>
test_setClientCapabilities_supportsUris_false_rejectsUris() async {
// Explicitly tell the server we do not support URIs.
await handleSuccessfulRequest(
ServerSetClientCapabilitiesParams(
[],
supportsUris: false,
).toRequest('-1', clientUriConverter: server.uriConverter),
);
// Try to send a URI anyway.
var request = Request('1', 'analysis.setAnalysisRoots', {
'included': [toUri(workspaceRootPath).toString()],
'excluded': [],
});
var response = await handleRequest(request);
expect(
response,
isResponseFailure(request.id, RequestErrorCode.INVALID_FILE_PATH_FORMAT),
);
}
/// Verify that the server uses URIs in notifications once we've enabled the
/// supportsUris client capability.
Future<void>
test_setClientCapabilities_supportsUris_serverToClient_notification() async {
// Add a file with an error for testing.
var testFilePath = convertPath('$testPackageLibPath/test.dart');
var testFileUriString = toUri(testFilePath).toString();
newFile(testFilePath, 'broken');
// Tell the server we support URIs before analysis starts since we will
// verify the analysis.errors notification.
await handleSuccessfulRequest(
ServerSetClientCapabilitiesParams(
[],
supportsUris: true,
).toRequest('10', clientUriConverter: server.uriConverter),
);
await pumpEventQueue(times: 5000);
// Trigger analysis.
await setRoots(
// We can use paths here because toJson() will handle the conversion.
included: [workspaceRootPath],
excluded: [],
);
await pumpEventQueue(times: 5000);
// Verify the last error for this file was using a URI.
var lastErrorFile = serverChannel.notificationsReceived
.where((notification) => notification.event == 'analysis.errors')
.map((notification) => notification.params!['file'] as String)
.lastWhere((filePath) => filePath.endsWith('test.dart'));
expect(lastErrorFile, testFileUriString);
}
/// Verify that the server returns URIs once we've enabled the supportsUris
/// client capability.
Future<void>
test_setClientCapabilities_supportsUris_serverToClient_response() async {
// Add a file with an error for testing.
var testFilePath = convertPath('$testPackageLibPath/test.dart');
var testFileUriString = toUri(testFilePath).toString();
newFile(testFilePath, 'broken');
await setRoots(included: [workspaceRootPath], excluded: []);
await pumpEventQueue(times: 5000);
// Tell the server we support URIs.
await handleSuccessfulRequest(
ServerSetClientCapabilitiesParams(
[],
supportsUris: true,
).toRequest('10', clientUriConverter: server.uriConverter),
);
await pumpEventQueue(times: 5000);
// Send a GetErrors request. The response has nested FilePaths inside the
// AnalysisErrors so will confirm the server mapped correctly.
var response = await handleSuccessfulRequest(
Request('1', 'analysis.getErrors', {'file': testFileUriString}),
);
// Verify the error location was the expected URI.
expect(
(response.result as dynamic)['errors'][0]!['location']['file'],
testFileUriString,
);
}
Future<void>
test_setClientCapabilities_supportsUris_unspecified_rejectsUris() async {
// Do not tell the server we support URIs.
// Try to send a URI anyway.
var request = Request('1', 'analysis.setAnalysisRoots', {
'included': [toUri(workspaceRootPath).toString()],
'excluded': [],
});
var response = await handleRequest(request);
expect(
response,
isResponseFailure(request.id, RequestErrorCode.INVALID_FILE_PATH_FORMAT),
);
}
Future<void> test_setSubscriptions_invalidServiceName() async {
var request = Request('0', SERVER_REQUEST_SET_SUBSCRIPTIONS, {
SUBSCRIPTIONS: ['noSuchService'],
});
var response = await handleRequest(request);
expect(response, isResponseFailure('0'));
}
Future<void> test_setSubscriptions_success() async {
expect(server.serverServices, isEmpty);
// send request
var request = ServerSetSubscriptionsParams([
ServerService.STATUS,
]).toRequest('0', clientUriConverter: server.uriConverter);
await handleSuccessfulRequest(request);
// set of services has been changed
expect(server.serverServices, contains(ServerService.STATUS));
}
Future<void> test_showMessage() async {
server.clientCapabilities.requests = ['showMessageRequest'];
// Send the request.
var responseFuture = server.userPromptSender!(
MessageType.warning,
'message',
['a', 'b'],
);
expect(serverChannel.serverRequestsSent, hasLength(1));
// Simulate the response.
var request = serverChannel.serverRequestsSent[0];
serverChannel.simulateResponseFromClient(
ServerShowMessageRequestResult(
action: 'a',
).toResponse(request.id, clientUriConverter: server.uriConverter),
);
var response = await responseFuture;
expect(response, 'a');
}
Future<void> test_showMessage_nullResponse() async {
server.clientCapabilities.requests = ['showMessageRequest'];
// Send the request.
var responseFuture = server.userPromptSender!(
MessageType.warning,
'message',
['a', 'b'],
);
expect(serverChannel.serverRequestsSent, hasLength(1));
// Simulate the response.
var request = serverChannel.serverRequestsSent[0];
serverChannel.simulateResponseFromClient(
ServerShowMessageRequestResult().toResponse(
request.id,
clientUriConverter: server.uriConverter,
),
);
var response = await responseFuture;
expect(response, isNull);
}
Future<void> test_shutdown() async {
var request = ServerShutdownParams().toRequest(
'0',
clientUriConverter: server.uriConverter,
);
await handleSuccessfulRequest(request);
}
}
class TestDartFixPromptManager implements DartFixPromptManager {
var checksTriggered = 0;
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
@override
void triggerCheck() {
checksTriggered++;
}
}