mirror of
https://github.com/fergalmoran/picard.git
synced 2025-12-22 17:28:58 +00:00
- Only default value was used in the code - having an enum for that is rather limiting its use - add tests for PluginFunctions class
322 lines
10 KiB
Python
322 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
||
#
|
||
# Picard, the next-generation MusicBrainz tagger
|
||
#
|
||
# Copyright (C) 2019-2021 Laurent Monin
|
||
# Copyright (C) 2019-2021, 2023 Philipp Wolfer
|
||
#
|
||
# This program is free software; you can redistribute it and/or
|
||
# modify it under the terms of the GNU General Public License
|
||
# as published by the Free Software Foundation; either version 2
|
||
# of the License, or (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with this program; if not, write to the Free Software
|
||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
||
|
||
import logging
|
||
import os
|
||
import sys
|
||
import unittest
|
||
from unittest.mock import Mock
|
||
|
||
from test.picardtestcase import PicardTestCase
|
||
|
||
import picard
|
||
from picard.const import USER_PLUGIN_DIR
|
||
from picard.plugin import (
|
||
PluginFunctions,
|
||
PluginWrapper,
|
||
_unregister_module_extensions,
|
||
)
|
||
from picard.pluginmanager import (
|
||
PluginManager,
|
||
_compatible_api_versions,
|
||
_plugin_dirs,
|
||
_plugin_name_from_path,
|
||
register_plugin_dir,
|
||
)
|
||
from picard.version import (
|
||
Version,
|
||
VersionError,
|
||
)
|
||
|
||
|
||
# testplugins structure along plugins (zipped or not) structures
|
||
#
|
||
# ├── module/
|
||
# │ └── dummyplugin/
|
||
# │ └── __init__.py
|
||
# ├── packaged_module/
|
||
# │ └── dummyplugin.picard.zip
|
||
# │ └── dummyplugin/ # FIXME: correct structure??
|
||
# │ └── __init__.py
|
||
# │ └── MANIFEST.json # FIXME: format??
|
||
# ├── singlefile/
|
||
# │ └── dummyplugin.py
|
||
# ├── zipped_module/
|
||
# │ └── dummyplugin.zip
|
||
# │ └── dummyplugin/
|
||
# │ └── __init__.py
|
||
# └── zipped_singlefile/
|
||
# └── dummyplugin.zip
|
||
# └── dummyplugin.py
|
||
|
||
# module
|
||
# packaged_module
|
||
# singlefile
|
||
# zipped_module
|
||
# zipped_singlefile
|
||
|
||
|
||
def _get_test_plugins():
|
||
testplugins = {}
|
||
testplugins_path = os.path.join('test', 'data', 'testplugins')
|
||
for f in os.listdir(testplugins_path):
|
||
testplugin = os.path.join(testplugins_path, f)
|
||
for e in os.listdir(testplugin):
|
||
if e == '__pycache__':
|
||
continue
|
||
testplugins[f] = os.path.join(testplugin, e)
|
||
return testplugins
|
||
|
||
|
||
_testplugins = _get_test_plugins()
|
||
|
||
|
||
def unload_plugin(plugin_name):
|
||
"""for testing purposes"""
|
||
_unregister_module_extensions(plugin_name)
|
||
if hasattr(picard.plugins, plugin_name):
|
||
delattr(picard.plugins, plugin_name)
|
||
if plugin_name in sys.modules:
|
||
del sys.modules[plugin_name]
|
||
|
||
|
||
class TestPicardPluginsCommon(PicardTestCase):
|
||
|
||
def setUp(self):
|
||
super().setUp()
|
||
logging.disable(logging.ERROR)
|
||
|
||
def tearDown(self):
|
||
pass
|
||
|
||
|
||
class TestPicardPluginsCommonTmpDir(TestPicardPluginsCommon):
|
||
|
||
def setUp(self):
|
||
super().setUp()
|
||
self.tmp_directory = self.mktmpdir()
|
||
register_plugin_dir(self.tmp_directory)
|
||
|
||
|
||
class TestPicardPluginManager(TestPicardPluginsCommon):
|
||
|
||
def test_compatible_api_version(self):
|
||
|
||
# use first element from picard.api_versions, it should be compatible
|
||
api_versions = picard.api_versions[:1]
|
||
expected = {Version.from_string(v) for v in api_versions}
|
||
result = _compatible_api_versions(api_versions)
|
||
self.assertEqual(result, expected)
|
||
|
||
# pretty sure 0.0 isn't compatible
|
||
api_versions = ["0.0"]
|
||
expected = set()
|
||
result = _compatible_api_versions(api_versions)
|
||
self.assertEqual(result, expected)
|
||
|
||
# buggy version
|
||
api_versions = ["0.a"]
|
||
with self.assertRaises(VersionError):
|
||
result = _compatible_api_versions(api_versions)
|
||
|
||
def test_plugin_name_from_path(self):
|
||
for name, path in _testplugins.items():
|
||
self.assertEqual(
|
||
_plugin_name_from_path(path), 'dummyplugin',
|
||
"failed to get plugin name from %s: %r" % (name, path)
|
||
)
|
||
|
||
|
||
class TestPicardPluginsInstall(TestPicardPluginsCommonTmpDir):
|
||
|
||
def _test_plugin_install(self, name):
|
||
plugin_path = _testplugins[name]
|
||
pm = PluginManager(plugins_directory=self.tmp_directory)
|
||
|
||
msg = "install_plugin: %s %r" % (name, plugin_path)
|
||
pm.install_plugin(plugin_path)
|
||
self.assertEqual(len(pm.plugins), 1, msg)
|
||
self.assertEqual(pm.plugins[0].name, 'Dummy plugin', msg)
|
||
|
||
# if module is properly loaded, this should work
|
||
from picard.plugins.dummyplugin import DummyPlugin
|
||
DummyPlugin()
|
||
|
||
# Remove plugin again
|
||
pm.remove_plugin('dummyplugin')
|
||
unload_plugin('picard.plugins.dummyplugin')
|
||
with self.assertRaises(ImportError):
|
||
from picard.plugins.dummyplugin import ( # noqa: F811 # pylint: disable=reimported
|
||
DummyPlugin,
|
||
)
|
||
|
||
def _test_plugin_install_data(self, name):
|
||
# simulate installation from UI using data from picard plugins api web service
|
||
with open(_testplugins[name], 'rb') as f:
|
||
data = f.read()
|
||
|
||
pm = PluginManager(plugins_directory=self.tmp_directory)
|
||
|
||
msg = "install_plugin_data: %s data: %d bytes" % (name, len(data))
|
||
pm.install_plugin(None, plugin_name='dummyplugin', plugin_data=data)
|
||
self.assertEqual(len(pm.plugins), 1, msg)
|
||
self.assertEqual(pm.plugins[0].name, 'Dummy plugin', msg)
|
||
|
||
# if module is properly loaded, this should work
|
||
from picard.plugins.dummyplugin import DummyPlugin
|
||
DummyPlugin()
|
||
|
||
# Remove plugin again
|
||
pm.remove_plugin('dummyplugin')
|
||
unload_plugin('picard.plugins.dummyplugin')
|
||
with self.assertRaises(ImportError):
|
||
from picard.plugins.dummyplugin import ( # noqa: F811 # pylint: disable=reimported
|
||
DummyPlugin,
|
||
)
|
||
|
||
# module
|
||
def test_plugin_install_module(self):
|
||
self._test_plugin_install('module')
|
||
|
||
# packaged_module
|
||
# FIXME : not really implemented
|
||
@unittest.skipIf(True, "FIXME")
|
||
def test_plugin_install_packaged_module(self):
|
||
self._test_plugin_install('packaged_module')
|
||
|
||
# singlefile
|
||
def test_plugin_install_packaged_singlefile(self):
|
||
self._test_plugin_install('singlefile')
|
||
|
||
# zipped_module
|
||
def test_plugin_install_packaged_zipped_module(self):
|
||
self._test_plugin_install('zipped_module')
|
||
|
||
# zipped_singlefile
|
||
def test_plugin_install_packaged_zipped_singlefile(self):
|
||
self._test_plugin_install('zipped_singlefile')
|
||
|
||
# zipped_module from picard plugins ws
|
||
def test_plugin_install_zipped_module_data(self):
|
||
self._test_plugin_install_data('zipped_module')
|
||
|
||
# zipped_singlefile from picard plugins ws
|
||
def test_plugin_install_zipped_singlefile_data(self):
|
||
self._test_plugin_install_data('zipped_singlefile')
|
||
|
||
def test_plugin_install_no_path_no_plugin_name(self):
|
||
pm = PluginManager(plugins_directory=self.tmp_directory)
|
||
with self.assertRaises(AssertionError):
|
||
pm.install_plugin(None)
|
||
|
||
|
||
class TestPicardPluginsLoad(TestPicardPluginsCommonTmpDir):
|
||
|
||
def setUp(self):
|
||
super().setUp()
|
||
self.pm = PluginManager(plugins_directory=self.tmp_directory)
|
||
self.src_dir = None
|
||
|
||
def tearDown(self):
|
||
super().tearDown()
|
||
unload_plugin('picard.plugins.dummyplugin')
|
||
if self.src_dir:
|
||
_plugin_dirs.remove(self.src_dir)
|
||
|
||
def _register_plugin_dir(self, name):
|
||
self.src_dir = os.path.dirname(_testplugins[name])
|
||
register_plugin_dir(self.src_dir)
|
||
|
||
def _test_plugin_load_from_directory(self, name):
|
||
self._register_plugin_dir(name)
|
||
msg = "plugins_load_from_directory: %s %r" % (name, self.src_dir)
|
||
self.pm.load_plugins_from_directory(self.src_dir)
|
||
self.assertEqual(len(self.pm.plugins), 1, msg)
|
||
self.assertEqual(self.pm.plugins[0].name, 'Dummy plugin', msg)
|
||
|
||
# if module is properly loaded, this should work
|
||
from picard.plugins.dummyplugin import DummyPlugin
|
||
DummyPlugin()
|
||
|
||
# singlefile
|
||
def test_plugin_load_from_directory_singlefile(self):
|
||
self._test_plugin_load_from_directory('singlefile')
|
||
|
||
# zipped_module
|
||
def test_plugin_load_from_directory_zipped_module(self):
|
||
self._test_plugin_load_from_directory('zipped_module')
|
||
|
||
# zipped_singlefile
|
||
def test_plugin_load_from_directory_zipped_singlefile(self):
|
||
self._test_plugin_load_from_directory('zipped_singlefile')
|
||
|
||
# module
|
||
def test_plugin_load_from_directory_module(self):
|
||
self._test_plugin_load_from_directory('module')
|
||
|
||
def test_plugin_import_error(self):
|
||
module_name = 'picard.plugins.dummyplugin'
|
||
self.assertIsNone(sys.modules.get(module_name))
|
||
self._register_plugin_dir('importerror')
|
||
self.pm.load_plugins_from_directory(self.src_dir)
|
||
self.assertIsNone(sys.modules.get(module_name))
|
||
|
||
|
||
class TestPluginWrapper(PicardTestCase):
|
||
|
||
def test_is_user_installed(self):
|
||
manifest = {
|
||
'PLUGIN_NAME': 'foo'
|
||
}
|
||
user_plugin = PluginWrapper({}, USER_PLUGIN_DIR, manifest_data=manifest)
|
||
self.assertTrue(user_plugin.is_user_installed)
|
||
system_plugin = PluginWrapper({}, '/other/path/plugins', manifest_data=manifest)
|
||
self.assertFalse(system_plugin.is_user_installed)
|
||
|
||
|
||
class TestPluginFunctions(PicardTestCase):
|
||
def setUp(self):
|
||
self.set_config_values({
|
||
'enabled_plugins': [],
|
||
})
|
||
|
||
def test_register_order(self):
|
||
pfs = PluginFunctions(label="test")
|
||
self.assertEqual(pfs.functions, {})
|
||
pfs.register('m', 'f1', priority=0)
|
||
pfs.register('m', 'f2', priority=0)
|
||
self.assertEqual(list(pfs._get_functions()), ['f1', 'f2'])
|
||
pfs.register('m', 'f3', priority=1)
|
||
pfs.register('m', 'f4', priority=-1)
|
||
self.assertEqual(list(pfs._get_functions()), ['f3', 'f1', 'f2', 'f4'])
|
||
|
||
def test_run_args(self):
|
||
testfunc1 = Mock()
|
||
testfunc2 = Mock()
|
||
|
||
pfs = PluginFunctions(label="test")
|
||
pfs.register('m', testfunc1)
|
||
pfs.register('m', testfunc2)
|
||
pfs.run(1, k=2)
|
||
testfunc1.assert_called_with(1, k=2)
|
||
testfunc2.assert_called_with(1, k=2)
|