def test_value_dict_persistent(): a = Value("a") b = Value("b") c = Value("c") dct = value_dict({"a": a, "b": b, "c": c}) # Initial value should have passed through assert dct.value == {"a": "a", "b": "b", "c": "c"} m = Mock() dct.on_value_changed(m) # Changes should propagate through a.value = "A" assert dct.value == {"a": "A", "b": "b", "c": "c"} m.assert_called_once_with({"a": "A", "b": "b", "c": "c"}) m.reset_mock() b.value = "B" assert dct.value == {"a": "A", "b": "B", "c": "c"} m.assert_called_once_with({"a": "A", "b": "B", "c": "c"}) m.reset_mock() c.value = "C" assert dct.value == {"a": "A", "b": "B", "c": "C"} m.assert_called_once_with({"a": "A", "b": "B", "c": "C"})
def test_positional_kwargs(): m = Mock() @fn def example(a, b): return a - b a_value = Value(10) b_value = Value(5) # Initial value should pass through result = example(a=a_value, b=b_value) result.on_value_changed(m) assert result.value == 5 # Changes should propagate, callbacks should fire m.reset_mock() a_value.value = 20 m.assert_called_once_with(15) assert result.value == 15 m.reset_mock() b_value.value = -5 m.assert_called_once_with(25) assert result.value == 25
def test_value_tuple_persistent(): a = Value("a") b = Value("b") c = Value("c") tup = value_tuple((a, b, c)) # Initial value should have passed through assert tup.value == ("a", "b", "c") m = Mock() tup.on_value_changed(m) # Changes should propagate through a.value = "A" assert tup.value == ("A", "b", "c") m.assert_called_once_with(("A", "b", "c")) m.reset_mock() b.value = "B" assert tup.value == ("A", "B", "c") m.assert_called_once_with(("A", "B", "c")) m.reset_mock() c.value = "C" assert tup.value == ("A", "B", "C") m.assert_called_once_with(("A", "B", "C"))
def test_value_list_persistent(): a = Value("a") b = Value("b") c = Value("c") lst = value_list([a, b, c]) # Initial value should have passed through assert lst.value == ["a", "b", "c"] m = Mock() lst.on_value_changed(m) # Changes should propagate through a.value = "A" assert lst.value == ["A", "b", "c"] m.assert_called_once_with(["A", "b", "c"]) m.reset_mock() b.value = "B" assert lst.value == ["A", "B", "c"] m.assert_called_once_with(["A", "B", "c"]) m.reset_mock() c.value = "C" assert lst.value == ["A", "B", "C"] m.assert_called_once_with(["A", "B", "C"])
async def test_set_property_register(qth_client, event_loop): a = Value(123) qth_yarp.set_property("foo/bar", a, register=True, description="Something", qth_client=qth_client) # Allow asyncio functions to run... await asyncio.sleep(0.1) # Initial should have been sent to Qth qth_client.set_property.assert_called_once_with("foo/bar", 123) # Registration should have been sent qth_client.register.assert_called_once_with( "foo/bar", qth.PROPERTY_ONE_TO_MANY, "Something", delete_on_unregister=True, ) # Setting the qth value should update Qth a.value = 321 await asyncio.sleep(0.1) qth_client.set_property.assert_called_with("foo/bar", 321) # And again... a.value = 1234 await asyncio.sleep(0.1) qth_client.set_property.assert_called_with("foo/bar", 1234)
def test_window(): v = Value(1) ws = Value(3) win = window(v, ws) # Should start with the current value assert win.value == [1] # Values should accumulate v.value = 2 assert win.value == [1, 2] v.value = 3 assert win.value == [1, 2, 3] # Should truncate to only most recent values v.value = 4 assert win.value == [2, 3, 4] # Increasing the window size should be possible ws.value = 4 v.value = 5 assert win.value == [2, 3, 4, 5] v.value = 6 assert win.value == [3, 4, 5, 6] # Decreasing it should be too ws.value = 2 assert win.value == [5, 6] v.value = 7 assert win.value == [6, 7]
async def test_rate_limit_persistent(event_loop): v = Value(1) # Initial value should be passed through rlv = rate_limit(v, 0.1, event_loop) assert rlv.value == 1 log = [] sem = asyncio.Semaphore(0, loop=event_loop) def on_change(new_value): log.append(new_value) sem.release() rlv.on_value_changed(on_change) # Change should not make it through yet v.value = 2 assert rlv.value == 1 assert len(log) == 0 # Change should come through after a delay before = time.time() await sem.acquire() assert time.time() - before >= 0.1 assert rlv.value == 2 assert len(log) == 1 assert log[-1] == 2 # After a suitable delay, the next change should come through immediately await asyncio.sleep(0.15, loop=event_loop) v.value = 3 assert rlv.value == 3 assert len(log) == 2 assert log[-1] == 3 await sem.acquire() # A rapid succession of calls should result in only the last value # comming out, and then only after a delay v.value = 4 v.value = 5 v.value = 6 assert rlv.value == 3 assert len(log) == 2 before = time.time() await sem.acquire() assert time.time() - before >= 0.1 assert rlv.value == 6 assert len(log) == 3 assert log[-1] == 6
def test_value_list_instantaneous(): # A mix of instantaneous and continuous a = Value("a") b = Value() c = Value() lst = value_list([a, b, c]) # Initial value should have passed through assert lst.value == ["a", NoValue, NoValue] m = Mock() lst.on_value_changed(m) # Changes should propagate through a.value = "A" assert lst.value == ["A", NoValue, NoValue] m.assert_called_once_with(["A", NoValue, NoValue]) # Instantaneous values should propagate only into the callback m.reset_mock() b.set_instantaneous_value("b") assert lst.value == ["A", NoValue, NoValue] m.assert_called_once_with(["A", "b", NoValue]) m.reset_mock() c.set_instantaneous_value("c") assert lst.value == ["A", NoValue, NoValue] m.assert_called_once_with(["A", NoValue, "c"])
def test_value_tuple_instantaneous(): # A mix of instantaneous and continuous a = Value("a") b = Value() c = Value() tup = value_tuple([a, b, c]) # Initial value should have passed through assert tup.value == ("a", NoValue, NoValue) m = Mock() tup.on_value_changed(m) # Changes should propagate through a.value = "A" assert tup.value == ("A", NoValue, NoValue) m.assert_called_once_with(("A", NoValue, NoValue)) # Instantaneous values should propagate only into the callback m.reset_mock() b.set_instantaneous_value("b") assert tup.value == ("A", NoValue, NoValue) m.assert_called_once_with(("A", "b", NoValue)) m.reset_mock() c.set_instantaneous_value("c") assert tup.value == ("A", NoValue, NoValue) m.assert_called_once_with(("A", NoValue, "c"))
def test_value_dict_instantaneous(): # A mix of instantaneous and continuous a = Value("a") b = Value() c = Value() dct = value_dict({"a": a, "b": b, "c": c}) # Initial value should have passed through assert dct.value == {"a": "a", "b": NoValue, "c": NoValue} m = Mock() dct.on_value_changed(m) # Changes should propagate through a.value = "A" assert dct.value == {"a": "A", "b": NoValue, "c": NoValue} m.assert_called_once_with({"a": "A", "b": NoValue, "c": NoValue}) # Instantaneous values should propagate only into the callback m.reset_mock() b.set_instantaneous_value("b") assert dct.value == {"a": "A", "b": NoValue, "c": NoValue} m.assert_called_once_with({"a": "A", "b": "b", "c": NoValue}) m.reset_mock() c.set_instantaneous_value("c") assert dct.value == {"a": "A", "b": NoValue, "c": NoValue} m.assert_called_once_with({"a": "A", "b": NoValue, "c": "c"})
def test_change_callback(): m = Mock() v = Value() v.on_value_changed(m) v.value = 123 m.assert_called_once_with(123)
def test_str_format_operator(): # Special attention due for str_format as its an almost custom function(!) a = Value(0xAB) b = Value("hi") fmt = Value("{}, {}") stringified = str_format(fmt, a, b) assert stringified.value == "171, hi" a.value = 0xBC assert stringified.value == "188, hi" b.value = "foo" assert stringified.value == "188, foo" fmt.value = "0x{:04X}: {!r}" assert stringified.value == "0x00BC: 'foo'"
def test_change_persistent(): rule = lambda x: x < 10 m = Mock() v = Value(1) fl = filter(v, rule) fl.on_value_changed(m) assert fl.value == 1 v.value = 2 assert fl.value == 2 m.assert_called_once_with(2) # Above ten, shouldn't get through v.value = 100 assert fl.value == 2 m.assert_called_once_with(2)
def test_replace_novalue(): a = Value() replacement = Value(123) ar = replace_novalue(a, replacement) assert ar.value == 123 a.value = "hi" assert ar.value == "hi" a.value = NoValue assert ar.value == 123 replacement.value = 321 assert ar.value == 321 a.value = "bye" assert ar.value == "bye"
def test_no_repeat_persistent(): v = Value(1) # Initial value should come through nrv = no_repeat(v) assert nrv.value == 1 m = Mock() nrv.on_value_changed(m) # Same value doesn't pass through v.value = 1 assert not m.called # New values do v.value = 2 assert nrv.value == 2 m.assert_called_once_with(2)
def test_operator_wrapper_continous(): # Only test addition since others are defined in exactly the same way a = Value(1) b = Value(2) a_add_b = add(a, b) assert a_add_b.value == 3 a.value = 10 assert a_add_b.value == 12
def test_ensure_value_list(): a = 123 b = Value(456) v = ensure_value([a, b]) assert isinstance(v, Value) assert v.value == [123, 456] b.value = 789 assert v.value == [123, 789]
def test_ensure_value_tuple(): a = 123 b = Value(456) v = ensure_value((a, b)) assert isinstance(v, Value) assert v.value == (123, 456) b.value = 789 assert v.value == (123, 789)
def test_ensure_value_dict(): a = 123 b = Value(456) v = ensure_value({"a": a, "b": b}) assert isinstance(v, Value) assert v.value == {"a": 123, "b": 456} b.value = 789 assert v.value == {"a": 123, "b": 789}
def test_ensure_value_nested(): a = Value(123) b = Value(456) c = Value(789) v = ensure_value({"a": a, "bc": [b, c]}) assert isinstance(v, Value) assert v.value == {"a": 123, "bc": [456, 789]} b.value = 654 assert v.value == {"a": 123, "bc": [654, 789]}
def test_make_instantaneous(): v = Value(1) iv = make_instantaneous(v) m = Mock() iv.on_value_changed(m) assert iv.value is NoValue v.value = 2 assert iv.value is NoValue m.assert_called_once_with(2)
async def test_set_property(qth_client, event_loop): a = Value(123) qth_yarp.set_property("foo/bar", a, qth_client=qth_client) # Allow asyncio functions to run... await asyncio.sleep(0.1) # Initial should have been sent to Qth qth_client.set_property.assert_called_once_with("foo/bar", 123) # No registration should be made assert len(qth_client.register.mock_calls) == 0 # Setting the qth value should update Qth a.value = 321 await asyncio.sleep(0.1) qth_client.set_property.assert_called_with("foo/bar", 321) # And again... a.value = 1234 await asyncio.sleep(0.1) qth_client.set_property.assert_called_with("foo/bar", 1234)
def test_getattr_value_name(): # Special attention since this is important m = Mock() m.foo = "FOO!" m.bar = "BAR!" v = Value(m) name_v = Value("foo") attr_v = getattr(v, name_v) assert attr_v.value == "FOO!" log = [] attr_v.on_value_changed(log.append) name_v.value = "bar" assert attr_v.value == "BAR!" assert log == ["BAR!"]
def test_getattr_string_name(): # Special attention since this is important m = Mock() m.foo = "bar" v = Value(m) foo_v = getattr(v, "foo") assert isinstance(foo_v, Value) assert foo_v.value == "bar" log = [] foo_v.on_value_changed(log.append) m2 = Mock() m2.foo = "baz" v.value = m2 assert foo_v.value == "baz" assert log == ["baz"]
def test_native_value_operators(): """Not exhaustive but just checks the most valuable ones.""" a = Value(1) b = Value(2) c = value_list([a, b]) d = value_dict({"ay": a, "be": b}) a_add_b = a + b assert a_add_b.value == 3 a.value = 0 assert a_add_b.value == 2 minus_b = -b assert minus_b.value == -2 # Comparison a_lt_b = a < b a_eq_b = a == b assert a_lt_b.value assert not a_eq_b.value a.value = 2 assert not a_lt_b.value assert a_eq_b.value # Non-Value things should get converted b_add_3 = b + 3 assert b_add_3.value == 5 four_sub_b = 4 - b assert four_sub_b.value == 2 # Indexing c_0 = c[0] assert c_0.value == 2 a.value = -1 assert c_0.value == -1 # Slicing c_backwards = c[::-1] assert c_backwards.value == [2, -1] b.value = 20 assert c_backwards.value == [20, -1] a.value = -10 assert c_backwards.value == [20, -10] # Dictionaries d_ay = d["ay"] assert d_ay.value == -10 a.value = 10 assert d_ay.value == 10 # Calling and getattring class Cls(object): def __init__(self, n): self.n = n def __call__(self, n2): return self.n + n2 def get(self): return self.n @property def n_plus_one(self): return self.n + 1 c1 = Cls(1) c2 = Cls(2) av = Value(c1) # Getattr for variable nv = av.n assert nv.value == 1 av.value = c2 assert nv.value == 2 av.value = c1 # Getattr for property n_plus_one_v = av.n_plus_one assert n_plus_one_v.value == 2 av.value = c2 assert n_plus_one_v.value == 3 av.value = c1 # Calling callable classes ret_v = av(10) assert ret_v.value == 11 av.value = c2 assert ret_v.value == 12 av.value = c1 # Calling callable attributes get_v = av.get() assert get_v.value == 1 av.value = c2 assert get_v.value == 2 av.value = c1
async def test_rate_limit_min_interval_change(event_loop): v = Value(123) mi = Value(0.1) start = time.time() rlv = rate_limit(v, mi, event_loop) log = [] sem = asyncio.Semaphore(0, loop=event_loop) def on_change(new_value): log.append(new_value) sem.release() rlv.on_value_changed(on_change) # The initial value will have blocked changes for 0.1 seconds, increase # this to 0.15 seconds and ensure it takes place v.value = 321 mi.value = 0.15 assert rlv.value == 123 await sem.acquire() assert time.time() - start >= 0.15 assert rlv.value == 321 assert len(log) == 1 assert log[-1] == 321 # Should be able to shorten the blocking period, too start = time.time() v.value = 1234 mi.value = 0.1 assert rlv.value == 321 await sem.acquire() assert 0.1 <= time.time() - start < 0.125 assert rlv.value == 1234 assert len(log) == 2 assert log[-1] == 1234 # Also, should be able to shorten the blocking period to shorter than has # already ellapsed and the value should emmerge immediately v.value = 4321 assert rlv.value == 1234 await asyncio.sleep(0.05, loop=event_loop) assert rlv.value == 1234 mi.value = 0.025 assert rlv.value == 4321 assert len(log) == 3 assert log[-1] == 4321 await sem.acquire() # If we ensure blocking is not occurring, changing the time shouldn't cause # problems await asyncio.sleep(0.05, loop=event_loop) mi.value = 0.1 v.value = 12345 assert rlv.value == 12345 await sem.acquire() v.value = 54321 start = time.time() assert rlv.value == 12345 await sem.acquire() assert rlv.value == 54321 assert time.time() - start >= 0.1