mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Improve plugin testing (#3517)
* refactor entrypoint into helpers * Add lookup by metadata This is geared towards plugins packaged in pkgs that differ in name from their top module * Make module lookup predictable in changing pkg-envs * remove no coverage from plugin packages * ignore coverage for production loadin * refactor plugin collection - move assigment out * do not cover fs errors * test custom dir loading * test module meta fetcher * add a bit more safety * do not cover sanity checkers * add folder loading test * ignore again for cleaner diffs for now * ignore safety catch * rename test * Add test for package installs * fix docstring name * depreciate test for now * Fix for out of BASE_DIR paths * ignore catch * remove unneeded complexity * add testing for outside folders * more docstrings and simpler methods * make call simpler
This commit is contained in:
parent
858d48afe7
commit
188ddc3be3
@ -43,7 +43,7 @@ class PluginAppConfig(AppConfig):
|
||||
pass
|
||||
|
||||
# get plugins and init them
|
||||
registry.collect_plugins()
|
||||
registry.plugin_modules = registry.collect_plugins()
|
||||
registry.load_plugins()
|
||||
|
||||
# drop out of maintenance
|
||||
|
@ -241,12 +241,15 @@ def get_module_meta(mdl_name):
|
||||
# Get spec for module
|
||||
spec = find_spec(mdl_name)
|
||||
|
||||
if not spec: # pragma: no cover
|
||||
raise PackageNotFoundError(mdl_name)
|
||||
|
||||
# Try to get specific package for the module
|
||||
result = None
|
||||
for dist in distributions():
|
||||
try:
|
||||
relative = pathlib.Path(spec.origin).relative_to(dist.locate_file(''))
|
||||
except ValueError:
|
||||
except ValueError: # pragma: no cover
|
||||
pass
|
||||
else:
|
||||
if relative in dist.files:
|
||||
@ -254,7 +257,7 @@ def get_module_meta(mdl_name):
|
||||
|
||||
# Check if a distribution was found
|
||||
# A no should not be possible here as a call can only be made on a discovered module but better save then sorry
|
||||
if not result:
|
||||
if not result: # pragma: no cover
|
||||
raise PackageNotFoundError(mdl_name)
|
||||
|
||||
# Return metadata
|
||||
|
0
InvenTree/plugin/mock/__init__.py
Normal file
0
InvenTree/plugin/mock/__init__.py
Normal file
10
InvenTree/plugin/mock/simple.py
Normal file
10
InvenTree/plugin/mock/simple.py
Normal file
@ -0,0 +1,10 @@
|
||||
"""Very simple sample plugin"""
|
||||
|
||||
from plugin import InvenTreePlugin
|
||||
|
||||
|
||||
class SimplePlugin(InvenTreePlugin):
|
||||
"""A very simple plugin."""
|
||||
|
||||
NAME = 'SimplePlugin'
|
||||
SLUG = "simple"
|
@ -4,6 +4,7 @@
|
||||
- Manages setup and teardown of plugin class instances
|
||||
"""
|
||||
|
||||
import imp
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
@ -200,7 +201,7 @@ class PluginsRegistry:
|
||||
|
||||
if settings.TESTING:
|
||||
custom_dirs = os.getenv('INVENTREE_PLUGIN_TEST_DIR', None)
|
||||
else:
|
||||
else: # pragma: no cover
|
||||
custom_dirs = get_setting('INVENTREE_PLUGIN_DIR', 'plugin_dir')
|
||||
|
||||
# Load from user specified directories (unless in testing mode)
|
||||
@ -215,7 +216,7 @@ class PluginsRegistry:
|
||||
if not pd.exists():
|
||||
try:
|
||||
pd.mkdir(exist_ok=True)
|
||||
except Exception:
|
||||
except Exception: # pragma: no cover
|
||||
logger.error(f"Could not create plugin directory '{pd}'")
|
||||
continue
|
||||
|
||||
@ -225,24 +226,31 @@ class PluginsRegistry:
|
||||
if not init_filename.exists():
|
||||
try:
|
||||
init_filename.write_text("# InvenTree plugin directory\n")
|
||||
except Exception:
|
||||
except Exception: # pragma: no cover
|
||||
logger.error(f"Could not create file '{init_filename}'")
|
||||
continue
|
||||
|
||||
if pd.exists() and pd.is_dir():
|
||||
# By this point, we have confirmed that the directory at least exists
|
||||
logger.info(f"Added plugin directory: '{pd}'")
|
||||
dirs.append(pd)
|
||||
if pd.exists() and pd.is_dir():
|
||||
# Convert to python dot-path
|
||||
if pd.is_relative_to(settings.BASE_DIR):
|
||||
pd_path = '.'.join(pd.relative_to(settings.BASE_DIR).parts)
|
||||
else:
|
||||
pd_path = str(pd)
|
||||
|
||||
# Add path
|
||||
dirs.append(pd_path)
|
||||
logger.info(f"Added plugin directory: '{pd}' as '{pd_path}'")
|
||||
|
||||
return dirs
|
||||
|
||||
def collect_plugins(self):
|
||||
"""Collect plugins from all possible ways of loading."""
|
||||
"""Collect plugins from all possible ways of loading. Returned as list."""
|
||||
if not settings.PLUGINS_ENABLED:
|
||||
# Plugins not enabled, do nothing
|
||||
return # pragma: no cover
|
||||
|
||||
self.plugin_modules = [] # clear
|
||||
collected_plugins = []
|
||||
|
||||
# Collect plugins from paths
|
||||
for plugin in self.plugin_dirs():
|
||||
@ -258,26 +266,33 @@ class PluginsRegistry:
|
||||
parent_path = str(parent_obj.parent)
|
||||
plugin = parent_obj.name
|
||||
|
||||
modules = get_plugins(importlib.import_module(plugin), InvenTreePlugin, path=parent_path)
|
||||
# Gather Modules
|
||||
if parent_path:
|
||||
raw_module = imp.load_source(plugin, str(parent_obj.joinpath('__init__.py')))
|
||||
else:
|
||||
raw_module = importlib.import_module(plugin)
|
||||
modules = get_plugins(raw_module, InvenTreePlugin, path=parent_path)
|
||||
|
||||
if modules:
|
||||
[self.plugin_modules.append(item) for item in modules]
|
||||
[collected_plugins.append(item) for item in modules]
|
||||
|
||||
# Check if not running in testing mode and apps should be loaded from hooks
|
||||
if (not settings.PLUGIN_TESTING) or (settings.PLUGIN_TESTING and settings.PLUGIN_TESTING_SETUP):
|
||||
# Collect plugins from setup entry points
|
||||
for entry in get_entrypoints(): # pragma: no cover
|
||||
for entry in get_entrypoints():
|
||||
try:
|
||||
plugin = entry.load()
|
||||
plugin.is_package = True
|
||||
plugin._get_package_metadata()
|
||||
self.plugin_modules.append(plugin)
|
||||
except Exception as error:
|
||||
collected_plugins.append(plugin)
|
||||
except Exception as error: # pragma: no cover
|
||||
handle_error(error, do_raise=False, log_name='discovery')
|
||||
|
||||
# Log collected plugins
|
||||
logger.info(f'Collected {len(self.plugin_modules)} plugins!')
|
||||
logger.info(", ".join([a.__module__ for a in self.plugin_modules]))
|
||||
logger.info(f'Collected {len(collected_plugins)} plugins!')
|
||||
logger.info(", ".join([a.__module__ for a in collected_plugins]))
|
||||
|
||||
return collected_plugins
|
||||
|
||||
def install_plugin_file(self):
|
||||
"""Make sure all plugins are installed in the current enviroment."""
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from .helpers import render_template
|
||||
from .helpers import get_module_meta, render_template
|
||||
|
||||
|
||||
class HelperTests(TestCase):
|
||||
@ -21,3 +21,15 @@ class HelperTests(TestCase):
|
||||
response = render_template(ErrorSource(), 'sample/wrongsample.html', {'abc': 123})
|
||||
self.assertTrue('lert alert-block alert-danger' in response)
|
||||
self.assertTrue('Template file <em>sample/wrongsample.html</em>' in response)
|
||||
|
||||
def test_get_module_meta(self):
|
||||
"""Test for get_module_meta."""
|
||||
|
||||
# We need a stable, known good that will be in enviroment for sure
|
||||
# and it can't be stdlib because does might differ depending on the abstraction layer
|
||||
# and version
|
||||
meta = get_module_meta('django')
|
||||
|
||||
# Lets just hope they do not change the name or author
|
||||
self.assertEqual(meta['Name'], 'Django')
|
||||
self.assertEqual(meta['Author'], 'Django Software Foundation')
|
||||
|
@ -1,8 +1,14 @@
|
||||
"""Unit tests for plugins."""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, override_settings
|
||||
|
||||
import plugin.templatetags.plugin_extras as plugin_tags
|
||||
from plugin import InvenTreePlugin, registry
|
||||
@ -162,3 +168,67 @@ class InvenTreePluginTests(TestCase):
|
||||
self.assertEqual(self.plugin_old.slug, 'old')
|
||||
# check default value is used
|
||||
self.assertEqual(self.plugin_old.get_meta_value('ABC', 'ABCD', '123'), '123')
|
||||
|
||||
|
||||
class RegistryTests(TestCase):
|
||||
"""Tests for registry loading methods."""
|
||||
|
||||
def run_package_test(self, directory):
|
||||
"""General runner for testing package based installs."""
|
||||
|
||||
# Patch enviroment varible to add dir
|
||||
envs = {'INVENTREE_PLUGIN_TEST_DIR': directory}
|
||||
with mock.patch.dict(os.environ, envs):
|
||||
# Reload to redicsover plugins
|
||||
registry.reload_plugins(full_reload=True)
|
||||
|
||||
# Depends on the meta set in InvenTree/plugin/mock/simple:SimplePlugin
|
||||
plg = registry.get_plugin('simple')
|
||||
self.assertEqual(plg.slug, 'simple')
|
||||
self.assertEqual(plg.human_name, 'SimplePlugin')
|
||||
|
||||
def test_custom_loading(self):
|
||||
"""Test if data in custom dir is loaded correctly."""
|
||||
test_dir = Path('plugin_test_dir')
|
||||
|
||||
# Patch env
|
||||
envs = {'INVENTREE_PLUGIN_TEST_DIR': 'plugin_test_dir'}
|
||||
with mock.patch.dict(os.environ, envs):
|
||||
# Run plugin directory discovery again
|
||||
registry.plugin_dirs()
|
||||
|
||||
# Check the directory was created
|
||||
self.assertTrue(test_dir.exists())
|
||||
|
||||
# Clean folder up
|
||||
shutil.rmtree(test_dir, ignore_errors=True)
|
||||
|
||||
def test_subfolder_loading(self):
|
||||
"""Test that plugins in subfolders get loaded."""
|
||||
self.run_package_test('InvenTree/plugin/mock')
|
||||
|
||||
def test_folder_loading(self):
|
||||
"""Test that plugins in folders outside of BASE_DIR get loaded."""
|
||||
|
||||
# Run in temporary directory -> always a new random name
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
# Fill directory with sample data
|
||||
new_dir = Path(tmp).joinpath('mock')
|
||||
shutil.copytree(Path('InvenTree/plugin/mock').absolute(), new_dir)
|
||||
|
||||
# Run tests
|
||||
self.run_package_test(str(new_dir))
|
||||
|
||||
@override_settings(PLUGIN_TESTING_SETUP=True)
|
||||
def test_package_loading(self):
|
||||
"""Test that package distributed plugins work."""
|
||||
# Install sample package
|
||||
subprocess.check_output('pip install inventree-zapier'.split())
|
||||
|
||||
# Reload to discover plugin
|
||||
registry.reload_plugins(full_reload=True)
|
||||
|
||||
# Test that plugin was installed
|
||||
plg = registry.get_plugin('zapier')
|
||||
self.assertEqual(plg.slug, 'zapier')
|
||||
self.assertEqual(plg.name, 'inventree_zapier')
|
||||
|
Loading…
Reference in New Issue
Block a user