1# (C) Copyright 2007-2019 Enthought, Inc., Austin, TX 2# All rights reserved. 3# 4# This software is provided without warranty under the terms of the BSD 5# license included in LICENSE.txt and may be redistributed only 6# under the conditions described in the aforementioned license. The license 7# is also available online at http://www.enthought.com/licenses/BSD.txt 8# Thanks for using Enthought open source! 9""" Tests for plugins. """ 10 11 12# Standard library imports. 13from os.path import exists, join 14 15# Enthought library imports. 16from envisage.api import Application, ExtensionPoint 17from envisage.api import IPluginActivator, Plugin, contributes_to 18from envisage.tests.ets_config_patcher import ETSConfigPatcher 19from traits.api import HasTraits, Instance, Int, Interface, List 20from traits.api import provides 21from traits.testing.unittest_tools import unittest 22 23 24def listener(obj, trait_name, old, new): 25 """ A useful trait change handler for testing! """ 26 27 listener.obj = obj 28 listener.trait_name = trait_name 29 listener.old = old 30 listener.new = new 31 32 33class TestApplication(Application): 34 """ The type of application used in the tests. """ 35 36 id = 'test' 37 38 39class PluginTestCase(unittest.TestCase): 40 """ Tests for plugins. """ 41 42 def setUp(self): 43 ets_config_patcher = ETSConfigPatcher() 44 ets_config_patcher.start() 45 self.addCleanup(ets_config_patcher.stop) 46 47 def test_id_policy(self): 48 """ id policy """ 49 50 # If no Id is specified then use 'module_name.class_name'. 51 p = Plugin() 52 self.assertEqual('envisage.plugin.Plugin', p.id) 53 54 # If an Id is specified make sure we use it! 55 p = Plugin(id='wilma') 56 self.assertEqual('wilma', p.id) 57 58 # Make sure setting the name doesn't interfere with the Id. 59 p = Plugin(name='fred', id='wilma') 60 self.assertEqual('wilma', p.id) 61 self.assertEqual('fred', p.name) 62 63 def test_name_policy(self): 64 """ name policy """ 65 66 # If name is specified then use the plugin's class name. 67 p = Plugin() 68 self.assertEqual('Plugin', p.name) 69 70 # If a name is specified make sure we use it! 71 p = Plugin(name='wilma') 72 self.assertEqual('wilma', p.name) 73 74 # Try a camel case plugin class. 75 class ThisIsMyPlugin(Plugin): 76 pass 77 78 p = ThisIsMyPlugin() 79 self.assertEqual('This Is My Plugin', p.name) 80 81 def test_plugin_activator(self): 82 """ plugin activator. """ 83 84 @provides(IPluginActivator) 85 class NullPluginActivator(HasTraits): 86 """ A plugin activator that does nothing! """ 87 88 def start_plugin(self, plugin): 89 """ Start a plugin. """ 90 91 self.started = plugin 92 93 def stop_plugin(self, plugin): 94 """ Stop a plugin. """ 95 96 self.stopped = plugin 97 98 class PluginA(Plugin): 99 id = 'A' 100 101 class PluginB(Plugin): 102 id = 'B' 103 104 plugin_activator = NullPluginActivator() 105 106 a = PluginA(activator=plugin_activator) 107 b = PluginB() 108 109 application = TestApplication(plugins=[a, b]) 110 application.start() 111 112 # Make sure A's plugin activator was called. 113 self.assertEqual(a, plugin_activator.started) 114 115 # Stop the application. 116 application.stop() 117 118 # Make sure A's plugin activator was called. 119 self.assertEqual(a, plugin_activator.stopped) 120 121 def test_service(self): 122 """ service """ 123 124 class Foo(HasTraits): 125 pass 126 127 class Bar(HasTraits): 128 pass 129 130 class Baz(HasTraits): 131 pass 132 133 class PluginA(Plugin): 134 id = 'A' 135 foo = Instance(Foo, (), service=True) 136 bar = Instance(Bar, (), service=True) 137 baz = Instance(Baz, (), service=True) 138 139 a = PluginA() 140 141 application = TestApplication(plugins=[a]) 142 application.start() 143 144 # Make sure the services were registered. 145 self.assertNotEqual(None, application.get_service(Foo)) 146 self.assertEqual(a.foo, application.get_service(Foo)) 147 148 self.assertNotEqual(None, application.get_service(Bar)) 149 self.assertEqual(a.bar, application.get_service(Bar)) 150 151 self.assertNotEqual(None, application.get_service(Baz)) 152 self.assertEqual(a.baz, application.get_service(Baz)) 153 154 application.stop() 155 156 # Make sure the service was unregistered. 157 self.assertEqual(None, application.get_service(Foo)) 158 self.assertEqual(None, application.get_service(Bar)) 159 self.assertEqual(None, application.get_service(Baz)) 160 161 def test_service_protocol(self): 162 """ service protocol """ 163 164 class IFoo(Interface): 165 pass 166 167 class IBar(Interface): 168 pass 169 170 @provides(IFoo, IBar) 171 class Foo(HasTraits): 172 pass 173 174 class PluginA(Plugin): 175 id = 'A' 176 foo = Instance(Foo, (), service=True, service_protocol=IBar) 177 178 a = PluginA() 179 180 application = TestApplication(plugins=[a]) 181 application.start() 182 183 # Make sure the service was registered with the 'IBar' protocol. 184 self.assertNotEqual(None, application.get_service(IBar)) 185 self.assertEqual(a.foo, application.get_service(IBar)) 186 187 application.stop() 188 189 # Make sure the service was unregistered. 190 self.assertEqual(None, application.get_service(IBar)) 191 192 def test_multiple_trait_contributions(self): 193 """ multiple trait contributions """ 194 195 class PluginA(Plugin): 196 id = 'A' 197 x = ExtensionPoint(List, id='x') 198 199 class PluginB(Plugin): 200 id = 'B' 201 202 x = List([1, 2, 3], contributes_to='x') 203 y = List([4, 5, 6], contributes_to='x') 204 205 a = PluginA() 206 b = PluginB() 207 208 application = TestApplication(plugins=[a, b]) 209 210 # We should get an error because the plugin has multiple traits 211 # contributing to the same extension point. 212 with self.assertRaises(ValueError): 213 application.get_extensions("x") 214 215 def test_exception_in_trait_contribution(self): 216 """ exception in trait contribution """ 217 218 class PluginA(Plugin): 219 id = 'A' 220 x = ExtensionPoint(List, id='x') 221 222 class PluginB(Plugin): 223 id = 'B' 224 225 x = List(contributes_to='x') 226 227 def _x_default(self): 228 """ Trait initializer. """ 229 230 raise 1/0 231 232 a = PluginA() 233 b = PluginB() 234 235 application = TestApplication(plugins=[a, b]) 236 237 # We should get an when we try to get the contributions to the 238 # extension point. 239 with self.assertRaises(ZeroDivisionError): 240 application.get_extensions("x") 241 242 def test_contributes_to(self): 243 """ contributes to """ 244 245 class PluginA(Plugin): 246 id = 'A' 247 x = ExtensionPoint(List, id='x') 248 249 class PluginB(Plugin): 250 id = 'B' 251 x = List([1, 2, 3], contributes_to='x') 252 253 a = PluginA() 254 b = PluginB() 255 256 application = TestApplication(plugins=[a, b]) 257 258 # We should get an error because the plugin has multiple traits 259 # contributing to the same extension point. 260 self.assertEqual([1, 2, 3], application.get_extensions('x')) 261 262 def test_contributes_to_decorator(self): 263 """ contributes to decorator """ 264 265 class PluginA(Plugin): 266 id = 'A' 267 x = ExtensionPoint(List, id='x') 268 269 class PluginB(Plugin): 270 id = 'B' 271 272 @contributes_to('x') 273 def _x_contributions(self): 274 return [1, 2, 3] 275 276 a = PluginA() 277 b = PluginB() 278 279 application = TestApplication(plugins=[a, b]) 280 self.assertEqual([1, 2, 3], application.get_extensions('x')) 281 282 def test_contributes_to_decorator_ignored_if_trait_present(self): 283 """ contributes to decorator ignored if trait present """ 284 285 class PluginA(Plugin): 286 id = 'A' 287 x = ExtensionPoint(List, id='x') 288 289 class PluginB(Plugin): 290 id = 'B' 291 x = List([1, 2, 3], contributes_to='x') 292 293 @contributes_to('x') 294 def _x_contributions(self): 295 return [4, 5, 6] 296 297 a = PluginA() 298 b = PluginB() 299 300 application = TestApplication(plugins=[a, b]) 301 self.assertEqual([1, 2, 3], application.get_extensions('x')) 302 303 def test_add_plugins_to_empty_application(self): 304 """ add plugins to empty application """ 305 306 class PluginA(Plugin): 307 id = 'A' 308 x = ExtensionPoint(List(Int), id='x') 309 310 def _x_items_changed(self, event): 311 self.added = event.added 312 self.removed = event.removed 313 314 class PluginB(Plugin): 315 id = 'B' 316 x = List(Int, [1, 2, 3], contributes_to='x') 317 318 class PluginC(Plugin): 319 id = 'C' 320 x = List(Int, [4, 5, 6], contributes_to='x') 321 322 a = PluginA() 323 b = PluginB() 324 c = PluginC() 325 326 # Create an empty application. 327 application = TestApplication() 328 application.start() 329 330 # Add the plugin that offers the extension point. 331 application.add_plugin(a) 332 333 ####################################################################### 334 # fixme: Currently, we connect up extension point traits when the 335 # plugin is started. Is this right? Should we start plugins by default 336 # when we add them (and maybe have the ability to add a plugin without 337 # starting it?). 338 # 339 # I think we should start the plugin, otherwise you have the wierdness 340 # that the extension contributed by the plugin are available after 341 # the call to 'add_plugin', but the plugin isn't started?!? 342 ####################################################################### 343 344 application.start_plugin(a) 345 346 ####################################################################### 347 # fixme: Currently, we only fire changed events if an extension point 348 # has already been accessed! Is this right? 349 ####################################################################### 350 351 self.assertEqual([], a.x) 352 353 # Add a plugin that contributes to the extension point. 354 application.add_plugin(b) 355 356 # Make sure that we pick up B's extensions and that the appropriate 357 # trait event was fired. 358 self.assertEqual([1, 2, 3], a.x) 359 self.assertEqual([1, 2, 3], a.added) 360 361 # Add another plugin that contributes to the extension point. 362 application.add_plugin(c) 363 364 self.assertEqual([1, 2, 3, 4, 5, 6], a.x) 365 self.assertEqual([4, 5, 6], a.added) 366 367 # Remove the first contributing plugin. 368 application.remove_plugin(b) 369 370 self.assertEqual([4, 5, 6], a.x) 371 self.assertEqual([1, 2, 3], a.removed) 372 373 # Remove the second contributing plugin. 374 application.remove_plugin(c) 375 376 self.assertEqual([], a.x) 377 self.assertEqual([4, 5, 6], a.removed) 378 379 def test_home(self): 380 """ home """ 381 382 class PluginA(Plugin): 383 id = 'A' 384 385 class PluginB(Plugin): 386 id = 'B' 387 388 a = PluginA() 389 b = PluginB() 390 391 application = TestApplication(plugins=[a, b]) 392 393 # Make sure that each plugin gets its own directory. 394 self.assertEqual(join(application.home, 'plugins', a.id), a.home) 395 self.assertEqual(join(application.home, 'plugins', b.id), b.home) 396 397 # Make sure that the directories got created. 398 self.assertTrue(exists(a.home)) 399 self.assertTrue(exists(b.home)) 400 401 # Create a new application with plugins with the same Id to make sure 402 # that it all works when the directories already exist. 403 a = PluginA() 404 b = PluginB() 405 406 application = TestApplication(plugins=[a, b]) 407 408 # Make sure that each plugin gets its own directory. 409 self.assertEqual(join(application.home, 'plugins', a.id), a.home) 410 self.assertEqual(join(application.home, 'plugins', b.id), b.home) 411 412 # Make sure the directories got created. 413 self.assertTrue(exists(a.home)) 414 self.assertTrue(exists(b.home)) 415 416 def test_no_recursion(self): 417 """ Regression test for #119. """ 418 419 class PluginA(Plugin): 420 id = 'A' 421 x = ExtensionPoint(List, id='bob') 422 423 application = Application(plugins=[PluginA()]) 424 application.get_extensions('bob') 425