| // 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); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| } |
| |
| 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); |
| } |
| |
| 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)) |
| 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 extends Object 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 { |
| 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 extends Object 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(); |
| } |
| } |
| } |