/** * 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 java.util.*; import java.util.concurrent.*; import org.junit.*; import org.reactivestreams.*; import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.*; import io.reactivex.internal.functions.Functions; import io.reactivex.internal.subscriptions.BooleanSubscription; import io.reactivex.processors.*; import io.reactivex.schedulers.TestScheduler; import io.reactivex.subscribers.*; public class FlowableWindowWithStartEndFlowableTest { private TestScheduler scheduler; private Scheduler.Worker innerScheduler; @Before public void before() { scheduler = new TestScheduler(); innerScheduler = scheduler.createWorker(); } @Test public void testFlowableBasedOpenerAndCloser() { final List<String> list = new ArrayList<String>(); final List<List<String>> lists = new ArrayList<List<String>>(); Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override public void subscribe(Subscriber<? super String> observer) { observer.onSubscribe(new BooleanSubscription()); push(observer, "one", 10); push(observer, "two", 60); push(observer, "three", 110); push(observer, "four", 160); push(observer, "five", 210); complete(observer, 500); } }); Flowable<Object> openings = Flowable.unsafeCreate(new Publisher<Object>() { @Override public void subscribe(Subscriber<? super Object> observer) { observer.onSubscribe(new BooleanSubscription()); push(observer, new Object(), 50); push(observer, new Object(), 200); complete(observer, 250); } }); Function<Object, Flowable<Object>> closer = new Function<Object, Flowable<Object>>() { @Override public Flowable<Object> apply(Object opening) { return Flowable.unsafeCreate(new Publisher<Object>() { @Override public void subscribe(Subscriber<? super Object> observer) { observer.onSubscribe(new BooleanSubscription()); push(observer, new Object(), 100); complete(observer, 101); } }); } }; Flowable<Flowable<String>> windowed = source.window(openings, closer); windowed.subscribe(observeWindow(list, lists)); scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); assertEquals(2, lists.size()); assertEquals(lists.get(0), list("two", "three")); assertEquals(lists.get(1), list("five")); } @Test public void testFlowableBasedCloser() { final List<String> list = new ArrayList<String>(); final List<List<String>> lists = new ArrayList<List<String>>(); Flowable<String> source = Flowable.unsafeCreate(new Publisher<String>() { @Override public void subscribe(Subscriber<? super String> observer) { observer.onSubscribe(new BooleanSubscription()); push(observer, "one", 10); push(observer, "two", 60); push(observer, "three", 110); push(observer, "four", 160); push(observer, "five", 210); complete(observer, 250); } }); Callable<Flowable<Object>> closer = new Callable<Flowable<Object>>() { int calls; @Override public Flowable<Object> call() { return Flowable.unsafeCreate(new Publisher<Object>() { @Override public void subscribe(Subscriber<? super Object> observer) { observer.onSubscribe(new BooleanSubscription()); int c = calls++; if (c == 0) { push(observer, new Object(), 100); } else if (c == 1) { push(observer, new Object(), 100); } else { complete(observer, 101); } } }); } }; Flowable<Flowable<String>> windowed = source.window(closer); windowed.subscribe(observeWindow(list, lists)); scheduler.advanceTimeTo(500, TimeUnit.MILLISECONDS); assertEquals(3, lists.size()); assertEquals(lists.get(0), list("one", "two")); assertEquals(lists.get(1), list("three", "four")); assertEquals(lists.get(2), list("five")); } private List<String> list(String... args) { List<String> list = new ArrayList<String>(); for (String arg : args) { list.add(arg); } return list; } private <T> void push(final Subscriber<T> observer, final T value, int delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { observer.onNext(value); } }, delay, TimeUnit.MILLISECONDS); } private void complete(final Subscriber<?> observer, int delay) { innerScheduler.schedule(new Runnable() { @Override public void run() { observer.onComplete(); } }, delay, TimeUnit.MILLISECONDS); } private Consumer<Flowable<String>> observeWindow(final List<String> list, final List<List<String>> lists) { return new Consumer<Flowable<String>>() { @Override public void accept(Flowable<String> stringFlowable) { stringFlowable.subscribe(new DefaultSubscriber<String>() { @Override public void onComplete() { lists.add(new ArrayList<String>(list)); list.clear(); } @Override public void onError(Throwable e) { fail(e.getMessage()); } @Override public void onNext(String args) { list.add(args); } }); } }; } @Test public void testNoUnsubscribeAndNoLeak() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> open = PublishProcessor.create(); final PublishProcessor<Integer> close = PublishProcessor.create(); TestSubscriber<Flowable<Integer>> ts = new TestSubscriber<Flowable<Integer>>(); source.window(open, new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer t) { return close; } }).subscribe(ts); open.onNext(1); source.onNext(1); assertTrue(open.hasSubscribers()); assertTrue(close.hasSubscribers()); close.onNext(1); assertFalse(close.hasSubscribers()); source.onComplete(); ts.assertComplete(); ts.assertNoErrors(); ts.assertValueCount(1); assertFalse(ts.isCancelled()); assertFalse(open.hasSubscribers()); assertFalse(close.hasSubscribers()); } @Test public void testUnsubscribeAll() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> open = PublishProcessor.create(); final PublishProcessor<Integer> close = PublishProcessor.create(); TestSubscriber<Flowable<Integer>> ts = new TestSubscriber<Flowable<Integer>>(); source.window(open, new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer t) { return close; } }).subscribe(ts); open.onNext(1); assertTrue(open.hasSubscribers()); assertTrue(close.hasSubscribers()); ts.dispose(); // FIXME subject has subscribers because of the open window assertTrue(open.hasSubscribers()); // FIXME subject has subscribers because of the open window assertTrue(close.hasSubscribers()); } @Test public void dispose() { TestHelper.checkDisposed(Flowable.just(1).window(Flowable.just(2), Functions.justFunction(Flowable.never()))); } @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), Functions.justFunction(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 badSourceCallable() { TestHelper.checkBadSourceFlowable(new Function<Flowable<Object>, Object>() { @Override public Object apply(Flowable<Object> o) throws Exception { return o.window(Flowable.just(1), Functions.justFunction(Flowable.never())); } }, false, 1, 1, (Object[])null); } @Test public void boundarySelectorNormal() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> start = PublishProcessor.create(); final PublishProcessor<Integer> end = PublishProcessor.create(); TestSubscriber<Integer> to = source.window(start, new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { return end; } }) .flatMap(Functions.<Flowable<Integer>>identity()) .test(); start.onNext(0); source.onNext(1); source.onNext(2); source.onNext(3); source.onNext(4); start.onNext(1); source.onNext(5); source.onNext(6); end.onNext(1); start.onNext(2); TestHelper.emit(source, 7, 8); to.assertResult(1, 2, 3, 4, 5, 5, 6, 6, 7, 8); } @Test public void startError() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> start = PublishProcessor.create(); final PublishProcessor<Integer> end = PublishProcessor.create(); TestSubscriber<Integer> to = source.window(start, new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { return end; } }) .flatMap(Functions.<Flowable<Integer>>identity()) .test(); start.onError(new TestException()); to.assertFailure(TestException.class); assertFalse("Source has observers!", source.hasSubscribers()); assertFalse("Start has observers!", start.hasSubscribers()); assertFalse("End has observers!", end.hasSubscribers()); } @Test public void endError() { PublishProcessor<Integer> source = PublishProcessor.create(); PublishProcessor<Integer> start = PublishProcessor.create(); final PublishProcessor<Integer> end = PublishProcessor.create(); TestSubscriber<Integer> to = source.window(start, new Function<Integer, Flowable<Integer>>() { @Override public Flowable<Integer> apply(Integer v) throws Exception { return end; } }) .flatMap(Functions.<Flowable<Integer>>identity()) .test(); start.onNext(1); end.onError(new TestException()); to.assertFailure(TestException.class); assertFalse("Source has observers!", source.hasSubscribers()); assertFalse("Start has observers!", start.hasSubscribers()); assertFalse("End has observers!", end.hasSubscribers()); } @Test public void mainError() { Flowable.<Integer>error(new TestException()) .window(Flowable.never(), Functions.justFunction(Flowable.just(1))) .flatMap(Functions.<Flowable<Integer>>identity()) .test() .assertFailure(TestException.class); } }