Merge lp:~vila/bzr/552922-plugins-at into lp:bzr

Proposed by Vincent Ladeuil
Status: Merged
Approved by: Ian Clatworthy
Approved revision: no longer in the source branch.
Merged at revision: 5134
Proposed branch: lp:~vila/bzr/552922-plugins-at
Merge into: lp:bzr
Diff against target: 181 lines (+68/-35)
3 files modified
NEWS (+4/-0)
bzrlib/plugin.py (+27/-25)
bzrlib/tests/test_plugins.py (+37/-10)
To merge this branch: bzr merge lp:~vila/bzr/552922-plugins-at
Reviewer Review Type Date Requested Status
Ian Clatworthy Approve
Gordon Tyler Approve
Review via email: mp+22697@code.launchpad.net

Description of the change

Since https://code.edge.launchpad.net/~vila/bzr/552922-plugins-at/+merge/22694
was broken, I'm proposing against bzr.dev to get a reasonable diff,
but the intent is still to land in 2.2 when it will be updated.

This patch fixes the BZR_PLUGINS_AT handling to not depend on os.listdir() order.
The automated tests couldn't catch the problem and I was lucky enough to not encounter
it before (and neither was Gary).

Unlike _find_module_plugin() which scan the 'plugins' directories and deduce the plugin
names from the loadable files, BZR_PLUGINS_AT *knows* the plugin names and can (and should)
try some file names explicitly.

Tests added.

To post a comment you must log in.
Revision history for this message
Gordon Tyler (doxxx) wrote :

This fixes the problem I was having in bug 552922.

review: Approve
Revision history for this message
Ian Clatworthy (ian-clatworthy) wrote :

Some requested tweaks ...

> name = fullname[len('bzrlib.plugins.'):]

This variable is never used. Perhaps you wanted to use it in the ImportError? Otherwise, just delete this line please.

> def test_loading_from___init__(self):

Given this test doesn't actual load from __init__ but fails because that file is not there (after an explicit rename), I'd prefer a better test name, e.g. test_loading_from___init__only() say.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2010-04-06 06:59:03 +0000
+++ NEWS 2010-04-06 07:24:38 +0000
@@ -205,6 +205,10 @@
205 deletion is involved.205 deletion is involved.
206 (Vincent Ladeuil, #531967)206 (Vincent Ladeuil, #531967)
207207
208* Loading a plugin from a given path with ``BZR_PLUGINS_AT`` doesn't depend
209 on os.lisdir() order and is now reliable.
210 (Vincent Ladeuil, #552922).
211
208* Network transfer amounts and rates are now displayed in SI units according212* Network transfer amounts and rates are now displayed in SI units according
209 to the Ubuntu Units Policy <https://wiki.ubuntu.com/UnitsPolicy>.213 to the Ubuntu Units Policy <https://wiki.ubuntu.com/UnitsPolicy>.
210 (Gordon Tyler, #514399)214 (Gordon Tyler, #514399)
211215
=== modified file 'bzrlib/plugin.py'
--- bzrlib/plugin.py 2010-03-24 13:57:35 +0000
+++ bzrlib/plugin.py 2010-04-06 07:24:38 +0000
@@ -303,7 +303,7 @@
303303
304304
305def _load_plugin_module(name, dir):305def _load_plugin_module(name, dir):
306 """Load plugine name from dir.306 """Load plugin name from dir.
307307
308 :param name: The plugin name in the bzrlib.plugins namespace.308 :param name: The plugin name in the bzrlib.plugins namespace.
309 :param dir: The directory the plugin is loaded from for error messages.309 :param dir: The directory the plugin is loaded from for error messages.
@@ -560,32 +560,34 @@
560 def load_module(self, fullname):560 def load_module(self, fullname):
561 """Load a plugin from a specific directory."""561 """Load a plugin from a specific directory."""
562 # We are called only for specific paths562 # We are called only for specific paths
563 plugin_dir = self.specific_paths[fullname]563 plugin_path = self.specific_paths[fullname]
564 candidate = None564 loading_path = None
565 maybe_package = False565 package = False
566 for p in os.listdir(plugin_dir):566 if os.path.isdir(plugin_path):
567 if os.path.isdir(osutils.pathjoin(plugin_dir, p)):567 for suffix, mode, kind in imp.get_suffixes():
568 # We're searching for files only and don't want submodules to568 if kind not in (imp.PY_SOURCE, imp.PY_COMPILED):
569 # be recognized as plugins (they are submodules inside the569 # We don't recognize compiled modules (.so, .dll, etc)
570 # plugin).570 continue
571 continue571 init_path = osutils.pathjoin(plugin_path, '__init__' + suffix)
572 name, path, (572 if os.path.isfile(init_path):
573 suffix, mode, kind) = _find_plugin_module(plugin_dir, p)573 loading_path = init_path
574 if name is not None:574 package = True
575 candidate = (name, path, suffix, mode, kind)575 break
576 if kind == imp.PY_SOURCE:576 else:
577 # We favour imp.PY_SOURCE (which will use the compiled577 for suffix, mode, kind in imp.get_suffixes():
578 # version if available) over imp.PY_COMPILED (which is used578 if plugin_path.endswith(suffix):
579 # only if the source is not available)579 loading_path = plugin_path
580 break580 break
581 if candidate is None:581 if loading_path is None:
582 raise ImportError('%s cannot be loaded from %s'582 raise ImportError('%s cannot be loaded from %s'
583 % (fullname, plugin_dir))583 % (fullname, plugin_path))
584 f = open(path, mode)584 f = open(loading_path, mode)
585 try:585 try:
586 mod = imp.load_module(fullname, f, path, (suffix, mode, kind))586 mod = imp.load_module(fullname, f, loading_path,
587 # The plugin can contain modules, so be ready587 (suffix, mode, kind))
588 mod.__path__ = [plugin_dir]588 if package:
589 # The plugin can contain modules, so be ready
590 mod.__path__ = [plugin_path]
589 mod.__package__ = fullname591 mod.__package__ = fullname
590 return mod592 return mod
591 finally:593 finally:
592594
=== modified file 'bzrlib/tests/test_plugins.py'
--- bzrlib/tests/test_plugins.py 2010-03-24 11:55:49 +0000
+++ bzrlib/tests/test_plugins.py 2010-04-06 07:24:38 +0000
@@ -810,21 +810,23 @@
810 self.overrideAttr(plugin, '_loaded', False)810 self.overrideAttr(plugin, '_loaded', False)
811 # Create the same plugin in two directories811 # Create the same plugin in two directories
812 self.create_plugin_package('test_foo', dir='non-standard-dir')812 self.create_plugin_package('test_foo', dir='non-standard-dir')
813 self.create_plugin_package('test_foo', dir='b/test_foo')813 # The "normal" directory, we use 'standard' instead of 'plugins' to
814 # avoid depending on the precise naming.
815 self.create_plugin_package('test_foo', dir='standard/test_foo')
814816
815 def assertTestFooLoadedFrom(self, dir):817 def assertTestFooLoadedFrom(self, path):
816 self.assertPluginKnown('test_foo')818 self.assertPluginKnown('test_foo')
817 self.assertEqual('This is the doc for test_foo',819 self.assertEqual('This is the doc for test_foo',
818 bzrlib.plugins.test_foo.__doc__)820 bzrlib.plugins.test_foo.__doc__)
819 self.assertEqual(dir, bzrlib.plugins.test_foo.dir_source)821 self.assertEqual(path, bzrlib.plugins.test_foo.dir_source)
820822
821 def test_regular_load(self):823 def test_regular_load(self):
822 plugin.load_plugins(['b'])824 plugin.load_plugins(['standard'])
823 self.assertTestFooLoadedFrom('b/test_foo')825 self.assertTestFooLoadedFrom('standard/test_foo')
824826
825 def test_import(self):827 def test_import(self):
826 osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')828 osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
827 plugin.set_plugins_path(['b'])829 plugin.set_plugins_path(['standard'])
828 try:830 try:
829 import bzrlib.plugins.test_foo831 import bzrlib.plugins.test_foo
830 except ImportError:832 except ImportError:
@@ -833,12 +835,12 @@
833835
834 def test_loading(self):836 def test_loading(self):
835 osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')837 osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
836 plugin.load_plugins(['b'])838 plugin.load_plugins(['standard'])
837 self.assertTestFooLoadedFrom('non-standard-dir')839 self.assertTestFooLoadedFrom('non-standard-dir')
838840
839 def test_compiled_loaded(self):841 def test_compiled_loaded(self):
840 osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')842 osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
841 plugin.load_plugins(['b'])843 plugin.load_plugins(['standard'])
842 self.assertTestFooLoadedFrom('non-standard-dir')844 self.assertTestFooLoadedFrom('non-standard-dir')
843 self.assertEqual('non-standard-dir/__init__.py',845 self.assertEqual('non-standard-dir/__init__.py',
844 bzrlib.plugins.test_foo.__file__)846 bzrlib.plugins.test_foo.__file__)
@@ -846,7 +848,7 @@
846 # Try importing again now that the source has been compiled848 # Try importing again now that the source has been compiled
847 self._unregister_plugin('test_foo')849 self._unregister_plugin('test_foo')
848 plugin._loaded = False850 plugin._loaded = False
849 plugin.load_plugins(['b'])851 plugin.load_plugins(['standard'])
850 self.assertTestFooLoadedFrom('non-standard-dir')852 self.assertTestFooLoadedFrom('non-standard-dir')
851 if __debug__:853 if __debug__:
852 suffix = 'pyc'854 suffix = 'pyc'
@@ -859,10 +861,35 @@
859 # We create an additional directory under the one for test_foo861 # We create an additional directory under the one for test_foo
860 self.create_plugin_package('test_bar', dir='non-standard-dir/test_bar')862 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')863 osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
862 plugin.set_plugins_path(['b'])864 plugin.set_plugins_path(['standard'])
863 import bzrlib.plugins.test_foo865 import bzrlib.plugins.test_foo
864 self.assertEqual('bzrlib.plugins.test_foo',866 self.assertEqual('bzrlib.plugins.test_foo',
865 bzrlib.plugins.test_foo.__package__)867 bzrlib.plugins.test_foo.__package__)
866 import bzrlib.plugins.test_foo.test_bar868 import bzrlib.plugins.test_foo.test_bar
867 self.assertEqual('non-standard-dir/test_bar/__init__.py',869 self.assertEqual('non-standard-dir/test_bar/__init__.py',
868 bzrlib.plugins.test_foo.test_bar.__file__)870 bzrlib.plugins.test_foo.test_bar.__file__)
871
872 def test_loading_from___init__only(self):
873 # We rename the existing __init__.py file to ensure that we don't load
874 # a random file
875 init = 'non-standard-dir/__init__.py'
876 random = 'non-standard-dir/setup.py'
877 os.rename(init, random)
878 self.addCleanup(os.rename, random, init)
879 osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@non-standard-dir')
880 plugin.load_plugins(['standard'])
881 self.assertPluginUnknown('test_foo')
882
883 def test_loading_from_specific_file(self):
884 plugin_dir = 'non-standard-dir'
885 plugin_file_name = 'iamtestfoo.py'
886 plugin_path = osutils.pathjoin(plugin_dir, plugin_file_name)
887 source = '''\
888"""This is the doc for %s"""
889dir_source = '%s'
890''' % ('test_foo', plugin_path)
891 self.create_plugin('test_foo', source=source,
892 dir=plugin_dir, file_name=plugin_file_name)
893 osutils.set_or_unset_env('BZR_PLUGINS_AT', 'test_foo@%s' % plugin_path)
894 plugin.load_plugins(['standard'])
895 self.assertTestFooLoadedFrom(plugin_path)