From 346f818707521432fceb73efe4e9550303875402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 7 Apr 2017 09:09:56 +0200 Subject: [PATCH] PGMR: More general flexibility for os compat check * Don't restrict the list of compatibility values to check against to only those we have mapped, also support unmapped more exotic ones. * Allow 1:1 check against sys.platform values (with startswith). Combined with the above that allows very granular compatibility modelling ("freebsd11", "freebsd12") if required. * Instead of only whitelisting ("linux", "freebsd") now also black listing is possible ("!windows"). A detected os must match all provided whitelist elements (if the whitelist is empty that is considered always the case) and none of the backlist elements (if the blacklist is empty that is also considered always the case). See the included unit tests for examples of how this works. --- src/octoprint/plugin/core.py | 4 ++ src/octoprint/plugins/__init__.py | 1 + .../plugins/pluginmanager/__init__.py | 33 +++++++--- tests/plugins/__init__.py | 10 +++ tests/plugins/test_pluginmanager.py | 61 +++++++++++++++++++ 5 files changed, 101 insertions(+), 8 deletions(-) create mode 100644 src/octoprint/plugins/__init__.py create mode 100644 tests/plugins/__init__.py create mode 100644 tests/plugins/test_pluginmanager.py 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)