DAS plugins: Run plugin code zoned
In this CL, I run plugin code that responds to a request in an error
zone. The bulk of this change though is the tests that verify how we
handle asynchronous and synchronous thrown errors.
I separate some shared code from plugin_server_test into a shared lint
rules file and a base class. Then a second test is introduced with a
few error cases.
Change-Id: I4e252ae0d3bec0cf6625c0044681677fba3132bd
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/384140
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Konstantin Shcheglov <scheglov@google.com>
Commit-Queue: Samuel Rawlins <srawlins@google.com>
diff --git a/pkg/analysis_server_plugin/lib/src/plugin_server.dart b/pkg/analysis_server_plugin/lib/src/plugin_server.dart
index 9b78633..9df1ebb 100644
--- a/pkg/analysis_server_plugin/lib/src/plugin_server.dart
+++ b/pkg/analysis_server_plugin/lib/src/plugin_server.dart
@@ -24,7 +24,6 @@
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
import 'package:analyzer/src/dart/analysis/session.dart';
-import 'package:analyzer/src/dart/ast/utilities.dart';
import 'package:analyzer/src/dart/element/type_system.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
import 'package:analyzer/src/lint/lint_rule_timers.dart';
@@ -81,9 +80,6 @@
}
/// Handles an 'analysis.setContextRoots' request.
- ///
- /// Throws a [RequestFailure] if the request could not be handled.
- // TODO(srawlins): Unnecessary??
Future<protocol.AnalysisSetContextRootsResult> handleAnalysisSetContextRoots(
protocol.AnalysisSetContextRootsParams parameters) async {
var currentContextCollection = _contextCollection;
@@ -111,7 +107,6 @@
/// Throws a [RequestFailure] if the request could not be handled.
Future<protocol.EditGetFixesResult> handleEditGetFixes(
protocol.EditGetFixesParams parameters) async {
- // TODO(srawlins): Run this all in `runZonedGuarded`.
var path = parameters.file;
var offset = parameters.offset;
@@ -171,8 +166,6 @@
}
/// Handles a 'plugin.versionCheck' request.
- ///
- /// Throws a [RequestFailure] if the request could not be handled.
Future<protocol.PluginVersionCheckResult> handlePluginVersionCheck(
protocol.PluginVersionCheckParams parameters) async {
// TODO(srawlins): It seems improper for _this_ method to be the point where
@@ -191,7 +184,7 @@
/// Starts this plugin by listening to the given communication [channel].
void start(PluginCommunicationChannel channel) {
_channel = channel;
- _channel.listen(_handleRequest,
+ _channel.listen(_handleRequestZoned,
// TODO(srawlins): Implement.
onError: () {},
// TODO(srawlins): Implement.
@@ -214,21 +207,6 @@
});
}
- /// Analyzes the file at the given [path].
- Future<void> _analyzeFile(
- {required AnalysisContext analysisContext, required String path}) async {
- // TODO(srawlins): Run this all in `runZonedGuarded`.
- var file = _resourceProvider.getFile(path);
- var analysisOptions = analysisContext.getAnalysisOptionsForFile(file);
- var lints = await _computeLints(
- analysisContext,
- path,
- analysisOptions: analysisOptions as AnalysisOptionsImpl,
- );
- _channel.sendNotification(
- protocol.AnalysisErrorsParams(path, lints).toNotification());
- }
-
/// Analyzes the files at the given [paths].
Future<void> _analyzeFiles({
required AnalysisContext analysisContext,
@@ -237,10 +215,15 @@
// TODO(srawlins): Implement "priority files" and analyze them first.
// TODO(srawlins): Analyze libraries instead of files, for efficiency.
for (var path in paths.toSet()) {
- await _analyzeFile(
- analysisContext: analysisContext,
- path: path,
+ var file = _resourceProvider.getFile(path);
+ var analysisOptions = analysisContext.getAnalysisOptionsForFile(file);
+ var lints = await _computeLints(
+ analysisContext,
+ path,
+ analysisOptions: analysisOptions as AnalysisOptionsImpl,
);
+ _channel.sendNotification(
+ protocol.AnalysisErrorsParams(path, lints).toNotification());
}
}
@@ -290,6 +273,7 @@
// TODO(srawlins): Support 'package' parameter.
null,
);
+
// TODO(srawlins): Distinguish between registered rules and enabled rules.
for (var rule in _registry.registeredRules) {
rule.reporter = errorReporter;
@@ -299,10 +283,7 @@
timer?.stop();
}
- var exceptionHandler = LinterExceptionHandler(
- propagateExceptions: analysisOptions.propagateLinterExceptions)
- .logException;
- currentUnit.unit.accept(LinterVisitor(nodeRegistry, exceptionHandler));
+ currentUnit.unit.accept(LinterVisitor(nodeRegistry));
// The list of the `AnalysisError`s and their associated
// `protocol.AnalysisError`s.
var errorsAndProtocolErrors = [
@@ -396,6 +377,17 @@
}
}
+ Future<void> _handleRequestZoned(Request request) async {
+ await runZonedGuarded(
+ () => _handleRequest(request),
+ (error, stackTrace) {
+ _channel.sendNotification(protocol.PluginErrorParams(
+ false /* isFatal */, error.toString(), stackTrace.toString())
+ .toNotification());
+ },
+ );
+ }
+
static protocol.Location _locationFor(
CompilationUnit unit, String path, AnalysisError error) {
var lineInfo = unit.lineInfo;
diff --git a/pkg/analysis_server_plugin/pubspec.yaml b/pkg/analysis_server_plugin/pubspec.yaml
index f6be036..bac09b8 100644
--- a/pkg/analysis_server_plugin/pubspec.yaml
+++ b/pkg/analysis_server_plugin/pubspec.yaml
@@ -14,6 +14,7 @@
# Use 'any' constraints here; we get our versions from the DEPS file.
dev_dependencies:
+ async: any
lints: any
test_reflective_loader: any
test: any
diff --git a/pkg/analysis_server_plugin/test/src/lint_rules.dart b/pkg/analysis_server_plugin/test/src/lint_rules.dart
new file mode 100644
index 0000000..4e550c1
--- /dev/null
+++ b/pkg/analysis_server_plugin/test/src/lint_rules.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2024, 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:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/error/error.dart';
+import 'package:analyzer/src/lint/linter.dart';
+
+class NoBoolsRule extends LintRule {
+ static const LintCode code = LintCode('no_bools', 'No bools message');
+
+ NoBoolsRule()
+ : super(
+ name: 'no_bools',
+ description: 'No bools desc',
+ details: 'No bools details',
+ );
+
+ @override
+ LintCode get lintCode => code;
+
+ @override
+ void registerNodeProcessors(
+ NodeLintRegistry registry, LinterContext context) {
+ var visitor = _NoBoolsVisitor(this);
+ registry.addBooleanLiteral(this, visitor);
+ }
+}
+
+class _NoBoolsVisitor extends SimpleAstVisitor<void> {
+ final LintRule rule;
+
+ _NoBoolsVisitor(this.rule);
+
+ @override
+ void visitBooleanLiteral(BooleanLiteral node) {
+ rule.reportLint(node);
+ }
+}
diff --git a/pkg/analysis_server_plugin/test/src/plugin_server_error_test.dart b/pkg/analysis_server_plugin/test/src/plugin_server_error_test.dart
new file mode 100644
index 0000000..100a61c
--- /dev/null
+++ b/pkg/analysis_server_plugin/test/src/plugin_server_error_test.dart
@@ -0,0 +1,272 @@
+// Copyright (c) 2024, 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_plugin/edit/dart/correction_producer.dart';
+import 'package:analysis_server_plugin/plugin.dart';
+import 'package:analysis_server_plugin/registry.dart';
+import 'package:analysis_server_plugin/src/plugin_server.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:analyzer/error/error.dart';
+import 'package:analyzer/src/lint/linter.dart';
+import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol;
+import 'package:analyzer_plugin/protocol/protocol_generated.dart' as protocol;
+import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
+import 'package:analyzer_plugin/utilities/fixes/fixes.dart';
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+import 'package:test_reflective_loader/test_reflective_loader.dart';
+
+import 'lint_rules.dart';
+import 'plugin_server_test_base.dart';
+
+void main() async {
+ defineReflectiveTests(PluginServerErrorTest);
+}
+
+@reflectiveTest
+class PluginServerErrorTest extends PluginServerTestBase {
+ Future<void> test_handleAnalysisSetContextRoots_throwingAsyncError() async {
+ pluginServer = PluginServer(
+ resourceProvider: resourceProvider,
+ plugins: [_RuleThrowsAsyncErrorPlugin()],
+ );
+ await startPlugin();
+
+ var packagePath = convertPath('/package1');
+ var filePath = join(packagePath, 'lib', 'test.dart');
+ newFile(filePath, 'bool b = false;');
+ var contextRoot = protocol.ContextRoot(packagePath, []);
+
+ await channel
+ .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot]));
+
+ var notifications = StreamQueue(channel.notifications);
+ var analysisErrorsParams = protocol.AnalysisErrorsParams.fromNotification(
+ await notifications.next);
+ expect(analysisErrorsParams.errors, isEmpty);
+
+ var pluginErrorParams =
+ protocol.PluginErrorParams.fromNotification(await notifications.next);
+ expect(pluginErrorParams.isFatal, false);
+ expect(pluginErrorParams.message, 'Bad state: A message.');
+ // TODO(srawlins): Does `StackTrace.toString()` not do what I think?
+ expect(pluginErrorParams.stackTrace, '');
+ }
+
+ Future<void> test_handleAnalysisSetContextRoots_throwingSyncError() async {
+ pluginServer = PluginServer(
+ resourceProvider: resourceProvider,
+ plugins: [_RuleThrowsSyncErrorPlugin()],
+ );
+ await startPlugin();
+
+ var packagePath = convertPath('/package1');
+ var filePath = join(packagePath, 'lib', 'test.dart');
+ newFile(filePath, 'bool b = false;');
+ var contextRoot = protocol.ContextRoot(packagePath, []);
+
+ var response = await channel
+ .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot]));
+
+ expect(
+ response.error,
+ isA<protocol.RequestError>()
+ .having((e) => e.message, 'message', 'Bad state: A message.')
+ .having((e) => e.stackTrace, 'stackTrace', isNotNull),
+ );
+ }
+
+ Future<void> test_handleEditGetFixes_throwingAsyncError() async {
+ pluginServer = PluginServer(
+ resourceProvider: resourceProvider,
+ plugins: [_FixThrowsAsyncErrorPlugin()],
+ );
+ await startPlugin();
+
+ var packagePath = convertPath('/package1');
+ var filePath = join(packagePath, 'lib', 'test.dart');
+ newFile(filePath, 'bool b = false;');
+ var contextRoot = protocol.ContextRoot(packagePath, []);
+
+ await channel
+ .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot]));
+ await channel
+ .sendRequest(protocol.EditGetFixesParams(filePath, 'bool b = '.length));
+
+ var notifications = StreamQueue(channel.notifications);
+ var analysisErrorsParams = protocol.AnalysisErrorsParams.fromNotification(
+ await notifications.next);
+ expect(analysisErrorsParams.errors.single, isA<protocol.AnalysisError>());
+
+ var pluginErrorParams =
+ protocol.PluginErrorParams.fromNotification(await notifications.next);
+ expect(pluginErrorParams.isFatal, false);
+ expect(pluginErrorParams.message, 'Bad state: A message.');
+ // TODO(srawlins): Does `StackTrace.toString()` not do what I think?
+ expect(pluginErrorParams.stackTrace, '');
+ }
+
+ Future<void> test_handleEditGetFixes_throwingSyncError() async {
+ pluginServer = PluginServer(
+ resourceProvider: resourceProvider,
+ plugins: [_FixThrowsSyncErrorPlugin()],
+ );
+ await startPlugin();
+
+ var packagePath = convertPath('/package1');
+ var filePath = join(packagePath, 'lib', 'test.dart');
+ newFile(filePath, 'bool b = false;');
+ var contextRoot = protocol.ContextRoot(packagePath, []);
+
+ await channel
+ .sendRequest(protocol.AnalysisSetContextRootsParams([contextRoot]));
+
+ var response = await channel
+ .sendRequest(protocol.EditGetFixesParams(filePath, 'bool b = '.length));
+ expect(
+ response.error,
+ isA<protocol.RequestError>()
+ .having((e) => e.message, 'message', 'Bad state: A message.')
+ .having((e) => e.stackTrace, 'stackTrace', isNotNull),
+ );
+ }
+}
+
+class _FixThrowsAsyncErrorPlugin extends Plugin {
+ @override
+ void register(PluginRegistry registry) {
+ registry.registerRule(NoBoolsRule());
+ registry.registerFixForRule(NoBoolsRule.code, _ThrowsAsyncErrorFix.new);
+ }
+}
+
+class _FixThrowsSyncErrorPlugin extends Plugin {
+ @override
+ void register(PluginRegistry registry) {
+ registry.registerRule(NoBoolsRule());
+ registry.registerFixForRule(NoBoolsRule.code, _ThrowsSyncErrorFix.new);
+ }
+}
+
+class _RuleThrowsAsyncErrorPlugin extends Plugin {
+ @override
+ void register(PluginRegistry registry) {
+ registry.registerRule(_ThrowsAsyncErrorRule());
+ }
+}
+
+class _RuleThrowsSyncErrorPlugin extends Plugin {
+ @override
+ void register(PluginRegistry registry) {
+ registry.registerRule(_ThrowsSyncErrorRule());
+ }
+}
+
+/// A correction producer that throws an async error while computing a
+/// correction.
+class _ThrowsAsyncErrorFix extends ResolvedCorrectionProducer {
+ _ThrowsAsyncErrorFix({required super.context});
+
+ @override
+ CorrectionApplicability get applicability =>
+ CorrectionApplicability.acrossFiles;
+
+ @override
+ FixKind get fixKind => FixKind('unused', 50, 'Unused');
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ // Raise an async error that can only be caught by an error zone's `onError`
+ // handler.
+ // ignore: unawaited_futures
+ Future<void>.error(StateError('A message.'));
+ }
+}
+
+class _ThrowsAsyncErrorRule extends LintRule {
+ static const LintCode code = LintCode('no_bools', 'No bools message');
+
+ _ThrowsAsyncErrorRule()
+ : super(
+ name: 'no_bools',
+ description: 'No bools desc',
+ details: 'No bools details',
+ );
+
+ @override
+ LintCode get lintCode => code;
+
+ @override
+ void registerNodeProcessors(
+ NodeLintRegistry registry, LinterContext context) {
+ var visitor = _ThrowsAsyncErrorVisitor(this);
+ registry.addBooleanLiteral(this, visitor);
+ }
+}
+
+class _ThrowsAsyncErrorVisitor extends SimpleAstVisitor<void> {
+ final LintRule rule;
+
+ _ThrowsAsyncErrorVisitor(this.rule);
+
+ @override
+ void visitBooleanLiteral(BooleanLiteral node) {
+ // Raise an async error that can only be caught by an error zone's `onError`
+ // handler.
+ // ignore: unawaited_futures
+ Future<void>.error(StateError('A message.'));
+ }
+}
+
+/// A correction producer that throws a sync error while computing a correction.
+class _ThrowsSyncErrorFix extends ResolvedCorrectionProducer {
+ _ThrowsSyncErrorFix({required super.context});
+
+ @override
+ CorrectionApplicability get applicability =>
+ CorrectionApplicability.acrossFiles;
+
+ @override
+ FixKind get fixKind => FixKind('unused', 50, 'Unused');
+
+ @override
+ Future<void> compute(ChangeBuilder builder) async {
+ throw StateError('A message.');
+ }
+}
+
+class _ThrowsSyncErrorRule extends LintRule {
+ static const LintCode code = LintCode('no_bools', 'No bools message');
+
+ _ThrowsSyncErrorRule()
+ : super(
+ name: 'no_bools',
+ description: 'No bools desc',
+ details: 'No bools details',
+ );
+
+ @override
+ LintCode get lintCode => code;
+
+ @override
+ void registerNodeProcessors(
+ NodeLintRegistry registry, LinterContext context) {
+ var visitor = _ThrowsSyncErrorVisitor(this);
+ registry.addBooleanLiteral(this, visitor);
+ }
+}
+
+class _ThrowsSyncErrorVisitor extends SimpleAstVisitor<void> {
+ final LintRule rule;
+
+ _ThrowsSyncErrorVisitor(this.rule);
+
+ @override
+ void visitBooleanLiteral(BooleanLiteral node) {
+ throw StateError('A message.');
+ }
+}
diff --git a/pkg/analysis_server_plugin/test/src/plugin_server_test.dart b/pkg/analysis_server_plugin/test/src/plugin_server_test.dart
index ab552be..2f36449 100644
--- a/pkg/analysis_server_plugin/test/src/plugin_server_test.dart
+++ b/pkg/analysis_server_plugin/test/src/plugin_server_test.dart
@@ -7,17 +7,7 @@
import 'package:analysis_server_plugin/edit/dart/correction_producer.dart';
import 'package:analysis_server_plugin/plugin.dart';
import 'package:analysis_server_plugin/registry.dart';
-import 'package:analysis_server_plugin/src/correction/fix_generators.dart';
import 'package:analysis_server_plugin/src/plugin_server.dart';
-import 'package:analyzer/dart/ast/ast.dart';
-import 'package:analyzer/dart/ast/visitor.dart';
-import 'package:analyzer/error/error.dart';
-import 'package:analyzer/file_system/file_system.dart';
-import 'package:analyzer/src/lint/linter.dart';
-import 'package:analyzer/src/test_utilities/mock_sdk.dart';
-import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
-import 'package:analyzer_plugin/channel/channel.dart';
-import 'package:analyzer_plugin/protocol/protocol.dart' as protocol;
import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol;
import 'package:analyzer_plugin/protocol/protocol_generated.dart' as protocol;
import 'package:analyzer_plugin/utilities/change_builder/change_builder_core.dart';
@@ -25,41 +15,24 @@
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
+import 'lint_rules.dart';
+import 'plugin_server_test_base.dart';
+
void main() async {
defineReflectiveTests(PluginServerTest);
}
@reflectiveTest
-class PluginServerTest with ResourceProviderMixin {
- final channel = _FakeChannel();
-
- late final PluginServer pluginServer;
-
- Folder get byteStoreRoot => getFolder('/byteStore');
-
- Folder get sdkRoot => getFolder('/sdk');
-
+class PluginServerTest extends PluginServerTestBase {
+ @override
Future<void> setUp() async {
- createMockSdk(resourceProvider: resourceProvider, root: sdkRoot);
+ await super.setUp();
pluginServer = PluginServer(
- resourceProvider: resourceProvider,
- plugins: [_NoBoolsPlugin()],
- );
- await pluginServer.initialize();
- pluginServer.start(channel);
-
- await pluginServer.handlePluginVersionCheck(
- protocol.PluginVersionCheckParams(
- byteStoreRoot.path,
- sdkRoot.path,
- '0.0.1',
- ),
- );
+ resourceProvider: resourceProvider, plugins: [_NoBoolsPlugin()]);
+ await startPlugin();
}
- void tearDown() => registeredFixGenerators.clearLintProducers();
-
Future<void> test_handleAnalysisSetContextRoots() async {
var packagePath = convertPath('/package1');
var filePath = join(packagePath, 'lib', 'test.dart');
@@ -102,76 +75,11 @@
}
}
-class _FakeChannel implements PluginCommunicationChannel {
- final _completers = <String, Completer<protocol.Response>>{};
-
- final StreamController<protocol.Notification> _notificationsController =
- StreamController();
-
- Stream<protocol.Notification> get notifications =>
- _notificationsController.stream;
-
- @override
- void close() {}
-
- @override
- void listen(void Function(protocol.Request request)? onRequest,
- {void Function()? onDone, Function? onError, Function? onNotification}) {}
-
- @override
- void sendNotification(protocol.Notification notification) {
- _notificationsController.add(notification);
- }
-
- @override
- void sendResponse(protocol.Response response) {
- var completer = _completers.remove(response.id);
- completer?.complete(response);
- }
-}
-
class _NoBoolsPlugin extends Plugin {
@override
void register(PluginRegistry registry) {
- registry.registerRule(_NoBoolsRule());
- registry.registerFixForRule(_NoBoolsRule.code, _WrapInQuotes.new);
- }
-}
-
-class _NoBoolsRule extends LintRule {
- static const LintCode code = LintCode(
- 'no_bools',
- 'No bools message',
- correctionMessage: 'No bools correction',
- );
-
- _NoBoolsRule()
- : super(
- name: 'no_bools',
- description: 'No bools desc',
- details: 'No bools details',
- categories: {LintRuleCategory.errorProne},
- );
-
- @override
- LintCode get lintCode => code;
-
- @override
- void registerNodeProcessors(
- NodeLintRegistry registry, LinterContext context) {
- var visitor = _NoBoolsVisitor(this);
- registry.addBooleanLiteral(this, visitor);
- }
-}
-
-class _NoBoolsVisitor extends SimpleAstVisitor<void> {
- final LintRule rule;
-
- _NoBoolsVisitor(this.rule);
-
- @override
- void visitBooleanLiteral(BooleanLiteral node) {
- rule.reportLint(node);
+ registry.registerRule(NoBoolsRule());
+ registry.registerFixForRule(NoBoolsRule.code, _WrapInQuotes.new);
}
}
@@ -179,22 +87,16 @@
static const _wrapInQuotesKind =
FixKind('dart.fix.wrapInQuotes', 50, 'Wrap in quotes');
- static const _wrapInQuotesMultiKind = FixKind(
- 'dart.fix.wrapInQuotes.multi', 40, 'Wrap in quotes everywhere in file');
+ _WrapInQuotes({required super.context});
@override
- final CorrectionApplicability applicability;
-
- _WrapInQuotes({required super.context})
- : applicability = CorrectionApplicability.acrossFiles;
+ CorrectionApplicability get applicability =>
+ CorrectionApplicability.acrossFiles;
@override
FixKind get fixKind => _wrapInQuotesKind;
@override
- FixKind get multiFixKind => _wrapInQuotesMultiKind;
-
- @override
Future<void> compute(ChangeBuilder builder) async {
var literal = node;
await builder.addDartFileEdit(file, (builder) {
diff --git a/pkg/analysis_server_plugin/test/src/plugin_server_test_base.dart b/pkg/analysis_server_plugin/test/src/plugin_server_test_base.dart
new file mode 100644
index 0000000..a42d919
--- /dev/null
+++ b/pkg/analysis_server_plugin/test/src/plugin_server_test_base.dart
@@ -0,0 +1,92 @@
+// Copyright (c) 2024, 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_plugin/src/correction/fix_generators.dart';
+import 'package:analysis_server_plugin/src/plugin_server.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/src/test_utilities/mock_sdk.dart';
+import 'package:analyzer/src/test_utilities/resource_provider_mixin.dart';
+import 'package:analyzer_plugin/channel/channel.dart';
+import 'package:analyzer_plugin/protocol/protocol.dart' as protocol;
+import 'package:analyzer_plugin/protocol/protocol_generated.dart' as protocol;
+import 'package:analyzer_plugin/src/protocol/protocol_internal.dart'
+ as protocol;
+import 'package:meta/meta.dart';
+import 'package:test/test.dart';
+
+class PluginServerTestBase with ResourceProviderMixin {
+ final channel = _FakeChannel();
+
+ late final PluginServer pluginServer;
+
+ Folder get byteStoreRoot => getFolder('/byteStore');
+
+ Folder get sdkRoot => getFolder('/sdk');
+
+ @mustCallSuper
+ Future<void> setUp() async {
+ createMockSdk(resourceProvider: resourceProvider, root: sdkRoot);
+ }
+
+ Future<void> startPlugin() async {
+ await pluginServer.initialize();
+ pluginServer.start(channel);
+
+ await pluginServer.handlePluginVersionCheck(
+ protocol.PluginVersionCheckParams(
+ byteStoreRoot.path, sdkRoot.path, '0.0.1'),
+ );
+ }
+
+ void tearDown() => registeredFixGenerators.clearLintProducers();
+}
+
+class _FakeChannel implements PluginCommunicationChannel {
+ final _completers = <String, Completer<protocol.Response>>{};
+
+ final StreamController<protocol.Notification> _notificationsController =
+ StreamController();
+
+ void Function(protocol.Request)? _onRequest;
+
+ int _idCounter = 0;
+
+ Stream<protocol.Notification> get notifications =>
+ _notificationsController.stream;
+
+ @override
+ void close() {}
+
+ @override
+ void listen(void Function(protocol.Request request)? onRequest,
+ {void Function()? onDone, Function? onError, Function? onNotification}) {
+ _onRequest = onRequest;
+ }
+
+ @override
+ void sendNotification(protocol.Notification notification) {
+ _notificationsController.add(notification);
+ }
+
+ Future<protocol.Response> sendRequest(protocol.RequestParams params) {
+ if (_onRequest == null) {
+ fail(
+ '_onReuest is null! `listen` has not yet been called on this channel.');
+ }
+ var id = (_idCounter++).toString();
+ var request = params.toRequest(id);
+ var completer = Completer<protocol.Response>();
+ _completers[request.id] = completer;
+ _onRequest!(request);
+ return completer.future;
+ }
+
+ @override
+ void sendResponse(protocol.Response response) {
+ var completer = _completers.remove(response.id);
+ completer?.complete(response);
+ }
+}
diff --git a/pkg/analysis_server_plugin/test/src/test_all.dart b/pkg/analysis_server_plugin/test/src/test_all.dart
new file mode 100644
index 0000000..d75bfad
--- /dev/null
+++ b/pkg/analysis_server_plugin/test/src/test_all.dart
@@ -0,0 +1,15 @@
+// Copyright (c) 2024, 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:test_reflective_loader/test_reflective_loader.dart';
+
+import 'plugin_server_error_test.dart' as plugin_server_error_test;
+import 'plugin_server_test.dart' as plugin_server_test;
+
+void main() {
+ defineReflectiveSuite(() {
+ plugin_server_error_test.main();
+ plugin_server_test.main();
+ }, name: 'src');
+}
diff --git a/pkg/analysis_server_plugin/test/test_all.dart b/pkg/analysis_server_plugin/test/test_all.dart
index 7471798..56970e8 100644
--- a/pkg/analysis_server_plugin/test/test_all.dart
+++ b/pkg/analysis_server_plugin/test/test_all.dart
@@ -5,9 +5,11 @@
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'edit/test_all.dart' as edit;
+import 'src/test_all.dart' as src;
void main() {
defineReflectiveSuite(() {
edit.main();
- }, name: 'edit');
+ src.main();
+ });
}