1
# Copyright (C) 2005 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Tests for plugins"""
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
39
class cmd_myplug(bzrlib.commands.Command):
40
'''Just a simple test plugin.'''
43
print 'Hello from my plugin'
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.capture('help commands').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.capture('help myplug')
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_launchpad(self):
254
"""Searching for 'launchpad' returns the launchpad plugin docstring."""
255
index = plugin.PluginsHelpIndex()
256
self.assertFalse(sys.modules.has_key('bzrlib.plugins.get_topics'))
257
demo_module = FakeModule('', 'bzrlib.plugins.get_topics')
258
sys.modules['bzrlib.plugins.get_topics'] = demo_module
260
topics = index.get_topics('get_topics')
261
self.assertEqual(1, len(topics))
262
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
263
self.assertEqual(demo_module, topics[0].module)
265
del sys.modules['bzrlib.plugins.get_topics']
267
def test_get_topics_no_topic(self):
268
"""Searching for something that is not a plugin returns []."""
269
# test this by using a name that cannot be a plugin - its not
270
# a valid python identifier.
271
index = plugin.PluginsHelpIndex()
272
self.assertEqual([], index.get_topics('nothing by this name'))
274
def test_prefix(self):
275
"""PluginsHelpIndex has a prefix of 'plugins/'."""
276
index = plugin.PluginsHelpIndex()
277
self.assertEqual('plugins/', index.prefix)
279
def test_get_topic_with_prefix(self):
280
"""Searching for plugins/launchpad returns launchpad module help."""
281
index = plugin.PluginsHelpIndex()
282
self.assertFalse(sys.modules.has_key('bzrlib.plugins.get_topics'))
283
demo_module = FakeModule('', 'bzrlib.plugins.get_topics')
284
sys.modules['bzrlib.plugins.get_topics'] = demo_module
286
topics = index.get_topics('plugins/get_topics')
287
self.assertEqual(1, len(topics))
288
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
289
self.assertEqual(demo_module, topics[0].module)
291
del sys.modules['bzrlib.plugins.get_topics']
294
class FakeModule(object):
295
"""A fake module to test with."""
297
def __init__(self, doc, name):
302
class TestModuleHelpTopic(tests.TestCase):
303
"""Tests for the ModuleHelpTopic class."""
305
def test_contruct(self):
306
"""Construction takes the module to document."""
307
mod = FakeModule('foo', 'foo')
308
topic = plugin.ModuleHelpTopic(mod)
309
self.assertEqual(mod, topic.module)
311
def test_get_help_text_None(self):
312
"""A ModuleHelpTopic returns the docstring for get_help_text."""
313
mod = FakeModule(None, 'demo')
314
topic = plugin.ModuleHelpTopic(mod)
315
self.assertEqual("Plugin 'demo' has no docstring.\n",
316
topic.get_help_text())
318
def test_get_help_text_no_carriage_return(self):
319
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
320
mod = FakeModule('one line of help', 'demo')
321
topic = plugin.ModuleHelpTopic(mod)
322
self.assertEqual("one line of help\n",
323
topic.get_help_text())
325
def test_get_help_text_carriage_return(self):
326
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
327
mod = FakeModule('two lines of help\nand more\n', 'demo')
328
topic = plugin.ModuleHelpTopic(mod)
329
self.assertEqual("two lines of help\nand more\n",
330
topic.get_help_text())
332
def test_get_help_text_with_additional_see_also(self):
333
mod = FakeModule('two lines of help\nand more', 'demo')
334
topic = plugin.ModuleHelpTopic(mod)
335
self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
336
topic.get_help_text(['foo', 'bar']))
338
def test_get_help_topic(self):
339
"""The help topic for a plugin is its module name."""
340
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
341
topic = plugin.ModuleHelpTopic(mod)
342
self.assertEqual('demo', topic.get_help_topic())
343
mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
344
topic = plugin.ModuleHelpTopic(mod)
345
self.assertEqual('foo_bar', topic.get_help_topic())