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
29
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)
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
74
40
import bzrlib.commands
82
48
# TODO: Write a test for plugin decoration of commands.
84
class TestOneNamedPluginOnly(TestCaseInTempDir):
50
class TestLoadingPlugins(TestCaseInTempDir):
86
52
activeattributes = {}
88
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):
89
102
# 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
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
92
106
# for accessing in activeattributes. - we cannot give import parameters.
107
tempattribute = "different-dirs"
94
108
self.failIf(tempattribute in self.activeattributes)
95
109
# set a place for the plugins to record their loading, and at the same
96
110
# time validate that the location the plugins should record to is
97
111
# valid and correct.
98
bzrlib.tests.test_plugins.TestOneNamedPluginOnly.activeattributes \
112
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
99
113
[tempattribute] = []
100
114
self.failUnless(tempattribute in self.activeattributes)
101
115
# create two plugin directories
102
116
os.mkdir('first')
103
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')
104
170
# write a plugin that will record when its loaded in the
105
171
# 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'])
112
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])
114
186
# remove the plugin 'plugin'
115
187
del self.activeattributes[tempattribute]
116
if getattr(bzrlib.plugins, 'plugin', None):
117
del bzrlib.plugins.plugin
118
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_'\.")
121
217
class TestAllPlugins(TestCaseInTempDir):
125
221
# check the plugin is not loaded already
126
222
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
127
223
# write a plugin that _cannot_ fail to load.
128
print >> file('plugin.py', 'w'), ""
224
file('plugin.py', 'w').write("\n")
130
bzrlib.plugin.load_from_dirs(['.'])
131
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)
132
230
self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
133
self.assertEqual(bzrlib.plugin.all_plugins()['plugin'],
134
bzrlib.plugins.plugin)
231
self.assertEqual(all_plugins['plugin'], bzrlib.plugins.plugin)
136
233
# remove the plugin 'plugin'
234
if 'bzrlib.plugins.plugin' in sys.modules:
235
del sys.modules['bzrlib.plugins.plugin']
137
236
if getattr(bzrlib.plugins, 'plugin', None):
138
237
del bzrlib.plugins.plugin
139
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_no_test_suite_gives_None_for_test_suite(self):
278
plugin = bzrlib.plugin.plugins()['plugin']
279
self.assertEqual(None, plugin.test_suite())
281
def test_test_suite_gives_test_suite_result(self):
282
source = """def test_suite(): return 'foo'"""
283
self.setup_plugin(source)
284
plugin = bzrlib.plugin.plugins()['plugin']
285
self.assertEqual('foo', plugin.test_suite())
287
def test_no_version_info(self):
289
plugin = bzrlib.plugin.plugins()['plugin']
290
self.assertEqual(None, plugin.version_info())
292
def test_with_version_info(self):
293
self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
294
plugin = bzrlib.plugin.plugins()['plugin']
295
self.assertEqual((1, 2, 3, 'dev', 4), plugin.version_info())
297
def test_short_version_info_gets_padded(self):
298
# the gtk plugin has version_info = (1,2,3) rather than the 5-tuple.
300
self.setup_plugin("version_info = (1, 2, 3)")
301
plugin = bzrlib.plugin.plugins()['plugin']
302
self.assertEqual((1, 2, 3, 'final', 0), plugin.version_info())
304
def test_no_version_info___version__(self):
306
plugin = bzrlib.plugin.plugins()['plugin']
307
self.assertEqual("unknown", plugin.__version__)
309
def test___version__with_version_info(self):
310
self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
311
plugin = bzrlib.plugin.plugins()['plugin']
312
self.assertEqual("1.2.3dev4", plugin.__version__)
314
def test_final__version__with_version_info(self):
315
self.setup_plugin("version_info = (1, 2, 3, 'final', 4)")
316
plugin = bzrlib.plugin.plugins()['plugin']
317
self.assertEqual("1.2.3", plugin.__version__)
320
class TestPluginHelp(TestCaseInTempDir):
322
def split_help_commands(self):
325
for line in self.run_bzr('help commands')[0].splitlines():
326
if not line.startswith(' '):
327
current = line.split()[0]
328
help[current] = help.get(current, '') + line
332
def test_plugin_help_builtins_unaffected(self):
333
# Check we don't get false positives
334
help_commands = self.split_help_commands()
335
for cmd_name in bzrlib.commands.builtin_command_names():
336
if cmd_name in bzrlib.commands.plugin_command_names():
339
help = bzrlib.commands.get_cmd_object(cmd_name).get_help_text()
340
except NotImplementedError:
341
# some commands have no help
344
self.assertNotContainsRe(help, 'plugin "[^"]*"')
346
if cmd_name in help_commands.keys():
347
# some commands are hidden
348
help = help_commands[cmd_name]
349
self.assertNotContainsRe(help, 'plugin "[^"]*"')
351
def test_plugin_help_shows_plugin(self):
352
# Create a test plugin
353
os.mkdir('plugin_test')
354
f = open(pathjoin('plugin_test', 'myplug.py'), 'w')
360
bzrlib.plugin.load_from_path(['plugin_test'])
361
bzrlib.commands.register_command( bzrlib.plugins.myplug.cmd_myplug)
362
help = self.run_bzr('help myplug')[0]
363
self.assertContainsRe(help, 'plugin "myplug"')
364
help = self.split_help_commands()['myplug']
365
self.assertContainsRe(help, '\[myplug\]')
368
if bzrlib.commands.plugin_cmds.get('myplug', None):
369
del bzrlib.commands.plugin_cmds['myplug']
370
# remove the plugin 'myplug'
371
if getattr(bzrlib.plugins, 'myplug', None):
372
delattr(bzrlib.plugins, 'myplug')
375
class TestPluginFromZip(TestCaseInTempDir):
377
def make_zipped_plugin(self, zip_name, filename):
378
z = zipfile.ZipFile(zip_name, 'w')
379
z.writestr(filename, PLUGIN_TEXT)
382
def check_plugin_load(self, zip_name, plugin_name):
383
self.assertFalse(plugin_name in dir(bzrlib.plugins),
384
'Plugin already loaded')
385
old_path = bzrlib.plugins.__path__
387
# this is normally done by load_plugins -> set_plugins_path
388
bzrlib.plugins.__path__ = [zip_name]
389
bzrlib.plugin.load_from_zip(zip_name)
390
self.assertTrue(plugin_name in dir(bzrlib.plugins),
391
'Plugin is not loaded')
394
if getattr(bzrlib.plugins, plugin_name, None):
395
delattr(bzrlib.plugins, plugin_name)
396
del sys.modules['bzrlib.plugins.' + plugin_name]
397
bzrlib.plugins.__path__ = old_path
399
def test_load_module(self):
400
self.make_zipped_plugin('./test.zip', 'ziplug.py')
401
self.check_plugin_load('./test.zip', 'ziplug')
403
def test_load_package(self):
404
self.make_zipped_plugin('./test.zip', 'ziplug/__init__.py')
405
self.check_plugin_load('./test.zip', 'ziplug')
408
class TestSetPluginsPath(TestCase):
410
def test_set_plugins_path(self):
411
"""set_plugins_path should set the module __path__ correctly."""
412
old_path = bzrlib.plugins.__path__
414
bzrlib.plugins.__path__ = []
415
expected_path = bzrlib.plugin.set_plugins_path()
416
self.assertEqual(expected_path, bzrlib.plugins.__path__)
418
bzrlib.plugins.__path__ = old_path
420
def test_set_plugins_path_with_trailing_slashes(self):
421
"""set_plugins_path should set the module __path__ based on
423
old_path = bzrlib.plugins.__path__
424
old_env = os.environ.get('BZR_PLUGIN_PATH')
426
bzrlib.plugins.__path__ = []
427
os.environ['BZR_PLUGIN_PATH'] = "first\\//\\" + os.pathsep + \
429
bzrlib.plugin.set_plugins_path()
430
expected_path = ['first', 'second',
431
os.path.dirname(bzrlib.plugins.__file__)]
432
self.assertEqual(expected_path, bzrlib.plugins.__path__)
434
bzrlib.plugins.__path__ = old_path
436
os.environ['BZR_PLUGIN_PATH'] = old_env
438
del os.environ['BZR_PLUGIN_PATH']
440
class TestHelpIndex(tests.TestCase):
441
"""Tests for the PluginsHelpIndex class."""
443
def test_default_constructable(self):
444
index = plugin.PluginsHelpIndex()
446
def test_get_topics_None(self):
447
"""Searching for None returns an empty list."""
448
index = plugin.PluginsHelpIndex()
449
self.assertEqual([], index.get_topics(None))
451
def test_get_topics_for_plugin(self):
452
"""Searching for plugin name gets its docstring."""
453
index = plugin.PluginsHelpIndex()
454
# make a new plugin here for this test, even if we're run with
456
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
457
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
458
sys.modules['bzrlib.plugins.demo_module'] = demo_module
460
topics = index.get_topics('demo_module')
461
self.assertEqual(1, len(topics))
462
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
463
self.assertEqual(demo_module, topics[0].module)
465
del sys.modules['bzrlib.plugins.demo_module']
467
def test_get_topics_no_topic(self):
468
"""Searching for something that is not a plugin returns []."""
469
# test this by using a name that cannot be a plugin - its not
470
# a valid python identifier.
471
index = plugin.PluginsHelpIndex()
472
self.assertEqual([], index.get_topics('nothing by this name'))
474
def test_prefix(self):
475
"""PluginsHelpIndex has a prefix of 'plugins/'."""
476
index = plugin.PluginsHelpIndex()
477
self.assertEqual('plugins/', index.prefix)
479
def test_get_plugin_topic_with_prefix(self):
480
"""Searching for plugins/demo_module returns help."""
481
index = plugin.PluginsHelpIndex()
482
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
483
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
484
sys.modules['bzrlib.plugins.demo_module'] = demo_module
486
topics = index.get_topics('plugins/demo_module')
487
self.assertEqual(1, len(topics))
488
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
489
self.assertEqual(demo_module, topics[0].module)
491
del sys.modules['bzrlib.plugins.demo_module']
494
class FakeModule(object):
495
"""A fake module to test with."""
497
def __init__(self, doc, name):
502
class TestModuleHelpTopic(tests.TestCase):
503
"""Tests for the ModuleHelpTopic class."""
505
def test_contruct(self):
506
"""Construction takes the module to document."""
507
mod = FakeModule('foo', 'foo')
508
topic = plugin.ModuleHelpTopic(mod)
509
self.assertEqual(mod, topic.module)
511
def test_get_help_text_None(self):
512
"""A ModuleHelpTopic returns the docstring for get_help_text."""
513
mod = FakeModule(None, 'demo')
514
topic = plugin.ModuleHelpTopic(mod)
515
self.assertEqual("Plugin 'demo' has no docstring.\n",
516
topic.get_help_text())
518
def test_get_help_text_no_carriage_return(self):
519
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
520
mod = FakeModule('one line of help', 'demo')
521
topic = plugin.ModuleHelpTopic(mod)
522
self.assertEqual("one line of help\n",
523
topic.get_help_text())
525
def test_get_help_text_carriage_return(self):
526
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
527
mod = FakeModule('two lines of help\nand more\n', 'demo')
528
topic = plugin.ModuleHelpTopic(mod)
529
self.assertEqual("two lines of help\nand more\n",
530
topic.get_help_text())
532
def test_get_help_text_with_additional_see_also(self):
533
mod = FakeModule('two lines of help\nand more', 'demo')
534
topic = plugin.ModuleHelpTopic(mod)
535
self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
536
topic.get_help_text(['foo', 'bar']))
538
def test_get_help_topic(self):
539
"""The help topic for a plugin is its module name."""
540
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
541
topic = plugin.ModuleHelpTopic(mod)
542
self.assertEqual('demo', topic.get_help_topic())
543
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
544
topic = plugin.ModuleHelpTopic(mod)
545
self.assertEqual('foo_bar', topic.get_help_topic())