~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_plugins.py

  • Committer: Ian Clatworthy
  • Date: 2009-01-19 02:24:15 UTC
  • mto: This revision was merged to the branch mainline in revision 3944.
  • Revision ID: ian.clatworthy@canonical.com-20090119022415-mo0mcfeiexfktgwt
apply jam's log --short fix (Ian Clatworthy)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2007 Canonical Ltd
 
2
#
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.
7
 
 
 
7
#
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.
12
 
 
 
12
#
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
16
16
 
17
 
 
18
17
"""Tests for plugins"""
19
18
 
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
22
21
# comments.
23
22
 
 
23
import logging
24
24
import os
 
25
from StringIO import StringIO
 
26
import sys
 
27
import zipfile
25
28
 
 
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
30
 
 
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)
38
 
#        f.close()
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
43
 
#
44
 
#        assert backtick('bzr commit -m test') == "I'm sorry dave, you can't do that\n"
45
 
#
46
 
#        shutil.rmtree('plugin_test')
47
 
#
48
 
 
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
53
 
 
54
 
 
55
 
#         assert backtick('bzr myplug') == 'Hello from my plugin\n'
56
 
#         assert backtick('bzr mplg') == 'Hello from my plugin\n'
57
 
 
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"
64
 
 
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)
70
 
 
71
 
#         """
 
32
import bzrlib.commands
 
33
import bzrlib.help
 
34
from bzrlib.symbol_versioning import one_three
 
35
from bzrlib.tests import (
 
36
    TestCase,
 
37
    TestCaseInTempDir,
 
38
    TestUtil,
 
39
    )
 
40
from bzrlib.osutils import pathjoin, abspath, normpath
 
41
 
72
42
 
73
43
PLUGIN_TEXT = """\
74
44
import bzrlib.commands
81
51
 
82
52
# TODO: Write a test for plugin decoration of commands.
83
53
 
84
 
class TestOneNamedPluginOnly(TestCaseInTempDir):
 
54
class TestLoadingPlugins(TestCaseInTempDir):
85
55
 
86
56
    activeattributes = {}
87
57
 
88
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.
 
63
        tempattribute = "0"
 
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
 
67
        # valid and correct.
 
68
        bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
 
69
            [tempattribute] = []
 
70
        self.failUnless(tempattribute in self.activeattributes)
 
71
        # create two plugin directories
 
72
        os.mkdir('first')
 
73
        os.mkdir('second')
 
74
        # write a plugin that will record when its loaded in the 
 
75
        # tempattribute list.
 
76
        template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
 
77
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
 
78
 
 
79
        outfile = open(os.path.join('first', 'plugin.py'), 'w')
 
80
        try:
 
81
            outfile.write(template % (tempattribute, 'first'))
 
82
            outfile.write('\n')
 
83
        finally:
 
84
            outfile.close()
 
85
 
 
86
        outfile = open(os.path.join('second', 'plugin.py'), 'w')
 
87
        try:
 
88
            outfile.write(template % (tempattribute, 'second'))
 
89
            outfile.write('\n')
 
90
        finally:
 
91
            outfile.close()
 
92
 
 
93
        try:
 
94
            bzrlib.plugin.load_from_path(['first', 'second'])
 
95
            self.assertEqual(['first'], self.activeattributes[tempattribute])
 
96
        finally:
 
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))
 
104
 
 
105
    def test_plugins_from_different_dirs_can_demand_load(self):
89
106
        # 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
 
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
92
110
        # for accessing in activeattributes. - we cannot give import parameters.
93
 
        tempattribute = "0"
 
111
        tempattribute = "different-dirs"
94
112
        self.failIf(tempattribute in self.activeattributes)
95
113
        # set a place for the plugins to record their loading, and at the same
96
114
        # time validate that the location the plugins should record to is
97
115
        # valid and correct.
98
 
        bzrlib.tests.test_plugins.TestOneNamedPluginOnly.activeattributes \
 
116
        bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
99
117
            [tempattribute] = []
100
118
        self.failUnless(tempattribute in self.activeattributes)
101
119
        # create two plugin directories
102
120
        os.mkdir('first')
103
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")
 
126
 
 
127
        outfile = open(os.path.join('first', 'pluginone.py'), 'w')
 
128
        try:
 
129
            outfile.write(template % (tempattribute, 'first'))
 
130
            outfile.write('\n')
 
131
        finally:
 
132
            outfile.close()
 
133
 
 
134
        outfile = open(os.path.join('second', 'plugintwo.py'), 'w')
 
135
        try:
 
136
            outfile.write(template % (tempattribute, 'second'))
 
137
            outfile.write('\n')
 
138
        finally:
 
139
            outfile.close()
 
140
 
 
141
        oldpath = bzrlib.plugins.__path__
 
142
        try:
 
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])
 
149
        finally:
 
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))
 
158
 
 
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
 
168
        # valid and correct.
 
169
        bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
 
170
            [tempattribute] = []
 
171
        self.failUnless(tempattribute in self.activeattributes)
 
172
        # create a directory for the plugin
 
173
        os.mkdir('plugin_test')
104
174
        # write a plugin that will record when its loaded in the 
105
175
        # 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')
110
 
        try:
111
 
            bzrlib.plugin.load_from_dirs(['first', 'second'])
112
 
            self.assertEqual(['first'], self.activeattributes[tempattribute])
 
176
        template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
 
177
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
 
178
 
 
179
        outfile = open(os.path.join('plugin_test', 'ts_plugin.py'), 'w')
 
180
        try:
 
181
            outfile.write(template % (tempattribute, 'plugin'))
 
182
            outfile.write('\n')
 
183
        finally:
 
184
            outfile.close()
 
185
 
 
186
        try:
 
187
            bzrlib.plugin.load_from_path(['plugin_test'+os.sep])
 
188
            self.assertEqual(['plugin'], self.activeattributes[tempattribute])
113
189
        finally:
114
190
            # remove the plugin 'plugin'
115
191
            del self.activeattributes[tempattribute]
116
 
            if getattr(bzrlib.plugins, 'plugin', None):
117
 
                del bzrlib.plugins.plugin
118
 
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
119
 
 
120
 
 
121
 
class TestAllPlugins(TestCaseInTempDir):
122
 
 
123
 
    def test_plugin_appears_in_all_plugins(self):
124
 
        # 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))
 
195
 
 
196
    def load_and_capture(self, name):
 
197
        """Load plugins from '.' capturing the output.
 
198
        
 
199
        :param name: The name of the plugin.
 
200
        :return: A string with the log from the plugin loading call.
 
201
        """
 
202
        # Capture output
 
203
        stream = StringIO()
 
204
        try:
 
205
            handler = logging.StreamHandler(stream)
 
206
            log = logging.getLogger('bzr')
 
207
            log.addHandler(handler)
 
208
            try:
 
209
                try:
 
210
                    bzrlib.plugin.load_from_path(['.'])
 
211
                finally:
 
212
                    if 'bzrlib.plugins.%s' % name in sys.modules:
 
213
                        del sys.modules['bzrlib.plugins.%s' % name]
 
214
                    if getattr(bzrlib.plugins, name, None):
 
215
                        delattr(bzrlib.plugins, name)
 
216
            finally:
 
217
                # Stop capturing output
 
218
                handler.flush()
 
219
                handler.close()
 
220
                log.removeHandler(handler)
 
221
            return stream.getvalue()
 
222
        finally:
 
223
            stream.close()
 
224
    
 
225
    def test_plugin_with_bad_api_version_reports(self):
 
226
        # This plugin asks for bzrlib api version 1.0.0, which is not supported
 
227
        # anymore.
 
228
        name = 'wants100.py'
 
229
        f = file(name, 'w')
 
230
        try:
 
231
            f.write("import bzrlib.api\n"
 
232
                "bzrlib.api.require_any_api(bzrlib, [(1, 0, 0)])\n")
 
233
        finally:
 
234
            f.close()
 
235
 
 
236
        log = self.load_and_capture(name)
 
237
        self.assertContainsRe(log,
 
238
            r"It requested API version")
 
239
 
 
240
    def test_plugin_with_bad_name_does_not_load(self):
 
241
        # The file name here invalid for a python module.
 
242
        name = 'bzr-bad plugin-name..py'
 
243
        file(name, 'w').close()
 
244
        log = self.load_and_capture(name)
 
245
        self.assertContainsRe(log,
 
246
            r"Unable to load 'bzr-bad plugin-name\.' in '\.' as a plugin "
 
247
            "because the file path isn't a valid module name; try renaming "
 
248
            "it to 'bad_plugin_name_'\.")
 
249
 
 
250
 
 
251
class TestPlugins(TestCaseInTempDir):
 
252
 
 
253
    def setup_plugin(self, source=""):
 
254
        # This test tests a new plugin appears in bzrlib.plugin.plugins().
125
255
        # check the plugin is not loaded already
126
256
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
127
257
        # write a plugin that _cannot_ fail to load.
128
 
        print >> file('plugin.py', 'w'), ""
129
 
        try:
130
 
            bzrlib.plugin.load_from_dirs(['.'])
131
 
            self.failUnless('plugin' in bzrlib.plugin.all_plugins())
132
 
            self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
133
 
            self.assertEqual(bzrlib.plugin.all_plugins()['plugin'],
134
 
                             bzrlib.plugins.plugin)
135
 
        finally:
136
 
            # remove the plugin 'plugin'
137
 
            if getattr(bzrlib.plugins, 'plugin', None):
138
 
                del bzrlib.plugins.plugin
 
258
        file('plugin.py', 'w').write(source + '\n')
 
259
        self.addCleanup(self.teardown_plugin)
 
260
        bzrlib.plugin.load_from_path(['.'])
 
261
    
 
262
    def teardown_plugin(self):
 
263
        # remove the plugin 'plugin'
 
264
        if 'bzrlib.plugins.plugin' in sys.modules:
 
265
            del sys.modules['bzrlib.plugins.plugin']
 
266
        if getattr(bzrlib.plugins, 'plugin', None):
 
267
            del bzrlib.plugins.plugin
139
268
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
 
269
 
 
270
    def test_plugin_appears_in_plugins(self):
 
271
        self.setup_plugin()
 
272
        self.failUnless('plugin' in bzrlib.plugin.plugins())
 
273
        self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
 
274
        plugins = bzrlib.plugin.plugins()
 
275
        plugin = plugins['plugin']
 
276
        self.assertIsInstance(plugin, bzrlib.plugin.PlugIn)
 
277
        self.assertEqual(bzrlib.plugins.plugin, plugin.module)
 
278
 
 
279
    def test_trivial_plugin_get_path(self):
 
280
        self.setup_plugin()
 
281
        plugins = bzrlib.plugin.plugins()
 
282
        plugin = plugins['plugin']
 
283
        plugin_path = self.test_dir + '/plugin.py'
 
284
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
 
285
 
 
286
    def test_plugin_get_path_py_not_pyc(self):
 
287
        self.setup_plugin()         # after first import there will be plugin.pyc
 
288
        self.teardown_plugin()
 
289
        bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
 
290
        plugins = bzrlib.plugin.plugins()
 
291
        plugin = plugins['plugin']
 
292
        plugin_path = self.test_dir + '/plugin.py'
 
293
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
 
294
 
 
295
    def test_plugin_get_path_pyc_only(self):
 
296
        self.setup_plugin()         # after first import there will be plugin.pyc
 
297
        self.teardown_plugin()
 
298
        os.unlink(self.test_dir + '/plugin.py')
 
299
        bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
 
300
        plugins = bzrlib.plugin.plugins()
 
301
        plugin = plugins['plugin']
 
302
        if __debug__:
 
303
            plugin_path = self.test_dir + '/plugin.pyc'
 
304
        else:
 
305
            plugin_path = self.test_dir + '/plugin.pyo'
 
306
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
 
307
 
 
308
    def test_no_test_suite_gives_None_for_test_suite(self):
 
309
        self.setup_plugin()
 
310
        plugin = bzrlib.plugin.plugins()['plugin']
 
311
        self.assertEqual(None, plugin.test_suite())
 
312
 
 
313
    def test_test_suite_gives_test_suite_result(self):
 
314
        source = """def test_suite(): return 'foo'"""
 
315
        self.setup_plugin(source)
 
316
        plugin = bzrlib.plugin.plugins()['plugin']
 
317
        self.assertEqual('foo', plugin.test_suite())
 
318
 
 
319
    def test_no_load_plugin_tests_gives_None_for_load_plugin_tests(self):
 
320
        self.setup_plugin()
 
321
        loader = TestUtil.TestLoader()
 
322
        plugin = bzrlib.plugin.plugins()['plugin']
 
323
        self.assertEqual(None, plugin.load_plugin_tests(loader))
 
324
 
 
325
    def test_load_plugin_tests_gives_load_plugin_tests_result(self):
 
326
        source = """
 
327
def load_tests(standard_tests, module, loader):
 
328
    return 'foo'"""
 
329
        self.setup_plugin(source)
 
330
        loader = TestUtil.TestLoader()
 
331
        plugin = bzrlib.plugin.plugins()['plugin']
 
332
        self.assertEqual('foo', plugin.load_plugin_tests(loader))
 
333
 
 
334
    def test_no_version_info(self):
 
335
        self.setup_plugin()
 
336
        plugin = bzrlib.plugin.plugins()['plugin']
 
337
        self.assertEqual(None, plugin.version_info())
 
338
 
 
339
    def test_with_version_info(self):
 
340
        self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
 
341
        plugin = bzrlib.plugin.plugins()['plugin']
 
342
        self.assertEqual((1, 2, 3, 'dev', 4), plugin.version_info())
 
343
 
 
344
    def test_short_version_info_gets_padded(self):
 
345
        # the gtk plugin has version_info = (1,2,3) rather than the 5-tuple.
 
346
        # so we adapt it
 
347
        self.setup_plugin("version_info = (1, 2, 3)")
 
348
        plugin = bzrlib.plugin.plugins()['plugin']
 
349
        self.assertEqual((1, 2, 3, 'final', 0), plugin.version_info())
 
350
 
 
351
    def test_no_version_info___version__(self):
 
352
        self.setup_plugin()
 
353
        plugin = bzrlib.plugin.plugins()['plugin']
 
354
        self.assertEqual("unknown", plugin.__version__)
 
355
 
 
356
    def test_str__version__with_version_info(self):
 
357
        self.setup_plugin("version_info = '1.2.3'")
 
358
        plugin = bzrlib.plugin.plugins()['plugin']
 
359
        self.assertEqual("1.2.3", plugin.__version__)
 
360
 
 
361
    def test_noniterable__version__with_version_info(self):
 
362
        self.setup_plugin("version_info = (1)")
 
363
        plugin = bzrlib.plugin.plugins()['plugin']
 
364
        self.assertEqual("1", plugin.__version__)
 
365
 
 
366
    def test_1__version__with_version_info(self):
 
367
        self.setup_plugin("version_info = (1,)")
 
368
        plugin = bzrlib.plugin.plugins()['plugin']
 
369
        self.assertEqual("1", plugin.__version__)
 
370
 
 
371
    def test_1_2__version__with_version_info(self):
 
372
        self.setup_plugin("version_info = (1, 2)")
 
373
        plugin = bzrlib.plugin.plugins()['plugin']
 
374
        self.assertEqual("1.2", plugin.__version__)
 
375
 
 
376
    def test_1_2_3__version__with_version_info(self):
 
377
        self.setup_plugin("version_info = (1, 2, 3)")
 
378
        plugin = bzrlib.plugin.plugins()['plugin']
 
379
        self.assertEqual("1.2.3", plugin.__version__)
 
380
 
 
381
    def test_candidate__version__with_version_info(self):
 
382
        self.setup_plugin("version_info = (1, 2, 3, 'candidate', 1)")
 
383
        plugin = bzrlib.plugin.plugins()['plugin']
 
384
        self.assertEqual("1.2.3rc1", plugin.__version__)
 
385
 
 
386
    def test_dev__version__with_version_info(self):
 
387
        self.setup_plugin("version_info = (1, 2, 3, 'dev', 0)")
 
388
        plugin = bzrlib.plugin.plugins()['plugin']
 
389
        self.assertEqual("1.2.3dev", plugin.__version__)
 
390
 
 
391
    def test_dev_fallback__version__with_version_info(self):
 
392
        self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
 
393
        plugin = bzrlib.plugin.plugins()['plugin']
 
394
        self.assertEqual("1.2.3.dev.4", plugin.__version__)
 
395
 
 
396
    def test_final__version__with_version_info(self):
 
397
        self.setup_plugin("version_info = (1, 2, 3, 'final', 0)")
 
398
        plugin = bzrlib.plugin.plugins()['plugin']
 
399
        self.assertEqual("1.2.3", plugin.__version__)
 
400
 
 
401
 
 
402
class TestPluginHelp(TestCaseInTempDir):
 
403
 
 
404
    def split_help_commands(self):
 
405
        help = {}
 
406
        current = None
 
407
        out, err = self.run_bzr('--no-plugins help commands')
 
408
        for line in out.splitlines():
 
409
            if not line.startswith(' '):
 
410
                current = line.split()[0]
 
411
            help[current] = help.get(current, '') + line
 
412
 
 
413
        return help
 
414
 
 
415
    def test_plugin_help_builtins_unaffected(self):
 
416
        # Check we don't get false positives
 
417
        help_commands = self.split_help_commands()
 
418
        for cmd_name in bzrlib.commands.builtin_command_names():
 
419
            if cmd_name in bzrlib.commands.plugin_command_names():
 
420
                continue
 
421
            try:
 
422
                help = bzrlib.commands.get_cmd_object(cmd_name).get_help_text()
 
423
            except NotImplementedError:
 
424
                # some commands have no help
 
425
                pass
 
426
            else:
 
427
                self.assertNotContainsRe(help, 'plugin "[^"]*"')
 
428
 
 
429
            if cmd_name in help_commands.keys():
 
430
                # some commands are hidden
 
431
                help = help_commands[cmd_name]
 
432
                self.assertNotContainsRe(help, 'plugin "[^"]*"')
 
433
 
 
434
    def test_plugin_help_shows_plugin(self):
 
435
        # Create a test plugin
 
436
        os.mkdir('plugin_test')
 
437
        f = open(pathjoin('plugin_test', 'myplug.py'), 'w')
 
438
        f.write(PLUGIN_TEXT)
 
439
        f.close()
 
440
 
 
441
        try:
 
442
            # Check its help
 
443
            bzrlib.plugin.load_from_path(['plugin_test'])
 
444
            bzrlib.commands.register_command( bzrlib.plugins.myplug.cmd_myplug)
 
445
            help = self.run_bzr('help myplug')[0]
 
446
            self.assertContainsRe(help, 'plugin "myplug"')
 
447
            help = self.split_help_commands()['myplug']
 
448
            self.assertContainsRe(help, '\[myplug\]')
 
449
        finally:
 
450
            # unregister command
 
451
            if 'myplug' in bzrlib.commands.plugin_cmds:
 
452
                bzrlib.commands.plugin_cmds.remove('myplug')
 
453
            # remove the plugin 'myplug'
 
454
            if getattr(bzrlib.plugins, 'myplug', None):
 
455
                delattr(bzrlib.plugins, 'myplug')
 
456
 
 
457
 
 
458
class TestPluginFromZip(TestCaseInTempDir):
 
459
 
 
460
    def make_zipped_plugin(self, zip_name, filename):
 
461
        z = zipfile.ZipFile(zip_name, 'w')
 
462
        z.writestr(filename, PLUGIN_TEXT)
 
463
        z.close()
 
464
 
 
465
    def check_plugin_load(self, zip_name, plugin_name):
 
466
        self.assertFalse(plugin_name in dir(bzrlib.plugins),
 
467
                         'Plugin already loaded')
 
468
        old_path = bzrlib.plugins.__path__
 
469
        try:
 
470
            # this is normally done by load_plugins -> set_plugins_path
 
471
            bzrlib.plugins.__path__ = [zip_name]
 
472
            self.applyDeprecated(one_three,
 
473
                bzrlib.plugin.load_from_zip, zip_name)
 
474
            self.assertTrue(plugin_name in dir(bzrlib.plugins),
 
475
                            'Plugin is not loaded')
 
476
        finally:
 
477
            # unregister plugin
 
478
            if getattr(bzrlib.plugins, plugin_name, None):
 
479
                delattr(bzrlib.plugins, plugin_name)
 
480
                del sys.modules['bzrlib.plugins.' + plugin_name]
 
481
            bzrlib.plugins.__path__ = old_path
 
482
 
 
483
    def test_load_module(self):
 
484
        self.make_zipped_plugin('./test.zip', 'ziplug.py')
 
485
        self.check_plugin_load('./test.zip', 'ziplug')
 
486
 
 
487
    def test_load_package(self):
 
488
        self.make_zipped_plugin('./test.zip', 'ziplug/__init__.py')
 
489
        self.check_plugin_load('./test.zip', 'ziplug')
 
490
 
 
491
 
 
492
class TestSetPluginsPath(TestCase):
 
493
    
 
494
    def test_set_plugins_path(self):
 
495
        """set_plugins_path should set the module __path__ correctly."""
 
496
        old_path = bzrlib.plugins.__path__
 
497
        try:
 
498
            bzrlib.plugins.__path__ = []
 
499
            expected_path = bzrlib.plugin.set_plugins_path()
 
500
            self.assertEqual(expected_path, bzrlib.plugins.__path__)
 
501
        finally:
 
502
            bzrlib.plugins.__path__ = old_path
 
503
 
 
504
    def test_set_plugins_path_with_trailing_slashes(self):
 
505
        """set_plugins_path should set the module __path__ based on
 
506
        BZR_PLUGIN_PATH after removing all trailing slashes."""
 
507
        old_path = bzrlib.plugins.__path__
 
508
        old_env = os.environ.get('BZR_PLUGIN_PATH')
 
509
        try:
 
510
            bzrlib.plugins.__path__ = []
 
511
            os.environ['BZR_PLUGIN_PATH'] = "first\\//\\" + os.pathsep + \
 
512
                "second/\\/\\/"
 
513
            bzrlib.plugin.set_plugins_path()
 
514
            # We expect our nominated paths to have all path-seps removed,
 
515
            # and this is testing only that.
 
516
            expected_path = ['first', 'second']
 
517
            self.assertEqual(expected_path,
 
518
                bzrlib.plugins.__path__[:len(expected_path)])
 
519
        finally:
 
520
            bzrlib.plugins.__path__ = old_path
 
521
            if old_env is not None:
 
522
                os.environ['BZR_PLUGIN_PATH'] = old_env
 
523
            else:
 
524
                del os.environ['BZR_PLUGIN_PATH']
 
525
 
 
526
 
 
527
class TestHelpIndex(tests.TestCase):
 
528
    """Tests for the PluginsHelpIndex class."""
 
529
 
 
530
    def test_default_constructable(self):
 
531
        index = plugin.PluginsHelpIndex()
 
532
 
 
533
    def test_get_topics_None(self):
 
534
        """Searching for None returns an empty list."""
 
535
        index = plugin.PluginsHelpIndex()
 
536
        self.assertEqual([], index.get_topics(None))
 
537
 
 
538
    def test_get_topics_for_plugin(self):
 
539
        """Searching for plugin name gets its docstring."""
 
540
        index = plugin.PluginsHelpIndex()
 
541
        # make a new plugin here for this test, even if we're run with
 
542
        # --no-plugins
 
543
        self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
 
544
        demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
 
545
        sys.modules['bzrlib.plugins.demo_module'] = demo_module
 
546
        try:
 
547
            topics = index.get_topics('demo_module')
 
548
            self.assertEqual(1, len(topics))
 
549
            self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
 
550
            self.assertEqual(demo_module, topics[0].module)
 
551
        finally:
 
552
            del sys.modules['bzrlib.plugins.demo_module']
 
553
 
 
554
    def test_get_topics_no_topic(self):
 
555
        """Searching for something that is not a plugin returns []."""
 
556
        # test this by using a name that cannot be a plugin - its not
 
557
        # a valid python identifier.
 
558
        index = plugin.PluginsHelpIndex()
 
559
        self.assertEqual([], index.get_topics('nothing by this name'))
 
560
 
 
561
    def test_prefix(self):
 
562
        """PluginsHelpIndex has a prefix of 'plugins/'."""
 
563
        index = plugin.PluginsHelpIndex()
 
564
        self.assertEqual('plugins/', index.prefix)
 
565
 
 
566
    def test_get_plugin_topic_with_prefix(self):
 
567
        """Searching for plugins/demo_module returns help."""
 
568
        index = plugin.PluginsHelpIndex()
 
569
        self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
 
570
        demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
 
571
        sys.modules['bzrlib.plugins.demo_module'] = demo_module
 
572
        try:
 
573
            topics = index.get_topics('plugins/demo_module')
 
574
            self.assertEqual(1, len(topics))
 
575
            self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
 
576
            self.assertEqual(demo_module, topics[0].module)
 
577
        finally:
 
578
            del sys.modules['bzrlib.plugins.demo_module']
 
579
 
 
580
 
 
581
class FakeModule(object):
 
582
    """A fake module to test with."""
 
583
 
 
584
    def __init__(self, doc, name):
 
585
        self.__doc__ = doc
 
586
        self.__name__ = name
 
587
 
 
588
 
 
589
class TestModuleHelpTopic(tests.TestCase):
 
590
    """Tests for the ModuleHelpTopic class."""
 
591
 
 
592
    def test_contruct(self):
 
593
        """Construction takes the module to document."""
 
594
        mod = FakeModule('foo', 'foo')
 
595
        topic = plugin.ModuleHelpTopic(mod)
 
596
        self.assertEqual(mod, topic.module)
 
597
 
 
598
    def test_get_help_text_None(self):
 
599
        """A ModuleHelpTopic returns the docstring for get_help_text."""
 
600
        mod = FakeModule(None, 'demo')
 
601
        topic = plugin.ModuleHelpTopic(mod)
 
602
        self.assertEqual("Plugin 'demo' has no docstring.\n",
 
603
            topic.get_help_text())
 
604
 
 
605
    def test_get_help_text_no_carriage_return(self):
 
606
        """ModuleHelpTopic.get_help_text adds a \n if needed."""
 
607
        mod = FakeModule('one line of help', 'demo')
 
608
        topic = plugin.ModuleHelpTopic(mod)
 
609
        self.assertEqual("one line of help\n",
 
610
            topic.get_help_text())
 
611
 
 
612
    def test_get_help_text_carriage_return(self):
 
613
        """ModuleHelpTopic.get_help_text adds a \n if needed."""
 
614
        mod = FakeModule('two lines of help\nand more\n', 'demo')
 
615
        topic = plugin.ModuleHelpTopic(mod)
 
616
        self.assertEqual("two lines of help\nand more\n",
 
617
            topic.get_help_text())
 
618
 
 
619
    def test_get_help_text_with_additional_see_also(self):
 
620
        mod = FakeModule('two lines of help\nand more', 'demo')
 
621
        topic = plugin.ModuleHelpTopic(mod)
 
622
        self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
 
623
            topic.get_help_text(['foo', 'bar']))
 
624
 
 
625
    def test_get_help_topic(self):
 
626
        """The help topic for a plugin is its module name."""
 
627
        mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
 
628
        topic = plugin.ModuleHelpTopic(mod)
 
629
        self.assertEqual('demo', topic.get_help_topic())
 
630
        mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
 
631
        topic = plugin.ModuleHelpTopic(mod)
 
632
        self.assertEqual('foo_bar', topic.get_help_topic())
 
633
 
 
634
 
 
635
def clear_plugins(test_case):
 
636
    # Save the attributes that we're about to monkey-patch.
 
637
    old_plugins_path = bzrlib.plugins.__path__
 
638
    old_loaded = plugin._loaded
 
639
    old_load_from_path = plugin.load_from_path
 
640
    # Change bzrlib.plugin to think no plugins have been loaded yet.
 
641
    bzrlib.plugins.__path__ = []
 
642
    plugin._loaded = False
 
643
    # Monkey-patch load_from_path to stop it from actually loading anything.
 
644
    def load_from_path(dirs):
 
645
        pass
 
646
    plugin.load_from_path = load_from_path
 
647
    def restore_plugins():
 
648
        bzrlib.plugins.__path__ = old_plugins_path
 
649
        plugin._loaded = old_loaded
 
650
        plugin.load_from_path = old_load_from_path
 
651
    test_case.addCleanup(restore_plugins)
 
652
 
 
653
 
 
654
class TestPluginPaths(tests.TestCase):
 
655
 
 
656
    def test_set_plugins_path_with_args(self):
 
657
        clear_plugins(self)
 
658
        plugin.set_plugins_path(['a', 'b'])
 
659
        self.assertEqual(['a', 'b'], bzrlib.plugins.__path__)
 
660
 
 
661
    def test_set_plugins_path_defaults(self):
 
662
        clear_plugins(self)
 
663
        plugin.set_plugins_path()
 
664
        self.assertEqual(plugin.get_standard_plugins_path(),
 
665
                         bzrlib.plugins.__path__)
 
666
 
 
667
    def test_get_standard_plugins_path(self):
 
668
        path = plugin.get_standard_plugins_path()
 
669
        self.assertEqual(plugin.get_default_plugin_path(), path[0])
 
670
        for directory in path:
 
671
            self.assertNotContainsRe(r'\\/$', directory)
 
672
        try:
 
673
            from distutils.sysconfig import get_python_lib
 
674
        except ImportError:
 
675
            pass
 
676
        else:
 
677
            if sys.platform != 'win32':
 
678
                python_lib = get_python_lib()
 
679
                for directory in path:
 
680
                    if directory.startswith(python_lib):
 
681
                        break
 
682
                else:
 
683
                    self.fail('No path to global plugins')
 
684
 
 
685
    def test_get_standard_plugins_path_env(self):
 
686
        os.environ['BZR_PLUGIN_PATH'] = 'foo/'
 
687
        self.assertEqual('foo', plugin.get_standard_plugins_path()[0])
 
688
 
 
689
 
 
690
class TestLoadPlugins(tests.TestCaseInTempDir):
 
691
 
 
692
    def test_load_plugins(self):
 
693
        clear_plugins(self)
 
694
        plugin.load_plugins(['.'])
 
695
        self.assertEqual(bzrlib.plugins.__path__, ['.'])
 
696
        # subsequent loads are no-ops
 
697
        plugin.load_plugins(['foo'])
 
698
        self.assertEqual(bzrlib.plugins.__path__, ['.'])
 
699
 
 
700
    def test_load_plugins_default(self):
 
701
        clear_plugins(self)
 
702
        plugin.load_plugins()
 
703
        path = plugin.get_standard_plugins_path()
 
704
        self.assertEqual(path, bzrlib.plugins.__path__)