~bzr-pqm/bzr/bzr.dev

2475.1.1 by Martin Pool
Rename test_plugin tests and the example module used there.
1
# Copyright (C) 2005, 2007 Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
750 by Martin Pool
- stubbed-out tests for python plugins
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.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
750 by Martin Pool
- stubbed-out tests for python plugins
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.
2052.3.1 by John Arbash Meinel
Add tests to cleanup the copyright of all source files
12
#
750 by Martin Pool
- stubbed-out tests for python plugins
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
750 by Martin Pool
- stubbed-out tests for python plugins
16
17
"""Tests for plugins"""
18
1185.16.83 by mbp at sourcefrog
- notes on testability of 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
21
# comments.
22
2967.4.5 by Daniel Watkins
Added test for badly-named plugins.
23
import logging
1185.16.83 by mbp at sourcefrog
- notes on testability of plugins
24
import os
1733.2.5 by Michael Ellerman
Show which plugin (if any) provides a command.
25
from StringIO import StringIO
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
26
import sys
2215.4.1 by Alexander Belchenko
Bugfix #68124: Allow plugins import from zip archives.
27
import zipfile
750 by Martin Pool
- stubbed-out tests for python plugins
28
2432.1.25 by Robert Collins
Return plugin module docstrings for 'bzr help plugin'.
29
from bzrlib import plugin, tests
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
30
import bzrlib.plugin
31
import bzrlib.plugins
1733.2.5 by Michael Ellerman
Show which plugin (if any) provides a command.
32
import bzrlib.commands
33
import bzrlib.help
3302.8.11 by Vincent Ladeuil
Simplify plugin.load_tests.
34
from bzrlib.tests import (
35
    TestCase,
36
    TestCaseInTempDir,
37
    TestUtil,
38
    )
2804.4.1 by Alexander Belchenko
some win32-specific fixes for selftest
39
from bzrlib.osutils import pathjoin, abspath, normpath
1141 by Martin Pool
- rename FunctionalTest to TestCaseInTempDir
40
1185.16.83 by mbp at sourcefrog
- notes on testability of plugins
41
1185.16.84 by mbp at sourcefrog
- fix indents
42
PLUGIN_TEXT = """\
43
import bzrlib.commands
44
class cmd_myplug(bzrlib.commands.Command):
45
    '''Just a simple test plugin.'''
46
    aliases = ['mplg']
47
    def run(self):
48
        print 'Hello from my plugin'
49
"""
1492 by Robert Collins
Support decoration of commands.
50
51
# TODO: Write a test for plugin decoration of commands.
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
52
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
53
class TestLoadingPlugins(TestCaseInTempDir):
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
54
55
    activeattributes = {}
56
57
    def test_plugins_with_the_same_name_are_not_loaded(self):
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
58
        # This test tests that having two plugins in different directories does
59
        # not result in both being loaded when they have the same name.  get a
60
        # file name we can use which is also a valid attribute for accessing in
61
        # activeattributes. - we cannot give import parameters.
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
62
        tempattribute = "0"
63
        self.failIf(tempattribute in self.activeattributes)
64
        # set a place for the plugins to record their loading, and at the same
65
        # time validate that the location the plugins should record to is
66
        # valid and correct.
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
67
        bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
68
            [tempattribute] = []
69
        self.failUnless(tempattribute in self.activeattributes)
70
        # create two plugin directories
71
        os.mkdir('first')
72
        os.mkdir('second')
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
73
        # write a plugin that will record when its loaded in the
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
74
        # tempattribute list.
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
75
        template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
76
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
77
78
        outfile = open(os.path.join('first', 'plugin.py'), 'w')
79
        try:
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
80
            outfile.write(template % (tempattribute, 'first'))
81
            outfile.write('\n')
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
82
        finally:
83
            outfile.close()
84
85
        outfile = open(os.path.join('second', 'plugin.py'), 'w')
86
        try:
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
87
            outfile.write(template % (tempattribute, 'second'))
88
            outfile.write('\n')
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
89
        finally:
90
            outfile.close()
91
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
92
        try:
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
93
            bzrlib.plugin.load_from_path(['first', 'second'])
94
            self.assertEqual(['first'], self.activeattributes[tempattribute])
95
        finally:
96
            # remove the plugin 'plugin'
97
            del self.activeattributes[tempattribute]
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
98
            if 'bzrlib.plugins.plugin' in sys.modules:
99
                del sys.modules['bzrlib.plugins.plugin']
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
100
            if getattr(bzrlib.plugins, 'plugin', None):
101
                del bzrlib.plugins.plugin
102
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
103
104
    def test_plugins_from_different_dirs_can_demand_load(self):
105
        # This test tests that having two plugins in different
106
        # directories with different names allows them both to be loaded, when
107
        # we do a direct import statement.
108
        # Determine a file name we can use which is also a valid attribute
109
        # for accessing in activeattributes. - we cannot give import parameters.
110
        tempattribute = "different-dirs"
111
        self.failIf(tempattribute in self.activeattributes)
112
        # set a place for the plugins to record their loading, and at the same
113
        # time validate that the location the plugins should record to is
114
        # valid and correct.
115
        bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
116
            [tempattribute] = []
117
        self.failUnless(tempattribute in self.activeattributes)
118
        # create two plugin directories
119
        os.mkdir('first')
120
        os.mkdir('second')
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
121
        # write plugins that will record when they are loaded in the
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
122
        # tempattribute list.
123
        template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
124
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
125
126
        outfile = open(os.path.join('first', 'pluginone.py'), 'w')
127
        try:
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
128
            outfile.write(template % (tempattribute, 'first'))
129
            outfile.write('\n')
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
130
        finally:
131
            outfile.close()
132
133
        outfile = open(os.path.join('second', 'plugintwo.py'), 'w')
134
        try:
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
135
            outfile.write(template % (tempattribute, 'second'))
136
            outfile.write('\n')
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
137
        finally:
138
            outfile.close()
139
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
140
        oldpath = bzrlib.plugins.__path__
141
        try:
142
            bzrlib.plugins.__path__ = ['first', 'second']
143
            exec "import bzrlib.plugins.pluginone"
144
            self.assertEqual(['first'], self.activeattributes[tempattribute])
145
            exec "import bzrlib.plugins.plugintwo"
146
            self.assertEqual(['first', 'second'],
147
                self.activeattributes[tempattribute])
1515 by Robert Collins
* Plugins with the same name in different directories in the bzr plugin
148
        finally:
149
            # remove the plugin 'plugin'
150
            del self.activeattributes[tempattribute]
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
151
            if getattr(bzrlib.plugins, 'pluginone', None):
152
                del bzrlib.plugins.pluginone
153
            if getattr(bzrlib.plugins, 'plugintwo', None):
154
                del bzrlib.plugins.plugintwo
155
        self.failIf(getattr(bzrlib.plugins, 'pluginone', None))
156
        self.failIf(getattr(bzrlib.plugins, 'plugintwo', None))
1516 by Robert Collins
* bzrlib.plugin.all_plugins has been changed from an attribute to a
157
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
158
    def test_plugins_can_load_from_directory_with_trailing_slash(self):
159
        # This test tests that a plugin can load from a directory when the
160
        # directory in the path has a trailing slash.
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
161
        # check the plugin is not loaded already
162
        self.failIf(getattr(bzrlib.plugins, 'ts_plugin', None))
163
        tempattribute = "trailing-slash"
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
164
        self.failIf(tempattribute in self.activeattributes)
2652.2.3 by Blake Winton
Understand the code and comments of the test, instead of just cargo-culting them.
165
        # set a place for the plugin to record its loading, and at the same
166
        # time validate that the location the plugin should record to is
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
167
        # valid and correct.
168
        bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
169
            [tempattribute] = []
170
        self.failUnless(tempattribute in self.activeattributes)
2652.2.3 by Blake Winton
Understand the code and comments of the test, instead of just cargo-culting them.
171
        # create a directory for the plugin
172
        os.mkdir('plugin_test')
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
173
        # write a plugin that will record when its loaded in the
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
174
        # tempattribute list.
175
        template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
176
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
177
178
        outfile = open(os.path.join('plugin_test', 'ts_plugin.py'), 'w')
179
        try:
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
180
            outfile.write(template % (tempattribute, 'plugin'))
2911.6.4 by Blake Winton
Fix test failures
181
            outfile.write('\n')
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
182
        finally:
183
            outfile.close()
184
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
185
        try:
2652.2.3 by Blake Winton
Understand the code and comments of the test, instead of just cargo-culting them.
186
            bzrlib.plugin.load_from_path(['plugin_test'+os.sep])
187
            self.assertEqual(['plugin'], self.activeattributes[tempattribute])
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
188
        finally:
189
            # remove the plugin 'plugin'
190
            del self.activeattributes[tempattribute]
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
191
            if getattr(bzrlib.plugins, 'ts_plugin', None):
192
                del bzrlib.plugins.ts_plugin
193
        self.failIf(getattr(bzrlib.plugins, 'ts_plugin', None))
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
194
3766.3.2 by Robert Collins
Fix reporting of incompatible api plugin load errors, fixing bug 279451.
195
    def load_and_capture(self, name):
196
        """Load plugins from '.' capturing the output.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
197
3766.3.2 by Robert Collins
Fix reporting of incompatible api plugin load errors, fixing bug 279451.
198
        :param name: The name of the plugin.
199
        :return: A string with the log from the plugin loading call.
200
        """
2967.4.5 by Daniel Watkins
Added test for badly-named plugins.
201
        # Capture output
202
        stream = StringIO()
3766.3.2 by Robert Collins
Fix reporting of incompatible api plugin load errors, fixing bug 279451.
203
        try:
204
            handler = logging.StreamHandler(stream)
205
            log = logging.getLogger('bzr')
206
            log.addHandler(handler)
207
            try:
208
                try:
209
                    bzrlib.plugin.load_from_path(['.'])
210
                finally:
211
                    if 'bzrlib.plugins.%s' % name in sys.modules:
212
                        del sys.modules['bzrlib.plugins.%s' % name]
213
                    if getattr(bzrlib.plugins, name, None):
214
                        delattr(bzrlib.plugins, name)
215
            finally:
216
                # Stop capturing output
217
                handler.flush()
218
                handler.close()
219
                log.removeHandler(handler)
220
            return stream.getvalue()
221
        finally:
222
            stream.close()
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
223
3766.3.2 by Robert Collins
Fix reporting of incompatible api plugin load errors, fixing bug 279451.
224
    def test_plugin_with_bad_api_version_reports(self):
225
        # This plugin asks for bzrlib api version 1.0.0, which is not supported
226
        # anymore.
227
        name = 'wants100.py'
228
        f = file(name, 'w')
229
        try:
230
            f.write("import bzrlib.api\n"
231
                "bzrlib.api.require_any_api(bzrlib, [(1, 0, 0)])\n")
232
        finally:
233
            f.close()
234
235
        log = self.load_and_capture(name)
236
        self.assertContainsRe(log,
237
            r"It requested API version")
238
239
    def test_plugin_with_bad_name_does_not_load(self):
240
        # The file name here invalid for a python module.
241
        name = 'bzr-bad plugin-name..py'
242
        file(name, 'w').close()
243
        log = self.load_and_capture(name)
244
        self.assertContainsRe(log,
3290.1.1 by James Westby
Strip "bzr_" from the start of the suggested plugin name.
245
            r"Unable to load 'bzr-bad plugin-name\.' in '\.' as a plugin "
246
            "because the file path isn't a valid module name; try renaming "
247
            "it to 'bad_plugin_name_'\.")
2967.4.5 by Daniel Watkins
Added test for badly-named plugins.
248
1516 by Robert Collins
* bzrlib.plugin.all_plugins has been changed from an attribute to a
249
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
250
class TestPlugins(TestCaseInTempDir):
251
252
    def setup_plugin(self, source=""):
253
        # This test tests a new plugin appears in bzrlib.plugin.plugins().
254
        # check the plugin is not loaded already
255
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
256
        # write a plugin that _cannot_ fail to load.
2911.6.1 by Blake Winton
Change 'print >> f,'s to 'f.write('s.
257
        file('plugin.py', 'w').write(source + '\n')
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
258
        self.addCleanup(self.teardown_plugin)
259
        bzrlib.plugin.load_from_path(['.'])
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
260
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
261
    def teardown_plugin(self):
262
        # remove the plugin 'plugin'
263
        if 'bzrlib.plugins.plugin' in sys.modules:
264
            del sys.modules['bzrlib.plugins.plugin']
265
        if getattr(bzrlib.plugins, 'plugin', None):
266
            del bzrlib.plugins.plugin
267
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
268
269
    def test_plugin_appears_in_plugins(self):
270
        self.setup_plugin()
271
        self.failUnless('plugin' in bzrlib.plugin.plugins())
272
        self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
273
        plugins = bzrlib.plugin.plugins()
274
        plugin = plugins['plugin']
275
        self.assertIsInstance(plugin, bzrlib.plugin.PlugIn)
276
        self.assertEqual(bzrlib.plugins.plugin, plugin.module)
277
278
    def test_trivial_plugin_get_path(self):
279
        self.setup_plugin()
280
        plugins = bzrlib.plugin.plugins()
281
        plugin = plugins['plugin']
282
        plugin_path = self.test_dir + '/plugin.py'
2823.1.10 by Vincent Ladeuil
merge bzr.dev
283
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
284
3193.2.1 by Alexander Belchenko
show path to plugin module as *.py instead of *.pyc if python source available
285
    def test_plugin_get_path_py_not_pyc(self):
286
        self.setup_plugin()         # after first import there will be plugin.pyc
287
        self.teardown_plugin()
288
        bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
289
        plugins = bzrlib.plugin.plugins()
290
        plugin = plugins['plugin']
291
        plugin_path = self.test_dir + '/plugin.py'
292
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
293
294
    def test_plugin_get_path_pyc_only(self):
295
        self.setup_plugin()         # after first import there will be plugin.pyc
296
        self.teardown_plugin()
297
        os.unlink(self.test_dir + '/plugin.py')
298
        bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
299
        plugins = bzrlib.plugin.plugins()
300
        plugin = plugins['plugin']
301
        if __debug__:
302
            plugin_path = self.test_dir + '/plugin.pyc'
303
        else:
304
            plugin_path = self.test_dir + '/plugin.pyo'
305
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
306
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
307
    def test_no_test_suite_gives_None_for_test_suite(self):
308
        self.setup_plugin()
309
        plugin = bzrlib.plugin.plugins()['plugin']
310
        self.assertEqual(None, plugin.test_suite())
311
312
    def test_test_suite_gives_test_suite_result(self):
313
        source = """def test_suite(): return 'foo'"""
314
        self.setup_plugin(source)
315
        plugin = bzrlib.plugin.plugins()['plugin']
316
        self.assertEqual('foo', plugin.test_suite())
317
3302.8.21 by Vincent Ladeuil
Fixed as per Robert's review.
318
    def test_no_load_plugin_tests_gives_None_for_load_plugin_tests(self):
3302.8.10 by Vincent Ladeuil
Prepare bzrlib.plugin to use the new test loader.
319
        self.setup_plugin()
3302.8.11 by Vincent Ladeuil
Simplify plugin.load_tests.
320
        loader = TestUtil.TestLoader()
3302.8.10 by Vincent Ladeuil
Prepare bzrlib.plugin to use the new test loader.
321
        plugin = bzrlib.plugin.plugins()['plugin']
3302.8.21 by Vincent Ladeuil
Fixed as per Robert's review.
322
        self.assertEqual(None, plugin.load_plugin_tests(loader))
3302.8.10 by Vincent Ladeuil
Prepare bzrlib.plugin to use the new test loader.
323
3302.8.21 by Vincent Ladeuil
Fixed as per Robert's review.
324
    def test_load_plugin_tests_gives_load_plugin_tests_result(self):
3302.8.10 by Vincent Ladeuil
Prepare bzrlib.plugin to use the new test loader.
325
        source = """
326
def load_tests(standard_tests, module, loader):
327
    return 'foo'"""
328
        self.setup_plugin(source)
3302.8.11 by Vincent Ladeuil
Simplify plugin.load_tests.
329
        loader = TestUtil.TestLoader()
3302.8.10 by Vincent Ladeuil
Prepare bzrlib.plugin to use the new test loader.
330
        plugin = bzrlib.plugin.plugins()['plugin']
3302.8.21 by Vincent Ladeuil
Fixed as per Robert's review.
331
        self.assertEqual('foo', plugin.load_plugin_tests(loader))
3302.8.10 by Vincent Ladeuil
Prepare bzrlib.plugin to use the new test loader.
332
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
333
    def test_no_version_info(self):
334
        self.setup_plugin()
335
        plugin = bzrlib.plugin.plugins()['plugin']
336
        self.assertEqual(None, plugin.version_info())
337
338
    def test_with_version_info(self):
339
        self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
340
        plugin = bzrlib.plugin.plugins()['plugin']
341
        self.assertEqual((1, 2, 3, 'dev', 4), plugin.version_info())
342
343
    def test_short_version_info_gets_padded(self):
344
        # the gtk plugin has version_info = (1,2,3) rather than the 5-tuple.
345
        # so we adapt it
346
        self.setup_plugin("version_info = (1, 2, 3)")
347
        plugin = bzrlib.plugin.plugins()['plugin']
348
        self.assertEqual((1, 2, 3, 'final', 0), plugin.version_info())
349
350
    def test_no_version_info___version__(self):
351
        self.setup_plugin()
352
        plugin = bzrlib.plugin.plugins()['plugin']
353
        self.assertEqual("unknown", plugin.__version__)
354
3777.6.7 by Marius Kruger
* Can now also handle non-iteratable and string plugin versions.
355
    def test_str__version__with_version_info(self):
356
        self.setup_plugin("version_info = '1.2.3'")
357
        plugin = bzrlib.plugin.plugins()['plugin']
358
        self.assertEqual("1.2.3", plugin.__version__)
359
360
    def test_noniterable__version__with_version_info(self):
361
        self.setup_plugin("version_info = (1)")
362
        plugin = bzrlib.plugin.plugins()['plugin']
363
        self.assertEqual("1", plugin.__version__)
364
365
    def test_1__version__with_version_info(self):
366
        self.setup_plugin("version_info = (1,)")
367
        plugin = bzrlib.plugin.plugins()['plugin']
368
        self.assertEqual("1", plugin.__version__)
369
370
    def test_1_2__version__with_version_info(self):
3777.6.5 by Marius Kruger
add 2 more tests for plugin version numbers
371
        self.setup_plugin("version_info = (1, 2)")
372
        plugin = bzrlib.plugin.plugins()['plugin']
373
        self.assertEqual("1.2", plugin.__version__)
374
3777.6.7 by Marius Kruger
* Can now also handle non-iteratable and string plugin versions.
375
    def test_1_2_3__version__with_version_info(self):
3777.6.5 by Marius Kruger
add 2 more tests for plugin version numbers
376
        self.setup_plugin("version_info = (1, 2, 3)")
377
        plugin = bzrlib.plugin.plugins()['plugin']
378
        self.assertEqual("1.2.3", plugin.__version__)
379
380
    def test_candidate__version__with_version_info(self):
3777.6.4 by Marius Kruger
fix tests
381
        self.setup_plugin("version_info = (1, 2, 3, 'candidate', 1)")
382
        plugin = bzrlib.plugin.plugins()['plugin']
383
        self.assertEqual("1.2.3rc1", plugin.__version__)
384
385
    def test_dev__version__with_version_info(self):
386
        self.setup_plugin("version_info = (1, 2, 3, 'dev', 0)")
387
        plugin = bzrlib.plugin.plugins()['plugin']
388
        self.assertEqual("1.2.3dev", plugin.__version__)
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
389
3777.6.7 by Marius Kruger
* Can now also handle non-iteratable and string plugin versions.
390
    def test_dev_fallback__version__with_version_info(self):
391
        self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
392
        plugin = bzrlib.plugin.plugins()['plugin']
4634.50.6 by John Arbash Meinel
Handle a plugin fallback versioning issue.
393
        self.assertEqual("1.2.3dev4", plugin.__version__)
3777.6.7 by Marius Kruger
* Can now also handle non-iteratable and string plugin versions.
394
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
395
    def test_final__version__with_version_info(self):
3777.6.4 by Marius Kruger
fix tests
396
        self.setup_plugin("version_info = (1, 2, 3, 'final', 0)")
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
397
        plugin = bzrlib.plugin.plugins()['plugin']
398
        self.assertEqual("1.2.3", plugin.__version__)
399
4634.50.6 by John Arbash Meinel
Handle a plugin fallback versioning issue.
400
    def test_final_fallback__version__with_version_info(self):
401
        self.setup_plugin("version_info = (1, 2, 3, 'final', 2)")
402
        plugin = bzrlib.plugin.plugins()['plugin']
403
        self.assertEqual("1.2.3.final.2", plugin.__version__)
404
2762.2.1 by Robert Collins
* ``bzr plugins`` now lists the version number for each plugin in square
405
1733.2.5 by Michael Ellerman
Show which plugin (if any) provides a command.
406
class TestPluginHelp(TestCaseInTempDir):
407
408
    def split_help_commands(self):
409
        help = {}
410
        current = None
3908.1.1 by Andrew Bennetts
Try harder to avoid loading plugins during the test suite.
411
        out, err = self.run_bzr('--no-plugins help commands')
412
        for line in out.splitlines():
2034.1.2 by Aaron Bentley
Fix testcase
413
            if not line.startswith(' '):
414
                current = line.split()[0]
1733.2.5 by Michael Ellerman
Show which plugin (if any) provides a command.
415
            help[current] = help.get(current, '') + line
416
417
        return help
418
419
    def test_plugin_help_builtins_unaffected(self):
420
        # Check we don't get false positives
421
        help_commands = self.split_help_commands()
422
        for cmd_name in bzrlib.commands.builtin_command_names():
423
            if cmd_name in bzrlib.commands.plugin_command_names():
424
                continue
425
            try:
2432.1.12 by Robert Collins
Relocate command help onto Command.
426
                help = bzrlib.commands.get_cmd_object(cmd_name).get_help_text()
1733.2.5 by Michael Ellerman
Show which plugin (if any) provides a command.
427
            except NotImplementedError:
428
                # some commands have no help
429
                pass
430
            else:
2666.1.1 by Ian Clatworthy
Bazaar User Reference generated from online help
431
                self.assertNotContainsRe(help, 'plugin "[^"]*"')
1733.2.5 by Michael Ellerman
Show which plugin (if any) provides a command.
432
2432.1.12 by Robert Collins
Relocate command help onto Command.
433
            if cmd_name in help_commands.keys():
1733.2.5 by Michael Ellerman
Show which plugin (if any) provides a command.
434
                # some commands are hidden
435
                help = help_commands[cmd_name]
2666.1.1 by Ian Clatworthy
Bazaar User Reference generated from online help
436
                self.assertNotContainsRe(help, 'plugin "[^"]*"')
1733.2.5 by Michael Ellerman
Show which plugin (if any) provides a command.
437
438
    def test_plugin_help_shows_plugin(self):
439
        # Create a test plugin
440
        os.mkdir('plugin_test')
441
        f = open(pathjoin('plugin_test', 'myplug.py'), 'w')
442
        f.write(PLUGIN_TEXT)
443
        f.close()
444
445
        try:
446
            # Check its help
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
447
            bzrlib.plugin.load_from_path(['plugin_test'])
1733.2.5 by Michael Ellerman
Show which plugin (if any) provides a command.
448
            bzrlib.commands.register_command( bzrlib.plugins.myplug.cmd_myplug)
2530.3.4 by Martin Pool
Deprecate run_bzr_captured in favour of just run_bzr
449
            help = self.run_bzr('help myplug')[0]
2666.1.1 by Ian Clatworthy
Bazaar User Reference generated from online help
450
            self.assertContainsRe(help, 'plugin "myplug"')
1733.2.5 by Michael Ellerman
Show which plugin (if any) provides a command.
451
            help = self.split_help_commands()['myplug']
2034.1.4 by Aaron Bentley
Change angle brackets to square brackets
452
            self.assertContainsRe(help, '\[myplug\]')
1733.2.5 by Michael Ellerman
Show which plugin (if any) provides a command.
453
        finally:
2204.3.2 by Alexander Belchenko
cherrypicking: test_plugin_help_shows_plugin: fix cleanup after test
454
            # unregister command
3785.1.1 by Aaron Bentley
Switch from dict to Registry for plugin_cmds
455
            if 'myplug' in bzrlib.commands.plugin_cmds:
456
                bzrlib.commands.plugin_cmds.remove('myplug')
2204.3.2 by Alexander Belchenko
cherrypicking: test_plugin_help_shows_plugin: fix cleanup after test
457
            # remove the plugin 'myplug'
458
            if getattr(bzrlib.plugins, 'myplug', None):
459
                delattr(bzrlib.plugins, 'myplug')
2215.4.1 by Alexander Belchenko
Bugfix #68124: Allow plugins import from zip archives.
460
461
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
462
class TestSetPluginsPath(TestCase):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
463
2256.2.2 by Robert Collins
Allow 'import bzrlib.plugins.NAME' to work when the plugin NAME has not
464
    def test_set_plugins_path(self):
465
        """set_plugins_path should set the module __path__ correctly."""
466
        old_path = bzrlib.plugins.__path__
467
        try:
468
            bzrlib.plugins.__path__ = []
469
            expected_path = bzrlib.plugin.set_plugins_path()
470
            self.assertEqual(expected_path, bzrlib.plugins.__path__)
471
        finally:
472
            bzrlib.plugins.__path__ = old_path
2432.1.25 by Robert Collins
Return plugin module docstrings for 'bzr help plugin'.
473
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
474
    def test_set_plugins_path_with_trailing_slashes(self):
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
475
        """set_plugins_path should set the module __path__ based on
3616.2.11 by Mark Hammond
3rd go at test_set_plugins_path_with_trailing_slashes in a Windows
476
        BZR_PLUGIN_PATH after removing all trailing slashes."""
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
477
        old_path = bzrlib.plugins.__path__
478
        old_env = os.environ.get('BZR_PLUGIN_PATH')
479
        try:
480
            bzrlib.plugins.__path__ = []
2652.2.7 by Blake Winton
fix lines which were wider than 79 chars. Also handle files a little more safely.
481
            os.environ['BZR_PLUGIN_PATH'] = "first\\//\\" + os.pathsep + \
482
                "second/\\/\\/"
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
483
            bzrlib.plugin.set_plugins_path()
3616.2.11 by Mark Hammond
3rd go at test_set_plugins_path_with_trailing_slashes in a Windows
484
            # We expect our nominated paths to have all path-seps removed,
485
            # and this is testing only that.
486
            expected_path = ['first', 'second']
3232.1.3 by Alexander Belchenko
fixing test_set_plugins_path_with_trailing_slashes.
487
            self.assertEqual(expected_path,
488
                bzrlib.plugins.__path__[:len(expected_path)])
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
489
        finally:
490
            bzrlib.plugins.__path__ = old_path
3376.2.11 by Martin Pool
Compare to None using is/is not not ==
491
            if old_env is not None:
2652.2.1 by Blake Winton
Add a test for BZR_PLUGIN_PATH, and code and another test to allow BZR_PLUGIN_PATH to contain trailing slashes.
492
                os.environ['BZR_PLUGIN_PATH'] = old_env
493
            else:
494
                del os.environ['BZR_PLUGIN_PATH']
2432.1.25 by Robert Collins
Return plugin module docstrings for 'bzr help plugin'.
495
3232.1.3 by Alexander Belchenko
fixing test_set_plugins_path_with_trailing_slashes.
496
2432.1.25 by Robert Collins
Return plugin module docstrings for 'bzr help plugin'.
497
class TestHelpIndex(tests.TestCase):
498
    """Tests for the PluginsHelpIndex class."""
499
500
    def test_default_constructable(self):
501
        index = plugin.PluginsHelpIndex()
502
503
    def test_get_topics_None(self):
504
        """Searching for None returns an empty list."""
505
        index = plugin.PluginsHelpIndex()
506
        self.assertEqual([], index.get_topics(None))
507
2475.1.1 by Martin Pool
Rename test_plugin tests and the example module used there.
508
    def test_get_topics_for_plugin(self):
509
        """Searching for plugin name gets its docstring."""
2432.1.25 by Robert Collins
Return plugin module docstrings for 'bzr help plugin'.
510
        index = plugin.PluginsHelpIndex()
2475.1.1 by Martin Pool
Rename test_plugin tests and the example module used there.
511
        # make a new plugin here for this test, even if we're run with
512
        # --no-plugins
513
        self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
514
        demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
515
        sys.modules['bzrlib.plugins.demo_module'] = demo_module
2457.1.1 by Robert Collins
(robertc) Fix bzr --no-plugins selftest which was broken by the help indices patch. (Robert Collins, Martin Pool)
516
        try:
2475.1.1 by Martin Pool
Rename test_plugin tests and the example module used there.
517
            topics = index.get_topics('demo_module')
2457.1.1 by Robert Collins
(robertc) Fix bzr --no-plugins selftest which was broken by the help indices patch. (Robert Collins, Martin Pool)
518
            self.assertEqual(1, len(topics))
519
            self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
520
            self.assertEqual(demo_module, topics[0].module)
521
        finally:
2475.1.1 by Martin Pool
Rename test_plugin tests and the example module used there.
522
            del sys.modules['bzrlib.plugins.demo_module']
2432.1.25 by Robert Collins
Return plugin module docstrings for 'bzr help plugin'.
523
524
    def test_get_topics_no_topic(self):
525
        """Searching for something that is not a plugin returns []."""
526
        # test this by using a name that cannot be a plugin - its not
527
        # a valid python identifier.
528
        index = plugin.PluginsHelpIndex()
529
        self.assertEqual([], index.get_topics('nothing by this name'))
530
531
    def test_prefix(self):
532
        """PluginsHelpIndex has a prefix of 'plugins/'."""
533
        index = plugin.PluginsHelpIndex()
534
        self.assertEqual('plugins/', index.prefix)
535
2475.1.1 by Martin Pool
Rename test_plugin tests and the example module used there.
536
    def test_get_plugin_topic_with_prefix(self):
537
        """Searching for plugins/demo_module returns help."""
2432.1.25 by Robert Collins
Return plugin module docstrings for 'bzr help plugin'.
538
        index = plugin.PluginsHelpIndex()
2475.1.1 by Martin Pool
Rename test_plugin tests and the example module used there.
539
        self.assertFalse(sys.modules.has_key('bzrlib.plugins.demo_module'))
540
        demo_module = FakeModule('', 'bzrlib.plugins.demo_module')
541
        sys.modules['bzrlib.plugins.demo_module'] = demo_module
2457.1.1 by Robert Collins
(robertc) Fix bzr --no-plugins selftest which was broken by the help indices patch. (Robert Collins, Martin Pool)
542
        try:
2475.1.1 by Martin Pool
Rename test_plugin tests and the example module used there.
543
            topics = index.get_topics('plugins/demo_module')
2457.1.1 by Robert Collins
(robertc) Fix bzr --no-plugins selftest which was broken by the help indices patch. (Robert Collins, Martin Pool)
544
            self.assertEqual(1, len(topics))
545
            self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
546
            self.assertEqual(demo_module, topics[0].module)
547
        finally:
2475.1.1 by Martin Pool
Rename test_plugin tests and the example module used there.
548
            del sys.modules['bzrlib.plugins.demo_module']
2432.1.25 by Robert Collins
Return plugin module docstrings for 'bzr help plugin'.
549
550
551
class FakeModule(object):
552
    """A fake module to test with."""
553
554
    def __init__(self, doc, name):
555
        self.__doc__ = doc
556
        self.__name__ = name
557
558
559
class TestModuleHelpTopic(tests.TestCase):
560
    """Tests for the ModuleHelpTopic class."""
561
562
    def test_contruct(self):
563
        """Construction takes the module to document."""
564
        mod = FakeModule('foo', 'foo')
565
        topic = plugin.ModuleHelpTopic(mod)
566
        self.assertEqual(mod, topic.module)
567
568
    def test_get_help_text_None(self):
569
        """A ModuleHelpTopic returns the docstring for get_help_text."""
570
        mod = FakeModule(None, 'demo')
571
        topic = plugin.ModuleHelpTopic(mod)
572
        self.assertEqual("Plugin 'demo' has no docstring.\n",
573
            topic.get_help_text())
574
575
    def test_get_help_text_no_carriage_return(self):
576
        """ModuleHelpTopic.get_help_text adds a \n if needed."""
577
        mod = FakeModule('one line of help', 'demo')
578
        topic = plugin.ModuleHelpTopic(mod)
579
        self.assertEqual("one line of help\n",
580
            topic.get_help_text())
581
582
    def test_get_help_text_carriage_return(self):
583
        """ModuleHelpTopic.get_help_text adds a \n if needed."""
584
        mod = FakeModule('two lines of help\nand more\n', 'demo')
585
        topic = plugin.ModuleHelpTopic(mod)
586
        self.assertEqual("two lines of help\nand more\n",
587
            topic.get_help_text())
588
589
    def test_get_help_text_with_additional_see_also(self):
590
        mod = FakeModule('two lines of help\nand more', 'demo')
591
        topic = plugin.ModuleHelpTopic(mod)
592
        self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
593
            topic.get_help_text(['foo', 'bar']))
2432.1.29 by Robert Collins
Add get_help_topic to ModuleHelpTopic.
594
595
    def test_get_help_topic(self):
596
        """The help topic for a plugin is its module name."""
2432.1.30 by Robert Collins
Fix the ModuleHelpTopic get_help_topic to be tested with closer to real world data and strip the bzrlib.plugins. prefix from the name.
597
        mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
2432.1.29 by Robert Collins
Add get_help_topic to ModuleHelpTopic.
598
        topic = plugin.ModuleHelpTopic(mod)
599
        self.assertEqual('demo', topic.get_help_topic())
2432.1.30 by Robert Collins
Fix the ModuleHelpTopic get_help_topic to be tested with closer to real world data and strip the bzrlib.plugins. prefix from the name.
600
        mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
2432.1.29 by Robert Collins
Add get_help_topic to ModuleHelpTopic.
601
        topic = plugin.ModuleHelpTopic(mod)
602
        self.assertEqual('foo_bar', topic.get_help_topic())
3835.2.7 by Aaron Bentley
Add tests for plugins
603
604
605
def clear_plugins(test_case):
3908.1.4 by Andrew Bennetts
Fix test_plugins to restore the original value of bzrlib.plugin._loaded.
606
    # Save the attributes that we're about to monkey-patch.
3835.2.7 by Aaron Bentley
Add tests for plugins
607
    old_plugins_path = bzrlib.plugins.__path__
3908.1.4 by Andrew Bennetts
Fix test_plugins to restore the original value of bzrlib.plugin._loaded.
608
    old_loaded = plugin._loaded
609
    old_load_from_path = plugin.load_from_path
610
    # Change bzrlib.plugin to think no plugins have been loaded yet.
3835.2.7 by Aaron Bentley
Add tests for plugins
611
    bzrlib.plugins.__path__ = []
612
    plugin._loaded = False
3908.1.4 by Andrew Bennetts
Fix test_plugins to restore the original value of bzrlib.plugin._loaded.
613
    # Monkey-patch load_from_path to stop it from actually loading anything.
3908.1.1 by Andrew Bennetts
Try harder to avoid loading plugins during the test suite.
614
    def load_from_path(dirs):
615
        pass
616
    plugin.load_from_path = load_from_path
3835.2.7 by Aaron Bentley
Add tests for plugins
617
    def restore_plugins():
618
        bzrlib.plugins.__path__ = old_plugins_path
3908.1.4 by Andrew Bennetts
Fix test_plugins to restore the original value of bzrlib.plugin._loaded.
619
        plugin._loaded = old_loaded
3908.1.1 by Andrew Bennetts
Try harder to avoid loading plugins during the test suite.
620
        plugin.load_from_path = old_load_from_path
3835.2.7 by Aaron Bentley
Add tests for plugins
621
    test_case.addCleanup(restore_plugins)
622
623
624
class TestPluginPaths(tests.TestCase):
625
626
    def test_set_plugins_path_with_args(self):
627
        clear_plugins(self)
628
        plugin.set_plugins_path(['a', 'b'])
629
        self.assertEqual(['a', 'b'], bzrlib.plugins.__path__)
630
631
    def test_set_plugins_path_defaults(self):
632
        clear_plugins(self)
633
        plugin.set_plugins_path()
634
        self.assertEqual(plugin.get_standard_plugins_path(),
635
                         bzrlib.plugins.__path__)
636
637
    def test_get_standard_plugins_path(self):
638
        path = plugin.get_standard_plugins_path()
639
        self.assertEqual(plugin.get_default_plugin_path(), path[0])
640
        for directory in path:
4412.2.1 by Vincent Ladeuil
Fix some OSX test regressions (well actual test bugs indeed).
641
            self.assertNotContainsRe(directory, r'\\/$')
3835.2.7 by Aaron Bentley
Add tests for plugins
642
        try:
643
            from distutils.sysconfig import get_python_lib
644
        except ImportError:
645
            pass
646
        else:
647
            if sys.platform != 'win32':
648
                python_lib = get_python_lib()
649
                for directory in path:
650
                    if directory.startswith(python_lib):
651
                        break
652
                else:
653
                    self.fail('No path to global plugins')
654
655
    def test_get_standard_plugins_path_env(self):
656
        os.environ['BZR_PLUGIN_PATH'] = 'foo/'
657
        self.assertEqual('foo', plugin.get_standard_plugins_path()[0])
658
659
660
class TestLoadPlugins(tests.TestCaseInTempDir):
661
662
    def test_load_plugins(self):
663
        clear_plugins(self)
664
        plugin.load_plugins(['.'])
665
        self.assertEqual(bzrlib.plugins.__path__, ['.'])
666
        # subsequent loads are no-ops
667
        plugin.load_plugins(['foo'])
668
        self.assertEqual(bzrlib.plugins.__path__, ['.'])
669
670
    def test_load_plugins_default(self):
671
        clear_plugins(self)
672
        plugin.load_plugins()
673
        path = plugin.get_standard_plugins_path()
674
        self.assertEqual(path, bzrlib.plugins.__path__)