diff --git a/src/plugin_system/plugin_manager.py b/src/plugin_system/plugin_manager.py index b72c4f02..2c8de821 100644 --- a/src/plugin_system/plugin_manager.py +++ b/src/plugin_system/plugin_manager.py @@ -8,6 +8,7 @@ API Version: 1.0.0 """ import json +import math import queue import sys import time @@ -793,6 +794,11 @@ class PluginManager: return None if requested is None: return None + if isinstance(requested, bool): + self.logger.debug( + "get_update_interval() returned a bool for %s, which is not a number", + plugin_id) + return None try: requested = float(requested) except (TypeError, ValueError): @@ -800,7 +806,7 @@ class PluginManager: "get_update_interval() returned %r for %s, which is not a number", requested, plugin_id) return None - if requested != requested or requested == float('inf'): # NaN / inf + if not math.isfinite(requested): # NaN / +inf / -inf return None return max(requested, self.MIN_DYNAMIC_UPDATE_INTERVAL) diff --git a/test/test_plugin_dynamic_update_interval.py b/test/test_plugin_dynamic_update_interval.py index e87cf104..bd70fce0 100644 --- a/test/test_plugin_dynamic_update_interval.py +++ b/test/test_plugin_dynamic_update_interval.py @@ -98,10 +98,16 @@ class TestABrokenHookCannotStopUpdates: def test_a_non_numeric_hook_falls_back(self, manager, junk): assert manager._get_plugin_update_interval("sports", _Plugin(wants=junk)) == 60.0 - @pytest.mark.parametrize("junk", [float("nan"), float("inf")]) + @pytest.mark.parametrize("junk", [float("nan"), float("inf"), float("-inf")]) def test_nan_and_infinity_fall_back(self, manager, junk): assert manager._get_plugin_update_interval("sports", _Plugin(wants=junk)) == 60.0 + @pytest.mark.parametrize("junk", [True, False]) + def test_a_bool_hook_falls_back(self, manager, junk): + """bool is a subclass of int, so it must be rejected before float() + turns it into 0.0/1.0 and the floor silently swallows the mistake.""" + assert manager._get_plugin_update_interval("sports", _Plugin(wants=junk)) == 60.0 + class TestTheFloor: """A plugin asking for 0 would be re-entered on every tick of the render