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
25
from StringIO import StringIO
29
from bzrlib import plugin, tests
27
30
import bzrlib.plugin
28
31
import bzrlib.plugins
29
32
import bzrlib.commands
31
from bzrlib.tests import TestCaseInTempDir
32
from bzrlib.osutils import pathjoin, abspath
34
class PluginTest(TestCaseInTempDir):
35
"""Create an external plugin and test loading."""
36
# def test_plugin_loading(self):
37
# orig_help = self.run_bzr_captured('bzr help commands')[0]
38
# os.mkdir('plugin_test')
39
# f = open(pathjoin('plugin_test', 'myplug.py'), 'wt')
40
# f.write(PLUGIN_TEXT)
42
# newhelp = self.run_bzr_captured('bzr help commands')[0]
43
# assert newhelp.startswith('You have been overridden\n')
44
# # We added a line, but the rest should work
45
# assert newhelp[25:] == help
47
# assert backtick('bzr commit -m test') == "I'm sorry dave, you can't do that\n"
49
# shutil.rmtree('plugin_test')
52
# os.environ['BZRPLUGINPATH'] = abspath('plugin_test')
53
# help = backtick('bzr help commands')
54
# assert help.find('myplug') != -1
55
# assert help.find('Just a simple test plugin.') != -1
58
# assert backtick('bzr myplug') == 'Hello from my plugin\n'
59
# assert backtick('bzr mplg') == 'Hello from my plugin\n'
61
# f = open(pathjoin('plugin_test', 'override.py'), 'wb')
62
# f.write("""import bzrlib, bzrlib.commands
63
# class cmd_commit(bzrlib.commands.cmd_commit):
64
# '''Commit changes into a new revision.'''
65
# def run(self, *args, **kwargs):
66
# print "I'm sorry dave, you can't do that"
68
# class cmd_help(bzrlib.commands.cmd_help):
69
# '''Show help on a command or other topic.'''
70
# def run(self, *args, **kwargs):
71
# print "You have been overridden"
72
# bzrlib.commands.cmd_help.run(self, *args, **kwargs)
34
from bzrlib.symbol_versioning import one_three
35
from bzrlib.tests import (
40
from bzrlib.osutils import pathjoin, abspath, normpath
77
44
import bzrlib.commands
85
52
# TODO: Write a test for plugin decoration of commands.
87
class TestOneNamedPluginOnly(TestCaseInTempDir):
54
class TestLoadingPlugins(TestCaseInTempDir):
89
56
activeattributes = {}
91
58
def test_plugins_with_the_same_name_are_not_loaded(self):
59
# This test tests that having two plugins in different directories does
60
# not result in both being loaded when they have the same name. get a
61
# file name we can use which is also a valid attribute for accessing in
62
# activeattributes. - we cannot give import parameters.
64
self.failIf(tempattribute in self.activeattributes)
65
# set a place for the plugins to record their loading, and at the same
66
# time validate that the location the plugins should record to is
68
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
70
self.failUnless(tempattribute in self.activeattributes)
71
# create two plugin directories
74
# write a plugin that will record when its loaded in the
76
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
77
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
79
outfile = open(os.path.join('first', 'plugin.py'), 'w')
81
outfile.write(template % (tempattribute, 'first'))
86
outfile = open(os.path.join('second', 'plugin.py'), 'w')
88
outfile.write(template % (tempattribute, 'second'))
94
bzrlib.plugin.load_from_path(['first', 'second'])
95
self.assertEqual(['first'], self.activeattributes[tempattribute])
97
# remove the plugin 'plugin'
98
del self.activeattributes[tempattribute]
99
if 'bzrlib.plugins.plugin' in sys.modules:
100
del sys.modules['bzrlib.plugins.plugin']
101
if getattr(bzrlib.plugins, 'plugin', None):
102
del bzrlib.plugins.plugin
103
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
105
def test_plugins_from_different_dirs_can_demand_load(self):
92
106
# This test tests that having two plugins in different
93
# directories does not result in both being loaded.
94
# get a file name we can use which is also a valid attribute
107
# directories with different names allows them both to be loaded, when
108
# we do a direct import statement.
109
# Determine a file name we can use which is also a valid attribute
95
110
# for accessing in activeattributes. - we cannot give import parameters.
111
tempattribute = "different-dirs"
97
112
self.failIf(tempattribute in self.activeattributes)
98
113
# set a place for the plugins to record their loading, and at the same
99
114
# time validate that the location the plugins should record to is
100
115
# valid and correct.
101
bzrlib.tests.test_plugins.TestOneNamedPluginOnly.activeattributes \
116
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
102
117
[tempattribute] = []
103
118
self.failUnless(tempattribute in self.activeattributes)
104
119
# create two plugin directories
105
120
os.mkdir('first')
106
121
os.mkdir('second')
122
# write plugins that will record when they are loaded in the
123
# tempattribute list.
124
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
125
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
127
outfile = open(os.path.join('first', 'pluginone.py'), 'w')
129
outfile.write(template % (tempattribute, 'first'))
134
outfile = open(os.path.join('second', 'plugintwo.py'), 'w')
136
outfile.write(template % (tempattribute, 'second'))
141
oldpath = bzrlib.plugins.__path__
143
bzrlib.plugins.__path__ = ['first', 'second']
144
exec "import bzrlib.plugins.pluginone"
145
self.assertEqual(['first'], self.activeattributes[tempattribute])
146
exec "import bzrlib.plugins.plugintwo"
147
self.assertEqual(['first', 'second'],
148
self.activeattributes[tempattribute])
150
# remove the plugin 'plugin'
151
del self.activeattributes[tempattribute]
152
if getattr(bzrlib.plugins, 'pluginone', None):
153
del bzrlib.plugins.pluginone
154
if getattr(bzrlib.plugins, 'plugintwo', None):
155
del bzrlib.plugins.plugintwo
156
self.failIf(getattr(bzrlib.plugins, 'pluginone', None))
157
self.failIf(getattr(bzrlib.plugins, 'plugintwo', None))
159
def test_plugins_can_load_from_directory_with_trailing_slash(self):
160
# This test tests that a plugin can load from a directory when the
161
# directory in the path has a trailing slash.
162
# check the plugin is not loaded already
163
self.failIf(getattr(bzrlib.plugins, 'ts_plugin', None))
164
tempattribute = "trailing-slash"
165
self.failIf(tempattribute in self.activeattributes)
166
# set a place for the plugin to record its loading, and at the same
167
# time validate that the location the plugin should record to is
169
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
171
self.failUnless(tempattribute in self.activeattributes)
172
# create a directory for the plugin
173
os.mkdir('plugin_test')
107
174
# write a plugin that will record when its loaded in the
108
175
# tempattribute list.
109
template = ("from bzrlib.tests.test_plugins import TestOneNamedPluginOnly\n"
110
"TestOneNamedPluginOnly.activeattributes[%r].append('%s')\n")
111
print >> file(os.path.join('first', 'plugin.py'), 'w'), template % (tempattribute, 'first')
112
print >> file(os.path.join('second', 'plugin.py'), 'w'), template % (tempattribute, 'second')
114
bzrlib.plugin.load_from_dirs(['first', 'second'])
115
self.assertEqual(['first'], self.activeattributes[tempattribute])
176
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
177
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
179
outfile = open(os.path.join('plugin_test', 'ts_plugin.py'), 'w')
181
outfile.write(template % (tempattribute, 'plugin'))
187
bzrlib.plugin.load_from_path(['plugin_test'+os.sep])
188
self.assertEqual(['plugin'], self.activeattributes[tempattribute])
117
190
# remove the plugin 'plugin'
118
191
del self.activeattributes[tempattribute]
119
if getattr(bzrlib.plugins, 'plugin', None):
120
del bzrlib.plugins.plugin
121
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
124
class TestAllPlugins(TestCaseInTempDir):
126
def test_plugin_appears_in_all_plugins(self):
127
# This test tests a new plugin appears in bzrlib.plugin.all_plugins().
192
if getattr(bzrlib.plugins, 'ts_plugin', None):
193
del bzrlib.plugins.ts_plugin
194
self.failIf(getattr(bzrlib.plugins, 'ts_plugin', None))
196
def test_plugin_with_bad_name_does_not_load(self):
197
# Create badly-named plugin
198
file('bzr-bad plugin-name..py', 'w').close()
202
handler = logging.StreamHandler(stream)
203
log = logging.getLogger('bzr')
204
log.addHandler(handler)
206
bzrlib.plugin.load_from_dir('.')
208
# Stop capturing output
211
log.removeHandler(handler)
213
self.assertContainsRe(stream.getvalue(),
214
r"Unable to load 'bzr-bad plugin-name\.' in '\.' as a plugin "
215
"because the file path isn't a valid module name; try renaming "
216
"it to 'bad_plugin_name_'\.")
221
class TestPlugins(TestCaseInTempDir):
223
def setup_plugin(self, source=""):
224
# This test tests a new plugin appears in bzrlib.plugin.plugins().
128
225
# check the plugin is not loaded already
129
226
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
130
227
# write a plugin that _cannot_ fail to load.
131
print >> file('plugin.py', 'w'), ""
133
bzrlib.plugin.load_from_dirs(['.'])
134
self.failUnless('plugin' in bzrlib.plugin.all_plugins())
135
self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
136
self.assertEqual(bzrlib.plugin.all_plugins()['plugin'],
137
bzrlib.plugins.plugin)
139
# remove the plugin 'plugin'
140
if getattr(bzrlib.plugins, 'plugin', None):
141
del bzrlib.plugins.plugin
228
file('plugin.py', 'w').write(source + '\n')
229
self.addCleanup(self.teardown_plugin)
230
bzrlib.plugin.load_from_path(['.'])
232
def teardown_plugin(self):
233
# remove the plugin 'plugin'
234
if 'bzrlib.plugins.plugin' in sys.modules:
235
del sys.modules['bzrlib.plugins.plugin']
236
if getattr(bzrlib.plugins, 'plugin', None):
237
del bzrlib.plugins.plugin
142
238
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
240
def test_plugin_appears_in_plugins(self):
242
self.failUnless('plugin' in bzrlib.plugin.plugins())
243
self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
244
plugins = bzrlib.plugin.plugins()
245
plugin = plugins['plugin']
246
self.assertIsInstance(plugin, bzrlib.plugin.PlugIn)
247
self.assertEqual(bzrlib.plugins.plugin, plugin.module)
249
def test_trivial_plugin_get_path(self):
251
plugins = bzrlib.plugin.plugins()
252
plugin = plugins['plugin']
253
plugin_path = self.test_dir + '/plugin.py'
254
self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
256
def test_plugin_get_path_py_not_pyc(self):
257
self.setup_plugin() # after first import there will be plugin.pyc
258
self.teardown_plugin()
259
bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
260
plugins = bzrlib.plugin.plugins()
261
plugin = plugins['plugin']
262
plugin_path = self.test_dir + '/plugin.py'
263
self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
265
def test_plugin_get_path_pyc_only(self):
266
self.setup_plugin() # after first import there will be plugin.pyc
267
self.teardown_plugin()
268
os.unlink(self.test_dir + '/plugin.py')
269
bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
270
plugins = bzrlib.plugin.plugins()
271
plugin = plugins['plugin']
273
plugin_path = self.test_dir + '/plugin.pyc'
275
plugin_path = self.test_dir + '/plugin.pyo'
276
self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
278
def test_no_test_suite_gives_None_for_test_suite(self):
280
plugin = bzrlib.plugin.plugins()['plugin']
281
self.assertEqual(None, plugin.test_suite())
283
def test_test_suite_gives_test_suite_result(self):
284
source = """def test_suite(): return 'foo'"""
285
self.setup_plugin(source)
286
plugin = bzrlib.plugin.plugins()['plugin']
287
self.assertEqual('foo', plugin.test_suite())
289
def test_no_load_plugin_tests_gives_None_for_load_plugin_tests(self):
291
loader = TestUtil.TestLoader()
292
plugin = bzrlib.plugin.plugins()['plugin']
293
self.assertEqual(None, plugin.load_plugin_tests(loader))
295
def test_load_plugin_tests_gives_load_plugin_tests_result(self):
297
def load_tests(standard_tests, module, loader):
299
self.setup_plugin(source)
300
loader = TestUtil.TestLoader()
301
plugin = bzrlib.plugin.plugins()['plugin']
302
self.assertEqual('foo', plugin.load_plugin_tests(loader))
304
def test_no_version_info(self):
306
plugin = bzrlib.plugin.plugins()['plugin']
307
self.assertEqual(None, plugin.version_info())
309
def test_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, 3, 'dev', 4), plugin.version_info())
314
def test_short_version_info_gets_padded(self):
315
# the gtk plugin has version_info = (1,2,3) rather than the 5-tuple.
317
self.setup_plugin("version_info = (1, 2, 3)")
318
plugin = bzrlib.plugin.plugins()['plugin']
319
self.assertEqual((1, 2, 3, 'final', 0), plugin.version_info())
321
def test_no_version_info___version__(self):
323
plugin = bzrlib.plugin.plugins()['plugin']
324
self.assertEqual("unknown", plugin.__version__)
326
def test___version__with_version_info(self):
327
self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
328
plugin = bzrlib.plugin.plugins()['plugin']
329
self.assertEqual("1.2.3dev4", plugin.__version__)
331
def test_final__version__with_version_info(self):
332
self.setup_plugin("version_info = (1, 2, 3, 'final', 4)")
333
plugin = bzrlib.plugin.plugins()['plugin']
334
self.assertEqual("1.2.3", plugin.__version__)
145
337
class TestPluginHelp(TestCaseInTempDir):
147
339
def split_help_commands(self):
150
for line in self.capture('help commands').splitlines():
151
if line.startswith('bzr '):
152
current = line.split()[1]
342
for line in self.run_bzr('help commands')[0].splitlines():
343
if not line.startswith(' '):
344
current = line.split()[0]
153
345
help[current] = help.get(current, '') + line
187
bzrlib.plugin.load_from_dirs(['plugin_test'])
377
bzrlib.plugin.load_from_path(['plugin_test'])
188
378
bzrlib.commands.register_command( bzrlib.plugins.myplug.cmd_myplug)
189
help = self.capture('help myplug')
190
self.assertContainsRe(help, 'From plugin "myplug"')
379
help = self.run_bzr('help myplug')[0]
380
self.assertContainsRe(help, 'plugin "myplug"')
191
381
help = self.split_help_commands()['myplug']
192
self.assertContainsRe(help, 'From plugin "myplug"')
194
# remove the plugin 'plugin'
195
if getattr(bzrlib.plugins, 'plugin', None):
196
del bzrlib.plugins.plugin
382
self.assertContainsRe(help, '\[myplug\]')
385
if bzrlib.commands.plugin_cmds.get('myplug', None):
386
del bzrlib.commands.plugin_cmds['myplug']
387
# remove the plugin 'myplug'
388
if getattr(bzrlib.plugins, 'myplug', None):
389
delattr(bzrlib.plugins, 'myplug')
392
class TestPluginFromZip(TestCaseInTempDir):
394
def make_zipped_plugin(self, zip_name, filename):
395
z = zipfile.ZipFile(zip_name, 'w')
396
z.writestr(filename, PLUGIN_TEXT)
399
def check_plugin_load(self, zip_name, plugin_name):
400
self.assertFalse(plugin_name in dir(bzrlib.plugins),
401
'Plugin already loaded')
402
old_path = bzrlib.plugins.__path__
404
# this is normally done by load_plugins -> set_plugins_path
405
bzrlib.plugins.__path__ = [zip_name]
406
self.applyDeprecated(one_three,
407
bzrlib.plugin.load_from_zip, zip_name)
408
self.assertTrue(plugin_name in dir(bzrlib.plugins),
409
'Plugin is not loaded')
412
if getattr(bzrlib.plugins, plugin_name, None):
413
delattr(bzrlib.plugins, plugin_name)
414
del sys.modules['bzrlib.plugins.' + plugin_name]
415
bzrlib.plugins.__path__ = old_path
417
def test_load_module(self):
418
self.make_zipped_plugin('./test.zip', 'ziplug.py')
419
self.check_plugin_load('./test.zip', 'ziplug')
421
def test_load_package(self):
422
self.make_zipped_plugin('./test.zip', 'ziplug/__init__.py')
423
self.check_plugin_load('./test.zip', 'ziplug')
426
class TestSetPluginsPath(TestCase):
428
def test_set_plugins_path(self):
429
"""set_plugins_path should set the module __path__ correctly."""
430
old_path = bzrlib.plugins.__path__
432
bzrlib.plugins.__path__ = []
433
expected_path = bzrlib.plugin.set_plugins_path()
434
self.assertEqual(expected_path, bzrlib.plugins.__path__)
436
bzrlib.plugins.__path__ = old_path
438
def test_set_plugins_path_with_trailing_slashes(self):
439
"""set_plugins_path should set the module __path__ based on
440
BZR_PLUGIN_PATH after removing all trailing slashes."""
441
old_path = bzrlib.plugins.__path__
442
old_env = os.environ.get('BZR_PLUGIN_PATH')
444
bzrlib.plugins.__path__ = []
445
os.environ['BZR_PLUGIN_PATH'] = "first\\//\\" + os.pathsep + \
447
bzrlib.plugin.set_plugins_path()
448
# We expect our nominated paths to have all path-seps removed,
449
# and this is testing only that.
450
expected_path = ['first', 'second']
451
self.assertEqual(expected_path,
452
bzrlib.plugins.__path__[:len(expected_path)])
454
bzrlib.plugins.__path__ = old_path
455
if old_env is not None:
456
os.environ['BZR_PLUGIN_PATH'] = old_env
458
del os.environ['BZR_PLUGIN_PATH']
461
class TestHelpIndex(tests.TestCase):
462
"""Tests for the PluginsHelpIndex class."""
464
def test_default_constructable(self):
465
index = plugin.PluginsHelpIndex()
467
def test_get_topics_None(self):
468
"""Searching for None returns an empty list."""
469
index = plugin.PluginsHelpIndex()
470
self.assertEqual([], index.get_topics(None))
472
def test_get_topics_for_plugin(self):
473
"""Searching for plugin name gets its docstring."""
474
index = plugin.PluginsHelpIndex()
475
# make a new plugin here for this test, even if we're run with
477
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
478
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
479
sys.modules['bzrlib.plugins.demo_module'] = demo_module
481
topics = index.get_topics('demo_module')
482
self.assertEqual(1, len(topics))
483
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
484
self.assertEqual(demo_module, topics[0].module)
486
del sys.modules['bzrlib.plugins.demo_module']
488
def test_get_topics_no_topic(self):
489
"""Searching for something that is not a plugin returns []."""
490
# test this by using a name that cannot be a plugin - its not
491
# a valid python identifier.
492
index = plugin.PluginsHelpIndex()
493
self.assertEqual([], index.get_topics('nothing by this name'))
495
def test_prefix(self):
496
"""PluginsHelpIndex has a prefix of 'plugins/'."""
497
index = plugin.PluginsHelpIndex()
498
self.assertEqual('plugins/', index.prefix)
500
def test_get_plugin_topic_with_prefix(self):
501
"""Searching for plugins/demo_module returns help."""
502
index = plugin.PluginsHelpIndex()
503
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
504
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
505
sys.modules['bzrlib.plugins.demo_module'] = demo_module
507
topics = index.get_topics('plugins/demo_module')
508
self.assertEqual(1, len(topics))
509
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
510
self.assertEqual(demo_module, topics[0].module)
512
del sys.modules['bzrlib.plugins.demo_module']
515
class FakeModule(object):
516
"""A fake module to test with."""
518
def __init__(self, doc, name):
523
class TestModuleHelpTopic(tests.TestCase):
524
"""Tests for the ModuleHelpTopic class."""
526
def test_contruct(self):
527
"""Construction takes the module to document."""
528
mod = FakeModule('foo', 'foo')
529
topic = plugin.ModuleHelpTopic(mod)
530
self.assertEqual(mod, topic.module)
532
def test_get_help_text_None(self):
533
"""A ModuleHelpTopic returns the docstring for get_help_text."""
534
mod = FakeModule(None, 'demo')
535
topic = plugin.ModuleHelpTopic(mod)
536
self.assertEqual("Plugin 'demo' has no docstring.\n",
537
topic.get_help_text())
539
def test_get_help_text_no_carriage_return(self):
540
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
541
mod = FakeModule('one line of help', 'demo')
542
topic = plugin.ModuleHelpTopic(mod)
543
self.assertEqual("one line of help\n",
544
topic.get_help_text())
546
def test_get_help_text_carriage_return(self):
547
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
548
mod = FakeModule('two lines of help\nand more\n', 'demo')
549
topic = plugin.ModuleHelpTopic(mod)
550
self.assertEqual("two lines of help\nand more\n",
551
topic.get_help_text())
553
def test_get_help_text_with_additional_see_also(self):
554
mod = FakeModule('two lines of help\nand more', 'demo')
555
topic = plugin.ModuleHelpTopic(mod)
556
self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
557
topic.get_help_text(['foo', 'bar']))
559
def test_get_help_topic(self):
560
"""The help topic for a plugin is its module name."""
561
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
562
topic = plugin.ModuleHelpTopic(mod)
563
self.assertEqual('demo', topic.get_help_topic())
564
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
565
topic = plugin.ModuleHelpTopic(mod)
566
self.assertEqual('foo_bar', topic.get_help_topic())