/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-07-23 22:06:41 UTC
  • mfrom: (6738 trunk)
  • mto: This revision was merged to the branch mainline in revision 6739.
  • Revision ID: jelmer@jelmer.uk-20170723220641-69eczax9bmv8d6kk
Merge trunk, address review comments.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2012, 2016 Canonical Ltd
2
 
# Copyright (C) 2017-2018 Breezy developers
 
1
# Copyright (C) 2005-2012, 2016 Canonical Ltd, 2017 Breezy developers
3
2
#
4
3
# This program is free software; you can redistribute it and/or modify
5
4
# it under the terms of the GNU General Public License as published by
17
16
 
18
17
"""Tests for plugins"""
19
18
 
 
19
import imp
20
20
import importlib
21
 
from io import StringIO
22
21
import logging
23
22
import os
24
23
import sys
25
 
import types
26
24
 
27
25
import breezy
28
26
from .. import (
 
27
    errors,
29
28
    osutils,
30
29
    plugin,
31
30
    tests,
32
 
    )
33
 
from ..tests.features import pkg_resources_feature
 
31
    trace,
 
32
    )
 
33
from ..sixish import (
 
34
    PY3,
 
35
    StringIO,
 
36
    viewkeys,
 
37
    )
34
38
 
35
39
 
36
40
# TODO: Write a test for plugin decoration of commands.
45
49
        super(BaseTestPlugins, self).setUp()
46
50
        self.module_name = "breezy.testingplugins"
47
51
        self.module_prefix = self.module_name + "."
48
 
        self.module = types.ModuleType(self.module_name)
 
52
        self.module = imp.new_module(self.module_name)
49
53
 
50
54
        self.overrideAttr(plugin, "_MODULE_PREFIX", self.module_prefix)
51
55
        self.overrideAttr(breezy, "testingplugins", self.module)
76
80
        self.log("using %r", paths)
77
81
        return paths
78
82
 
79
 
    def load_with_paths(self, paths, warn_load_problems=True):
 
83
    def load_with_paths(self, paths):
80
84
        self.log("loading plugins!")
81
 
        plugin.load_plugins(
82
 
            self.update_module_paths(paths), state=self,
83
 
            warn_load_problems=warn_load_problems)
 
85
        plugin.load_plugins(self.update_module_paths(paths), state=self)
84
86
 
85
87
    def create_plugin(self, name, source=None, dir='.', file_name=None):
86
88
        if source is None:
115
117
                rel = osutils.relpath(self.test_dir, cache_dir)
116
118
                self.log("moving %s in %s to %s", name, rel, magicless_name)
117
119
                os.rename(os.path.join(cache_dir, name),
118
 
                          os.path.join(directory, magicless_name))
 
120
                    os.path.join(directory, magicless_name))
119
121
 
120
122
    def _unregister_finder(self):
121
123
        """Removes any test copies of _PluginsAtFinder from sys.meta_path."""
137
139
    def assertPluginModules(self, plugin_dict):
138
140
        self.assertEqual(
139
141
            dict((k[len(self.module_prefix):], sys.modules[k])
140
 
                 for k in sys.modules if k.startswith(self.module_prefix)),
 
142
                for k in sys.modules if k.startswith(self.module_prefix)),
141
143
            plugin_dict)
142
144
 
143
145
    def assertPluginUnknown(self, name):
163
165
        # set a place for the plugins to record their loading, and at the same
164
166
        # time validate that the location the plugins should record to is
165
167
        # valid and correct.
166
 
        self.__class__.activeattributes[tempattribute] = []
 
168
        self.__class__.activeattributes [tempattribute] = []
167
169
        self.assertTrue(tempattribute in self.activeattributes)
168
170
        # create two plugin directories
169
171
        os.mkdir('first')
200
202
        # set a place for the plugins to record their loading, and at the same
201
203
        # time validate that the location the plugins should record to is
202
204
        # valid and correct.
203
 
        breezy.tests.test_plugins.TestLoadingPlugins.activeattributes[tempattribute] = [
204
 
            ]
 
205
        breezy.tests.test_plugins.TestLoadingPlugins.activeattributes \
 
206
            [tempattribute] = []
205
207
        self.assertTrue(tempattribute in self.activeattributes)
206
208
        # create two plugin directories
207
209
        os.mkdir('first')
227
229
            self.assertEqual(['first'], self.activeattributes[tempattribute])
228
230
            exec("import %splugintwo" % self.module_prefix)
229
231
            self.assertEqual(['first', 'second'],
230
 
                             self.activeattributes[tempattribute])
 
232
                self.activeattributes[tempattribute])
231
233
        finally:
232
234
            del self.activeattributes[tempattribute]
233
235
 
241
243
        # set a place for the plugin to record its loading, and at the same
242
244
        # time validate that the location the plugin should record to is
243
245
        # valid and correct.
244
 
        breezy.tests.test_plugins.TestLoadingPlugins.activeattributes[tempattribute] = [
245
 
            ]
 
246
        breezy.tests.test_plugins.TestLoadingPlugins.activeattributes \
 
247
            [tempattribute] = []
246
248
        self.assertTrue(tempattribute in self.activeattributes)
247
249
        # create a directory for the plugin
248
250
        os.mkdir('plugin_test')
256
258
            outfile.write('\n')
257
259
 
258
260
        try:
259
 
            self.load_with_paths(['plugin_test' + os.sep])
 
261
            self.load_with_paths(['plugin_test'+os.sep])
260
262
            self.assertEqual(['plugin'], self.activeattributes[tempattribute])
261
263
            self.assertPluginKnown('ts_plugin')
262
264
        finally:
263
265
            del self.activeattributes[tempattribute]
264
266
 
265
 
    def load_and_capture(self, name, warn_load_problems=True):
 
267
    def load_and_capture(self, name):
266
268
        """Load plugins from '.' capturing the output.
267
269
 
268
270
        :param name: The name of the plugin.
275
277
            log = logging.getLogger('brz')
276
278
            log.addHandler(handler)
277
279
            try:
278
 
                self.load_with_paths(
279
 
                    ['.'], warn_load_problems=warn_load_problems)
 
280
                self.load_with_paths(['.'])
280
281
            finally:
281
282
                # Stop capturing output
282
283
                handler.flush()
289
290
    def test_plugin_with_bad_api_version_reports(self):
290
291
        """Try loading a plugin that requests an unsupported api.
291
292
 
292
 
        Observe that it records the problem but doesn't complain on stderr
293
 
        when warn_load_problems=False
 
293
        Observe that it records the problem but doesn't complain on stderr.
 
294
 
 
295
        See https://bugs.launchpad.net/bzr/+bug/704195
294
296
        """
295
297
        name = 'wants100.py'
296
298
        with open(name, 'w') as f:
297
299
            f.write("import breezy\n"
298
 
                    "from breezy.errors import IncompatibleVersion\n"
299
 
                    "raise IncompatibleVersion(breezy, [(1, 0, 0)], (0, 0, 5))\n")
300
 
        log = self.load_and_capture(name, warn_load_problems=False)
301
 
        self.assertNotContainsRe(log, r"It supports breezy version")
302
 
        self.assertEqual({'wants100'}, self.plugin_warnings.keys())
 
300
                "from breezy.errors import IncompatibleVersion\n"
 
301
                "raise IncompatibleVersion(breezy, [(1, 0, 0)], (0, 0, 5))\n")
 
302
        log = self.load_and_capture(name)
 
303
        self.assertNotContainsRe(log,
 
304
            r"It supports breezy version")
 
305
        self.assertEqual({'wants100'}, viewkeys(self.plugin_warnings))
303
306
        self.assertContainsRe(
304
307
            self.plugin_warnings['wants100'][0],
305
308
            r"It supports breezy version")
310
313
        open(name, 'w').close()
311
314
        log = self.load_and_capture(name)
312
315
        self.assertContainsRe(log,
313
 
                              r"Unable to load 'brz-bad plugin-name\.' in '\.' as a plugin "
314
 
                              "because the file path isn't a valid module name; try renaming "
315
 
                              "it to 'bad_plugin_name_'\\.")
316
 
 
317
 
    def test_plugin_with_error_suppress(self):
318
 
        # The file name here invalid for a python module.
319
 
        name = 'some_error.py'
320
 
        with open(name, 'w') as f:
321
 
            f.write('raise Exception("bad")\n')
322
 
        log = self.load_and_capture(name, warn_load_problems=False)
323
 
        self.assertEqual('', log)
324
 
 
325
 
    def test_plugin_with_error(self):
326
 
        # The file name here invalid for a python module.
327
 
        name = 'some_error.py'
328
 
        with open(name, 'w') as f:
329
 
            f.write('raise Exception("bad")\n')
330
 
        log = self.load_and_capture(name, warn_load_problems=True)
331
 
        self.assertEqual(
332
 
            'Unable to load plugin \'some_error\' from \'.\': bad\n', log)
 
316
            r"Unable to load 'brz-bad plugin-name\.' in '\.' as a plugin "
 
317
            "because the file path isn't a valid module name; try renaming "
 
318
            "it to 'bad_plugin_name_'\.")
333
319
 
334
320
 
335
321
class TestPlugins(BaseTestPlugins):
339
325
        # check the plugin is not loaded already
340
326
        self.assertPluginUnknown('plugin')
341
327
        # write a plugin that _cannot_ fail to load.
342
 
        with open('plugin.py', 'w') as f:
343
 
            f.write(source + '\n')
 
328
        with open('plugin.py', 'w') as f: f.write(source + '\n')
344
329
        self.load_with_paths(['.'])
345
330
 
346
 
    def test_plugin_loaded(self):
347
 
        self.assertPluginUnknown('plugin')
348
 
        self.assertIs(None, breezy.plugin.get_loaded_plugin('plugin'))
349
 
        self.setup_plugin()
350
 
        p = breezy.plugin.get_loaded_plugin('plugin')
351
 
        self.assertIsInstance(p, breezy.plugin.PlugIn)
352
 
        self.assertIs(p.module, sys.modules[self.module_prefix + 'plugin'])
353
 
 
354
 
    def test_plugin_loaded_disabled(self):
355
 
        self.assertPluginUnknown('plugin')
356
 
        self.overrideEnv('BRZ_DISABLE_PLUGINS', 'plugin')
357
 
        self.setup_plugin()
358
 
        self.assertIs(None, breezy.plugin.get_loaded_plugin('plugin'))
359
 
 
360
331
    def test_plugin_appears_in_plugins(self):
361
332
        self.setup_plugin()
362
333
        self.assertPluginKnown('plugin')
375
346
        self.setup_plugin()
376
347
        self.promote_cache(self.test_dir)
377
348
        self.reset()
378
 
        self.load_with_paths(['.'])  # import plugin.pyc
 
349
        self.load_with_paths(['.']) # import plugin.pyc
379
350
        p = plugin.plugins()['plugin']
380
351
        plugin_path = self.test_dir + '/plugin.py'
381
352
        self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
386
357
        os.unlink(self.test_dir + '/plugin.py')
387
358
        self.promote_cache(self.test_dir)
388
359
        self.reset()
389
 
        self.load_with_paths(['.'])  # import plugin.pyc (or .pyo)
 
360
        self.load_with_paths(['.']) # import plugin.pyc (or .pyo)
390
361
        p = plugin.plugins()['plugin']
391
362
        plugin_path = self.test_dir + '/plugin' + plugin.COMPILED_EXT
392
363
        self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
494
465
        self.assertEqual("1.2.3.2", plugin.__version__)
495
466
 
496
467
 
 
468
# GZ 2017-06-02: Move this suite to blackbox, as it's what it actually is.
 
469
class TestPluginHelp(BaseTestPlugins):
 
470
 
 
471
    def split_help_commands(self):
 
472
        help = {}
 
473
        current = None
 
474
        out, err = self.run_bzr('--no-plugins help commands')
 
475
        for line in out.splitlines():
 
476
            if not line.startswith(' '):
 
477
                current = line.split()[0]
 
478
            help[current] = help.get(current, '') + line
 
479
 
 
480
        return help
 
481
 
 
482
    def test_plugin_help_builtins_unaffected(self):
 
483
        # Check we don't get false positives
 
484
        help_commands = self.split_help_commands()
 
485
        for cmd_name in breezy.commands.builtin_command_names():
 
486
            if cmd_name in breezy.commands.plugin_command_names():
 
487
                continue
 
488
            try:
 
489
                help = breezy.commands.get_cmd_object(cmd_name).get_help_text()
 
490
            except NotImplementedError:
 
491
                # some commands have no help
 
492
                pass
 
493
            else:
 
494
                self.assertNotContainsRe(help, 'plugin "[^"]*"')
 
495
 
 
496
            if cmd_name in help_commands:
 
497
                # some commands are hidden
 
498
                help = help_commands[cmd_name]
 
499
                self.assertNotContainsRe(help, 'plugin "[^"]*"')
 
500
 
 
501
    def test_plugin_help_shows_plugin(self):
 
502
        # Create a test plugin
 
503
        os.mkdir('plugin_test')
 
504
        source = (
 
505
            "from breezy import commands\n"
 
506
            "class cmd_myplug(commands.Command):\n"
 
507
            "    __doc__ = '''Just a simple test plugin.'''\n"
 
508
            "    aliases = ['mplg']\n"
 
509
            "    def run(self):\n"
 
510
            "        print ('Hello from my plugin')\n"
 
511
        )
 
512
        self.create_plugin('myplug', source, 'plugin_test')
 
513
 
 
514
        # Check its help
 
515
        self.load_with_paths(['plugin_test'])
 
516
        myplug = self.plugins['myplug'].module
 
517
        breezy.commands.register_command(myplug.cmd_myplug)
 
518
        self.addCleanup(breezy.commands.plugin_cmds.remove, 'myplug')
 
519
        help = self.run_bzr('help myplug')[0]
 
520
        self.assertContainsRe(help, 'plugin "myplug"')
 
521
        help = self.split_help_commands()['myplug']
 
522
        self.assertContainsRe(help, '\[myplug\]')
 
523
 
 
524
 
497
525
class TestHelpIndex(tests.TestCase):
498
526
    """Tests for the PluginsHelpIndex class."""
499
527
 
570
598
        mod = FakeModule(None, 'demo')
571
599
        topic = plugin.ModuleHelpTopic(mod)
572
600
        self.assertEqual("Plugin 'demo' has no docstring.\n",
573
 
                         topic.get_help_text())
 
601
            topic.get_help_text())
574
602
 
575
603
    def test_get_help_text_no_carriage_return(self):
576
604
        """ModuleHelpTopic.get_help_text adds a \n if needed."""
577
605
        mod = FakeModule('one line of help', 'demo')
578
606
        topic = plugin.ModuleHelpTopic(mod)
579
607
        self.assertEqual("one line of help\n",
580
 
                         topic.get_help_text())
 
608
            topic.get_help_text())
581
609
 
582
610
    def test_get_help_text_carriage_return(self):
583
611
        """ModuleHelpTopic.get_help_text adds a \n if needed."""
584
612
        mod = FakeModule('two lines of help\nand more\n', 'demo')
585
613
        topic = plugin.ModuleHelpTopic(mod)
586
614
        self.assertEqual("two lines of help\nand more\n",
587
 
                         topic.get_help_text())
 
615
            topic.get_help_text())
588
616
 
589
617
    def test_get_help_text_with_additional_see_also(self):
590
618
        mod = FakeModule('two lines of help\nand more', 'demo')
591
619
        topic = plugin.ModuleHelpTopic(mod)
592
 
        self.assertEqual(
593
 
            "two lines of help\nand more\n\n:See also: bar, foo\n",
594
 
            topic.get_help_text(['foo', 'bar']))
 
620
        self.assertEqual("two lines of help\nand more\n\n:See also: bar, foo\n",
 
621
                         topic.get_help_text(['foo', 'bar']))
595
622
 
596
623
    def test_get_help_topic(self):
597
624
        """The help topic for a plugin is its module name."""
609
636
    user = "USER"
610
637
    core = "CORE"
611
638
    site = "SITE"
612
 
    entrypoints = "ENTRYPOINTS"
613
639
 
614
640
    def check_path(self, expected_dirs, setting_dirs):
615
641
        if setting_dirs is None:
617
643
        else:
618
644
            os.environ['BRZ_PLUGIN_PATH'] = os.pathsep.join(setting_dirs)
619
645
        actual = [(p if t == 'path' else t.upper())
620
 
                  for p, t in plugin._env_plugin_path()]
 
646
            for p, t in plugin._env_plugin_path()]
621
647
        self.assertEqual(expected_dirs, actual)
622
648
 
623
649
    def test_default(self):
624
 
        self.check_path([self.user, self.core, self.site], None)
 
650
        self.check_path([self.user, self.core, self.site],
 
651
                        None)
625
652
 
626
653
    def test_adhoc_policy(self):
627
654
        self.check_path([self.user, self.core, self.site],
635
662
        self.check_path([self.user, self.site, self.core],
636
663
                        ['+user', '+site', '+core'])
637
664
 
638
 
    def test_enable_entrypoints(self):
639
 
        self.check_path([self.user, self.core, self.site, self.entrypoints],
640
 
                        ['+user', '+core', '+site', '+entrypoints'])
641
 
 
642
665
    def test_disable_user(self):
643
666
        self.check_path([self.core, self.site], ['-user'])
644
667
 
680
703
                        ['mycore', '-core'])
681
704
 
682
705
    def test_my_plugin_only(self):
683
 
        self.check_path(
684
 
            ['myplugin'],
685
 
            ['myplugin', '-user', '-core', '-site', '-entrypoints'])
 
706
        self.check_path(['myplugin'], ['myplugin', '-user', '-core', '-site'])
686
707
 
687
708
    def test_my_plugin_first(self):
688
709
        self.check_path(['myplugin', self.core, self.site, self.user],
715
736
        self.create_plugin_package('ugly')
716
737
        self.overrideEnv('BRZ_DISABLE_PLUGINS', 'bad:ugly')
717
738
        self.load_with_paths(['.'])
718
 
        self.assertEqual({'good'}, self.plugins.keys())
 
739
        self.assertEqual({'good'}, viewkeys(self.plugins))
719
740
        self.assertPluginModules({
720
741
            'good': self.plugins['good'].module,
721
742
            'bad': None,
748
769
    def test_mixed(self):
749
770
        value = os.pathsep.join(['valid', 'in-valid'])
750
771
        self.assertEqual(['valid'], self._get_names(value))
751
 
        self.assertContainsRe(
752
 
            self.get_log(),
 
772
        self.assertContainsRe(self.get_log(),
753
773
            r"Invalid name 'in-valid' in BRZ_DISABLE_PLUGINS=" + repr(value))
754
774
 
755
775
 
784
804
 
785
805
    def test_bad_name(self):
786
806
        self.assertEqual([], self._get_paths('/usr/local/bzr-git'))
787
 
        self.assertContainsRe(
788
 
            self.get_log(),
 
807
        self.assertContainsRe(self.get_log(),
789
808
            r"Invalid name 'bzr-git' in BRZ_PLUGINS_AT='/usr/local/bzr-git'")
790
809
 
791
810
 
848
867
        self.create_plugin_package('test_bar', dir='non-standard-dir/test_bar')
849
868
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
850
869
        self.update_module_paths(['standard'])
851
 
        import breezy.testingplugins.test_foo  # noqa: F401
 
870
        import breezy.testingplugins.test_foo
852
871
        self.assertEqual(self.module_prefix + 'test_foo',
853
872
                         self.module.test_foo.__package__)
854
 
        import breezy.testingplugins.test_foo.test_bar  # noqa: F401
 
873
        import breezy.testingplugins.test_foo.test_bar
855
874
        self.assertIsSameRealPath('non-standard-dir/test_bar/__init__.py',
856
875
                                  self.module.test_foo.test_bar.__file__)
857
876
 
863
882
        self.create_plugin_package('test_bar', dir='another-dir/test_bar')
864
883
        self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@another-dir')
865
884
        self.update_module_paths(['standard'])
866
 
        import breezy.testingplugins.test_foo  # noqa: F401
 
885
        import breezy.testingplugins.test_foo
867
886
        self.assertEqual(self.module_prefix + 'test_foo',
868
887
                         self.module.test_foo.__package__)
869
888
        self.assertIsSameRealPath('another-dir/test_bar/__init__.py',
899
918
    def test_describe_plugins(self):
900
919
        class DummyModule(object):
901
920
            __doc__ = 'Hi there'
902
 
 
903
921
        class DummyPlugin(object):
904
922
            __version__ = '0.1.0'
905
923
            module = DummyModule()
913
931
  Hi there
914
932
 
915
933
""", ''.join(plugin.describe_plugins(state=self)))
916
 
 
917
 
 
918
 
class DummyPlugin(object):
919
 
    """Plugin."""
920
 
 
921
 
 
922
 
class TestLoadEnvPlugin(BaseTestPlugins):
923
 
 
924
 
    _test_needs_features = [pkg_resources_feature]
925
 
 
926
 
    def setup_plugin(self, source=""):
927
 
        # This test tests a new plugin appears in breezy.plugin.plugins().
928
 
        # check the plugin is not loaded already
929
 
        self.assertPluginUnknown('plugin')
930
 
        # write a plugin that _cannot_ fail to load.
931
 
        import pkg_resources
932
 
        d = pkg_resources.Distribution(__file__)
933
 
        ep = pkg_resources.EntryPoint.parse(
934
 
            'plugin = ' + __name__ + ':DummyPlugin', dist=d)
935
 
        d._ep_map = {'breezy.plugin': {'plugin': ep}}
936
 
        pkg_resources.working_set.add(d, 'plugin', replace=True)
937
 
        eps = list(pkg_resources.iter_entry_points('breezy.plugin'))
938
 
        self.assertEqual(['plugin'], [ep.name for ep in eps])
939
 
        self.load_with_paths(['.'])
940
 
        self.addCleanup(d._ep_map.clear)
941
 
 
942
 
    def test_plugin_loaded(self):
943
 
        self.assertPluginUnknown('plugin')
944
 
        self.overrideEnv('BRZ_PLUGIN_PATH', '+entrypoints')
945
 
        self.setup_plugin()
946
 
        p = self.plugins['plugin']
947
 
        self.assertIsInstance(p, breezy.plugin.PlugIn)
948
 
        self.assertIs(p.module, sys.modules[self.module_prefix + 'plugin'])
949
 
 
950
 
    def test_plugin_loaded_disabled(self):
951
 
        self.assertPluginUnknown('plugin')
952
 
        self.overrideEnv('BRZ_PLUGIN_PATH', '+entrypoints')
953
 
        self.overrideEnv('BRZ_DISABLE_PLUGINS', 'plugin')
954
 
        self.setup_plugin()
955
 
        self.assertNotIn('plugin', self.plugins)