38546e90ab
https://github.com/dart-lang/linter/pull/4441 Change-Id: I55afa6b7d78bc1114ce97c1b5bebcf7a8bca5b98 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/307975 Commit-Queue: Konstantin Shcheglov <scheglov@google.com> Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
913 lines
31 KiB
Dart
913 lines
31 KiB
Dart
// 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/notification_manager.dart';
|
|
import 'package:analysis_server/src/plugin/plugin_manager.dart';
|
|
import 'package:analyzer/file_system/physical_file_system.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/test_utilities/resource_provider_mixin.dart';
|
|
import 'package:analyzer/src/util/file_paths.dart' as file_paths;
|
|
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:path/path.dart' as path;
|
|
import 'package:test/test.dart';
|
|
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
|
import 'package:watcher/watcher.dart' as watcher;
|
|
|
|
void main() {
|
|
defineReflectiveSuite(() {
|
|
defineReflectiveTests(BuiltInPluginInfoTest);
|
|
defineReflectiveTests(DiscoveredPluginInfoTest);
|
|
defineReflectiveTests(PluginManagerTest);
|
|
defineReflectiveTests(PluginManagerFromDiskTest);
|
|
defineReflectiveTests(PluginSessionTest);
|
|
defineReflectiveTests(PluginSessionFromDiskTest);
|
|
});
|
|
}
|
|
|
|
@reflectiveTest
|
|
class BuiltInPluginInfoTest with ResourceProviderMixin, _ContextRoot {
|
|
late TestNotificationManager notificationManager;
|
|
late BuiltInPluginInfo plugin;
|
|
|
|
void setUp() {
|
|
notificationManager = TestNotificationManager();
|
|
plugin = BuiltInPluginInfo((_) {}, 'test plugin', notificationManager,
|
|
InstrumentationService.NULL_SERVICE);
|
|
}
|
|
|
|
void test_addContextRoot() {
|
|
var contextRoot1 = _newContextRoot('/pkg1');
|
|
plugin.addContextRoot(contextRoot1);
|
|
expect(plugin.contextRoots, [contextRoot1]);
|
|
plugin.addContextRoot(contextRoot1);
|
|
expect(plugin.contextRoots, [contextRoot1]);
|
|
}
|
|
|
|
void test_creation() {
|
|
expect(plugin.pluginId, 'test plugin');
|
|
expect(plugin.notificationManager, notificationManager);
|
|
expect(plugin.contextRoots, isEmpty);
|
|
expect(plugin.currentSession, isNull);
|
|
}
|
|
|
|
void test_removeContextRoot() {
|
|
var contextRoot1 = _newContextRoot('/pkg1');
|
|
var contextRoot2 = _newContextRoot('/pkg2');
|
|
plugin.addContextRoot(contextRoot1);
|
|
expect(plugin.contextRoots, unorderedEquals([contextRoot1]));
|
|
plugin.addContextRoot(contextRoot2);
|
|
expect(plugin.contextRoots, unorderedEquals([contextRoot1, contextRoot2]));
|
|
plugin.removeContextRoot(contextRoot1);
|
|
expect(plugin.contextRoots, unorderedEquals([contextRoot2]));
|
|
plugin.removeContextRoot(contextRoot2);
|
|
expect(plugin.contextRoots, isEmpty);
|
|
}
|
|
|
|
@failingTest
|
|
Future<void> test_start_notRunning() {
|
|
fail('Not tested');
|
|
}
|
|
|
|
Future<void> test_start_running() async {
|
|
plugin.currentSession = PluginSession(plugin);
|
|
try {
|
|
await plugin.start('', '');
|
|
fail('Expected a StateError');
|
|
} on StateError {
|
|
// Expected.
|
|
}
|
|
}
|
|
|
|
void test_stop_notRunning() {
|
|
expect(() => plugin.stop(), throwsStateError);
|
|
}
|
|
|
|
Future<void> test_stop_running() async {
|
|
var session = PluginSession(plugin);
|
|
var channel = TestServerCommunicationChannel(session);
|
|
plugin.currentSession = session;
|
|
await plugin.stop();
|
|
expect(plugin.currentSession, isNull);
|
|
expect(channel.sentRequests, hasLength(1));
|
|
expect(channel.sentRequests[0].method, 'plugin.shutdown');
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class DiscoveredPluginInfoTest with ResourceProviderMixin, _ContextRoot {
|
|
late TestNotificationManager notificationManager;
|
|
String pluginPath = '/pluginDir';
|
|
String executionPath = '/pluginDir/bin/plugin.dart';
|
|
String packagesPath = '/pluginDir/.packages';
|
|
late DiscoveredPluginInfo plugin;
|
|
|
|
void setUp() {
|
|
notificationManager = TestNotificationManager();
|
|
plugin = DiscoveredPluginInfo(pluginPath, executionPath, packagesPath,
|
|
notificationManager, InstrumentationService.NULL_SERVICE);
|
|
}
|
|
|
|
void test_addContextRoot() {
|
|
var contextRoot1 = _newContextRoot('/pkg1');
|
|
var optionsFile = getFile('/pkg1/analysis_options.yaml');
|
|
contextRoot1.optionsFile = optionsFile;
|
|
var session = PluginSession(plugin);
|
|
var channel = TestServerCommunicationChannel(session);
|
|
plugin.currentSession = session;
|
|
plugin.addContextRoot(contextRoot1);
|
|
expect(plugin.contextRoots, [contextRoot1]);
|
|
plugin.addContextRoot(contextRoot1);
|
|
expect(plugin.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(plugin.path, pluginPath);
|
|
expect(plugin.executionPath, executionPath);
|
|
expect(plugin.notificationManager, notificationManager);
|
|
expect(plugin.contextRoots, isEmpty);
|
|
expect(plugin.currentSession, isNull);
|
|
}
|
|
|
|
void test_removeContextRoot() {
|
|
var contextRoot1 = _newContextRoot('/pkg1');
|
|
var contextRoot2 = _newContextRoot('/pkg2');
|
|
plugin.addContextRoot(contextRoot1);
|
|
expect(plugin.contextRoots, unorderedEquals([contextRoot1]));
|
|
plugin.addContextRoot(contextRoot2);
|
|
expect(plugin.contextRoots, unorderedEquals([contextRoot1, contextRoot2]));
|
|
plugin.removeContextRoot(contextRoot1);
|
|
expect(plugin.contextRoots, unorderedEquals([contextRoot2]));
|
|
plugin.removeContextRoot(contextRoot2);
|
|
expect(plugin.contextRoots, isEmpty);
|
|
}
|
|
|
|
@failingTest
|
|
Future<void> test_start_notRunning() {
|
|
fail('Not tested');
|
|
}
|
|
|
|
Future<void> test_start_running() async {
|
|
plugin.currentSession = PluginSession(plugin);
|
|
try {
|
|
await plugin.start('', '');
|
|
fail('Expected a StateError');
|
|
} on StateError {
|
|
// Expected.
|
|
}
|
|
}
|
|
|
|
void test_stop_notRunning() {
|
|
expect(() => plugin.stop(), throwsStateError);
|
|
}
|
|
|
|
Future<void> test_stop_running() async {
|
|
var session = PluginSession(plugin);
|
|
var channel = TestServerCommunicationChannel(session);
|
|
plugin.currentSession = session;
|
|
await plugin.stop();
|
|
expect(plugin.currentSession, isNull);
|
|
expect(channel.sentRequests, hasLength(1));
|
|
expect(channel.sentRequests[0].method, 'plugin.shutdown');
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class PluginManagerFromDiskTest extends PluginTestSupport {
|
|
String byteStorePath = '/byteStore';
|
|
late PluginManager manager;
|
|
|
|
@override
|
|
void setUp() {
|
|
super.setUp();
|
|
manager = PluginManager(resourceProvider, byteStorePath, '',
|
|
notificationManager, InstrumentationService.NULL_SERVICE);
|
|
}
|
|
|
|
@SkippedTest(
|
|
reason: 'flaky timeouts',
|
|
issue: 'https://github.com/dart-lang/sdk/issues/38629')
|
|
Future<void> test_addPluginToContextRoot() async {
|
|
var pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
|
|
var pkgPath = pkg1Dir.resolveSymbolicLinksSync();
|
|
await withPlugin(test: (String pluginPath) async {
|
|
var contextRoot = _newContextRoot(pkgPath);
|
|
await manager.addPluginToContextRoot(contextRoot, pluginPath);
|
|
await manager.stopAll();
|
|
});
|
|
pkg1Dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
@failingTest
|
|
Future<void> test_addPluginToContextRoot_pubspec() async {
|
|
// We can't successfully run pub until after the analyzer_plugin package has
|
|
// been published.
|
|
fail('Cannot run pub');
|
|
// io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
|
|
// String pkgPath = pkg1Dir.resolveSymbolicLinksSync();
|
|
// await withPubspecPlugin(test: (String pluginPath) async {
|
|
// ContextRoot contextRoot = _newContextRoot(pkgPath);
|
|
// await manager.addPluginToContextRoot(contextRoot, pluginPath);
|
|
// String packagesPath =
|
|
// resourceProvider.pathContext.join(pluginPath, '.packages');
|
|
// File packagesFile = resourceProvider.getFile(packagesPath);
|
|
// bool exists = packagesFile.exists;
|
|
// await manager.stopAll();
|
|
// expect(exists, isTrue, reason: '.packages file was not created');
|
|
// });
|
|
// pkg1Dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
@SkippedTest(
|
|
reason: 'flaky timeouts',
|
|
issue: 'https://github.com/dart-lang/sdk/issues/38629')
|
|
Future<void> test_broadcastRequest_many() async {
|
|
var pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
|
|
var pkgPath = pkg1Dir.resolveSymbolicLinksSync();
|
|
await withPlugin(
|
|
pluginName: 'plugin1',
|
|
test: (String plugin1Path) async {
|
|
await withPlugin(
|
|
pluginName: 'plugin2',
|
|
test: (String plugin2Path) async {
|
|
var contextRoot = _newContextRoot(pkgPath);
|
|
await manager.addPluginToContextRoot(contextRoot, plugin1Path);
|
|
await manager.addPluginToContextRoot(contextRoot, plugin2Path);
|
|
|
|
var responses = manager.broadcastRequest(
|
|
CompletionGetSuggestionsParams('/pkg1/lib/pkg1.dart', 100),
|
|
contextRoot: contextRoot);
|
|
expect(responses, hasLength(2));
|
|
|
|
await manager.stopAll();
|
|
});
|
|
});
|
|
pkg1Dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
@SkippedTest(
|
|
reason: 'flaky timeouts',
|
|
issue: 'https://github.com/dart-lang/sdk/issues/38629')
|
|
Future<void> test_broadcastRequest_many_noContextRoot() async {
|
|
var pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
|
|
var pkgPath = pkg1Dir.resolveSymbolicLinksSync();
|
|
await withPlugin(
|
|
pluginName: 'plugin1',
|
|
test: (String plugin1Path) async {
|
|
await withPlugin(
|
|
pluginName: 'plugin2',
|
|
test: (String plugin2Path) async {
|
|
var contextRoot = _newContextRoot(pkgPath);
|
|
await manager.addPluginToContextRoot(contextRoot, plugin1Path);
|
|
await manager.addPluginToContextRoot(contextRoot, plugin2Path);
|
|
|
|
var responses = manager.broadcastRequest(
|
|
CompletionGetSuggestionsParams('/pkg1/lib/pkg1.dart', 100));
|
|
expect(responses, hasLength(2));
|
|
|
|
await manager.stopAll();
|
|
});
|
|
});
|
|
pkg1Dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
@SkippedTest(
|
|
reason: 'flaky timeouts',
|
|
issue: 'https://github.com/dart-lang/sdk/issues/38629')
|
|
Future<void> test_broadcastRequest_noCurrentSession() async {
|
|
var pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
|
|
var pkgPath = pkg1Dir.resolveSymbolicLinksSync();
|
|
await withPlugin(
|
|
pluginName: 'plugin1',
|
|
content: '(invalid content here)',
|
|
test: (String plugin1Path) async {
|
|
var contextRoot = _newContextRoot(pkgPath);
|
|
await manager.addPluginToContextRoot(contextRoot, plugin1Path);
|
|
|
|
var responses = manager.broadcastRequest(
|
|
CompletionGetSuggestionsParams('/pkg1/lib/pkg1.dart', 100),
|
|
contextRoot: contextRoot);
|
|
expect(responses, hasLength(0));
|
|
|
|
await manager.stopAll();
|
|
});
|
|
pkg1Dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
@SkippedTest(
|
|
reason: 'flaky timeouts',
|
|
issue: 'https://github.com/dart-lang/sdk/issues/38629')
|
|
Future<void> test_broadcastWatchEvent() async {
|
|
var pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
|
|
var pkgPath = pkg1Dir.resolveSymbolicLinksSync();
|
|
await withPlugin(
|
|
pluginName: 'plugin1',
|
|
test: (String plugin1Path) async {
|
|
var contextRoot = _newContextRoot(pkgPath);
|
|
await manager.addPluginToContextRoot(contextRoot, plugin1Path);
|
|
var plugins = manager.pluginsForContextRoot(contextRoot);
|
|
expect(plugins, hasLength(1));
|
|
var watchEvent = watcher.WatchEvent(
|
|
watcher.ChangeType.MODIFY, path.join(pkgPath, 'lib', 'lib.dart'));
|
|
var responses = await manager.broadcastWatchEvent(watchEvent);
|
|
expect(responses, hasLength(1));
|
|
var response = await responses[0];
|
|
expect(response, isNotNull);
|
|
expect(response.error, isNull);
|
|
await manager.stopAll();
|
|
});
|
|
pkg1Dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
@SkippedTest(
|
|
reason: 'flaky timeouts',
|
|
issue: 'https://github.com/dart-lang/sdk/issues/38629')
|
|
Future<void> test_pluginsForContextRoot_multiple() async {
|
|
var pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
|
|
var pkgPath = pkg1Dir.resolveSymbolicLinksSync();
|
|
await withPlugin(
|
|
pluginName: 'plugin1',
|
|
test: (String plugin1Path) async {
|
|
await withPlugin(
|
|
pluginName: 'plugin2',
|
|
test: (String plugin2Path) async {
|
|
var contextRoot = _newContextRoot(pkgPath);
|
|
await manager.addPluginToContextRoot(contextRoot, plugin1Path);
|
|
await manager.addPluginToContextRoot(contextRoot, plugin2Path);
|
|
|
|
var plugins = manager.pluginsForContextRoot(contextRoot);
|
|
expect(plugins, hasLength(2));
|
|
var paths = plugins
|
|
.map((PluginInfo plugin) => plugin.pluginId)
|
|
.toList();
|
|
expect(paths, unorderedEquals([plugin1Path, plugin2Path]));
|
|
|
|
await manager.stopAll();
|
|
});
|
|
});
|
|
pkg1Dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
@SkippedTest(
|
|
reason: 'flaky timeouts',
|
|
issue: 'https://github.com/dart-lang/sdk/issues/38629')
|
|
Future<void> test_pluginsForContextRoot_one() async {
|
|
var pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
|
|
var pkgPath = pkg1Dir.resolveSymbolicLinksSync();
|
|
await withPlugin(test: (String pluginPath) async {
|
|
var contextRoot = _newContextRoot(pkgPath);
|
|
await manager.addPluginToContextRoot(contextRoot, pluginPath);
|
|
|
|
var plugins = manager.pluginsForContextRoot(contextRoot);
|
|
expect(plugins, hasLength(1));
|
|
expect(plugins[0].pluginId, pluginPath);
|
|
|
|
await manager.stopAll();
|
|
});
|
|
pkg1Dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
@SkippedTest(
|
|
reason: 'flaky timeouts',
|
|
issue: 'https://github.com/dart-lang/sdk/issues/38629')
|
|
Future<void> test_removedContextRoot() async {
|
|
var pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
|
|
var pkgPath = pkg1Dir.resolveSymbolicLinksSync();
|
|
await withPlugin(test: (String pluginPath) async {
|
|
var contextRoot = _newContextRoot(pkgPath);
|
|
await manager.addPluginToContextRoot(contextRoot, pluginPath);
|
|
|
|
manager.removedContextRoot(contextRoot);
|
|
|
|
await manager.stopAll();
|
|
});
|
|
pkg1Dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
@TestTimeout(Timeout.factor(4))
|
|
@SkippedTest(
|
|
reason: 'flaky timeouts',
|
|
issue: 'https://github.com/dart-lang/sdk/issues/38629')
|
|
Future<void> test_restartPlugins() async {
|
|
var pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
|
|
var pkg1Path = pkg1Dir.resolveSymbolicLinksSync();
|
|
var pkg2Dir = io.Directory.systemTemp.createTempSync('pkg2');
|
|
var pkg2Path = pkg2Dir.resolveSymbolicLinksSync();
|
|
await withPlugin(
|
|
pluginName: 'plugin1',
|
|
test: (String plugin1Path) async {
|
|
await withPlugin(
|
|
pluginName: 'plugin2',
|
|
test: (String plugin2Path) async {
|
|
var contextRoot1 = _newContextRoot(pkg1Path);
|
|
var contextRoot2 = _newContextRoot(pkg2Path);
|
|
await manager.addPluginToContextRoot(contextRoot1, plugin1Path);
|
|
await manager.addPluginToContextRoot(contextRoot1, plugin2Path);
|
|
await manager.addPluginToContextRoot(contextRoot2, plugin1Path);
|
|
|
|
await manager.restartPlugins();
|
|
var plugins = manager.plugins;
|
|
expect(plugins, hasLength(2));
|
|
expect(plugins[0].currentSession, isNotNull);
|
|
expect(plugins[1].currentSession, isNotNull);
|
|
if (plugins[0].pluginId.contains('plugin1')) {
|
|
expect(plugins[0].contextRoots,
|
|
unorderedEquals([contextRoot1, contextRoot2]));
|
|
expect(
|
|
plugins[1].contextRoots, unorderedEquals([contextRoot1]));
|
|
} else {
|
|
expect(
|
|
plugins[0].contextRoots, unorderedEquals([contextRoot1]));
|
|
expect(plugins[1].contextRoots,
|
|
unorderedEquals([contextRoot1, contextRoot2]));
|
|
}
|
|
await manager.stopAll();
|
|
});
|
|
});
|
|
pkg1Dir.deleteSync(recursive: true);
|
|
}
|
|
|
|
ContextRootImpl _newContextRoot(String root) {
|
|
throw UnimplementedError();
|
|
}
|
|
}
|
|
|
|
@reflectiveTest
|
|
class PluginManagerTest with ResourceProviderMixin, _ContextRoot {
|
|
late String byteStorePath;
|
|
late String sdkPath;
|
|
late TestNotificationManager notificationManager;
|
|
late PluginManager manager;
|
|
|
|
void setUp() {
|
|
byteStorePath = resourceProvider.convertPath('/byteStore');
|
|
sdkPath = resourceProvider.convertPath('/sdk');
|
|
notificationManager = TestNotificationManager();
|
|
manager = PluginManager(resourceProvider, byteStorePath, sdkPath,
|
|
notificationManager, InstrumentationService.NULL_SERVICE);
|
|
}
|
|
|
|
void test_broadcastRequest_none() {
|
|
var contextRoot = _newContextRoot('/pkg1');
|
|
var responses = manager.broadcastRequest(
|
|
CompletionGetSuggestionsParams('/pkg1/lib/pkg1.dart', 100),
|
|
contextRoot: contextRoot);
|
|
expect(responses, hasLength(0));
|
|
}
|
|
|
|
void test_creation() {
|
|
expect(manager.resourceProvider, resourceProvider);
|
|
expect(manager.byteStorePath, byteStorePath);
|
|
expect(manager.sdkPath, sdkPath);
|
|
expect(manager.notificationManager, notificationManager);
|
|
}
|
|
|
|
void test_pathsFor_withPackageConfigJsonFile() {
|
|
//
|
|
// Build the minimal directory structure for a plugin package that includes
|
|
// a '.dart_tool/package_config.json' file.
|
|
//
|
|
var pluginDirPath = newFolder('/plugin').path;
|
|
var pluginFile = newFile('/plugin/bin/plugin.dart', '');
|
|
var packageConfigFile = newPackageConfigJsonFile('/plugin', '');
|
|
//
|
|
// Test path computation.
|
|
//
|
|
var files = manager.filesFor(pluginDirPath);
|
|
expect(files.execution, pluginFile);
|
|
expect(files.packages, packageConfigFile);
|
|
}
|
|
|
|
void test_pathsFor_withPubspec_inBlazeWorkspace() {
|
|
//
|
|
// Build a Blaze workspace containing four packages, including the plugin.
|
|
//
|
|
newFile('/workspaceRoot/${file_paths.blazeWorkspaceMarker}', '');
|
|
newFolder('/workspaceRoot/blaze-bin');
|
|
newFolder('/workspaceRoot/blaze-genfiles');
|
|
|
|
String newPackage(String packageName, [List<String>? dependencies]) {
|
|
var packageRoot =
|
|
newFolder('/workspaceRoot/third_party/dart/$packageName').path;
|
|
newFile('$packageRoot/lib/$packageName.dart', '');
|
|
var buffer = StringBuffer();
|
|
if (dependencies != null) {
|
|
buffer.writeln('dependencies:');
|
|
for (var dependency in dependencies) {
|
|
buffer.writeln(' $dependency: any');
|
|
}
|
|
}
|
|
newPubspecYamlFile(packageRoot, buffer.toString());
|
|
return packageRoot;
|
|
}
|
|
|
|
var pluginDirPath = newPackage('plugin', ['b', 'c']);
|
|
var bRootPath = newPackage('b', ['d']);
|
|
var cRootPath = newPackage('c', ['d']);
|
|
var dRootPath = newPackage('d');
|
|
var pluginFile = newFile('$pluginDirPath/bin/plugin.dart', '');
|
|
//
|
|
// Test path computation.
|
|
//
|
|
var files = manager.filesFor(pluginDirPath);
|
|
expect(files.execution, pluginFile);
|
|
var packagesFile = files.packages;
|
|
expect(packagesFile.exists, isTrue);
|
|
|
|
var content = packagesFile.readAsStringSync();
|
|
expect(content, '''
|
|
{
|
|
"configVersion": 2,
|
|
"packages": [
|
|
{
|
|
"name": "b",
|
|
"rootUri": "${toUriStr(bRootPath)}",
|
|
"packageUri": "lib/"
|
|
},
|
|
{
|
|
"name": "c",
|
|
"rootUri": "${toUriStr(cRootPath)}",
|
|
"packageUri": "lib/"
|
|
},
|
|
{
|
|
"name": "d",
|
|
"rootUri": "${toUriStr(dRootPath)}",
|
|
"packageUri": "lib/"
|
|
},
|
|
{
|
|
"name": "plugin",
|
|
"rootUri": "${toUriStr(pluginDirPath)}",
|
|
"packageUri": "lib/"
|
|
}
|
|
]
|
|
}
|
|
''');
|
|
}
|
|
|
|
void test_pluginsForContextRoot_none() {
|
|
var contextRoot = _newContextRoot('/pkg1');
|
|
expect(manager.pluginsForContextRoot(contextRoot), isEmpty);
|
|
}
|
|
|
|
void test_stopAll_none() {
|
|
manager.stopAll();
|
|
}
|
|
}
|
|
|
|
@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();
|
|
PluginInfo plugin = DiscoveredPluginInfo(
|
|
pluginPath,
|
|
mainPath,
|
|
packagesPath,
|
|
notificationManager,
|
|
InstrumentationService.NULL_SERVICE);
|
|
var session = PluginSession(plugin);
|
|
plugin.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 PluginInfo plugin;
|
|
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');
|
|
plugin = DiscoveredPluginInfo(pluginPath, executionPath, packagesPath,
|
|
notificationManager, InstrumentationService.NULL_SERVICE);
|
|
session = PluginSession(plugin);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
@failingTest
|
|
void test_handleOnError() {
|
|
session.handleOnError(<String>['message', 'trace']);
|
|
fail('The method handleOnError is not implemented');
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
Future<void> test_start_running() async {
|
|
TestServerCommunicationChannel(session);
|
|
try {
|
|
await session.start('', '');
|
|
fail('Expected a StateError to be thrown');
|
|
} on StateError {
|
|
// Expected behavior
|
|
}
|
|
}
|
|
|
|
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');
|
|
}
|
|
}
|
|
|
|
/// A class designed to be used as a superclass for test classes that define
|
|
/// tests that require plugins to be created on disk.
|
|
abstract class PluginTestSupport {
|
|
late PhysicalResourceProvider resourceProvider;
|
|
late TestNotificationManager notificationManager;
|
|
|
|
/// The content to be used for the '.packages' file, or `null` if the content
|
|
/// has not yet been computed.
|
|
String? _packagesFileContent;
|
|
|
|
void setUp() {
|
|
resourceProvider = PhysicalResourceProvider.INSTANCE;
|
|
notificationManager = TestNotificationManager();
|
|
}
|
|
|
|
/// Create a directory structure representing a plugin on disk, run the given
|
|
/// [test] function, and then remove the directory. The directory will have
|
|
/// the following structure:
|
|
/// ```
|
|
/// pluginDirectory
|
|
/// .packages
|
|
/// bin
|
|
/// plugin.dart
|
|
/// ```
|
|
/// The name of the plugin directory will be the [pluginName], if one is
|
|
/// provided (in order to allow more than one plugin to be created by a single
|
|
/// test). The 'plugin.dart' file will contain the given [content], or default
|
|
/// content that implements a minimal plugin if the contents are not given.
|
|
/// The [test] function will be passed the path of the directory that was
|
|
/// created.
|
|
Future<void> withPlugin(
|
|
{String? content,
|
|
String? pluginName,
|
|
required Future<void> Function(String) test}) async {
|
|
var tempDirectory =
|
|
io.Directory.systemTemp.createTempSync(pluginName ?? 'test_plugin');
|
|
try {
|
|
var pluginPath = tempDirectory.resolveSymbolicLinksSync();
|
|
//
|
|
// Create a .packages file.
|
|
//
|
|
var packagesFile = io.File(path.join(pluginPath, '.packages'));
|
|
packagesFile.writeAsStringSync(_getPackagesFileContent());
|
|
//
|
|
// Create the 'bin' directory.
|
|
//
|
|
var binPath = path.join(pluginPath, 'bin');
|
|
io.Directory(binPath).createSync();
|
|
//
|
|
// Create the 'plugin.dart' file.
|
|
//
|
|
var pluginFile = io.File(path.join(binPath, 'plugin.dart'));
|
|
pluginFile.writeAsStringSync(content ?? _defaultPluginContent());
|
|
//
|
|
// Run the actual test code.
|
|
//
|
|
await test(pluginPath);
|
|
} finally {
|
|
tempDirectory.deleteSync(recursive: true);
|
|
}
|
|
}
|
|
|
|
/// Convert the [sdkPackageMap] into a plugin-specific map by applying the
|
|
/// given relative path [delta] to each line.
|
|
String _convertPackageMap(String sdkDirPath, List<String> sdkPackageMap) {
|
|
var buffer = StringBuffer();
|
|
for (var line in sdkPackageMap) {
|
|
if (!line.startsWith('#')) {
|
|
var index = line.indexOf(':');
|
|
var packageName = line.substring(0, index + 1);
|
|
var relativePath = line.substring(index + 1);
|
|
var absolutePath = path.join(sdkDirPath, relativePath);
|
|
// Convert to file:/// URI since that's how absolute paths in
|
|
// .packages must be for windows
|
|
absolutePath = Uri.file(absolutePath).toString();
|
|
buffer.write(packageName);
|
|
buffer.writeln(absolutePath);
|
|
}
|
|
}
|
|
return buffer.toString();
|
|
}
|
|
|
|
/// The default content of the plugin. This is a minimal plugin that will only
|
|
/// respond correctly to version checks and to shutdown requests.
|
|
String _defaultPluginContent() {
|
|
return r'''
|
|
import 'dart:async';
|
|
import 'dart:isolate';
|
|
import 'package:analyzer/file_system/file_system.dart';
|
|
import 'package:analyzer/file_system/physical_file_system.dart';
|
|
import 'package:analyzer/src/dart/analysis/driver.dart';
|
|
import 'package:analyzer_plugin/plugin/plugin.dart';
|
|
import 'package:analyzer_plugin/protocol/protocol_generated.dart';
|
|
import 'package:analyzer_plugin/starter.dart';
|
|
import 'package:pub_semver/pub_semver.dart';
|
|
|
|
void main(List<String> args, SendPort sendPort) {
|
|
MinimalPlugin plugin = new MinimalPlugin(PhysicalResourceProvider.INSTANCE);
|
|
new ServerPluginStarter(plugin).start(sendPort);
|
|
}
|
|
|
|
class MinimalPlugin extends ServerPlugin {
|
|
MinimalPlugin(ResourceProvider provider) : super(provider);
|
|
|
|
@override
|
|
List<String> get fileGlobsToAnalyze => <String>['**/*.dart'];
|
|
|
|
@override
|
|
String get name => 'minimal';
|
|
|
|
@override
|
|
String get version => '0.0.1';
|
|
|
|
@override
|
|
AnalysisDriverGeneric createAnalysisDriver(ContextRoot contextRoot) => null;
|
|
|
|
@override
|
|
Future<AnalysisHandleWatchEventsResult> handleAnalysisHandleWatchEvents(
|
|
AnalysisHandleWatchEventsParams parameters) async =>
|
|
new AnalysisHandleWatchEventsResult();
|
|
|
|
@override
|
|
bool isCompatibleWith(Version serverVersion) => true;
|
|
}
|
|
''';
|
|
}
|
|
|
|
/// Return the content to be used for the '.packages' file.
|
|
String _getPackagesFileContent() {
|
|
var packagesFileContent = _packagesFileContent;
|
|
if (packagesFileContent == null) {
|
|
var sdkPackagesFile = io.File(_sdkPackagesPath());
|
|
var sdkPackageMap = sdkPackagesFile.readAsLinesSync();
|
|
packagesFileContent = _packagesFileContent =
|
|
_convertPackageMap(path.dirname(sdkPackagesFile.path), sdkPackageMap);
|
|
}
|
|
return packagesFileContent;
|
|
}
|
|
|
|
/// Return the path to the '.packages' file in the root of the SDK checkout.
|
|
String _sdkPackagesPath() {
|
|
var packagesPath = io.Platform.script.toFilePath();
|
|
while (packagesPath.isNotEmpty &&
|
|
path.basename(packagesPath) != 'analysis_server') {
|
|
packagesPath = path.dirname(packagesPath);
|
|
}
|
|
packagesPath = path.dirname(packagesPath);
|
|
packagesPath = path.dirname(packagesPath);
|
|
return path.join(packagesPath, '.packages');
|
|
}
|
|
}
|
|
|
|
class TestNotificationManager implements AbstractNotificationManager {
|
|
List<Notification> notifications = <Notification>[];
|
|
|
|
Map<String, Map<String, List<AnalysisError>>> recordedErrors =
|
|
<String, Map<String, List<AnalysisError>>>{};
|
|
|
|
@override
|
|
void handlePluginNotification(String pluginId, Notification notification) {
|
|
notifications.add(notification);
|
|
}
|
|
|
|
@override
|
|
dynamic noSuchMethod(Invocation invocation) {
|
|
fail('Unexpected invocation of ${invocation.memberName}');
|
|
}
|
|
|
|
@override
|
|
void recordAnalysisErrors(
|
|
String pluginId, String filePath, List<AnalysisError> errorData) {
|
|
recordedErrors.putIfAbsent(
|
|
pluginId, () => <String, List<AnalysisError>>{})[filePath] = errorData;
|
|
}
|
|
}
|
|
|
|
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 root) {
|
|
root = convertPath(root);
|
|
return ContextRootImpl(
|
|
resourceProvider,
|
|
resourceProvider.getFolder(root),
|
|
BasicWorkspace.find(resourceProvider, Packages.empty, root),
|
|
);
|
|
}
|
|
}
|