Skip to content

Commit 549a785

Browse files
DanTupCommit Queue
authored andcommitted
[analysis_server] Add a shared test interface to simplify sharing tests between LSP, LSP-over-Legacy
This is refactor extracted from an upcoming change (to make property editor tests run against both servers) to make that change smaller and easier to review. There are some existing shared tests that run for both LSP and Legacy servers, but they currently do not touch much server API (one is for DTD and one tests reverse-requests). Migrating other tests (such as EditableArguments) requires some additional API be the same between the different test/server base classes. This change adds an `abstract interface class SharedTestInterface` to serve as a common interface for methods that shared tests need to use that have different implementations between LSP and Legacy. For example, updating the overlays in an LSP-over-Legacy test needs to use the Legacy APIs for updating the overlay and not the LSP ones (so we can't just use the LSP methods like we would for calling something like getHover for LSP-over-Legacy). It also: - adds some new futures to the LSP test base to match the behaviour of the legacy one (wait for in-progress analysis) - replaces the shared mixins with real base classes that implement the shared interface (for ex. `abstract class SharedLspOverLegacyTest extends LspOverLegacyTest implements SharedTestInterface`) to make it easier to create shared tests - renames `sendLspRequest` to `sendLspRequestToClient` to make it clearer what direction this method is for Change-Id: I070c2c005b11b9afd8a87aa22b04972a9dde2320 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/404680 Reviewed-by: Phil Quitslund <[email protected]> Commit-Queue: Brian Wilkerson <[email protected]> Reviewed-by: Brian Wilkerson <[email protected]> Commit-Queue: Phil Quitslund <[email protected]>
1 parent eddf844 commit 549a785

File tree

6 files changed

+165
-24
lines changed

6 files changed

+165
-24
lines changed

pkg/analysis_server/test/lsp/server_abstract.dart

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import 'package:unified_analytics/unified_analytics.dart';
3333

3434
import '../mocks.dart';
3535
import '../mocks_lsp.dart';
36+
import '../shared/shared_test_interface.dart';
3637
import '../support/configuration_files.dart';
3738
import '../test_macros.dart';
3839
import 'change_verifier.dart';
@@ -248,6 +249,10 @@ abstract class AbstractLspAnalysisServerTest
248249
_previousContextBuilds = server.contextBuilds;
249250
}
250251

252+
Future<ResponseMessage> sendLspRequestToClient(Method method, Object params) {
253+
return server.sendRequest(method, params);
254+
}
255+
251256
@override
252257
Future<void> sendNotificationToServer(
253258
NotificationMessage notification,
@@ -882,9 +887,19 @@ mixin LspAnalysisServerTestMixin on LspRequestHelpersMixin, LspEditHelpersMixin
882887
/// server.
883888
bool failTestOnErrorDiagnostic = true;
884889

890+
/// A completer for [initialAnalysis].
891+
final Completer<void> _initialAnalysisCompleter = Completer<void>();
892+
893+
/// A completer for [currentAnalysis].
894+
Completer<void> _currentAnalysisCompleter = Completer<void>()..complete();
895+
885896
/// [analysisOptionsPath] as a 'file:///' [Uri].
886897
Uri get analysisOptionsUri => pathContext.toUri(analysisOptionsPath);
887898

899+
/// A [Future] that completes when the current analysis completes (or is
900+
/// already completed if no analysis is in progress).
901+
Future<void> get currentAnalysis => _currentAnalysisCompleter.future;
902+
888903
/// A stream of [NotificationMessage]s from the server that may be errors.
889904
Stream<NotificationMessage> get errorNotificationsFromServer {
890905
return notificationsFromServer.where(_isErrorNotification);
@@ -895,8 +910,7 @@ mixin LspAnalysisServerTestMixin on LspRequestHelpersMixin, LspEditHelpersMixin
895910
serverCapabilities.experimental as Map<String, Object?>? ?? {};
896911

897912
/// A [Future] that completes with the first analysis after initialization.
898-
Future<void> get initialAnalysis =>
899-
initialized ? Future.value() : waitForAnalysisComplete();
913+
Future<void> get initialAnalysis => _initialAnalysisCompleter.future;
900914

901915
bool get initialized => _clientCapabilities != null;
902916

@@ -1152,6 +1166,16 @@ mixin LspAnalysisServerTestMixin on LspRequestHelpersMixin, LspEditHelpersMixin
11521166
notificationsFromServer.listen((notification) async {
11531167
if (notification.method == Method.progress) {
11541168
await _handleProgress(notification);
1169+
} else if (notification.method == CustomMethods.analyzerStatus) {
1170+
var params = AnalyzerStatusParams.fromJson(
1171+
notification.params as Map<String, Object?>,
1172+
);
1173+
1174+
if (params.isAnalyzing) {
1175+
_handleAnalysisBegin();
1176+
} else {
1177+
_handleAnalysisEnd();
1178+
}
11551179
}
11561180
});
11571181

@@ -1592,6 +1616,19 @@ mixin LspAnalysisServerTestMixin on LspRequestHelpersMixin, LspEditHelpersMixin
15921616
return outlineParams.outline;
15931617
}
15941618

1619+
void _handleAnalysisBegin() async {
1620+
assert(_currentAnalysisCompleter.isCompleted);
1621+
_currentAnalysisCompleter = Completer<void>();
1622+
}
1623+
1624+
void _handleAnalysisEnd() async {
1625+
if (!_initialAnalysisCompleter.isCompleted) {
1626+
_initialAnalysisCompleter.complete();
1627+
}
1628+
assert(!_currentAnalysisCompleter.isCompleted);
1629+
_currentAnalysisCompleter.complete();
1630+
}
1631+
15951632
Future<void> _handleProgress(NotificationMessage request) async {
15961633
var params = ProgressParams.fromJson(
15971634
request.params as Map<String, Object?>,
@@ -1607,6 +1644,15 @@ mixin LspAnalysisServerTestMixin on LspRequestHelpersMixin, LspEditHelpersMixin
16071644
if (WorkDoneProgressEnd.canParse(params.value, nullLspJsonReporter)) {
16081645
validProgressTokens.remove(params.token);
16091646
}
1647+
1648+
if (params.token == analyzingProgressToken) {
1649+
if (WorkDoneProgressBegin.canParse(params.value, nullLspJsonReporter)) {
1650+
_handleAnalysisBegin();
1651+
}
1652+
if (WorkDoneProgressEnd.canParse(params.value, nullLspJsonReporter)) {
1653+
_handleAnalysisEnd();
1654+
}
1655+
}
16101656
}
16111657

16121658
Future<void> _handleWorkDoneProgressCreate(RequestMessage request) async {
@@ -1642,16 +1688,24 @@ mixin LspAnalysisServerTestMixin on LspRequestHelpersMixin, LspEditHelpersMixin
16421688
}
16431689
}
16441690

1645-
/// A mixin that provides a common interface for [AbstractLspAnalysisServerTest] classes
1646-
/// to work with shared tests.
1647-
mixin SharedLspAnalysisServerTestMixin on AbstractLspAnalysisServerTest {
1691+
/// An [AbstractLspAnalysisServerTest] that provides an implementation of
1692+
/// [SharedTestInterface] to allow tests to be shared between server/test kinds.
1693+
abstract class SharedAbstractLspAnalysisServerTest
1694+
extends AbstractLspAnalysisServerTest
1695+
implements SharedTestInterface {
1696+
@override
16481697
String get testFilePath => join(projectFolderPath, 'lib', 'test.dart');
16491698

1699+
@override
1700+
Uri get testFileUri => toUri(testFilePath);
1701+
1702+
@override
16501703
void createFile(String path, String content) {
16511704
newFile(path, content);
16521705
}
16531706

1654-
Future<ResponseMessage> sendLspRequest(Method method, Object params) {
1655-
return server.sendRequest(method, params);
1707+
@override
1708+
Future<void> initializeServer() async {
1709+
await initialize();
16561710
}
16571711
}

pkg/analysis_server/test/lsp/workspace_apply_edit_test.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,15 @@ void main() {
1414
}
1515

1616
@reflectiveTest
17-
class WorkspaceApplyEditTest extends AbstractLspAnalysisServerTest
17+
class WorkspaceApplyEditTest extends SharedAbstractLspAnalysisServerTest
1818
with
19-
// Tests are defined in SharedLspAnalysisServerTestMixin because they
19+
// Tests are defined in SharedWorkspaceApplyEditTests because they
2020
// are shared and run for both LSP and Legacy servers.
21-
SharedLspAnalysisServerTestMixin,
2221
SharedWorkspaceApplyEditTests {
2322
@override
2423
Future<void> initializeServer() async {
2524
await initialize();
26-
await initialAnalysis;
25+
await currentAnalysis;
2726
}
2827

2928
@override

pkg/analysis_server/test/lsp_over_legacy/abstract_lsp_over_legacy.dart

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import '../lsp/change_verifier.dart';
2424
import '../lsp/request_helpers_mixin.dart';
2525
import '../lsp/server_abstract.dart';
2626
import '../services/completion/dart/text_expectations.dart';
27+
import '../shared/shared_test_interface.dart';
2728

2829
class EventsCollector {
2930
final ContextResolutionTest test;
@@ -258,12 +259,10 @@ abstract class LspOverLegacyTest extends PubPackageAnalysisServerTest
258259
var error = lspResponse.error;
259260
if (error != null) {
260261
throw error;
261-
} else if (T == Null) {
262+
} else {
262263
return lspResponse.result == null
263264
? null as T
264-
: throw 'Expected Null response but got ${lspResponse.result}';
265-
} else {
266-
return fromJson(lspResponse.result as R);
265+
: fromJson(lspResponse.result as R);
267266
}
268267
}
269268

@@ -311,6 +310,17 @@ abstract class LspOverLegacyTest extends PubPackageAnalysisServerTest
311310
}
312311
}
313312

313+
Future<void> removeOverlay(String filePath) {
314+
return handleSuccessfulRequest(
315+
AnalysisUpdateContentParams({
316+
convertPath(filePath): RemoveContentOverlay(),
317+
}).toRequest(
318+
'${_nextRequestId++}',
319+
clientUriConverter: server.uriConverter,
320+
),
321+
);
322+
}
323+
314324
/// Send the configured LSP client capabilities to the server in a
315325
/// `server.setClientCapabilities` request.
316326
Future<void> sendClientCapabilities() async {
@@ -369,15 +379,42 @@ abstract class LspOverLegacyTest extends PubPackageAnalysisServerTest
369379
}
370380
}
371381

372-
/// A mixin that provides a common interface for [LspOverLegacyTest] classes
373-
/// to work with shared tests.
374-
mixin SharedLspOverLegacyMixin on LspOverLegacyTest {
382+
/// A [LspOverLegacyTest] that provides an implementation of
383+
/// [SharedTestInterface] to allow tests to be shared between server/test kinds.
384+
abstract class SharedLspOverLegacyTest extends LspOverLegacyTest
385+
implements SharedTestInterface {
386+
// TODO(dantup): Support this for LSP-over-Legacy shared tests.
387+
var failTestOnErrorDiagnostic = false;
388+
389+
@override
390+
Future<void> get currentAnalysis => waitForTasksFinished();
391+
392+
@override
393+
Future<void> closeFile(Uri uri) async {
394+
await removeOverlay(fromUri(uri));
395+
}
396+
397+
@override
375398
void createFile(String path, String content) {
376399
newFile(path, content);
377400
}
378401

402+
@override
403+
Future<void> openFile(Uri uri, String content, {int version = 1}) async {
404+
await addOverlay(fromUri(uri), content);
405+
}
406+
407+
@override
408+
Future<void> replaceFile(int newVersion, Uri uri, String content) async {
409+
// For legacy, we can use addOverlay to replace the whole file.
410+
await addOverlay(fromUri(uri), content);
411+
}
412+
379413
/// Wraps an LSP request up and sends it from the server to the client.
380-
Future<ResponseMessage> sendLspRequest(Method method, Object params) async {
414+
Future<ResponseMessage> sendLspRequestToClient(
415+
Method method,
416+
Object params,
417+
) async {
381418
var id = server.nextServerRequestId++;
382419
// Round-trip through JSON to ensure everything becomes basic types and we
383420
// don't have instances of classes like `Either2<>` in the JSON.

pkg/analysis_server/test/lsp_over_legacy/workspace_apply_edit_test.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@ void main() {
1414
}
1515

1616
@reflectiveTest
17-
class WorkspaceApplyEditTest extends LspOverLegacyTest
17+
class WorkspaceApplyEditTest extends SharedLspOverLegacyTest
1818
with
19-
// Tests are defined in SharedLspAnalysisServerTestMixin because they
19+
// Tests are defined in SharedWorkspaceApplyEditTests because they
2020
// are shared and run for both LSP and Legacy servers.
21-
SharedLspOverLegacyMixin,
2221
SharedWorkspaceApplyEditTests {
2322
@override
2423
Future<void> initializeServer() async {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// A common interface that can be implemented by a set of base classes to
6+
/// allow tests to be written to run in different configurations
7+
/// (LSP and LSP-over-Legacy, and in-process and out-of-process).
8+
///
9+
/// Implementations should use the appropriate APIs for the given server, for
10+
/// example a test running against LSP will send a `textDocument/didOpen`
11+
/// notification from [openFile] whereas when using the legacy server (even for
12+
/// LSP-over-Legacy tests) will send an `analysis.updateContent` request.
13+
abstract interface class SharedTestInterface {
14+
/// A future that completes when the current analysis completes.
15+
///
16+
/// If there is no analysis in progress, completes immediately.
17+
Future<void> get currentAnalysis;
18+
19+
/// Sets whether the test should fail if error diagnostics are generated.
20+
///
21+
/// This is used to avoid accidentally including invalid code in tests but can
22+
/// be overridden for tests that are deliberately testing invalid code.
23+
set failTestOnErrorDiagnostic(bool value);
24+
25+
/// Gets the full normalized file path of a file named "test.dart" in the test
26+
/// project.
27+
String get testFilePath;
28+
29+
/// Gets a file:/// URI for [testFilePath];
30+
Uri get testFileUri => Uri.file(testFilePath);
31+
32+
/// Tells the server that file with [uri] has been closed and any overlay
33+
/// should be removed.
34+
Future<void> closeFile(Uri uri);
35+
36+
/// Creates a file at [filePath] with the given [content].
37+
void createFile(String filePath, String content);
38+
39+
/// Performs standard initialization of the server, including starting
40+
/// the server (an external process for integration tests) and sending any
41+
/// initialization/analysis roots, and waiting for initial analysis to
42+
/// complete.
43+
Future<void> initializeServer();
44+
45+
/// Tells the server that the file with [uri] has been opened and has the
46+
/// given [content].
47+
Future<void> openFile(Uri uri, String content, {int version = 1});
48+
49+
/// Tells the server that the file with [uri] has had it's content replaced
50+
/// with [content].
51+
Future<void> replaceFile(int newVersion, Uri uri, String content);
52+
}

pkg/analysis_server/test/shared/shared_workspace_apply_edit_tests.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ mixin SharedWorkspaceApplyEditTests
3131

3232
/// Overridden by test subclasses to send LSP requests from the server to
3333
/// the client.
34-
Future<ResponseMessage> sendLspRequest(Method method, Object params);
34+
Future<ResponseMessage> sendLspRequestToClient(Method method, Object params);
3535

3636
test_applyEdit_existingFile() async {
3737
var code = TestCode.parse('''
@@ -147,7 +147,7 @@ inserted<<<<<<<<<<
147147
ApplyWorkspaceEditResult? receivedApplyEditResult;
148148

149149
var verifier = await executeForEdits(() async {
150-
var result = await sendLspRequest(
150+
var result = await sendLspRequestToClient(
151151
Method.workspace_applyEdit,
152152
ApplyWorkspaceEditParams(edit: workspaceEdit),
153153
);

0 commit comments

Comments
 (0)