/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/test_plugins.py

  • Committer: Jelmer Vernooij
  • Date: 2018-07-19 23:03:50 UTC
  • mto: (7045.3.3 python3-r)
  • mto: This revision was merged to the branch mainline in revision 7051.
  • Revision ID: jelmer@jelmer.uk-20180719230350-t4d108pdabo1j3tw
Fix some testament tests.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005-2012, 2016 Canonical Ltd
 
2
# Copyright (C) 2017-2018 Breezy developers
 
3
#
3
4
# This program is free software; you can redistribute it and/or modify
4
5
# it under the terms of the GNU General Public License as published by
5
6
# the Free Software Foundation; either version 2 of the License, or
6
7
# (at your option) any later version.
7
 
 
 
8
#
8
9
# This program is distributed in the hope that it will be useful,
9
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
12
# GNU General Public License for more details.
12
 
 
 
13
#
13
14
# You should have received a copy of the GNU General Public License
14
15
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
 
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
 
18
18
"""Tests for plugins"""
19
19
 
20
 
# XXX: There are no plugin tests at the moment because the plugin module
21
 
# affects the global state of the process.  See bzrlib/plugins.py for more
22
 
# comments.
23
 
 
 
20
import importlib
 
21
import logging
24
22
import os
25
 
 
26
 
import bzrlib.plugin
27
 
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
 
#         """
72
 
 
73
 
PLUGIN_TEXT = """\
74
 
import bzrlib.commands
75
 
class cmd_myplug(bzrlib.commands.Command):
76
 
    '''Just a simple test plugin.'''
77
 
    aliases = ['mplg']
78
 
    def run(self):
79
 
        print 'Hello from my plugin'
80
 
"""
 
23
import sys
 
24
import types
 
25
 
 
26
import breezy
 
27
from .. import (
 
28
    errors,
 
29
    osutils,
 
30
    plugin,
 
31
    tests,
 
32
    trace,
 
33
    )
 
34
from ..sixish import (
 
35
    PY3,
 
36
    StringIO,
 
37
    viewkeys,
 
38
    )
 
39
 
81
40
 
82
41
# TODO: Write a test for plugin decoration of commands.
83
42
 
84
 
class TestOneNamedPluginOnly(TestCaseInTempDir):
 
43
invalidate_caches = getattr(importlib, "invalidate_caches", lambda: None)
 
44
 
 
45
 
 
46
class BaseTestPlugins(tests.TestCaseInTempDir):
 
47
    """TestCase that isolates plugin imports and cleans up on completion."""
 
48
 
 
49
    def setUp(self):
 
50
        super(BaseTestPlugins, self).setUp()
 
51
        self.module_name = "breezy.testingplugins"
 
52
        self.module_prefix = self.module_name + "."
 
53
        self.module = types.ModuleType(self.module_name)
 
54
 
 
55
        self.overrideAttr(plugin, "_MODULE_PREFIX", self.module_prefix)
 
56
        self.overrideAttr(breezy, "testingplugins", self.module)
 
57
 
 
58
        sys.modules[self.module_name] = self.module
 
59
        self.addCleanup(self._unregister_all)
 
60
        self.addCleanup(self._unregister_finder)
 
61
 
 
62
        invalidate_caches()
 
63
 
 
64
    def reset(self):
 
65
        """Remove all global testing state and clean up module."""
 
66
        # GZ 2017-06-02: Ideally don't do this, write new test or generate
 
67
        # bytecode by other mechanism.
 
68
        self.log("resetting plugin testing context")
 
69
        self._unregister_all()
 
70
        self._unregister_finder()
 
71
        sys.modules[self.module_name] = self.module
 
72
        for name in list(self.module.__dict__):
 
73
            if name[:2] != '__':
 
74
                delattr(self.module, name)
 
75
        invalidate_caches()
 
76
        self.plugins = None
 
77
 
 
78
    def update_module_paths(self, paths):
 
79
        paths = plugin.extend_path(paths, self.module_name)
 
80
        self.module.__path__ = paths
 
81
        self.log("using %r", paths)
 
82
        return paths
 
83
 
 
84
    def load_with_paths(self, paths):
 
85
        self.log("loading plugins!")
 
86
        plugin.load_plugins(self.update_module_paths(paths), state=self)
 
87
 
 
88
    def create_plugin(self, name, source=None, dir='.', file_name=None):
 
89
        if source is None:
 
90
            source = '''\
 
91
"""This is the doc for %s"""
 
92
''' % (name)
 
93
        if file_name is None:
 
94
            file_name = name + '.py'
 
95
        # 'source' must not fail to load
 
96
        path = osutils.pathjoin(dir, file_name)
 
97
        with open(path, 'w') as f:
 
98
            f.write(source + '\n')
 
99
 
 
100
    def create_plugin_package(self, name, dir=None, source=None):
 
101
        if dir is None:
 
102
            dir = name
 
103
        if source is None:
 
104
            source = '''\
 
105
"""This is the doc for %s"""
 
106
dir_source = '%s'
 
107
''' % (name, dir)
 
108
        os.makedirs(dir)
 
109
        self.create_plugin(name, source, dir,
 
110
                           file_name='__init__.py')
 
111
 
 
112
    def promote_cache(self, directory):
 
113
        """Move bytecode files out of __pycache__ in given directory."""
 
114
        cache_dir = os.path.join(directory, '__pycache__')
 
115
        if os.path.isdir(cache_dir):
 
116
            for name in os.listdir(cache_dir):
 
117
                magicless_name = '.'.join(name.split('.')[0::name.count('.')])
 
118
                rel = osutils.relpath(self.test_dir, cache_dir)
 
119
                self.log("moving %s in %s to %s", name, rel, magicless_name)
 
120
                os.rename(os.path.join(cache_dir, name),
 
121
                    os.path.join(directory, magicless_name))
 
122
 
 
123
    def _unregister_finder(self):
 
124
        """Removes any test copies of _PluginsAtFinder from sys.meta_path."""
 
125
        idx = len(sys.meta_path)
 
126
        while idx:
 
127
            idx -= 1
 
128
            finder = sys.meta_path[idx]
 
129
            if getattr(finder, "prefix", "") == self.module_prefix:
 
130
                self.log("removed %r from sys.meta_path", finder)
 
131
                sys.meta_path.pop(idx)
 
132
 
 
133
    def _unregister_all(self):
 
134
        """Remove all plugins in the test namespace from sys.modules."""
 
135
        for name in list(sys.modules):
 
136
            if name.startswith(self.module_prefix) or name == self.module_name:
 
137
                self.log("removed %s from sys.modules", name)
 
138
                del sys.modules[name]
 
139
 
 
140
    def assertPluginModules(self, plugin_dict):
 
141
        self.assertEqual(
 
142
            dict((k[len(self.module_prefix):], sys.modules[k])
 
143
                for k in sys.modules if k.startswith(self.module_prefix)),
 
144
            plugin_dict)
 
145
 
 
146
    def assertPluginUnknown(self, name):
 
147
        self.assertTrue(getattr(self.module, name, None) is None)
 
148
        self.assertFalse(self.module_prefix + name in sys.modules)
 
149
 
 
150
    def assertPluginKnown(self, name):
 
151
        self.assertTrue(getattr(self.module, name, None) is not None)
 
152
        self.assertTrue(self.module_prefix + name in sys.modules)
 
153
 
 
154
 
 
155
class TestLoadingPlugins(BaseTestPlugins):
85
156
 
86
157
    activeattributes = {}
87
158
 
88
159
    def test_plugins_with_the_same_name_are_not_loaded(self):
 
160
        # This test tests that having two plugins in different directories does
 
161
        # not result in both being loaded when they have the same name.  get a
 
162
        # file name we can use which is also a valid attribute for accessing in
 
163
        # activeattributes. - we cannot give import parameters.
 
164
        tempattribute = "0"
 
165
        self.assertFalse(tempattribute in self.activeattributes)
 
166
        # set a place for the plugins to record their loading, and at the same
 
167
        # time validate that the location the plugins should record to is
 
168
        # valid and correct.
 
169
        self.__class__.activeattributes [tempattribute] = []
 
170
        self.assertTrue(tempattribute in self.activeattributes)
 
171
        # create two plugin directories
 
172
        os.mkdir('first')
 
173
        os.mkdir('second')
 
174
        # write a plugin that will record when its loaded in the
 
175
        # tempattribute list.
 
176
        template = ("from breezy.tests.test_plugins import TestLoadingPlugins\n"
 
177
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
 
178
 
 
179
        with open(os.path.join('first', 'plugin.py'), 'w') as outfile:
 
180
            outfile.write(template % (tempattribute, 'first'))
 
181
            outfile.write('\n')
 
182
 
 
183
        with open(os.path.join('second', 'plugin.py'), 'w') as outfile:
 
184
            outfile.write(template % (tempattribute, 'second'))
 
185
            outfile.write('\n')
 
186
 
 
187
        try:
 
188
            self.load_with_paths(['first', 'second'])
 
189
            self.assertEqual(['first'], self.activeattributes[tempattribute])
 
190
        finally:
 
191
            del self.activeattributes[tempattribute]
 
192
 
 
193
    def test_plugins_from_different_dirs_can_demand_load(self):
 
194
        self.assertFalse('breezy.plugins.pluginone' in sys.modules)
 
195
        self.assertFalse('breezy.plugins.plugintwo' in sys.modules)
89
196
        # 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
 
197
        # directories with different names allows them both to be loaded, when
 
198
        # we do a direct import statement.
 
199
        # Determine a file name we can use which is also a valid attribute
92
200
        # for accessing in activeattributes. - we cannot give import parameters.
93
 
        tempattribute = "0"
94
 
        self.failIf(tempattribute in self.activeattributes)
 
201
        tempattribute = "different-dirs"
 
202
        self.assertFalse(tempattribute in self.activeattributes)
95
203
        # set a place for the plugins to record their loading, and at the same
96
204
        # time validate that the location the plugins should record to is
97
205
        # valid and correct.
98
 
        bzrlib.tests.test_plugins.TestOneNamedPluginOnly.activeattributes \
 
206
        breezy.tests.test_plugins.TestLoadingPlugins.activeattributes \
99
207
            [tempattribute] = []
100
 
        self.failUnless(tempattribute in self.activeattributes)
 
208
        self.assertTrue(tempattribute in self.activeattributes)
101
209
        # create two plugin directories
102
210
        os.mkdir('first')
103
211
        os.mkdir('second')
104
 
        # write a plugin that will record when its loaded in the 
 
212
        # write plugins that will record when they are loaded in the
105
213
        # 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')
 
214
        template = ("from breezy.tests.test_plugins import TestLoadingPlugins\n"
 
215
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
 
216
 
 
217
        with open(os.path.join('first', 'pluginone.py'), 'w') as outfile:
 
218
            outfile.write(template % (tempattribute, 'first'))
 
219
            outfile.write('\n')
 
220
 
 
221
        with open(os.path.join('second', 'plugintwo.py'), 'w') as outfile:
 
222
            outfile.write(template % (tempattribute, 'second'))
 
223
            outfile.write('\n')
 
224
 
110
225
        try:
111
 
            bzrlib.plugin.load_from_dirs(['first', 'second'])
 
226
            self.assertPluginUnknown('pluginone')
 
227
            self.assertPluginUnknown('plugintwo')
 
228
            self.update_module_paths(['first', 'second'])
 
229
            exec("import %spluginone" % self.module_prefix)
112
230
            self.assertEqual(['first'], self.activeattributes[tempattribute])
113
 
        finally:
114
 
            # remove the plugin 'plugin'
115
 
            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().
125
 
        # check the plugin is not loaded already
126
 
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
 
231
            exec("import %splugintwo" % self.module_prefix)
 
232
            self.assertEqual(['first', 'second'],
 
233
                self.activeattributes[tempattribute])
 
234
        finally:
 
235
            del self.activeattributes[tempattribute]
 
236
 
 
237
    def test_plugins_can_load_from_directory_with_trailing_slash(self):
 
238
        # This test tests that a plugin can load from a directory when the
 
239
        # directory in the path has a trailing slash.
 
240
        # check the plugin is not loaded already
 
241
        self.assertPluginUnknown('ts_plugin')
 
242
        tempattribute = "trailing-slash"
 
243
        self.assertFalse(tempattribute in self.activeattributes)
 
244
        # set a place for the plugin to record its loading, and at the same
 
245
        # time validate that the location the plugin should record to is
 
246
        # valid and correct.
 
247
        breezy.tests.test_plugins.TestLoadingPlugins.activeattributes \
 
248
            [tempattribute] = []
 
249
        self.assertTrue(tempattribute in self.activeattributes)
 
250
        # create a directory for the plugin
 
251
        os.mkdir('plugin_test')
 
252
        # write a plugin that will record when its loaded in the
 
253
        # tempattribute list.
 
254
        template = ("from breezy.tests.test_plugins import TestLoadingPlugins\n"
 
255
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
 
256
 
 
257
        with open(os.path.join('plugin_test', 'ts_plugin.py'), 'w') as outfile:
 
258
            outfile.write(template % (tempattribute, 'plugin'))
 
259
            outfile.write('\n')
 
260
 
 
261
        try:
 
262
            self.load_with_paths(['plugin_test'+os.sep])
 
263
            self.assertEqual(['plugin'], self.activeattributes[tempattribute])
 
264
            self.assertPluginKnown('ts_plugin')
 
265
        finally:
 
266
            del self.activeattributes[tempattribute]
 
267
 
 
268
    def load_and_capture(self, name):
 
269
        """Load plugins from '.' capturing the output.
 
270
 
 
271
        :param name: The name of the plugin.
 
272
        :return: A string with the log from the plugin loading call.
 
273
        """
 
274
        # Capture output
 
275
        stream = StringIO()
 
276
        try:
 
277
            handler = logging.StreamHandler(stream)
 
278
            log = logging.getLogger('brz')
 
279
            log.addHandler(handler)
 
280
            try:
 
281
                self.load_with_paths(['.'])
 
282
            finally:
 
283
                # Stop capturing output
 
284
                handler.flush()
 
285
                handler.close()
 
286
                log.removeHandler(handler)
 
287
            return stream.getvalue()
 
288
        finally:
 
289
            stream.close()
 
290
 
 
291
    def test_plugin_with_bad_api_version_reports(self):
 
292
        """Try loading a plugin that requests an unsupported api.
 
293
 
 
294
        Observe that it records the problem but doesn't complain on stderr.
 
295
 
 
296
        See https://bugs.launchpad.net/bzr/+bug/704195
 
297
        """
 
298
        name = 'wants100.py'
 
299
        with open(name, 'w') as f:
 
300
            f.write("import breezy\n"
 
301
                "from breezy.errors import IncompatibleVersion\n"
 
302
                "raise IncompatibleVersion(breezy, [(1, 0, 0)], (0, 0, 5))\n")
 
303
        log = self.load_and_capture(name)
 
304
        self.assertNotContainsRe(log,
 
305
            r"It supports breezy version")
 
306
        self.assertEqual({'wants100'}, viewkeys(self.plugin_warnings))
 
307
        self.assertContainsRe(
 
308
            self.plugin_warnings['wants100'][0],
 
309
            r"It supports breezy version")
 
310
 
 
311
    def test_plugin_with_bad_name_does_not_load(self):
 
312
        # The file name here invalid for a python module.
 
313
        name = 'brz-bad plugin-name..py'
 
314
        open(name, 'w').close()
 
315
        log = self.load_and_capture(name)
 
316
        self.assertContainsRe(log,
 
317
            r"Unable to load 'brz-bad plugin-name\.' in '\.' as a plugin "
 
318
            "because the file path isn't a valid module name; try renaming "
 
319
            "it to 'bad_plugin_name_'\\.")
 
320
 
 
321
 
 
322
class TestPlugins(BaseTestPlugins):
 
323
 
 
324
    def setup_plugin(self, source=""):
 
325
        # This test tests a new plugin appears in breezy.plugin.plugins().
 
326
        # check the plugin is not loaded already
 
327
        self.assertPluginUnknown('plugin')
127
328
        # 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
139
 
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
 
329
        with open('plugin.py', 'w') as f: f.write(source + '\n')
 
330
        self.load_with_paths(['.'])
 
331
 
 
332
    def test_plugin_loaded(self):
 
333
        self.assertPluginUnknown('plugin')
 
334
        self.assertIs(None, breezy.plugin.get_loaded_plugin('plugin'))
 
335
        self.setup_plugin()
 
336
        p = breezy.plugin.get_loaded_plugin('plugin')
 
337
        self.assertIsInstance(p, breezy.plugin.PlugIn)
 
338
        self.assertIs(p.module, sys.modules[self.module_prefix + 'plugin'])
 
339
 
 
340
    def test_plugin_loaded_disabled(self):
 
341
        self.assertPluginUnknown('plugin')
 
342
        self.overrideEnv('BRZ_DISABLE_PLUGINS', 'plugin')
 
343
        self.setup_plugin()
 
344
        self.assertIs(None, breezy.plugin.get_loaded_plugin('plugin'))
 
345
 
 
346
    def test_plugin_appears_in_plugins(self):
 
347
        self.setup_plugin()
 
348
        self.assertPluginKnown('plugin')
 
349
        p = self.plugins['plugin']
 
350
        self.assertIsInstance(p, breezy.plugin.PlugIn)
 
351
        self.assertIs(p.module, sys.modules[self.module_prefix + 'plugin'])
 
352
 
 
353
    def test_trivial_plugin_get_path(self):
 
354
        self.setup_plugin()
 
355
        p = self.plugins['plugin']
 
356
        plugin_path = self.test_dir + '/plugin.py'
 
357
        self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
 
358
 
 
359
    def test_plugin_get_path_py_not_pyc(self):
 
360
        # first import creates plugin.pyc
 
361
        self.setup_plugin()
 
362
        self.promote_cache(self.test_dir)
 
363
        self.reset()
 
364
        self.load_with_paths(['.']) # import plugin.pyc
 
365
        p = plugin.plugins()['plugin']
 
366
        plugin_path = self.test_dir + '/plugin.py'
 
367
        self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
 
368
 
 
369
    def test_plugin_get_path_pyc_only(self):
 
370
        # first import creates plugin.pyc (or plugin.pyo depending on __debug__)
 
371
        self.setup_plugin()
 
372
        os.unlink(self.test_dir + '/plugin.py')
 
373
        self.promote_cache(self.test_dir)
 
374
        self.reset()
 
375
        self.load_with_paths(['.']) # import plugin.pyc (or .pyo)
 
376
        p = plugin.plugins()['plugin']
 
377
        plugin_path = self.test_dir + '/plugin' + plugin.COMPILED_EXT
 
378
        self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
 
379
 
 
380
    def test_no_test_suite_gives_None_for_test_suite(self):
 
381
        self.setup_plugin()
 
382
        p = plugin.plugins()['plugin']
 
383
        self.assertEqual(None, p.test_suite())
 
384
 
 
385
    def test_test_suite_gives_test_suite_result(self):
 
386
        source = """def test_suite(): return 'foo'"""
 
387
        self.setup_plugin(source)
 
388
        p = plugin.plugins()['plugin']
 
389
        self.assertEqual('foo', p.test_suite())
 
390
 
 
391
    def test_no_load_plugin_tests_gives_None_for_load_plugin_tests(self):
 
392
        self.setup_plugin()
 
393
        loader = tests.TestUtil.TestLoader()
 
394
        p = plugin.plugins()['plugin']
 
395
        self.assertEqual(None, p.load_plugin_tests(loader))
 
396
 
 
397
    def test_load_plugin_tests_gives_load_plugin_tests_result(self):
 
398
        source = """
 
399
def load_tests(loader, standard_tests, pattern):
 
400
    return 'foo'"""
 
401
        self.setup_plugin(source)
 
402
        loader = tests.TestUtil.TestLoader()
 
403
        p = plugin.plugins()['plugin']
 
404
        self.assertEqual('foo', p.load_plugin_tests(loader))
 
405
 
 
406
    def check_version_info(self, expected, source='', name='plugin'):
 
407
        self.setup_plugin(source)
 
408
        self.assertEqual(expected, plugin.plugins()[name].version_info())
 
409
 
 
410
    def test_no_version_info(self):
 
411
        self.check_version_info(None)
 
412
 
 
413
    def test_with_version_info(self):
 
414
        self.check_version_info((1, 2, 3, 'dev', 4),
 
415
                                "version_info = (1, 2, 3, 'dev', 4)")
 
416
 
 
417
    def test_short_version_info_gets_padded(self):
 
418
        # the gtk plugin has version_info = (1,2,3) rather than the 5-tuple.
 
419
        # so we adapt it
 
420
        self.check_version_info((1, 2, 3, 'final', 0),
 
421
                                "version_info = (1, 2, 3)")
 
422
 
 
423
    def check_version(self, expected, source=None, name='plugin'):
 
424
        self.setup_plugin(source)
 
425
        self.assertEqual(expected, plugins[name].__version__)
 
426
 
 
427
    def test_no_version_info___version__(self):
 
428
        self.setup_plugin()
 
429
        plugin = breezy.plugin.plugins()['plugin']
 
430
        self.assertEqual("unknown", plugin.__version__)
 
431
 
 
432
    def test_str__version__with_version_info(self):
 
433
        self.setup_plugin("version_info = '1.2.3'")
 
434
        plugin = breezy.plugin.plugins()['plugin']
 
435
        self.assertEqual("1.2.3", plugin.__version__)
 
436
 
 
437
    def test_noniterable__version__with_version_info(self):
 
438
        self.setup_plugin("version_info = (1)")
 
439
        plugin = breezy.plugin.plugins()['plugin']
 
440
        self.assertEqual("1", plugin.__version__)
 
441
 
 
442
    def test_1__version__with_version_info(self):
 
443
        self.setup_plugin("version_info = (1,)")
 
444
        plugin = breezy.plugin.plugins()['plugin']
 
445
        self.assertEqual("1", plugin.__version__)
 
446
 
 
447
    def test_1_2__version__with_version_info(self):
 
448
        self.setup_plugin("version_info = (1, 2)")
 
449
        plugin = breezy.plugin.plugins()['plugin']
 
450
        self.assertEqual("1.2", plugin.__version__)
 
451
 
 
452
    def test_1_2_3__version__with_version_info(self):
 
453
        self.setup_plugin("version_info = (1, 2, 3)")
 
454
        plugin = breezy.plugin.plugins()['plugin']
 
455
        self.assertEqual("1.2.3", plugin.__version__)
 
456
 
 
457
    def test_candidate__version__with_version_info(self):
 
458
        self.setup_plugin("version_info = (1, 2, 3, 'candidate', 1)")
 
459
        plugin = breezy.plugin.plugins()['plugin']
 
460
        self.assertEqual("1.2.3rc1", plugin.__version__)
 
461
 
 
462
    def test_dev__version__with_version_info(self):
 
463
        self.setup_plugin("version_info = (1, 2, 3, 'dev', 0)")
 
464
        plugin = breezy.plugin.plugins()['plugin']
 
465
        self.assertEqual("1.2.3dev", plugin.__version__)
 
466
 
 
467
    def test_dev_fallback__version__with_version_info(self):
 
468
        self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
 
469
        plugin = breezy.plugin.plugins()['plugin']
 
470
        self.assertEqual("1.2.3dev4", plugin.__version__)
 
471
 
 
472
    def test_final__version__with_version_info(self):
 
473
        self.setup_plugin("version_info = (1, 2, 3, 'final', 0)")
 
474
        plugin = breezy.plugin.plugins()['plugin']
 
475
        self.assertEqual("1.2.3", plugin.__version__)
 
476
 
 
477
    def test_final_fallback__version__with_version_info(self):
 
478
        self.setup_plugin("version_info = (1, 2, 3, 'final', 2)")
 
479
        plugin = breezy.plugin.plugins()['plugin']
 
480
        self.assertEqual("1.2.3.2", plugin.__version__)
 
481
 
 
482
 
 
483
class TestHelpIndex(tests.TestCase):
 
484
    """Tests for the PluginsHelpIndex class."""
 
485
 
 
486
    def test_default_constructable(self):
 
487
        index = plugin.PluginsHelpIndex()
 
488
 
 
489
    def test_get_topics_None(self):
 
490
        """Searching for None returns an empty list."""
 
491
        index = plugin.PluginsHelpIndex()
 
492
        self.assertEqual([], index.get_topics(None))
 
493
 
 
494
    def test_get_topics_for_plugin(self):
 
495
        """Searching for plugin name gets its docstring."""
 
496
        index = plugin.PluginsHelpIndex()
 
497
        # make a new plugin here for this test, even if we're run with
 
498
        # --no-plugins
 
499
        self.assertFalse('breezy.plugins.demo_module' in sys.modules)
 
500
        demo_module = FakeModule('', 'breezy.plugins.demo_module')
 
501
        sys.modules['breezy.plugins.demo_module'] = demo_module
 
502
        try:
 
503
            topics = index.get_topics('demo_module')
 
504
            self.assertEqual(1, len(topics))
 
505
            self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
 
506
            self.assertEqual(demo_module, topics[0].module)
 
507
        finally:
 
508
            del sys.modules['breezy.plugins.demo_module']
 
509
 
 
510
    def test_get_topics_no_topic(self):
 
511
        """Searching for something that is not a plugin returns []."""
 
512
        # test this by using a name that cannot be a plugin - its not
 
513
        # a valid python identifier.
 
514
        index = plugin.PluginsHelpIndex()
 
515
        self.assertEqual([], index.get_topics('nothing by this name'))
 
516
 
 
517
    def test_prefix(self):
 
518
        """PluginsHelpIndex has a prefix of 'plugins/'."""
 
519
        index = plugin.PluginsHelpIndex()
 
520
        self.assertEqual('plugins/', index.prefix)
 
521
 
 
522
    def test_get_plugin_topic_with_prefix(self):
 
523
        """Searching for plugins/demo_module returns help."""
 
524
        index = plugin.PluginsHelpIndex()
 
525
        self.assertFalse('breezy.plugins.demo_module' in sys.modules)
 
526
        demo_module = FakeModule('', 'breezy.plugins.demo_module')
 
527
        sys.modules['breezy.plugins.demo_module'] = demo_module
 
528
        try:
 
529
            topics = index.get_topics('plugins/demo_module')
 
530
            self.assertEqual(1, len(topics))
 
531
            self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
 
532
            self.assertEqual(demo_module, topics[0].module)
 
533
        finally:
 
534
            del sys.modules['breezy.plugins.demo_module']
 
535
 
 
536
 
 
537
class FakeModule(object):
 
538
    """A fake module to test with."""
 
539
 
 
540
    def __init__(self, doc, name):
 
541
        self.__doc__ = doc
 
542
        self.__name__ = name
 
543
 
 
544
 
 
545
class TestModuleHelpTopic(tests.TestCase):
 
546
    """Tests for the ModuleHelpTopic class."""
 
547
 
 
548
    def test_contruct(self):
 
549
        """Construction takes the module to document."""
 
550
        mod = FakeModule('foo', 'foo')
 
551
        topic = plugin.ModuleHelpTopic(mod)
 
552
        self.assertEqual(mod, topic.module)
 
553
 
 
554
    def test_get_help_text_None(self):
 
555
        """A ModuleHelpTopic returns the docstring for get_help_text."""
 
556
        mod = FakeModule(None, 'demo')
 
557
        topic = plugin.ModuleHelpTopic(mod)
 
558
        self.assertEqual("Plugin 'demo' has no docstring.\n",
 
559
            topic.get_help_text())
 
560
 
 
561
    def test_get_help_text_no_carriage_return(self):
 
562
        """ModuleHelpTopic.get_help_text adds a \n if needed."""
 
563
        mod = FakeModule('one line of help', 'demo')
 
564
        topic = plugin.ModuleHelpTopic(mod)
 
565
        self.assertEqual("one line of help\n",
 
566
            topic.get_help_text())
 
567
 
 
568
    def test_get_help_text_carriage_return(self):
 
569
        """ModuleHelpTopic.get_help_text adds a \n if needed."""
 
570
        mod = FakeModule('two lines of help\nand more\n', 'demo')
 
571
        topic = plugin.ModuleHelpTopic(mod)
 
572
        self.assertEqual("two lines of help\nand more\n",
 
573
            topic.get_help_text())
 
574
 
 
575
    def test_get_help_text_with_additional_see_also(self):
 
576
        mod = FakeModule('two lines of help\nand more', 'demo')
 
577
        topic = plugin.ModuleHelpTopic(mod)
 
578
        self.assertEqual("two lines of help\nand more\n\n:See also: bar, foo\n",
 
579
                         topic.get_help_text(['foo', 'bar']))
 
580
 
 
581
    def test_get_help_topic(self):
 
582
        """The help topic for a plugin is its module name."""
 
583
        mod = FakeModule('two lines of help\nand more', 'breezy.plugins.demo')
 
584
        topic = plugin.ModuleHelpTopic(mod)
 
585
        self.assertEqual('demo', topic.get_help_topic())
 
586
        mod = FakeModule('two lines of help\nand more',
 
587
                         'breezy.plugins.foo_bar')
 
588
        topic = plugin.ModuleHelpTopic(mod)
 
589
        self.assertEqual('foo_bar', topic.get_help_topic())
 
590
 
 
591
 
 
592
class TestEnvPluginPath(tests.TestCase):
 
593
 
 
594
    user = "USER"
 
595
    core = "CORE"
 
596
    site = "SITE"
 
597
 
 
598
    def check_path(self, expected_dirs, setting_dirs):
 
599
        if setting_dirs is None:
 
600
            del os.environ['BRZ_PLUGIN_PATH']
 
601
        else:
 
602
            os.environ['BRZ_PLUGIN_PATH'] = os.pathsep.join(setting_dirs)
 
603
        actual = [(p if t == 'path' else t.upper())
 
604
            for p, t in plugin._env_plugin_path()]
 
605
        self.assertEqual(expected_dirs, actual)
 
606
 
 
607
    def test_default(self):
 
608
        self.check_path([self.user, self.core, self.site],
 
609
                        None)
 
610
 
 
611
    def test_adhoc_policy(self):
 
612
        self.check_path([self.user, self.core, self.site],
 
613
                        ['+user', '+core', '+site'])
 
614
 
 
615
    def test_fallback_policy(self):
 
616
        self.check_path([self.core, self.site, self.user],
 
617
                        ['+core', '+site', '+user'])
 
618
 
 
619
    def test_override_policy(self):
 
620
        self.check_path([self.user, self.site, self.core],
 
621
                        ['+user', '+site', '+core'])
 
622
 
 
623
    def test_disable_user(self):
 
624
        self.check_path([self.core, self.site], ['-user'])
 
625
 
 
626
    def test_disable_user_twice(self):
 
627
        # Ensures multiple removals don't left cruft
 
628
        self.check_path([self.core, self.site], ['-user', '-user'])
 
629
 
 
630
    def test_duplicates_are_removed(self):
 
631
        self.check_path([self.user, self.core, self.site],
 
632
                        ['+user', '+user'])
 
633
        # And only the first reference is kept (since the later references will
 
634
        # only produce '<plugin> already loaded' mutters)
 
635
        self.check_path([self.user, self.core, self.site],
 
636
                        ['+user', '+user', '+core',
 
637
                         '+user', '+site', '+site',
 
638
                         '+core'])
 
639
 
 
640
    def test_disable_overrides_enable(self):
 
641
        self.check_path([self.core, self.site], ['-user', '+user'])
 
642
 
 
643
    def test_disable_core(self):
 
644
        self.check_path([self.site], ['-core'])
 
645
        self.check_path([self.user, self.site], ['+user', '-core'])
 
646
 
 
647
    def test_disable_site(self):
 
648
        self.check_path([self.core], ['-site'])
 
649
        self.check_path([self.user, self.core], ['-site', '+user'])
 
650
 
 
651
    def test_override_site(self):
 
652
        self.check_path(['mysite', self.user, self.core],
 
653
                        ['mysite', '-site', '+user'])
 
654
        self.check_path(['mysite', self.core],
 
655
                        ['mysite', '-site'])
 
656
 
 
657
    def test_override_core(self):
 
658
        self.check_path(['mycore', self.user, self.site],
 
659
                        ['mycore', '-core', '+user', '+site'])
 
660
        self.check_path(['mycore', self.site],
 
661
                        ['mycore', '-core'])
 
662
 
 
663
    def test_my_plugin_only(self):
 
664
        self.check_path(['myplugin'], ['myplugin', '-user', '-core', '-site'])
 
665
 
 
666
    def test_my_plugin_first(self):
 
667
        self.check_path(['myplugin', self.core, self.site, self.user],
 
668
                        ['myplugin', '+core', '+site', '+user'])
 
669
 
 
670
    def test_bogus_references(self):
 
671
        self.check_path(['+foo', '-bar', self.core, self.site],
 
672
                        ['+foo', '-bar'])
 
673
 
 
674
 
 
675
class TestDisablePlugin(BaseTestPlugins):
 
676
 
 
677
    def test_cannot_import(self):
 
678
        self.create_plugin_package('works')
 
679
        self.create_plugin_package('fails')
 
680
        self.overrideEnv('BRZ_DISABLE_PLUGINS', 'fails')
 
681
        self.update_module_paths(["."])
 
682
        import breezy.testingplugins.works as works
 
683
        try:
 
684
            import breezy.testingplugins.fails as fails
 
685
        except ImportError:
 
686
            pass
 
687
        else:
 
688
            self.fail("Loaded blocked plugin: " + repr(fails))
 
689
        self.assertPluginModules({'fails': None, 'works': works})
 
690
 
 
691
    def test_partial_imports(self):
 
692
        self.create_plugin('good')
 
693
        self.create_plugin('bad')
 
694
        self.create_plugin_package('ugly')
 
695
        self.overrideEnv('BRZ_DISABLE_PLUGINS', 'bad:ugly')
 
696
        self.load_with_paths(['.'])
 
697
        self.assertEqual({'good'}, viewkeys(self.plugins))
 
698
        self.assertPluginModules({
 
699
            'good': self.plugins['good'].module,
 
700
            'bad': None,
 
701
            'ugly': None,
 
702
        })
 
703
        # Ensure there are no warnings about plugins not being imported as
 
704
        # the user has explictly requested they be disabled.
 
705
        self.assertNotContainsRe(self.get_log(), r"Unable to load plugin")
 
706
 
 
707
 
 
708
class TestEnvDisablePlugins(tests.TestCase):
 
709
 
 
710
    def _get_names(self, env_value):
 
711
        os.environ['BRZ_DISABLE_PLUGINS'] = env_value
 
712
        return plugin._env_disable_plugins()
 
713
 
 
714
    def test_unset(self):
 
715
        self.assertEqual([], plugin._env_disable_plugins())
 
716
 
 
717
    def test_empty(self):
 
718
        self.assertEqual([], self._get_names(''))
 
719
 
 
720
    def test_single(self):
 
721
        self.assertEqual(['single'], self._get_names('single'))
 
722
 
 
723
    def test_multi(self):
 
724
        expected = ['one', 'two']
 
725
        self.assertEqual(expected, self._get_names(os.pathsep.join(expected)))
 
726
 
 
727
    def test_mixed(self):
 
728
        value = os.pathsep.join(['valid', 'in-valid'])
 
729
        self.assertEqual(['valid'], self._get_names(value))
 
730
        self.assertContainsRe(self.get_log(),
 
731
            r"Invalid name 'in-valid' in BRZ_DISABLE_PLUGINS=" + repr(value))
 
732
 
 
733
 
 
734
class TestEnvPluginsAt(tests.TestCase):
 
735
 
 
736
    def _get_paths(self, env_value):
 
737
        os.environ['BRZ_PLUGINS_AT'] = env_value
 
738
        return plugin._env_plugins_at()
 
739
 
 
740
    def test_empty(self):
 
741
        self.assertEqual([], plugin._env_plugins_at())
 
742
        self.assertEqual([], self._get_paths(''))
 
743
 
 
744
    def test_one_path(self):
 
745
        self.assertEqual([('b', 'man')], self._get_paths('b@man'))
 
746
 
 
747
    def test_multiple(self):
 
748
        self.assertEqual(
 
749
            [('tools', 'bzr-tools'), ('p', 'play.py')],
 
750
            self._get_paths(os.pathsep.join(('tools@bzr-tools', 'p@play.py'))))
 
751
 
 
752
    def test_many_at(self):
 
753
        self.assertEqual(
 
754
            [('church', 'StMichael@Plea@Norwich')],
 
755
            self._get_paths('church@StMichael@Plea@Norwich'))
 
756
 
 
757
    def test_only_py(self):
 
758
        self.assertEqual([('test', './test.py')], self._get_paths('./test.py'))
 
759
 
 
760
    def test_only_package(self):
 
761
        self.assertEqual([('py', '/opt/b/py')], self._get_paths('/opt/b/py'))
 
762
 
 
763
    def test_bad_name(self):
 
764
        self.assertEqual([], self._get_paths('/usr/local/bzr-git'))
 
765
        self.assertContainsRe(self.get_log(),
 
766
            r"Invalid name 'bzr-git' in BRZ_PLUGINS_AT='/usr/local/bzr-git'")
 
767
 
 
768
 
 
769
class TestLoadPluginAt(BaseTestPlugins):
 
770
 
 
771
    def setUp(self):
 
772
        super(TestLoadPluginAt, self).setUp()
 
773
        # Create the same plugin in two directories
 
774
        self.create_plugin_package('test_foo', dir='non-standard-dir')
 
775
        # The "normal" directory, we use 'standard' instead of 'plugins' to
 
776
        # avoid depending on the precise naming.
 
777
        self.create_plugin_package('test_foo', dir='standard/test_foo')
 
778
 
 
779
    def assertTestFooLoadedFrom(self, path):
 
780
        self.assertPluginKnown('test_foo')
 
781
        self.assertDocstring('This is the doc for test_foo',
 
782
                             self.module.test_foo)
 
783
        self.assertEqual(path, self.module.test_foo.dir_source)
 
784
 
 
785
    def test_regular_load(self):
 
786
        self.load_with_paths(['standard'])
 
787
        self.assertTestFooLoadedFrom('standard/test_foo')
 
788
 
 
789
    def test_import(self):
 
790
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
 
791
        self.update_module_paths(['standard'])
 
792
        import breezy.testingplugins.test_foo
 
793
        self.assertTestFooLoadedFrom('non-standard-dir')
 
794
 
 
795
    def test_loading(self):
 
796
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
 
797
        self.load_with_paths(['standard'])
 
798
        self.assertTestFooLoadedFrom('non-standard-dir')
 
799
 
 
800
    def test_loading_other_name(self):
 
801
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
 
802
        os.rename('standard/test_foo', 'standard/test_bar')
 
803
        self.load_with_paths(['standard'])
 
804
        self.assertTestFooLoadedFrom('non-standard-dir')
 
805
 
 
806
    def test_compiled_loaded(self):
 
807
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
 
808
        self.load_with_paths(['standard'])
 
809
        self.assertTestFooLoadedFrom('non-standard-dir')
 
810
        self.assertIsSameRealPath('non-standard-dir/__init__.py',
 
811
                                  self.module.test_foo.__file__)
 
812
 
 
813
        # Try importing again now that the source has been compiled
 
814
        os.remove('non-standard-dir/__init__.py')
 
815
        self.promote_cache('non-standard-dir')
 
816
        self.reset()
 
817
        self.load_with_paths(['standard'])
 
818
        self.assertTestFooLoadedFrom('non-standard-dir')
 
819
        suffix = plugin.COMPILED_EXT
 
820
        self.assertIsSameRealPath('non-standard-dir/__init__' + suffix,
 
821
                                  self.module.test_foo.__file__)
 
822
 
 
823
    def test_submodule_loading(self):
 
824
        # We create an additional directory under the one for test_foo
 
825
        self.create_plugin_package('test_bar', dir='non-standard-dir/test_bar')
 
826
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
 
827
        self.update_module_paths(['standard'])
 
828
        import breezy.testingplugins.test_foo
 
829
        self.assertEqual(self.module_prefix + 'test_foo',
 
830
                         self.module.test_foo.__package__)
 
831
        import breezy.testingplugins.test_foo.test_bar
 
832
        self.assertIsSameRealPath('non-standard-dir/test_bar/__init__.py',
 
833
                                  self.module.test_foo.test_bar.__file__)
 
834
 
 
835
    def test_relative_submodule_loading(self):
 
836
        self.create_plugin_package('test_foo', dir='another-dir', source='''
 
837
from . import test_bar
 
838
''')
 
839
        # We create an additional directory under the one for test_foo
 
840
        self.create_plugin_package('test_bar', dir='another-dir/test_bar')
 
841
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@another-dir')
 
842
        self.update_module_paths(['standard'])
 
843
        import breezy.testingplugins.test_foo
 
844
        self.assertEqual(self.module_prefix + 'test_foo',
 
845
                         self.module.test_foo.__package__)
 
846
        self.assertIsSameRealPath('another-dir/test_bar/__init__.py',
 
847
                                  self.module.test_foo.test_bar.__file__)
 
848
 
 
849
    def test_loading_from___init__only(self):
 
850
        # We rename the existing __init__.py file to ensure that we don't load
 
851
        # a random file
 
852
        init = 'non-standard-dir/__init__.py'
 
853
        random = 'non-standard-dir/setup.py'
 
854
        os.rename(init, random)
 
855
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
 
856
        self.load_with_paths(['standard'])
 
857
        self.assertPluginUnknown('test_foo')
 
858
 
 
859
    def test_loading_from_specific_file(self):
 
860
        plugin_dir = 'non-standard-dir'
 
861
        plugin_file_name = 'iamtestfoo.py'
 
862
        plugin_path = osutils.pathjoin(plugin_dir, plugin_file_name)
 
863
        source = '''\
 
864
"""This is the doc for %s"""
 
865
dir_source = '%s'
 
866
''' % ('test_foo', plugin_path)
 
867
        self.create_plugin('test_foo', source=source,
 
868
                           dir=plugin_dir, file_name=plugin_file_name)
 
869
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@%s' % plugin_path)
 
870
        self.load_with_paths(['standard'])
 
871
        self.assertTestFooLoadedFrom(plugin_path)
 
872
 
 
873
 
 
874
class TestDescribePlugins(BaseTestPlugins):
 
875
 
 
876
    def test_describe_plugins(self):
 
877
        class DummyModule(object):
 
878
            __doc__ = 'Hi there'
 
879
        class DummyPlugin(object):
 
880
            __version__ = '0.1.0'
 
881
            module = DummyModule()
 
882
        self.plugin_warnings = {'bad': ['Failed to load (just testing)']}
 
883
        self.plugins = {'good': DummyPlugin()}
 
884
        self.assertEqual("""\
 
885
bad (failed to load)
 
886
  ** Failed to load (just testing)
 
887
 
 
888
good 0.1.0
 
889
  Hi there
 
890
 
 
891
""", ''.join(plugin.describe_plugins(state=self)))