166 lines
5.8 KiB
Python
166 lines
5.8 KiB
Python
from typing import Any, Callable, List, Optional, TypeVar, Union, cast
|
|
|
|
import reactivex
|
|
from reactivex import Observable, abc, typing
|
|
from reactivex.disposable import Disposable
|
|
from reactivex.scheduler import VirtualTimeScheduler
|
|
from reactivex.testing.recorded import Recorded
|
|
|
|
from .coldobservable import ColdObservable
|
|
from .hotobservable import HotObservable
|
|
from .mockobserver import MockObserver
|
|
from .reactivetest import ReactiveTest
|
|
|
|
_T = TypeVar("_T")
|
|
_TState = TypeVar("_TState")
|
|
|
|
|
|
class TestScheduler(VirtualTimeScheduler):
|
|
"""Test time scheduler used for testing applications and libraries
|
|
built using Reactive Extensions. All time, both absolute and relative is
|
|
specified as integer ticks"""
|
|
|
|
__test__ = False
|
|
|
|
def schedule_absolute(
|
|
self,
|
|
duetime: typing.AbsoluteTime,
|
|
action: typing.ScheduledAction[_TState],
|
|
state: _TState = None,
|
|
) -> abc.DisposableBase:
|
|
"""Schedules an action to be executed at the specified virtual
|
|
time.
|
|
|
|
Args:
|
|
duetime: Absolute virtual time at which to execute the
|
|
action.
|
|
action: Action to be executed.
|
|
state: State passed to the action to be executed.
|
|
|
|
Returns:
|
|
Disposable object used to cancel the scheduled action
|
|
(best effort).
|
|
"""
|
|
|
|
duetime = duetime if isinstance(duetime, float) else self.to_seconds(duetime)
|
|
return super().schedule_absolute(duetime, action, state)
|
|
|
|
def start(
|
|
self,
|
|
create: Optional[Callable[[], Observable[_T]]] = None,
|
|
created: Optional[float] = None,
|
|
subscribed: Optional[float] = None,
|
|
disposed: Optional[float] = None,
|
|
) -> MockObserver[_T]:
|
|
"""Starts the test scheduler and uses the specified virtual
|
|
times to invoke the factory function, subscribe to the
|
|
resulting sequence, and dispose the subscription.
|
|
|
|
Args:
|
|
create: Factory method to create an observable sequence.
|
|
created: Virtual time at which to invoke the factory to
|
|
create an observable sequence.
|
|
subscribed: Virtual time at which to subscribe to the
|
|
created observable sequence.
|
|
disposed: Virtual time at which to dispose the
|
|
subscription.
|
|
|
|
Returns:
|
|
Observer with timestamped recordings of notification
|
|
messages that were received during the virtual time window
|
|
when the subscription to the source sequence was active.
|
|
"""
|
|
|
|
# Defaults
|
|
created = created or ReactiveTest.created
|
|
subscribed = subscribed or ReactiveTest.subscribed
|
|
disposed = disposed or ReactiveTest.disposed
|
|
|
|
observer = self.create_observer()
|
|
subscription: Optional[abc.DisposableBase] = None
|
|
source: Optional[abc.ObservableBase[_T]] = None
|
|
|
|
def action_create(
|
|
scheduler: abc.SchedulerBase, state: Any = None
|
|
) -> abc.DisposableBase:
|
|
"""Called at create time. Defaults to 100"""
|
|
nonlocal source
|
|
source = create() if create is not None else reactivex.never()
|
|
return Disposable()
|
|
|
|
self.schedule_absolute(created, action_create)
|
|
|
|
def action_subscribe(
|
|
scheduler: abc.SchedulerBase, state: Any = None
|
|
) -> abc.DisposableBase:
|
|
"""Called at subscribe time. Defaults to 200"""
|
|
nonlocal subscription
|
|
if source:
|
|
subscription = source.subscribe(observer, scheduler=scheduler)
|
|
return Disposable()
|
|
|
|
self.schedule_absolute(subscribed, action_subscribe)
|
|
|
|
def action_dispose(
|
|
scheduler: abc.SchedulerBase, state: Any = None
|
|
) -> abc.DisposableBase:
|
|
"""Called at dispose time. Defaults to 1000"""
|
|
if subscription:
|
|
subscription.dispose()
|
|
return Disposable()
|
|
|
|
self.schedule_absolute(disposed, action_dispose)
|
|
|
|
super().start()
|
|
return observer
|
|
|
|
def create_hot_observable(
|
|
self, *args: Union[Recorded[_T], List[Recorded[_T]]]
|
|
) -> HotObservable[_T]:
|
|
"""Creates a hot observable using the specified timestamped
|
|
notification messages either as a list or by multiple arguments.
|
|
|
|
Args:
|
|
messages: Notifications to surface through the created sequence at
|
|
their specified absolute virtual times.
|
|
|
|
Returns hot observable sequence that can be used to assert the timing
|
|
of subscriptions and notifications.
|
|
"""
|
|
|
|
if args and isinstance(args[0], List):
|
|
messages = args[0]
|
|
else:
|
|
messages = cast(List[Recorded[_T]], list(args))
|
|
return HotObservable(self, messages)
|
|
|
|
def create_cold_observable(
|
|
self, *args: Union[Recorded[_T], List[Recorded[_T]]]
|
|
) -> ColdObservable[_T]:
|
|
"""Creates a cold observable using the specified timestamped
|
|
notification messages either as an array or arguments.
|
|
|
|
Args:
|
|
args: Notifications to surface through the created sequence
|
|
at their specified virtual time offsets from the
|
|
sequence subscription time.
|
|
|
|
Returns:
|
|
Cold observable sequence that can be used to assert the
|
|
timing of subscriptions and notifications.
|
|
"""
|
|
|
|
if args and isinstance(args[0], list):
|
|
messages = args[0]
|
|
else:
|
|
messages = cast(List[Recorded[_T]], list(args))
|
|
return ColdObservable(self, messages)
|
|
|
|
def create_observer(self) -> MockObserver[Any]:
|
|
"""Creates an observer that records received notification messages and
|
|
timestamps those. Return an Observer that can be used to assert the
|
|
timing of received notifications.
|
|
"""
|
|
|
|
return MockObserver(self)
|