/** * 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.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 io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; import io.reactivex.exceptions.*; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.*; import io.reactivex.subjects.*; public class ObservableWindowWithObservableTest { @Test public void testWindowViaObservableNormal1() { PublishSubject<Integer> source = PublishSubject.create(); PublishSubject<Integer> boundary = PublishSubject.create(); final Observer<Object> o = TestHelper.mockObserver(); final List<Observer<Object>> values = new ArrayList<Observer<Object>>(); Observer<Observable<Integer>> wo = new DefaultObserver<Observable<Integer>>() { @Override public void onNext(Observable<Integer> args) { final Observer<Object> mo = TestHelper.mockObserver(); 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 (Observer<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 testWindowViaObservableBoundaryCompletes() { PublishSubject<Integer> source = PublishSubject.create(); PublishSubject<Integer> boundary = PublishSubject.create(); final Observer<Object> o = TestHelper.mockObserver(); final List<Observer<Object>> values = new ArrayList<Observer<Object>>(); Observer<Observable<Integer>> wo = new DefaultObserver<Observable<Integer>>() { @Override public void onNext(Observable<Integer> args) { final Observer<Object> mo = TestHelper.mockObserver(); 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 (Observer<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 testWindowViaObservableBoundaryThrows() { PublishSubject<Integer> source = PublishSubject.create(); PublishSubject<Integer> boundary = PublishSubject.create(); final Observer<Object> o = TestHelper.mockObserver(); final List<Observer<Object>> values = new ArrayList<Observer<Object>>(); Observer<Observable<Integer>> wo = new DefaultObserver<Observable<Integer>>() { @Override public void onNext(Observable<Integer> args) { final Observer<Object> mo = TestHelper.mockObserver(); 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()); Observer<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 testWindowViaObservableSourceThrows() { PublishSubject<Integer> source = PublishSubject.create(); PublishSubject<Integer> boundary = PublishSubject.create(); final Observer<Object> o = TestHelper.mockObserver(); final List<Observer<Object>> values = new ArrayList<Observer<Object>>(); Observer<Observable<Integer>> wo = new DefaultObserver<Observable<Integer>>() { @Override public void onNext(Observable<Integer> args) { final Observer<Object> mo = TestHelper.mockObserver(); 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()); Observer<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 PublishSubject<Integer> source = PublishSubject.create(); final TestObserver<Integer> tsw = new TestObserver<Integer>() { boolean once; @Override public void onNext(Integer t) { if (!once) { once = true; source.onNext(2); } super.onNext(t); } }; TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>() { @Override public void onNext(Observable<Integer> t) { t.subscribe(tsw); super.onNext(t); } }; source.window(new Callable<Observable<Object>>() { @Override public Observable<Object> call() { return Observable.never(); } }).subscribe(ts); source.onNext(1); source.onComplete(); ts.assertValueCount(1); tsw.assertValues(1, 2); } @Test public void testWindowViaObservableNoUnsubscribe() { Observable<Integer> source = Observable.range(1, 10); Callable<Observable<String>> boundary = new Callable<Observable<String>>() { @Override public Observable<String> call() { return Observable.empty(); } }; TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); source.window(boundary).subscribe(ts); // 2.0.2 - not anymore // assertTrue("Not cancelled!", ts.isCancelled()); ts.assertComplete(); } @Test public void testBoundaryUnsubscribedOnMainCompletion() { PublishSubject<Integer> source = PublishSubject.create(); final PublishSubject<Integer> boundary = PublishSubject.create(); Callable<Observable<Integer>> boundaryFunc = new Callable<Observable<Integer>>() { @Override public Observable<Integer> call() { return boundary; } }; TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); source.window(boundaryFunc).subscribe(ts); assertTrue(source.hasObservers()); assertTrue(boundary.hasObservers()); source.onComplete(); assertFalse(source.hasObservers()); assertFalse(boundary.hasObservers()); ts.assertComplete(); ts.assertNoErrors(); ts.assertValueCount(1); } @Test public void testMainUnsubscribedOnBoundaryCompletion() { PublishSubject<Integer> source = PublishSubject.create(); final PublishSubject<Integer> boundary = PublishSubject.create(); Callable<Observable<Integer>> boundaryFunc = new Callable<Observable<Integer>>() { @Override public Observable<Integer> call() { return boundary; } }; TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); source.window(boundaryFunc).subscribe(ts); assertTrue(source.hasObservers()); assertTrue(boundary.hasObservers()); boundary.onComplete(); // FIXME source still active because the open window assertTrue(source.hasObservers()); assertFalse(boundary.hasObservers()); ts.assertComplete(); ts.assertNoErrors(); ts.assertValueCount(1); } @Test public void testChildUnsubscribed() { PublishSubject<Integer> source = PublishSubject.create(); final PublishSubject<Integer> boundary = PublishSubject.create(); Callable<Observable<Integer>> boundaryFunc = new Callable<Observable<Integer>>() { @Override public Observable<Integer> call() { return boundary; } }; TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); source.window(boundaryFunc).subscribe(ts); assertTrue(source.hasObservers()); assertTrue(boundary.hasObservers()); ts.dispose(); // FIXME source has subscribers because the open window assertTrue(source.hasObservers()); // FIXME boundary has subscribers because the open window assertTrue(boundary.hasObservers()); ts.assertNotComplete(); ts.assertNoErrors(); ts.assertValueCount(1); } @Test public void newBoundaryCalledAfterWindowClosed() { final AtomicInteger calls = new AtomicInteger(); PublishSubject<Integer> source = PublishSubject.create(); final PublishSubject<Integer> boundary = PublishSubject.create(); Callable<Observable<Integer>> boundaryFunc = new Callable<Observable<Integer>>() { @Override public Observable<Integer> call() { calls.getAndIncrement(); return boundary; } }; TestObserver<Observable<Integer>> ts = new TestObserver<Observable<Integer>>(); source.window(boundaryFunc).subscribe(ts); source.onNext(1); boundary.onNext(1); assertTrue(boundary.hasObservers()); source.onNext(2); boundary.onNext(2); assertTrue(boundary.hasObservers()); source.onNext(3); boundary.onNext(3); assertTrue(boundary.hasObservers()); source.onNext(4); source.onComplete(); ts.assertNoErrors(); ts.assertValueCount(4); ts.assertComplete(); assertFalse(source.hasObservers()); assertFalse(boundary.hasObservers()); } @Test public void boundaryDispose() { TestHelper.checkDisposed(Observable.never().window(Observable.never())); } @Test public void boundaryDispose2() { TestHelper.checkDisposed(Observable.never().window(Functions.justCallable(Observable.never()))); } @Test public void boundaryOnError() { TestObserver<Object> to = Observable.error(new TestException()) .window(Observable.never()) .flatMap(Functions.<Observable<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() { Observable.error(new TestException()) .window(Functions.justCallable(Observable.never())) .test() .assertError(TestException.class); } @Test public void innerBadSource() { TestHelper.checkBadSourceObservable(new Function<Observable<Integer>, Object>() { @Override public Object apply(Observable<Integer> o) throws Exception { return Observable.just(1).window(o).flatMap(new Function<Observable<Integer>, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Observable<Integer> v) throws Exception { return v; } }); } }, false, 1, 1, (Object[])null); TestHelper.checkBadSourceObservable(new Function<Observable<Integer>, Object>() { @Override public Object apply(Observable<Integer> o) throws Exception { return Observable.just(1).window(Functions.justCallable(o)).flatMap(new Function<Observable<Integer>, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Observable<Integer> v) throws Exception { return v; } }); } }, false, 1, 1, (Object[])null); } @Test public void reentrant() { final Subject<Integer> ps = PublishSubject.<Integer>create(); TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { ps.onNext(2); ps.onComplete(); } } }; ps.window(BehaviorSubject.createDefault(1)) .flatMap(new Function<Observable<Integer>, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Observable<Integer> v) throws Exception { return v; } }) .subscribe(to); ps.onNext(1); to .awaitDone(1, TimeUnit.SECONDS) .assertResult(1, 2); } @Test public void reentrantCallable() { final Subject<Integer> ps = PublishSubject.<Integer>create(); TestObserver<Integer> to = new TestObserver<Integer>() { @Override public void onNext(Integer t) { super.onNext(t); if (t == 1) { ps.onNext(2); ps.onComplete(); } } }; ps.window(new Callable<Observable<Integer>>() { boolean once; @Override public Observable<Integer> call() throws Exception { if (!once) { once = true; return BehaviorSubject.createDefault(1); } return Observable.never(); } }) .flatMap(new Function<Observable<Integer>, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Observable<Integer> v) throws Exception { return v; } }) .subscribe(to); ps.onNext(1); to .awaitDone(1, TimeUnit.SECONDS) .assertResult(1, 2); } @Test public void badSource() { TestHelper.checkBadSourceObservable(new Function<Observable<Object>, Object>() { @Override public Object apply(Observable<Object> o) throws Exception { return o.window(Observable.never()).flatMap(new Function<Observable<Object>, ObservableSource<Object>>() { @Override public ObservableSource<Object> apply(Observable<Object> v) throws Exception { return v; } }); } }, false, 1, 1, 1); } @Test public void badSourceCallable() { TestHelper.checkBadSourceObservable(new Function<Observable<Object>, Object>() { @Override public Object apply(Observable<Object> o) throws Exception { return o.window(Functions.justCallable(Observable.never())).flatMap(new Function<Observable<Object>, ObservableSource<Object>>() { @Override public ObservableSource<Object> apply(Observable<Object> v) throws Exception { return v; } }); } }, false, 1, 1, 1); } }