/** * Copyright (c) 2016-present, RxJava Contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See * the License for the specific language governing permissions and limitations under the License. */ package io.reactivex.internal.operators.observable; import static org.junit.Assert.*; import static org.mockito.Mockito.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import org.junit.*; import io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; import io.reactivex.disposables.Disposables; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.observers.TestObserver; import io.reactivex.schedulers.Schedulers; import io.reactivex.subjects.PublishSubject; public class ObservableCacheTest { @Test public void testColdReplayNoBackpressure() { ObservableCache<Integer> source = (ObservableCache<Integer>)ObservableCache.from(Observable.range(0, 1000)); assertFalse("Source is connected!", source.isConnected()); TestObserver<Integer> ts = new TestObserver<Integer>(); source.subscribe(ts); assertTrue("Source is not connected!", source.isConnected()); assertFalse("Subscribers retained!", source.hasObservers()); ts.assertNoErrors(); ts.assertTerminated(); List<Integer> onNextEvents = ts.values(); assertEquals(1000, onNextEvents.size()); for (int i = 0; i < 1000; i++) { assertEquals((Integer)i, onNextEvents.get(i)); } } @Test public void testCache() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); Observable<String> o = Observable.unsafeCreate(new ObservableSource<String>() { @Override public void subscribe(final Observer<? super String> observer) { observer.onSubscribe(Disposables.empty()); new Thread(new Runnable() { @Override public void run() { counter.incrementAndGet(); System.out.println("published Observable being executed"); observer.onNext("one"); observer.onComplete(); } }).start(); } }).cache(); // we then expect the following 2 subscriptions to get that same value final CountDownLatch latch = new CountDownLatch(2); // subscribe once o.subscribe(new Consumer<String>() { @Override public void accept(String v) { assertEquals("one", v); System.out.println("v: " + v); latch.countDown(); } }); // subscribe again o.subscribe(new Consumer<String>() { @Override public void accept(String v) { assertEquals("one", v); System.out.println("v: " + v); latch.countDown(); } }); if (!latch.await(1000, TimeUnit.MILLISECONDS)) { fail("subscriptions did not receive values"); } assertEquals(1, counter.get()); } @Test public void testUnsubscribeSource() throws Exception { Action unsubscribe = mock(Action.class); Observable<Integer> o = Observable.just(1).doOnDispose(unsubscribe).cache(); o.subscribe(); o.subscribe(); o.subscribe(); verify(unsubscribe, times(1)).run(); } @Test public void testTake() { TestObserver<Integer> ts = new TestObserver<Integer>(); ObservableCache<Integer> cached = (ObservableCache<Integer>)ObservableCache.from(Observable.range(1, 100)); cached.take(10).subscribe(ts); ts.assertNoErrors(); ts.assertComplete(); ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // ts.assertUnsubscribed(); // FIXME no longer valid assertFalse(cached.hasObservers()); } @Test public void testAsync() { Observable<Integer> source = Observable.range(1, 10000); for (int i = 0; i < 100; i++) { TestObserver<Integer> ts1 = new TestObserver<Integer>(); ObservableCache<Integer> cached = (ObservableCache<Integer>)ObservableCache.from(source); cached.observeOn(Schedulers.computation()).subscribe(ts1); ts1.awaitTerminalEvent(2, TimeUnit.SECONDS); ts1.assertNoErrors(); ts1.assertComplete(); assertEquals(10000, ts1.values().size()); TestObserver<Integer> ts2 = new TestObserver<Integer>(); cached.observeOn(Schedulers.computation()).subscribe(ts2); ts2.awaitTerminalEvent(2, TimeUnit.SECONDS); ts2.assertNoErrors(); ts2.assertComplete(); assertEquals(10000, ts2.values().size()); } } @Test public void testAsyncComeAndGo() { Observable<Long> source = Observable.interval(1, 1, TimeUnit.MILLISECONDS) .take(1000) .subscribeOn(Schedulers.io()); ObservableCache<Long> cached = (ObservableCache<Long>)ObservableCache.from(source); Observable<Long> output = cached.observeOn(Schedulers.computation()); List<TestObserver<Long>> list = new ArrayList<TestObserver<Long>>(100); for (int i = 0; i < 100; i++) { TestObserver<Long> ts = new TestObserver<Long>(); list.add(ts); output.skip(i * 10).take(10).subscribe(ts); } List<Long> expected = new ArrayList<Long>(); for (int i = 0; i < 10; i++) { expected.add((long)(i - 10)); } int j = 0; for (TestObserver<Long> ts : list) { ts.awaitTerminalEvent(3, TimeUnit.SECONDS); ts.assertNoErrors(); ts.assertComplete(); for (int i = j * 10; i < j * 10 + 10; i++) { expected.set(i - j * 10, (long)i); } ts.assertValueSequence(expected); j++; } } @Test public void testNoMissingBackpressureException() { final int m = 4 * 1000 * 1000; Observable<Integer> firehose = Observable.unsafeCreate(new ObservableSource<Integer>() { @Override public void subscribe(Observer<? super Integer> t) { t.onSubscribe(Disposables.empty()); for (int i = 0; i < m; i++) { t.onNext(i); } t.onComplete(); } }); TestObserver<Integer> ts = new TestObserver<Integer>(); firehose.cache().observeOn(Schedulers.computation()).takeLast(100).subscribe(ts); ts.awaitTerminalEvent(3, TimeUnit.SECONDS); ts.assertNoErrors(); ts.assertComplete(); assertEquals(100, ts.values().size()); } @Test public void testValuesAndThenError() { Observable<Integer> source = Observable.range(1, 10) .concatWith(Observable.<Integer>error(new TestException())) .cache(); TestObserver<Integer> ts = new TestObserver<Integer>(); source.subscribe(ts); ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ts.assertNotComplete(); ts.assertError(TestException.class); TestObserver<Integer> ts2 = new TestObserver<Integer>(); source.subscribe(ts2); ts2.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ts2.assertNotComplete(); ts2.assertError(TestException.class); } @Test @Ignore("2.x consumers are not allowed to throw") public void unsafeChildThrows() { final AtomicInteger count = new AtomicInteger(); Observable<Integer> source = Observable.range(1, 100) .doOnNext(new Consumer<Integer>() { @Override public void accept(Integer t) { count.getAndIncrement(); } }) .cache(); TestObserver<Integer> ts = new TestObserver<Integer>() { @Override public void onNext(Integer t) { throw new TestException(); } }; source.subscribe(ts); Assert.assertEquals(100, count.get()); ts.assertNoValues(); ts.assertNotComplete(); ts.assertError(TestException.class); } @Test public void observers() { PublishSubject<Integer> ps = PublishSubject.create(); ObservableCache<Integer> cache = (ObservableCache<Integer>)Observable.range(1, 5).concatWith(ps).cache(); assertFalse(cache.hasObservers()); assertEquals(0, cache.cachedEventCount()); TestObserver<Integer> to = cache.test(); assertTrue(cache.hasObservers()); assertEquals(5, cache.cachedEventCount()); ps.onComplete(); to.assertResult(1, 2, 3, 4, 5); } @Test public void disposeOnArrival() { Observable.range(1, 5).cache() .test(true) .assertEmpty(); } @Test public void disposeOnArrival2() { Observable<Integer> o = PublishSubject.<Integer>create().cache(); o.test(); o.test(true) .assertEmpty(); } @Test public void dispose() { TestHelper.checkDisposed(Observable.range(1, 5).cache()); } @Test public void take() { Observable<Integer> cache = Observable.range(1, 5).cache(); cache.take(2).test().assertResult(1, 2); cache.take(3).test().assertResult(1, 2, 3); } @Test public void subscribeEmitRace() { for (int i = 0; i < 500; i++) { final PublishSubject<Integer> ps = PublishSubject.<Integer>create(); final Observable<Integer> cache = ps.cache(); cache.test(); final TestObserver<Integer> to = new TestObserver<Integer>(); Runnable r1 = new Runnable() { @Override public void run() { cache.subscribe(to); } }; Runnable r2 = new Runnable() { @Override public void run() { for (int j = 0; j < 500; j++) { ps.onNext(j); } ps.onComplete(); } }; TestHelper.race(r1, r2, Schedulers.single()); to .awaitDone(5, TimeUnit.SECONDS) .assertSubscribed().assertValueCount(500).assertComplete().assertNoErrors(); } } }