# (C) Copyright 2007-2023 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
""" Tests for plugins. """
import unittest
# Standard library imports.
from os.path import exists, join
from traits.api import HasTraits, Instance, Int, Interface, List, provides
# Enthought library imports.
from envisage.api import Application, ExtensionPoint, IPluginActivator, Plugin
from envisage.tests.ets_config_patcher import ETSConfigPatcher
from envisage.tests.support import SimpleApplication
[docs]class TestPlugin(Plugin):
id = "test_plugin"
[docs]class PluginTestCase(unittest.TestCase):
"""Tests for plugins."""
[docs] def setUp(self):
ets_config_patcher = ETSConfigPatcher()
ets_config_patcher.start()
self.addCleanup(ets_config_patcher.stop)
[docs] def test_id_policy(self):
"""id policy"""
# If no Id is specified then use 'module_name.class_name'.
p = Plugin()
self.assertEqual("envisage.plugin.Plugin", p.id)
# If an Id is specified make sure we use it!
p = Plugin(id="wilma")
self.assertEqual("wilma", p.id)
# Make sure setting the name doesn't interfere with the Id.
p = Plugin(name="fred", id="wilma")
self.assertEqual("wilma", p.id)
self.assertEqual("fred", p.name)
[docs] def test_name_policy(self):
"""name policy"""
# If name is specified then use the plugin's class name.
p = Plugin()
self.assertEqual("Plugin", p.name)
# If a name is specified make sure we use it!
p = Plugin(name="wilma")
self.assertEqual("wilma", p.name)
# Try a camel case plugin class.
class ThisIsMyPlugin(Plugin):
pass
p = ThisIsMyPlugin()
self.assertEqual("This Is My Plugin", p.name)
[docs] def test_plugin_activator(self):
"""plugin activator."""
@provides(IPluginActivator)
class NullPluginActivator(HasTraits):
"""A plugin activator that does nothing!"""
def start_plugin(self, plugin):
"""Start a plugin."""
self.started = plugin
def stop_plugin(self, plugin):
"""Stop a plugin."""
self.stopped = plugin
class PluginA(Plugin):
id = "A"
class PluginB(Plugin):
id = "B"
plugin_activator = NullPluginActivator()
a = PluginA(activator=plugin_activator)
b = PluginB()
application = SimpleApplication(plugins=[a, b])
application.start()
# Make sure A's plugin activator was called.
self.assertEqual(a, plugin_activator.started)
# Stop the application.
application.stop()
# Make sure A's plugin activator was called.
self.assertEqual(a, plugin_activator.stopped)
[docs] def test_service(self):
"""service"""
class Foo(HasTraits):
pass
class Bar(HasTraits):
pass
class Baz(HasTraits):
pass
class PluginA(Plugin):
id = "A"
foo = Instance(Foo, (), service=True)
bar = Instance(Bar, (), service=True)
baz = Instance(Baz, (), service=True)
a = PluginA()
application = SimpleApplication(plugins=[a])
application.start()
# Make sure the services were registered.
self.assertNotEqual(None, application.get_service(Foo))
self.assertEqual(a.foo, application.get_service(Foo))
self.assertNotEqual(None, application.get_service(Bar))
self.assertEqual(a.bar, application.get_service(Bar))
self.assertNotEqual(None, application.get_service(Baz))
self.assertEqual(a.baz, application.get_service(Baz))
application.stop()
# Make sure the service was unregistered.
self.assertEqual(None, application.get_service(Foo))
self.assertEqual(None, application.get_service(Bar))
self.assertEqual(None, application.get_service(Baz))
[docs] def test_service_protocol(self):
"""service protocol"""
class IFoo(Interface):
pass
class IBar(Interface):
pass
@provides(IFoo, IBar)
class Foo(HasTraits):
pass
class PluginA(Plugin):
id = "A"
foo = Instance(Foo, (), service=True, service_protocol=IBar)
a = PluginA()
application = SimpleApplication(plugins=[a])
application.start()
# Make sure the service was registered with the 'IBar' protocol.
self.assertNotEqual(None, application.get_service(IBar))
self.assertEqual(a.foo, application.get_service(IBar))
application.stop()
# Make sure the service was unregistered.
self.assertEqual(None, application.get_service(IBar))
[docs] def test_multiple_trait_contributions(self):
"""multiple trait contributions"""
class PluginA(Plugin):
id = "A"
x = ExtensionPoint(List, id="x")
class PluginB(Plugin):
id = "B"
x = List([1, 2, 3], contributes_to="x")
y = List([4, 5, 6], contributes_to="x")
a = PluginA()
b = PluginB()
application = SimpleApplication(plugins=[a, b])
# We should get an error because the plugin has multiple traits
# contributing to the same extension point.
with self.assertRaises(ValueError):
application.get_extensions("x")
[docs] def test_exception_in_trait_contribution(self):
"""exception in trait contribution"""
class PluginA(Plugin):
id = "A"
x = ExtensionPoint(List, id="x")
class PluginB(Plugin):
id = "B"
x = List(contributes_to="x")
def _x_default(self):
"""Trait initializer."""
raise 1 / 0
a = PluginA()
b = PluginB()
application = SimpleApplication(plugins=[a, b])
# We should get an when we try to get the contributions to the
# extension point.
with self.assertRaises(ZeroDivisionError):
application.get_extensions("x")
[docs] def test_contributes_to(self):
"""contributes to"""
class PluginA(Plugin):
id = "A"
x = ExtensionPoint(List, id="x")
class PluginB(Plugin):
id = "B"
x = List([1, 2, 3], contributes_to="x")
a = PluginA()
b = PluginB()
application = SimpleApplication(plugins=[a, b])
# We should get an error because the plugin has multiple traits
# contributing to the same extension point.
self.assertEqual([1, 2, 3], application.get_extensions("x"))
[docs] def test_add_plugins_to_empty_application(self):
"""add plugins to empty application"""
class PluginA(Plugin):
id = "A"
x = ExtensionPoint(List(Int), id="x")
def _x_items_changed(self, event):
self.added = event.added
self.removed = event.removed
class PluginB(Plugin):
id = "B"
x = List(Int, [1, 2, 3], contributes_to="x")
class PluginC(Plugin):
id = "C"
x = List(Int, [4, 5, 6], contributes_to="x")
a = PluginA()
b = PluginB()
c = PluginC()
# Create an empty application.
application = SimpleApplication()
application.start()
# Add the plugin that offers the extension point.
application.add_plugin(a)
#######################################################################
# fixme: Currently, we connect up extension point traits when the
# plugin is started. Is this right? Should we start plugins by default
# when we add them (and maybe have the ability to add a plugin without
# starting it?).
#
# I think we should start the plugin, otherwise you have the wierdness
# that the extension contributed by the plugin are available after
# the call to 'add_plugin', but the plugin isn't started?!?
#######################################################################
application.start_plugin(a)
#######################################################################
# fixme: Currently, we only fire changed events if an extension point
# has already been accessed! Is this right?
#######################################################################
self.assertEqual([], a.x)
# Add a plugin that contributes to the extension point.
application.add_plugin(b)
# Make sure that we pick up B's extensions and that the appropriate
# trait event was fired.
self.assertEqual([1, 2, 3], a.x)
self.assertEqual([1, 2, 3], a.added)
# Add another plugin that contributes to the extension point.
application.add_plugin(c)
self.assertEqual([1, 2, 3, 4, 5, 6], a.x)
self.assertEqual([4, 5, 6], a.added)
# Remove the first contributing plugin.
application.remove_plugin(b)
self.assertEqual([4, 5, 6], a.x)
self.assertEqual([1, 2, 3], a.removed)
# Remove the second contributing plugin.
application.remove_plugin(c)
self.assertEqual([], a.x)
self.assertEqual([4, 5, 6], a.removed)
[docs] def test_home(self):
"""home"""
class PluginA(Plugin):
id = "A"
class PluginB(Plugin):
id = "B"
a = PluginA()
b = PluginB()
application = SimpleApplication(plugins=[a, b])
# Make sure that each plugin gets its own directory.
self.assertEqual(join(application.home, "plugins", a.id), a.home)
self.assertEqual(join(application.home, "plugins", b.id), b.home)
# Make sure that the directories got created.
self.assertTrue(exists(a.home))
self.assertTrue(exists(b.home))
# Create a new application with plugins with the same Id to make sure
# that it all works when the directories already exist.
a = PluginA()
b = PluginB()
application = SimpleApplication(plugins=[a, b])
# Make sure that each plugin gets its own directory.
self.assertEqual(join(application.home, "plugins", a.id), a.home)
self.assertEqual(join(application.home, "plugins", b.id), b.home)
# Make sure the directories got created.
self.assertTrue(exists(a.home))
self.assertTrue(exists(b.home))
[docs] def test_no_recursion(self):
"""Regression test for #119."""
class PluginA(Plugin):
id = "A"
x = ExtensionPoint(List, id="bob")
application = Application(plugins=[PluginA()])
application.get_extensions("bob")
[docs] def test_plugin_str_representation(self):
"""test the string representation of the plugin"""
plugin_repr = "TestPlugin(id={!r}, name={!r})"
plugin = TestPlugin(id="Fred", name="Wilma")
self.assertEqual(str(plugin), plugin_repr.format("Fred", "Wilma"))
self.assertEqual(repr(plugin), plugin_repr.format("Fred", "Wilma"))