~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_plugins.py

  • Committer: Vincent Ladeuil
  • Date: 2010-03-02 10:21:39 UTC
  • mfrom: (4797.2.24 2.1)
  • mto: This revision was merged to the branch mainline in revision 5069.
  • Revision ID: v.ladeuil+lp@free.fr-20100302102139-b5cba7h6xu13mekg
Merge 2.1 into trunk including fixes for #331095, #507557, #185103, #524184 and #369501

Show diffs side-by-side

added added

removed removed

Lines of Context:
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tests for plugins"""
18
18
 
20
20
# affects the global state of the process.  See bzrlib/plugins.py for more
21
21
# comments.
22
22
 
 
23
import logging
23
24
import os
24
25
from StringIO import StringIO
25
26
import sys
26
27
import zipfile
27
28
 
28
 
from bzrlib import plugin, tests
 
29
from bzrlib import (
 
30
    osutils,
 
31
    plugin,
 
32
    tests,
 
33
    )
29
34
import bzrlib.plugin
30
35
import bzrlib.plugins
31
36
import bzrlib.commands
32
37
import bzrlib.help
33
 
from bzrlib.symbol_versioning import zero_ninetyone
34
 
from bzrlib.tests import TestCase, TestCaseInTempDir
 
38
from bzrlib.tests import (
 
39
    TestCase,
 
40
    TestCaseInTempDir,
 
41
    TestUtil,
 
42
    )
35
43
from bzrlib.osutils import pathjoin, abspath, normpath
36
44
 
37
45
 
66
74
        # create two plugin directories
67
75
        os.mkdir('first')
68
76
        os.mkdir('second')
69
 
        # write a plugin that will record when its loaded in the 
 
77
        # write a plugin that will record when its loaded in the
70
78
        # tempattribute list.
71
79
        template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
72
80
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
114
122
        # create two plugin directories
115
123
        os.mkdir('first')
116
124
        os.mkdir('second')
117
 
        # write plugins that will record when they are loaded in the 
 
125
        # write plugins that will record when they are loaded in the
118
126
        # tempattribute list.
119
127
        template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
120
128
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
166
174
        self.failUnless(tempattribute in self.activeattributes)
167
175
        # create a directory for the plugin
168
176
        os.mkdir('plugin_test')
169
 
        # write a plugin that will record when its loaded in the 
 
177
        # write a plugin that will record when its loaded in the
170
178
        # tempattribute list.
171
179
        template = ("from bzrlib.tests.test_plugins import TestLoadingPlugins\n"
172
180
                    "TestLoadingPlugins.activeattributes[%r].append('%s')\n")
188
196
                del bzrlib.plugins.ts_plugin
189
197
        self.failIf(getattr(bzrlib.plugins, 'ts_plugin', None))
190
198
 
191
 
 
192
 
class TestAllPlugins(TestCaseInTempDir):
193
 
 
194
 
    def test_plugin_appears_in_all_plugins(self):
195
 
        # This test tests a new plugin appears in bzrlib.plugin.all_plugins().
196
 
        # check the plugin is not loaded already
197
 
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
198
 
        # write a plugin that _cannot_ fail to load.
199
 
        file('plugin.py', 'w').write("\n")
200
 
        try:
201
 
            bzrlib.plugin.load_from_path(['.'])
202
 
            all_plugins = self.applyDeprecated(zero_ninetyone,
203
 
                bzrlib.plugin.all_plugins)
204
 
            self.failUnless('plugin' in all_plugins)
205
 
            self.failUnless(getattr(bzrlib.plugins, 'plugin', None))
206
 
            self.assertEqual(all_plugins['plugin'], bzrlib.plugins.plugin)
207
 
        finally:
208
 
            # remove the plugin 'plugin'
209
 
            if 'bzrlib.plugins.plugin' in sys.modules:
210
 
                del sys.modules['bzrlib.plugins.plugin']
211
 
            if getattr(bzrlib.plugins, 'plugin', None):
212
 
                del bzrlib.plugins.plugin
213
 
        self.failIf(getattr(bzrlib.plugins, 'plugin', None))
 
199
    def load_and_capture(self, name):
 
200
        """Load plugins from '.' capturing the output.
 
201
 
 
202
        :param name: The name of the plugin.
 
203
        :return: A string with the log from the plugin loading call.
 
204
        """
 
205
        # Capture output
 
206
        stream = StringIO()
 
207
        try:
 
208
            handler = logging.StreamHandler(stream)
 
209
            log = logging.getLogger('bzr')
 
210
            log.addHandler(handler)
 
211
            try:
 
212
                try:
 
213
                    bzrlib.plugin.load_from_path(['.'])
 
214
                finally:
 
215
                    if 'bzrlib.plugins.%s' % name in sys.modules:
 
216
                        del sys.modules['bzrlib.plugins.%s' % name]
 
217
                    if getattr(bzrlib.plugins, name, None):
 
218
                        delattr(bzrlib.plugins, name)
 
219
            finally:
 
220
                # Stop capturing output
 
221
                handler.flush()
 
222
                handler.close()
 
223
                log.removeHandler(handler)
 
224
            return stream.getvalue()
 
225
        finally:
 
226
            stream.close()
 
227
 
 
228
    def test_plugin_with_bad_api_version_reports(self):
 
229
        # This plugin asks for bzrlib api version 1.0.0, which is not supported
 
230
        # anymore.
 
231
        name = 'wants100.py'
 
232
        f = file(name, 'w')
 
233
        try:
 
234
            f.write("import bzrlib.api\n"
 
235
                "bzrlib.api.require_any_api(bzrlib, [(1, 0, 0)])\n")
 
236
        finally:
 
237
            f.close()
 
238
 
 
239
        log = self.load_and_capture(name)
 
240
        self.assertContainsRe(log,
 
241
            r"It requested API version")
 
242
 
 
243
    def test_plugin_with_bad_name_does_not_load(self):
 
244
        # The file name here invalid for a python module.
 
245
        name = 'bzr-bad plugin-name..py'
 
246
        file(name, 'w').close()
 
247
        log = self.load_and_capture(name)
 
248
        self.assertContainsRe(log,
 
249
            r"Unable to load 'bzr-bad plugin-name\.' in '\.' as a plugin "
 
250
            "because the file path isn't a valid module name; try renaming "
 
251
            "it to 'bad_plugin_name_'\.")
214
252
 
215
253
 
216
254
class TestPlugins(TestCaseInTempDir):
223
261
        file('plugin.py', 'w').write(source + '\n')
224
262
        self.addCleanup(self.teardown_plugin)
225
263
        bzrlib.plugin.load_from_path(['.'])
226
 
    
 
264
 
227
265
    def teardown_plugin(self):
228
266
        # remove the plugin 'plugin'
229
267
        if 'bzrlib.plugins.plugin' in sys.modules:
248
286
        plugin_path = self.test_dir + '/plugin.py'
249
287
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
250
288
 
 
289
    def test_plugin_get_path_py_not_pyc(self):
 
290
        self.setup_plugin()         # after first import there will be plugin.pyc
 
291
        self.teardown_plugin()
 
292
        bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
 
293
        plugins = bzrlib.plugin.plugins()
 
294
        plugin = plugins['plugin']
 
295
        plugin_path = self.test_dir + '/plugin.py'
 
296
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
 
297
 
 
298
    def test_plugin_get_path_pyc_only(self):
 
299
        self.setup_plugin()         # after first import there will be plugin.pyc
 
300
        self.teardown_plugin()
 
301
        os.unlink(self.test_dir + '/plugin.py')
 
302
        bzrlib.plugin.load_from_path(['.']) # import plugin.pyc
 
303
        plugins = bzrlib.plugin.plugins()
 
304
        plugin = plugins['plugin']
 
305
        if __debug__:
 
306
            plugin_path = self.test_dir + '/plugin.pyc'
 
307
        else:
 
308
            plugin_path = self.test_dir + '/plugin.pyo'
 
309
        self.assertIsSameRealPath(plugin_path, normpath(plugin.path()))
 
310
 
251
311
    def test_no_test_suite_gives_None_for_test_suite(self):
252
312
        self.setup_plugin()
253
313
        plugin = bzrlib.plugin.plugins()['plugin']
259
319
        plugin = bzrlib.plugin.plugins()['plugin']
260
320
        self.assertEqual('foo', plugin.test_suite())
261
321
 
 
322
    def test_no_load_plugin_tests_gives_None_for_load_plugin_tests(self):
 
323
        self.setup_plugin()
 
324
        loader = TestUtil.TestLoader()
 
325
        plugin = bzrlib.plugin.plugins()['plugin']
 
326
        self.assertEqual(None, plugin.load_plugin_tests(loader))
 
327
 
 
328
    def test_load_plugin_tests_gives_load_plugin_tests_result(self):
 
329
        source = """
 
330
def load_tests(standard_tests, module, loader):
 
331
    return 'foo'"""
 
332
        self.setup_plugin(source)
 
333
        loader = TestUtil.TestLoader()
 
334
        plugin = bzrlib.plugin.plugins()['plugin']
 
335
        self.assertEqual('foo', plugin.load_plugin_tests(loader))
 
336
 
262
337
    def test_no_version_info(self):
263
338
        self.setup_plugin()
264
339
        plugin = bzrlib.plugin.plugins()['plugin']
281
356
        plugin = bzrlib.plugin.plugins()['plugin']
282
357
        self.assertEqual("unknown", plugin.__version__)
283
358
 
284
 
    def test___version__with_version_info(self):
 
359
    def test_str__version__with_version_info(self):
 
360
        self.setup_plugin("version_info = '1.2.3'")
 
361
        plugin = bzrlib.plugin.plugins()['plugin']
 
362
        self.assertEqual("1.2.3", plugin.__version__)
 
363
 
 
364
    def test_noniterable__version__with_version_info(self):
 
365
        self.setup_plugin("version_info = (1)")
 
366
        plugin = bzrlib.plugin.plugins()['plugin']
 
367
        self.assertEqual("1", plugin.__version__)
 
368
 
 
369
    def test_1__version__with_version_info(self):
 
370
        self.setup_plugin("version_info = (1,)")
 
371
        plugin = bzrlib.plugin.plugins()['plugin']
 
372
        self.assertEqual("1", plugin.__version__)
 
373
 
 
374
    def test_1_2__version__with_version_info(self):
 
375
        self.setup_plugin("version_info = (1, 2)")
 
376
        plugin = bzrlib.plugin.plugins()['plugin']
 
377
        self.assertEqual("1.2", plugin.__version__)
 
378
 
 
379
    def test_1_2_3__version__with_version_info(self):
 
380
        self.setup_plugin("version_info = (1, 2, 3)")
 
381
        plugin = bzrlib.plugin.plugins()['plugin']
 
382
        self.assertEqual("1.2.3", plugin.__version__)
 
383
 
 
384
    def test_candidate__version__with_version_info(self):
 
385
        self.setup_plugin("version_info = (1, 2, 3, 'candidate', 1)")
 
386
        plugin = bzrlib.plugin.plugins()['plugin']
 
387
        self.assertEqual("1.2.3rc1", plugin.__version__)
 
388
 
 
389
    def test_dev__version__with_version_info(self):
 
390
        self.setup_plugin("version_info = (1, 2, 3, 'dev', 0)")
 
391
        plugin = bzrlib.plugin.plugins()['plugin']
 
392
        self.assertEqual("1.2.3dev", plugin.__version__)
 
393
 
 
394
    def test_dev_fallback__version__with_version_info(self):
285
395
        self.setup_plugin("version_info = (1, 2, 3, 'dev', 4)")
286
396
        plugin = bzrlib.plugin.plugins()['plugin']
287
397
        self.assertEqual("1.2.3dev4", plugin.__version__)
288
398
 
289
399
    def test_final__version__with_version_info(self):
290
 
        self.setup_plugin("version_info = (1, 2, 3, 'final', 4)")
 
400
        self.setup_plugin("version_info = (1, 2, 3, 'final', 0)")
291
401
        plugin = bzrlib.plugin.plugins()['plugin']
292
402
        self.assertEqual("1.2.3", plugin.__version__)
293
403
 
 
404
    def test_final_fallback__version__with_version_info(self):
 
405
        self.setup_plugin("version_info = (1, 2, 3, 'final', 2)")
 
406
        plugin = bzrlib.plugin.plugins()['plugin']
 
407
        self.assertEqual("1.2.3.final.2", plugin.__version__)
 
408
 
294
409
 
295
410
class TestPluginHelp(TestCaseInTempDir):
296
411
 
297
412
    def split_help_commands(self):
298
413
        help = {}
299
414
        current = None
300
 
        for line in self.run_bzr('help commands')[0].splitlines():
 
415
        out, err = self.run_bzr('--no-plugins help commands')
 
416
        for line in out.splitlines():
301
417
            if not line.startswith(' '):
302
418
                current = line.split()[0]
303
419
            help[current] = help.get(current, '') + line
340
456
            self.assertContainsRe(help, '\[myplug\]')
341
457
        finally:
342
458
            # unregister command
343
 
            if bzrlib.commands.plugin_cmds.get('myplug', None):
344
 
                del bzrlib.commands.plugin_cmds['myplug']
 
459
            if 'myplug' in bzrlib.commands.plugin_cmds:
 
460
                bzrlib.commands.plugin_cmds.remove('myplug')
345
461
            # remove the plugin 'myplug'
346
462
            if getattr(bzrlib.plugins, 'myplug', None):
347
463
                delattr(bzrlib.plugins, 'myplug')
348
464
 
349
465
 
350
 
class TestPluginFromZip(TestCaseInTempDir):
351
 
 
352
 
    def make_zipped_plugin(self, zip_name, filename):
353
 
        z = zipfile.ZipFile(zip_name, 'w')
354
 
        z.writestr(filename, PLUGIN_TEXT)
355
 
        z.close()
356
 
 
357
 
    def check_plugin_load(self, zip_name, plugin_name):
358
 
        self.assertFalse(plugin_name in dir(bzrlib.plugins),
359
 
                         'Plugin already loaded')
360
 
        old_path = bzrlib.plugins.__path__
361
 
        try:
362
 
            # this is normally done by load_plugins -> set_plugins_path
363
 
            bzrlib.plugins.__path__ = [zip_name]
364
 
            bzrlib.plugin.load_from_zip(zip_name)
365
 
            self.assertTrue(plugin_name in dir(bzrlib.plugins),
366
 
                            'Plugin is not loaded')
367
 
        finally:
368
 
            # unregister plugin
369
 
            if getattr(bzrlib.plugins, plugin_name, None):
370
 
                delattr(bzrlib.plugins, plugin_name)
371
 
                del sys.modules['bzrlib.plugins.' + plugin_name]
372
 
            bzrlib.plugins.__path__ = old_path
373
 
 
374
 
    def test_load_module(self):
375
 
        self.make_zipped_plugin('./test.zip', 'ziplug.py')
376
 
        self.check_plugin_load('./test.zip', 'ziplug')
377
 
 
378
 
    def test_load_package(self):
379
 
        self.make_zipped_plugin('./test.zip', 'ziplug/__init__.py')
380
 
        self.check_plugin_load('./test.zip', 'ziplug')
381
 
 
382
 
 
383
 
class TestSetPluginsPath(TestCase):
384
 
    
385
 
    def test_set_plugins_path(self):
386
 
        """set_plugins_path should set the module __path__ correctly."""
387
 
        old_path = bzrlib.plugins.__path__
388
 
        try:
389
 
            bzrlib.plugins.__path__ = []
390
 
            expected_path = bzrlib.plugin.set_plugins_path()
391
 
            self.assertEqual(expected_path, bzrlib.plugins.__path__)
392
 
        finally:
393
 
            bzrlib.plugins.__path__ = old_path
394
 
 
395
 
    def test_set_plugins_path_with_trailing_slashes(self):
396
 
        """set_plugins_path should set the module __path__ based on
397
 
        BZR_PLUGIN_PATH."""
398
 
        old_path = bzrlib.plugins.__path__
399
 
        old_env = os.environ.get('BZR_PLUGIN_PATH')
400
 
        try:
401
 
            bzrlib.plugins.__path__ = []
402
 
            os.environ['BZR_PLUGIN_PATH'] = "first\\//\\" + os.pathsep + \
403
 
                "second/\\/\\/"
404
 
            bzrlib.plugin.set_plugins_path()
405
 
            expected_path = ['first', 'second',
406
 
                os.path.dirname(bzrlib.plugins.__file__)]
407
 
            self.assertEqual(expected_path, bzrlib.plugins.__path__)
408
 
        finally:
409
 
            bzrlib.plugins.__path__ = old_path
410
 
            if old_env != None:
411
 
                os.environ['BZR_PLUGIN_PATH'] = old_env
412
 
            else:
413
 
                del os.environ['BZR_PLUGIN_PATH']
414
 
 
415
466
class TestHelpIndex(tests.TestCase):
416
467
    """Tests for the PluginsHelpIndex class."""
417
468
 
518
569
        mod = FakeModule('two lines of help\nand more', 'bzrlib.plugins.foo_bar')
519
570
        topic = plugin.ModuleHelpTopic(mod)
520
571
        self.assertEqual('foo_bar', topic.get_help_topic())
 
572
 
 
573
 
 
574
class TestLoadFromPath(tests.TestCaseInTempDir):
 
575
 
 
576
    def setUp(self):
 
577
        super(TestLoadFromPath, self).setUp()
 
578
        # Change bzrlib.plugin to think no plugins have been loaded yet.
 
579
        self.overrideAttr(bzrlib.plugins, '__path__', [])
 
580
        self.overrideAttr(plugin, '_loaded', False)
 
581
 
 
582
        # Monkey-patch load_from_path to stop it from actually loading anything.
 
583
        self.overrideAttr(plugin, 'load_from_path', lambda dirs: None)
 
584
 
 
585
    def test_set_plugins_path_with_args(self):
 
586
        plugin.set_plugins_path(['a', 'b'])
 
587
        self.assertEqual(['a', 'b'], bzrlib.plugins.__path__)
 
588
 
 
589
    def test_set_plugins_path_defaults(self):
 
590
        plugin.set_plugins_path()
 
591
        self.assertEqual(plugin.get_standard_plugins_path(),
 
592
                         bzrlib.plugins.__path__)
 
593
 
 
594
    def test_get_standard_plugins_path(self):
 
595
        path = plugin.get_standard_plugins_path()
 
596
        for directory in path:
 
597
            self.assertNotContainsRe(directory, r'\\/$')
 
598
        try:
 
599
            from distutils.sysconfig import get_python_lib
 
600
        except ImportError:
 
601
            pass
 
602
        else:
 
603
            if sys.platform != 'win32':
 
604
                python_lib = get_python_lib()
 
605
                for directory in path:
 
606
                    if directory.startswith(python_lib):
 
607
                        break
 
608
                else:
 
609
                    self.fail('No path to global plugins')
 
610
 
 
611
    def test_get_standard_plugins_path_env(self):
 
612
        os.environ['BZR_PLUGIN_PATH'] = 'foo/'
 
613
        path = plugin.get_standard_plugins_path()
 
614
        for directory in path:
 
615
            self.assertNotContainsRe(directory, r'\\/$')
 
616
 
 
617
    def test_load_plugins(self):
 
618
        plugin.load_plugins(['.'])
 
619
        self.assertEqual(bzrlib.plugins.__path__, ['.'])
 
620
        # subsequent loads are no-ops
 
621
        plugin.load_plugins(['foo'])
 
622
        self.assertEqual(bzrlib.plugins.__path__, ['.'])
 
623
 
 
624
    def test_load_plugins_default(self):
 
625
        plugin.load_plugins()
 
626
        path = plugin.get_standard_plugins_path()
 
627
        self.assertEqual(path, bzrlib.plugins.__path__)
 
628
 
 
629
 
 
630
class TestEnvPluginPath(tests.TestCaseInTempDir):
 
631
 
 
632
    def setUp(self):
 
633
        super(TestEnvPluginPath, self).setUp()
 
634
        self.overrideAttr(plugin, 'DEFAULT_PLUGIN_PATH', None)
 
635
 
 
636
        self.user = plugin.get_user_plugin_path()
 
637
        self.site = plugin.get_site_plugin_path()
 
638
        self.core = plugin.get_core_plugin_path()
 
639
 
 
640
    def _list2paths(self, *args):
 
641
        paths = []
 
642
        for p in args:
 
643
            plugin._append_new_path(paths, p)
 
644
        return paths
 
645
 
 
646
    def _set_path(self, *args):
 
647
        path = os.pathsep.join(self._list2paths(*args))
 
648
        osutils.set_or_unset_env('BZR_PLUGIN_PATH', path)
 
649
 
 
650
    def check_path(self, expected_dirs, setting_dirs):
 
651
        if setting_dirs:
 
652
            self._set_path(*setting_dirs)
 
653
        actual = plugin.get_standard_plugins_path()
 
654
        self.assertEquals(self._list2paths(*expected_dirs), actual)
 
655
 
 
656
    def test_default(self):
 
657
        self.check_path([self.user, self.core, self.site],
 
658
                        None)
 
659
 
 
660
    def test_adhoc_policy(self):
 
661
        self.check_path([self.user, self.core, self.site],
 
662
                        ['+user', '+core', '+site'])
 
663
 
 
664
    def test_fallback_policy(self):
 
665
        self.check_path([self.core, self.site, self.user],
 
666
                        ['+core', '+site', '+user'])
 
667
 
 
668
    def test_override_policy(self):
 
669
        self.check_path([self.user, self.site, self.core],
 
670
                        ['+user', '+site', '+core'])
 
671
 
 
672
    def test_disable_user(self):
 
673
        self.check_path([self.core, self.site], ['-user'])
 
674
 
 
675
    def test_disable_user_twice(self):
 
676
        # Ensures multiple removals don't left cruft
 
677
        self.check_path([self.core, self.site], ['-user', '-user'])
 
678
 
 
679
    def test_duplicates_are_removed(self):
 
680
        self.check_path([self.user, self.core, self.site],
 
681
                        ['+user', '+user'])
 
682
        # And only the first reference is kept (since the later references will
 
683
        # onnly produce <plugin> already loaded mutters)
 
684
        self.check_path([self.user, self.core, self.site],
 
685
                        ['+user', '+user', '+core',
 
686
                         '+user', '+site', '+site',
 
687
                         '+core'])
 
688
 
 
689
    def test_disable_overrides_disable(self):
 
690
        self.check_path([self.core, self.site], ['-user', '+user'])
 
691
 
 
692
    def test_disable_core(self):
 
693
        self.check_path([self.site], ['-core'])
 
694
        self.check_path([self.user, self.site], ['+user', '-core'])
 
695
 
 
696
    def test_disable_site(self):
 
697
        self.check_path([self.core], ['-site'])
 
698
        self.check_path([self.user, self.core], ['-site', '+user'])
 
699
 
 
700
    def test_override_site(self):
 
701
        self.check_path(['mysite', self.user, self.core],
 
702
                        ['mysite', '-site', '+user'])
 
703
        self.check_path(['mysite', self.core],
 
704
                        ['mysite', '-site'])
 
705
 
 
706
    def test_override_core(self):
 
707
        self.check_path(['mycore', self.user, self.site],
 
708
                        ['mycore', '-core', '+user', '+site'])
 
709
        self.check_path(['mycore', self.site],
 
710
                        ['mycore', '-core'])
 
711
 
 
712
    def test_my_plugin_only(self):
 
713
        self.check_path(['myplugin'], ['myplugin', '-user', '-core', '-site'])
 
714
 
 
715
    def test_my_plugin_first(self):
 
716
        self.check_path(['myplugin', self.core, self.site, self.user],
 
717
                        ['myplugin', '+core', '+site', '+user'])
 
718
 
 
719
    def test_bogus_references(self):
 
720
        self.check_path(['+foo', '-bar', self.core, self.site],
 
721
                        ['+foo', '-bar'])