1
# Copyright (C) 2005, 2007 Canonical Ltd
1
# Copyright (C) 2005 by Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
# GNU General Public License for more details.
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
"""Tests for plugins"""
19
# XXX: There are no plugin tests at the moment because the plugin module
20
# affects the global state of the process. See bzrlib/plugins.py for more
24
from StringIO import StringIO
28
from bzrlib import plugin, tests
31
import bzrlib.commands
33
from bzrlib.symbol_versioning import zero_ninetyone
34
from bzrlib.tests import TestCase, TestCaseInTempDir
35
from bzrlib.osutils import pathjoin, abspath, normpath
39
import bzrlib.commands
40
class cmd_myplug(bzrlib.commands.Command):
41
'''Just a simple test plugin.'''
44
print 'Hello from my plugin'
47
# TODO: Write a test for plugin decoration of commands.
49
class TestLoadingPlugins(TestCaseInTempDir):
53
def test_plugins_with_the_same_name_are_not_loaded(self):
54
# This test tests that having two plugins in different directories does
55
# not result in both being loaded when they have the same name. get a
56
# file name we can use which is also a valid attribute for accessing in
57
# activeattributes. - we cannot give import parameters.
59
self.failIf(tempattribute in self.activeattributes)
60
# set a place for the plugins to record their loading, and at the same
61
# time validate that the location the plugins should record to is
63
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
65
self.failUnless(tempattribute in self.activeattributes)
66
# create two plugin directories
69
# write a plugin that will record when its loaded in the
71
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
72
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
74
outfile = open(os.path.join('first', 'plugin.py'), 'w')
76
print >> outfile, template % (tempattribute, 'first')
80
outfile = open(os.path.join('second', 'plugin.py'), 'w')
82
print >> outfile, template % (tempattribute, 'second')
87
bzrlib.plugin.load_from_path(['first', 'second'])
88
self.assertEqual(['first'], self.activeattributes[tempattribute])
90
# remove the plugin 'plugin'
91
del self.activeattributes[tempattribute]
92
if 'bzrlib.plugins.plugin' in sys.modules:
93
del sys.modules['bzrlib.plugins.plugin']
94
if getattr(bzrlib.plugins, 'plugin', None):
95
del bzrlib.plugins.plugin
96
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
98
def test_plugins_from_different_dirs_can_demand_load(self):
99
# This test tests that having two plugins in different
100
# directories with different names allows them both to be loaded, when
101
# we do a direct import statement.
102
# Determine a file name we can use which is also a valid attribute
103
# for accessing in activeattributes. - we cannot give import parameters.
104
tempattribute = "different-dirs"
105
self.failIf(tempattribute in self.activeattributes)
106
# set a place for the plugins to record their loading, and at the same
107
# time validate that the location the plugins should record to is
109
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
111
self.failUnless(tempattribute in self.activeattributes)
112
# create two plugin directories
115
# write plugins that will record when they are loaded in the
116
# tempattribute list.
117
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
118
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
120
outfile = open(os.path.join('first', 'pluginone.py'), 'w')
122
print >> outfile, template % (tempattribute, 'first')
126
outfile = open(os.path.join('second', 'plugintwo.py'), 'w')
128
print >> outfile, template % (tempattribute, 'second')
132
oldpath = bzrlib.plugins.__path__
134
bzrlib.plugins.__path__ = ['first', 'second']
135
exec "import bzrlib.plugins.pluginone"
136
self.assertEqual(['first'], self.activeattributes[tempattribute])
137
exec "import bzrlib.plugins.plugintwo"
138
self.assertEqual(['first', 'second'],
139
self.activeattributes[tempattribute])
141
# remove the plugin 'plugin'
142
del self.activeattributes[tempattribute]
143
if getattr(bzrlib.plugins, 'pluginone', None):
144
del bzrlib.plugins.pluginone
145
if getattr(bzrlib.plugins, 'plugintwo', None):
146
del bzrlib.plugins.plugintwo
147
self.failIf(getattr(bzrlib.plugins, 'pluginone', None))
148
self.failIf(getattr(bzrlib.plugins, 'plugintwo', None))
150
def test_plugins_can_load_from_directory_with_trailing_slash(self):
151
# This test tests that a plugin can load from a directory when the
152
# directory in the path has a trailing slash.
153
# check the plugin is not loaded already
154
self.failIf(getattr(bzrlib.plugins, 'ts_plugin', None))
155
tempattribute = "trailing-slash"
156
self.failIf(tempattribute in self.activeattributes)
157
# set a place for the plugin to record its loading, and at the same
158
# time validate that the location the plugin should record to is
160
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
162
self.failUnless(tempattribute in self.activeattributes)
163
# create a directory for the plugin
164
os.mkdir('plugin_test')
165
# write a plugin that will record when its loaded in the
166
# tempattribute list.
167
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
168
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
170
outfile = open(os.path.join('plugin_test', 'ts_plugin.py'), 'w')
172
print >> outfile, template % (tempattribute, 'plugin')
177
bzrlib.plugin.load_from_path(['plugin_test'+os.sep])
178
self.assertEqual(['plugin'], self.activeattributes[tempattribute])
180
# remove the plugin 'plugin'
181
del self.activeattributes[tempattribute]
182
if getattr(bzrlib.plugins, 'ts_plugin', None):
183
del bzrlib.plugins.ts_plugin
184
self.failIf(getattr(bzrlib.plugins, 'ts_plugin', None))
187
class TestAllPlugins(TestCaseInTempDir):
189
def test_plugin_appears_in_all_plugins(self):
190
# This test tests a new plugin appears in bzrlib.plugin.all_plugins().
191
# check the plugin is not loaded already
192
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
193
# write a plugin that _cannot_ fail to load.
194
print >> file('plugin.py', 'w'), ""
196
bzrlib.plugin.load_from_path(['.'])
197
all_plugins = self.applyDeprecated(zero_ninetyone,
198
bzrlib.plugin.all_plugins)
199
self.failUnless('plugin' in all_plugins)
200
self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
201
self.assertEqual(all_plugins['plugin'], bzrlib.plugins.plugin)
203
# remove the plugin 'plugin'
204
if 'bzrlib.plugins.plugin' in sys.modules:
205
del sys.modules['bzrlib.plugins.plugin']
206
if getattr(bzrlib.plugins, 'plugin', None):
207
del bzrlib.plugins.plugin
208
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
211
class TestPlugins(TestCaseInTempDir):
213
def setup_plugin(self, source=""):
214
# This test tests a new plugin appears in bzrlib.plugin.plugins().
215
# check the plugin is not loaded already
216
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
217
# write a plugin that _cannot_ fail to load.
218
print >> file('plugin.py', 'w'), source
219
self.addCleanup(self.teardown_plugin)
220
bzrlib.plugin.load_from_path(['.'])
222
def teardown_plugin(self):
223
# remove the plugin 'plugin'
224
if 'bzrlib.plugins.plugin' in sys.modules:
225
del sys.modules['bzrlib.plugins.plugin']
226
if getattr(bzrlib.plugins, 'plugin', None):
227
del bzrlib.plugins.plugin
228
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
230
def test_plugin_appears_in_plugins(self):
232
self.failUnless('plugin' in bzrlib.plugin.plugins())
233
self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
234
plugins = bzrlib.plugin.plugins()
235
plugin = plugins['plugin']
236
self.assertIsInstance(plugin, bzrlib.plugin.PlugIn)
237
self.assertEqual(bzrlib.plugins.plugin, plugin.module)
239
def test_trivial_plugin_get_path(self):
241
plugins = bzrlib.plugin.plugins()
242
plugin = plugins['plugin']
243
plugin_path = self.test_dir + '/plugin.py'
244
self.assertEqual(plugin_path, normpath(plugin.path()))
246
def test_no_test_suite_gives_None_for_test_suite(self):
248
plugin = bzrlib.plugin.plugins()['plugin']
249
self.assertEqual(None, plugin.test_suite())
251
def test_test_suite_gives_test_suite_result(self):
252
source = """def test_suite(): return 'foo'"""
253
self.setup_plugin(source)
254
plugin = bzrlib.plugin.plugins()['plugin']
255
self.assertEqual('foo', plugin.test_suite())
257
def test_no_version_info(self):
259
plugin = bzrlib.plugin.plugins()['plugin']
260
self.assertEqual(None, plugin.version_info())
262
def test_with_version_info(self):
263
self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
264
plugin = bzrlib.plugin.plugins()['plugin']
265
self.assertEqual((1, 2, 3, 'dev', 4), plugin.version_info())
267
def test_short_version_info_gets_padded(self):
268
# the gtk plugin has version_info = (1,2,3) rather than the 5-tuple.
270
self.setup_plugin("version_info = (1, 2, 3)")
271
plugin = bzrlib.plugin.plugins()['plugin']
272
self.assertEqual((1, 2, 3, 'final', 0), plugin.version_info())
274
def test_no_version_info___version__(self):
276
plugin = bzrlib.plugin.plugins()['plugin']
277
self.assertEqual("unknown", plugin.__version__)
279
def test___version__with_version_info(self):
280
self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
281
plugin = bzrlib.plugin.plugins()['plugin']
282
self.assertEqual("1.2.3dev4", plugin.__version__)
284
def test_final__version__with_version_info(self):
285
self.setup_plugin("version_info = (1, 2, 3, 'final', 4)")
286
plugin = bzrlib.plugin.plugins()['plugin']
287
self.assertEqual("1.2.3", plugin.__version__)
290
class TestPluginHelp(TestCaseInTempDir):
292
def split_help_commands(self):
295
for line in self.run_bzr('help commands')[0].splitlines():
296
if not line.startswith(' '):
297
current = line.split()[0]
298
help[current] = help.get(current, '') + line
302
def test_plugin_help_builtins_unaffected(self):
303
# Check we don't get false positives
304
help_commands = self.split_help_commands()
305
for cmd_name in bzrlib.commands.builtin_command_names():
306
if cmd_name in bzrlib.commands.plugin_command_names():
309
help = bzrlib.commands.get_cmd_object(cmd_name).get_help_text()
310
except NotImplementedError:
311
# some commands have no help
314
self.assertNotContainsRe(help, 'plugin "[^"]*"')
316
if cmd_name in help_commands.keys():
317
# some commands are hidden
318
help = help_commands[cmd_name]
319
self.assertNotContainsRe(help, 'plugin "[^"]*"')
321
def test_plugin_help_shows_plugin(self):
322
# Create a test plugin
323
os.mkdir('plugin_test')
324
f = open(pathjoin('plugin_test', 'myplug.py'), 'w')
22
# **************************************************
24
# **************************************************
33
from bzrlib.selftest import TestCaseInTempDir
36
class PluginTest(TestCaseInTempDir):
37
"""Create an external plugin and test loading."""
38
def test_plugin_loading(self):
41
orig_help = self.backtick('bzr help commands') # No plugins yet
42
os.mkdir('plugin_test')
43
f = open(os.path.join('plugin_test', 'myplug.py'), 'wt')
325
44
f.write(PLUGIN_TEXT)
330
bzrlib.plugin.load_from_path(['plugin_test'])
331
bzrlib.commands.register_command( bzrlib.plugins.myplug.cmd_myplug)
332
help = self.run_bzr('help myplug')[0]
333
self.assertContainsRe(help, 'plugin "myplug"')
334
help = self.split_help_commands()['myplug']
335
self.assertContainsRe(help, '\[myplug\]')
338
if bzrlib.commands.plugin_cmds.get('myplug', None):
339
del bzrlib.commands.plugin_cmds['myplug']
340
# remove the plugin 'myplug'
341
if getattr(bzrlib.plugins, 'myplug', None):
342
delattr(bzrlib.plugins, 'myplug')
345
class TestPluginFromZip(TestCaseInTempDir):
347
def make_zipped_plugin(self, zip_name, filename):
348
z = zipfile.ZipFile(zip_name, 'w')
349
z.writestr(filename, PLUGIN_TEXT)
352
def check_plugin_load(self, zip_name, plugin_name):
353
self.assertFalse(plugin_name in dir(bzrlib.plugins),
354
'Plugin already loaded')
355
old_path = bzrlib.plugins.__path__
357
# this is normally done by load_plugins -> set_plugins_path
358
bzrlib.plugins.__path__ = [zip_name]
359
bzrlib.plugin.load_from_zip(zip_name)
360
self.assertTrue(plugin_name in dir(bzrlib.plugins),
361
'Plugin is not loaded')
364
if getattr(bzrlib.plugins, plugin_name, None):
365
delattr(bzrlib.plugins, plugin_name)
366
del sys.modules['bzrlib.plugins.' + plugin_name]
367
bzrlib.plugins.__path__ = old_path
369
def test_load_module(self):
370
self.make_zipped_plugin('./test.zip', 'ziplug.py')
371
self.check_plugin_load('./test.zip', 'ziplug')
373
def test_load_package(self):
374
self.make_zipped_plugin('./test.zip', 'ziplug/__init__.py')
375
self.check_plugin_load('./test.zip', 'ziplug')
378
class TestSetPluginsPath(TestCase):
380
def test_set_plugins_path(self):
381
"""set_plugins_path should set the module __path__ correctly."""
382
old_path = bzrlib.plugins.__path__
384
bzrlib.plugins.__path__ = []
385
expected_path = bzrlib.plugin.set_plugins_path()
386
self.assertEqual(expected_path, bzrlib.plugins.__path__)
388
bzrlib.plugins.__path__ = old_path
390
def test_set_plugins_path_with_trailing_slashes(self):
391
"""set_plugins_path should set the module __path__ based on
393
old_path = bzrlib.plugins.__path__
394
old_env = os.environ.get('BZR_PLUGIN_PATH')
396
bzrlib.plugins.__path__ = []
397
os.environ['BZR_PLUGIN_PATH'] = "first\\//\\" + os.pathsep + \
399
bzrlib.plugin.set_plugins_path()
400
expected_path = ['first', 'second',
401
os.path.dirname(bzrlib.plugins.__file__)]
402
self.assertEqual(expected_path, bzrlib.plugins.__path__)
404
bzrlib.plugins.__path__ = old_path
406
os.environ['BZR_PLUGIN_PATH'] = old_env
408
del os.environ['BZR_PLUGIN_PATH']
410
class TestHelpIndex(tests.TestCase):
411
"""Tests for the PluginsHelpIndex class."""
413
def test_default_constructable(self):
414
index = plugin.PluginsHelpIndex()
416
def test_get_topics_None(self):
417
"""Searching for None returns an empty list."""
418
index = plugin.PluginsHelpIndex()
419
self.assertEqual([], index.get_topics(None))
421
def test_get_topics_for_plugin(self):
422
"""Searching for plugin name gets its docstring."""
423
index = plugin.PluginsHelpIndex()
424
# make a new plugin here for this test, even if we're run with
426
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
427
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
428
sys.modules['bzrlib.plugins.demo_module'] = demo_module
430
topics = index.get_topics('demo_module')
431
self.assertEqual(1, len(topics))
432
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
433
self.assertEqual(demo_module, topics[0].module)
435
del sys.modules['bzrlib.plugins.demo_module']
437
def test_get_topics_no_topic(self):
438
"""Searching for something that is not a plugin returns []."""
439
# test this by using a name that cannot be a plugin - its not
440
# a valid python identifier.
441
index = plugin.PluginsHelpIndex()
442
self.assertEqual([], index.get_topics('nothing by this name'))
444
def test_prefix(self):
445
"""PluginsHelpIndex has a prefix of 'plugins/'."""
446
index = plugin.PluginsHelpIndex()
447
self.assertEqual('plugins/', index.prefix)
449
def test_get_plugin_topic_with_prefix(self):
450
"""Searching for plugins/demo_module returns help."""
451
index = plugin.PluginsHelpIndex()
452
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
453
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
454
sys.modules['bzrlib.plugins.demo_module'] = demo_module
456
topics = index.get_topics('plugins/demo_module')
457
self.assertEqual(1, len(topics))
458
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
459
self.assertEqual(demo_module, topics[0].module)
461
del sys.modules['bzrlib.plugins.demo_module']
464
class FakeModule(object):
465
"""A fake module to test with."""
467
def __init__(self, doc, name):
472
class TestModuleHelpTopic(tests.TestCase):
473
"""Tests for the ModuleHelpTopic class."""
475
def test_contruct(self):
476
"""Construction takes the module to document."""
477
mod = FakeModule('foo', 'foo')
478
topic = plugin.ModuleHelpTopic(mod)
479
self.assertEqual(mod, topic.module)
481
def test_get_help_text_None(self):
482
"""A ModuleHelpTopic returns the docstring for get_help_text."""
483
mod = FakeModule(None, 'demo')
484
topic = plugin.ModuleHelpTopic(mod)
485
self.assertEqual("Plugin 'demo' has no docstring.\n",
486
topic.get_help_text())
488
def test_get_help_text_no_carriage_return(self):
489
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
490
mod = FakeModule('one line of help', 'demo')
491
topic = plugin.ModuleHelpTopic(mod)
492
self.assertEqual("one line of help\n",
493
topic.get_help_text())
495
def test_get_help_text_carriage_return(self):
496
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
497
mod = FakeModule('two lines of help\nand more\n', 'demo')
498
topic = plugin.ModuleHelpTopic(mod)
499
self.assertEqual("two lines of help\nand more\n",
500
topic.get_help_text())
502
def test_get_help_text_with_additional_see_also(self):
503
mod = FakeModule('two lines of help\nand more', 'demo')
504
topic = plugin.ModuleHelpTopic(mod)
505
self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
506
topic.get_help_text(['foo', 'bar']))
508
def test_get_help_topic(self):
509
"""The help topic for a plugin is its module name."""
510
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
511
topic = plugin.ModuleHelpTopic(mod)
512
self.assertEqual('demo', topic.get_help_topic())
513
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
514
topic = plugin.ModuleHelpTopic(mod)
515
self.assertEqual('foo_bar', topic.get_help_topic())
47
newhelp = backtick('bzr help commands')
48
assert newhelp.startswith('You have been overridden\n')
49
# We added a line, but the rest should work
50
assert newhelp[25:] == help
52
assert backtick('bzr commit -m test') == "I'm sorry dave, you can't do that\n"
54
shutil.rmtree('plugin_test')
60
# """import bzrlib, bzrlib.commands
61
# class cmd_myplug(bzrlib.commands.Command):
62
# '''Just a simple test plugin.'''
65
# print 'Hello from my plugin'
69
# os.environ['BZRPLUGINPATH'] = os.path.abspath('plugin_test')
70
# help = backtick('bzr help commands')
71
# assert help.find('myplug') != -1
72
# assert help.find('Just a simple test plugin.') != -1
75
# assert backtick('bzr myplug') == 'Hello from my plugin\n'
76
# assert backtick('bzr mplg') == 'Hello from my plugin\n'
78
# f = open(os.path.join('plugin_test', 'override.py'), 'wb')
79
# f.write("""import bzrlib, bzrlib.commands
80
# class cmd_commit(bzrlib.commands.cmd_commit):
81
# '''Commit changes into a new revision.'''
82
# def run(self, *args, **kwargs):
83
# print "I'm sorry dave, you can't do that"
85
# class cmd_help(bzrlib.commands.cmd_help):
86
# '''Show help on a command or other topic.'''
87
# def run(self, *args, **kwargs):
88
# print "You have been overridden"
89
# bzrlib.commands.cmd_help.run(self, *args, **kwargs)