A new epheleral_condition sensor type + fix issue with dangling callback
This commit is contained in:
1 parent
3b0c24e795
commit
b97445ce92
4 files changed
+369
-13
No files matched your search
@@ -0,0 +1,105 @@
|
||||
"""Check initial windows, rearming and queued timer races."""
|
||||
import ast
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
import time
|
||||
|
||||
from test_continuous_condition import ContinuousConditionTests, namespace, source
|
||||
|
||||
namespace['time'] = time
|
||||
node = next(node for node in ast.parse(source.read_text()).body
|
||||
if isinstance(node, ast.ClassDef) and node.name == 'EphemeralCondition')
|
||||
exec(compile(ast.Module(body=[node], type_ignores=[]), str(source), 'exec'), namespace)
|
||||
EphemeralCondition = namespace['EphemeralCondition']
|
||||
|
||||
|
||||
class EphemeralConditionTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
ContinuousConditionTests.setUp(self)
|
||||
self.sensor.__class__ = EphemeralCondition
|
||||
self.sensor.attributes = {}
|
||||
self.sensor.ad_api.get_state = Mock(return_value=None)
|
||||
self.sensor.output_sensor.set_state.side_effect = lambda *, state, **kwargs: setattr(self, 'state', state)
|
||||
self.sensor.initialize()
|
||||
|
||||
def test_window_expires_without_restarting_while_true(self):
|
||||
self.sensor.on_condition_succeed()
|
||||
self.assertEqual(self.state, 'on')
|
||||
handle = self.sensor.cb_handle
|
||||
self.sensor.on_condition_succeed()
|
||||
self.assertEqual(self.sensor.cb_handle, handle)
|
||||
self.scheduler.expire(handle)()
|
||||
self.assertEqual(self.state, 'off')
|
||||
self.sensor.on_condition_succeed()
|
||||
self.assertEqual(self.state, 'off')
|
||||
self.assertIsNone(self.sensor.cb_handle)
|
||||
|
||||
def test_failure_ends_window_and_rearms(self):
|
||||
self.sensor.on_condition_succeed()
|
||||
self.sensor.on_condition_fail()
|
||||
self.assertEqual(self.state, 'off')
|
||||
self.assertEqual(self.scheduler.timers, {})
|
||||
self.sensor.on_condition_succeed()
|
||||
self.assertEqual(self.state, 'on')
|
||||
self.scheduler.expire(self.sensor.cb_handle)()
|
||||
self.assertEqual(self.state, 'off')
|
||||
|
||||
def test_queued_expiry_cannot_end_new_window(self):
|
||||
self.sensor.on_condition_succeed()
|
||||
queued = self.scheduler.expire(self.sensor.cb_handle)
|
||||
self.sensor.on_condition_fail()
|
||||
self.sensor.on_condition_succeed()
|
||||
handle = self.sensor.cb_handle
|
||||
queued()
|
||||
self.assertEqual(self.state, 'on')
|
||||
self.assertEqual(self.sensor.cb_handle, handle)
|
||||
self.scheduler.expire(handle)()
|
||||
self.assertEqual(self.state, 'off')
|
||||
|
||||
def test_restart_resumes_remaining_time(self):
|
||||
self.sensor.ad_api.get_state.return_value = 100
|
||||
with patch.object(time, 'time', return_value=103):
|
||||
self.sensor.initialize()
|
||||
self.sensor.ad_api.run_in = Mock(return_value='restored')
|
||||
self.sensor.on_condition_succeed()
|
||||
self.assertEqual(self.state, 'on')
|
||||
self.assertEqual(self.sensor.ad_api.run_in.call_args.args[1], 2)
|
||||
self.assertEqual(self.sensor.attributes['condition_started_at'], 100)
|
||||
|
||||
def test_restart_does_not_rearm_expired_window(self):
|
||||
self.sensor.ad_api.get_state.return_value = 100
|
||||
with patch.object(time, 'time', return_value=110):
|
||||
self.sensor.initialize()
|
||||
self.sensor.on_condition_succeed()
|
||||
self.assertEqual(self.state, 'off')
|
||||
self.assertIsNone(self.sensor.cb_handle)
|
||||
self.sensor.on_condition_fail()
|
||||
self.assertIsNone(self.sensor.attributes['condition_started_at'])
|
||||
self.sensor.on_condition_succeed()
|
||||
self.assertEqual(self.state, 'on')
|
||||
|
||||
def test_invalid_restored_timestamp_starts_fresh(self):
|
||||
for value in ('invalid', float('nan'), float('inf'), -1, 200):
|
||||
with self.subTest(value=value), patch.object(time, 'time', return_value=100):
|
||||
self.sensor.ad_api.get_state.return_value = value
|
||||
self.sensor.initialize()
|
||||
self.sensor.on_condition_succeed()
|
||||
self.assertEqual(self.state, 'on')
|
||||
self.assertEqual(self.sensor.attributes['condition_started_at'], 100)
|
||||
self.sensor.on_condition_fail()
|
||||
|
||||
def test_initial_true_condition_starts_window(self):
|
||||
evaluator = namespace['SmartCondition'].Evaluator
|
||||
def initialize_condition(*args, **kwargs):
|
||||
kwargs['on_succeed_cb']()
|
||||
evaluator.side_effect = initialize_condition
|
||||
try:
|
||||
self.sensor.initialize()
|
||||
self.assertEqual(self.state, 'on')
|
||||
self.assertIsNotNone(self.sensor.cb_handle)
|
||||
finally:
|
||||
evaluator.side_effect = None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user