/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: 2017-06-05 22:01:28 UTC
  • mfrom: (6658 work)
  • mto: This revision was merged to the branch mainline in revision 6666.
  • Revision ID: jelmer@jelmer.uk-20170605220128-xurjonb8cjf5o52j
Merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2012, 2016 Canonical Ltd
 
1
# Copyright (C) 2005-2012, 2016 Canonical Ltd, 2017 Breezy developers
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
"""Tests for plugins"""
18
18
 
19
 
# XXX: There are no plugin tests at the moment because the plugin module
20
 
# affects the global state of the process.  See breezy/plugins.py for more
21
 
# comments.
22
 
 
 
19
import imp
 
20
import importlib
23
21
import logging
24
22
import os
25
23
import sys
29
27
    errors,
30
28
    osutils,
31
29
    plugin,
32
 
    plugins,
33
30
    tests,
34
31
    trace,
35
32
    )
36
33
from ..sixish import (
37
 
    BytesIO,
 
34
    PY3,
 
35
    StringIO,
 
36
    viewkeys,
38
37
    )
39
38
 
40
39
 
41
40
# TODO: Write a test for plugin decoration of commands.
42
41
 
 
42
invalidate_caches = getattr(importlib, "invalidate_caches", lambda: None)
 
43
 
 
44
 
43
45
class BaseTestPlugins(tests.TestCaseInTempDir):
 
46
    """TestCase that isolates plugin imports and cleans up on completion."""
 
47
 
 
48
    def setUp(self):
 
49
        super(BaseTestPlugins, self).setUp()
 
50
        self.module_name = "breezy.testingplugins"
 
51
        self.module_prefix = self.module_name + "."
 
52
        self.module = imp.new_module(self.module_name)
 
53
 
 
54
        self.overrideAttr(plugin, "_MODULE_PREFIX", self.module_prefix)
 
55
        self.overrideAttr(breezy, "testingplugins", self.module)
 
56
 
 
57
        sys.modules[self.module_name] = self.module
 
58
        self.addCleanup(self._unregister_all)
 
59
        self.addCleanup(self._unregister_finder)
 
60
 
 
61
        invalidate_caches()
 
62
 
 
63
    def reset(self):
 
64
        """Remove all global testing state and clean up module."""
 
65
        # GZ 2017-06-02: Ideally don't do this, write new test or generate
 
66
        # bytecode by other mechanism.
 
67
        self.log("resetting plugin testing context")
 
68
        self._unregister_all()
 
69
        self._unregister_finder()
 
70
        sys.modules[self.module_name] = self.module
 
71
        for name in list(self.module.__dict__):
 
72
            if name[:2] != '__':
 
73
                delattr(self.module, name)
 
74
        invalidate_caches()
 
75
        self.plugins = None
 
76
 
 
77
    def update_module_paths(self, paths):
 
78
        paths = plugin.extend_path(paths, self.module_name)
 
79
        self.module.__path__ = paths
 
80
        self.log("using %r", paths)
 
81
        return paths
 
82
 
 
83
    def load_with_paths(self, paths):
 
84
        self.log("loading plugins!")
 
85
        plugin.load_plugins(self.update_module_paths(paths), state=self)
44
86
 
45
87
    def create_plugin(self, name, source=None, dir='.', file_name=None):
46
88
        if source is None:
51
93
            file_name = name + '.py'
52
94
        # 'source' must not fail to load
53
95
        path = osutils.pathjoin(dir, file_name)
54
 
        f = open(path, 'w')
55
 
        self.addCleanup(os.unlink, path)
56
 
        try:
 
96
        with open(path, 'w') as f:
57
97
            f.write(source + '\n')
58
 
        finally:
59
 
            f.close()
60
98
 
61
99
    def create_plugin_package(self, name, dir=None, source=None):
62
100
        if dir is None:
67
105
dir_source = '%s'
68
106
''' % (name, dir)
69
107
        os.makedirs(dir)
70
 
        def cleanup():
71
 
            # Workaround lazy import random? madness
72
 
            osutils.rmtree(dir)
73
 
        self.addCleanup(cleanup)
74
108
        self.create_plugin(name, source, dir,
75
109
                           file_name='__init__.py')
76
110
 
77
 
    def _unregister_plugin(self, name):
78
 
        """Remove the plugin from sys.modules and the breezy namespace."""
79
 
        py_name = 'breezy.plugins.%s' % name
80
 
        if py_name in sys.modules:
81
 
            del sys.modules[py_name]
82
 
        if getattr(breezy.plugins, name, None) is not None:
83
 
            delattr(breezy.plugins, name)
84
 
 
85
 
    def _unregister_plugin_submodule(self, plugin_name, submodule_name):
86
 
        """Remove the submodule from sys.modules and the breezy namespace."""
87
 
        py_name = 'breezy.plugins.%s.%s' % (plugin_name, submodule_name)
88
 
        if py_name in sys.modules:
89
 
            del sys.modules[py_name]
90
 
        plugin = getattr(breezy.plugins, plugin_name, None)
91
 
        if plugin is not None:
92
 
            if getattr(plugin, submodule_name, None) is not None:
93
 
                delattr(plugin, submodule_name)
 
111
    def promote_cache(self, directory):
 
112
        """Move bytecode files out of __pycache__ in given directory."""
 
113
        cache_dir = os.path.join(directory, '__pycache__')
 
114
        if os.path.isdir(cache_dir):
 
115
            for name in os.listdir(cache_dir):
 
116
                magicless_name = '.'.join(name.split('.')[0::name.count('.')])
 
117
                rel = osutils.relpath(self.test_dir, cache_dir)
 
118
                self.log("moving %s in %s to %s", name, rel, magicless_name)
 
119
                os.rename(os.path.join(cache_dir, name),
 
120
                    os.path.join(directory, magicless_name))
 
121
 
 
122
    def _unregister_finder(self):
 
123
        """Removes any test copies of _PluginsAtFinder from sys.meta_path."""
 
124
        idx = len(sys.meta_path)
 
125
        while idx:
 
126
            idx -= 1
 
127
            finder = sys.meta_path[idx]
 
128
            if getattr(finder, "prefix", "") == self.module_prefix:
 
129
                self.log("removed %r from sys.meta_path", finder)
 
130
                sys.meta_path.pop(idx)
 
131
 
 
132
    def _unregister_all(self):
 
133
        """Remove all plugins in the test namespace from sys.modules."""
 
134
        for name in list(sys.modules):
 
135
            if name.startswith(self.module_prefix) or name == self.module_name:
 
136
                self.log("removed %s from sys.modules", name)
 
137
                del sys.modules[name]
 
138
 
 
139
    def assertPluginModules(self, plugin_dict):
 
140
        self.assertEqual(
 
141
            dict((k[len(self.module_prefix):], sys.modules[k])
 
142
                for k in sys.modules if k.startswith(self.module_prefix)),
 
143
            plugin_dict)
94
144
 
95
145
    def assertPluginUnknown(self, name):
96
 
        self.assertFalse(getattr(breezy.plugins, name, None) is not None)
97
 
        self.assertFalse('breezy.plugins.%s' % name in sys.modules)
 
146
        self.assertTrue(getattr(self.module, name, None) is None)
 
147
        self.assertFalse(self.module_prefix + name in sys.modules)
98
148
 
99
149
    def assertPluginKnown(self, name):
100
 
        self.assertTrue(getattr(breezy.plugins, name, None) is not None)
101
 
        self.assertTrue('breezy.plugins.%s' % name in sys.modules)
 
150
        self.assertTrue(getattr(self.module, name, None) is not None)
 
151
        self.assertTrue(self.module_prefix + name in sys.modules)
102
152
 
103
153
 
104
154
class TestLoadingPlugins(BaseTestPlugins):
125
175
        template = ("from breezy.tests.test_plugins import TestLoadingPlugins\n"
126
176
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
127
177
 
128
 
        outfile = open(os.path.join('first', 'plugin.py'), 'w')
129
 
        try:
 
178
        with open(os.path.join('first', 'plugin.py'), 'w') as outfile:
130
179
            outfile.write(template % (tempattribute, 'first'))
131
180
            outfile.write('\n')
132
 
        finally:
133
 
            outfile.close()
134
181
 
135
 
        outfile = open(os.path.join('second', 'plugin.py'), 'w')
136
 
        try:
 
182
        with open(os.path.join('second', 'plugin.py'), 'w') as outfile:
137
183
            outfile.write(template % (tempattribute, 'second'))
138
184
            outfile.write('\n')
139
 
        finally:
140
 
            outfile.close()
141
185
 
142
186
        try:
143
 
            breezy.plugin.load_from_path(['first', 'second'])
 
187
            self.load_with_paths(['first', 'second'])
144
188
            self.assertEqual(['first'], self.activeattributes[tempattribute])
145
189
        finally:
146
 
            # remove the plugin 'plugin'
147
190
            del self.activeattributes[tempattribute]
148
 
            self._unregister_plugin('plugin')
149
 
        self.assertPluginUnknown('plugin')
150
191
 
151
192
    def test_plugins_from_different_dirs_can_demand_load(self):
152
193
        self.assertFalse('breezy.plugins.pluginone' in sys.modules)
172
213
        template = ("from breezy.tests.test_plugins import TestLoadingPlugins\n"
173
214
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
174
215
 
175
 
        outfile = open(os.path.join('first', 'pluginone.py'), 'w')
176
 
        try:
 
216
        with open(os.path.join('first', 'pluginone.py'), 'w') as outfile:
177
217
            outfile.write(template % (tempattribute, 'first'))
178
218
            outfile.write('\n')
179
 
        finally:
180
 
            outfile.close()
181
219
 
182
 
        outfile = open(os.path.join('second', 'plugintwo.py'), 'w')
183
 
        try:
 
220
        with open(os.path.join('second', 'plugintwo.py'), 'w') as outfile:
184
221
            outfile.write(template % (tempattribute, 'second'))
185
222
            outfile.write('\n')
186
 
        finally:
187
 
            outfile.close()
188
223
 
189
 
        oldpath = breezy.plugins.__path__
190
224
        try:
191
 
            self.assertFalse('breezy.plugins.pluginone' in sys.modules)
192
 
            self.assertFalse('breezy.plugins.plugintwo' in sys.modules)
193
 
            breezy.plugins.__path__ = ['first', 'second']
194
 
            exec("import breezy.plugins.pluginone")
 
225
            self.assertPluginUnknown('pluginone')
 
226
            self.assertPluginUnknown('plugintwo')
 
227
            self.update_module_paths(['first', 'second'])
 
228
            exec("import %spluginone" % self.module_prefix)
195
229
            self.assertEqual(['first'], self.activeattributes[tempattribute])
196
 
            exec("import breezy.plugins.plugintwo")
 
230
            exec("import %splugintwo" % self.module_prefix)
197
231
            self.assertEqual(['first', 'second'],
198
232
                self.activeattributes[tempattribute])
199
233
        finally:
200
 
            # remove the plugin 'plugin'
201
234
            del self.activeattributes[tempattribute]
202
 
            self._unregister_plugin('pluginone')
203
 
            self._unregister_plugin('plugintwo')
204
 
        self.assertPluginUnknown('pluginone')
205
 
        self.assertPluginUnknown('plugintwo')
206
235
 
207
236
    def test_plugins_can_load_from_directory_with_trailing_slash(self):
208
237
        # This test tests that a plugin can load from a directory when the
224
253
        template = ("from breezy.tests.test_plugins import TestLoadingPlugins\n"
225
254
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
226
255
 
227
 
        outfile = open(os.path.join('plugin_test', 'ts_plugin.py'), 'w')
228
 
        try:
 
256
        with open(os.path.join('plugin_test', 'ts_plugin.py'), 'w') as outfile:
229
257
            outfile.write(template % (tempattribute, 'plugin'))
230
258
            outfile.write('\n')
231
 
        finally:
232
 
            outfile.close()
233
259
 
234
260
        try:
235
 
            breezy.plugin.load_from_path(['plugin_test'+os.sep])
 
261
            self.load_with_paths(['plugin_test'+os.sep])
236
262
            self.assertEqual(['plugin'], self.activeattributes[tempattribute])
 
263
            self.assertPluginKnown('ts_plugin')
237
264
        finally:
238
265
            del self.activeattributes[tempattribute]
239
 
            self._unregister_plugin('ts_plugin')
240
 
        self.assertPluginUnknown('ts_plugin')
241
266
 
242
267
    def load_and_capture(self, name):
243
268
        """Load plugins from '.' capturing the output.
246
271
        :return: A string with the log from the plugin loading call.
247
272
        """
248
273
        # Capture output
249
 
        stream = BytesIO()
 
274
        stream = StringIO()
250
275
        try:
251
276
            handler = logging.StreamHandler(stream)
252
277
            log = logging.getLogger('brz')
253
278
            log.addHandler(handler)
254
279
            try:
255
 
                try:
256
 
                    breezy.plugin.load_from_path(['.'])
257
 
                finally:
258
 
                    if 'breezy.plugins.%s' % name in sys.modules:
259
 
                        del sys.modules['breezy.plugins.%s' % name]
260
 
                    if getattr(breezy.plugins, name, None):
261
 
                        delattr(breezy.plugins, name)
 
280
                self.load_with_paths(['.'])
262
281
            finally:
263
282
                # Stop capturing output
264
283
                handler.flush()
270
289
 
271
290
    def test_plugin_with_bad_api_version_reports(self):
272
291
        """Try loading a plugin that requests an unsupported api.
273
 
        
 
292
 
274
293
        Observe that it records the problem but doesn't complain on stderr.
275
294
 
276
295
        See https://bugs.launchpad.net/bzr/+bug/704195
277
296
        """
278
 
        self.overrideAttr(plugin, 'plugin_warnings', {})
279
297
        name = 'wants100.py'
280
 
        f = file(name, 'w')
281
 
        try:
 
298
        with open(name, 'w') as f:
282
299
            f.write("import breezy.api\n"
283
300
                "breezy.api.require_any_api(breezy, [(1, 0, 0)])\n")
284
 
        finally:
285
 
            f.close()
286
301
        log = self.load_and_capture(name)
287
302
        self.assertNotContainsRe(log,
288
303
            r"It requested API version")
289
 
        self.assertEqual(
290
 
            ['wants100'],
291
 
            plugin.plugin_warnings.keys())
 
304
        self.assertEqual({'wants100'}, viewkeys(self.plugin_warnings))
292
305
        self.assertContainsRe(
293
 
            plugin.plugin_warnings['wants100'][0],
 
306
            self.plugin_warnings['wants100'][0],
294
307
            r"It requested API version")
295
308
 
296
309
    def test_plugin_with_bad_name_does_not_load(self):
297
310
        # The file name here invalid for a python module.
298
311
        name = 'brz-bad plugin-name..py'
299
 
        file(name, 'w').close()
 
312
        open(name, 'w').close()
300
313
        log = self.load_and_capture(name)
301
314
        self.assertContainsRe(log,
302
315
            r"Unable to load 'brz-bad plugin-name\.' in '\.' as a plugin "
311
324
        # check the plugin is not loaded already
312
325
        self.assertPluginUnknown('plugin')
313
326
        # write a plugin that _cannot_ fail to load.
314
 
        with file('plugin.py', 'w') as f: f.write(source + '\n')
315
 
        self.addCleanup(self.teardown_plugin)
316
 
        plugin.load_from_path(['.'])
317
 
 
318
 
    def teardown_plugin(self):
319
 
        self._unregister_plugin('plugin')
320
 
        self.assertPluginUnknown('plugin')
 
327
        with open('plugin.py', 'w') as f: f.write(source + '\n')
 
328
        self.load_with_paths(['.'])
321
329
 
322
330
    def test_plugin_appears_in_plugins(self):
323
331
        self.setup_plugin()
324
332
        self.assertPluginKnown('plugin')
325
 
        p = plugin.plugins()['plugin']
 
333
        p = self.plugins['plugin']
326
334
        self.assertIsInstance(p, breezy.plugin.PlugIn)
327
 
        self.assertEqual(p.module, plugins.plugin)
 
335
        self.assertIs(p.module, sys.modules[self.module_prefix + 'plugin'])
328
336
 
329
337
    def test_trivial_plugin_get_path(self):
330
338
        self.setup_plugin()
331
 
        p = plugin.plugins()['plugin']
 
339
        p = self.plugins['plugin']
332
340
        plugin_path = self.test_dir + '/plugin.py'
333
341
        self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
334
342
 
335
343
    def test_plugin_get_path_py_not_pyc(self):
336
344
        # first import creates plugin.pyc
337
345
        self.setup_plugin()
338
 
        self.teardown_plugin()
339
 
        plugin.load_from_path(['.']) # import plugin.pyc
 
346
        self.promote_cache(self.test_dir)
 
347
        self.reset()
 
348
        self.load_with_paths(['.']) # import plugin.pyc
340
349
        p = plugin.plugins()['plugin']
341
350
        plugin_path = self.test_dir + '/plugin.py'
342
351
        self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
344
353
    def test_plugin_get_path_pyc_only(self):
345
354
        # first import creates plugin.pyc (or plugin.pyo depending on __debug__)
346
355
        self.setup_plugin()
347
 
        self.teardown_plugin()
348
356
        os.unlink(self.test_dir + '/plugin.py')
349
 
        plugin.load_from_path(['.']) # import plugin.pyc (or .pyo)
 
357
        self.promote_cache(self.test_dir)
 
358
        self.reset()
 
359
        self.load_with_paths(['.']) # import plugin.pyc (or .pyo)
350
360
        p = plugin.plugins()['plugin']
351
 
        if __debug__:
352
 
            plugin_path = self.test_dir + '/plugin.pyc'
353
 
        else:
354
 
            plugin_path = self.test_dir + '/plugin.pyo'
 
361
        plugin_path = self.test_dir + '/plugin' + plugin.COMPILED_EXT
355
362
        self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
356
363
 
357
364
    def test_no_test_suite_gives_None_for_test_suite(self):
457
464
        self.assertEqual("1.2.3.2", plugin.__version__)
458
465
 
459
466
 
460
 
class TestPluginHelp(tests.TestCaseInTempDir):
 
467
# GZ 2017-06-02: Move this suite to blackbox, as it's what it actually is.
 
468
class TestPluginHelp(BaseTestPlugins):
461
469
 
462
470
    def split_help_commands(self):
463
471
        help = {}
484
492
            else:
485
493
                self.assertNotContainsRe(help, 'plugin "[^"]*"')
486
494
 
487
 
            if cmd_name in help_commands.keys():
 
495
            if cmd_name in help_commands:
488
496
                # some commands are hidden
489
497
                help = help_commands[cmd_name]
490
498
                self.assertNotContainsRe(help, 'plugin "[^"]*"')
492
500
    def test_plugin_help_shows_plugin(self):
493
501
        # Create a test plugin
494
502
        os.mkdir('plugin_test')
495
 
        f = open(osutils.pathjoin('plugin_test', 'myplug.py'), 'w')
496
 
        f.write("""\
497
 
from breezy import commands
498
 
class cmd_myplug(commands.Command):
499
 
    __doc__ = '''Just a simple test plugin.'''
500
 
    aliases = ['mplg']
501
 
    def run(self):
502
 
        print 'Hello from my plugin'
503
 
 
504
 
"""
505
 
)
506
 
        f.close()
507
 
 
508
 
        try:
509
 
            # Check its help
510
 
            breezy.plugin.load_from_path(['plugin_test'])
511
 
            breezy.commands.register_command( breezy.plugins.myplug.cmd_myplug)
512
 
            help = self.run_bzr('help myplug')[0]
513
 
            self.assertContainsRe(help, 'plugin "myplug"')
514
 
            help = self.split_help_commands()['myplug']
515
 
            self.assertContainsRe(help, '\[myplug\]')
516
 
        finally:
517
 
            # unregister command
518
 
            if 'myplug' in breezy.commands.plugin_cmds:
519
 
                breezy.commands.plugin_cmds.remove('myplug')
520
 
            # remove the plugin 'myplug'
521
 
            if getattr(breezy.plugins, 'myplug', None):
522
 
                delattr(breezy.plugins, 'myplug')
 
503
        source = (
 
504
            "from breezy import commands\n"
 
505
            "class cmd_myplug(commands.Command):\n"
 
506
            "    __doc__ = '''Just a simple test plugin.'''\n"
 
507
            "    aliases = ['mplg']\n"
 
508
            "    def run(self):\n"
 
509
            "        print ('Hello from my plugin')\n"
 
510
        )
 
511
        self.create_plugin('myplug', source, 'plugin_test')
 
512
 
 
513
        # Check its help
 
514
        self.load_with_paths(['plugin_test'])
 
515
        myplug = self.plugins['myplug'].module
 
516
        breezy.commands.register_command(myplug.cmd_myplug)
 
517
        self.addCleanup(breezy.commands.plugin_cmds.remove, 'myplug')
 
518
        help = self.run_bzr('help myplug')[0]
 
519
        self.assertContainsRe(help, 'plugin "myplug"')
 
520
        help = self.split_help_commands()['myplug']
 
521
        self.assertContainsRe(help, '\[myplug\]')
523
522
 
524
523
 
525
524
class TestHelpIndex(tests.TestCase):
631
630
        self.assertEqual('foo_bar', topic.get_help_topic())
632
631
 
633
632
 
634
 
class TestLoadFromPath(tests.TestCaseInTempDir):
635
 
 
636
 
    def setUp(self):
637
 
        super(TestLoadFromPath, self).setUp()
638
 
        # Change breezy.plugin to think no plugins have been loaded yet.
639
 
        self.overrideAttr(breezy.plugins, '__path__', [])
640
 
        self.overrideAttr(plugin, '_loaded', False)
641
 
 
642
 
        # Monkey-patch load_from_path to stop it from actually loading anything.
643
 
        self.overrideAttr(plugin, 'load_from_path', lambda dirs: None)
644
 
 
645
 
    def test_set_plugins_path_with_args(self):
646
 
        plugin.set_plugins_path(['a', 'b'])
647
 
        self.assertEqual(['a', 'b'], breezy.plugins.__path__)
648
 
 
649
 
    def test_set_plugins_path_defaults(self):
650
 
        plugin.set_plugins_path()
651
 
        self.assertEqual(plugin.get_standard_plugins_path(),
652
 
                         breezy.plugins.__path__)
653
 
 
654
 
    def test_get_standard_plugins_path(self):
655
 
        path = plugin.get_standard_plugins_path()
656
 
        for directory in path:
657
 
            self.assertNotContainsRe(directory, r'\\/$')
658
 
        try:
659
 
            from distutils.sysconfig import get_python_lib
660
 
        except ImportError:
661
 
            pass
662
 
        else:
663
 
            if sys.platform != 'win32':
664
 
                python_lib = get_python_lib()
665
 
                for directory in path:
666
 
                    if directory.startswith(python_lib):
667
 
                        break
668
 
                else:
669
 
                    self.fail('No path to global plugins')
670
 
 
671
 
    def test_get_standard_plugins_path_env(self):
672
 
        self.overrideEnv('BRZ_PLUGIN_PATH', 'foo/')
673
 
        path = plugin.get_standard_plugins_path()
674
 
        for directory in path:
675
 
            self.assertNotContainsRe(directory, r'\\/$')
676
 
 
677
 
    def test_load_plugins(self):
678
 
        plugin.load_plugins(['.'])
679
 
        self.assertEqual(breezy.plugins.__path__, ['.'])
680
 
        # subsequent loads are no-ops
681
 
        plugin.load_plugins(['foo'])
682
 
        self.assertEqual(breezy.plugins.__path__, ['.'])
683
 
 
684
 
    def test_load_plugins_default(self):
685
 
        plugin.load_plugins()
686
 
        path = plugin.get_standard_plugins_path()
687
 
        self.assertEqual(path, breezy.plugins.__path__)
688
 
 
689
 
 
690
633
class TestEnvPluginPath(tests.TestCase):
691
634
 
692
 
    def setUp(self):
693
 
        super(TestEnvPluginPath, self).setUp()
694
 
        self.overrideAttr(plugin, 'DEFAULT_PLUGIN_PATH', None)
695
 
 
696
 
        self.user = plugin.get_user_plugin_path()
697
 
        self.site = plugin.get_site_plugin_path()
698
 
        self.core = plugin.get_core_plugin_path()
699
 
 
700
 
    def _list2paths(self, *args):
701
 
        paths = []
702
 
        for p in args:
703
 
            plugin._append_new_path(paths, p)
704
 
        return paths
705
 
 
706
 
    def _set_path(self, *args):
707
 
        path = os.pathsep.join(self._list2paths(*args))
708
 
        self.overrideEnv('BRZ_PLUGIN_PATH', path)
 
635
    user = "USER"
 
636
    core = "CORE"
 
637
    site = "SITE"
709
638
 
710
639
    def check_path(self, expected_dirs, setting_dirs):
711
 
        if setting_dirs:
712
 
            self._set_path(*setting_dirs)
713
 
        actual = plugin.get_standard_plugins_path()
714
 
        self.assertEqual(self._list2paths(*expected_dirs), actual)
 
640
        if setting_dirs is None:
 
641
            del os.environ['BRZ_PLUGIN_PATH']
 
642
        else:
 
643
            os.environ['BRZ_PLUGIN_PATH'] = os.pathsep.join(setting_dirs)
 
644
        actual = [(p if t == 'path' else t.upper())
 
645
            for p, t in plugin._env_plugin_path()]
 
646
        self.assertEqual(expected_dirs, actual)
715
647
 
716
648
    def test_default(self):
717
649
        self.check_path([self.user, self.core, self.site],
783
715
 
784
716
class TestDisablePlugin(BaseTestPlugins):
785
717
 
786
 
    def setUp(self):
787
 
        super(TestDisablePlugin, self).setUp()
788
 
        self.create_plugin_package('test_foo')
789
 
        # Make sure we don't pollute the plugins namespace
790
 
        self.overrideAttr(plugins, '__path__')
791
 
        # Be paranoid in case a test fail
792
 
        self.addCleanup(self._unregister_plugin, 'test_foo')
793
 
 
794
718
    def test_cannot_import(self):
795
 
        self.overrideEnv('BRZ_DISABLE_PLUGINS', 'test_foo')
796
 
        plugin.set_plugins_path(['.'])
 
719
        self.create_plugin_package('works')
 
720
        self.create_plugin_package('fails')
 
721
        self.overrideEnv('BRZ_DISABLE_PLUGINS', 'fails')
 
722
        self.update_module_paths(["."])
 
723
        import breezy.testingplugins.works as works
797
724
        try:
798
 
            import breezy.plugins.test_foo
 
725
            import breezy.testingplugins.fails as fails
799
726
        except ImportError:
800
727
            pass
801
 
        self.assertPluginUnknown('test_foo')
802
 
 
803
 
    def test_regular_load(self):
804
 
        self.overrideAttr(plugin, '_loaded', False)
805
 
        plugin.load_plugins(['.'])
806
 
        self.assertPluginKnown('test_foo')
807
 
        self.assertDocstring("This is the doc for test_foo",
808
 
                             breezy.plugins.test_foo)
809
 
 
810
 
    def test_not_loaded(self):
811
 
        self.warnings = []
812
 
        def captured_warning(*args, **kwargs):
813
 
            self.warnings.append((args, kwargs))
814
 
        self.overrideAttr(trace, 'warning', captured_warning)
815
 
        # Reset the flag that protect against double loading
816
 
        self.overrideAttr(plugin, '_loaded', False)
817
 
        self.overrideEnv('BRZ_DISABLE_PLUGINS', 'test_foo')
818
 
        plugin.load_plugins(['.'])
819
 
        self.assertPluginUnknown('test_foo')
820
 
        # Make sure we don't warn about the plugin ImportError since this has
821
 
        # been *requested* by the user.
822
 
        self.assertLength(0, self.warnings)
823
 
 
824
 
 
825
 
 
826
 
class TestLoadPluginAtSyntax(tests.TestCase):
827
 
 
828
 
    def _get_paths(self, paths):
829
 
        return plugin._get_specific_plugin_paths(paths)
830
 
 
831
 
    def test_empty(self):
832
 
        self.assertEqual([], self._get_paths(None))
 
728
        else:
 
729
            self.fail("Loaded blocked plugin: " + repr(fails))
 
730
        self.assertPluginModules({'fails': None, 'works': works})
 
731
 
 
732
    def test_partial_imports(self):
 
733
        self.create_plugin('good')
 
734
        self.create_plugin('bad')
 
735
        self.create_plugin_package('ugly')
 
736
        self.overrideEnv('BRZ_DISABLE_PLUGINS', 'bad:ugly')
 
737
        self.load_with_paths(['.'])
 
738
        self.assertEqual({'good'}, viewkeys(self.plugins))
 
739
        self.assertPluginModules({
 
740
            'good': self.plugins['good'].module,
 
741
            'bad': None,
 
742
            'ugly': None,
 
743
        })
 
744
        # Ensure there are no warnings about plugins not being imported as
 
745
        # the user has explictly requested they be disabled.
 
746
        self.assertNotContainsRe(self.get_log(), r"Unable to load plugin")
 
747
 
 
748
 
 
749
class TestEnvDisablePlugins(tests.TestCase):
 
750
 
 
751
    def _get_names(self, env_value):
 
752
        os.environ['BRZ_DISABLE_PLUGINS'] = env_value
 
753
        return plugin._env_disable_plugins()
 
754
 
 
755
    def test_unset(self):
 
756
        self.assertEqual([], plugin._env_disable_plugins())
 
757
 
 
758
    def test_empty(self):
 
759
        self.assertEqual([], self._get_names(''))
 
760
 
 
761
    def test_single(self):
 
762
        self.assertEqual(['single'], self._get_names('single'))
 
763
 
 
764
    def test_multi(self):
 
765
        expected = ['one', 'two']
 
766
        self.assertEqual(expected, self._get_names(os.pathsep.join(expected)))
 
767
 
 
768
    def test_mixed(self):
 
769
        value = os.pathsep.join(['valid', 'in-valid'])
 
770
        self.assertEqual(['valid'], self._get_names(value))
 
771
        self.assertContainsRe(self.get_log(),
 
772
            r"Invalid name 'in-valid' in BRZ_DISABLE_PLUGINS=" + repr(value))
 
773
 
 
774
 
 
775
class TestEnvPluginsAt(tests.TestCase):
 
776
 
 
777
    def _get_paths(self, env_value):
 
778
        os.environ['BRZ_PLUGINS_AT'] = env_value
 
779
        return plugin._env_plugins_at()
 
780
 
 
781
    def test_empty(self):
 
782
        self.assertEqual([], plugin._env_plugins_at())
833
783
        self.assertEqual([], self._get_paths(''))
834
784
 
835
785
    def test_one_path(self):
836
786
        self.assertEqual([('b', 'man')], self._get_paths('b@man'))
837
787
 
838
 
    def test_bogus_path(self):
839
 
        # We need a '@'
840
 
        self.assertRaises(errors.BzrCommandError, self._get_paths, 'batman')
841
 
        # Too much '@' isn't good either
842
 
        self.assertRaises(errors.BzrCommandError, self._get_paths,
843
 
                          'batman@mobile@cave')
844
 
        # An empty description probably indicates a problem
845
 
        self.assertRaises(errors.BzrCommandError, self._get_paths,
846
 
                          os.pathsep.join(['batman@cave', '', 'robin@mobile']))
 
788
    def test_multiple(self):
 
789
        self.assertEqual(
 
790
            [('tools', 'bzr-tools'), ('p', 'play.py')],
 
791
            self._get_paths(os.pathsep.join(('tools@bzr-tools', 'p@play.py'))))
 
792
 
 
793
    def test_many_at(self):
 
794
        self.assertEqual(
 
795
            [('church', 'StMichael@Plea@Norwich')],
 
796
            self._get_paths('church@StMichael@Plea@Norwich'))
 
797
 
 
798
    def test_only_py(self):
 
799
        self.assertEqual([('test', './test.py')], self._get_paths('./test.py'))
 
800
 
 
801
    def test_only_package(self):
 
802
        self.assertEqual([('py', '/opt/b/py')], self._get_paths('/opt/b/py'))
 
803
 
 
804
    def test_bad_name(self):
 
805
        self.assertEqual([], self._get_paths('/usr/local/bzr-git'))
 
806
        self.assertContainsRe(self.get_log(),
 
807
            r"Invalid name 'bzr-git' in BRZ_PLUGINS_AT='/usr/local/bzr-git'")
847
808
 
848
809
 
849
810
class TestLoadPluginAt(BaseTestPlugins):
850
811
 
851
812
    def setUp(self):
852
813
        super(TestLoadPluginAt, self).setUp()
853
 
        # Make sure we don't pollute the plugins namespace
854
 
        self.overrideAttr(plugins, '__path__')
855
 
        # Reset the flag that protect against double loading
856
 
        self.overrideAttr(plugin, '_loaded', False)
857
814
        # Create the same plugin in two directories
858
815
        self.create_plugin_package('test_foo', dir='non-standard-dir')
859
816
        # The "normal" directory, we use 'standard' instead of 'plugins' to
860
817
        # avoid depending on the precise naming.
861
818
        self.create_plugin_package('test_foo', dir='standard/test_foo')
862
 
        # All the tests will load the 'test_foo' plugin from various locations
863
 
        self.addCleanup(self._unregister_plugin, 'test_foo')
864
 
        # Unfortunately there's global cached state for the specific
865
 
        # registered paths.
866
 
        self.addCleanup(plugin.PluginImporter.reset)
867
819
 
868
820
    def assertTestFooLoadedFrom(self, path):
869
821
        self.assertPluginKnown('test_foo')
870
822
        self.assertDocstring('This is the doc for test_foo',
871
 
                             breezy.plugins.test_foo)
872
 
        self.assertEqual(path, breezy.plugins.test_foo.dir_source)
 
823
                             self.module.test_foo)
 
824
        self.assertEqual(path, self.module.test_foo.dir_source)
873
825
 
874
826
    def test_regular_load(self):
875
 
        plugin.load_plugins(['standard'])
 
827
        self.load_with_paths(['standard'])
876
828
        self.assertTestFooLoadedFrom('standard/test_foo')
877
829
 
878
830
    def test_import(self):
879
831
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
880
 
        plugin.set_plugins_path(['standard'])
881
 
        try:
882
 
            import breezy.plugins.test_foo
883
 
        except ImportError:
884
 
            pass
 
832
        self.update_module_paths(['standard'])
 
833
        import breezy.testingplugins.test_foo
885
834
        self.assertTestFooLoadedFrom('non-standard-dir')
886
835
 
887
836
    def test_loading(self):
888
837
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
889
 
        plugin.load_plugins(['standard'])
 
838
        self.load_with_paths(['standard'])
 
839
        self.assertTestFooLoadedFrom('non-standard-dir')
 
840
 
 
841
    def test_loading_other_name(self):
 
842
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
 
843
        os.rename('standard/test_foo', 'standard/test_bar')
 
844
        self.load_with_paths(['standard'])
890
845
        self.assertTestFooLoadedFrom('non-standard-dir')
891
846
 
892
847
    def test_compiled_loaded(self):
893
848
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
894
 
        plugin.load_plugins(['standard'])
 
849
        self.load_with_paths(['standard'])
895
850
        self.assertTestFooLoadedFrom('non-standard-dir')
896
851
        self.assertIsSameRealPath('non-standard-dir/__init__.py',
897
 
                                  breezy.plugins.test_foo.__file__)
 
852
                                  self.module.test_foo.__file__)
898
853
 
899
854
        # Try importing again now that the source has been compiled
900
 
        self._unregister_plugin('test_foo')
901
 
        plugin._loaded = False
902
 
        plugin.load_plugins(['standard'])
 
855
        os.remove('non-standard-dir/__init__.py')
 
856
        self.promote_cache('non-standard-dir')
 
857
        self.reset()
 
858
        self.load_with_paths(['standard'])
903
859
        self.assertTestFooLoadedFrom('non-standard-dir')
904
 
        if __debug__:
905
 
            suffix = 'pyc'
906
 
        else:
907
 
            suffix = 'pyo'
908
 
        self.assertIsSameRealPath('non-standard-dir/__init__.%s' % suffix,
909
 
                                  breezy.plugins.test_foo.__file__)
 
860
        suffix = plugin.COMPILED_EXT
 
861
        self.assertIsSameRealPath('non-standard-dir/__init__' + suffix,
 
862
                                  self.module.test_foo.__file__)
910
863
 
911
864
    def test_submodule_loading(self):
912
865
        # We create an additional directory under the one for test_foo
913
866
        self.create_plugin_package('test_bar', dir='non-standard-dir/test_bar')
914
 
        self.addCleanup(self._unregister_plugin_submodule,
915
 
                        'test_foo', 'test_bar')
916
867
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
917
 
        plugin.set_plugins_path(['standard'])
918
 
        import breezy.plugins.test_foo
919
 
        self.assertEqual('breezy.plugins.test_foo',
920
 
                         breezy.plugins.test_foo.__package__)
921
 
        import breezy.plugins.test_foo.test_bar
 
868
        self.update_module_paths(['standard'])
 
869
        import breezy.testingplugins.test_foo
 
870
        self.assertEqual(self.module_prefix + 'test_foo',
 
871
                         self.module.test_foo.__package__)
 
872
        import breezy.testingplugins.test_foo.test_bar
922
873
        self.assertIsSameRealPath('non-standard-dir/test_bar/__init__.py',
923
 
                                  breezy.plugins.test_foo.test_bar.__file__)
 
874
                                  self.module.test_foo.test_bar.__file__)
924
875
 
925
876
    def test_relative_submodule_loading(self):
926
877
        self.create_plugin_package('test_foo', dir='another-dir', source='''
927
 
import test_bar
 
878
from . import test_bar
928
879
''')
929
880
        # We create an additional directory under the one for test_foo
930
881
        self.create_plugin_package('test_bar', dir='another-dir/test_bar')
931
 
        self.addCleanup(self._unregister_plugin_submodule,
932
 
                        'test_foo', 'test_bar')
933
882
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@another-dir')
934
 
        plugin.set_plugins_path(['standard'])
935
 
        import breezy.plugins.test_foo
936
 
        self.assertEqual('breezy.plugins.test_foo',
937
 
                         breezy.plugins.test_foo.__package__)
 
883
        self.update_module_paths(['standard'])
 
884
        import breezy.testingplugins.test_foo
 
885
        self.assertEqual(self.module_prefix + 'test_foo',
 
886
                         self.module.test_foo.__package__)
938
887
        self.assertIsSameRealPath('another-dir/test_bar/__init__.py',
939
 
                                  breezy.plugins.test_foo.test_bar.__file__)
 
888
                                  self.module.test_foo.test_bar.__file__)
940
889
 
941
890
    def test_loading_from___init__only(self):
942
891
        # We rename the existing __init__.py file to ensure that we don't load
944
893
        init = 'non-standard-dir/__init__.py'
945
894
        random = 'non-standard-dir/setup.py'
946
895
        os.rename(init, random)
947
 
        self.addCleanup(os.rename, random, init)
948
896
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
949
 
        plugin.load_plugins(['standard'])
 
897
        self.load_with_paths(['standard'])
950
898
        self.assertPluginUnknown('test_foo')
951
899
 
952
900
    def test_loading_from_specific_file(self):
960
908
        self.create_plugin('test_foo', source=source,
961
909
                           dir=plugin_dir, file_name=plugin_file_name)
962
910
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@%s' % plugin_path)
963
 
        plugin.load_plugins(['standard'])
 
911
        self.load_with_paths(['standard'])
964
912
        self.assertTestFooLoadedFrom(plugin_path)
965
913
 
966
914
 
972
920
        class DummyPlugin(object):
973
921
            __version__ = '0.1.0'
974
922
            module = DummyModule()
975
 
        def dummy_plugins():
976
 
            return { 'good': DummyPlugin() }
977
 
        self.overrideAttr(plugin, 'plugin_warnings',
978
 
            {'bad': ['Failed to load (just testing)']})
979
 
        self.overrideAttr(plugin, 'plugins', dummy_plugins)
 
923
        self.plugin_warnings = {'bad': ['Failed to load (just testing)']}
 
924
        self.plugins = {'good': DummyPlugin()}
980
925
        self.assertEqual("""\
981
926
bad (failed to load)
982
927
  ** Failed to load (just testing)
984
929
good 0.1.0
985
930
  Hi there
986
931
 
987
 
""", ''.join(plugin.describe_plugins()))
 
932
""", ''.join(plugin.describe_plugins(state=self)))