1
# Copyright (C) 2005 by Canonical Ltd
1
# Copyright (C) 2005, 2007 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
18
17
"""Tests for plugins"""
20
19
# XXX: There are no plugin tests at the moment because the plugin module
21
20
# affects the global state of the process. See bzrlib/plugins.py for more
25
from StringIO import StringIO
29
from bzrlib import plugin, tests
26
30
import bzrlib.plugin
27
31
import bzrlib.plugins
28
from bzrlib.tests import TestCaseInTempDir
30
class PluginTest(TestCaseInTempDir):
31
"""Create an external plugin and test loading."""
32
# def test_plugin_loading(self):
33
# orig_help = self.run_bzr_captured('bzr help commands')[0]
34
# os.mkdir('plugin_test')
35
# f = open(os.path.join('plugin_test', 'myplug.py'), 'wt')
36
# f.write(PLUGIN_TEXT)
38
# newhelp = self.run_bzr_captured('bzr help commands')[0]
39
# assert newhelp.startswith('You have been overridden\n')
40
# # We added a line, but the rest should work
41
# assert newhelp[25:] == help
43
# assert backtick('bzr commit -m test') == "I'm sorry dave, you can't do that\n"
45
# shutil.rmtree('plugin_test')
48
# os.environ['BZRPLUGINPATH'] = os.path.abspath('plugin_test')
49
# help = backtick('bzr help commands')
50
# assert help.find('myplug') != -1
51
# assert help.find('Just a simple test plugin.') != -1
54
# assert backtick('bzr myplug') == 'Hello from my plugin\n'
55
# assert backtick('bzr mplg') == 'Hello from my plugin\n'
57
# f = open(os.path.join('plugin_test', 'override.py'), 'wb')
58
# f.write("""import bzrlib, bzrlib.commands
59
# class cmd_commit(bzrlib.commands.cmd_commit):
60
# '''Commit changes into a new revision.'''
61
# def run(self, *args, **kwargs):
62
# print "I'm sorry dave, you can't do that"
64
# class cmd_help(bzrlib.commands.cmd_help):
65
# '''Show help on a command or other topic.'''
66
# def run(self, *args, **kwargs):
67
# print "You have been overridden"
68
# bzrlib.commands.cmd_help.run(self, *args, **kwargs)
32
import bzrlib.commands
34
from bzrlib.symbol_versioning import zero_ninetyone
35
from bzrlib.tests import TestCase, TestCaseInTempDir
36
from bzrlib.osutils import pathjoin, abspath, normpath
73
40
import bzrlib.commands
81
48
# TODO: Write a test for plugin decoration of commands.
83
class TestOneNamedPluginOnly(TestCaseInTempDir):
50
class TestLoadingPlugins(TestCaseInTempDir):
85
52
activeattributes = {}
87
54
def test_plugins_with_the_same_name_are_not_loaded(self):
55
# This test tests that having two plugins in different directories does
56
# not result in both being loaded when they have the same name. get a
57
# file name we can use which is also a valid attribute for accessing in
58
# activeattributes. - we cannot give import parameters.
60
self.failIf(tempattribute in self.activeattributes)
61
# set a place for the plugins to record their loading, and at the same
62
# time validate that the location the plugins should record to is
64
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
66
self.failUnless(tempattribute in self.activeattributes)
67
# create two plugin directories
70
# write a plugin that will record when its loaded in the
72
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
73
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
75
outfile = open(os.path.join('first', 'plugin.py'), 'w')
77
outfile.write(template % (tempattribute, 'first'))
82
outfile = open(os.path.join('second', 'plugin.py'), 'w')
84
outfile.write(template % (tempattribute, 'second'))
90
bzrlib.plugin.load_from_path(['first', 'second'])
91
self.assertEqual(['first'], self.activeattributes[tempattribute])
93
# remove the plugin 'plugin'
94
del self.activeattributes[tempattribute]
95
if 'bzrlib.plugins.plugin' in sys.modules:
96
del sys.modules['bzrlib.plugins.plugin']
97
if getattr(bzrlib.plugins, 'plugin', None):
98
del bzrlib.plugins.plugin
99
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
101
def test_plugins_from_different_dirs_can_demand_load(self):
88
102
# This test tests that having two plugins in different
89
# directories does not result in both being loaded.
90
# get a file name we can use which is also a valid attribute
103
# directories with different names allows them both to be loaded, when
104
# we do a direct import statement.
105
# Determine a file name we can use which is also a valid attribute
91
106
# for accessing in activeattributes. - we cannot give import parameters.
107
tempattribute = "different-dirs"
93
108
self.failIf(tempattribute in self.activeattributes)
94
109
# set a place for the plugins to record their loading, and at the same
95
110
# time validate that the location the plugins should record to is
96
111
# valid and correct.
97
bzrlib.tests.test_plugins.TestOneNamedPluginOnly.activeattributes \
112
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
98
113
[tempattribute] = []
99
114
self.failUnless(tempattribute in self.activeattributes)
100
115
# create two plugin directories
101
116
os.mkdir('first')
102
117
os.mkdir('second')
118
# write plugins that will record when they are loaded in the
119
# tempattribute list.
120
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
121
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
123
outfile = open(os.path.join('first', 'pluginone.py'), 'w')
125
outfile.write(template % (tempattribute, 'first'))
130
outfile = open(os.path.join('second', 'plugintwo.py'), 'w')
132
outfile.write(template % (tempattribute, 'second'))
137
oldpath = bzrlib.plugins.__path__
139
bzrlib.plugins.__path__ = ['first', 'second']
140
exec "import bzrlib.plugins.pluginone"
141
self.assertEqual(['first'], self.activeattributes[tempattribute])
142
exec "import bzrlib.plugins.plugintwo"
143
self.assertEqual(['first', 'second'],
144
self.activeattributes[tempattribute])
146
# remove the plugin 'plugin'
147
del self.activeattributes[tempattribute]
148
if getattr(bzrlib.plugins, 'pluginone', None):
149
del bzrlib.plugins.pluginone
150
if getattr(bzrlib.plugins, 'plugintwo', None):
151
del bzrlib.plugins.plugintwo
152
self.failIf(getattr(bzrlib.plugins, 'pluginone', None))
153
self.failIf(getattr(bzrlib.plugins, 'plugintwo', None))
155
def test_plugins_can_load_from_directory_with_trailing_slash(self):
156
# This test tests that a plugin can load from a directory when the
157
# directory in the path has a trailing slash.
158
# check the plugin is not loaded already
159
self.failIf(getattr(bzrlib.plugins, 'ts_plugin', None))
160
tempattribute = "trailing-slash"
161
self.failIf(tempattribute in self.activeattributes)
162
# set a place for the plugin to record its loading, and at the same
163
# time validate that the location the plugin should record to is
165
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
167
self.failUnless(tempattribute in self.activeattributes)
168
# create a directory for the plugin
169
os.mkdir('plugin_test')
103
170
# write a plugin that will record when its loaded in the
104
171
# tempattribute list.
105
template = ("from bzrlib.tests.test_plugins import TestOneNamedPluginOnly\n"
106
"TestOneNamedPluginOnly.activeattributes[%r].append('%s')\n")
107
print >> file(os.path.join('first', 'plugin.py'), 'w'), template % (tempattribute, 'first')
108
print >> file(os.path.join('second', 'plugin.py'), 'w'), template % (tempattribute, 'second')
110
bzrlib.plugin.load_from_dirs(['first', 'second'])
111
self.assertEqual(['first'], self.activeattributes[tempattribute])
172
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
173
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
175
outfile = open(os.path.join('plugin_test', 'ts_plugin.py'), 'w')
177
outfile.write(template % (tempattribute, 'plugin'))
183
bzrlib.plugin.load_from_path(['plugin_test'+os.sep])
184
self.assertEqual(['plugin'], self.activeattributes[tempattribute])
113
186
# remove the plugin 'plugin'
114
187
del self.activeattributes[tempattribute]
115
if getattr(bzrlib.plugins, 'plugin', None):
116
del bzrlib.plugins.plugin
117
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
188
if getattr(bzrlib.plugins, 'ts_plugin', None):
189
del bzrlib.plugins.ts_plugin
190
self.failIf(getattr(bzrlib.plugins, 'ts_plugin', None))
192
def test_plugin_with_bad_name_does_not_load(self):
193
# Create badly-named plugin
194
file('bad plugin-name..py', 'w').close()
198
handler = logging.StreamHandler(stream)
199
log = logging.getLogger('bzr')
200
log.addHandler(handler)
202
bzrlib.plugin.load_from_dir('.')
204
# Stop capturing output
207
log.removeHandler(handler)
209
self.assertContainsRe(stream.getvalue(),
210
r"Unable to load 'bad plugin-name\.' in '\.' as a plugin because"
211
" file path isn't a valid module name; try renaming it to"
212
" 'bad_plugin_name_'\.")
120
217
class TestAllPlugins(TestCaseInTempDir):
124
221
# check the plugin is not loaded already
125
222
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
126
223
# write a plugin that _cannot_ fail to load.
127
print >> file('plugin.py', 'w'), ""
224
file('plugin.py', 'w').write("\n")
129
bzrlib.plugin.load_from_dirs(['.'])
130
self.failUnless('plugin' in bzrlib.plugin.all_plugins())
226
bzrlib.plugin.load_from_path(['.'])
227
all_plugins = self.applyDeprecated(zero_ninetyone,
228
bzrlib.plugin.all_plugins)
229
self.failUnless('plugin' in all_plugins)
131
230
self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
132
self.assertEqual(bzrlib.plugin.all_plugins()['plugin'],
133
bzrlib.plugins.plugin)
231
self.assertEqual(all_plugins['plugin'], bzrlib.plugins.plugin)
135
233
# remove the plugin 'plugin'
234
if 'bzrlib.plugins.plugin' in sys.modules:
235
del sys.modules['bzrlib.plugins.plugin']
136
236
if getattr(bzrlib.plugins, 'plugin', None):
137
237
del bzrlib.plugins.plugin
138
238
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
241
class TestPlugins(TestCaseInTempDir):
243
def setup_plugin(self, source=""):
244
# This test tests a new plugin appears in bzrlib.plugin.plugins().
245
# check the plugin is not loaded already
246
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
247
# write a plugin that _cannot_ fail to load.
248
file('plugin.py', 'w').write(source + '\n')
249
self.addCleanup(self.teardown_plugin)
250
bzrlib.plugin.load_from_path(['.'])
252
def teardown_plugin(self):
253
# remove the plugin 'plugin'
254
if 'bzrlib.plugins.plugin' in sys.modules:
255
del sys.modules['bzrlib.plugins.plugin']
256
if getattr(bzrlib.plugins, 'plugin', None):
257
del bzrlib.plugins.plugin
258
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
260
def test_plugin_appears_in_plugins(self):
262
self.failUnless('plugin' in bzrlib.plugin.plugins())
263
self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
264
plugins = bzrlib.plugin.plugins()
265
plugin = plugins['plugin']
266
self.assertIsInstance(plugin, bzrlib.plugin.PlugIn)
267
self.assertEqual(bzrlib.plugins.plugin, plugin.module)
269
def test_trivial_plugin_get_path(self):
271
plugins = bzrlib.plugin.plugins()
272
plugin = plugins['plugin']
273
plugin_path = self.test_dir + '/plugin.py'
274
self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
276
def test_plugin_get_path_py_not_pyc(self):
277
self.setup_plugin() # after first import there will be plugin.pyc
278
self.teardown_plugin()
279
bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
280
plugins = bzrlib.plugin.plugins()
281
plugin = plugins['plugin']
282
plugin_path = self.test_dir + '/plugin.py'
283
self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
285
def test_plugin_get_path_pyc_only(self):
286
self.setup_plugin() # after first import there will be plugin.pyc
287
self.teardown_plugin()
288
os.unlink(self.test_dir + '/plugin.py')
289
bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
290
plugins = bzrlib.plugin.plugins()
291
plugin = plugins['plugin']
293
plugin_path = self.test_dir + '/plugin.pyc'
295
plugin_path = self.test_dir + '/plugin.pyo'
296
self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
298
def test_no_test_suite_gives_None_for_test_suite(self):
300
plugin = bzrlib.plugin.plugins()['plugin']
301
self.assertEqual(None, plugin.test_suite())
303
def test_test_suite_gives_test_suite_result(self):
304
source = """def test_suite(): return 'foo'"""
305
self.setup_plugin(source)
306
plugin = bzrlib.plugin.plugins()['plugin']
307
self.assertEqual('foo', plugin.test_suite())
309
def test_no_version_info(self):
311
plugin = bzrlib.plugin.plugins()['plugin']
312
self.assertEqual(None, plugin.version_info())
314
def test_with_version_info(self):
315
self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
316
plugin = bzrlib.plugin.plugins()['plugin']
317
self.assertEqual((1, 2, 3, 'dev', 4), plugin.version_info())
319
def test_short_version_info_gets_padded(self):
320
# the gtk plugin has version_info = (1,2,3) rather than the 5-tuple.
322
self.setup_plugin("version_info = (1, 2, 3)")
323
plugin = bzrlib.plugin.plugins()['plugin']
324
self.assertEqual((1, 2, 3, 'final', 0), plugin.version_info())
326
def test_no_version_info___version__(self):
328
plugin = bzrlib.plugin.plugins()['plugin']
329
self.assertEqual("unknown", plugin.__version__)
331
def test___version__with_version_info(self):
332
self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
333
plugin = bzrlib.plugin.plugins()['plugin']
334
self.assertEqual("1.2.3dev4", plugin.__version__)
336
def test_final__version__with_version_info(self):
337
self.setup_plugin("version_info = (1, 2, 3, 'final', 4)")
338
plugin = bzrlib.plugin.plugins()['plugin']
339
self.assertEqual("1.2.3", plugin.__version__)
342
class TestPluginHelp(TestCaseInTempDir):
344
def split_help_commands(self):
347
for line in self.run_bzr('help commands')[0].splitlines():
348
if not line.startswith(' '):
349
current = line.split()[0]
350
help[current] = help.get(current, '') + line
354
def test_plugin_help_builtins_unaffected(self):
355
# Check we don't get false positives
356
help_commands = self.split_help_commands()
357
for cmd_name in bzrlib.commands.builtin_command_names():
358
if cmd_name in bzrlib.commands.plugin_command_names():
361
help = bzrlib.commands.get_cmd_object(cmd_name).get_help_text()
362
except NotImplementedError:
363
# some commands have no help
366
self.assertNotContainsRe(help, 'plugin "[^"]*"')
368
if cmd_name in help_commands.keys():
369
# some commands are hidden
370
help = help_commands[cmd_name]
371
self.assertNotContainsRe(help, 'plugin "[^"]*"')
373
def test_plugin_help_shows_plugin(self):
374
# Create a test plugin
375
os.mkdir('plugin_test')
376
f = open(pathjoin('plugin_test', 'myplug.py'), 'w')
382
bzrlib.plugin.load_from_path(['plugin_test'])
383
bzrlib.commands.register_command( bzrlib.plugins.myplug.cmd_myplug)
384
help = self.run_bzr('help myplug')[0]
385
self.assertContainsRe(help, 'plugin "myplug"')
386
help = self.split_help_commands()['myplug']
387
self.assertContainsRe(help, '\[myplug\]')
390
if bzrlib.commands.plugin_cmds.get('myplug', None):
391
del bzrlib.commands.plugin_cmds['myplug']
392
# remove the plugin 'myplug'
393
if getattr(bzrlib.plugins, 'myplug', None):
394
delattr(bzrlib.plugins, 'myplug')
397
class TestPluginFromZip(TestCaseInTempDir):
399
def make_zipped_plugin(self, zip_name, filename):
400
z = zipfile.ZipFile(zip_name, 'w')
401
z.writestr(filename, PLUGIN_TEXT)
404
def check_plugin_load(self, zip_name, plugin_name):
405
self.assertFalse(plugin_name in dir(bzrlib.plugins),
406
'Plugin already loaded')
407
old_path = bzrlib.plugins.__path__
409
# this is normally done by load_plugins -> set_plugins_path
410
bzrlib.plugins.__path__ = [zip_name]
411
bzrlib.plugin.load_from_zip(zip_name)
412
self.assertTrue(plugin_name in dir(bzrlib.plugins),
413
'Plugin is not loaded')
416
if getattr(bzrlib.plugins, plugin_name, None):
417
delattr(bzrlib.plugins, plugin_name)
418
del sys.modules['bzrlib.plugins.' + plugin_name]
419
bzrlib.plugins.__path__ = old_path
421
def test_load_module(self):
422
self.make_zipped_plugin('./test.zip', 'ziplug.py')
423
self.check_plugin_load('./test.zip', 'ziplug')
425
def test_load_package(self):
426
self.make_zipped_plugin('./test.zip', 'ziplug/__init__.py')
427
self.check_plugin_load('./test.zip', 'ziplug')
430
class TestSetPluginsPath(TestCase):
432
def test_set_plugins_path(self):
433
"""set_plugins_path should set the module __path__ correctly."""
434
old_path = bzrlib.plugins.__path__
436
bzrlib.plugins.__path__ = []
437
expected_path = bzrlib.plugin.set_plugins_path()
438
self.assertEqual(expected_path, bzrlib.plugins.__path__)
440
bzrlib.plugins.__path__ = old_path
442
def test_set_plugins_path_with_trailing_slashes(self):
443
"""set_plugins_path should set the module __path__ based on
445
old_path = bzrlib.plugins.__path__
446
old_env = os.environ.get('BZR_PLUGIN_PATH')
448
bzrlib.plugins.__path__ = []
449
os.environ['BZR_PLUGIN_PATH'] = "first\\//\\" + os.pathsep + \
451
bzrlib.plugin.set_plugins_path()
452
expected_path = ['first', 'second',
453
os.path.dirname(bzrlib.plugins.__file__)]
454
self.assertEqual(expected_path, bzrlib.plugins.__path__)
456
bzrlib.plugins.__path__ = old_path
458
os.environ['BZR_PLUGIN_PATH'] = old_env
460
del os.environ['BZR_PLUGIN_PATH']
462
class TestHelpIndex(tests.TestCase):
463
"""Tests for the PluginsHelpIndex class."""
465
def test_default_constructable(self):
466
index = plugin.PluginsHelpIndex()
468
def test_get_topics_None(self):
469
"""Searching for None returns an empty list."""
470
index = plugin.PluginsHelpIndex()
471
self.assertEqual([], index.get_topics(None))
473
def test_get_topics_for_plugin(self):
474
"""Searching for plugin name gets its docstring."""
475
index = plugin.PluginsHelpIndex()
476
# make a new plugin here for this test, even if we're run with
478
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
479
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
480
sys.modules['bzrlib.plugins.demo_module'] = demo_module
482
topics = index.get_topics('demo_module')
483
self.assertEqual(1, len(topics))
484
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
485
self.assertEqual(demo_module, topics[0].module)
487
del sys.modules['bzrlib.plugins.demo_module']
489
def test_get_topics_no_topic(self):
490
"""Searching for something that is not a plugin returns []."""
491
# test this by using a name that cannot be a plugin - its not
492
# a valid python identifier.
493
index = plugin.PluginsHelpIndex()
494
self.assertEqual([], index.get_topics('nothing by this name'))
496
def test_prefix(self):
497
"""PluginsHelpIndex has a prefix of 'plugins/'."""
498
index = plugin.PluginsHelpIndex()
499
self.assertEqual('plugins/', index.prefix)
501
def test_get_plugin_topic_with_prefix(self):
502
"""Searching for plugins/demo_module returns help."""
503
index = plugin.PluginsHelpIndex()
504
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
505
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
506
sys.modules['bzrlib.plugins.demo_module'] = demo_module
508
topics = index.get_topics('plugins/demo_module')
509
self.assertEqual(1, len(topics))
510
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
511
self.assertEqual(demo_module, topics[0].module)
513
del sys.modules['bzrlib.plugins.demo_module']
516
class FakeModule(object):
517
"""A fake module to test with."""
519
def __init__(self, doc, name):
524
class TestModuleHelpTopic(tests.TestCase):
525
"""Tests for the ModuleHelpTopic class."""
527
def test_contruct(self):
528
"""Construction takes the module to document."""
529
mod = FakeModule('foo', 'foo')
530
topic = plugin.ModuleHelpTopic(mod)
531
self.assertEqual(mod, topic.module)
533
def test_get_help_text_None(self):
534
"""A ModuleHelpTopic returns the docstring for get_help_text."""
535
mod = FakeModule(None, 'demo')
536
topic = plugin.ModuleHelpTopic(mod)
537
self.assertEqual("Plugin 'demo' has no docstring.\n",
538
topic.get_help_text())
540
def test_get_help_text_no_carriage_return(self):
541
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
542
mod = FakeModule('one line of help', 'demo')
543
topic = plugin.ModuleHelpTopic(mod)
544
self.assertEqual("one line of help\n",
545
topic.get_help_text())
547
def test_get_help_text_carriage_return(self):
548
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
549
mod = FakeModule('two lines of help\nand more\n', 'demo')
550
topic = plugin.ModuleHelpTopic(mod)
551
self.assertEqual("two lines of help\nand more\n",
552
topic.get_help_text())
554
def test_get_help_text_with_additional_see_also(self):
555
mod = FakeModule('two lines of help\nand more', 'demo')
556
topic = plugin.ModuleHelpTopic(mod)
557
self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
558
topic.get_help_text(['foo', 'bar']))
560
def test_get_help_topic(self):
561
"""The help topic for a plugin is its module name."""
562
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
563
topic = plugin.ModuleHelpTopic(mod)
564
self.assertEqual('demo', topic.get_help_topic())
565
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
566
topic = plugin.ModuleHelpTopic(mod)
567
self.assertEqual('foo_bar', topic.get_help_topic())