blob: 384cafc9bb556f871cb0fce4e0903a309814f6de [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:async';
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/file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/instrumentation/instrumentation.dart';
import 'package:analyzer/src/context/context_root.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';
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;
main() {
defineReflectiveSuite(() {
defineReflectiveTests(BuiltInPluginInfoTest);
defineReflectiveTests(DiscoveredPluginInfoTest);
defineReflectiveTests(PluginManagerTest);
defineReflectiveTests(PluginManagerFromDiskTest);
defineReflectiveTests(PluginSessionTest);
defineReflectiveTests(PluginSessionFromDiskTest);
});
}
ContextRoot _newContextRoot(String root, {List<String> exclude = const []}) {
return new ContextRoot(root, exclude, pathContext: path.context);
}
@reflectiveTest
class BuiltInPluginInfoTest {
TestNotificationManager notificationManager;
BuiltInPluginInfo plugin;
void setUp() {
notificationManager = new TestNotificationManager();
plugin = new BuiltInPluginInfo(null, 'test plugin', notificationManager,
InstrumentationService.NULL_SERVICE);
}
test_addContextRoot() {
ContextRoot contextRoot1 = _newContextRoot('/pkg1');
plugin.addContextRoot(contextRoot1);
expect(plugin.contextRoots, [contextRoot1]);
plugin.addContextRoot(contextRoot1);
expect(plugin.contextRoots, [contextRoot1]);
}
test_creation() {
expect(plugin.pluginId, 'test plugin');
expect(plugin.notificationManager, notificationManager);
expect(plugin.contextRoots, isEmpty);
expect(plugin.currentSession, isNull);
}
test_removeContextRoot() {
ContextRoot contextRoot1 = _newContextRoot('/pkg1');
ContextRoot 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
test_start_notRunning() {
fail('Not tested');
}
test_start_running() async {
plugin.currentSession = new PluginSession(plugin);
try {
await plugin.start('', '');
fail('Expected a StateError');
} on StateError {
// Expected.
}
}
test_stop_notRunning() {
expect(() => plugin.stop(), throwsStateError);
}
test_stop_running() async {
PluginSession session = new PluginSession(plugin);
TestServerCommunicationChannel channel =
new 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 {
TestNotificationManager notificationManager;
String pluginPath = '/pluginDir';
String executionPath = '/pluginDir/bin/plugin.dart';
String packagesPath = '/pluginDir/.packages';
DiscoveredPluginInfo plugin;
void setUp() {
notificationManager = new TestNotificationManager();
plugin = new DiscoveredPluginInfo(pluginPath, executionPath, packagesPath,
notificationManager, InstrumentationService.NULL_SERVICE);
}
test_addContextRoot() {
String optionsFilePath = '/pkg1/analysis_options.yaml';
ContextRoot contextRoot1 = _newContextRoot('/pkg1');
contextRoot1.optionsFilePath = optionsFilePath;
PluginSession session = new PluginSession(plugin);
TestServerCommunicationChannel channel =
new TestServerCommunicationChannel(session);
plugin.currentSession = session;
plugin.addContextRoot(contextRoot1);
expect(plugin.contextRoots, [contextRoot1]);
plugin.addContextRoot(contextRoot1);
expect(plugin.contextRoots, [contextRoot1]);
List<Request> sentRequests = channel.sentRequests;
expect(sentRequests, hasLength(1));
List<Map> roots = sentRequests[0].params['roots'];
expect(roots[0]['optionsFile'], optionsFilePath);
}
test_creation() {
expect(plugin.path, pluginPath);
expect(plugin.executionPath, executionPath);
expect(plugin.notificationManager, notificationManager);
expect(plugin.contextRoots, isEmpty);
expect(plugin.currentSession, isNull);
}
test_removeContextRoot() {
ContextRoot contextRoot1 = _newContextRoot('/pkg1');
ContextRoot 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
test_start_notRunning() {
fail('Not tested');
}
test_start_running() async {
plugin.currentSession = new PluginSession(plugin);
try {
await plugin.start('', '');
fail('Expected a StateError');
} on StateError {
// Expected.
}
}
test_stop_notRunning() {
expect(() => plugin.stop(), throwsStateError);
}
test_stop_running() async {
PluginSession session = new PluginSession(plugin);
TestServerCommunicationChannel channel =
new 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';
PluginManager manager;
void setUp() {
super.setUp();
manager = new PluginManager(resourceProvider, byteStorePath, '',
notificationManager, InstrumentationService.NULL_SERVICE);
}
@SkippedTest(
reason: 'flaky timeouts',
issue: 'https://github.com/dart-lang/sdk/issues/38629')
test_addPluginToContextRoot() async {
io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
String pkgPath = pkg1Dir.resolveSymbolicLinksSync();
await withPlugin(test: (String pluginPath) async {
ContextRoot contextRoot = _newContextRoot(pkgPath);
await manager.addPluginToContextRoot(contextRoot, pluginPath);
await manager.stopAll();
});
pkg1Dir.deleteSync(recursive: true);
}
@failingTest
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')
test_broadcastRequest_many() async {
io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
String pkgPath = pkg1Dir.resolveSymbolicLinksSync();
await withPlugin(
pluginName: 'plugin1',
test: (String plugin1Path) async {
await withPlugin(
pluginName: 'plugin2',
test: (String plugin2Path) async {
ContextRoot contextRoot = _newContextRoot(pkgPath);
await manager.addPluginToContextRoot(contextRoot, plugin1Path);
await manager.addPluginToContextRoot(contextRoot, plugin2Path);
Map<PluginInfo, Future<Response>> responses =
manager.broadcastRequest(
new 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')
test_broadcastRequest_many_noContextRoot() async {
io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
String pkgPath = pkg1Dir.resolveSymbolicLinksSync();
await withPlugin(
pluginName: 'plugin1',
test: (String plugin1Path) async {
await withPlugin(
pluginName: 'plugin2',
test: (String plugin2Path) async {
ContextRoot contextRoot = _newContextRoot(pkgPath);
await manager.addPluginToContextRoot(contextRoot, plugin1Path);
await manager.addPluginToContextRoot(contextRoot, plugin2Path);
Map<PluginInfo, Future<Response>> responses =
manager.broadcastRequest(new 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')
test_broadcastWatchEvent() async {
io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
String pkgPath = pkg1Dir.resolveSymbolicLinksSync();
await withPlugin(
pluginName: 'plugin1',
test: (String plugin1Path) async {
ContextRoot contextRoot = _newContextRoot(pkgPath);
await manager.addPluginToContextRoot(contextRoot, plugin1Path);
List<PluginInfo> plugins = manager.pluginsForContextRoot(contextRoot);
expect(plugins, hasLength(1));
watcher.WatchEvent watchEvent = new watcher.WatchEvent(
watcher.ChangeType.MODIFY, path.join(pkgPath, 'lib', 'lib.dart'));
List<Future<Response>> responses =
await manager.broadcastWatchEvent(watchEvent);
expect(responses, hasLength(1));
Response 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')
test_pluginsForContextRoot_multiple() async {
io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
String pkgPath = pkg1Dir.resolveSymbolicLinksSync();
await withPlugin(
pluginName: 'plugin1',
test: (String plugin1Path) async {
await withPlugin(
pluginName: 'plugin2',
test: (String plugin2Path) async {
ContextRoot contextRoot = _newContextRoot(pkgPath);
await manager.addPluginToContextRoot(contextRoot, plugin1Path);
await manager.addPluginToContextRoot(contextRoot, plugin2Path);
List<PluginInfo> plugins =
manager.pluginsForContextRoot(contextRoot);
expect(plugins, hasLength(2));
List<String> 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')
test_pluginsForContextRoot_one() async {
io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
String pkgPath = pkg1Dir.resolveSymbolicLinksSync();
await withPlugin(test: (String pluginPath) async {
ContextRoot contextRoot = _newContextRoot(pkgPath);
await manager.addPluginToContextRoot(contextRoot, pluginPath);
List<PluginInfo> 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')
test_removedContextRoot() async {
io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
String pkgPath = pkg1Dir.resolveSymbolicLinksSync();
await withPlugin(test: (String pluginPath) async {
ContextRoot contextRoot = _newContextRoot(pkgPath);
await manager.addPluginToContextRoot(contextRoot, pluginPath);
manager.removedContextRoot(contextRoot);
await manager.stopAll();
});
pkg1Dir.deleteSync(recursive: true);
}
@TestTimeout(const Timeout.factor(4))
@SkippedTest(
reason: 'flaky timeouts',
issue: 'https://github.com/dart-lang/sdk/issues/38629')
test_restartPlugins() async {
io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1');
String pkg1Path = pkg1Dir.resolveSymbolicLinksSync();
io.Directory pkg2Dir = io.Directory.systemTemp.createTempSync('pkg2');
String pkg2Path = pkg2Dir.resolveSymbolicLinksSync();
await withPlugin(
pluginName: 'plugin1',
test: (String plugin1Path) async {
await withPlugin(
pluginName: 'plugin2',
test: (String plugin2Path) async {
ContextRoot contextRoot1 = _newContextRoot(pkg1Path);
ContextRoot contextRoot2 = _newContextRoot(pkg2Path);
await manager.addPluginToContextRoot(contextRoot1, plugin1Path);
await manager.addPluginToContextRoot(contextRoot1, plugin2Path);
await manager.addPluginToContextRoot(contextRoot2, plugin1Path);
await manager.restartPlugins();
List<PluginInfo> 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);
}
}
@reflectiveTest
class PluginManagerTest with ResourceProviderMixin {
String byteStorePath;
String sdkPath;
TestNotificationManager notificationManager;
PluginManager manager;
void setUp() {
byteStorePath = resourceProvider.convertPath('/byteStore');
sdkPath = resourceProvider.convertPath('/sdk');
notificationManager = new TestNotificationManager();
manager = new PluginManager(resourceProvider, byteStorePath, sdkPath,
notificationManager, InstrumentationService.NULL_SERVICE);
}
void test_broadcastRequest_none() {
ContextRoot contextRoot = _newContextRoot('/pkg1');
Map<PluginInfo, Future<Response>> responses = manager.broadcastRequest(
new 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_withPackagesFile() {
//
// Build the minimal directory structure for a plugin package that includes
// a .packages file.
//
String pluginDirPath = newFolder('/plugin').path;
String pluginFilePath = newFile('/plugin/bin/plugin.dart').path;
String packagesFilePath = newFile('/plugin/.packages').path;
//
// Test path computation.
//
List<String> paths = manager.pathsFor(pluginDirPath);
expect(paths, hasLength(2));
expect(paths[0], pluginFilePath);
expect(paths[1], packagesFilePath);
}
void test_pathsFor_withPubspec_inBazelWorkspace() {
//
// Build a Bazel workspace containing four packages, including the plugin.
//
newFile('/workspaceRoot/WORKSPACE');
newFolder('/workspaceRoot/bazel-bin');
newFolder('/workspaceRoot/bazel-genfiles');
String newPackage(String packageName, [List<String> dependencies]) {
String packageRoot =
newFolder('/workspaceRoot/third_party/dart/$packageName').path;
newFile('$packageRoot/lib/$packageName.dart');
StringBuffer buffer = new StringBuffer();
if (dependencies != null) {
buffer.writeln('dependencies:');
for (String dependency in dependencies) {
buffer.writeln(' $dependency: any');
}
}
newFile('$packageRoot/pubspec.yaml', content: buffer.toString());
return packageRoot;
}
String pluginDirPath = newPackage('plugin', ['b', 'c']);
newPackage('b', ['d']);
newPackage('c', ['d']);
newPackage('d');
String pluginFilePath = newFile('$pluginDirPath/bin/plugin.dart').path;
//
// Test path computation.
//
List<String> paths = manager.pathsFor(pluginDirPath);
expect(paths, hasLength(2));
expect(paths[0], pluginFilePath);
File packagesFile = getFile(paths[1]);
expect(packagesFile.exists, isTrue);
String content = packagesFile.readAsStringSync();
List<String> lines = content.split('\n');
String asFileUri(String input) =>
new Uri.file(convertPath(input)).toString();
expect(
lines,
unorderedEquals([
'plugin:${asFileUri('/workspaceRoot/third_party/dart/plugin/lib')}',
'b:${asFileUri('/workspaceRoot/third_party/dart/b/lib')}',
'c:${asFileUri('/workspaceRoot/third_party/dart/c/lib')}',
'd:${asFileUri('/workspaceRoot/third_party/dart/d/lib')}',
''
]));
}
void test_pluginsForContextRoot_none() {
ContextRoot 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')
test_start_notRunning() async {
await withPlugin(test: (String pluginPath) async {
String packagesPath = path.join(pluginPath, '.packages');
String mainPath = path.join(pluginPath, 'bin', 'plugin.dart');
String byteStorePath = path.join(pluginPath, 'byteStore');
new io.Directory(byteStorePath).createSync();
PluginInfo plugin = new DiscoveredPluginInfo(
pluginPath,
mainPath,
packagesPath,
notificationManager,
InstrumentationService.NULL_SERVICE);
PluginSession session = new PluginSession(plugin);
plugin.currentSession = session;
expect(await session.start(byteStorePath, ''), isTrue);
await session.stop();
});
}
}
@reflectiveTest
class PluginSessionTest with ResourceProviderMixin {
TestNotificationManager notificationManager;
String pluginPath;
String executionPath;
String packagesPath;
String sdkPath;
PluginInfo plugin;
PluginSession session;
void setUp() {
notificationManager = new TestNotificationManager();
pluginPath = resourceProvider.convertPath('/pluginDir');
executionPath = resourceProvider.convertPath('/pluginDir/bin/plugin.dart');
packagesPath = resourceProvider.convertPath('/pluginDir/.packages');
sdkPath = resourceProvider.convertPath('/sdk');
plugin = new DiscoveredPluginInfo(pluginPath, executionPath, packagesPath,
notificationManager, InstrumentationService.NULL_SERVICE);
session = new PluginSession(plugin);
}
void test_handleNotification() {
Notification notification =
new 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() {
TestServerCommunicationChannel channel =
new 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');
}
test_handleResponse() async {
new TestServerCommunicationChannel(session);
Response response = new PluginVersionCheckResult(
true, 'name', 'version', <String>[],
contactInfo: 'contactInfo')
.toResponse('0', 1);
Future<Response> future =
session.sendRequest(new PluginVersionCheckParams('', '', ''));
expect(session.pendingRequests, hasLength(1));
session.handleResponse(response);
expect(session.pendingRequests, hasLength(0));
Response 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() {
TestServerCommunicationChannel channel =
new TestServerCommunicationChannel(session);
session.sendRequest(new PluginVersionCheckParams('', '', ''));
expect(channel.sentRequests, hasLength(1));
expect(channel.sentRequests[0].method, 'plugin.versionCheck');
}
test_start_notCompatible() async {
session.isCompatible = false;
expect(await session.start(path.join(pluginPath, 'byteStore'), sdkPath),
isFalse);
}
test_start_running() async {
new TestServerCommunicationChannel(session);
try {
await session.start(null, '');
fail('Expected a StateError to be thrown');
} on StateError {
// Expected behavior
}
}
test_stop_notRunning() {
expect(() => session.stop(), throwsStateError);
}
test_stop_running() async {
TestServerCommunicationChannel channel =
new 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 {
PhysicalResourceProvider resourceProvider;
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 = new 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,
Future<void> test(String pluginPath)}) async {
io.Directory tempDirectory =
io.Directory.systemTemp.createTempSync(pluginName ?? 'test_plugin');
try {
String pluginPath = tempDirectory.resolveSymbolicLinksSync();
//
// Create a .packages file.
//
io.File packagesFile = new io.File(path.join(pluginPath, '.packages'));
packagesFile.writeAsStringSync(_getPackagesFileContent());
//
// Create the 'bin' directory.
//
String binPath = path.join(pluginPath, 'bin');
new io.Directory(binPath).createSync();
//
// Create the 'plugin.dart' file.
//
io.File pluginFile = new io.File(path.join(binPath, 'plugin.dart'));
pluginFile.writeAsStringSync(content ?? _defaultPluginContent());
//
// Run the actual test code.
//
await test(pluginPath);
} finally {
tempDirectory.deleteSync(recursive: true);
}
}
/**
* 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
* pubspec.yaml
* 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> withPubspecPlugin(
{String content,
String pluginName,
Future<void> test(String pluginPath)}) async {
io.Directory tempDirectory =
io.Directory.systemTemp.createTempSync(pluginName ?? 'test_plugin');
try {
String pluginPath = tempDirectory.resolveSymbolicLinksSync();
//
// Create a pubspec.yaml file.
//
io.File pubspecFile = new io.File(path.join(pluginPath, 'pubspec.yaml'));
pubspecFile.writeAsStringSync(_getPubspecFileContent());
//
// Create the 'bin' directory.
//
String binPath = path.join(pluginPath, 'bin');
new io.Directory(binPath).createSync();
//
// Create the 'plugin.dart' file.
//
io.File pluginFile = new 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) {
StringBuffer buffer = new StringBuffer();
for (String line in sdkPackageMap) {
if (!line.startsWith('#')) {
int index = line.indexOf(':');
String packageName = line.substring(0, index + 1);
String relativePath = line.substring(index + 1);
String absolutePath = path.join(sdkDirPath, relativePath);
// Convert to file:/// URI since that's how absolute paths in
// .packages must be for windows
absolutePath = new 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() {
if (_packagesFileContent == null) {
io.File sdkPackagesFile = new io.File(_sdkPackagesPath());
List<String> sdkPackageMap = sdkPackagesFile.readAsLinesSync();
_packagesFileContent =
_convertPackageMap(path.dirname(sdkPackagesFile.path), sdkPackageMap);
}
return _packagesFileContent;
}
/**
* Return the content to be used for the 'pubspec.yaml' file.
*/
String _getPubspecFileContent() {
return '''
name: 'test'
dependencies:
analyzer: any
analyzer_plugin: any
''';
}
/**
* Return the path to the '.packages' file in the root of the SDK checkout.
*/
String _sdkPackagesPath() {
String 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 NotificationManager {
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
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++;
}
void kill() {
fail('Unexpected invocation of kill');
}
@override
void listen(void onResponse(Response response),
void onNotification(Notification notification),
{void onError(dynamic error), void onDone()}) {
fail('Unexpected invocation of listen');
}
@override
void sendRequest(Request request) {
sentRequests.add(request);
if (request.method == 'plugin.shutdown') {
session.handleOnDone();
}
}
}