/** * 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.*; import org.junit.Test; import org.mockito.InOrder; import io.reactivex.*; import io.reactivex.Observable; import io.reactivex.Observer; import io.reactivex.disposables.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Function; import io.reactivex.internal.functions.Functions; import io.reactivex.observers.*; import io.reactivex.schedulers.*; import io.reactivex.subjects.*; public class ObservableConcatTest { @Test public void testConcat() { Observer<String> observer = TestHelper.mockObserver(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; final Observable<String> odds = Observable.fromArray(o); final Observable<String> even = Observable.fromArray(e); Observable<String> concat = Observable.concat(odds, even); concat.subscribe(observer); verify(observer, times(7)).onNext(anyString()); } @Test public void testConcatWithList() { Observer<String> observer = TestHelper.mockObserver(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; final Observable<String> odds = Observable.fromArray(o); final Observable<String> even = Observable.fromArray(e); final List<Observable<String>> list = new ArrayList<Observable<String>>(); list.add(odds); list.add(even); Observable<String> concat = Observable.concat(Observable.fromIterable(list)); concat.subscribe(observer); verify(observer, times(7)).onNext(anyString()); } @Test public void testConcatObservableOfObservables() { Observer<String> observer = TestHelper.mockObserver(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; final Observable<String> odds = Observable.fromArray(o); final Observable<String> even = Observable.fromArray(e); Observable<Observable<String>> observableOfObservables = Observable.unsafeCreate(new ObservableSource<Observable<String>>() { @Override public void subscribe(Observer<? super Observable<String>> observer) { observer.onSubscribe(Disposables.empty()); // simulate what would happen in an Observable observer.onNext(odds); observer.onNext(even); observer.onComplete(); } }); Observable<String> concat = Observable.concat(observableOfObservables); concat.subscribe(observer); verify(observer, times(7)).onNext(anyString()); } /** * Simple concat of 2 asynchronous observables ensuring it emits in correct order. */ @Test public void testSimpleAsyncConcat() { Observer<String> observer = TestHelper.mockObserver(); TestObservable<String> o1 = new TestObservable<String>("one", "two", "three"); TestObservable<String> o2 = new TestObservable<String>("four", "five", "six"); Observable.concat(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2)).subscribe(observer); try { // wait for async observables to complete o1.t.join(); o2.t.join(); } catch (Throwable e) { throw new RuntimeException("failed waiting on threads"); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(1)).onNext("four"); inOrder.verify(observer, times(1)).onNext("five"); inOrder.verify(observer, times(1)).onNext("six"); } @Test public void testNestedAsyncConcatLoop() throws Throwable { for (int i = 0; i < 500; i++) { if (i % 10 == 0) { System.out.println("testNestedAsyncConcat >> " + i); } testNestedAsyncConcat(); } } /** * Test an async Observable that emits more async Observables. * @throws InterruptedException if the test is interrupted */ @Test public void testNestedAsyncConcat() throws InterruptedException { Observer<String> observer = TestHelper.mockObserver(); final TestObservable<String> o1 = new TestObservable<String>("one", "two", "three"); final TestObservable<String> o2 = new TestObservable<String>("four", "five", "six"); final TestObservable<String> o3 = new TestObservable<String>("seven", "eight", "nine"); final CountDownLatch allowThird = new CountDownLatch(1); final AtomicReference<Thread> parent = new AtomicReference<Thread>(); final CountDownLatch parentHasStarted = new CountDownLatch(1); final CountDownLatch parentHasFinished = new CountDownLatch(1); Observable<Observable<String>> observableOfObservables = Observable.unsafeCreate(new ObservableSource<Observable<String>>() { @Override public void subscribe(final Observer<? super Observable<String>> observer) { final Disposable d = Disposables.empty(); observer.onSubscribe(d); parent.set(new Thread(new Runnable() { @Override public void run() { try { // emit first if (!d.isDisposed()) { System.out.println("Emit o1"); observer.onNext(Observable.unsafeCreate(o1)); } // emit second if (!d.isDisposed()) { System.out.println("Emit o2"); observer.onNext(Observable.unsafeCreate(o2)); } // wait until sometime later and emit third try { allowThird.await(); } catch (InterruptedException e) { observer.onError(e); } if (!d.isDisposed()) { System.out.println("Emit o3"); observer.onNext(Observable.unsafeCreate(o3)); } } catch (Throwable e) { observer.onError(e); } finally { System.out.println("Done parent Observable"); observer.onComplete(); parentHasFinished.countDown(); } } })); parent.get().start(); parentHasStarted.countDown(); } }); Observable.concat(observableOfObservables).subscribe(observer); // wait for parent to start parentHasStarted.await(); try { // wait for first 2 async observables to complete System.out.println("Thread1 is starting ... waiting for it to complete ..."); o1.waitForThreadDone(); System.out.println("Thread2 is starting ... waiting for it to complete ..."); o2.waitForThreadDone(); } catch (Throwable e) { throw new RuntimeException("failed waiting on threads", e); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(1)).onNext("four"); inOrder.verify(observer, times(1)).onNext("five"); inOrder.verify(observer, times(1)).onNext("six"); // we shouldn't have the following 3 yet inOrder.verify(observer, never()).onNext("seven"); inOrder.verify(observer, never()).onNext("eight"); inOrder.verify(observer, never()).onNext("nine"); // we should not be completed yet verify(observer, never()).onComplete(); verify(observer, never()).onError(any(Throwable.class)); // now allow the third allowThird.countDown(); try { // wait for 3rd to complete o3.waitForThreadDone(); } catch (Throwable e) { throw new RuntimeException("failed waiting on threads", e); } try { // wait for the parent to complete if (!parentHasFinished.await(5, TimeUnit.SECONDS)) { fail("Parent didn't finish within the time limit"); } } catch (Throwable e) { throw new RuntimeException("failed waiting on threads", e); } inOrder.verify(observer, times(1)).onNext("seven"); inOrder.verify(observer, times(1)).onNext("eight"); inOrder.verify(observer, times(1)).onNext("nine"); verify(observer, never()).onError(any(Throwable.class)); inOrder.verify(observer, times(1)).onComplete(); } @Test public void testBlockedObservableOfObservables() { Observer<String> observer = TestHelper.mockObserver(); final String[] o = { "1", "3", "5", "7" }; final String[] e = { "2", "4", "6" }; final Observable<String> odds = Observable.fromArray(o); final Observable<String> even = Observable.fromArray(e); final CountDownLatch callOnce = new CountDownLatch(1); final CountDownLatch okToContinue = new CountDownLatch(1); @SuppressWarnings("unchecked") TestObservable<Observable<String>> observableOfObservables = new TestObservable<Observable<String>>(callOnce, okToContinue, odds, even); Observable<String> concatF = Observable.concat(Observable.unsafeCreate(observableOfObservables)); concatF.subscribe(observer); try { //Block main thread to allow observables to serve up o1. callOnce.await(); } catch (Throwable ex) { ex.printStackTrace(); fail(ex.getMessage()); } // The concated Observable should have served up all of the odds. verify(observer, times(1)).onNext("1"); verify(observer, times(1)).onNext("3"); verify(observer, times(1)).onNext("5"); verify(observer, times(1)).onNext("7"); try { // unblock observables so it can serve up o2 and complete okToContinue.countDown(); observableOfObservables.t.join(); } catch (Throwable ex) { ex.printStackTrace(); fail(ex.getMessage()); } // The concatenated Observable should now have served up all the evens. verify(observer, times(1)).onNext("2"); verify(observer, times(1)).onNext("4"); verify(observer, times(1)).onNext("6"); } @Test public void testConcatConcurrentWithInfinity() { final TestObservable<String> w1 = new TestObservable<String>("one", "two", "three"); //This Observable will send "hello" MAX_VALUE time. final TestObservable<String> w2 = new TestObservable<String>("hello", Integer.MAX_VALUE); Observer<String> observer = TestHelper.mockObserver(); @SuppressWarnings("unchecked") TestObservable<Observable<String>> observableOfObservables = new TestObservable<Observable<String>>(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2)); Observable<String> concatF = Observable.concat(Observable.unsafeCreate(observableOfObservables)); concatF.take(50).subscribe(observer); //Wait for the thread to start up. try { w1.waitForThreadDone(); w2.waitForThreadDone(); } catch (InterruptedException e) { e.printStackTrace(); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(47)).onNext("hello"); verify(observer, times(1)).onComplete(); verify(observer, never()).onError(any(Throwable.class)); } @Test public void testConcatNonBlockingObservables() { final CountDownLatch okToContinueW1 = new CountDownLatch(1); final CountDownLatch okToContinueW2 = new CountDownLatch(1); final TestObservable<String> w1 = new TestObservable<String>(null, okToContinueW1, "one", "two", "three"); final TestObservable<String> w2 = new TestObservable<String>(null, okToContinueW2, "four", "five", "six"); Observer<String> observer = TestHelper.mockObserver(); Observable<Observable<String>> observableOfObservables = Observable.unsafeCreate(new ObservableSource<Observable<String>>() { @Override public void subscribe(Observer<? super Observable<String>> observer) { observer.onSubscribe(Disposables.empty()); // simulate what would happen in an Observable observer.onNext(Observable.unsafeCreate(w1)); observer.onNext(Observable.unsafeCreate(w2)); observer.onComplete(); } }); Observable<String> concat = Observable.concat(observableOfObservables); concat.subscribe(observer); verify(observer, times(0)).onComplete(); try { // release both threads okToContinueW1.countDown(); okToContinueW2.countDown(); // wait for both to finish w1.t.join(); w2.t.join(); } catch (InterruptedException e) { e.printStackTrace(); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(1)).onNext("four"); inOrder.verify(observer, times(1)).onNext("five"); inOrder.verify(observer, times(1)).onNext("six"); verify(observer, times(1)).onComplete(); } /** * Test unsubscribing the concatenated Observable in a single thread. */ @Test public void testConcatUnsubscribe() { final CountDownLatch callOnce = new CountDownLatch(1); final CountDownLatch okToContinue = new CountDownLatch(1); final TestObservable<String> w1 = new TestObservable<String>("one", "two", "three"); final TestObservable<String> w2 = new TestObservable<String>(callOnce, okToContinue, "four", "five", "six"); Observer<String> observer = TestHelper.mockObserver(); TestObserver<String> ts = new TestObserver<String>(observer); final Observable<String> concat = Observable.concat(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2)); try { // Subscribe concat.subscribe(ts); //Block main thread to allow Observable "w1" to complete and Observable "w2" to call onNext once. callOnce.await(); // Unsubcribe ts.dispose(); //Unblock the Observable to continue. okToContinue.countDown(); w1.t.join(); w2.t.join(); } catch (Throwable e) { e.printStackTrace(); fail(e.getMessage()); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(1)).onNext("four"); inOrder.verify(observer, never()).onNext("five"); inOrder.verify(observer, never()).onNext("six"); inOrder.verify(observer, never()).onComplete(); } /** * All observables will be running in different threads so subscribe() is unblocked. CountDownLatch is only used in order to call unsubscribe() in a predictable manner. */ @Test public void testConcatUnsubscribeConcurrent() { final CountDownLatch callOnce = new CountDownLatch(1); final CountDownLatch okToContinue = new CountDownLatch(1); final TestObservable<String> w1 = new TestObservable<String>("one", "two", "three"); final TestObservable<String> w2 = new TestObservable<String>(callOnce, okToContinue, "four", "five", "six"); Observer<String> observer = TestHelper.mockObserver(); TestObserver<String> ts = new TestObserver<String>(observer); @SuppressWarnings("unchecked") TestObservable<Observable<String>> observableOfObservables = new TestObservable<Observable<String>>(Observable.unsafeCreate(w1), Observable.unsafeCreate(w2)); Observable<String> concatF = Observable.concat(Observable.unsafeCreate(observableOfObservables)); concatF.subscribe(ts); try { //Block main thread to allow Observable "w1" to complete and Observable "w2" to call onNext exactly once. callOnce.await(); //"four" from w2 has been processed by onNext() ts.dispose(); //"five" and "six" will NOT be processed by onNext() //Unblock the Observable to continue. okToContinue.countDown(); w1.t.join(); w2.t.join(); } catch (Throwable e) { e.printStackTrace(); fail(e.getMessage()); } InOrder inOrder = inOrder(observer); inOrder.verify(observer, times(1)).onNext("one"); inOrder.verify(observer, times(1)).onNext("two"); inOrder.verify(observer, times(1)).onNext("three"); inOrder.verify(observer, times(1)).onNext("four"); inOrder.verify(observer, never()).onNext("five"); inOrder.verify(observer, never()).onNext("six"); verify(observer, never()).onComplete(); verify(observer, never()).onError(any(Throwable.class)); } static class TestObservable<T> implements ObservableSource<T> { private final Disposable s = new Disposable() { @Override public void dispose() { subscribed = false; } @Override public boolean isDisposed() { return subscribed; } }; private final List<T> values; private Thread t; private int count; private volatile boolean subscribed = true; private final CountDownLatch once; private final CountDownLatch okToContinue; private final CountDownLatch threadHasStarted = new CountDownLatch(1); private final T seed; private final int size; TestObservable(T... values) { this(null, null, values); } TestObservable(CountDownLatch once, CountDownLatch okToContinue, T... values) { this.values = Arrays.asList(values); this.size = this.values.size(); this.once = once; this.okToContinue = okToContinue; this.seed = null; } TestObservable(T seed, int size) { values = null; once = null; okToContinue = null; this.seed = seed; this.size = size; } @Override public void subscribe(final Observer<? super T> observer) { observer.onSubscribe(s); t = new Thread(new Runnable() { @Override public void run() { try { while (count < size && subscribed) { if (null != values) { observer.onNext(values.get(count)); } else { observer.onNext(seed); } count++; //Unblock the main thread to call unsubscribe. if (null != once) { once.countDown(); } //Block until the main thread has called unsubscribe. if (null != okToContinue) { okToContinue.await(5, TimeUnit.SECONDS); } } if (subscribed) { observer.onComplete(); } } catch (InterruptedException e) { e.printStackTrace(); fail(e.getMessage()); } } }); t.start(); threadHasStarted.countDown(); } void waitForThreadDone() throws InterruptedException { threadHasStarted.await(); t.join(); } } @Test public void testMultipleObservers() { Observer<Object> o1 = TestHelper.mockObserver(); Observer<Object> o2 = TestHelper.mockObserver(); TestScheduler s = new TestScheduler(); Observable<Long> timer = Observable.interval(500, TimeUnit.MILLISECONDS, s).take(2); Observable<Long> o = Observable.concat(timer, timer); o.subscribe(o1); o.subscribe(o2); InOrder inOrder1 = inOrder(o1); InOrder inOrder2 = inOrder(o2); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); inOrder1.verify(o1, times(1)).onNext(0L); inOrder2.verify(o2, times(1)).onNext(0L); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); inOrder1.verify(o1, times(1)).onNext(1L); inOrder2.verify(o2, times(1)).onNext(1L); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); inOrder1.verify(o1, times(1)).onNext(0L); inOrder2.verify(o2, times(1)).onNext(0L); s.advanceTimeBy(500, TimeUnit.MILLISECONDS); inOrder1.verify(o1, times(1)).onNext(1L); inOrder2.verify(o2, times(1)).onNext(1L); inOrder1.verify(o1, times(1)).onComplete(); inOrder2.verify(o2, times(1)).onComplete(); verify(o1, never()).onError(any(Throwable.class)); verify(o2, never()).onError(any(Throwable.class)); } @Test public void concatVeryLongObservableOfObservables() { final int n = 10000; Observable<Observable<Integer>> source = Observable.range(0, n).map(new Function<Integer, Observable<Integer>>() { @Override public Observable<Integer> apply(Integer v) { return Observable.just(v); } }); Single<List<Integer>> result = Observable.concat(source).toList(); SingleObserver<List<Integer>> o = TestHelper.mockSingleObserver(); InOrder inOrder = inOrder(o); result.subscribe(o); List<Integer> list = new ArrayList<Integer>(n); for (int i = 0; i < n; i++) { list.add(i); } inOrder.verify(o).onSuccess(list); verify(o, never()).onError(any(Throwable.class)); } @Test public void concatVeryLongObservableOfObservablesTakeHalf() { final int n = 10000; Observable<Observable<Integer>> source = Observable.range(0, n).map(new Function<Integer, Observable<Integer>>() { @Override public Observable<Integer> apply(Integer v) { return Observable.just(v); } }); Single<List<Integer>> result = Observable.concat(source).take(n / 2).toList(); SingleObserver<List<Integer>> o = TestHelper.mockSingleObserver(); InOrder inOrder = inOrder(o); result.subscribe(o); List<Integer> list = new ArrayList<Integer>(n); for (int i = 0; i < n / 2; i++) { list.add(i); } inOrder.verify(o).onSuccess(list); verify(o, never()).onError(any(Throwable.class)); } @Test public void testConcatOuterBackpressure() { assertEquals(1, (int) Observable.<Integer> empty() .concatWith(Observable.just(1)) .take(1) .blockingSingle()); } // https://github.com/ReactiveX/RxJava/issues/1818 @Test public void testConcatWithNonCompliantSourceDoubleOnComplete() { Observable<String> o = Observable.unsafeCreate(new ObservableSource<String>() { @Override public void subscribe(Observer<? super String> s) { s.onSubscribe(Disposables.empty()); s.onNext("hello"); s.onComplete(); s.onComplete(); } }); TestObserver<String> ts = new TestObserver<String>(); Observable.concat(o, o).subscribe(ts); ts.awaitTerminalEvent(500, TimeUnit.MILLISECONDS); ts.assertTerminated(); ts.assertNoErrors(); ts.assertValues("hello", "hello"); } @Test(timeout = 30000) public void testIssue2890NoStackoverflow() throws InterruptedException { final ExecutorService executor = Executors.newFixedThreadPool(2); final Scheduler sch = Schedulers.from(executor); Function<Integer, Observable<Integer>> func = new Function<Integer, Observable<Integer>>() { @Override public Observable<Integer> apply(Integer t) { Observable<Integer> o = Observable.just(t) .subscribeOn(sch) ; Subject<Integer> subject = UnicastSubject.create(); o.subscribe(subject); return subject; } }; int n = 5000; final AtomicInteger counter = new AtomicInteger(); Observable.range(1, n) .concatMap(func).subscribe(new DefaultObserver<Integer>() { @Override public void onNext(Integer t) { // Consume after sleep for 1 ms try { Thread.sleep(1); } catch (InterruptedException e) { // ignored } if (counter.getAndIncrement() % 100 == 0) { System.out.println("testIssue2890NoStackoverflow -> " + counter.get()); }; } @Override public void onComplete() { executor.shutdown(); } @Override public void onError(Throwable e) { executor.shutdown(); } }); executor.awaitTermination(20000, TimeUnit.MILLISECONDS); assertEquals(n, counter.get()); } @Test//(timeout = 100000) public void concatMapRangeAsyncLoopIssue2876() { final long durationSeconds = 2; final long startTime = System.currentTimeMillis(); for (int i = 0;; i++) { //only run this for a max of ten seconds if (System.currentTimeMillis() - startTime > TimeUnit.SECONDS.toMillis(durationSeconds)) { return; } if (i % 1000 == 0) { System.out.println("concatMapRangeAsyncLoop > " + i); } TestObserver<Integer> ts = new TestObserver<Integer>(); Observable.range(0, 1000) .concatMap(new Function<Integer, Observable<Integer>>() { @Override public Observable<Integer> apply(Integer t) { return Observable.fromIterable(Arrays.asList(t)); } }) .observeOn(Schedulers.computation()).subscribe(ts); ts.awaitTerminalEvent(2500, TimeUnit.MILLISECONDS); ts.assertTerminated(); ts.assertNoErrors(); assertEquals(1000, ts.valueCount()); assertEquals((Integer)999, ts.values().get(999)); } } @Test public void concat3() { Observable.concat(Observable.just(1), Observable.just(2), Observable.just(3)) .test() .assertResult(1, 2, 3); } @Test public void concat4() { Observable.concat(Observable.just(1), Observable.just(2), Observable.just(3), Observable.just(4)) .test() .assertResult(1, 2, 3, 4); } @SuppressWarnings("unchecked") @Test public void concatArrayDelayError() { Observable.concatArrayDelayError(Observable.just(1), Observable.just(2), Observable.just(3), Observable.just(4)) .test() .assertResult(1, 2, 3, 4); } @SuppressWarnings("unchecked") @Test public void concatArrayDelayErrorWithError() { Observable.concatArrayDelayError(Observable.just(1), Observable.just(2), Observable.just(3).concatWith(Observable.<Integer>error(new TestException())), Observable.just(4)) .test() .assertFailure(TestException.class, 1, 2, 3, 4); } @SuppressWarnings("unchecked") @Test public void concatIterableDelayError() { Observable.concatDelayError( Arrays.asList(Observable.just(1), Observable.just(2), Observable.just(3), Observable.just(4))) .test() .assertResult(1, 2, 3, 4); } @SuppressWarnings("unchecked") @Test public void concatIterableDelayErrorWithError() { Observable.concatDelayError( Arrays.asList(Observable.just(1), Observable.just(2), Observable.just(3).concatWith(Observable.<Integer>error(new TestException())), Observable.just(4))) .test() .assertFailure(TestException.class, 1, 2, 3, 4); } @Test public void concatObservableDelayError() { Observable.concatDelayError( Observable.just(Observable.just(1), Observable.just(2), Observable.just(3), Observable.just(4))) .test() .assertResult(1, 2, 3, 4); } @Test public void concatObservableDelayErrorWithError() { Observable.concatDelayError( Observable.just(Observable.just(1), Observable.just(2), Observable.just(3).concatWith(Observable.<Integer>error(new TestException())), Observable.just(4))) .test() .assertFailure(TestException.class, 1, 2, 3, 4); } @Test public void concatObservableDelayErrorBoundary() { Observable.concatDelayError( Observable.just(Observable.just(1), Observable.just(2), Observable.just(3).concatWith(Observable.<Integer>error(new TestException())), Observable.just(4)), 2, false) .test() .assertFailure(TestException.class, 1, 2, 3); } @Test public void concatObservableDelayErrorTillEnd() { Observable.concatDelayError( Observable.just(Observable.just(1), Observable.just(2), Observable.just(3).concatWith(Observable.<Integer>error(new TestException())), Observable.just(4)), 2, true) .test() .assertFailure(TestException.class, 1, 2, 3, 4); } @Test public void concatMapDelayError() { Observable.just(Observable.just(1), Observable.just(2)) .concatMapDelayError(Functions.<Observable<Integer>>identity()) .test() .assertResult(1, 2); } @Test public void concatMapDelayErrorWithError() { Observable.just(Observable.just(1).concatWith(Observable.<Integer>error(new TestException())), Observable.just(2)) .concatMapDelayError(Functions.<Observable<Integer>>identity()) .test() .assertFailure(TestException.class, 1, 2); } @Test public void concatMapIterableBufferSize() { Observable.just(1, 2).concatMapIterable(new Function<Integer, Iterable<Integer>>() { @Override public Iterable<Integer> apply(Integer v) throws Exception { return Arrays.asList(1, 2, 3, 4, 5); } }, 1) .test() .assertResult(1, 2, 3, 4, 5, 1, 2, 3, 4, 5); } @SuppressWarnings("unchecked") @Test public void emptyArray() { assertSame(Observable.empty(), Observable.concatArrayDelayError()); } @SuppressWarnings("unchecked") @Test public void singleElementArray() { assertSame(Observable.never(), Observable.concatArrayDelayError(Observable.never())); } @Test public void concatMapDelayErrorEmptySource() { assertSame(Observable.empty(), Observable.<Object>empty() .concatMapDelayError(new Function<Object, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Object v) throws Exception { return Observable.just(1); } }, 16, true)); } @Test public void concatMapDelayErrorJustSource() { Observable.just(0) .concatMapDelayError(new Function<Object, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Object v) throws Exception { return Observable.just(1); } }, 16, true) .test() .assertResult(1); } @SuppressWarnings("unchecked") @Test public void concatArrayEmpty() { assertSame(Observable.empty(), Observable.concatArray()); } @SuppressWarnings("unchecked") @Test public void concatArraySingleElement() { assertSame(Observable.never(), Observable.concatArray(Observable.never())); } @Test public void concatMapErrorEmptySource() { assertSame(Observable.empty(), Observable.<Object>empty() .concatMap(new Function<Object, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Object v) throws Exception { return Observable.just(1); } }, 16)); } @Test public void concatMapJustSource() { Observable.just(0) .concatMap(new Function<Object, ObservableSource<Integer>>() { @Override public ObservableSource<Integer> apply(Object v) throws Exception { return Observable.just(1); } }, 16) .test() .assertResult(1); } @SuppressWarnings("unchecked") @Test public void noSubsequentSubscription() { final int[] calls = { 0 }; Observable<Integer> source = Observable.create(new ObservableOnSubscribe<Integer>() { @Override public void subscribe(ObservableEmitter<Integer> s) throws Exception { calls[0]++; s.onNext(1); s.onComplete(); } }); Observable.concatArray(source, source).firstElement() .test() .assertResult(1); assertEquals(1, calls[0]); } @SuppressWarnings("unchecked") @Test public void noSubsequentSubscriptionDelayError() { final int[] calls = { 0 }; Observable<Integer> source = Observable.create(new ObservableOnSubscribe<Integer>() { @Override public void subscribe(ObservableEmitter<Integer> s) throws Exception { calls[0]++; s.onNext(1); s.onComplete(); } }); Observable.concatArrayDelayError(source, source).firstElement() .test() .assertResult(1); assertEquals(1, calls[0]); } @SuppressWarnings("unchecked") @Test public void noSubsequentSubscriptionIterable() { final int[] calls = { 0 }; Observable<Integer> source = Observable.create(new ObservableOnSubscribe<Integer>() { @Override public void subscribe(ObservableEmitter<Integer> s) throws Exception { calls[0]++; s.onNext(1); s.onComplete(); } }); Observable.concat(Arrays.asList(source, source)).firstElement() .test() .assertResult(1); assertEquals(1, calls[0]); } @SuppressWarnings("unchecked") @Test public void noSubsequentSubscriptionDelayErrorIterable() { final int[] calls = { 0 }; Observable<Integer> source = Observable.create(new ObservableOnSubscribe<Integer>() { @Override public void subscribe(ObservableEmitter<Integer> s) throws Exception { calls[0]++; s.onNext(1); s.onComplete(); } }); Observable.concatDelayError(Arrays.asList(source, source)).firstElement() .test() .assertResult(1); assertEquals(1, calls[0]); } }