blob: 5584ddb12d10fef0f45cd47f1f88bb6e2ac8ffbe [file] [log] [blame]
// Copyright (c) 2017, 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:io' as io;
import 'package:analysis_server/src/plugin/plugin_isolate.dart';
import 'package:analyzer/instrumentation/instrumentation.dart';
import 'package:analyzer/src/context/packages.dart';
import 'package:analyzer/src/dart/analysis/context_root.dart';
import 'package:analyzer/src/workspace/basic.dart';
import 'package:analyzer_plugin/channel/channel.dart';
import 'package:analyzer_plugin/protocol/protocol.dart';
import 'package:analyzer_plugin/protocol/protocol_common.dart';
import 'package:analyzer_plugin/protocol/protocol_generated.dart'
hide ContextRoot;
import 'package:analyzer_testing/resource_provider_mixin.dart';
import 'package:analyzer_testing/utilities/extensions/resource_provider.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../../mocks.dart';
import 'plugin_test_support.dart';
void main() {
defineReflectiveSuite(() {
defineReflectiveTests(PluginIsolateTest);
defineReflectiveTests(PluginSessionTest);
defineReflectiveTests(PluginSessionFromDiskTest);
});
}
@reflectiveTest
class PluginIsolateTest with ResourceProviderMixin, _ContextRoot {
late TestNotificationManager notificationManager;
String pluginPath = '/pluginDir';
String executionPath = '/pluginDir/bin/plugin.dart';
String packagesPath = '/pluginDir/.packages';
late PluginIsolate pluginIsolate;
void setUp() {
notificationManager = TestNotificationManager();
pluginIsolate = PluginIsolate(
pluginPath,
executionPath,
packagesPath,
notificationManager,
InstrumentationService.NULL_SERVICE,
isLegacy: true,
);
}
void test_addContextRoot() {
var contextRoot1 = _newContextRoot('/pkg1');
var optionsFile = getFile('/pkg1/analysis_options.yaml');
contextRoot1.optionsFile = optionsFile;
var session = PluginSession(pluginIsolate);
var channel = TestServerCommunicationChannel(session);
pluginIsolate.currentSession = session;
pluginIsolate.addContextRoot(contextRoot1);
expect(pluginIsolate.contextRoots, [contextRoot1]);
pluginIsolate.addContextRoot(contextRoot1);
expect(pluginIsolate.contextRoots, [contextRoot1]);
var sentRequests = channel.sentRequests;
expect(sentRequests, hasLength(1));
var roots = sentRequests[0].params['roots'] as List<Map>;
expect(roots[0]['optionsFile'], optionsFile.path);
}
void test_creation() {
expect(pluginIsolate.executionPath, executionPath);
expect(pluginIsolate.contextRoots, isEmpty);
expect(pluginIsolate.currentSession, isNull);
}
void test_removeContextRoot() {
var contextRoot1 = _newContextRoot('/pkg1');
var contextRoot2 = _newContextRoot('/pkg2');
pluginIsolate.addContextRoot(contextRoot1);
expect(pluginIsolate.contextRoots, unorderedEquals([contextRoot1]));
pluginIsolate.addContextRoot(contextRoot2);
expect(
pluginIsolate.contextRoots,
unorderedEquals([contextRoot1, contextRoot2]),
);
pluginIsolate.removeContextRoot(contextRoot1);
expect(pluginIsolate.contextRoots, unorderedEquals([contextRoot2]));
pluginIsolate.removeContextRoot(contextRoot2);
expect(pluginIsolate.contextRoots, isEmpty);
}
@failingTest
Future<void> test_start_notRunning() {
fail('Not tested');
}
Future<void> test_start_running() async {
pluginIsolate.currentSession = PluginSession(pluginIsolate);
expect(() => pluginIsolate.start('', ''), throwsStateError);
}
void test_stop_notRunning() {
expect(() => pluginIsolate.stop(), throwsStateError);
}
Future<void> test_stop_running() async {
var session = PluginSession(pluginIsolate);
var channel = TestServerCommunicationChannel(session);
pluginIsolate.currentSession = session;
await pluginIsolate.stop();
expect(pluginIsolate.currentSession, isNull);
expect(channel.sentRequests, hasLength(1));
expect(channel.sentRequests[0].method, 'plugin.shutdown');
}
}
@reflectiveTest
class PluginSessionFromDiskTest extends PluginTestSupport {
@SkippedTest(
reason: 'flaky timeouts',
issue: 'https://github.com/dart-lang/sdk/issues/38629',
)
Future<void> test_start_notRunning() async {
await withPlugin(
test: (String pluginPath) async {
var packagesPath = path.join(pluginPath, '.packages');
var mainPath = path.join(pluginPath, 'bin', 'plugin.dart');
var byteStorePath = path.join(pluginPath, 'byteStore');
io.Directory(byteStorePath).createSync();
var pluginIsolate = PluginIsolate(
pluginPath,
mainPath,
packagesPath,
notificationManager,
InstrumentationService.NULL_SERVICE,
isLegacy: true,
);
var session = PluginSession(pluginIsolate);
pluginIsolate.currentSession = session;
expect(await session.start(byteStorePath, ''), isTrue);
await session.stop();
},
);
}
}
@reflectiveTest
class PluginSessionTest with ResourceProviderMixin {
late TestNotificationManager notificationManager;
late String pluginPath;
late String executionPath;
late String packagesPath;
late String sdkPath;
late PluginIsolate pluginIsolate;
late PluginSession session;
void setUp() {
notificationManager = TestNotificationManager();
pluginPath = resourceProvider.convertPath('/pluginDir');
executionPath = resourceProvider.convertPath('/pluginDir/bin/plugin.dart');
packagesPath = resourceProvider.convertPath('/pluginDir/.packages');
sdkPath = resourceProvider.convertPath('/sdk');
pluginIsolate = PluginIsolate(
pluginPath,
executionPath,
packagesPath,
notificationManager,
InstrumentationService.NULL_SERVICE,
isLegacy: true,
);
session = PluginSession(pluginIsolate);
}
void test_handleNotification() {
var notification =
AnalysisErrorsParams('/test.dart', <AnalysisError>[]).toNotification();
expect(notificationManager.notifications, hasLength(0));
session.handleNotification(notification);
expect(notificationManager.notifications, hasLength(1));
expect(notificationManager.notifications[0], notification);
}
void test_handleOnDone() {
var channel = TestServerCommunicationChannel(session);
session.handleOnDone();
expect(channel.closeCount, 1);
expect(session.pluginStoppedCompleter.isCompleted, isTrue);
}
void test_handleOnError() {
session.handleOnError(<String>['message', 'trace']);
expect(
notificationManager.pluginErrors.first,
'An error occurred while executing an analyzer plugin: message\ntrace',
);
}
Future<void> test_handleResponse() async {
TestServerCommunicationChannel(session);
var response = PluginVersionCheckResult(
true,
'name',
'version',
<String>[],
contactInfo: 'contactInfo',
).toResponse('0', 1);
var future = session.sendRequest(PluginVersionCheckParams('', '', ''));
expect(session.pendingRequests, hasLength(1));
session.handleResponse(response);
expect(session.pendingRequests, hasLength(0));
var result = await future;
expect(result, same(response));
}
Future<void> test_handleResponse_withError() async {
TestServerCommunicationChannel(session);
var response = Response(
'0' /* id */,
1 /* requestTime */,
error: RequestError(
RequestErrorCode.PLUGIN_ERROR,
'exception',
stackTrace: 'some stackTrace',
),
);
var responseFuture = session.sendRequest(
PluginVersionCheckParams('', '', ''),
);
session.handleResponse(response);
await responseFuture;
expect(
notificationManager.pluginErrors,
equals([
'An error occurred while executing an analyzer plugin: exception\n'
'some stackTrace',
]),
);
}
void test_nextRequestId() {
expect(session.requestId, 0);
expect(session.nextRequestId, '0');
expect(session.requestId, 1);
}
void test_sendRequest() {
var channel = TestServerCommunicationChannel(session);
session.sendRequest(PluginVersionCheckParams('', '', ''));
expect(channel.sentRequests, hasLength(1));
expect(channel.sentRequests[0].method, 'plugin.versionCheck');
}
Future<void> test_start_notCompatible() async {
session.isCompatible = false;
expect(
await session.start(path.join(pluginPath, 'byteStore'), sdkPath),
isFalse,
);
expect(
notificationManager.pluginErrors.first,
startsWith(
'An error occurred while executing an analyzer plugin: Plugin is not '
'compatible.',
),
);
}
Future<void> test_start_running() async {
TestServerCommunicationChannel(session);
expect(() => session.start('', ''), throwsStateError);
}
void test_stop_notRunning() {
expect(() => session.stop(), throwsStateError);
}
Future<void> test_stop_running() async {
var channel = TestServerCommunicationChannel(session);
await session.stop();
expect(channel.sentRequests, hasLength(1));
expect(channel.sentRequests[0].method, 'plugin.shutdown');
}
}
class TestServerCommunicationChannel implements ServerCommunicationChannel {
final PluginSession session;
int closeCount = 0;
List<Request> sentRequests = <Request>[];
TestServerCommunicationChannel(this.session) {
session.channel = this;
}
@override
void close() {
closeCount++;
}
@override
void kill() {
fail('Unexpected invocation of kill');
}
@override
void listen(
void Function(Response response) onResponse,
void Function(Notification notification) onNotification, {
void Function(dynamic error)? onError,
void Function()? onDone,
}) {
fail('Unexpected invocation of listen');
}
@override
void sendRequest(Request request) {
sentRequests.add(request);
if (request.method == 'plugin.shutdown') {
session.handleOnDone();
}
}
}
mixin _ContextRoot on ResourceProviderMixin {
ContextRootImpl _newContextRoot(String rootPath) {
rootPath = convertPath(rootPath);
return ContextRootImpl(
resourceProvider,
resourceProvider.getFolder(rootPath),
BasicWorkspace.find(resourceProvider, Packages.empty, rootPath),
);
}
}