~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_plugins.py

(jelmer) Add load_plugin_translations() function. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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
27
27
 
28
28
import bzrlib
29
29
from bzrlib import (
 
30
    errors,
30
31
    osutils,
31
32
    plugin,
32
33
    plugins,
37
38
 
38
39
# TODO: Write a test for plugin decoration of commands.
39
40
 
40
 
class TestPluginMixin(object):
 
41
class BaseTestPlugins(tests.TestCaseInTempDir):
41
42
 
42
43
    def create_plugin(self, name, source=None, dir='.', file_name=None):
43
44
        if source is None:
79
80
        if getattr(bzrlib.plugins, name, None) is not None:
80
81
            delattr(bzrlib.plugins, name)
81
82
 
 
83
    def _unregister_plugin_submodule(self, plugin_name, submodule_name):
 
84
        """Remove the submodule from sys.modules and the bzrlib namespace."""
 
85
        py_name = 'bzrlib.plugins.%s.%s' % (plugin_name, submodule_name)
 
86
        if py_name in sys.modules:
 
87
            del sys.modules[py_name]
 
88
        plugin = getattr(bzrlib.plugins, plugin_name, None)
 
89
        if plugin is not None:
 
90
            if getattr(plugin, submodule_name, None) is not None:
 
91
                delattr(plugin, submodule_name)
 
92
 
82
93
    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)
 
94
        self.assertFalse(getattr(bzrlib.plugins, name, None) is not None)
 
95
        self.assertFalse('bzrlib.plugins.%s' % name in sys.modules)
85
96
 
86
97
    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):
 
98
        self.assertTrue(getattr(bzrlib.plugins, name, None) is not None)
 
99
        self.assertTrue('bzrlib.plugins.%s' % name in sys.modules)
 
100
 
 
101
 
 
102
class TestLoadingPlugins(BaseTestPlugins):
92
103
 
93
104
    activeattributes = {}
94
105
 
98
109
        # file name we can use which is also a valid attribute for accessing in
99
110
        # activeattributes. - we cannot give import parameters.
100
111
        tempattribute = "0"
101
 
        self.failIf(tempattribute in self.activeattributes)
 
112
        self.assertFalse(tempattribute in self.activeattributes)
102
113
        # set a place for the plugins to record their loading, and at the same
103
114
        # time validate that the location the plugins should record to is
104
115
        # valid and correct.
105
116
        self.__class__.activeattributes [tempattribute] = []
106
 
        self.failUnless(tempattribute in self.activeattributes)
 
117
        self.assertTrue(tempattribute in self.activeattributes)
107
118
        # create two plugin directories
108
119
        os.mkdir('first')
109
120
        os.mkdir('second')
136
147
        self.assertPluginUnknown('plugin')
137
148
 
138
149
    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)
 
150
        self.assertFalse('bzrlib.plugins.pluginone' in sys.modules)
 
151
        self.assertFalse('bzrlib.plugins.plugintwo' in sys.modules)
141
152
        # This test tests that having two plugins in different
142
153
        # directories with different names allows them both to be loaded, when
143
154
        # we do a direct import statement.
144
155
        # Determine a file name we can use which is also a valid attribute
145
156
        # for accessing in activeattributes. - we cannot give import parameters.
146
157
        tempattribute = "different-dirs"
147
 
        self.failIf(tempattribute in self.activeattributes)
 
158
        self.assertFalse(tempattribute in self.activeattributes)
148
159
        # set a place for the plugins to record their loading, and at the same
149
160
        # time validate that the location the plugins should record to is
150
161
        # valid and correct.
151
162
        bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
152
163
            [tempattribute] = []
153
 
        self.failUnless(tempattribute in self.activeattributes)
 
164
        self.assertTrue(tempattribute in self.activeattributes)
154
165
        # create two plugin directories
155
166
        os.mkdir('first')
156
167
        os.mkdir('second')
175
186
 
176
187
        oldpath = bzrlib.plugins.__path__
177
188
        try:
178
 
            self.failIf('bzrlib.plugins.pluginone' in sys.modules)
179
 
            self.failIf('bzrlib.plugins.plugintwo' in sys.modules)
 
189
            self.assertFalse('bzrlib.plugins.pluginone' in sys.modules)
 
190
            self.assertFalse('bzrlib.plugins.plugintwo' in sys.modules)
180
191
            bzrlib.plugins.__path__ = ['first', 'second']
181
192
            exec "import bzrlib.plugins.pluginone"
182
193
            self.assertEqual(['first'], self.activeattributes[tempattribute])
197
208
        # check the plugin is not loaded already
198
209
        self.assertPluginUnknown('ts_plugin')
199
210
        tempattribute = "trailing-slash"
200
 
        self.failIf(tempattribute in self.activeattributes)
 
211
        self.assertFalse(tempattribute in self.activeattributes)
201
212
        # set a place for the plugin to record its loading, and at the same
202
213
        # time validate that the location the plugin should record to is
203
214
        # valid and correct.
204
215
        bzrlib.tests.test_plugins.TestLoadingPlugins.activeattributes \
205
216
            [tempattribute] = []
206
 
        self.failUnless(tempattribute in self.activeattributes)
 
217
        self.assertTrue(tempattribute in self.activeattributes)
207
218
        # create a directory for the plugin
208
219
        os.mkdir('plugin_test')
209
220
        # write a plugin that will record when its loaded in the
256
267
            stream.close()
257
268
 
258
269
    def test_plugin_with_bad_api_version_reports(self):
259
 
        # This plugin asks for bzrlib api version 1.0.0, which is not supported
260
 
        # anymore.
 
270
        """Try loading a plugin that requests an unsupported api.
 
271
        
 
272
        Observe that it records the problem but doesn't complain on stderr.
 
273
 
 
274
        See https://bugs.launchpad.net/bzr/+bug/704195
 
275
        """
 
276
        self.overrideAttr(plugin, 'plugin_warnings', {})
261
277
        name = 'wants100.py'
262
278
        f = file(name, 'w')
263
279
        try:
265
281
                "bzrlib.api.require_any_api(bzrlib, [(1, 0, 0)])\n")
266
282
        finally:
267
283
            f.close()
268
 
 
269
284
        log = self.load_and_capture(name)
270
 
        self.assertContainsRe(log,
 
285
        self.assertNotContainsRe(log,
 
286
            r"It requested API version")
 
287
        self.assertEquals(
 
288
            ['wants100'],
 
289
            plugin.plugin_warnings.keys())
 
290
        self.assertContainsRe(
 
291
            plugin.plugin_warnings['wants100'][0],
271
292
            r"It requested API version")
272
293
 
273
294
    def test_plugin_with_bad_name_does_not_load(self):
281
302
            "it to 'bad_plugin_name_'\.")
282
303
 
283
304
 
284
 
class TestPlugins(tests.TestCaseInTempDir, TestPluginMixin):
 
305
class TestPlugins(BaseTestPlugins):
285
306
 
286
307
    def setup_plugin(self, source=""):
287
308
        # This test tests a new plugin appears in bzrlib.plugin.plugins().
431
452
    def test_final_fallback__version__with_version_info(self):
432
453
        self.setup_plugin("version_info = (1, 2, 3, 'final', 2)")
433
454
        plugin = bzrlib.plugin.plugins()['plugin']
434
 
        self.assertEqual("1.2.3.final.2", plugin.__version__)
 
455
        self.assertEqual("1.2.3.2", plugin.__version__)
435
456
 
436
457
 
437
458
class TestPluginHelp(tests.TestCaseInTempDir):
473
494
        f.write("""\
474
495
from bzrlib import commands
475
496
class cmd_myplug(commands.Command):
476
 
    '''Just a simple test plugin.'''
 
497
    __doc__ = '''Just a simple test plugin.'''
477
498
    aliases = ['mplg']
478
499
    def run(self):
479
500
        print 'Hello from my plugin'
594
615
    def test_get_help_text_with_additional_see_also(self):
595
616
        mod = FakeModule('two lines of help\nand more', 'demo')
596
617
        topic = plugin.ModuleHelpTopic(mod)
597
 
        self.assertEqual("two lines of help\nand more\nSee also: bar, foo\n",
598
 
            topic.get_help_text(['foo', 'bar']))
 
618
        self.assertEqual("two lines of help\nand more\n\n:See also: bar, foo\n",
 
619
                         topic.get_help_text(['foo', 'bar']))
599
620
 
600
621
    def test_get_help_topic(self):
601
622
        """The help topic for a plugin is its module name."""
602
623
        mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.demo')
603
624
        topic = plugin.ModuleHelpTopic(mod)
604
625
        self.assertEqual('demo', topic.get_help_topic())
605
 
        mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
 
626
        mod = FakeModule('two lines of help\nand more',
 
627
                         'bzrlib.plugins.foo_bar')
606
628
        topic = plugin.ModuleHelpTopic(mod)
607
629
        self.assertEqual('foo_bar', topic.get_help_topic())
608
630
 
645
667
                    self.fail('No path to global plugins')
646
668
 
647
669
    def test_get_standard_plugins_path_env(self):
648
 
        os.environ['BZR_PLUGIN_PATH'] = 'foo/'
 
670
        self.overrideEnv('BZR_PLUGIN_PATH', 'foo/')
649
671
        path = plugin.get_standard_plugins_path()
650
672
        for directory in path:
651
673
            self.assertNotContainsRe(directory, r'\\/$')
681
703
 
682
704
    def _set_path(self, *args):
683
705
        path = os.pathsep.join(self._list2paths(*args))
684
 
        osutils.set_or_unset_env('BZR_PLUGIN_PATH', path)
 
706
        self.overrideEnv('BZR_PLUGIN_PATH', path)
685
707
 
686
708
    def check_path(self, expected_dirs, setting_dirs):
687
709
        if setting_dirs:
757
779
                        ['+foo', '-bar'])
758
780
 
759
781
 
760
 
class TestDisablePlugin(tests.TestCaseInTempDir, TestPluginMixin):
 
782
class TestDisablePlugin(BaseTestPlugins):
761
783
 
762
784
    def setUp(self):
763
785
        super(TestDisablePlugin, self).setUp()
768
790
        self.addCleanup(self._unregister_plugin, 'test_foo')
769
791
 
770
792
    def test_cannot_import(self):
771
 
        osutils.set_or_unset_env('BZR_DISABLE_PLUGINS', 'test_foo')
 
793
        self.overrideEnv('BZR_DISABLE_PLUGINS', 'test_foo')
772
794
        plugin.set_plugins_path(['.'])
773
795
        try:
774
796
            import bzrlib.plugins.test_foo
780
802
        self.overrideAttr(plugin, '_loaded', False)
781
803
        plugin.load_plugins(['.'])
782
804
        self.assertPluginKnown('test_foo')
783
 
        self.assertEqual("This is the doc for test_foo",
784
 
                         bzrlib.plugins.test_foo.__doc__)
 
805
        self.assertDocstring("This is the doc for test_foo",
 
806
                             bzrlib.plugins.test_foo)
785
807
 
786
808
    def test_not_loaded(self):
787
809
        self.warnings = []
790
812
        self.overrideAttr(trace, 'warning', captured_warning)
791
813
        # Reset the flag that protect against double loading
792
814
        self.overrideAttr(plugin, '_loaded', False)
793
 
        osutils.set_or_unset_env('BZR_DISABLE_PLUGINS', 'test_foo')
 
815
        self.overrideEnv('BZR_DISABLE_PLUGINS', 'test_foo')
794
816
        plugin.load_plugins(['.'])
795
817
        self.assertPluginUnknown('test_foo')
796
818
        # Make sure we don't warn about the plugin ImportError since this has
798
820
        self.assertLength(0, self.warnings)
799
821
 
800
822
 
801
 
class TestLoadPluginAt(tests.TestCaseInTempDir, TestPluginMixin):
 
823
 
 
824
class TestLoadPluginAtSyntax(tests.TestCase):
 
825
 
 
826
    def _get_paths(self, paths):
 
827
        return plugin._get_specific_plugin_paths(paths)
 
828
 
 
829
    def test_empty(self):
 
830
        self.assertEquals([], self._get_paths(None))
 
831
        self.assertEquals([], self._get_paths(''))
 
832
 
 
833
    def test_one_path(self):
 
834
        self.assertEquals([('b', 'man')], self._get_paths('b@man'))
 
835
 
 
836
    def test_bogus_path(self):
 
837
        # We need a '@'
 
838
        self.assertRaises(errors.BzrCommandError, self._get_paths, 'batman')
 
839
        # Too much '@' isn't good either
 
840
        self.assertRaises(errors.BzrCommandError, self._get_paths,
 
841
                          'batman@mobile@cave')
 
842
        # An empty description probably indicates a problem
 
843
        self.assertRaises(errors.BzrCommandError, self._get_paths,
 
844
                          os.pathsep.join(['batman@cave', '', 'robin@mobile']))
 
845
 
 
846
 
 
847
class TestLoadPluginAt(BaseTestPlugins):
802
848
 
803
849
    def setUp(self):
804
850
        super(TestLoadPluginAt, self).setUp()
805
851
        # Make sure we don't pollute the plugins namespace
806
852
        self.overrideAttr(plugins, '__path__')
807
 
        # Be paranoid in case a test fail
808
 
        self.addCleanup(self._unregister_plugin, 'test_foo')
809
853
        # Reset the flag that protect against double loading
810
854
        self.overrideAttr(plugin, '_loaded', False)
811
855
        # Create the same plugin in two directories
813
857
        # The "normal" directory, we use 'standard' instead of 'plugins' to
814
858
        # avoid depending on the precise naming.
815
859
        self.create_plugin_package('test_foo', dir='standard/test_foo')
 
860
        # All the tests will load the 'test_foo' plugin from various locations
 
861
        self.addCleanup(self._unregister_plugin, 'test_foo')
 
862
        # Unfortunately there's global cached state for the specific
 
863
        # registered paths.
 
864
        self.addCleanup(plugin.PluginImporter.reset)
816
865
 
817
866
    def assertTestFooLoadedFrom(self, path):
818
867
        self.assertPluginKnown('test_foo')
819
 
        self.assertEqual('This is the doc for test_foo',
820
 
                         bzrlib.plugins.test_foo.__doc__)
 
868
        self.assertDocstring('This is the doc for test_foo',
 
869
                             bzrlib.plugins.test_foo)
821
870
        self.assertEqual(path, bzrlib.plugins.test_foo.dir_source)
822
871
 
823
872
    def test_regular_load(self):
825
874
        self.assertTestFooLoadedFrom('standard/test_foo')
826
875
 
827
876
    def test_import(self):
828
 
        osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
 
877
        self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
829
878
        plugin.set_plugins_path(['standard'])
830
879
        try:
831
880
            import bzrlib.plugins.test_foo
834
883
        self.assertTestFooLoadedFrom('non-standard-dir')
835
884
 
836
885
    def test_loading(self):
837
 
        osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
 
886
        self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
838
887
        plugin.load_plugins(['standard'])
839
888
        self.assertTestFooLoadedFrom('non-standard-dir')
840
889
 
841
890
    def test_compiled_loaded(self):
842
 
        osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
 
891
        self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
843
892
        plugin.load_plugins(['standard'])
844
893
        self.assertTestFooLoadedFrom('non-standard-dir')
845
 
        self.assertEqual('non-standard-dir/__init__.py',
846
 
                         bzrlib.plugins.test_foo.__file__)
 
894
        self.assertIsSameRealPath('non-standard-dir/__init__.py',
 
895
                                  bzrlib.plugins.test_foo.__file__)
847
896
 
848
897
        # Try importing again now that the source has been compiled
849
898
        self._unregister_plugin('test_foo')
854
903
            suffix = 'pyc'
855
904
        else:
856
905
            suffix = 'pyo'
857
 
        self.assertEqual('non-standard-dir/__init__.%s' % suffix,
858
 
                         bzrlib.plugins.test_foo.__file__)
 
906
        self.assertIsSameRealPath('non-standard-dir/__init__.%s' % suffix,
 
907
                                  bzrlib.plugins.test_foo.__file__)
859
908
 
860
909
    def test_submodule_loading(self):
861
910
        # We create an additional directory under the one for test_foo
862
911
        self.create_plugin_package('test_bar', dir='non-standard-dir/test_bar')
863
 
        osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
 
912
        self.addCleanup(self._unregister_plugin_submodule,
 
913
                        'test_foo', 'test_bar')
 
914
        self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
864
915
        plugin.set_plugins_path(['standard'])
865
916
        import bzrlib.plugins.test_foo
866
917
        self.assertEqual('bzrlib.plugins.test_foo',
867
918
                         bzrlib.plugins.test_foo.__package__)
868
919
        import bzrlib.plugins.test_foo.test_bar
869
 
        self.assertEqual('non-standard-dir/test_bar/__init__.py',
870
 
                         bzrlib.plugins.test_foo.test_bar.__file__)
 
920
        self.assertIsSameRealPath('non-standard-dir/test_bar/__init__.py',
 
921
                                  bzrlib.plugins.test_foo.test_bar.__file__)
 
922
 
 
923
    def test_relative_submodule_loading(self):
 
924
        self.create_plugin_package('test_foo', dir='another-dir', source='''
 
925
import test_bar
 
926
''')
 
927
        # We create an additional directory under the one for test_foo
 
928
        self.create_plugin_package('test_bar', dir='another-dir/test_bar')
 
929
        self.addCleanup(self._unregister_plugin_submodule,
 
930
                        'test_foo', 'test_bar')
 
931
        self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@another-dir')
 
932
        plugin.set_plugins_path(['standard'])
 
933
        import bzrlib.plugins.test_foo
 
934
        self.assertEqual('bzrlib.plugins.test_foo',
 
935
                         bzrlib.plugins.test_foo.__package__)
 
936
        self.assertIsSameRealPath('another-dir/test_bar/__init__.py',
 
937
                                  bzrlib.plugins.test_foo.test_bar.__file__)
871
938
 
872
939
    def test_loading_from___init__only(self):
873
940
        # We rename the existing __init__.py file to ensure that we don't load
876
943
        random = 'non-standard-dir/setup.py'
877
944
        os.rename(init, random)
878
945
        self.addCleanup(os.rename, random, init)
879
 
        osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
 
946
        self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
880
947
        plugin.load_plugins(['standard'])
881
948
        self.assertPluginUnknown('test_foo')
882
949
 
890
957
''' % ('test_foo', plugin_path)
891
958
        self.create_plugin('test_foo', source=source,
892
959
                           dir=plugin_dir, file_name=plugin_file_name)
893
 
        osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@%s' % plugin_path)
 
960
        self.overrideEnv('BZR_PLUGINS_AT', 'test_foo@%s' % plugin_path)
894
961
        plugin.load_plugins(['standard'])
895
962
        self.assertTestFooLoadedFrom(plugin_path)
 
963
 
 
964
 
 
965
class TestDescribePlugins(BaseTestPlugins):
 
966
 
 
967
    def test_describe_plugins(self):
 
968
        class DummyModule(object):
 
969
            __doc__ = 'Hi there'
 
970
        class DummyPlugin(object):
 
971
            __version__ = '0.1.0'
 
972
            module = DummyModule()
 
973
        def dummy_plugins():
 
974
            return { 'good': DummyPlugin() }
 
975
        self.overrideAttr(plugin, 'plugin_warnings',
 
976
            {'bad': ['Failed to load (just testing)']})
 
977
        self.overrideAttr(plugin, 'plugins', dummy_plugins)
 
978
        self.assertEquals("""\
 
979
bad (failed to load)
 
980
  ** Failed to load (just testing)
 
981
 
 
982
good 0.1.0
 
983
  Hi there
 
984
 
 
985
""", ''.join(plugin.describe_plugins()))