| OLD | NEW |
| (Empty) | |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 import 'dart:async'; |
| 6 import 'dart:io' as io; |
| 7 |
| 8 import 'package:analysis_server/src/plugin/notification_manager.dart'; |
| 9 import 'package:analysis_server/src/plugin/plugin_manager.dart'; |
| 10 import 'package:analyzer/file_system/memory_file_system.dart'; |
| 11 import 'package:analyzer/file_system/physical_file_system.dart'; |
| 12 import 'package:analyzer/instrumentation/instrumentation.dart'; |
| 13 import 'package:analyzer_plugin/channel/channel.dart'; |
| 14 import 'package:analyzer_plugin/protocol/protocol.dart'; |
| 15 import 'package:analyzer_plugin/protocol/protocol_generated.dart'; |
| 16 import 'package:path/path.dart' as path; |
| 17 import 'package:test/test.dart'; |
| 18 import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| 19 |
| 20 main() { |
| 21 defineReflectiveSuite(() { |
| 22 defineReflectiveTests(PluginInfoTest); |
| 23 defineReflectiveTests(PluginManagerTest); |
| 24 defineReflectiveTests(PluginManagerFromDiskTest); |
| 25 defineReflectiveTests(PluginSessionTest); |
| 26 defineReflectiveTests(PluginSessionFromDiskTest); |
| 27 }); |
| 28 } |
| 29 |
| 30 @reflectiveTest |
| 31 class PluginInfoTest { |
| 32 MemoryResourceProvider resourceProvider; |
| 33 TestNotificationManager notificationManager; |
| 34 String pluginPath = '/pluginDir'; |
| 35 String executionPath = '/pluginDir/bin/plugin.dart'; |
| 36 String packagesPath = '/pluginDir/.packages'; |
| 37 PluginInfo plugin; |
| 38 |
| 39 void setUp() { |
| 40 resourceProvider = new MemoryResourceProvider(); |
| 41 notificationManager = new TestNotificationManager(); |
| 42 plugin = new PluginInfo(pluginPath, executionPath, packagesPath, |
| 43 notificationManager, InstrumentationService.NULL_SERVICE); |
| 44 } |
| 45 |
| 46 test_addContextRoot() { |
| 47 ContextRoot contextRoot1 = new ContextRoot('/pkg1', []); |
| 48 plugin.addContextRoot(contextRoot1); |
| 49 expect(plugin.contextRoots, [contextRoot1]); |
| 50 plugin.addContextRoot(contextRoot1); |
| 51 expect(plugin.contextRoots, [contextRoot1]); |
| 52 } |
| 53 |
| 54 test_creation() { |
| 55 expect(plugin.path, pluginPath); |
| 56 expect(plugin.executionPath, executionPath); |
| 57 expect(plugin.notificationManager, notificationManager); |
| 58 expect(plugin.contextRoots, isEmpty); |
| 59 expect(plugin.currentSession, isNull); |
| 60 } |
| 61 |
| 62 test_removeContextRoot() { |
| 63 ContextRoot contextRoot1 = new ContextRoot('/pkg1', []); |
| 64 ContextRoot contextRoot2 = new ContextRoot('/pkg2', []); |
| 65 plugin.addContextRoot(contextRoot1); |
| 66 expect(plugin.contextRoots, unorderedEquals([contextRoot1])); |
| 67 plugin.addContextRoot(contextRoot2); |
| 68 expect(plugin.contextRoots, unorderedEquals([contextRoot1, contextRoot2])); |
| 69 plugin.removeContextRoot(contextRoot1); |
| 70 expect(plugin.contextRoots, unorderedEquals([contextRoot2])); |
| 71 plugin.removeContextRoot(contextRoot2); |
| 72 expect(plugin.contextRoots, isEmpty); |
| 73 } |
| 74 |
| 75 @failingTest |
| 76 test_start_notRunning() { |
| 77 fail('Not tested'); |
| 78 } |
| 79 |
| 80 test_start_running() async { |
| 81 plugin.currentSession = new PluginSession(plugin); |
| 82 try { |
| 83 await plugin.start(''); |
| 84 fail('Expected a StateError'); |
| 85 } on StateError { |
| 86 // Expected. |
| 87 } |
| 88 } |
| 89 |
| 90 test_stop_notRunning() { |
| 91 expect(() => plugin.stop(), throwsA(new isInstanceOf<StateError>())); |
| 92 } |
| 93 |
| 94 test_stop_running() { |
| 95 PluginSession session = new PluginSession(plugin); |
| 96 TestServerCommunicationChannel channel = |
| 97 new TestServerCommunicationChannel(session); |
| 98 plugin.currentSession = session; |
| 99 plugin.stop(); |
| 100 expect(plugin.currentSession, isNull); |
| 101 expect(channel.sentRequests, hasLength(1)); |
| 102 expect(channel.sentRequests[0].method, 'plugin.shutdown'); |
| 103 } |
| 104 } |
| 105 |
| 106 @reflectiveTest |
| 107 class PluginManagerFromDiskTest extends PluginTestSupport { |
| 108 String byteStorePath = '/byteStore'; |
| 109 PluginManager manager; |
| 110 |
| 111 void setUp() { |
| 112 super.setUp(); |
| 113 manager = new PluginManager(resourceProvider, byteStorePath, |
| 114 notificationManager, InstrumentationService.NULL_SERVICE); |
| 115 } |
| 116 |
| 117 test_addPluginToContextRoot() async { |
| 118 io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1'); |
| 119 String pkgPath = pkg1Dir.resolveSymbolicLinksSync(); |
| 120 await withPlugin(test: (String pluginPath) async { |
| 121 ContextRoot contextRoot = new ContextRoot(pkgPath, []); |
| 122 await manager.addPluginToContextRoot(contextRoot, pluginPath); |
| 123 await manager.stopAll(); |
| 124 }); |
| 125 pkg1Dir.deleteSync(recursive: true); |
| 126 } |
| 127 |
| 128 test_broadcast_many() async { |
| 129 io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1'); |
| 130 String pkgPath = pkg1Dir.resolveSymbolicLinksSync(); |
| 131 await withPlugin( |
| 132 pluginName: 'plugin1', |
| 133 test: (String plugin1Path) async { |
| 134 await withPlugin( |
| 135 pluginName: 'plugin2', |
| 136 test: (String plugin2Path) async { |
| 137 ContextRoot contextRoot = new ContextRoot(pkgPath, []); |
| 138 await manager.addPluginToContextRoot(contextRoot, plugin1Path); |
| 139 await manager.addPluginToContextRoot(contextRoot, plugin2Path); |
| 140 |
| 141 List<Future<Response>> responses = manager.broadcast( |
| 142 contextRoot, |
| 143 new CompletionGetSuggestionsParams( |
| 144 '/pkg1/lib/pkg1.dart', 100)); |
| 145 expect(responses, hasLength(2)); |
| 146 |
| 147 await manager.stopAll(); |
| 148 }); |
| 149 }); |
| 150 pkg1Dir.deleteSync(recursive: true); |
| 151 } |
| 152 |
| 153 test_pluginsForContextRoot_multiple() async { |
| 154 io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1'); |
| 155 String pkgPath = pkg1Dir.resolveSymbolicLinksSync(); |
| 156 await withPlugin( |
| 157 pluginName: 'plugin1', |
| 158 test: (String plugin1Path) async { |
| 159 await withPlugin( |
| 160 pluginName: 'plugin2', |
| 161 test: (String plugin2Path) async { |
| 162 ContextRoot contextRoot = new ContextRoot(pkgPath, []); |
| 163 await manager.addPluginToContextRoot(contextRoot, plugin1Path); |
| 164 await manager.addPluginToContextRoot(contextRoot, plugin2Path); |
| 165 |
| 166 List<PluginInfo> plugins = |
| 167 manager.pluginsForContextRoot(contextRoot); |
| 168 expect(plugins, hasLength(2)); |
| 169 List<String> paths = |
| 170 plugins.map((PluginInfo plugin) => plugin.path).toList(); |
| 171 expect(paths, unorderedEquals([plugin1Path, plugin2Path])); |
| 172 |
| 173 await manager.stopAll(); |
| 174 }); |
| 175 }); |
| 176 pkg1Dir.deleteSync(recursive: true); |
| 177 } |
| 178 |
| 179 test_pluginsForContextRoot_one() async { |
| 180 io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1'); |
| 181 String pkgPath = pkg1Dir.resolveSymbolicLinksSync(); |
| 182 await withPlugin(test: (String pluginPath) async { |
| 183 ContextRoot contextRoot = new ContextRoot(pkgPath, []); |
| 184 await manager.addPluginToContextRoot(contextRoot, pluginPath); |
| 185 |
| 186 List<PluginInfo> plugins = manager.pluginsForContextRoot(contextRoot); |
| 187 expect(plugins, hasLength(1)); |
| 188 expect(plugins[0].path, pluginPath); |
| 189 |
| 190 await manager.stopAll(); |
| 191 }); |
| 192 pkg1Dir.deleteSync(recursive: true); |
| 193 } |
| 194 |
| 195 test_removedContextRoot() async { |
| 196 io.Directory pkg1Dir = io.Directory.systemTemp.createTempSync('pkg1'); |
| 197 String pkgPath = pkg1Dir.resolveSymbolicLinksSync(); |
| 198 await withPlugin(test: (String pluginPath) async { |
| 199 ContextRoot contextRoot = new ContextRoot(pkgPath, []); |
| 200 await manager.addPluginToContextRoot(contextRoot, pluginPath); |
| 201 |
| 202 manager.removedContextRoot(contextRoot); |
| 203 |
| 204 await manager.stopAll(); |
| 205 }); |
| 206 pkg1Dir.deleteSync(recursive: true); |
| 207 } |
| 208 } |
| 209 |
| 210 @reflectiveTest |
| 211 class PluginManagerTest { |
| 212 MemoryResourceProvider resourceProvider; |
| 213 String byteStorePath; |
| 214 TestNotificationManager notificationManager; |
| 215 PluginManager manager; |
| 216 |
| 217 void setUp() { |
| 218 resourceProvider = new MemoryResourceProvider(); |
| 219 byteStorePath = '/byteStore'; |
| 220 notificationManager = new TestNotificationManager(); |
| 221 manager = new PluginManager(resourceProvider, byteStorePath, |
| 222 notificationManager, InstrumentationService.NULL_SERVICE); |
| 223 } |
| 224 |
| 225 void test_broadcast_none() { |
| 226 ContextRoot contextRoot = new ContextRoot('/pkg1', []); |
| 227 List<Future<Response>> responses = manager.broadcast(contextRoot, |
| 228 new CompletionGetSuggestionsParams('/pkg1/lib/pkg1.dart', 100)); |
| 229 expect(responses, hasLength(0)); |
| 230 } |
| 231 |
| 232 void test_creation() { |
| 233 expect(manager.resourceProvider, resourceProvider); |
| 234 expect(manager.byteStorePath, byteStorePath); |
| 235 expect(manager.notificationManager, notificationManager); |
| 236 } |
| 237 |
| 238 void test_pluginsForContextRoot_none() { |
| 239 ContextRoot contextRoot = new ContextRoot('/pkg1', []); |
| 240 expect(manager.pluginsForContextRoot(contextRoot), isEmpty); |
| 241 } |
| 242 |
| 243 void test_stopAll_none() { |
| 244 manager.stopAll(); |
| 245 } |
| 246 } |
| 247 |
| 248 @reflectiveTest |
| 249 class PluginSessionFromDiskTest extends PluginTestSupport { |
| 250 test_start_notRunning() async { |
| 251 await withPlugin(test: (String pluginPath) async { |
| 252 String packagesPath = path.join(pluginPath, '.packages'); |
| 253 String mainPath = path.join(pluginPath, 'bin', 'plugin.dart'); |
| 254 String byteStorePath = path.join(pluginPath, 'byteStore'); |
| 255 new io.Directory(byteStorePath).createSync(); |
| 256 PluginInfo plugin = new PluginInfo(pluginPath, mainPath, packagesPath, |
| 257 notificationManager, InstrumentationService.NULL_SERVICE); |
| 258 PluginSession session = new PluginSession(plugin); |
| 259 plugin.currentSession = session; |
| 260 expect(await session.start(byteStorePath), isTrue); |
| 261 await session.stop(); |
| 262 }); |
| 263 } |
| 264 } |
| 265 |
| 266 @reflectiveTest |
| 267 class PluginSessionTest { |
| 268 MemoryResourceProvider resourceProvider; |
| 269 TestNotificationManager notificationManager; |
| 270 String pluginPath = '/pluginDir'; |
| 271 String executionPath = '/pluginDir/bin/plugin.dart'; |
| 272 String packagesPath = '/pluginDir/.packages'; |
| 273 PluginInfo plugin; |
| 274 PluginSession session; |
| 275 |
| 276 void setUp() { |
| 277 resourceProvider = new MemoryResourceProvider(); |
| 278 notificationManager = new TestNotificationManager(); |
| 279 plugin = new PluginInfo(pluginPath, executionPath, packagesPath, |
| 280 notificationManager, InstrumentationService.NULL_SERVICE); |
| 281 session = new PluginSession(plugin); |
| 282 } |
| 283 |
| 284 void test_handleNotification() { |
| 285 Notification notification = |
| 286 new AnalysisErrorsParams('/test.dart', <AnalysisError>[]) |
| 287 .toNotification(); |
| 288 expect(notificationManager.notifications, hasLength(0)); |
| 289 session.handleNotification(notification); |
| 290 expect(notificationManager.notifications, hasLength(1)); |
| 291 expect(notificationManager.notifications[0], notification); |
| 292 } |
| 293 |
| 294 void test_handleOnDone() { |
| 295 TestServerCommunicationChannel channel = |
| 296 new TestServerCommunicationChannel(session); |
| 297 session.handleOnDone(); |
| 298 expect(channel.closeCount, 1); |
| 299 expect(session.pluginStoppedCompleter.isCompleted, isTrue); |
| 300 } |
| 301 |
| 302 @failingTest |
| 303 void test_handleOnError() { |
| 304 session.handleOnError(<String>['message', 'trace']); |
| 305 fail('The method handleOnError is not implemented'); |
| 306 } |
| 307 |
| 308 test_handleResponse() async { |
| 309 new TestServerCommunicationChannel(session); |
| 310 Response response = new PluginVersionCheckResult( |
| 311 true, 'name', 'version', <String>[], |
| 312 contactInfo: 'contactInfo') |
| 313 .toResponse('0'); |
| 314 Future<Response> future = |
| 315 session.sendRequest(new PluginVersionCheckParams('', '')); |
| 316 expect(session.pendingRequests, hasLength(1)); |
| 317 session.handleResponse(response); |
| 318 expect(session.pendingRequests, hasLength(0)); |
| 319 Response result = await future; |
| 320 expect(result, same(response)); |
| 321 } |
| 322 |
| 323 void test_nextRequestId() { |
| 324 expect(session.requestId, 0); |
| 325 expect(session.nextRequestId, '0'); |
| 326 expect(session.requestId, 1); |
| 327 } |
| 328 |
| 329 void test_sendRequest() { |
| 330 TestServerCommunicationChannel channel = |
| 331 new TestServerCommunicationChannel(session); |
| 332 session.sendRequest(new PluginVersionCheckParams('', '')); |
| 333 expect(channel.sentRequests, hasLength(1)); |
| 334 expect(channel.sentRequests[0].method, 'plugin.versionCheck'); |
| 335 } |
| 336 |
| 337 test_start_notCompatible() async { |
| 338 session.isCompatible = false; |
| 339 expect(await session.start(path.join(pluginPath, 'byteStore')), isFalse); |
| 340 } |
| 341 |
| 342 test_start_running() async { |
| 343 new TestServerCommunicationChannel(session); |
| 344 try { |
| 345 await session.start(null); |
| 346 fail('Expected a StateError to be thrown'); |
| 347 } on StateError { |
| 348 // Expected behavior |
| 349 } |
| 350 } |
| 351 |
| 352 test_stop_notRunning() { |
| 353 expect(() => session.stop(), throwsA(new isInstanceOf<StateError>())); |
| 354 } |
| 355 |
| 356 void test_stop_running() { |
| 357 TestServerCommunicationChannel channel = |
| 358 new TestServerCommunicationChannel(session); |
| 359 session.stop(); |
| 360 expect(channel.sentRequests, hasLength(1)); |
| 361 expect(channel.sentRequests[0].method, 'plugin.shutdown'); |
| 362 } |
| 363 } |
| 364 |
| 365 /** |
| 366 * A class designed to be used as a superclass for test classes that define |
| 367 * tests that require plugins to be created on disk. |
| 368 */ |
| 369 abstract class PluginTestSupport { |
| 370 PhysicalResourceProvider resourceProvider; |
| 371 TestNotificationManager notificationManager; |
| 372 |
| 373 /** |
| 374 * The content to be used for the '.packages' file, or `null` if the content |
| 375 * has not yet been computed. |
| 376 */ |
| 377 String _packagesFileContent; |
| 378 |
| 379 void setUp() { |
| 380 resourceProvider = PhysicalResourceProvider.INSTANCE; |
| 381 notificationManager = new TestNotificationManager(); |
| 382 } |
| 383 |
| 384 /** |
| 385 * Create a directory structure representing a plugin on disk, run the given |
| 386 * [test] function, and then remove the directory. The directory will have the |
| 387 * following structure: |
| 388 * ``` |
| 389 * pluginDirectory |
| 390 * .packages |
| 391 * bin |
| 392 * plugin.dart |
| 393 * ``` |
| 394 * The name of the plugin directory will be the [pluginName], if one is |
| 395 * provided (in order to allow more than one plugin to be created by a single |
| 396 * test). The 'plugin.dart' file will contain the given [content], or default |
| 397 * content that implements a minimal plugin if the contents are not given. The |
| 398 * [test] function will be passed the path of the directory that was created. |
| 399 */ |
| 400 Future<Null> withPlugin( |
| 401 {String content, |
| 402 String pluginName, |
| 403 Future<Null> test(String pluginPath)}) async { |
| 404 io.Directory tempDirectory = |
| 405 io.Directory.systemTemp.createTempSync(pluginName ?? 'test_plugin'); |
| 406 try { |
| 407 String pluginPath = tempDirectory.resolveSymbolicLinksSync(); |
| 408 // |
| 409 // Create a .packages file. |
| 410 // |
| 411 io.File packagesFile = new io.File(path.join(pluginPath, '.packages')); |
| 412 packagesFile.writeAsStringSync(_getPackagesFileContent()); |
| 413 // |
| 414 // Create the 'bin' directory. |
| 415 // |
| 416 String binPath = path.join(pluginPath, 'bin'); |
| 417 new io.Directory(binPath).createSync(); |
| 418 // |
| 419 // Create the 'plugin.dart' file. |
| 420 // |
| 421 io.File pluginFile = new io.File(path.join(binPath, 'plugin.dart')); |
| 422 pluginFile.writeAsStringSync(content ?? _defaultPluginContent()); |
| 423 // |
| 424 // Run the actual test code. |
| 425 // |
| 426 await test(pluginPath); |
| 427 } finally { |
| 428 tempDirectory.deleteSync(recursive: true); |
| 429 } |
| 430 } |
| 431 |
| 432 /** |
| 433 * Convert the [sdkPackageMap] into a plugin-specific map by applying the |
| 434 * given relative path [delta] to each line. |
| 435 */ |
| 436 String _convertPackageMap(String sdkDirPath, List<String> sdkPackageMap) { |
| 437 StringBuffer buffer = new StringBuffer(); |
| 438 for (String line in sdkPackageMap) { |
| 439 if (!line.startsWith('#')) { |
| 440 int index = line.indexOf(':'); |
| 441 String packageName = line.substring(0, index + 1); |
| 442 String relativePath = line.substring(index + 1); |
| 443 String absolutePath = path.join(sdkDirPath, relativePath); |
| 444 buffer.write(packageName); |
| 445 buffer.writeln(absolutePath); |
| 446 } |
| 447 } |
| 448 return buffer.toString(); |
| 449 } |
| 450 |
| 451 /** |
| 452 * The default content of the plugin. This is a minimal plugin that will only |
| 453 * respond correctly to version checks and to shutdown requests. |
| 454 */ |
| 455 String _defaultPluginContent() { |
| 456 return r''' |
| 457 import 'dart:isolate'; |
| 458 import 'package:analyzer/file_system/file_system.dart'; |
| 459 import 'package:analyzer/file_system/physical_file_system.dart'; |
| 460 import 'package:analyzer_plugin/plugin/plugin.dart'; |
| 461 import 'package:analyzer_plugin/starter.dart'; |
| 462 import 'package:pub_semver/pub_semver.dart'; |
| 463 |
| 464 void main(List<String> args, SendPort sendPort) { |
| 465 MinimalPlugin plugin = new MinimalPlugin(PhysicalResourceProvider.INSTANCE); |
| 466 new ServerPluginStarter(plugin).start(sendPort); |
| 467 } |
| 468 |
| 469 class MinimalPlugin extends ServerPlugin { |
| 470 MinimalPlugin(ResourceProvider provider) : super(provider); |
| 471 |
| 472 @override |
| 473 List<String> get fileGlobsToAnalyze => <String>[]; |
| 474 |
| 475 @override |
| 476 String get name => 'minimal'; |
| 477 |
| 478 @override |
| 479 String get version => '0.0.1'; |
| 480 |
| 481 @override |
| 482 bool isCompatibleWith(Version serverVersion) => true; |
| 483 } |
| 484 '''; |
| 485 } |
| 486 |
| 487 /** |
| 488 * Return the content to be used for the '.packages' file. |
| 489 */ |
| 490 String _getPackagesFileContent() { |
| 491 if (_packagesFileContent == null) { |
| 492 io.File sdkPackagesFile = new io.File(_sdkPackagesPath()); |
| 493 List<String> sdkPackageMap = sdkPackagesFile.readAsLinesSync(); |
| 494 _packagesFileContent = |
| 495 _convertPackageMap(path.dirname(sdkPackagesFile.path), sdkPackageMap); |
| 496 } |
| 497 return _packagesFileContent; |
| 498 } |
| 499 |
| 500 /** |
| 501 * Return the path to the '.packages' file in the root of the SDK checkout. |
| 502 */ |
| 503 String _sdkPackagesPath() { |
| 504 String packagesPath = |
| 505 io.Platform.script.toFilePath(windows: io.Platform.isWindows); |
| 506 while (packagesPath.isNotEmpty && |
| 507 path.basename(packagesPath) != 'analysis_server') { |
| 508 packagesPath = path.dirname(packagesPath); |
| 509 } |
| 510 packagesPath = path.dirname(packagesPath); |
| 511 packagesPath = path.dirname(packagesPath); |
| 512 return path.join(packagesPath, '.packages'); |
| 513 } |
| 514 } |
| 515 |
| 516 class TestNotificationManager implements NotificationManager { |
| 517 List<Notification> notifications = <Notification>[]; |
| 518 |
| 519 @override |
| 520 void handlePluginNotification(String pluginId, Notification notification) { |
| 521 notifications.add(notification); |
| 522 } |
| 523 |
| 524 @override |
| 525 noSuchMethod(Invocation invocation) { |
| 526 fail('Unexpected invocation of ${invocation.memberName}'); |
| 527 } |
| 528 } |
| 529 |
| 530 class TestServerCommunicationChannel implements ServerCommunicationChannel { |
| 531 int closeCount = 0; |
| 532 List<Request> sentRequests = <Request>[]; |
| 533 |
| 534 TestServerCommunicationChannel(PluginSession session) { |
| 535 session.channel = this; |
| 536 } |
| 537 |
| 538 @override |
| 539 void close() { |
| 540 closeCount++; |
| 541 } |
| 542 |
| 543 @override |
| 544 void listen(void onResponse(Response response), |
| 545 void onNotification(Notification notification), |
| 546 {Function onError, void onDone()}) { |
| 547 fail('Unexpected invocation of listen'); |
| 548 } |
| 549 |
| 550 @override |
| 551 void sendRequest(Request request) { |
| 552 sentRequests.add(request); |
| 553 } |
| 554 } |
| OLD | NEW |