| // 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_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]) { |
| 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); |
| } |
| } |
| |
| /// 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, |
| required Future<void> Function(String) test}) async { |
| var tempDirectory = |
| io.Directory.systemTemp.createTempSync(pluginName ?? 'test_plugin'); |
| try { |
| var pluginPath = tempDirectory.resolveSymbolicLinksSync(); |
| // |
| // Create a pubspec.yaml file. |
| // |
| var pubspecFile = io.File(path.join(pluginPath, file_paths.pubspecYaml)); |
| pubspecFile.writeAsStringSync(_getPubspecFileContent()); |
| // |
| // 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 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() { |
| 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), |
| ); |
| } |
| } |