~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_plugins.py

Merge bzr.dev into cleanup

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
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
20
20
# affects the global state of the process.  See bzrlib/plugins.py for more
21
21
# comments.
22
22
 
 
23
from cStringIO import StringIO
23
24
import logging
24
25
import os
25
 
from StringIO import StringIO
26
26
import sys
27
 
import zipfile
28
27
 
 
28
import bzrlib
29
29
from bzrlib import (
30
30
    osutils,
31
31
    plugin,
 
32
    plugins,
32
33
    tests,
33
 
    )
34
 
import bzrlib.plugin
35
 
import bzrlib.plugins
36
 
import bzrlib.commands
37
 
import bzrlib.help
38
 
from bzrlib.tests import (
39
 
    TestCase,
40
 
    TestCaseInTempDir,
41
 
    TestUtil,
42
 
    )
43
 
from bzrlib.osutils import pathjoin, abspath, normpath
44
 
 
45
 
 
46
 
PLUGIN_TEXT = """\
47
 
import bzrlib.commands
48
 
class cmd_myplug(bzrlib.commands.Command):
49
 
    '''Just a simple test plugin.'''
50
 
    aliases = ['mplg']
51
 
    def run(self):
52
 
        print 'Hello from my plugin'
53
 
"""
 
34
    trace,
 
35
    )
 
36
 
54
37
 
55
38
# TODO: Write a test for plugin decoration of commands.
56
39
 
57
 
class TestLoadingPlugins(TestCaseInTempDir):
 
40
class TestPluginMixin(object):
 
41
 
 
42
    def create_plugin(self, name, source=None, dir='.', file_name=None):
 
43
        if source is None:
 
44
            source = '''\
 
45
"""This is the doc for %s"""
 
46
''' % (name)
 
47
        if file_name is None:
 
48
            file_name = name + '.py'
 
49
        # 'source' must not fail to load
 
50
        path = osutils.pathjoin(dir, file_name)
 
51
        f = open(path, 'w')
 
52
        self.addCleanup(os.unlink, path)
 
53
        try:
 
54
            f.write(source + '\n')
 
55
        finally:
 
56
            f.close()
 
57
 
 
58
    def create_plugin_package(self, name, dir=None, source=None):
 
59
        if dir is None:
 
60
            dir = name
 
61
        if source is None:
 
62
            source = '''\
 
63
"""This is the doc for %s"""
 
64
dir_source = '%s'
 
65
''' % (name, dir)
 
66
        os.makedirs(dir)
 
67
        def cleanup():
 
68
            # Workaround lazy import random? madness
 
69
            osutils.rmtree(dir)
 
70
        self.addCleanup(cleanup)
 
71
        self.create_plugin(name, source, dir,
 
72
                           file_name='__init__.py')
 
73
 
 
74
    def _unregister_plugin(self, name):
 
75
        """Remove the plugin from sys.modules and the bzrlib namespace."""
 
76
        py_name = 'bzrlib.plugins.%s' % name
 
77
        if py_name in sys.modules:
 
78
            del sys.modules[py_name]
 
79
        if getattr(bzrlib.plugins, name, None) is not None:
 
80
            delattr(bzrlib.plugins, name)
 
81
 
 
82
    def assertPluginUnknown(self, name):
 
83
        self.failIf(getattr(bzrlib.plugins, name, None) is not None)
 
84
        self.failIf('bzrlib.plugins.%s' % name in sys.modules)
 
85
 
 
86
    def assertPluginKnown(self, name):
 
87
        self.failUnless(getattr(bzrlib.plugins, name, None) is not None)
 
88
        self.failUnless('bzrlib.plugins.%s' % name in sys.modules)
 
89
 
 
90
 
 
91
class TestLoadingPlugins(tests.TestCaseInTempDir, TestPluginMixin):
58
92
 
59
93
    activeattributes = {}
60
94
 
68
102
        # set a place for the plugins to record their loading, and at the same
69
103
        # time validate that the location the plugins should record to is
70
104
        # valid and correct.
71
 
        bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
72
 
            [tempattribute] = []
 
105
        self.__class__.activeattributes [tempattribute] = []
73
106
        self.failUnless(tempattribute in self.activeattributes)
74
107
        # create two plugin directories
75
108
        os.mkdir('first')
99
132
        finally:
100
133
            # remove the plugin 'plugin'
101
134
            del self.activeattributes[tempattribute]
102
 
            if 'bzrlib.plugins.plugin' in sys.modules:
103
 
                del sys.modules['bzrlib.plugins.plugin']
104
 
            if getattr(bzrlib.plugins, 'plugin', None):
105
 
                del bzrlib.plugins.plugin
106
 
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
 
135
            self._unregister_plugin('plugin')
 
136
        self.assertPluginUnknown('plugin')
107
137
 
108
138
    def test_plugins_from_different_dirs_can_demand_load(self):
 
139
        self.failIf('bzrlib.plugins.pluginone' in sys.modules)
 
140
        self.failIf('bzrlib.plugins.plugintwo' in sys.modules)
109
141
        # This test tests that having two plugins in different
110
142
        # directories with different names allows them both to be loaded, when
111
143
        # we do a direct import statement.
143
175
 
144
176
        oldpath = bzrlib.plugins.__path__
145
177
        try:
 
178
            self.failIf('bzrlib.plugins.pluginone' in sys.modules)
 
179
            self.failIf('bzrlib.plugins.plugintwo' in sys.modules)
146
180
            bzrlib.plugins.__path__ = ['first', 'second']
147
181
            exec "import bzrlib.plugins.pluginone"
148
182
            self.assertEqual(['first'], self.activeattributes[tempattribute])
152
186
        finally:
153
187
            # remove the plugin 'plugin'
154
188
            del self.activeattributes[tempattribute]
155
 
            if getattr(bzrlib.plugins, 'pluginone', None):
156
 
                del bzrlib.plugins.pluginone
157
 
            if getattr(bzrlib.plugins, 'plugintwo', None):
158
 
                del bzrlib.plugins.plugintwo
159
 
        self.failIf(getattr(bzrlib.plugins, 'pluginone', None))
160
 
        self.failIf(getattr(bzrlib.plugins, 'plugintwo', None))
 
189
            self._unregister_plugin('pluginone')
 
190
            self._unregister_plugin('plugintwo')
 
191
        self.assertPluginUnknown('pluginone')
 
192
        self.assertPluginUnknown('plugintwo')
161
193
 
162
194
    def test_plugins_can_load_from_directory_with_trailing_slash(self):
163
195
        # This test tests that a plugin can load from a directory when the
164
196
        # directory in the path has a trailing slash.
165
197
        # check the plugin is not loaded already
166
 
        self.failIf(getattr(bzrlib.plugins, 'ts_plugin', None))
 
198
        self.assertPluginUnknown('ts_plugin')
167
199
        tempattribute = "trailing-slash"
168
200
        self.failIf(tempattribute in self.activeattributes)
169
201
        # set a place for the plugin to record its loading, and at the same
190
222
            bzrlib.plugin.load_from_path(['plugin_test'+os.sep])
191
223
            self.assertEqual(['plugin'], self.activeattributes[tempattribute])
192
224
        finally:
193
 
            # remove the plugin 'plugin'
194
225
            del self.activeattributes[tempattribute]
195
 
            if getattr(bzrlib.plugins, 'ts_plugin', None):
196
 
                del bzrlib.plugins.ts_plugin
197
 
        self.failIf(getattr(bzrlib.plugins, 'ts_plugin', None))
 
226
            self._unregister_plugin('ts_plugin')
 
227
        self.assertPluginUnknown('ts_plugin')
198
228
 
199
229
    def load_and_capture(self, name):
200
230
        """Load plugins from '.' capturing the output.
251
281
            "it to 'bad_plugin_name_'\.")
252
282
 
253
283
 
254
 
class TestPlugins(TestCaseInTempDir):
 
284
class TestPlugins(tests.TestCaseInTempDir, TestPluginMixin):
255
285
 
256
286
    def setup_plugin(self, source=""):
257
287
        # This test tests a new plugin appears in bzrlib.plugin.plugins().
258
288
        # check the plugin is not loaded already
259
 
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
 
289
        self.assertPluginUnknown('plugin')
260
290
        # write a plugin that _cannot_ fail to load.
261
291
        file('plugin.py', 'w').write(source + '\n')
262
292
        self.addCleanup(self.teardown_plugin)
263
 
        bzrlib.plugin.load_from_path(['.'])
 
293
        plugin.load_from_path(['.'])
264
294
 
265
295
    def teardown_plugin(self):
266
 
        # remove the plugin 'plugin'
267
 
        if 'bzrlib.plugins.plugin' in sys.modules:
268
 
            del sys.modules['bzrlib.plugins.plugin']
269
 
        if getattr(bzrlib.plugins, 'plugin', None):
270
 
            del bzrlib.plugins.plugin
271
 
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
 
296
        self._unregister_plugin('plugin')
 
297
        self.assertPluginUnknown('plugin')
272
298
 
273
299
    def test_plugin_appears_in_plugins(self):
274
300
        self.setup_plugin()
275
 
        self.failUnless('plugin' in bzrlib.plugin.plugins())
276
 
        self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
277
 
        plugins = bzrlib.plugin.plugins()
278
 
        plugin = plugins['plugin']
279
 
        self.assertIsInstance(plugin, bzrlib.plugin.PlugIn)
280
 
        self.assertEqual(bzrlib.plugins.plugin, plugin.module)
 
301
        self.assertPluginKnown('plugin')
 
302
        p = plugin.plugins()['plugin']
 
303
        self.assertIsInstance(p, bzrlib.plugin.PlugIn)
 
304
        self.assertEqual(p.module, plugins.plugin)
281
305
 
282
306
    def test_trivial_plugin_get_path(self):
283
307
        self.setup_plugin()
284
 
        plugins = bzrlib.plugin.plugins()
285
 
        plugin = plugins['plugin']
 
308
        p = plugin.plugins()['plugin']
286
309
        plugin_path = self.test_dir + '/plugin.py'
287
 
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
 
310
        self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
288
311
 
289
312
    def test_plugin_get_path_py_not_pyc(self):
290
 
        self.setup_plugin()         # after first import there will be plugin.pyc
 
313
        # first import creates plugin.pyc
 
314
        self.setup_plugin()
291
315
        self.teardown_plugin()
292
 
        bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
293
 
        plugins = bzrlib.plugin.plugins()
294
 
        plugin = plugins['plugin']
 
316
        plugin.load_from_path(['.']) # import plugin.pyc
 
317
        p = plugin.plugins()['plugin']
295
318
        plugin_path = self.test_dir + '/plugin.py'
296
 
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
 
319
        self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
297
320
 
298
321
    def test_plugin_get_path_pyc_only(self):
299
 
        self.setup_plugin()         # after first import there will be plugin.pyc
 
322
        # first import creates plugin.pyc (or plugin.pyo depending on __debug__)
 
323
        self.setup_plugin()
300
324
        self.teardown_plugin()
301
325
        os.unlink(self.test_dir + '/plugin.py')
302
 
        bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
303
 
        plugins = bzrlib.plugin.plugins()
304
 
        plugin = plugins['plugin']
 
326
        plugin.load_from_path(['.']) # import plugin.pyc (or .pyo)
 
327
        p = plugin.plugins()['plugin']
305
328
        if __debug__:
306
329
            plugin_path = self.test_dir + '/plugin.pyc'
307
330
        else:
308
331
            plugin_path = self.test_dir + '/plugin.pyo'
309
 
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
 
332
        self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
310
333
 
311
334
    def test_no_test_suite_gives_None_for_test_suite(self):
312
335
        self.setup_plugin()
313
 
        plugin = bzrlib.plugin.plugins()['plugin']
314
 
        self.assertEqual(None, plugin.test_suite())
 
336
        p = plugin.plugins()['plugin']
 
337
        self.assertEqual(None, p.test_suite())
315
338
 
316
339
    def test_test_suite_gives_test_suite_result(self):
317
340
        source = """def test_suite(): return 'foo'"""
318
341
        self.setup_plugin(source)
319
 
        plugin = bzrlib.plugin.plugins()['plugin']
320
 
        self.assertEqual('foo', plugin.test_suite())
 
342
        p = plugin.plugins()['plugin']
 
343
        self.assertEqual('foo', p.test_suite())
321
344
 
322
345
    def test_no_load_plugin_tests_gives_None_for_load_plugin_tests(self):
323
346
        self.setup_plugin()
324
 
        loader = TestUtil.TestLoader()
325
 
        plugin = bzrlib.plugin.plugins()['plugin']
326
 
        self.assertEqual(None, plugin.load_plugin_tests(loader))
 
347
        loader = tests.TestUtil.TestLoader()
 
348
        p = plugin.plugins()['plugin']
 
349
        self.assertEqual(None, p.load_plugin_tests(loader))
327
350
 
328
351
    def test_load_plugin_tests_gives_load_plugin_tests_result(self):
329
352
        source = """
330
353
def load_tests(standard_tests, module, loader):
331
354
    return 'foo'"""
332
355
        self.setup_plugin(source)
333
 
        loader = TestUtil.TestLoader()
334
 
        plugin = bzrlib.plugin.plugins()['plugin']
335
 
        self.assertEqual('foo', plugin.load_plugin_tests(loader))
 
356
        loader = tests.TestUtil.TestLoader()
 
357
        p = plugin.plugins()['plugin']
 
358
        self.assertEqual('foo', p.load_plugin_tests(loader))
 
359
 
 
360
    def check_version_info(self, expected, source='', name='plugin'):
 
361
        self.setup_plugin(source)
 
362
        self.assertEqual(expected, plugin.plugins()[name].version_info())
336
363
 
337
364
    def test_no_version_info(self):
338
 
        self.setup_plugin()
339
 
        plugin = bzrlib.plugin.plugins()['plugin']
340
 
        self.assertEqual(None, plugin.version_info())
 
365
        self.check_version_info(None)
341
366
 
342
367
    def test_with_version_info(self):
343
 
        self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
344
 
        plugin = bzrlib.plugin.plugins()['plugin']
345
 
        self.assertEqual((1, 2, 3, 'dev', 4), plugin.version_info())
 
368
        self.check_version_info((1, 2, 3, 'dev', 4),
 
369
                                "version_info = (1, 2, 3, 'dev', 4)")
346
370
 
347
371
    def test_short_version_info_gets_padded(self):
348
372
        # the gtk plugin has version_info = (1,2,3) rather than the 5-tuple.
349
373
        # so we adapt it
350
 
        self.setup_plugin("version_info = (1, 2, 3)")
351
 
        plugin = bzrlib.plugin.plugins()['plugin']
352
 
        self.assertEqual((1, 2, 3, 'final', 0), plugin.version_info())
 
374
        self.check_version_info((1, 2, 3, 'final', 0),
 
375
                                "version_info = (1, 2, 3)")
 
376
 
 
377
    def check_version(self, expected, source=None, name='plugin'):
 
378
        self.setup_plugin(source)
 
379
        self.assertEqual(expected, plugins[name].__version__)
353
380
 
354
381
    def test_no_version_info___version__(self):
355
382
        self.setup_plugin()
407
434
        self.assertEqual("1.2.3.final.2", plugin.__version__)
408
435
 
409
436
 
410
 
class TestPluginHelp(TestCaseInTempDir):
 
437
class TestPluginHelp(tests.TestCaseInTempDir):
411
438
 
412
439
    def split_help_commands(self):
413
440
        help = {}
442
469
    def test_plugin_help_shows_plugin(self):
443
470
        # Create a test plugin
444
471
        os.mkdir('plugin_test')
445
 
        f = open(pathjoin('plugin_test', 'myplug.py'), 'w')
446
 
        f.write(PLUGIN_TEXT)
 
472
        f = open(osutils.pathjoin('plugin_test', 'myplug.py'), 'w')
 
473
        f.write("""\
 
474
from bzrlib import commands
 
475
class cmd_myplug(commands.Command):
 
476
    '''Just a simple test plugin.'''
 
477
    aliases = ['mplg']
 
478
    def run(self):
 
479
        print 'Hello from my plugin'
 
480
 
 
481
"""
 
482
)
447
483
        f.close()
448
484
 
449
485
        try:
627
663
        self.assertEqual(path, bzrlib.plugins.__path__)
628
664
 
629
665
 
630
 
class TestEnvPluginPath(tests.TestCaseInTempDir):
 
666
class TestEnvPluginPath(tests.TestCase):
631
667
 
632
668
    def setUp(self):
633
669
        super(TestEnvPluginPath, self).setUp()
680
716
        self.check_path([self.user, self.core, self.site],
681
717
                        ['+user', '+user'])
682
718
        # And only the first reference is kept (since the later references will
683
 
        # onnly produce <plugin> already loaded mutters)
 
719
        # only produce '<plugin> already loaded' mutters)
684
720
        self.check_path([self.user, self.core, self.site],
685
721
                        ['+user', '+user', '+core',
686
722
                         '+user', '+site', '+site',
687
723
                         '+core'])
688
724
 
689
 
    def test_disable_overrides_disable(self):
 
725
    def test_disable_overrides_enable(self):
690
726
        self.check_path([self.core, self.site], ['-user', '+user'])
691
727
 
692
728
    def test_disable_core(self):
719
755
    def test_bogus_references(self):
720
756
        self.check_path(['+foo', '-bar', self.core, self.site],
721
757
                        ['+foo', '-bar'])
 
758
 
 
759
 
 
760
class TestDisablePlugin(tests.TestCaseInTempDir, TestPluginMixin):
 
761
 
 
762
    def setUp(self):
 
763
        super(TestDisablePlugin, self).setUp()
 
764
        self.create_plugin_package('test_foo')
 
765
        # Make sure we don't pollute the plugins namespace
 
766
        self.overrideAttr(plugins, '__path__')
 
767
        # Be paranoid in case a test fail
 
768
        self.addCleanup(self._unregister_plugin, 'test_foo')
 
769
 
 
770
    def test_cannot_import(self):
 
771
        osutils.set_or_unset_env('BZR_DISABLE_PLUGINS', 'test_foo')
 
772
        plugin.set_plugins_path(['.'])
 
773
        try:
 
774
            import bzrlib.plugins.test_foo
 
775
        except ImportError:
 
776
            pass
 
777
        self.assertPluginUnknown('test_foo')
 
778
 
 
779
    def test_regular_load(self):
 
780
        self.overrideAttr(plugin, '_loaded', False)
 
781
        plugin.load_plugins(['.'])
 
782
        self.assertPluginKnown('test_foo')
 
783
        self.assertEqual("This is the doc for test_foo",
 
784
                         bzrlib.plugins.test_foo.__doc__)
 
785
 
 
786
    def test_not_loaded(self):
 
787
        self.warnings = []
 
788
        def captured_warning(*args, **kwargs):
 
789
            self.warnings.append((args, kwargs))
 
790
        self.overrideAttr(trace, 'warning', captured_warning)
 
791
        # Reset the flag that protect against double loading
 
792
        self.overrideAttr(plugin, '_loaded', False)
 
793
        osutils.set_or_unset_env('BZR_DISABLE_PLUGINS', 'test_foo')
 
794
        plugin.load_plugins(['.'])
 
795
        self.assertPluginUnknown('test_foo')
 
796
        # Make sure we don't warn about the plugin ImportError since this has
 
797
        # been *requested* by the user.
 
798
        self.assertLength(0, self.warnings)
 
799
 
 
800
 
 
801
class TestLoadPluginAt(tests.TestCaseInTempDir, TestPluginMixin):
 
802
 
 
803
    def setUp(self):
 
804
        super(TestLoadPluginAt, self).setUp()
 
805
        # Make sure we don't pollute the plugins namespace
 
806
        self.overrideAttr(plugins, '__path__')
 
807
        # Be paranoid in case a test fail
 
808
        self.addCleanup(self._unregister_plugin, 'test_foo')
 
809
        # Reset the flag that protect against double loading
 
810
        self.overrideAttr(plugin, '_loaded', False)
 
811
        # Create the same plugin in two directories
 
812
        self.create_plugin_package('test_foo', dir='non-standard-dir')
 
813
        self.create_plugin_package('test_foo', dir='b/test_foo')
 
814
 
 
815
    def assertTestFooLoadedFrom(self, dir):
 
816
        self.assertPluginKnown('test_foo')
 
817
        self.assertEqual('This is the doc for test_foo',
 
818
                         bzrlib.plugins.test_foo.__doc__)
 
819
        self.assertEqual(dir, bzrlib.plugins.test_foo.dir_source)
 
820
 
 
821
    def test_regular_load(self):
 
822
        plugin.load_plugins(['b'])
 
823
        self.assertTestFooLoadedFrom('b/test_foo')
 
824
 
 
825
    def test_import(self):
 
826
        osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
 
827
        plugin.set_plugins_path(['b'])
 
828
        try:
 
829
            import bzrlib.plugins.test_foo
 
830
        except ImportError:
 
831
            pass
 
832
        self.assertTestFooLoadedFrom('non-standard-dir')
 
833
 
 
834
    def test_loading(self):
 
835
        osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
 
836
        plugin.load_plugins(['b'])
 
837
        self.assertTestFooLoadedFrom('non-standard-dir')
 
838
 
 
839
    def test_compiled_loaded(self):
 
840
        osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
 
841
        plugin.load_plugins(['b'])
 
842
        self.assertTestFooLoadedFrom('non-standard-dir')
 
843
        self.assertEqual('non-standard-dir/__init__.py',
 
844
                         bzrlib.plugins.test_foo.__file__)
 
845
 
 
846
        # Try importing again now that the source has been compiled
 
847
        self._unregister_plugin('test_foo')
 
848
        plugin._loaded = False
 
849
        plugin.load_plugins(['b'])
 
850
        self.assertTestFooLoadedFrom('non-standard-dir')
 
851
        if __debug__:
 
852
            suffix = 'pyc'
 
853
        else:
 
854
            suffix = 'pyo'
 
855
        self.assertEqual('non-standard-dir/__init__.%s' % suffix,
 
856
                         bzrlib.plugins.test_foo.__file__)
 
857
 
 
858
    def test_submodule_loading(self):
 
859
        # We create an additional directory under the one for test_foo
 
860
        self.create_plugin_package('test_bar', dir='non-standard-dir/test_bar')
 
861
        osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
 
862
        plugin.set_plugins_path(['b'])
 
863
        import bzrlib.plugins.test_foo
 
864
        self.assertEqual('bzrlib.plugins.test_foo',
 
865
                         bzrlib.plugins.test_foo.__package__)
 
866
        import bzrlib.plugins.test_foo.test_bar
 
867
        self.assertEqual('non-standard-dir/test_bar/__init__.py',
 
868
                         bzrlib.plugins.test_foo.test_bar.__file__)