/** * https://github.com/ReactiveX/RxJava/issues/198 * * <p>Rx Design Guidelines 5.2 * * <p>"when calling the Subscribe method that only has an onNext argument, the OnError behavior * will be to rethrow the exception on the thread that the message comes out from the Observable. * The OnCompleted behavior in this case is to do nothing." * * @throws InterruptedException */ @Test @Ignore("Subscribers can't throw") public void testErrorThrownWithoutErrorHandlerAsynchronous() throws InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference<Throwable> exception = new AtomicReference<>(); Observable.create( observer -> { new Thread( () -> { try { observer.onError(new Error("failure")); } catch (Throwable e) { // without an onError handler it has to just throw on whatever thread // invokes it exception.set(e); } latch.countDown(); }) .start(); }) .subscribe(); // wait for exception latch.await(3000, TimeUnit.MILLISECONDS); assertNotNull(exception.get()); assertEquals("failure", exception.get().getMessage()); }
/** * Incrementing int without backpressure. * * @param counter * @return */ private static Observable<Integer> firehose(final AtomicInteger counter) { return Observable.create( s -> { Subscription s2 = new FirehoseNoBackpressure(counter, s); s.onSubscribe(s2); }); }
@Test public void testPublishLast() throws InterruptedException { final AtomicInteger count = new AtomicInteger(); ConnectableObservable<String> connectable = Observable.<String>create( observer -> { observer.onSubscribe(EmptySubscription.INSTANCE); count.incrementAndGet(); new Thread( () -> { observer.onNext("first"); observer.onNext("last"); observer.onComplete(); }) .start(); }) .takeLast(1) .publish(); // subscribe once final CountDownLatch latch = new CountDownLatch(1); connectable.subscribe( value -> { assertEquals("last", value); latch.countDown(); }); // subscribe twice connectable.subscribe(); Disposable subscription = connectable.connect(); assertTrue(latch.await(1000, TimeUnit.MILLISECONDS)); assertEquals(1, count.get()); subscription.dispose(); }
@Test public void testReplay() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); ConnectableObservable<String> o = Observable.<String>create( observer -> { observer.onSubscribe(EmptySubscription.INSTANCE); new Thread( new Runnable() { @Override public void run() { counter.incrementAndGet(); observer.onNext("one"); observer.onComplete(); } }) .start(); }) .replay(); // we connect immediately and it will emit the value Disposable s = o.connect(); try { // we then expect the following 2 subscriptions to get that same value final CountDownLatch latch = new CountDownLatch(2); // subscribe once o.subscribe( v -> { assertEquals("one", v); latch.countDown(); }); // subscribe again o.subscribe( v -> { assertEquals("one", v); latch.countDown(); }); if (!latch.await(1000, TimeUnit.MILLISECONDS)) { fail("subscriptions did not receive values"); } assertEquals(1, counter.get()); } finally { s.dispose(); } }
/** * Incrementing int without backpressure. * * @param counter * @return */ private static Observable<Integer> firehose(final AtomicInteger counter) { return Observable.create( new OnSubscribe<Integer>() { int i = 0; @Override public void call(final Subscriber<? super Integer> s) { while (!s.isUnsubscribed()) { s.onNext(i++); counter.incrementAndGet(); } System.out.println("unsubscribed after: " + i); } }); }
@Ignore // FIXME throwing is not allowed from the create?! @Test public void testOnSubscribeFails() { Subscriber<String> observer = TestHelper.mockSubscriber(); final RuntimeException re = new RuntimeException("bad impl"); Observable<String> o = Observable.create( s -> { throw re; }); o.subscribe(observer); verify(observer, times(0)).onNext(anyString()); verify(observer, times(0)).onComplete(); verify(observer, times(1)).onError(re); }
private static Observable<Integer> incrementingIntegers( final AtomicInteger counter, final ConcurrentLinkedQueue<Thread> threadsSeen) { return Observable.create( new Publisher<Integer>() { @Override public void subscribe(final Subscriber<? super Integer> s) { s.onSubscribe( new Subscription() { int i = 0; volatile boolean cancelled; final AtomicLong requested = new AtomicLong(); @Override public void request(long n) { if (SubscriptionHelper.validateRequest(n)) { return; } if (threadsSeen != null) { threadsSeen.offer(Thread.currentThread()); } long _c = BackpressureHelper.add(requested, n); if (_c == 0) { while (!cancelled) { counter.incrementAndGet(); s.onNext(i++); if (requested.decrementAndGet() == 0) { // we're done emitting the number requested so return return; } } } } @Override public void cancel() { cancelled = true; } }); } }); }
@Test public void testRequestToObservable() { TestSubscriber<Integer> ts = new TestSubscriber<>(); ts.request(3); final AtomicLong requested = new AtomicLong(); Observable.<Integer>create( s -> s.onSubscribe( new Subscription() { @Override public void request(long n) { requested.set(n); } @Override public void cancel() {} })) .subscribe(ts); assertEquals(3, requested.get()); }
@Test public void testCacheWithCapacity() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); Observable<String> o = Observable.<String>create( observer -> { observer.onSubscribe(EmptySubscription.INSTANCE); new Thread( () -> { counter.incrementAndGet(); observer.onNext("one"); observer.onComplete(); }) .start(); }) .cache(1); // we then expect the following 2 subscriptions to get that same value final CountDownLatch latch = new CountDownLatch(2); // subscribe once o.subscribe( v -> { assertEquals("one", v); latch.countDown(); }); // subscribe again o.subscribe( v -> { assertEquals("one", v); latch.countDown(); }); if (!latch.await(1000, TimeUnit.MILLISECONDS)) { fail("subscriptions did not receive values"); } assertEquals(1, counter.get()); }
@Test public void testRequestThroughTakeWhereRequestIsSmallerThanTake() { TestSubscriber<Integer> ts = new TestSubscriber<>((Long) null); ts.request(3); final AtomicLong requested = new AtomicLong(); Observable.<Integer>create( s -> s.onSubscribe( new Subscription() { @Override public void request(long n) { requested.set(n); } @Override public void cancel() {} })) .take(10) .subscribe(ts); assertEquals(3, requested.get()); }
private static Observable<Integer> incrementingIntegers( final AtomicInteger counter, final ConcurrentLinkedQueue<Thread> threadsSeen) { return Observable.create( new OnSubscribe<Integer>() { final AtomicLong requested = new AtomicLong(); @Override public void call(final Subscriber<? super Integer> s) { s.setProducer( new Producer() { int i = 0; @Override public void request(long n) { if (n == 0) { // nothing to do return; } if (threadsSeen != null) { threadsSeen.offer(Thread.currentThread()); } long _c = requested.getAndAdd(n); if (_c == 0) { while (!s.isUnsubscribed()) { counter.incrementAndGet(); s.onNext(i++); if (requested.decrementAndGet() == 0) { // we're done emitting the number requested so return return; } } } } }); } }); }
@Test public void testRequestThroughTakeThatReducesRequest() { TestSubscriber<Integer> ts = new TestSubscriber<>((Long) null); ts.request(3); final AtomicLong requested = new AtomicLong(); Observable.<Integer>create( s -> s.onSubscribe( new Subscription() { @Override public void request(long n) { requested.set(n); } @Override public void cancel() {} })) .take(2) .subscribe(ts); // FIXME the take now requests Long.MAX_PATH if downstream requests at least the limit assertEquals(Long.MAX_VALUE, requested.get()); }