38
39
# TODO: Write a test for plugin decoration of commands.
40
class TestPluginMixin(object):
41
class BaseTestPlugins(tests.TestCaseInTempDir):
42
43
def create_plugin(self, name, source=None, dir='.', file_name=None):
79
80
if getattr(bzrlib.plugins, name, None) is not None:
80
81
delattr(bzrlib.plugins, name)
83
def _unregister_plugin_submodule(self, plugin_name, submodule_name):
84
"""Remove the submodule from sys.modules and the bzrlib namespace."""
85
py_name = 'bzrlib.plugins.%s.%s' % (plugin_name, submodule_name)
86
if py_name in sys.modules:
87
del sys.modules[py_name]
88
plugin = getattr(bzrlib.plugins, plugin_name, None)
89
if plugin is not None:
90
if getattr(plugin, submodule_name, None) is not None:
91
delattr(plugin, submodule_name)
82
93
def assertPluginUnknown(self, name):
83
self.failIf(getattr(bzrlib.plugins, name, None) is not None)
84
self.failIf('bzrlib.plugins.%s' % name in sys.modules)
94
self.assertFalse(getattr(bzrlib.plugins, name, None) is not None)
95
self.assertFalse('bzrlib.plugins.%s' % name in sys.modules)
86
97
def assertPluginKnown(self, name):
87
self.failUnless(getattr(bzrlib.plugins, name, None) is not None)
88
self.failUnless('bzrlib.plugins.%s' % name in sys.modules)
91
class TestLoadingPlugins(tests.TestCaseInTempDir, TestPluginMixin):
98
self.assertTrue(getattr(bzrlib.plugins, name, None) is not None)
99
self.assertTrue('bzrlib.plugins.%s' % name in sys.modules)
102
class TestLoadingPlugins(BaseTestPlugins):
93
104
activeattributes = {}
98
109
# file name we can use which is also a valid attribute for accessing in
99
110
# activeattributes. - we cannot give import parameters.
100
111
tempattribute = "0"
101
self.failIf(tempattribute in self.activeattributes)
112
self.assertFalse(tempattribute in self.activeattributes)
102
113
# set a place for the plugins to record their loading, and at the same
103
114
# time validate that the location the plugins should record to is
104
115
# valid and correct.
105
116
self.__class__.activeattributes [tempattribute] = []
106
self.failUnless(tempattribute in self.activeattributes)
117
self.assertTrue(tempattribute in self.activeattributes)
107
118
# create two plugin directories
108
119
os.mkdir('first')
109
120
os.mkdir('second')
136
147
self.assertPluginUnknown('plugin')
138
149
def test_plugins_from_different_dirs_can_demand_load(self):
139
self.failIf('bzrlib.plugins.pluginone' in sys.modules)
140
self.failIf('bzrlib.plugins.plugintwo' in sys.modules)
150
self.assertFalse('bzrlib.plugins.pluginone' in sys.modules)
151
self.assertFalse('bzrlib.plugins.plugintwo' in sys.modules)
141
152
# This test tests that having two plugins in different
142
153
# directories with different names allows them both to be loaded, when
143
154
# we do a direct import statement.
144
155
# Determine a file name we can use which is also a valid attribute
145
156
# for accessing in activeattributes. - we cannot give import parameters.
146
157
tempattribute = "different-dirs"
147
self.failIf(tempattribute in self.activeattributes)
158
self.assertFalse(tempattribute in self.activeattributes)
148
159
# set a place for the plugins to record their loading, and at the same
149
160
# time validate that the location the plugins should record to is
150
161
# valid and correct.
151
162
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
152
163
[tempattribute] = []
153
self.failUnless(tempattribute in self.activeattributes)
164
self.assertTrue(tempattribute in self.activeattributes)
154
165
# create two plugin directories
155
166
os.mkdir('first')
156
167
os.mkdir('second')
176
187
oldpath = bzrlib.plugins.__path__
178
self.failIf('bzrlib.plugins.pluginone' in sys.modules)
179
self.failIf('bzrlib.plugins.plugintwo' in sys.modules)
189
self.assertFalse('bzrlib.plugins.pluginone' in sys.modules)
190
self.assertFalse('bzrlib.plugins.plugintwo' in sys.modules)
180
191
bzrlib.plugins.__path__ = ['first', 'second']
181
192
exec "import bzrlib.plugins.pluginone"
182
193
self.assertEqual(['first'], self.activeattributes[tempattribute])
197
208
# check the plugin is not loaded already
198
209
self.assertPluginUnknown('ts_plugin')
199
210
tempattribute = "trailing-slash"
200
self.failIf(tempattribute in self.activeattributes)
211
self.assertFalse(tempattribute in self.activeattributes)
201
212
# set a place for the plugin to record its loading, and at the same
202
213
# time validate that the location the plugin should record to is
203
214
# valid and correct.
204
215
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
205
216
[tempattribute] = []
206
self.failUnless(tempattribute in self.activeattributes)
217
self.assertTrue(tempattribute in self.activeattributes)
207
218
# create a directory for the plugin
208
219
os.mkdir('plugin_test')
209
220
# write a plugin that will record when its loaded in the
258
269
def test_plugin_with_bad_api_version_reports(self):
259
# This plugin asks for bzrlib api version 1.0.0, which is not supported
270
"""Try loading a plugin that requests an unsupported api.
272
Observe that it records the problem but doesn't complain on stderr.
274
See https://bugs.launchpad.net/bzr/+bug/704195
276
self.overrideAttr(plugin, 'plugin_warnings', {})
261
277
name = 'wants100.py'
262
278
f = file(name, 'w')
265
281
"bzrlib.api.require_any_api(bzrlib, [(1, 0, 0)])\n")
269
284
log = self.load_and_capture(name)
270
self.assertContainsRe(log,
285
self.assertNotContainsRe(log,
286
r"It requested API version")
289
plugin.plugin_warnings.keys())
290
self.assertContainsRe(
291
plugin.plugin_warnings['wants100'][0],
271
292
r"It requested API version")
273
294
def test_plugin_with_bad_name_does_not_load(self):
281
302
"it to 'bad_plugin_name_'\.")
284
class TestPlugins(tests.TestCaseInTempDir, TestPluginMixin):
305
class TestPlugins(BaseTestPlugins):
286
307
def setup_plugin(self, source=""):
287
308
# This test tests a new plugin appears in bzrlib.plugin.plugins().
431
452
def test_final_fallback__version__with_version_info(self):
432
453
self.setup_plugin("version_info = (1, 2, 3, 'final', 2)")
433
454
plugin = bzrlib.plugin.plugins()['plugin']
434
self.assertEqual("1.2.3.final.2", plugin.__version__)
455
self.assertEqual("1.2.3.2", plugin.__version__)
437
458
class TestPluginHelp(tests.TestCaseInTempDir):
594
615
def test_get_help_text_with_additional_see_also(self):
595
616
mod = FakeModule('two lines of help\nand more', 'demo')
596
617
topic = plugin.ModuleHelpTopic(mod)
597
self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
598
topic.get_help_text(['foo', 'bar']))
618
self.assertEqual("two lines of help\nand more\n\n:See also: bar, foo\n",
619
topic.get_help_text(['foo', 'bar']))
600
621
def test_get_help_topic(self):
601
622
"""The help topic for a plugin is its module name."""
602
623
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
603
624
topic = plugin.ModuleHelpTopic(mod)
604
625
self.assertEqual('demo', topic.get_help_topic())
605
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
626
mod = FakeModule('two lines of help\nand more',
627
'bzrlib.plugins.foo_bar')
606
628
topic = plugin.ModuleHelpTopic(mod)
607
629
self.assertEqual('foo_bar', topic.get_help_topic())
645
667
self.fail('No path to global plugins')
647
669
def test_get_standard_plugins_path_env(self):
648
os.environ['BZR_PLUGIN_PATH'] = 'foo/'
670
self.overrideEnv('BZR_PLUGIN_PATH', 'foo/')
649
671
path = plugin.get_standard_plugins_path()
650
672
for directory in path:
651
673
self.assertNotContainsRe(directory, r'\\/$')
682
704
def _set_path(self, *args):
683
705
path = os.pathsep.join(self._list2paths(*args))
684
osutils.set_or_unset_env('BZR_PLUGIN_PATH', path)
706
self.overrideEnv('BZR_PLUGIN_PATH', path)
686
708
def check_path(self, expected_dirs, setting_dirs):
768
790
self.addCleanup(self._unregister_plugin, 'test_foo')
770
792
def test_cannot_import(self):
771
osutils.set_or_unset_env('BZR_DISABLE_PLUGINS', 'test_foo')
793
self.overrideEnv('BZR_DISABLE_PLUGINS', 'test_foo')
772
794
plugin.set_plugins_path(['.'])
774
796
import bzrlib.plugins.test_foo
780
802
self.overrideAttr(plugin, '_loaded', False)
781
803
plugin.load_plugins(['.'])
782
804
self.assertPluginKnown('test_foo')
783
self.assertEqual("This is the doc for test_foo",
784
bzrlib.plugins.test_foo.__doc__)
805
self.assertDocstring("This is the doc for test_foo",
806
bzrlib.plugins.test_foo)
786
808
def test_not_loaded(self):
787
809
self.warnings = []
790
812
self.overrideAttr(trace, 'warning', captured_warning)
791
813
# Reset the flag that protect against double loading
792
814
self.overrideAttr(plugin, '_loaded', False)
793
osutils.set_or_unset_env('BZR_DISABLE_PLUGINS', 'test_foo')
815
self.overrideEnv('BZR_DISABLE_PLUGINS', 'test_foo')
794
816
plugin.load_plugins(['.'])
795
817
self.assertPluginUnknown('test_foo')
796
818
# Make sure we don't warn about the plugin ImportError since this has
798
820
self.assertLength(0, self.warnings)
801
class TestLoadPluginAt(tests.TestCaseInTempDir, TestPluginMixin):
824
class TestLoadPluginAtSyntax(tests.TestCase):
826
def _get_paths(self, paths):
827
return plugin._get_specific_plugin_paths(paths)
829
def test_empty(self):
830
self.assertEquals([], self._get_paths(None))
831
self.assertEquals([], self._get_paths(''))
833
def test_one_path(self):
834
self.assertEquals([('b', 'man')], self._get_paths('b@man'))
836
def test_bogus_path(self):
838
self.assertRaises(errors.BzrCommandError, self._get_paths, 'batman')
839
# Too much '@' isn't good either
840
self.assertRaises(errors.BzrCommandError, self._get_paths,
841
'batman@mobile@cave')
842
# An empty description probably indicates a problem
843
self.assertRaises(errors.BzrCommandError, self._get_paths,
844
os.pathsep.join(['batman@cave', '', 'robin@mobile']))
847
class TestLoadPluginAt(BaseTestPlugins):
804
850
super(TestLoadPluginAt, self).setUp()
805
851
# Make sure we don't pollute the plugins namespace
806
852
self.overrideAttr(plugins, '__path__')
807
# Be paranoid in case a test fail
808
self.addCleanup(self._unregister_plugin, 'test_foo')
809
853
# Reset the flag that protect against double loading
810
854
self.overrideAttr(plugin, '_loaded', False)
811
855
# Create the same plugin in two directories
813
857
# The "normal" directory, we use 'standard' instead of 'plugins' to
814
858
# avoid depending on the precise naming.
815
859
self.create_plugin_package('test_foo', dir='standard/test_foo')
860
# All the tests will load the 'test_foo' plugin from various locations
861
self.addCleanup(self._unregister_plugin, 'test_foo')
862
# Unfortunately there's global cached state for the specific
864
self.addCleanup(plugin.PluginImporter.reset)
817
866
def assertTestFooLoadedFrom(self, path):
818
867
self.assertPluginKnown('test_foo')
819
self.assertEqual('This is the doc for test_foo',
820
bzrlib.plugins.test_foo.__doc__)
868
self.assertDocstring('This is the doc for test_foo',
869
bzrlib.plugins.test_foo)
821
870
self.assertEqual(path, bzrlib.plugins.test_foo.dir_source)
823
872
def test_regular_load(self):
825
874
self.assertTestFooLoadedFrom('standard/test_foo')
827
876
def test_import(self):
828
osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
877
self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
829
878
plugin.set_plugins_path(['standard'])
831
880
import bzrlib.plugins.test_foo
834
883
self.assertTestFooLoadedFrom('non-standard-dir')
836
885
def test_loading(self):
837
osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
886
self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
838
887
plugin.load_plugins(['standard'])
839
888
self.assertTestFooLoadedFrom('non-standard-dir')
841
890
def test_compiled_loaded(self):
842
osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
891
self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
843
892
plugin.load_plugins(['standard'])
844
893
self.assertTestFooLoadedFrom('non-standard-dir')
845
self.assertEqual('non-standard-dir/__init__.py',
846
bzrlib.plugins.test_foo.__file__)
894
self.assertIsSameRealPath('non-standard-dir/__init__.py',
895
bzrlib.plugins.test_foo.__file__)
848
897
# Try importing again now that the source has been compiled
849
898
self._unregister_plugin('test_foo')
857
self.assertEqual('non-standard-dir/__init__.%s' % suffix,
858
bzrlib.plugins.test_foo.__file__)
906
self.assertIsSameRealPath('non-standard-dir/__init__.%s' % suffix,
907
bzrlib.plugins.test_foo.__file__)
860
909
def test_submodule_loading(self):
861
910
# We create an additional directory under the one for test_foo
862
911
self.create_plugin_package('test_bar', dir='non-standard-dir/test_bar')
863
osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
912
self.addCleanup(self._unregister_plugin_submodule,
913
'test_foo', 'test_bar')
914
self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
864
915
plugin.set_plugins_path(['standard'])
865
916
import bzrlib.plugins.test_foo
866
917
self.assertEqual('bzrlib.plugins.test_foo',
867
918
bzrlib.plugins.test_foo.__package__)
868
919
import bzrlib.plugins.test_foo.test_bar
869
self.assertEqual('non-standard-dir/test_bar/__init__.py',
870
bzrlib.plugins.test_foo.test_bar.__file__)
920
self.assertIsSameRealPath('non-standard-dir/test_bar/__init__.py',
921
bzrlib.plugins.test_foo.test_bar.__file__)
923
def test_relative_submodule_loading(self):
924
self.create_plugin_package('test_foo', dir='another-dir', source='''
927
# We create an additional directory under the one for test_foo
928
self.create_plugin_package('test_bar', dir='another-dir/test_bar')
929
self.addCleanup(self._unregister_plugin_submodule,
930
'test_foo', 'test_bar')
931
self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@another-dir')
932
plugin.set_plugins_path(['standard'])
933
import bzrlib.plugins.test_foo
934
self.assertEqual('bzrlib.plugins.test_foo',
935
bzrlib.plugins.test_foo.__package__)
936
self.assertIsSameRealPath('another-dir/test_bar/__init__.py',
937
bzrlib.plugins.test_foo.test_bar.__file__)
872
939
def test_loading_from___init__only(self):
873
940
# We rename the existing __init__.py file to ensure that we don't load
876
943
random = 'non-standard-dir/setup.py'
877
944
os.rename(init, random)
878
945
self.addCleanup(os.rename, random, init)
879
osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
946
self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
880
947
plugin.load_plugins(['standard'])
881
948
self.assertPluginUnknown('test_foo')
890
957
''' % ('test_foo', plugin_path)
891
958
self.create_plugin('test_foo', source=source,
892
959
dir=plugin_dir, file_name=plugin_file_name)
893
osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@%s' % plugin_path)
960
self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@%s' % plugin_path)
894
961
plugin.load_plugins(['standard'])
895
962
self.assertTestFooLoadedFrom(plugin_path)
965
class TestDescribePlugins(BaseTestPlugins):
967
def test_describe_plugins(self):
968
class DummyModule(object):
970
class DummyPlugin(object):
971
__version__ = '0.1.0'
972
module = DummyModule()
974
return { 'good': DummyPlugin() }
975
self.overrideAttr(plugin, 'plugin_warnings',
976
{'bad': ['Failed to load (just testing)']})
977
self.overrideAttr(plugin, 'plugins', dummy_plugins)
978
self.assertEquals("""\
980
** Failed to load (just testing)
985
""", ''.join(plugin.describe_plugins()))