1
# Copyright (C) 2005-2012, 2016 Canonical Ltd, 2017 Breezy developers
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for plugins"""
20
from importlib.util import module_from_spec
21
except ImportError: # python < 3
22
from imp import new_module as module_from_spec
36
from ..sixish import (
43
# TODO: Write a test for plugin decoration of commands.
45
invalidate_caches = getattr(importlib, "invalidate_caches", lambda: None)
48
class BaseTestPlugins(tests.TestCaseInTempDir):
49
"""TestCase that isolates plugin imports and cleans up on completion."""
52
super(BaseTestPlugins, self).setUp()
53
self.module_name = "breezy.testingplugins"
54
self.module_prefix = self.module_name + "."
55
self.module = module_from_spec(self.module_name)
57
self.overrideAttr(plugin, "_MODULE_PREFIX", self.module_prefix)
58
self.overrideAttr(breezy, "testingplugins", self.module)
60
sys.modules[self.module_name] = self.module
61
self.addCleanup(self._unregister_all)
62
self.addCleanup(self._unregister_finder)
67
"""Remove all global testing state and clean up module."""
68
# GZ 2017-06-02: Ideally don't do this, write new test or generate
69
# bytecode by other mechanism.
70
self.log("resetting plugin testing context")
71
self._unregister_all()
72
self._unregister_finder()
73
sys.modules[self.module_name] = self.module
74
for name in list(self.module.__dict__):
76
delattr(self.module, name)
80
def update_module_paths(self, paths):
81
paths = plugin.extend_path(paths, self.module_name)
82
self.module.__path__ = paths
83
self.log("using %r", paths)
86
def load_with_paths(self, paths):
87
self.log("loading plugins!")
88
plugin.load_plugins(self.update_module_paths(paths), state=self)
90
def create_plugin(self, name, source=None, dir='.', file_name=None):
93
"""This is the doc for %s"""
96
file_name = name + '.py'
97
# 'source' must not fail to load
98
path = osutils.pathjoin(dir, file_name)
99
with open(path, 'w') as f:
100
f.write(source + '\n')
102
def create_plugin_package(self, name, dir=None, source=None):
107
"""This is the doc for %s"""
111
self.create_plugin(name, source, dir,
112
file_name='__init__.py')
114
def promote_cache(self, directory):
115
"""Move bytecode files out of __pycache__ in given directory."""
116
cache_dir = os.path.join(directory, '__pycache__')
117
if os.path.isdir(cache_dir):
118
for name in os.listdir(cache_dir):
119
magicless_name = '.'.join(name.split('.')[0::name.count('.')])
120
rel = osutils.relpath(self.test_dir, cache_dir)
121
self.log("moving %s in %s to %s", name, rel, magicless_name)
122
os.rename(os.path.join(cache_dir, name),
123
os.path.join(directory, magicless_name))
125
def _unregister_finder(self):
126
"""Removes any test copies of _PluginsAtFinder from sys.meta_path."""
127
idx = len(sys.meta_path)
130
finder = sys.meta_path[idx]
131
if getattr(finder, "prefix", "") == self.module_prefix:
132
self.log("removed %r from sys.meta_path", finder)
133
sys.meta_path.pop(idx)
135
def _unregister_all(self):
136
"""Remove all plugins in the test namespace from sys.modules."""
137
for name in list(sys.modules):
138
if name.startswith(self.module_prefix) or name == self.module_name:
139
self.log("removed %s from sys.modules", name)
140
del sys.modules[name]
142
def assertPluginModules(self, plugin_dict):
144
dict((k[len(self.module_prefix):], sys.modules[k])
145
for k in sys.modules if k.startswith(self.module_prefix)),
148
def assertPluginUnknown(self, name):
149
self.assertTrue(getattr(self.module, name, None) is None)
150
self.assertFalse(self.module_prefix + name in sys.modules)
152
def assertPluginKnown(self, name):
153
self.assertTrue(getattr(self.module, name, None) is not None)
154
self.assertTrue(self.module_prefix + name in sys.modules)
157
class TestLoadingPlugins(BaseTestPlugins):
159
activeattributes = {}
161
def test_plugins_with_the_same_name_are_not_loaded(self):
162
# This test tests that having two plugins in different directories does
163
# not result in both being loaded when they have the same name. get a
164
# file name we can use which is also a valid attribute for accessing in
165
# activeattributes. - we cannot give import parameters.
167
self.assertFalse(tempattribute in self.activeattributes)
168
# set a place for the plugins to record their loading, and at the same
169
# time validate that the location the plugins should record to is
171
self.__class__.activeattributes [tempattribute] = []
172
self.assertTrue(tempattribute in self.activeattributes)
173
# create two plugin directories
176
# write a plugin that will record when its loaded in the
177
# tempattribute list.
178
template = ("from breezy.tests.test_plugins import TestLoadingPlugins\n"
179
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
181
with open(os.path.join('first', 'plugin.py'), 'w') as outfile:
182
outfile.write(template % (tempattribute, 'first'))
185
with open(os.path.join('second', 'plugin.py'), 'w') as outfile:
186
outfile.write(template % (tempattribute, 'second'))
190
self.load_with_paths(['first', 'second'])
191
self.assertEqual(['first'], self.activeattributes[tempattribute])
193
del self.activeattributes[tempattribute]
195
def test_plugins_from_different_dirs_can_demand_load(self):
196
self.assertFalse('breezy.plugins.pluginone' in sys.modules)
197
self.assertFalse('breezy.plugins.plugintwo' in sys.modules)
198
# This test tests that having two plugins in different
199
# directories with different names allows them both to be loaded, when
200
# we do a direct import statement.
201
# Determine a file name we can use which is also a valid attribute
202
# for accessing in activeattributes. - we cannot give import parameters.
203
tempattribute = "different-dirs"
204
self.assertFalse(tempattribute in self.activeattributes)
205
# set a place for the plugins to record their loading, and at the same
206
# time validate that the location the plugins should record to is
208
breezy.tests.test_plugins.TestLoadingPlugins.activeattributes \
210
self.assertTrue(tempattribute in self.activeattributes)
211
# create two plugin directories
214
# write plugins that will record when they are loaded in the
215
# tempattribute list.
216
template = ("from breezy.tests.test_plugins import TestLoadingPlugins\n"
217
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
219
with open(os.path.join('first', 'pluginone.py'), 'w') as outfile:
220
outfile.write(template % (tempattribute, 'first'))
223
with open(os.path.join('second', 'plugintwo.py'), 'w') as outfile:
224
outfile.write(template % (tempattribute, 'second'))
228
self.assertPluginUnknown('pluginone')
229
self.assertPluginUnknown('plugintwo')
230
self.update_module_paths(['first', 'second'])
231
exec("import %spluginone" % self.module_prefix)
232
self.assertEqual(['first'], self.activeattributes[tempattribute])
233
exec("import %splugintwo" % self.module_prefix)
234
self.assertEqual(['first', 'second'],
235
self.activeattributes[tempattribute])
237
del self.activeattributes[tempattribute]
239
def test_plugins_can_load_from_directory_with_trailing_slash(self):
240
# This test tests that a plugin can load from a directory when the
241
# directory in the path has a trailing slash.
242
# check the plugin is not loaded already
243
self.assertPluginUnknown('ts_plugin')
244
tempattribute = "trailing-slash"
245
self.assertFalse(tempattribute in self.activeattributes)
246
# set a place for the plugin to record its loading, and at the same
247
# time validate that the location the plugin should record to is
249
breezy.tests.test_plugins.TestLoadingPlugins.activeattributes \
251
self.assertTrue(tempattribute in self.activeattributes)
252
# create a directory for the plugin
253
os.mkdir('plugin_test')
254
# write a plugin that will record when its loaded in the
255
# tempattribute list.
256
template = ("from breezy.tests.test_plugins import TestLoadingPlugins\n"
257
"TestLoadingPlugins.activeattributes[%r].append('%s')\n")
259
with open(os.path.join('plugin_test', 'ts_plugin.py'), 'w') as outfile:
260
outfile.write(template % (tempattribute, 'plugin'))
264
self.load_with_paths(['plugin_test'+os.sep])
265
self.assertEqual(['plugin'], self.activeattributes[tempattribute])
266
self.assertPluginKnown('ts_plugin')
268
del self.activeattributes[tempattribute]
270
def load_and_capture(self, name):
271
"""Load plugins from '.' capturing the output.
273
:param name: The name of the plugin.
274
:return: A string with the log from the plugin loading call.
279
handler = logging.StreamHandler(stream)
280
log = logging.getLogger('brz')
281
log.addHandler(handler)
283
self.load_with_paths(['.'])
285
# Stop capturing output
288
log.removeHandler(handler)
289
return stream.getvalue()
293
def test_plugin_with_bad_api_version_reports(self):
294
"""Try loading a plugin that requests an unsupported api.
296
Observe that it records the problem but doesn't complain on stderr.
298
See https://bugs.launchpad.net/bzr/+bug/704195
301
with open(name, 'w') as f:
302
f.write("import breezy\n"
303
"from breezy.errors import IncompatibleVersion\n"
304
"raise IncompatibleVersion(breezy, [(1, 0, 0)], (0, 0, 5))\n")
305
log = self.load_and_capture(name)
306
self.assertNotContainsRe(log,
307
r"It supports breezy version")
308
self.assertEqual({'wants100'}, viewkeys(self.plugin_warnings))
309
self.assertContainsRe(
310
self.plugin_warnings['wants100'][0],
311
r"It supports breezy version")
313
def test_plugin_with_bad_name_does_not_load(self):
314
# The file name here invalid for a python module.
315
name = 'brz-bad plugin-name..py'
316
open(name, 'w').close()
317
log = self.load_and_capture(name)
318
self.assertContainsRe(log,
319
r"Unable to load 'brz-bad plugin-name\.' in '\.' as a plugin "
320
"because the file path isn't a valid module name; try renaming "
321
"it to 'bad_plugin_name_'\\.")
324
class TestPlugins(BaseTestPlugins):
326
def setup_plugin(self, source=""):
327
# This test tests a new plugin appears in breezy.plugin.plugins().
328
# check the plugin is not loaded already
329
self.assertPluginUnknown('plugin')
330
# write a plugin that _cannot_ fail to load.
331
with open('plugin.py', 'w') as f: f.write(source + '\n')
332
self.load_with_paths(['.'])
334
def test_plugin_loaded(self):
335
self.assertPluginUnknown('plugin')
336
self.assertIs(None, breezy.plugin.get_loaded_plugin('plugin'))
338
p = breezy.plugin.get_loaded_plugin('plugin')
339
self.assertIsInstance(p, breezy.plugin.PlugIn)
340
self.assertIs(p.module, sys.modules[self.module_prefix + 'plugin'])
342
def test_plugin_loaded_disabled(self):
343
self.assertPluginUnknown('plugin')
344
self.overrideEnv('BRZ_DISABLE_PLUGINS', 'plugin')
346
self.assertIs(None, breezy.plugin.get_loaded_plugin('plugin'))
348
def test_plugin_appears_in_plugins(self):
350
self.assertPluginKnown('plugin')
351
p = self.plugins['plugin']
352
self.assertIsInstance(p, breezy.plugin.PlugIn)
353
self.assertIs(p.module, sys.modules[self.module_prefix + 'plugin'])
355
def test_trivial_plugin_get_path(self):
357
p = self.plugins['plugin']
358
plugin_path = self.test_dir + '/plugin.py'
359
self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
361
def test_plugin_get_path_py_not_pyc(self):
362
# first import creates plugin.pyc
364
self.promote_cache(self.test_dir)
366
self.load_with_paths(['.']) # import plugin.pyc
367
p = plugin.plugins()['plugin']
368
plugin_path = self.test_dir + '/plugin.py'
369
self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
371
def test_plugin_get_path_pyc_only(self):
372
# first import creates plugin.pyc (or plugin.pyo depending on __debug__)
374
os.unlink(self.test_dir + '/plugin.py')
375
self.promote_cache(self.test_dir)
377
self.load_with_paths(['.']) # import plugin.pyc (or .pyo)
378
p = plugin.plugins()['plugin']
379
plugin_path = self.test_dir + '/plugin' + plugin.COMPILED_EXT
380
self.assertIsSameRealPath(plugin_path, osutils.normpath(p.path()))
382
def test_no_test_suite_gives_None_for_test_suite(self):
384
p = plugin.plugins()['plugin']
385
self.assertEqual(None, p.test_suite())
387
def test_test_suite_gives_test_suite_result(self):
388
source = """def test_suite(): return 'foo'"""
389
self.setup_plugin(source)
390
p = plugin.plugins()['plugin']
391
self.assertEqual('foo', p.test_suite())
393
def test_no_load_plugin_tests_gives_None_for_load_plugin_tests(self):
395
loader = tests.TestUtil.TestLoader()
396
p = plugin.plugins()['plugin']
397
self.assertEqual(None, p.load_plugin_tests(loader))
399
def test_load_plugin_tests_gives_load_plugin_tests_result(self):
401
def load_tests(loader, standard_tests, pattern):
403
self.setup_plugin(source)
404
loader = tests.TestUtil.TestLoader()
405
p = plugin.plugins()['plugin']
406
self.assertEqual('foo', p.load_plugin_tests(loader))
408
def check_version_info(self, expected, source='', name='plugin'):
409
self.setup_plugin(source)
410
self.assertEqual(expected, plugin.plugins()[name].version_info())
412
def test_no_version_info(self):
413
self.check_version_info(None)
415
def test_with_version_info(self):
416
self.check_version_info((1, 2, 3, 'dev', 4),
417
"version_info = (1, 2, 3, 'dev', 4)")
419
def test_short_version_info_gets_padded(self):
420
# the gtk plugin has version_info = (1,2,3) rather than the 5-tuple.
422
self.check_version_info((1, 2, 3, 'final', 0),
423
"version_info = (1, 2, 3)")
425
def check_version(self, expected, source=None, name='plugin'):
426
self.setup_plugin(source)
427
self.assertEqual(expected, plugins[name].__version__)
429
def test_no_version_info___version__(self):
431
plugin = breezy.plugin.plugins()['plugin']
432
self.assertEqual("unknown", plugin.__version__)
434
def test_str__version__with_version_info(self):
435
self.setup_plugin("version_info = '1.2.3'")
436
plugin = breezy.plugin.plugins()['plugin']
437
self.assertEqual("1.2.3", plugin.__version__)
439
def test_noniterable__version__with_version_info(self):
440
self.setup_plugin("version_info = (1)")
441
plugin = breezy.plugin.plugins()['plugin']
442
self.assertEqual("1", plugin.__version__)
444
def test_1__version__with_version_info(self):
445
self.setup_plugin("version_info = (1,)")
446
plugin = breezy.plugin.plugins()['plugin']
447
self.assertEqual("1", plugin.__version__)
449
def test_1_2__version__with_version_info(self):
450
self.setup_plugin("version_info = (1, 2)")
451
plugin = breezy.plugin.plugins()['plugin']
452
self.assertEqual("1.2", plugin.__version__)
454
def test_1_2_3__version__with_version_info(self):
455
self.setup_plugin("version_info = (1, 2, 3)")
456
plugin = breezy.plugin.plugins()['plugin']
457
self.assertEqual("1.2.3", plugin.__version__)
459
def test_candidate__version__with_version_info(self):
460
self.setup_plugin("version_info = (1, 2, 3, 'candidate', 1)")
461
plugin = breezy.plugin.plugins()['plugin']
462
self.assertEqual("1.2.3rc1", plugin.__version__)
464
def test_dev__version__with_version_info(self):
465
self.setup_plugin("version_info = (1, 2, 3, 'dev', 0)")
466
plugin = breezy.plugin.plugins()['plugin']
467
self.assertEqual("1.2.3dev", plugin.__version__)
469
def test_dev_fallback__version__with_version_info(self):
470
self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
471
plugin = breezy.plugin.plugins()['plugin']
472
self.assertEqual("1.2.3dev4", plugin.__version__)
474
def test_final__version__with_version_info(self):
475
self.setup_plugin("version_info = (1, 2, 3, 'final', 0)")
476
plugin = breezy.plugin.plugins()['plugin']
477
self.assertEqual("1.2.3", plugin.__version__)
479
def test_final_fallback__version__with_version_info(self):
480
self.setup_plugin("version_info = (1, 2, 3, 'final', 2)")
481
plugin = breezy.plugin.plugins()['plugin']
482
self.assertEqual("1.2.3.2", plugin.__version__)
485
# GZ 2017-06-02: Move this suite to blackbox, as it's what it actually is.
486
class TestPluginHelp(BaseTestPlugins):
488
def split_help_commands(self):
491
out, err = self.run_bzr('--no-plugins help commands')
492
for line in out.splitlines():
493
if not line.startswith(' '):
494
current = line.split()[0]
495
help[current] = help.get(current, '') + line
499
def test_plugin_help_builtins_unaffected(self):
500
# Check we don't get false positives
501
help_commands = self.split_help_commands()
502
for cmd_name in breezy.commands.builtin_command_names():
503
if cmd_name in breezy.commands.plugin_command_names():
506
help = breezy.commands.get_cmd_object(cmd_name).get_help_text()
507
except NotImplementedError:
508
# some commands have no help
511
self.assertNotContainsRe(help, 'plugin "[^"]*"')
513
if cmd_name in help_commands:
514
# some commands are hidden
515
help = help_commands[cmd_name]
516
self.assertNotContainsRe(help, 'plugin "[^"]*"')
518
def test_plugin_help_shows_plugin(self):
519
# Create a test plugin
520
os.mkdir('plugin_test')
522
"from breezy import commands\n"
523
"class cmd_myplug(commands.Command):\n"
524
" __doc__ = '''Just a simple test plugin.'''\n"
525
" aliases = ['mplg']\n"
527
" print ('Hello from my plugin')\n"
529
self.create_plugin('myplug', source, 'plugin_test')
532
self.load_with_paths(['plugin_test'])
533
myplug = self.plugins['myplug'].module
534
breezy.commands.register_command(myplug.cmd_myplug)
535
self.addCleanup(breezy.commands.plugin_cmds.remove, 'myplug')
536
help = self.run_bzr('help myplug')[0]
537
self.assertContainsRe(help, 'plugin "myplug"')
538
help = self.split_help_commands()['myplug']
539
self.assertContainsRe(help, '\\[myplug\\]')
542
class TestHelpIndex(tests.TestCase):
543
"""Tests for the PluginsHelpIndex class."""
545
def test_default_constructable(self):
546
index = plugin.PluginsHelpIndex()
548
def test_get_topics_None(self):
549
"""Searching for None returns an empty list."""
550
index = plugin.PluginsHelpIndex()
551
self.assertEqual([], index.get_topics(None))
553
def test_get_topics_for_plugin(self):
554
"""Searching for plugin name gets its docstring."""
555
index = plugin.PluginsHelpIndex()
556
# make a new plugin here for this test, even if we're run with
558
self.assertFalse('breezy.plugins.demo_module' in sys.modules)
559
demo_module = FakeModule('', 'breezy.plugins.demo_module')
560
sys.modules['breezy.plugins.demo_module'] = demo_module
562
topics = index.get_topics('demo_module')
563
self.assertEqual(1, len(topics))
564
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
565
self.assertEqual(demo_module, topics[0].module)
567
del sys.modules['breezy.plugins.demo_module']
569
def test_get_topics_no_topic(self):
570
"""Searching for something that is not a plugin returns []."""
571
# test this by using a name that cannot be a plugin - its not
572
# a valid python identifier.
573
index = plugin.PluginsHelpIndex()
574
self.assertEqual([], index.get_topics('nothing by this name'))
576
def test_prefix(self):
577
"""PluginsHelpIndex has a prefix of 'plugins/'."""
578
index = plugin.PluginsHelpIndex()
579
self.assertEqual('plugins/', index.prefix)
581
def test_get_plugin_topic_with_prefix(self):
582
"""Searching for plugins/demo_module returns help."""
583
index = plugin.PluginsHelpIndex()
584
self.assertFalse('breezy.plugins.demo_module' in sys.modules)
585
demo_module = FakeModule('', 'breezy.plugins.demo_module')
586
sys.modules['breezy.plugins.demo_module'] = demo_module
588
topics = index.get_topics('plugins/demo_module')
589
self.assertEqual(1, len(topics))
590
self.assertIsInstance(topics[0], plugin.ModuleHelpTopic)
591
self.assertEqual(demo_module, topics[0].module)
593
del sys.modules['breezy.plugins.demo_module']
596
class FakeModule(object):
597
"""A fake module to test with."""
599
def __init__(self, doc, name):
604
class TestModuleHelpTopic(tests.TestCase):
605
"""Tests for the ModuleHelpTopic class."""
607
def test_contruct(self):
608
"""Construction takes the module to document."""
609
mod = FakeModule('foo', 'foo')
610
topic = plugin.ModuleHelpTopic(mod)
611
self.assertEqual(mod, topic.module)
613
def test_get_help_text_None(self):
614
"""A ModuleHelpTopic returns the docstring for get_help_text."""
615
mod = FakeModule(None, 'demo')
616
topic = plugin.ModuleHelpTopic(mod)
617
self.assertEqual("Plugin 'demo' has no docstring.\n",
618
topic.get_help_text())
620
def test_get_help_text_no_carriage_return(self):
621
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
622
mod = FakeModule('one line of help', 'demo')
623
topic = plugin.ModuleHelpTopic(mod)
624
self.assertEqual("one line of help\n",
625
topic.get_help_text())
627
def test_get_help_text_carriage_return(self):
628
"""ModuleHelpTopic.get_help_text adds a \n if needed."""
629
mod = FakeModule('two lines of help\nand more\n', 'demo')
630
topic = plugin.ModuleHelpTopic(mod)
631
self.assertEqual("two lines of help\nand more\n",
632
topic.get_help_text())
634
def test_get_help_text_with_additional_see_also(self):
635
mod = FakeModule('two lines of help\nand more', 'demo')
636
topic = plugin.ModuleHelpTopic(mod)
637
self.assertEqual("two lines of help\nand more\n\n:See also: bar, foo\n",
638
topic.get_help_text(['foo', 'bar']))
640
def test_get_help_topic(self):
641
"""The help topic for a plugin is its module name."""
642
mod = FakeModule('two lines of help\nand more', 'breezy.plugins.demo')
643
topic = plugin.ModuleHelpTopic(mod)
644
self.assertEqual('demo', topic.get_help_topic())
645
mod = FakeModule('two lines of help\nand more',
646
'breezy.plugins.foo_bar')
647
topic = plugin.ModuleHelpTopic(mod)
648
self.assertEqual('foo_bar', topic.get_help_topic())
651
class TestEnvPluginPath(tests.TestCase):
657
def check_path(self, expected_dirs, setting_dirs):
658
if setting_dirs is None:
659
del os.environ['BRZ_PLUGIN_PATH']
661
os.environ['BRZ_PLUGIN_PATH'] = os.pathsep.join(setting_dirs)
662
actual = [(p if t == 'path' else t.upper())
663
for p, t in plugin._env_plugin_path()]
664
self.assertEqual(expected_dirs, actual)
666
def test_default(self):
667
self.check_path([self.user, self.core, self.site],
670
def test_adhoc_policy(self):
671
self.check_path([self.user, self.core, self.site],
672
['+user', '+core', '+site'])
674
def test_fallback_policy(self):
675
self.check_path([self.core, self.site, self.user],
676
['+core', '+site', '+user'])
678
def test_override_policy(self):
679
self.check_path([self.user, self.site, self.core],
680
['+user', '+site', '+core'])
682
def test_disable_user(self):
683
self.check_path([self.core, self.site], ['-user'])
685
def test_disable_user_twice(self):
686
# Ensures multiple removals don't left cruft
687
self.check_path([self.core, self.site], ['-user', '-user'])
689
def test_duplicates_are_removed(self):
690
self.check_path([self.user, self.core, self.site],
692
# And only the first reference is kept (since the later references will
693
# only produce '<plugin> already loaded' mutters)
694
self.check_path([self.user, self.core, self.site],
695
['+user', '+user', '+core',
696
'+user', '+site', '+site',
699
def test_disable_overrides_enable(self):
700
self.check_path([self.core, self.site], ['-user', '+user'])
702
def test_disable_core(self):
703
self.check_path([self.site], ['-core'])
704
self.check_path([self.user, self.site], ['+user', '-core'])
706
def test_disable_site(self):
707
self.check_path([self.core], ['-site'])
708
self.check_path([self.user, self.core], ['-site', '+user'])
710
def test_override_site(self):
711
self.check_path(['mysite', self.user, self.core],
712
['mysite', '-site', '+user'])
713
self.check_path(['mysite', self.core],
716
def test_override_core(self):
717
self.check_path(['mycore', self.user, self.site],
718
['mycore', '-core', '+user', '+site'])
719
self.check_path(['mycore', self.site],
722
def test_my_plugin_only(self):
723
self.check_path(['myplugin'], ['myplugin', '-user', '-core', '-site'])
725
def test_my_plugin_first(self):
726
self.check_path(['myplugin', self.core, self.site, self.user],
727
['myplugin', '+core', '+site', '+user'])
729
def test_bogus_references(self):
730
self.check_path(['+foo', '-bar', self.core, self.site],
734
class TestDisablePlugin(BaseTestPlugins):
736
def test_cannot_import(self):
737
self.create_plugin_package('works')
738
self.create_plugin_package('fails')
739
self.overrideEnv('BRZ_DISABLE_PLUGINS', 'fails')
740
self.update_module_paths(["."])
741
import breezy.testingplugins.works as works
743
import breezy.testingplugins.fails as fails
747
self.fail("Loaded blocked plugin: " + repr(fails))
748
self.assertPluginModules({'fails': None, 'works': works})
750
def test_partial_imports(self):
751
self.create_plugin('good')
752
self.create_plugin('bad')
753
self.create_plugin_package('ugly')
754
self.overrideEnv('BRZ_DISABLE_PLUGINS', 'bad:ugly')
755
self.load_with_paths(['.'])
756
self.assertEqual({'good'}, viewkeys(self.plugins))
757
self.assertPluginModules({
758
'good': self.plugins['good'].module,
762
# Ensure there are no warnings about plugins not being imported as
763
# the user has explictly requested they be disabled.
764
self.assertNotContainsRe(self.get_log(), r"Unable to load plugin")
767
class TestEnvDisablePlugins(tests.TestCase):
769
def _get_names(self, env_value):
770
os.environ['BRZ_DISABLE_PLUGINS'] = env_value
771
return plugin._env_disable_plugins()
773
def test_unset(self):
774
self.assertEqual([], plugin._env_disable_plugins())
776
def test_empty(self):
777
self.assertEqual([], self._get_names(''))
779
def test_single(self):
780
self.assertEqual(['single'], self._get_names('single'))
782
def test_multi(self):
783
expected = ['one', 'two']
784
self.assertEqual(expected, self._get_names(os.pathsep.join(expected)))
786
def test_mixed(self):
787
value = os.pathsep.join(['valid', 'in-valid'])
788
self.assertEqual(['valid'], self._get_names(value))
789
self.assertContainsRe(self.get_log(),
790
r"Invalid name 'in-valid' in BRZ_DISABLE_PLUGINS=" + repr(value))
793
class TestEnvPluginsAt(tests.TestCase):
795
def _get_paths(self, env_value):
796
os.environ['BRZ_PLUGINS_AT'] = env_value
797
return plugin._env_plugins_at()
799
def test_empty(self):
800
self.assertEqual([], plugin._env_plugins_at())
801
self.assertEqual([], self._get_paths(''))
803
def test_one_path(self):
804
self.assertEqual([('b', 'man')], self._get_paths('b@man'))
806
def test_multiple(self):
808
[('tools', 'bzr-tools'), ('p', 'play.py')],
809
self._get_paths(os.pathsep.join(('tools@bzr-tools', 'p@play.py'))))
811
def test_many_at(self):
813
[('church', 'StMichael@Plea@Norwich')],
814
self._get_paths('church@StMichael@Plea@Norwich'))
816
def test_only_py(self):
817
self.assertEqual([('test', './test.py')], self._get_paths('./test.py'))
819
def test_only_package(self):
820
self.assertEqual([('py', '/opt/b/py')], self._get_paths('/opt/b/py'))
822
def test_bad_name(self):
823
self.assertEqual([], self._get_paths('/usr/local/bzr-git'))
824
self.assertContainsRe(self.get_log(),
825
r"Invalid name 'bzr-git' in BRZ_PLUGINS_AT='/usr/local/bzr-git'")
828
class TestLoadPluginAt(BaseTestPlugins):
831
super(TestLoadPluginAt, self).setUp()
832
# Create the same plugin in two directories
833
self.create_plugin_package('test_foo', dir='non-standard-dir')
834
# The "normal" directory, we use 'standard' instead of 'plugins' to
835
# avoid depending on the precise naming.
836
self.create_plugin_package('test_foo', dir='standard/test_foo')
838
def assertTestFooLoadedFrom(self, path):
839
self.assertPluginKnown('test_foo')
840
self.assertDocstring('This is the doc for test_foo',
841
self.module.test_foo)
842
self.assertEqual(path, self.module.test_foo.dir_source)
844
def test_regular_load(self):
845
self.load_with_paths(['standard'])
846
self.assertTestFooLoadedFrom('standard/test_foo')
848
def test_import(self):
849
self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
850
self.update_module_paths(['standard'])
851
import breezy.testingplugins.test_foo
852
self.assertTestFooLoadedFrom('non-standard-dir')
854
def test_loading(self):
855
self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
856
self.load_with_paths(['standard'])
857
self.assertTestFooLoadedFrom('non-standard-dir')
859
def test_loading_other_name(self):
860
self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
861
os.rename('standard/test_foo', 'standard/test_bar')
862
self.load_with_paths(['standard'])
863
self.assertTestFooLoadedFrom('non-standard-dir')
865
def test_compiled_loaded(self):
866
self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
867
self.load_with_paths(['standard'])
868
self.assertTestFooLoadedFrom('non-standard-dir')
869
self.assertIsSameRealPath('non-standard-dir/__init__.py',
870
self.module.test_foo.__file__)
872
# Try importing again now that the source has been compiled
873
os.remove('non-standard-dir/__init__.py')
874
self.promote_cache('non-standard-dir')
876
self.load_with_paths(['standard'])
877
self.assertTestFooLoadedFrom('non-standard-dir')
878
suffix = plugin.COMPILED_EXT
879
self.assertIsSameRealPath('non-standard-dir/__init__' + suffix,
880
self.module.test_foo.__file__)
882
def test_submodule_loading(self):
883
# We create an additional directory under the one for test_foo
884
self.create_plugin_package('test_bar', dir='non-standard-dir/test_bar')
885
self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
886
self.update_module_paths(['standard'])
887
import breezy.testingplugins.test_foo
888
self.assertEqual(self.module_prefix + 'test_foo',
889
self.module.test_foo.__package__)
890
import breezy.testingplugins.test_foo.test_bar
891
self.assertIsSameRealPath('non-standard-dir/test_bar/__init__.py',
892
self.module.test_foo.test_bar.__file__)
894
def test_relative_submodule_loading(self):
895
self.create_plugin_package('test_foo', dir='another-dir', source='''
896
from . import test_bar
898
# We create an additional directory under the one for test_foo
899
self.create_plugin_package('test_bar', dir='another-dir/test_bar')
900
self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@another-dir')
901
self.update_module_paths(['standard'])
902
import breezy.testingplugins.test_foo
903
self.assertEqual(self.module_prefix + 'test_foo',
904
self.module.test_foo.__package__)
905
self.assertIsSameRealPath('another-dir/test_bar/__init__.py',
906
self.module.test_foo.test_bar.__file__)
908
def test_loading_from___init__only(self):
909
# We rename the existing __init__.py file to ensure that we don't load
911
init = 'non-standard-dir/__init__.py'
912
random = 'non-standard-dir/setup.py'
913
os.rename(init, random)
914
self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@non-standard-dir')
915
self.load_with_paths(['standard'])
916
self.assertPluginUnknown('test_foo')
918
def test_loading_from_specific_file(self):
919
plugin_dir = 'non-standard-dir'
920
plugin_file_name = 'iamtestfoo.py'
921
plugin_path = osutils.pathjoin(plugin_dir, plugin_file_name)
923
"""This is the doc for %s"""
925
''' % ('test_foo', plugin_path)
926
self.create_plugin('test_foo', source=source,
927
dir=plugin_dir, file_name=plugin_file_name)
928
self.overrideEnv('BRZ_PLUGINS_AT', 'test_foo@%s' % plugin_path)
929
self.load_with_paths(['standard'])
930
self.assertTestFooLoadedFrom(plugin_path)
933
class TestDescribePlugins(BaseTestPlugins):
935
def test_describe_plugins(self):
936
class DummyModule(object):
938
class DummyPlugin(object):
939
__version__ = '0.1.0'
940
module = DummyModule()
941
self.plugin_warnings = {'bad': ['Failed to load (just testing)']}
942
self.plugins = {'good': DummyPlugin()}
943
self.assertEqual("""\
945
** Failed to load (just testing)
950
""", ''.join(plugin.describe_plugins(state=self)))