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
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
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)
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)
79
def load_with_paths(self, paths, warn_load_problems=True):
83
def load_with_paths(self, paths):
80
84
self.log("loading 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)
85
87
def create_plugin(self, name, source=None, dir='.', file_name=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))
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)),
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] = [
205
breezy.tests.test_plugins.TestLoadingPlugins.activeattributes \
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])
232
234
del self.activeattributes[tempattribute]
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] = [
246
breezy.tests.test_plugins.TestLoadingPlugins.activeattributes \
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')
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')
263
265
del self.activeattributes[tempattribute]
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.
268
270
:param name: The name of the plugin.
289
290
def test_plugin_with_bad_api_version_reports(self):
290
291
"""Try loading a plugin that requests an unsupported api.
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.
295
See https://bugs.launchpad.net/bzr/+bug/704195
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_'\\.")
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)
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)
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_'\.")
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(['.'])
346
def test_plugin_loaded(self):
347
self.assertPluginUnknown('plugin')
348
self.assertIs(None, breezy.plugin.get_loaded_plugin('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'])
354
def test_plugin_loaded_disabled(self):
355
self.assertPluginUnknown('plugin')
356
self.overrideEnv('BRZ_DISABLE_PLUGINS', 'plugin')
358
self.assertIs(None, breezy.plugin.get_loaded_plugin('plugin'))
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)
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)
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__)
468
# GZ 2017-06-02: Move this suite to blackbox, as it's what it actually is.
469
class TestPluginHelp(BaseTestPlugins):
471
def split_help_commands(self):
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
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():
489
help = breezy.commands.get_cmd_object(cmd_name).get_help_text()
490
except NotImplementedError:
491
# some commands have no help
494
self.assertNotContainsRe(help, 'plugin "[^"]*"')
496
if cmd_name in help_commands:
497
# some commands are hidden
498
help = help_commands[cmd_name]
499
self.assertNotContainsRe(help, 'plugin "[^"]*"')
501
def test_plugin_help_shows_plugin(self):
502
# Create a test plugin
503
os.mkdir('plugin_test')
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"
510
" print ('Hello from my plugin')\n"
512
self.create_plugin('myplug', source, 'plugin_test')
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\]')
497
525
class TestHelpIndex(tests.TestCase):
498
526
"""Tests for the PluginsHelpIndex class."""
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())
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())
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())
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)
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']))
596
623
def test_get_help_topic(self):
597
624
"""The help topic for a plugin is its module name."""
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)
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],
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'])
638
def test_enable_entrypoints(self):
639
self.check_path([self.user, self.core, self.site, self.entrypoints],
640
['+user', '+core', '+site', '+entrypoints'])
642
665
def test_disable_user(self):
643
666
self.check_path([self.core, self.site], ['-user'])
680
703
['mycore', '-core'])
682
705
def test_my_plugin_only(self):
685
['myplugin', '-user', '-core', '-site', '-entrypoints'])
706
self.check_path(['myplugin'], ['myplugin', '-user', '-core', '-site'])
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,
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(
772
self.assertContainsRe(self.get_log(),
753
773
r"Invalid name 'in-valid' in BRZ_DISABLE_PLUGINS=" + repr(value))
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__)
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',
915
933
""", ''.join(plugin.describe_plugins(state=self)))
918
class DummyPlugin(object):
922
class TestLoadEnvPlugin(BaseTestPlugins):
924
_test_needs_features = [pkg_resources_feature]
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.
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)
942
def test_plugin_loaded(self):
943
self.assertPluginUnknown('plugin')
944
self.overrideEnv('BRZ_PLUGIN_PATH', '+entrypoints')
946
p = self.plugins['plugin']
947
self.assertIsInstance(p, breezy.plugin.PlugIn)
948
self.assertIs(p.module, sys.modules[self.module_prefix + 'plugin'])
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')
955
self.assertNotIn('plugin', self.plugins)