/** * 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.ArgumentMatchers.any; import static org.mockito.Mockito.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Test; import org.reactivestreams.Subscriber; import io.reactivex.*; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; import io.reactivex.processors.*; import io.reactivex.subscribers.*; public class FlowableWindowWithFlowableTest { @Test public void testWindowViaFlowableNormal1() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); final Subscriber<Object> o = TestHelper.mockSubscriber(); final List<Subscriber<Object>> values = new ArrayList<Subscriber<Object>>(); Subscriber<Flowable<Integer>> wo = new DefaultSubscriber<Flowable<Integer>>() { @Override public void onNext(Flowable<Integer> args) { final Subscriber<Object> mo = TestHelper.mockSubscriber(); values.add(mo); args.subscribe(mo); } @Override public void onError(Throwable e) { o.onError(e); } @Override public void onComplete() { o.onComplete(); } }; source.window(boundary).subscribe(wo); int n = 30; for (int i = 0; i < n; i++) { source.onNext(i); if (i % 3 == 2 && i < n - 1) { boundary.onNext(i / 3); } } source.onComplete(); verify(o, never()).onError(any(Throwable.class)); assertEquals(n / 3, values.size()); int j = 0; for (Subscriber<Object> mo : values) { verify(mo, never()).onError(any(Throwable.class)); for (int i = 0; i < 3; i++) { verify(mo).onNext(j + i); } verify(mo).onComplete(); j += 3; } verify(o).onComplete(); } @Test public void testWindowViaFlowableBoundaryCompletes() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); final Subscriber<Object> o = TestHelper.mockSubscriber(); final List<Subscriber<Object>> values = new ArrayList<Subscriber<Object>>(); Subscriber<Flowable<Integer>> wo = new DefaultSubscriber<Flowable<Integer>>() { @Override public void onNext(Flowable<Integer> args) { final Subscriber<Object> mo = TestHelper.mockSubscriber(); values.add(mo); args.subscribe(mo); } @Override public void onError(Throwable e) { o.onError(e); } @Override public void onComplete() { o.onComplete(); } }; source.window(boundary).subscribe(wo); int n = 30; for (int i = 0; i < n; i++) { source.onNext(i); if (i % 3 == 2 && i < n - 1) { boundary.onNext(i / 3); } } boundary.onComplete(); assertEquals(n / 3, values.size()); int j = 0; for (Subscriber<Object> mo : values) { for (int i = 0; i < 3; i++) { verify(mo).onNext(j + i); } verify(mo).onComplete(); verify(mo, never()).onError(any(Throwable.class)); j += 3; } verify(o).onComplete(); verify(o, never()).onError(any(Throwable.class)); } @Test public void testWindowViaFlowableBoundaryThrows() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); final Subscriber<Object> o = TestHelper.mockSubscriber(); final List<Subscriber<Object>> values = new ArrayList<Subscriber<Object>>(); Subscriber<Flowable<Integer>> wo = new DefaultSubscriber<Flowable<Integer>>() { @Override public void onNext(Flowable<Integer> args) { final Subscriber<Object> mo = TestHelper.mockSubscriber(); values.add(mo); args.subscribe(mo); } @Override public void onError(Throwable e) { o.onError(e); } @Override public void onComplete() { o.onComplete(); } }; source.window(boundary).subscribe(wo); source.onNext(0); source.onNext(1); source.onNext(2); boundary.onError(new TestException()); assertEquals(1, values.size()); Subscriber<Object> mo = values.get(0); verify(mo).onNext(0); verify(mo).onNext(1); verify(mo).onNext(2); verify(mo).onError(any(TestException.class)); verify(o, never()).onComplete(); verify(o).onError(any(TestException.class)); } @Test public void testWindowViaFlowableSourceThrows() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> boundary = PublishProcessor.create(); final Subscriber<Object> o = TestHelper.mockSubscriber(); final List<Subscriber<Object>> values = new ArrayList<Subscriber<Object>>(); Subscriber<Flowable<Integer>> wo = new DefaultSubscriber<Flowable<Integer>>() { @Override public void onNext(Flowable<Integer> args) { final Subscriber<Object> mo = TestHelper.mockSubscriber(); values.add(mo); args.subscribe(mo); } @Override public void onError(Throwable e) { o.onError(e); } @Override public void onComplete() { o.onComplete(); } }; source.window(boundary).subscribe(wo); source.onNext(0); source.onNext(1); source.onNext(2); source.onError(new TestException()); assertEquals(1, values.size()); Subscriber<Object> mo = values.get(0); verify(mo).onNext(0); verify(mo).onNext(1); verify(mo).onNext(2); verify(mo).onError(any(TestException.class)); verify(o, never()).onComplete(); verify(o).onError(any(TestException.class)); } @Test public void testWindowNoDuplication() { final PublishProcessor<Integer> source = PublishProcessor.create(); final TestSubscriber<Integer> tsw = new TestSubscriber<Integer>() { boolean once; @Override public void onNext(Integer t) { if (!once) { once = true; source.onNext(2); } super.onNext(t); } }; TestSubscriber<Flowable<Integer>> ts = new TestSubscriber<Flowable<Integer>>() { @Override public void onNext(Flowable<Integer> t) { t.subscribe(tsw); super.onNext(t); } }; source.window(new Callable<Flowable<Object>>() { @Override public Flowable<Object> call() { return Flowable.never(); } }).subscribe(ts); source.onNext(1); source.onComplete(); ts.assertValueCount(1); tsw.assertValues(1, 2); } @Test public void testWindowViaFlowableNoUnsubscribe() { Flowable<Integer> source = Flowable.range(1, 10); Callable<Flowable<String>> boundary = new Callable<Flowable<String>>() { @Override public Flowable<String> call() { return Flowable.empty(); } }; TestSubscriber<Flowable<Integer>> ts = new TestSubscriber<Flowable<Integer>>(); source.window(boundary).subscribe(ts); assertFalse(ts.isCancelled()); } @Test public void testBoundaryUnsubscribedOnMainCompletion() { PublishProcessor<Integer> source = PublishProcessor.create(); final PublishProcessor<Integer> boundary = PublishProcessor.create(); Callable<Flowable<Integer>> boundaryFunc = new Callable<Flowable<Integer>>() { @Override public Flowable<Integer> call() { return boundary; } }; TestSubscriber<Flowable<Integer>> ts = new TestSubscriber<Flowable<Integer>>(); source.window(boundaryFunc).subscribe(ts); assertTrue(source.hasSubscribers()); assertTrue(boundary.hasSubscribers()); source.onComplete(); assertFalse(source.hasSubscribers()); assertFalse(boundary.hasSubscribers()); ts.assertComplete(); ts.assertNoErrors(); ts.assertValueCount(1); } @Test public void testMainUnsubscribedOnBoundaryCompletion() { PublishProcessor<Integer> source = PublishProcessor.create(); final PublishProcessor<Integer> boundary = PublishProcessor.create(); Callable<Flowable<Integer>> boundaryFunc = new Callable<Flowable<Integer>>() { @Override public Flowable<Integer> call() { return boundary; } }; TestSubscriber<Flowable<Integer>> ts = new TestSubscriber<Flowable<Integer>>(); source.window(boundaryFunc).subscribe(ts); assertTrue(source.hasSubscribers()); assertTrue(boundary.hasSubscribers()); boundary.onComplete(); // FIXME source still active because the open window assertTrue(source.hasSubscribers()); assertFalse(boundary.hasSubscribers()); ts.assertComplete(); ts.assertNoErrors(); ts.assertValueCount(1); } @Test public void testChildUnsubscribed() { PublishProcessor<Integer> source = PublishProcessor.create(); final PublishProcessor<Integer> boundary = PublishProcessor.create(); Callable<Flowable<Integer>> boundaryFunc = new Callable<Flowable<Integer>>() { @Override public Flowable<Integer> call() { return boundary; } }; TestSubscriber<Flowable<Integer>> ts = new TestSubscriber<Flowable<Integer>>(); source.window(boundaryFunc).subscribe(ts); assertTrue(source.hasSubscribers()); assertTrue(boundary.hasSubscribers()); ts.dispose(); // FIXME source has subscribers because the open window assertTrue(source.hasSubscribers()); // FIXME boundary has subscribers because the open window assertTrue(boundary.hasSubscribers()); ts.assertNotComplete(); ts.assertNoErrors(); ts.assertValueCount(1); } @Test public void testInnerBackpressure() { Flowable<Integer> source = Flowable.range(1, 10); final PublishProcessor<Integer> boundary = PublishProcessor.create(); Callable<Flowable<Integer>> boundaryFunc = new Callable<Flowable<Integer>>() { @Override public Flowable<Integer> call() { return boundary; } }; final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1L); final TestSubscriber<Flowable<Integer>> ts1 = new TestSubscriber<Flowable<Integer>>(1L) { @Override public void onNext(Flowable<Integer> t) { super.onNext(t); t.subscribe(ts); } }; source.window(boundaryFunc) .subscribe(ts1); ts1.assertNoErrors(); ts1.assertComplete(); ts1.assertValueCount(1); ts.assertNoErrors(); ts.assertNotComplete(); ts.assertValues(1); ts.request(11); ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); ts.assertNoErrors(); ts.assertComplete(); } @Test public void newBoundaryCalledAfterWindowClosed() { final AtomicInteger calls = new AtomicInteger(); PublishProcessor<Integer> source = PublishProcessor.create(); final PublishProcessor<Integer> boundary = PublishProcessor.create(); Callable<Flowable<Integer>> boundaryFunc = new Callable<Flowable<Integer>>() { @Override public Flowable<Integer> call() { calls.getAndIncrement(); return boundary; } }; TestSubscriber<Flowable<Integer>> ts = new TestSubscriber<Flowable<Integer>>(); source.window(boundaryFunc).subscribe(ts); source.onNext(1); boundary.onNext(1); assertTrue(boundary.hasSubscribers()); source.onNext(2); boundary.onNext(2); assertTrue(boundary.hasSubscribers()); source.onNext(3); boundary.onNext(3); assertTrue(boundary.hasSubscribers()); source.onNext(4); source.onComplete(); ts.assertNoErrors(); ts.assertValueCount(4); ts.assertComplete(); assertFalse(source.hasSubscribers()); assertFalse(boundary.hasSubscribers()); } @Test public void boundaryDispose() { TestHelper.checkDisposed(Flowable.never().window(Flowable.never())); } @Test public void boundaryDispose2() { TestHelper.checkDisposed(Flowable.never().window(Functions.justCallable(Flowable.never()))); } @Test public void boundaryOnError() { TestSubscriber<Object> to = Flowable.error(new TestException()) .window(Flowable.never()) .flatMap(Functions.<Flowable<Object>>identity(), true) .test() .assertFailure(CompositeException.class); List<Throwable> errors = TestHelper.compositeList(to.errors().get(0)); TestHelper.assertError(errors, 0, TestException.class); } @Test public void mainError() { Flowable.error(new TestException()) .window(Functions.justCallable(Flowable.never())) .test() .assertError(TestException.class); } @Test public void innerBadSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override public Object apply(Flowable<Integer> o) throws Exception { return Flowable.just(1).window(o).flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; } }); } }, false, 1, 1, (Object[])null); TestHelper.checkBadSourceFlowable(new Function<Flowable<Integer>, Object>() { @Override public Object apply(Flowable<Integer> o) throws Exception { return Flowable.just(1).window(Functions.justCallable(o)).flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; } }); } }, false, 1, 1, (Object[])null); } @Test public void reentrant() { final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create(); TestSubscriber<Integer> to = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { ps.onNext(2); ps.onComplete(); } } }; ps.window(BehaviorProcessor.createDefault(1)) .flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; } }) .subscribe(to); ps.onNext(1); to .awaitDone(1, TimeUnit.SECONDS) .assertResult(1, 2); } @Test public void reentrantCallable() { final FlowableProcessor<Integer> ps = PublishProcessor.<Integer>create(); TestSubscriber<Integer> to = new TestSubscriber<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { ps.onNext(2); ps.onComplete(); } } }; ps.window(new Callable<Flowable<Integer>>() { boolean once; @Override public Flowable<Integer> call() throws Exception { if (!once) { once = true; return BehaviorProcessor.createDefault(1); } return Flowable.never(); } }) .flatMap(new Function<Flowable<Integer>, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Flowable<Integer> v) throws Exception { return v; } }) .subscribe(to); ps.onNext(1); to .awaitDone(1, TimeUnit.SECONDS) .assertResult(1, 2); } @Test public void badSource() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override public Object apply(Flowable<Object> o) throws Exception { return o.window(Flowable.never()).flatMap(new Function<Flowable<Object>, Flowable<Object>>() { @Override public Flowable<Object> apply(Flowable<Object> v) throws Exception { return v; } }); } }, false, 1, 1, 1); } @Test public void badSourceCallable() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override public Object apply(Flowable<Object> o) throws Exception { return o.window(Functions.justCallable(Flowable.never())).flatMap(new Function<Flowable<Object>, Flowable<Object>>() { @Override public Flowable<Object> apply(Flowable<Object> v) throws Exception { return v; } }); } }, false, 1, 1, 1); } }