24
from StringIO import StringIO
28
from bzrlib import plugin, tests
26
29
import bzrlib.plugin
27
30
import bzrlib.plugins
28
from bzrlib.tests import TestCaseInTempDir
31
import bzrlib.commands
33
from bzrlib.tests import TestCase, TestCaseInTempDir
29
34
from bzrlib.osutils import pathjoin, abspath
31
class PluginTest(TestCaseInTempDir):
32
"""Create an external plugin and test loading."""
33
# def test_plugin_loading(self):
34
# orig_help = self.run_bzr_captured('bzr help commands')[0]
35
# os.mkdir('plugin_test')
36
# f = open(pathjoin('plugin_test', 'myplug.py'), 'wt')
37
# f.write(PLUGIN_TEXT)
39
# newhelp = self.run_bzr_captured('bzr help commands')[0]
40
# assert newhelp.startswith('You have been overridden\n')
41
# # We added a line, but the rest should work
42
# assert newhelp[25:] == help
44
# assert backtick('bzr commit -m test') == "I'm sorry dave, you can't do that\n"
46
# shutil.rmtree('plugin_test')
49
# os.environ['BZRPLUGINPATH'] = abspath('plugin_test')
50
# help = backtick('bzr help commands')
51
# assert help.find('myplug') != -1
52
# assert help.find('Just a simple test plugin.') != -1
55
# assert backtick('bzr myplug') == 'Hello from my plugin\n'
56
# assert backtick('bzr mplg') == 'Hello from my plugin\n'
58
# f = open(pathjoin('plugin_test', 'override.py'), 'wb')
59
# f.write("""import bzrlib, bzrlib.commands
60
# class cmd_commit(bzrlib.commands.cmd_commit):
61
# '''Commit changes into a new revision.'''
62
# def run(self, *args, **kwargs):
63
# print "I'm sorry dave, you can't do that"
65
# class cmd_help(bzrlib.commands.cmd_help):
66
# '''Show help on a command or other topic.'''
67
# def run(self, *args, **kwargs):
68
# print "You have been overridden"
69
# bzrlib.commands.cmd_help.run(self, *args, **kwargs)
74
38
import bzrlib.commands
82
46
# TODO: Write a test for plugin decoration of commands.
84
class TestOneNamedPluginOnly(TestCaseInTempDir):
48
class TestLoadingPlugins(TestCaseInTempDir):
86
50
activeattributes = {}
88
52
def test_plugins_with_the_same_name_are_not_loaded(self):
53
# This test tests that having two plugins in different directories does
54
# not result in both being loaded when they have the same name. get a
55
# file name we can use which is also a valid attribute for accessing in
56
# activeattributes. - we cannot give import parameters.
58
self.failIf(tempattribute in self.activeattributes)
59
# set a place for the plugins to record their loading, and at the same
60
# time validate that the location the plugins should record to is
62
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
64
self.failUnless(tempattribute in self.activeattributes)
65
# create two plugin directories
68
# write a plugin that will record when its loaded in the
70
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
71
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
73
outfile = open(os.path.join('first', 'plugin.py'), 'w')
75
print >> outfile, template % (tempattribute, 'first')
79
outfile = open(os.path.join('second', 'plugin.py'), 'w')
81
print >> outfile, template % (tempattribute, 'second')
86
bzrlib.plugin.load_from_path(['first', 'second'])
87
self.assertEqual(['first'], self.activeattributes[tempattribute])
89
# remove the plugin 'plugin'
90
del self.activeattributes[tempattribute]
91
if getattr(bzrlib.plugins, 'plugin', None):
92
del bzrlib.plugins.plugin
93
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
95
def test_plugins_from_different_dirs_can_demand_load(self):
89
96
# This test tests that having two plugins in different
90
# directories does not result in both being loaded.
91
# get a file name we can use which is also a valid attribute
97
# directories with different names allows them both to be loaded, when
98
# we do a direct import statement.
99
# Determine a file name we can use which is also a valid attribute
92
100
# for accessing in activeattributes. - we cannot give import parameters.
101
tempattribute = "different-dirs"
94
102
self.failIf(tempattribute in self.activeattributes)
95
103
# set a place for the plugins to record their loading, and at the same
96
104
# time validate that the location the plugins should record to is
97
105
# valid and correct.
98
bzrlib.tests.test_plugins.TestOneNamedPluginOnly.activeattributes \
106
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
99
107
[tempattribute] = []
100
108
self.failUnless(tempattribute in self.activeattributes)
101
109
# create two plugin directories
102
110
os.mkdir('first')
103
111
os.mkdir('second')
104
# write a plugin that will record when its loaded in the
112
# write plugins that will record when they are loaded in the
105
113
# tempattribute list.
106
template = ("from bzrlib.tests.test_plugins import TestOneNamedPluginOnly\n"
107
"TestOneNamedPluginOnly.activeattributes[%r].append('%s')\n")
108
print >> file(os.path.join('first', 'plugin.py'), 'w'), template % (tempattribute, 'first')
109
print >> file(os.path.join('second', 'plugin.py'), 'w'), template % (tempattribute, 'second')
111
bzrlib.plugin.load_from_dirs(['first', 'second'])
114
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
115
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
117
outfile = open(os.path.join('first', 'pluginone.py'), 'w')
119
print >> outfile, template % (tempattribute, 'first')
123
outfile = open(os.path.join('second', 'plugintwo.py'), 'w')
125
print >> outfile, template % (tempattribute, 'second')
129
oldpath = bzrlib.plugins.__path__
131
bzrlib.plugins.__path__ = ['first', 'second']
132
exec "import bzrlib.plugins.pluginone"
112
133
self.assertEqual(['first'], self.activeattributes[tempattribute])
134
exec "import bzrlib.plugins.plugintwo"
135
self.assertEqual(['first', 'second'],
136
self.activeattributes[tempattribute])
114
138
# remove the plugin 'plugin'
115
139
del self.activeattributes[tempattribute]
127
187
# write a plugin that _cannot_ fail to load.
128
188
print >> file('plugin.py', 'w'), ""
130
bzrlib.plugin.load_from_dirs(['.'])
190
bzrlib.plugin.load_from_path(['.'])
131
191
self.failUnless('plugin' in bzrlib.plugin.all_plugins())
132
192
self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
133
193
self.assertEqual(bzrlib.plugin.all_plugins()['plugin'],
134
194
bzrlib.plugins.plugin)
136
196
# remove the plugin 'plugin'
197
if 'bzrlib.plugins.plugin' in sys.modules:
198
del sys.modules['bzrlib.plugins.plugin']
137
199
if getattr(bzrlib.plugins, 'plugin', None):
138
200
del bzrlib.plugins.plugin
139
201
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
204
class TestPluginHelp(TestCaseInTempDir):
206
def split_help_commands(self):
209
for line in self.run_bzr('help commands')[0].splitlines():
210
if not line.startswith(' '):
211
current = line.split()[0]
212
help[current] = help.get(current, '') + line
216
def test_plugin_help_builtins_unaffected(self):
217
# Check we don't get false positives
218
help_commands = self.split_help_commands()
219
for cmd_name in bzrlib.commands.builtin_command_names():
220
if cmd_name in bzrlib.commands.plugin_command_names():
223
help = bzrlib.commands.get_cmd_object(cmd_name).get_help_text()
224
except NotImplementedError:
225
# some commands have no help
228
self.assertNotContainsRe(help, 'plugin "[^"]*"')
230
if cmd_name in help_commands.keys():
231
# some commands are hidden
232
help = help_commands[cmd_name]
233
self.assertNotContainsRe(help, 'plugin "[^"]*"')
235
def test_plugin_help_shows_plugin(self):
236
# Create a test plugin
237
os.mkdir('plugin_test')
238
f = open(pathjoin('plugin_test', 'myplug.py'), 'w')
244
bzrlib.plugin.load_from_path(['plugin_test'])
245
bzrlib.commands.register_command( bzrlib.plugins.myplug.cmd_myplug)
246
help = self.run_bzr('help myplug')[0]
247
self.assertContainsRe(help, 'plugin "myplug"')
248
help = self.split_help_commands()['myplug']
249
self.assertContainsRe(help, '\[myplug\]')
252
if bzrlib.commands.plugin_cmds.get('myplug', None):
253
del bzrlib.commands.plugin_cmds['myplug']
254
# remove the plugin 'myplug'
255
if getattr(bzrlib.plugins, 'myplug', None):
256
delattr(bzrlib.plugins, 'myplug')
259
class TestPluginFromZip(TestCaseInTempDir):
261
def make_zipped_plugin(self, zip_name, filename):
262
z = zipfile.ZipFile(zip_name, 'w')
263
z.writestr(filename, PLUGIN_TEXT)
266
def check_plugin_load(self, zip_name, plugin_name):
267
self.assertFalse(plugin_name in dir(bzrlib.plugins),
268
'Plugin already loaded')
269
old_path = bzrlib.plugins.__path__
271
# this is normally done by load_plugins -> set_plugins_path
272
bzrlib.plugins.__path__ = [zip_name]
273
bzrlib.plugin.load_from_zip(zip_name)
274
self.assertTrue(plugin_name in dir(bzrlib.plugins),
275
'Plugin is not loaded')
278
if getattr(bzrlib.plugins, plugin_name, None):
279
delattr(bzrlib.plugins, plugin_name)
280
del sys.modules['bzrlib.plugins.' + plugin_name]
281
bzrlib.plugins.__path__ = old_path
283
def test_load_module(self):
284
self.make_zipped_plugin('./test.zip', 'ziplug.py')
285
self.check_plugin_load('./test.zip', 'ziplug')
287
def test_load_package(self):
288
self.make_zipped_plugin('./test.zip', 'ziplug/__init__.py')
289
self.check_plugin_load('./test.zip', 'ziplug')
292
class TestSetPluginsPath(TestCase):
294
def test_set_plugins_path(self):
295
"""set_plugins_path should set the module __path__ correctly."""
296
old_path = bzrlib.plugins.__path__
298
bzrlib.plugins.__path__ = []
299
expected_path = bzrlib.plugin.set_plugins_path()
300
self.assertEqual(expected_path, bzrlib.plugins.__path__)
302
bzrlib.plugins.__path__ = old_path
304
def test_set_plugins_path_with_trailing_slashes(self):
305
"""set_plugins_path should set the module __path__ based on
307
old_path = bzrlib.plugins.__path__
308
old_env = os.environ.get('BZR_PLUGIN_PATH')
310
bzrlib.plugins.__path__ = []
311
os.environ['BZR_PLUGIN_PATH'] = "first\\//\\" + os.pathsep + \
313
bzrlib.plugin.set_plugins_path()
314
expected_path = ['first', 'second',
315
os.path.dirname(bzrlib.plugins.__file__)]
316
self.assertEqual(expected_path, bzrlib.plugins.__path__)
318
bzrlib.plugins.__path__ = old_path
320
os.environ['BZR_PLUGIN_PATH'] = old_env
322
del os.environ['BZR_PLUGIN_PATH']
324
class TestHelpIndex(tests.TestCase):
325
"""Tests for the PluginsHelpIndex class."""
327
def test_default_constructable(self):
328
index = plugin.PluginsHelpIndex()
330
def test_get_topics_None(self):
331
"""Searching for None returns an empty list."""
332
index = plugin.PluginsHelpIndex()
333
self.assertEqual([], index.get_topics(None))
335
def test_get_topics_for_plugin(self):
336
"""Searching for plugin name gets its docstring."""
337
index = plugin.PluginsHelpIndex()
338
# make a new plugin here for this test, even if we're run with
340
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
341
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
342
sys.modules['bzrlib.plugins.demo_module'] = demo_module
344
topics = index.get_topics('demo_module')
345
self.assertEqual(1, len(topics))
346
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
347
self.assertEqual(demo_module, topics[0].module)
349
del sys.modules['bzrlib.plugins.demo_module']
351
def test_get_topics_no_topic(self):
352
"""Searching for something that is not a plugin returns []."""
353
# test this by using a name that cannot be a plugin - its not
354
# a valid python identifier.
355
index = plugin.PluginsHelpIndex()
356
self.assertEqual([], index.get_topics('nothing by this name'))
358
def test_prefix(self):
359
"""PluginsHelpIndex has a prefix of 'plugins/'."""
360
index = plugin.PluginsHelpIndex()
361
self.assertEqual('plugins/', index.prefix)
363
def test_get_plugin_topic_with_prefix(self):
364
"""Searching for plugins/demo_module returns help."""
365
index = plugin.PluginsHelpIndex()
366
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
367
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
368
sys.modules['bzrlib.plugins.demo_module'] = demo_module
370
topics = index.get_topics('plugins/demo_module')
371
self.assertEqual(1, len(topics))
372
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
373
self.assertEqual(demo_module, topics[0].module)
375
del sys.modules['bzrlib.plugins.demo_module']
378
class FakeModule(object):
379
"""A fake module to test with."""
381
def __init__(self, doc, name):
386
class TestModuleHelpTopic(tests.TestCase):
387
"""Tests for the ModuleHelpTopic class."""
389
def test_contruct(self):
390
"""Construction takes the module to document."""
391
mod = FakeModule('foo', 'foo')
392
topic = plugin.ModuleHelpTopic(mod)
393
self.assertEqual(mod, topic.module)
395
def test_get_help_text_None(self):
396
"""A ModuleHelpTopic returns the docstring for get_help_text."""
397
mod = FakeModule(None, 'demo')
398
topic = plugin.ModuleHelpTopic(mod)
399
self.assertEqual("Plugin 'demo' has no docstring.\n",
400
topic.get_help_text())
402
def test_get_help_text_no_carriage_return(self):
403
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
404
mod = FakeModule('one line of help', 'demo')
405
topic = plugin.ModuleHelpTopic(mod)
406
self.assertEqual("one line of help\n",
407
topic.get_help_text())
409
def test_get_help_text_carriage_return(self):
410
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
411
mod = FakeModule('two lines of help\nand more\n', 'demo')
412
topic = plugin.ModuleHelpTopic(mod)
413
self.assertEqual("two lines of help\nand more\n",
414
topic.get_help_text())
416
def test_get_help_text_with_additional_see_also(self):
417
mod = FakeModule('two lines of help\nand more', 'demo')
418
topic = plugin.ModuleHelpTopic(mod)
419
self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
420
topic.get_help_text(['foo', 'bar']))
422
def test_get_help_topic(self):
423
"""The help topic for a plugin is its module name."""
424
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
425
topic = plugin.ModuleHelpTopic(mod)
426
self.assertEqual('demo', topic.get_help_topic())
427
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
428
topic = plugin.ModuleHelpTopic(mod)
429
self.assertEqual('foo_bar', topic.get_help_topic())