diff --git a/src/octoprint/plugin/core.py b/src/octoprint/plugin/core.py index f8a168f5..4f98a29a 100644 --- a/src/octoprint/plugin/core.py +++ b/src/octoprint/plugin/core.py @@ -571,6 +571,10 @@ class PluginManager(object): key = entry.name elif entry.is_file() and entry.name.endswith(".py"): key = entry.name[:-3] # strip off the .py extension + if key.startswith("__"): + # might be an __init__.py in our plugins folder, or something else we don't want + # to handle + continue else: continue diff --git a/src/octoprint/plugins/__init__.py b/src/octoprint/plugins/__init__.py new file mode 100644 index 00000000..1b0510c6 --- /dev/null +++ b/src/octoprint/plugins/__init__.py @@ -0,0 +1 @@ +# make our plugins testable diff --git a/src/octoprint/plugins/pluginmanager/__init__.py b/src/octoprint/plugins/pluginmanager/__init__.py index 8f31b101..05985833 100644 --- a/src/octoprint/plugins/pluginmanager/__init__.py +++ b/src/octoprint/plugins/pluginmanager/__init__.py @@ -808,20 +808,37 @@ class PluginManagerPlugin(octoprint.plugin.SimpleApiPlugin, return True - def _is_os_compatible(self, current_os, compatibility_entries): + @staticmethod + def _is_os_compatible(current_os, compatibility_entries): """ - Tests if the ``current_os`` or ``sys.platform`` matches any of the provided ``compatibility_entries``. + Tests if the ``current_os`` or ``sys.platform`` are blacklisted or whitelisted in ``compatibility_entries`` """ - general_match = current_os in filter(lambda x: x in self.__class__.OPERATING_SYSTEMS.keys(), compatibility_entries) - exact_match = sys.platform in compatibility_entries - return general_match or exact_match + if len(compatibility_entries) == 0: + # shortcut - no compatibility info means we are compatible + return True - def _get_os(self): - for identifier, platforms in self.__class__.OPERATING_SYSTEMS.items(): + negative_entries = map(lambda x: x[1:], filter(lambda x: x.startswith("!"), compatibility_entries)) + positive_entries = filter(lambda x: not x.startswith("!"), compatibility_entries) + + negative_match = False + if negative_entries: + # check if we are blacklisted + negative_match = current_os in negative_entries or any(map(lambda x: sys.platform.startswith(x), negative_entries)) + + positive_match = True + if positive_entries: + # check if we are whitelisted + positive_match = current_os in positive_entries or any(map(lambda x: sys.platform.startswith(x), positive_entries)) + + return positive_match and not negative_match + + @classmethod + def _get_os(cls): + for identifier, platforms in cls.OPERATING_SYSTEMS.items(): if (callable(platforms) and platforms(sys.platform)) or (isinstance(platforms, list) and sys.platform in platforms): return identifier else: - return "unknown" + return "unmapped" def _get_octoprint_version_string(self): return VERSION diff --git a/tests/plugins/__init__.py b/tests/plugins/__init__.py new file mode 100644 index 00000000..858aac82 --- /dev/null +++ b/tests/plugins/__init__.py @@ -0,0 +1,10 @@ +# coding=utf-8 +""" +Unit tests for bundled plugins. +""" + +from __future__ import absolute_import + +__author__ = "Gina Häußge " +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' +__copyright__ = "Copyright (C) 2017 The OctoPrint Project - Released under terms of the AGPLv3 License" diff --git a/tests/plugins/test_pluginmanager.py b/tests/plugins/test_pluginmanager.py new file mode 100644 index 00000000..28194c85 --- /dev/null +++ b/tests/plugins/test_pluginmanager.py @@ -0,0 +1,61 @@ +# coding=utf-8 +""" +Unit tests for bundled plugin "PluginManager". +""" + +from __future__ import absolute_import + +__author__ = "Gina Häußge " +__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' +__copyright__ = "Copyright (C) 2017 The OctoPrint Project - Released under terms of the AGPLv3 License" + +import unittest +import mock +import ddt + +from octoprint.plugins.pluginmanager import PluginManagerPlugin + +@ddt.ddt +class PluginManagerPluginTests(unittest.TestCase): + + @ddt.data( + ("linux", "linux2", [], True), + ("linux", "linux2", ["linux", "freebsd"], True), + ("windows", "win32", ["linux", "freebsd"], False), + ("linux", "linux2", ["!windows"], True), + ("windows", "win32", ["!windows"], False), + ("unmapped", "os2", [], True), + ("unmapped", "os2", ["linux", "freebsd"], False), + ("unmapped", "os2", ["!os2"], False), + ("unmapped", "sunos5", ["linux", "freebsd", "sunos"], True), + ("unmapped", "sunos5", ["!sunos", "!os2"], False), + + # both black and white listing at the same time usually doesn't + # make a whole lot of sense, but let's test it anyhow + ("linux", "linux2", ["!windows", "linux", "freebsd"], True), + ("linux", "linux2", ["!windows", "freebsd"], False), + ("windows", "win32", ["!windows", "linux", "freebsd"], False), + ("unmapped", "sunos5", ["!windows", "linux", "freebsd"], False) + ) + @ddt.unpack + def test_is_os_compatible(self, current_os, sys_platform, entries, expected): + with mock.patch("sys.platform", sys_platform): + actual = PluginManagerPlugin._is_os_compatible(current_os, entries) + self.assertEqual(actual, expected) + + @ddt.data( + ("win32", "windows"), + ("linux2", "linux"), + ("darwin", "macos"), + ("linux", "linux"), + ("linux3", "linux"), + ("freebsd", "freebsd"), + ("freebsd2342", "freebsd"), + ("os2", "unmapped"), + ("sunos5", "unmapped") + ) + @ddt.unpack + def test_get_os(self, sys_platform, expected): + with mock.patch("sys.platform", sys_platform): + actual = PluginManagerPlugin._get_os() + self.assertEqual(actual, expected)