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"""
28
from bzrlib.selftest import InTempDir
31
def PluginTest(InTempDir):
32
"""Create an external plugin and test loading."""
36
orig_help = self.backtick('bzr help commands') # No plugins yet
37
os.mkdir('plugin_test')
38
f = open(os.path.join('plugin_test', 'myplug.py'), 'wt')
42
newhelp = backtick('bzr help commands')
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')
55
"""import bzrlib, bzrlib.commands
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.tests import TestCase, TestCaseInTempDir
34
from bzrlib.osutils import pathjoin, abspath
38
import bzrlib.commands
56
39
class cmd_myplug(bzrlib.commands.Command):
57
40
'''Just a simple test plugin.'''
60
43
print 'Hello from my plugin'
64
os.environ['BZRPLUGINPATH'] = os.path.abspath('plugin_test')
65
help = backtick('bzr help commands')
66
assert help.find('myplug') != -1
67
assert help.find('Just a simple test plugin.') != -1
70
assert backtick('bzr myplug') == 'Hello from my plugin\n'
71
assert backtick('bzr mplg') == 'Hello from my plugin\n'
73
f = open(os.path.join('plugin_test', 'override.py'), 'wb')
74
f.write("""import bzrlib, bzrlib.commands
75
class cmd_commit(bzrlib.commands.cmd_commit):
76
'''Commit changes into a new revision.'''
77
def run(self, *args, **kwargs):
78
print "I'm sorry dave, you can't do that"
80
class cmd_help(bzrlib.commands.cmd_help):
81
'''Show help on a command or other topic.'''
82
def run(self, *args, **kwargs):
83
print "You have been overridden"
84
bzrlib.commands.cmd_help.run(self, *args, **kwargs)
46
# TODO: Write a test for plugin decoration of commands.
48
class TestLoadingPlugins(TestCaseInTempDir):
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")
72
print >> file(os.path.join('first', 'plugin.py'), 'w'), template % (tempattribute, 'first')
73
print >> file(os.path.join('second', 'plugin.py'), 'w'), template % (tempattribute, 'second')
75
bzrlib.plugin.load_from_path(['first', 'second'])
76
self.assertEqual(['first'], self.activeattributes[tempattribute])
78
# remove the plugin 'plugin'
79
del self.activeattributes[tempattribute]
80
if getattr(bzrlib.plugins, 'plugin', None):
81
del bzrlib.plugins.plugin
82
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
84
def test_plugins_from_different_dirs_can_demand_load(self):
85
# This test tests that having two plugins in different
86
# directories with different names allows them both to be loaded, when
87
# we do a direct import statement.
88
# Determine a file name we can use which is also a valid attribute
89
# for accessing in activeattributes. - we cannot give import parameters.
90
tempattribute = "different-dirs"
91
self.failIf(tempattribute in self.activeattributes)
92
# set a place for the plugins to record their loading, and at the same
93
# time validate that the location the plugins should record to is
95
bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
97
self.failUnless(tempattribute in self.activeattributes)
98
# create two plugin directories
101
# write plugins that will record when they are loaded in the
102
# tempattribute list.
103
template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
104
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
105
print >> file(os.path.join('first', 'pluginone.py'), 'w'), template % (tempattribute, 'first')
106
print >> file(os.path.join('second', 'plugintwo.py'), 'w'), template % (tempattribute, 'second')
107
oldpath = bzrlib.plugins.__path__
109
bzrlib.plugins.__path__ = ['first', 'second']
110
exec "import bzrlib.plugins.pluginone"
111
self.assertEqual(['first'], self.activeattributes[tempattribute])
112
exec "import bzrlib.plugins.plugintwo"
113
self.assertEqual(['first', 'second'],
114
self.activeattributes[tempattribute])
116
# remove the plugin 'plugin'
117
del self.activeattributes[tempattribute]
118
if getattr(bzrlib.plugins, 'plugin', None):
119
del bzrlib.plugins.plugin
120
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
123
class TestAllPlugins(TestCaseInTempDir):
125
def test_plugin_appears_in_all_plugins(self):
126
# This test tests a new plugin appears in bzrlib.plugin.all_plugins().
127
# check the plugin is not loaded already
128
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
129
# write a plugin that _cannot_ fail to load.
130
print >> file('plugin.py', 'w'), ""
132
bzrlib.plugin.load_from_path(['.'])
133
self.failUnless('plugin' in bzrlib.plugin.all_plugins())
134
self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
135
self.assertEqual(bzrlib.plugin.all_plugins()['plugin'],
136
bzrlib.plugins.plugin)
138
# remove the plugin 'plugin'
139
if 'bzrlib.plugins.plugin' in sys.modules:
140
del sys.modules['bzrlib.plugins.plugin']
141
if getattr(bzrlib.plugins, 'plugin', None):
142
del bzrlib.plugins.plugin
143
self.failIf(getattr(bzrlib.plugins, 'plugin', None))
146
class TestPluginHelp(TestCaseInTempDir):
148
def split_help_commands(self):
151
for line in self.run_bzr('help commands')[0].splitlines():
152
if not line.startswith(' '):
153
current = line.split()[0]
154
help[current] = help.get(current, '') + line
158
def test_plugin_help_builtins_unaffected(self):
159
# Check we don't get false positives
160
help_commands = self.split_help_commands()
161
for cmd_name in bzrlib.commands.builtin_command_names():
162
if cmd_name in bzrlib.commands.plugin_command_names():
165
help = bzrlib.commands.get_cmd_object(cmd_name).get_help_text()
166
except NotImplementedError:
167
# some commands have no help
170
self.assertNotContainsRe(help, 'From plugin "[^"]*"')
172
if cmd_name in help_commands.keys():
173
# some commands are hidden
174
help = help_commands[cmd_name]
175
self.assertNotContainsRe(help, 'From plugin "[^"]*"')
177
def test_plugin_help_shows_plugin(self):
178
# Create a test plugin
179
os.mkdir('plugin_test')
180
f = open(pathjoin('plugin_test', 'myplug.py'), 'w')
186
bzrlib.plugin.load_from_path(['plugin_test'])
187
bzrlib.commands.register_command( bzrlib.plugins.myplug.cmd_myplug)
188
help = self.run_bzr('help myplug')[0]
189
self.assertContainsRe(help, 'From plugin "myplug"')
190
help = self.split_help_commands()['myplug']
191
self.assertContainsRe(help, '\[myplug\]')
194
if bzrlib.commands.plugin_cmds.get('myplug', None):
195
del bzrlib.commands.plugin_cmds['myplug']
196
# remove the plugin 'myplug'
197
if getattr(bzrlib.plugins, 'myplug', None):
198
delattr(bzrlib.plugins, 'myplug')
201
class TestPluginFromZip(TestCaseInTempDir):
203
def make_zipped_plugin(self, zip_name, filename):
204
z = zipfile.ZipFile(zip_name, 'w')
205
z.writestr(filename, PLUGIN_TEXT)
208
def check_plugin_load(self, zip_name, plugin_name):
209
self.assertFalse(plugin_name in dir(bzrlib.plugins),
210
'Plugin already loaded')
212
bzrlib.plugin.load_from_zip(zip_name)
213
self.assertTrue(plugin_name in dir(bzrlib.plugins),
214
'Plugin is not loaded')
217
if getattr(bzrlib.plugins, plugin_name, None):
218
delattr(bzrlib.plugins, plugin_name)
220
def test_load_module(self):
221
self.make_zipped_plugin('./test.zip', 'ziplug.py')
222
self.check_plugin_load('./test.zip', 'ziplug')
224
def test_load_package(self):
225
self.make_zipped_plugin('./test.zip', 'ziplug/__init__.py')
226
self.check_plugin_load('./test.zip', 'ziplug')
229
class TestSetPluginsPath(TestCase):
231
def test_set_plugins_path(self):
232
"""set_plugins_path should set the module __path__ correctly."""
233
old_path = bzrlib.plugins.__path__
235
bzrlib.plugins.__path__ = []
236
expected_path = bzrlib.plugin.set_plugins_path()
237
self.assertEqual(expected_path, bzrlib.plugins.__path__)
239
bzrlib.plugins.__path__ = old_path
242
class TestHelpIndex(tests.TestCase):
243
"""Tests for the PluginsHelpIndex class."""
245
def test_default_constructable(self):
246
index = plugin.PluginsHelpIndex()
248
def test_get_topics_None(self):
249
"""Searching for None returns an empty list."""
250
index = plugin.PluginsHelpIndex()
251
self.assertEqual([], index.get_topics(None))
253
def test_get_topics_for_plugin(self):
254
"""Searching for plugin name gets its docstring."""
255
index = plugin.PluginsHelpIndex()
256
# make a new plugin here for this test, even if we're run with
258
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
259
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
260
sys.modules['bzrlib.plugins.demo_module'] = demo_module
262
topics = index.get_topics('demo_module')
263
self.assertEqual(1, len(topics))
264
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
265
self.assertEqual(demo_module, topics[0].module)
267
del sys.modules['bzrlib.plugins.demo_module']
269
def test_get_topics_no_topic(self):
270
"""Searching for something that is not a plugin returns []."""
271
# test this by using a name that cannot be a plugin - its not
272
# a valid python identifier.
273
index = plugin.PluginsHelpIndex()
274
self.assertEqual([], index.get_topics('nothing by this name'))
276
def test_prefix(self):
277
"""PluginsHelpIndex has a prefix of 'plugins/'."""
278
index = plugin.PluginsHelpIndex()
279
self.assertEqual('plugins/', index.prefix)
281
def test_get_plugin_topic_with_prefix(self):
282
"""Searching for plugins/demo_module returns help."""
283
index = plugin.PluginsHelpIndex()
284
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
285
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
286
sys.modules['bzrlib.plugins.demo_module'] = demo_module
288
topics = index.get_topics('plugins/demo_module')
289
self.assertEqual(1, len(topics))
290
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
291
self.assertEqual(demo_module, topics[0].module)
293
del sys.modules['bzrlib.plugins.demo_module']
296
class FakeModule(object):
297
"""A fake module to test with."""
299
def __init__(self, doc, name):
304
class TestModuleHelpTopic(tests.TestCase):
305
"""Tests for the ModuleHelpTopic class."""
307
def test_contruct(self):
308
"""Construction takes the module to document."""
309
mod = FakeModule('foo', 'foo')
310
topic = plugin.ModuleHelpTopic(mod)
311
self.assertEqual(mod, topic.module)
313
def test_get_help_text_None(self):
314
"""A ModuleHelpTopic returns the docstring for get_help_text."""
315
mod = FakeModule(None, 'demo')
316
topic = plugin.ModuleHelpTopic(mod)
317
self.assertEqual("Plugin 'demo' has no docstring.\n",
318
topic.get_help_text())
320
def test_get_help_text_no_carriage_return(self):
321
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
322
mod = FakeModule('one line of help', 'demo')
323
topic = plugin.ModuleHelpTopic(mod)
324
self.assertEqual("one line of help\n",
325
topic.get_help_text())
327
def test_get_help_text_carriage_return(self):
328
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
329
mod = FakeModule('two lines of help\nand more\n', 'demo')
330
topic = plugin.ModuleHelpTopic(mod)
331
self.assertEqual("two lines of help\nand more\n",
332
topic.get_help_text())
334
def test_get_help_text_with_additional_see_also(self):
335
mod = FakeModule('two lines of help\nand more', 'demo')
336
topic = plugin.ModuleHelpTopic(mod)
337
self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
338
topic.get_help_text(['foo', 'bar']))
340
def test_get_help_topic(self):
341
"""The help topic for a plugin is its module name."""
342
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
343
topic = plugin.ModuleHelpTopic(mod)
344
self.assertEqual('demo', topic.get_help_topic())
345
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
346
topic = plugin.ModuleHelpTopic(mod)
347
self.assertEqual('foo_bar', topic.get_help_topic())