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')
211
old_path = bzrlib.plugins.__path__
213
# this is normally done by load_plugins -> set_plugins_path
214
bzrlib.plugins.__path__ = [zip_name]
215
bzrlib.plugin.load_from_zip(zip_name)
216
self.assertTrue(plugin_name in dir(bzrlib.plugins),
217
'Plugin is not loaded')
220
if getattr(bzrlib.plugins, plugin_name, None):
221
delattr(bzrlib.plugins, plugin_name)
222
del sys.modules['bzrlib.plugins.' + plugin_name]
223
bzrlib.plugins.__path__ = old_path
225
def test_load_module(self):
226
self.make_zipped_plugin('./test.zip', 'ziplug.py')
227
self.check_plugin_load('./test.zip', 'ziplug')
229
def test_load_package(self):
230
self.make_zipped_plugin('./test.zip', 'ziplug/__init__.py')
231
self.check_plugin_load('./test.zip', 'ziplug')
234
class TestSetPluginsPath(TestCase):
236
def test_set_plugins_path(self):
237
"""set_plugins_path should set the module __path__ correctly."""
238
old_path = bzrlib.plugins.__path__
240
bzrlib.plugins.__path__ = []
241
expected_path = bzrlib.plugin.set_plugins_path()
242
self.assertEqual(expected_path, bzrlib.plugins.__path__)
244
bzrlib.plugins.__path__ = old_path
247
class TestHelpIndex(tests.TestCase):
248
"""Tests for the PluginsHelpIndex class."""
250
def test_default_constructable(self):
251
index = plugin.PluginsHelpIndex()
253
def test_get_topics_None(self):
254
"""Searching for None returns an empty list."""
255
index = plugin.PluginsHelpIndex()
256
self.assertEqual([], index.get_topics(None))
258
def test_get_topics_for_plugin(self):
259
"""Searching for plugin name gets its docstring."""
260
index = plugin.PluginsHelpIndex()
261
# make a new plugin here for this test, even if we're run with
263
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
264
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
265
sys.modules['bzrlib.plugins.demo_module'] = demo_module
267
topics = index.get_topics('demo_module')
268
self.assertEqual(1, len(topics))
269
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
270
self.assertEqual(demo_module, topics[0].module)
272
del sys.modules['bzrlib.plugins.demo_module']
274
def test_get_topics_no_topic(self):
275
"""Searching for something that is not a plugin returns []."""
276
# test this by using a name that cannot be a plugin - its not
277
# a valid python identifier.
278
index = plugin.PluginsHelpIndex()
279
self.assertEqual([], index.get_topics('nothing by this name'))
281
def test_prefix(self):
282
"""PluginsHelpIndex has a prefix of 'plugins/'."""
283
index = plugin.PluginsHelpIndex()
284
self.assertEqual('plugins/', index.prefix)
286
def test_get_plugin_topic_with_prefix(self):
287
"""Searching for plugins/demo_module returns help."""
288
index = plugin.PluginsHelpIndex()
289
self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
290
demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
291
sys.modules['bzrlib.plugins.demo_module'] = demo_module
293
topics = index.get_topics('plugins/demo_module')
294
self.assertEqual(1, len(topics))
295
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
296
self.assertEqual(demo_module, topics[0].module)
298
del sys.modules['bzrlib.plugins.demo_module']
301
class FakeModule(object):
302
"""A fake module to test with."""
304
def __init__(self, doc, name):
309
class TestModuleHelpTopic(tests.TestCase):
310
"""Tests for the ModuleHelpTopic class."""
312
def test_contruct(self):
313
"""Construction takes the module to document."""
314
mod = FakeModule('foo', 'foo')
315
topic = plugin.ModuleHelpTopic(mod)
316
self.assertEqual(mod, topic.module)
318
def test_get_help_text_None(self):
319
"""A ModuleHelpTopic returns the docstring for get_help_text."""
320
mod = FakeModule(None, 'demo')
321
topic = plugin.ModuleHelpTopic(mod)
322
self.assertEqual("Plugin 'demo' has no docstring.\n",
323
topic.get_help_text())
325
def test_get_help_text_no_carriage_return(self):
326
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
327
mod = FakeModule('one line of help', 'demo')
328
topic = plugin.ModuleHelpTopic(mod)
329
self.assertEqual("one line of help\n",
330
topic.get_help_text())
332
def test_get_help_text_carriage_return(self):
333
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
334
mod = FakeModule('two lines of help\nand more\n', 'demo')
335
topic = plugin.ModuleHelpTopic(mod)
336
self.assertEqual("two lines of help\nand more\n",
337
topic.get_help_text())
339
def test_get_help_text_with_additional_see_also(self):
340
mod = FakeModule('two lines of help\nand more', 'demo')
341
topic = plugin.ModuleHelpTopic(mod)
342
self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
343
topic.get_help_text(['foo', 'bar']))
345
def test_get_help_topic(self):
346
"""The help topic for a plugin is its module name."""
347
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
348
topic = plugin.ModuleHelpTopic(mod)
349
self.assertEqual('demo', topic.get_help_topic())
350
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
351
topic = plugin.ModuleHelpTopic(mod)
352
self.assertEqual('foo_bar', topic.get_help_topic())