def test_notify(init, number_of_subscribers): """ Testing .notify(v) method """ p = Publisher(init) m = mock.Mock() # subscribing Sinks to the publisher and test .notify on subscribe subscribers = [Sink(m, i) for i in range(number_of_subscribers)] m.assert_not_called() for s in subscribers: p.subscribe(s) if init is not NONE: m.assert_has_calls([mock.call(i, init) for i in range(number_of_subscribers)]) else: m.assert_not_called() m.reset_mock() # test .notify() for listening subscribers p.notify(1) m.assert_has_calls([mock.call(i, 1) for i in range(number_of_subscribers)]) m.reset_mock() # re- .notify() with the same value p.notify(1) m.assert_has_calls([mock.call(i, 1) for i in range(number_of_subscribers)])
def test_operator_concat(): DUT = Concat(Map(lambda v: v / 2), Map(lambda v: v + 1)) mock_cb = mock.Mock() p = Publisher() o = p | DUT assert o.get() == NONE p.notify(0) assert o.get() == 1.0 o.subscribe(Sink(mock_cb)) for v in range(5): p.notify(v) mock_cb.assert_has_calls([ mock.call(1.0), mock.call(1.5), mock.call(2.0), mock.call(2.5), mock.call(3.0) ]) assert o.get() == 3.0
async def test_throttle_errorhandler(event_loop): from broqer import default_error_handler p = Publisher() mock_sink = mock.Mock() mock_error_handler = mock.Mock() default_error_handler.set(mock_error_handler) throttle = p | op.Throttle(0.1) disposable = throttle.subscribe(Sink(mock_sink)) mock_sink.side_effect = (None, ZeroDivisionError('FAIL')) # test error_handler p.notify(1) await asyncio.sleep(0.05, loop=event_loop) mock_sink.assert_called_once_with(1) p.notify(2) await asyncio.sleep(0.1, loop=event_loop) mock_error_handler.assert_called_once_with(ZeroDivisionError, mock.ANY, mock.ANY) mock_sink.assert_has_calls((mock.call(1), mock.call(2))) mock_sink.reset_mock()
async def test_throttle_reset(event_loop): p = Publisher() mock_sink = mock.Mock() throttle = p | op.Throttle(0.1) disposable = throttle.subscribe(Sink(mock_sink)) p.notify(1) await asyncio.sleep(0.05, loop=event_loop) throttle.reset() p.notify(3) await asyncio.sleep(0.05, loop=event_loop) # reset is called after "1" was emitted mock_sink.assert_has_calls((mock.call(1), mock.call(3))) ## wait until initial state is set and reset mock await asyncio.sleep(0.1, loop=event_loop) mock_sink.reset_mock() p.notify(1) await asyncio.sleep(0.05, loop=event_loop) p.notify(2) throttle.reset() p.notify(3) await asyncio.sleep(0.05, loop=event_loop) # reset is called after "1" was emitted, and while "2" was hold back, # therefore "1" and "3" are emitted, but "2" is ignored mock_sink.assert_has_calls((mock.call(1), mock.call(3))) disposable.dispose()
def test_reset_state(): """ Test .reset_state() """ m = mock.Mock() p = Publisher() p.subscribe(Sink(m, 1)) m.assert_not_called() # test .reset_state() before and after subscribing p.reset_state() assert p.get() == NONE p.subscribe(Sink(m, 2)) m.assert_not_called() m.reset_mock() # test .reset_state() after notify p.notify('check') assert p.get() == 'check' m.assert_has_calls([mock.call(1, 'check'), mock.call(2, 'check')]) m.reset_mock() # test no subscribers get notified p.reset_state() m.assert_not_called() assert p.get() == NONE
def test_stateless_nested(): source1 = Publisher() dut1 = CombineLatest(source1, allow_stateless=True) source2 = Publisher() dut2 = CombineLatest(dut1, source2, allow_stateless=True) assert len(source1.subscriptions) == 0 assert len(source2.subscriptions) == 0 assert len(dut1.subscriptions) == 0 assert len(dut2.subscriptions) == 0 collector = Collector() dut2 | collector assert len(source1.subscriptions) == 1 assert len(source2.subscriptions) == 1 assert len(dut1.subscriptions) == 1 assert len(dut2.subscriptions) == 1 with pytest.raises(ValueError): dut2.get() assert collector.result_vector == () collector.reset() source1.notify(True) assert collector.result_vector == (((True, ), NONE), ) collector.reset() source2.notify(False) assert collector.result_vector == ((NONE, False), )
async def test_future_return(): class S(Subscriber): def __init__(self): self.future = asyncio.get_event_loop().create_future() def emit(self, value, who: Publisher) -> asyncio.Future: return self.future p = Publisher() s1 = S() p.subscribe(s1) assert p.notify(None) is s1.future s2 = S() p.subscribe(s2) gathered_future = p.notify(None) await asyncio.sleep(0) assert not gathered_future.done() s1.future.set_result(1) await asyncio.sleep(0) assert not gathered_future.done() s2.future.set_result(1) await asyncio.sleep(0) assert gathered_future.done()
def test_getattr_attribute(): class Foo: a = None def __init__(self, a=5): self.a = a p = Publisher(Foo(3)) p.inherit_type(Foo) dut = p.a m = mock.Mock() dut.subscribe(Sink(m)) m.assert_called_once_with(3) assert dut.get() == 3 m.reset_mock() p.notify(Foo(4)) assert dut.get() == 4 m.assert_called_once_with(4) with pytest.raises(ValueError): dut.emit(0, who=Publisher()) with pytest.raises(AttributeError): dut.assnign(5)
async def test_throttle_unsubscribe(event_loop): p = Publisher() mock_sink = mock.Mock() throttle = p | op.Throttle(0.1) disposable = throttle.subscribe(Sink(mock_sink)) # test subscription and unsubscribe p.notify(2) mock_sink.assert_called_once_with(2) await asyncio.sleep(0.05, loop=event_loop) mock_sink.reset_mock() disposable.dispose() await asyncio.sleep(0.1, loop=event_loop) # dispose must not emit anything mock_sink.assert_not_called() p.notify(3) await asyncio.sleep(0.1, loop=event_loop) # after dispose was called, p.notify must not emit to mock_sink mock_sink.assert_not_called()
async def test_cancel(): p = Publisher() future = p | OnEmitFuture(timeout=0.01) future.cancel() p.notify(1) with pytest.raises(asyncio.CancelledError): future.result()
def _done(self, future: asyncio.Future): try: result = future.result() except Exception: # pylint: disable=broad-except self._error_callback(*sys.exc_info()) else: if result != NONE: Publisher.notify(self, result)
def emit(self, value: Any_, who: Publisher) -> None: if who is not self._orginator: raise ValueError('Emit from non assigned publisher') attribute = getattr(value, self._attribute_name) if self._args is None: return Publisher.notify(self, attribute) return Publisher.notify(self, attribute(*self._args, **self._kwargs))
def emit(self, value: Any, who: Publisher) -> None: if who is not self._orginator: raise ValueError('Emit from non assigned publisher') if self._unpack: if self._predicate(*value): return Publisher.notify(self, value) elif self._predicate(value): return Publisher.notify(self, value) return None
def test_publisher(): p = Publisher() future = p | OnEmitFuture() assert not future.done() p.notify(1) assert future.result() == 1 p.notify(2) assert future.result() == 1
def _wait_done_cb(self): if self._last_state is not NONE: try: Publisher.notify(self, self._last_state) except Exception: # pylint: disable=broad-except self._error_callback(*sys.exc_info()) self._last_state = NONE self._call_later_handler = self._loop.call_later( self._duration, self._wait_done_cb) else: self._call_later_handler = None
def test_partition(): mock = Mock() p = Publisher() dut = p | Partition(3) dut | Sink(mock) p.notify(1) p.notify(2) mock.assert_not_called() dut.flush() mock.assert_called_once_with((1, 2))
def _delayed_emit_cb(self, value=NONE): if value is NONE: # since the last emit the given duration has passed without another # emit return try: Publisher.notify(self, value) except Exception: # pylint: disable=broad-except self._error_callback(*sys.exc_info()) self._timer.start(self._duration)
def subscribe(self, subscriber: 'Subscriber', prepend: bool = False) -> SubscriptionDisposable: disposable = MultiOperator.subscribe(self, subscriber, prepend) if self._missing: self._missing.clear() if self._state is NONE: Publisher.notify(self, self._init) else: Publisher.notify(self, self._state) return disposable
def test_allow_stateless_extensive(): source1 = StatefulPublisher(0) source2 = Publisher() source3 = StatefulPublisher(0) source4 = Publisher() dut = CombineLatest(source1, source2, source3, source4, allow_stateless=True, emit_on=(source2, source3)) with pytest.raises(ValueError): dut | Sink() # source4 is stateless but not in emit_on list def reverse(s1, s2, s3, s4): return (s4, s3, s2, s1) dut = CombineLatest(source1, source2, source3, source4, map_=reverse, allow_stateless=True, emit_on=(source2, source3, source4)) with pytest.raises(ValueError): dut.get() collector = Collector() dut.subscribe(collector) with pytest.raises(ValueError): dut.get() assert collector.result_vector == () collector.reset() source1.notify(1) source2.notify(2) with pytest.raises(ValueError): dut.get() assert collector.result_vector == ((NONE, 0, 2, 1), ) collector.reset() source3.notify(3) source4.notify(4) with pytest.raises(ValueError): dut.get() assert collector.result_vector == ( (NONE, 3, NONE, 1), (4, 3, NONE, 1), )
def test_inherit_getattr(): p = Publisher('') p.inherit_type(str) dut = p.lower().split(' ') m = mock.Mock() dut.subscribe(Sink(m)) m.assert_called_once_with(['']) m.reset_mock() p.notify('This is a TEST') m.assert_called_once_with(['this', 'is', 'a', 'test'])
def test_inherit_with_operators(): p = Publisher('') p.inherit_type(str) dut = op.Len(('abc' + p + 'ghi').upper()) m = mock.Mock() dut.subscribe(Sink(m)) m.assert_called_once_with(6) m.reset_mock() p.notify('def') m.assert_called_once_with(9)
def test_uninitialised_with_publisher(): source = Publisher() dut = source | Cache() cb = mock.Mock() dut | Sink(cb) cb.assert_not_called() source.notify(1) cb.assert_called_once_with(1) source.notify(1) cb.assert_called_once_with(1)
async def test_errorhandler(): mock = Mock(side_effect=ZeroDivisionError) mock_errorhandler = Mock() p = Publisher() dut = p | Delay(0.1, error_callback=mock_errorhandler) dut | Sink(mock) p.notify(1) await asyncio.sleep(0.15) mock.assert_called_once_with(1) mock_errorhandler.assert_called_once_with(ZeroDivisionError, ANY, ANY)
def test_stateless_only(): source1 = Publisher() source2 = Publisher() dut = CombineLatest(source1, source2, allow_stateless=True) collector = Collector() dut.subscribe(collector) with pytest.raises(ValueError): dut.get() source1.notify(1) source2.notify(True) source1.notify(2) source2.notify(False) assert collector.result_vector == ((1, NONE), (NONE, True), (2, NONE), (NONE, False)) # special case with one source dut2 = CombineLatest(source1, allow_stateless=True) collector2 = Collector() dut2.subscribe(collector2) with pytest.raises(ValueError): dut2.get() assert collector2.result_vector == ()
def emit(self, value: Any_, who: Publisher) -> None: if who is not self._orginator: raise ValueError('Emit from non assigned publisher') result = self._operation(value) return Publisher.notify(self, result)
def emit(self, value: Any, who: Publisher) -> None: if who is not self._orginator: raise ValueError('Emit from non assigned publisher') if not bool(value): return Publisher.notify(self, value) return None
def emit(self, value: Any, who: Publisher) -> None: if all(who is not p for p in self._orginators): raise ValueError('Emit from non assigned publisher') # remove source publisher from ._missing self._missing.discard(who) index = self._index[who] # remember state of this source self._partial_state[index] = value # if emits from publishers are missing or source of this emit # is not one of emit_on -> don't evaluate and notify subscribers if self._missing or (self._emit_on is not None and all(who is not p for p in self._emit_on)): return None # evaluate if self._map: state = self._map(*self._partial_state) else: state = tuple(self._partial_state) # if result of _map() was NONE don't emit if state is NONE: return None self._state = state return Publisher.notify(self, state)
async def test_throttle(event_loop, emit_sequence, expected_emits): p = Publisher() mock_sink = mock.Mock() throttle = p | op.Throttle(0.5) disposable = throttle.subscribe(Sink(mock_sink)) mock_sink.assert_not_called() for item in emit_sequence: await asyncio.sleep(item[0], loop=event_loop) p.notify(item[1]) await asyncio.sleep(0.5, loop=event_loop) mock_sink.assert_has_calls(expected_emits)
def check_subscription(operator, input_vector, output_vector): assert len(input_vector) == len(output_vector) m = mock.Mock() p = Publisher(input_vector[0]) o = p | operator o2 = Publisher() | operator # subscribe operator to publisher disposable = o.subscribe(Sink(m)) assert p.subscriptions == (o,) # now it should be subscribed assert o.dependencies == (p,) # test emit on subscription if output_vector[0] is not NONE: m.assert_called_once_with(output_vector[0]) m.reset_mock() # test input_vector for input_value, output_value in zip(input_vector[1:], output_vector[1:]): if input_value is not NONE: p.notify(input_value) if output_value is NONE: m.assert_not_called() else: m.assert_called_with(output_value) m.reset_mock() # test input_vector with unsubscriptions between disposable.dispose() assert o.dependencies == (p,) disposable = o.subscribe(Sink(m)) for input_value, output_value in zip(input_vector, output_vector): if input_value is not NONE: m.reset_mock() p.notify(input_value) if output_value is NONE: m.assert_not_called() else: m.assert_called_once_with(output_value)
def test_initialised_with_publisher(): source = Publisher() dut = source | Cache(1) cb = mock.Mock() dut | Sink(cb) cb.assert_called_once_with(1) cb.reset_mock() source.notify(2) cb.assert_called_once_with(2) source.notify(2) cb.assert_called_once_with(2) with pytest.raises(ValueError): Publisher() | dut