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)