/** * 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.flowable; 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 org.reactivestreams.*; import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.processors.PublishProcessor; import io.reactivex.schedulers.Schedulers; import io.reactivex.subscribers.TestSubscriber; public class FlowableCacheTest { @Test public void testColdReplayNoBackpressure() { FlowableCache<Integer> source = new FlowableCache<Integer>(Flowable.range(0, 1000), 16); assertFalse("Source is connected!", source.isConnected()); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); source.subscribe(ts); assertTrue("Source is not connected!", source.isConnected()); assertFalse("Subscribers retained!", source.hasSubscribers()); 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 testColdReplayBackpressure() { FlowableCache<Integer> source = new FlowableCache<Integer>(Flowable.range(0, 1000), 16); assertFalse("Source is connected!", source.isConnected()); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L); ts.request(10); source.subscribe(ts); assertTrue("Source is not connected!", source.isConnected()); assertFalse("Subscribers retained!", source.hasSubscribers()); ts.assertNoErrors(); ts.assertNotComplete(); List<Integer> onNextEvents = ts.values(); assertEquals(10, onNextEvents.size()); for (int i = 0; i < 10; i++) { assertEquals((Integer)i, onNextEvents.get(i)); } ts.dispose(); assertFalse("Subscribers retained!", source.hasSubscribers()); } @Test public void testCache() throws InterruptedException { final AtomicInteger counter = new AtomicInteger(); Flowable<String> o = Flowable.unsafeCreate(new Publisher<String>() { @Override public void subscribe(final Subscriber<? super String> observer) { observer.onSubscribe(new BooleanSubscription()); 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); Flowable<Integer> o = Flowable.just(1).doOnCancel(unsubscribe).cache(); o.subscribe(); o.subscribe(); o.subscribe(); verify(unsubscribe, times(1)).run(); } @Test public void testTake() { TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); FlowableCache<Integer> cached = new FlowableCache<Integer>(Flowable.range(1, 100), 16); 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.hasSubscribers()); } @Test public void testAsync() { Flowable<Integer> source = Flowable.range(1, 10000); for (int i = 0; i < 100; i++) { TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(); FlowableCache<Integer> cached = new FlowableCache<Integer>(source, 16); cached.observeOn(Schedulers.computation()).subscribe(ts1); ts1.awaitTerminalEvent(2, TimeUnit.SECONDS); ts1.assertNoErrors(); ts1.assertComplete(); assertEquals(10000, ts1.values().size()); TestSubscriber<Integer> ts2 = new TestSubscriber<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() { Flowable<Long> source = Flowable.interval(1, 1, TimeUnit.MILLISECONDS) .take(1000) .subscribeOn(Schedulers.io()); FlowableCache<Long> cached = new FlowableCache<Long>(source, 16); Flowable<Long> output = cached.observeOn(Schedulers.computation()); List<TestSubscriber<Long>> list = new ArrayList<TestSubscriber<Long>>(100); for (int i = 0; i < 100; i++) { TestSubscriber<Long> ts = new TestSubscriber<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 (TestSubscriber<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; Flowable<Integer> firehose = Flowable.unsafeCreate(new Publisher<Integer>() { @Override public void subscribe(Subscriber<? super Integer> t) { t.onSubscribe(new BooleanSubscription()); for (int i = 0; i < m; i++) { t.onNext(i); } t.onComplete(); } }); TestSubscriber<Integer> ts = new TestSubscriber<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() { Flowable<Integer> source = Flowable.range(1, 10) .concatWith(Flowable.<Integer>error(new TestException())) .cache(); TestSubscriber<Integer> ts = new TestSubscriber<Integer>(); source.subscribe(ts); ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ts.assertNotComplete(); ts.assertError(TestException.class); TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>(); source.subscribe(ts2); ts2.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ts2.assertNotComplete(); ts2.assertError(TestException.class); } @Test @Ignore("RS subscribers should not throw") public void unsafeChildThrows() { final AtomicInteger count = new AtomicInteger(); Flowable<Integer> source = Flowable.range(1, 100) .doOnNext(new Consumer<Integer>() { @Override public void accept(Integer t) { count.getAndIncrement(); } }) .cache(); TestSubscriber<Integer> ts = new TestSubscriber<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 take() { Flowable<Integer> cache = Flowable.range(1, 5).cache(); cache.take(2).test().assertResult(1, 2); cache.take(3).test().assertResult(1, 2, 3); } @Test public void dispose() { TestHelper.checkDisposed(Flowable.range(1, 5).cache()); } @Test public void disposeOnArrival2() { Flowable<Integer> o = PublishProcessor.<Integer>create().cache(); o.test(); o.test(0L, true) .assertEmpty(); } @Test public void subscribeEmitRace() { for (int i = 0; i < 500; i++) { final PublishProcessor<Integer> ps = PublishProcessor.<Integer>create(); final Flowable<Integer> cache = ps.cache(); cache.test(); final TestSubscriber<Integer> to = new TestSubscriber<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(); } } @Test public void observers() { PublishProcessor<Integer> ps = PublishProcessor.create(); FlowableCache<Integer> cache = (FlowableCache<Integer>)Flowable.range(1, 5).concatWith(ps).cache(); assertFalse(cache.hasSubscribers()); assertEquals(0, cache.cachedEventCount()); TestSubscriber<Integer> to = cache.test(); assertTrue(cache.hasSubscribers()); assertEquals(5, cache.cachedEventCount()); ps.onComplete(); to.assertResult(1, 2, 3, 4, 5); } @Test public void disposeOnArrival() { Flowable.range(1, 5).cache() .test(0L, true) .assertEmpty(); } @Test public void badSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override public Object apply(Flowable<Object> f) throws Exception { return f.cache(); } }, false, 1, 1, 1); } @Test public void badRequest() { TestHelper.assertBadRequestReported(Flowable.never().cache()); } @Test public void take1() { Flowable<Integer> cache = Flowable.just(1, 2) .cache(); cache.test(); cache .take(1) .test() .assertResult(1); } @Test public void empty() { Flowable.empty() .cache() .test(0L) .assertResult(); } @Test public void error() { Flowable.error(new TestException()) .cache() .test(0L) .assertFailure(TestException.class); } }