/**
* 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.*;
import io.reactivex.*;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.Scheduler.Worker;
import io.reactivex.disposables.*;
import io.reactivex.exceptions.TestException;
import io.reactivex.functions.*;
import io.reactivex.observers.*;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.*;
public class ObservableMergeTest {
Observer<String> stringObserver;
int count;
@Before
public void before() {
stringObserver = TestHelper.mockObserver();
for (Thread t : Thread.getAllStackTraces().keySet()) {
if (t.getName().startsWith("RxNewThread")) {
count++;
}
}
}
@After
public void after() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (Thread t : Thread.getAllStackTraces().keySet()) {
if (t.getName().startsWith("RxNewThread")) {
--count;
}
}
if (count != 0) {
throw new IllegalStateException("NewThread leak!");
}
}
@Test
public void testMergeObservableOfObservables() {
final Observable<String> o1 = Observable.unsafeCreate(new TestSynchronousObservable());
final Observable<String> o2 = Observable.unsafeCreate(new TestSynchronousObservable());
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(o1);
observer.onNext(o2);
observer.onComplete();
}
});
Observable<String> m = Observable.merge(observableOfObservables);
m.subscribe(stringObserver);
verify(stringObserver, never()).onError(any(Throwable.class));
verify(stringObserver, times(1)).onComplete();
verify(stringObserver, times(2)).onNext("hello");
}
@Test
public void testMergeArray() {
final Observable<String> o1 = Observable.unsafeCreate(new TestSynchronousObservable());
final Observable<String> o2 = Observable.unsafeCreate(new TestSynchronousObservable());
Observable<String> m = Observable.merge(o1, o2);
m.subscribe(stringObserver);
verify(stringObserver, never()).onError(any(Throwable.class));
verify(stringObserver, times(2)).onNext("hello");
verify(stringObserver, times(1)).onComplete();
}
@Test
public void testMergeList() {
final Observable<String> o1 = Observable.unsafeCreate(new TestSynchronousObservable());
final Observable<String> o2 = Observable.unsafeCreate(new TestSynchronousObservable());
List<Observable<String>> listOfObservables = new ArrayList<Observable<String>>();
listOfObservables.add(o1);
listOfObservables.add(o2);
Observable<String> m = Observable.merge(listOfObservables);
m.subscribe(stringObserver);
verify(stringObserver, never()).onError(any(Throwable.class));
verify(stringObserver, times(1)).onComplete();
verify(stringObserver, times(2)).onNext("hello");
}
@Test(timeout = 1000)
public void testUnSubscribeObservableOfObservables() throws InterruptedException {
final AtomicBoolean unsubscribed = new AtomicBoolean();
final CountDownLatch latch = new CountDownLatch(1);
Observable<Observable<Long>> source = Observable.unsafeCreate(new ObservableSource<Observable<Long>>() {
@Override
public void subscribe(final Observer<? super Observable<Long>> observer) {
// verbose on purpose so I can track the inside of it
final Disposable s = Disposables.fromRunnable(new Runnable() {
@Override
public void run() {
System.out.println("*** unsubscribed");
unsubscribed.set(true);
}
});
observer.onSubscribe(s);
new Thread(new Runnable() {
@Override
public void run() {
while (!unsubscribed.get()) {
observer.onNext(Observable.just(1L, 2L));
}
System.out.println("Done looping after unsubscribe: " + unsubscribed.get());
observer.onComplete();
// mark that the thread is finished
latch.countDown();
}
}).start();
}
});
final AtomicInteger count = new AtomicInteger();
Observable.merge(source).take(6).blockingForEach(new Consumer<Long>() {
@Override
public void accept(Long v) {
System.out.println("Value: " + v);
int c = count.incrementAndGet();
if (c > 6) {
fail("Should be only 6");
}
}
});
latch.await(1000, TimeUnit.MILLISECONDS);
System.out.println("unsubscribed: " + unsubscribed.get());
assertTrue(unsubscribed.get());
}
@Test
public void testMergeArrayWithThreading() {
final TestASynchronousObservable o1 = new TestASynchronousObservable();
final TestASynchronousObservable o2 = new TestASynchronousObservable();
Observable<String> m = Observable.merge(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2));
TestObserver<String> ts = new TestObserver<String>(stringObserver);
m.subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
verify(stringObserver, never()).onError(any(Throwable.class));
verify(stringObserver, times(2)).onNext("hello");
verify(stringObserver, times(1)).onComplete();
}
@Test
public void testSynchronizationOfMultipleSequencesLoop() throws Throwable {
for (int i = 0; i < 100; i++) {
System.out.println("testSynchronizationOfMultipleSequencesLoop > " + i);
testSynchronizationOfMultipleSequences();
}
}
@Test
public void testSynchronizationOfMultipleSequences() throws Throwable {
final TestASynchronousObservable o1 = new TestASynchronousObservable();
final TestASynchronousObservable o2 = new TestASynchronousObservable();
// use this latch to cause onNext to wait until we're ready to let it go
final CountDownLatch endLatch = new CountDownLatch(1);
final AtomicInteger concurrentCounter = new AtomicInteger();
final AtomicInteger totalCounter = new AtomicInteger();
Observable<String> m = Observable.merge(Observable.unsafeCreate(o1), Observable.unsafeCreate(o2));
m.subscribe(new DefaultObserver<String>() {
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
throw new RuntimeException("failed", e);
}
@Override
public void onNext(String v) {
totalCounter.incrementAndGet();
concurrentCounter.incrementAndGet();
try {
// avoid deadlocking the main thread
if (Thread.currentThread().getName().equals("TestASynchronousObservable")) {
// wait here until we're done asserting
endLatch.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
throw new RuntimeException("failed", e);
} finally {
concurrentCounter.decrementAndGet();
}
}
});
// wait for both observables to send (one should be blocked)
o1.onNextBeingSent.await();
o2.onNextBeingSent.await();
// I can't think of a way to know for sure that both threads have or are trying to send onNext
// since I can't use a CountDownLatch for "after" onNext since I want to catch during it
// but I can't know for sure onNext is invoked
// so I'm unfortunately reverting to using a Thread.sleep to allow the process scheduler time
// to make sure after o1.onNextBeingSent and o2.onNextBeingSent are hit that the following
// onNext is invoked.
int timeout = 20;
while (timeout-- > 0 && concurrentCounter.get() != 1) {
Thread.sleep(100);
}
try { // in try/finally so threads are released via latch countDown even if assertion fails
assertEquals(1, concurrentCounter.get());
} finally {
// release so it can finish
endLatch.countDown();
}
try {
o1.t.join();
o2.t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
assertEquals(2, totalCounter.get());
assertEquals(0, concurrentCounter.get());
}
/**
* Unit test from OperationMergeDelayError backported here to show how these use cases work with normal merge.
*/
@Test
public void testError1() {
// we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior
final Observable<String> o1 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six"
final Observable<String> o2 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three")); // we expect to lose all of these since o1 is done first and fails
Observable<String> m = Observable.merge(o1, o2);
m.subscribe(stringObserver);
verify(stringObserver, times(1)).onError(any(NullPointerException.class));
verify(stringObserver, never()).onComplete();
verify(stringObserver, times(0)).onNext("one");
verify(stringObserver, times(0)).onNext("two");
verify(stringObserver, times(0)).onNext("three");
verify(stringObserver, times(1)).onNext("four");
verify(stringObserver, times(0)).onNext("five");
verify(stringObserver, times(0)).onNext("six");
}
/**
* Unit test from OperationMergeDelayError backported here to show how these use cases work with normal merge.
*/
@Test
public void testError2() {
// we are using synchronous execution to test this exactly rather than non-deterministic concurrent behavior
final Observable<String> o1 = Observable.unsafeCreate(new TestErrorObservable("one", "two", "three"));
final Observable<String> o2 = Observable.unsafeCreate(new TestErrorObservable("four", null, "six")); // we expect to lose "six"
final Observable<String> o3 = Observable.unsafeCreate(new TestErrorObservable("seven", "eight", null));// we expect to lose all of these since o2 is done first and fails
final Observable<String> o4 = Observable.unsafeCreate(new TestErrorObservable("nine"));// we expect to lose all of these since o2 is done first and fails
Observable<String> m = Observable.merge(o1, o2, o3, o4);
m.subscribe(stringObserver);
verify(stringObserver, times(1)).onError(any(NullPointerException.class));
verify(stringObserver, never()).onComplete();
verify(stringObserver, times(1)).onNext("one");
verify(stringObserver, times(1)).onNext("two");
verify(stringObserver, times(1)).onNext("three");
verify(stringObserver, times(1)).onNext("four");
verify(stringObserver, times(0)).onNext("five");
verify(stringObserver, times(0)).onNext("six");
verify(stringObserver, times(0)).onNext("seven");
verify(stringObserver, times(0)).onNext("eight");
verify(stringObserver, times(0)).onNext("nine");
}
@Test
@Ignore("Subscribe should not throw")
public void testThrownErrorHandling() {
TestObserver<String> ts = new TestObserver<String>();
Observable<String> o1 = Observable.unsafeCreate(new ObservableSource<String>() {
@Override
public void subscribe(Observer<? super String> s) {
throw new RuntimeException("fail");
}
});
Observable.merge(o1, o1).subscribe(ts);
ts.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS);
ts.assertTerminated();
System.out.println("Error: " + ts.errors());
}
private static class TestSynchronousObservable implements ObservableSource<String> {
@Override
public void subscribe(Observer<? super String> observer) {
observer.onSubscribe(Disposables.empty());
observer.onNext("hello");
observer.onComplete();
}
}
private static class TestASynchronousObservable implements ObservableSource<String> {
Thread t;
final CountDownLatch onNextBeingSent = new CountDownLatch(1);
@Override
public void subscribe(final Observer<? super String> observer) {
observer.onSubscribe(Disposables.empty());
t = new Thread(new Runnable() {
@Override
public void run() {
onNextBeingSent.countDown();
try {
observer.onNext("hello");
// I can't use a countDownLatch to prove we are actually sending 'onNext'
// since it will block if synchronized and I'll deadlock
observer.onComplete();
} catch (Exception e) {
observer.onError(e);
}
}
}, "TestASynchronousObservable");
t.start();
}
}
private static class TestErrorObservable implements ObservableSource<String> {
String[] valuesToReturn;
TestErrorObservable(String... values) {
valuesToReturn = values;
}
@Override
public void subscribe(Observer<? super String> observer) {
observer.onSubscribe(Disposables.empty());
for (String s : valuesToReturn) {
if (s == null) {
System.out.println("throwing exception");
observer.onError(new NullPointerException());
} else {
observer.onNext(s);
}
}
observer.onComplete();
}
}
@Test
public void testUnsubscribeAsObservablesComplete() {
TestScheduler scheduler1 = new TestScheduler();
AtomicBoolean os1 = new AtomicBoolean(false);
Observable<Long> o1 = createObservableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler1, os1);
TestScheduler scheduler2 = new TestScheduler();
AtomicBoolean os2 = new AtomicBoolean(false);
Observable<Long> o2 = createObservableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler2, os2);
TestObserver<Long> ts = new TestObserver<Long>();
Observable.merge(o1, o2).subscribe(ts);
// we haven't incremented time so nothing should be received yet
ts.assertNoValues();
scheduler1.advanceTimeBy(3, TimeUnit.SECONDS);
scheduler2.advanceTimeBy(2, TimeUnit.SECONDS);
ts.assertValues(0L, 1L, 2L, 0L, 1L);
// not unsubscribed yet
assertFalse(os1.get());
assertFalse(os2.get());
// advance to the end at which point it should complete
scheduler1.advanceTimeBy(3, TimeUnit.SECONDS);
ts.assertValues(0L, 1L, 2L, 0L, 1L, 3L, 4L);
assertTrue(os1.get());
assertFalse(os2.get());
// both should be completed now
scheduler2.advanceTimeBy(3, TimeUnit.SECONDS);
ts.assertValues(0L, 1L, 2L, 0L, 1L, 3L, 4L, 2L, 3L, 4L);
assertTrue(os1.get());
assertTrue(os2.get());
ts.assertTerminated();
}
@Test
public void testEarlyUnsubscribe() {
for (int i = 0; i < 10; i++) {
TestScheduler scheduler1 = new TestScheduler();
AtomicBoolean os1 = new AtomicBoolean(false);
Observable<Long> o1 = createObservableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler1, os1);
TestScheduler scheduler2 = new TestScheduler();
AtomicBoolean os2 = new AtomicBoolean(false);
Observable<Long> o2 = createObservableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(scheduler2, os2);
TestObserver<Long> ts = new TestObserver<Long>();
Observable.merge(o1, o2).subscribe(ts);
// we haven't incremented time so nothing should be received yet
ts.assertNoValues();
scheduler1.advanceTimeBy(3, TimeUnit.SECONDS);
scheduler2.advanceTimeBy(2, TimeUnit.SECONDS);
ts.assertValues(0L, 1L, 2L, 0L, 1L);
// not unsubscribed yet
assertFalse(os1.get());
assertFalse(os2.get());
// early unsubscribe
ts.dispose();
assertTrue(os1.get());
assertTrue(os2.get());
ts.assertValues(0L, 1L, 2L, 0L, 1L);
// FIXME not happening anymore
// ts.assertUnsubscribed();
}
}
private Observable<Long> createObservableOf5IntervalsOf1SecondIncrementsWithSubscriptionHook(final Scheduler scheduler, final AtomicBoolean unsubscribed) {
return Observable.unsafeCreate(new ObservableSource<Long>() {
@Override
public void subscribe(final Observer<? super Long> child) {
Observable.interval(1, TimeUnit.SECONDS, scheduler)
.take(5)
.subscribe(new Observer<Long>() {
@Override
public void onSubscribe(final Disposable s) {
child.onSubscribe(Disposables.fromRunnable(new Runnable() {
@Override
public void run() {
unsubscribed.set(true);
s.dispose();
}
}));
}
@Override
public void onNext(Long t) {
child.onNext(t);
}
@Override
public void onError(Throwable t) {
unsubscribed.set(true);
child.onError(t);
}
@Override
public void onComplete() {
unsubscribed.set(true);
child.onComplete();
}
});
}
});
}
@Test//(timeout = 10000)
public void testConcurrency() {
Observable<Integer> o = Observable.range(1, 10000).subscribeOn(Schedulers.newThread());
for (int i = 0; i < 10; i++) {
Observable<Integer> merge = Observable.merge(o, o, o);
TestObserver<Integer> ts = new TestObserver<Integer>();
merge.subscribe(ts);
ts.awaitTerminalEvent(3, TimeUnit.SECONDS);
ts.assertTerminated();
ts.assertNoErrors();
ts.assertComplete();
List<Integer> onNextEvents = ts.values();
assertEquals(30000, onNextEvents.size());
// System.out.println("onNext: " + onNextEvents.size() + " onComplete: " + ts.getOnCompletedEvents().size());
}
}
@Test
public void testConcurrencyWithSleeping() {
Observable<Integer> o = Observable.unsafeCreate(new ObservableSource<Integer>() {
@Override
public void subscribe(final Observer<? super Integer> s) {
Worker inner = Schedulers.newThread().createWorker();
final CompositeDisposable as = new CompositeDisposable();
as.add(Disposables.empty());
as.add(inner);
s.onSubscribe(as);
inner.schedule(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
s.onNext(1);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
s.onError(e);
}
as.dispose();
s.onComplete();
}
});
}
});
for (int i = 0; i < 10; i++) {
Observable<Integer> merge = Observable.merge(o, o, o);
TestObserver<Integer> ts = new TestObserver<Integer>();
merge.subscribe(ts);
ts.awaitTerminalEvent();
ts.assertComplete();
List<Integer> onNextEvents = ts.values();
assertEquals(300, onNextEvents.size());
// System.out.println("onNext: " + onNextEvents.size() + " onComplete: " + ts.getOnCompletedEvents().size());
}
}
@Test
public void testConcurrencyWithBrokenOnCompleteContract() {
Observable<Integer> o = Observable.unsafeCreate(new ObservableSource<Integer>() {
@Override
public void subscribe(final Observer<? super Integer> s) {
Worker inner = Schedulers.newThread().createWorker();
final CompositeDisposable as = new CompositeDisposable();
as.add(Disposables.empty());
as.add(inner);
s.onSubscribe(as);
inner.schedule(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 10000; i++) {
s.onNext(i);
}
} catch (Exception e) {
s.onError(e);
}
as.dispose();
s.onComplete();
s.onComplete();
s.onComplete();
}
});
}
});
for (int i = 0; i < 10; i++) {
Observable<Integer> merge = Observable.merge(o, o, o);
TestObserver<Integer> ts = new TestObserver<Integer>();
merge.subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
ts.assertComplete();
List<Integer> onNextEvents = ts.values();
assertEquals(30000, onNextEvents.size());
// System.out.println("onNext: " + onNextEvents.size() + " onComplete: " + ts.getOnCompletedEvents().size());
}
}
@Test
public void testBackpressureUpstream() throws InterruptedException {
final AtomicInteger generated1 = new AtomicInteger();
Observable<Integer> o1 = createInfiniteObservable(generated1).subscribeOn(Schedulers.computation());
final AtomicInteger generated2 = new AtomicInteger();
Observable<Integer> o2 = createInfiniteObservable(generated2).subscribeOn(Schedulers.computation());
TestObserver<Integer> testObserver = new TestObserver<Integer>() {
@Override
public void onNext(Integer t) {
System.err.println("TestObserver received => " + t + " on thread " + Thread.currentThread());
super.onNext(t);
}
};
Observable.merge(o1.take(Flowable.bufferSize() * 2), o2.take(Flowable.bufferSize() * 2)).subscribe(testObserver);
testObserver.awaitTerminalEvent();
if (testObserver.errors().size() > 0) {
testObserver.errors().get(0).printStackTrace();
}
testObserver.assertNoErrors();
System.err.println(testObserver.values());
assertEquals(Flowable.bufferSize() * 4, testObserver.values().size());
// it should be between the take num and requested batch size across the async boundary
System.out.println("Generated 1: " + generated1.get());
System.out.println("Generated 2: " + generated2.get());
assertTrue(generated1.get() >= Flowable.bufferSize() * 2
&& generated1.get() <= Flowable.bufferSize() * 4);
}
@Test
public void testBackpressureUpstream2InLoop() throws InterruptedException {
for (int i = 0; i < 1000; i++) {
System.err.flush();
System.out.println("---");
System.out.flush();
testBackpressureUpstream2();
}
}
@Test
public void testBackpressureUpstream2() throws InterruptedException {
final AtomicInteger generated1 = new AtomicInteger();
Observable<Integer> o1 = createInfiniteObservable(generated1).subscribeOn(Schedulers.computation());
TestObserver<Integer> testObserver = new TestObserver<Integer>() {
@Override
public void onNext(Integer t) {
super.onNext(t);
}
};
Observable.merge(o1.take(Flowable.bufferSize() * 2), Observable.just(-99)).subscribe(testObserver);
testObserver.awaitTerminalEvent();
List<Integer> onNextEvents = testObserver.values();
System.out.println("Generated 1: " + generated1.get() + " / received: " + onNextEvents.size());
System.out.println(onNextEvents);
if (testObserver.errors().size() > 0) {
testObserver.errors().get(0).printStackTrace();
}
testObserver.assertNoErrors();
assertEquals(Flowable.bufferSize() * 2 + 1, onNextEvents.size());
// it should be between the take num and requested batch size across the async boundary
assertTrue(generated1.get() >= Flowable.bufferSize() * 2 && generated1.get() <= Flowable.bufferSize() * 3);
}
/**
* This is the same as the upstreams ones, but now adds the downstream as well by using observeOn.
*
* This requires merge to also obey the Product.request values coming from it's child Observer.
* @throws InterruptedException if the test is interrupted
*/
@Test(timeout = 10000)
public void testBackpressureDownstreamWithConcurrentStreams() throws InterruptedException {
final AtomicInteger generated1 = new AtomicInteger();
Observable<Integer> o1 = createInfiniteObservable(generated1).subscribeOn(Schedulers.computation());
final AtomicInteger generated2 = new AtomicInteger();
Observable<Integer> o2 = createInfiniteObservable(generated2).subscribeOn(Schedulers.computation());
TestObserver<Integer> to = new TestObserver<Integer>() {
@Override
public void onNext(Integer t) {
if (t < 100) {
try {
// force a slow consumer
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// System.err.println("TestObserver received => " + t + " on thread " + Thread.currentThread());
super.onNext(t);
}
};
Observable.merge(o1.take(Flowable.bufferSize() * 2), o2.take(Flowable.bufferSize() * 2)).observeOn(Schedulers.computation()).subscribe(to);
to.awaitTerminalEvent();
if (to.errors().size() > 0) {
to.errors().get(0).printStackTrace();
}
to.assertNoErrors();
System.err.println(to.values());
assertEquals(Flowable.bufferSize() * 4, to.values().size());
// it should be between the take num and requested batch size across the async boundary
System.out.println("Generated 1: " + generated1.get());
System.out.println("Generated 2: " + generated2.get());
assertTrue(generated1.get() >= Flowable.bufferSize() * 2 && generated1.get() <= Flowable.bufferSize() * 4);
}
/**
* Currently there is no solution to this ... we can't exert backpressure on the outer Observable if we
* can't know if the ones we've received so far are going to emit or not, otherwise we could starve the system.
*
* For example, 10,000 Observables are being merged (bad use case to begin with, but ...) and it's only one of them
* that will ever emit. If backpressure only allowed the first 1,000 to be sent, we would hang and never receive an event.
*
* Thus, we must allow all Observables to be sent. The ScalarSynchronousObservable use case is an exception to this since
* we can grab the value synchronously.
*
* @throws InterruptedException if the await is interrupted
*/
@Test(timeout = 5000)
public void testBackpressureBothUpstreamAndDownstreamWithRegularObservables() throws InterruptedException {
final AtomicInteger generated1 = new AtomicInteger();
Observable<Observable<Integer>> o1 = createInfiniteObservable(generated1).map(new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
return Observable.just(1, 2, 3);
}
});
TestObserver<Integer> to = new TestObserver<Integer>() {
int i;
@Override
public void onNext(Integer t) {
if (i++ < 400) {
try {
// force a slow consumer
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// System.err.println("TestObserver received => " + t + " on thread " + Thread.currentThread());
super.onNext(t);
}
};
Observable.merge(o1).observeOn(Schedulers.computation()).take(Flowable.bufferSize() * 2).subscribe(to);
to.awaitTerminalEvent();
if (to.errors().size() > 0) {
to.errors().get(0).printStackTrace();
}
to.assertNoErrors();
System.out.println("Generated 1: " + generated1.get());
System.err.println(to.values());
System.out.println("done1 testBackpressureBothUpstreamAndDownstreamWithRegularObservables ");
assertEquals(Flowable.bufferSize() * 2, to.values().size());
System.out.println("done2 testBackpressureBothUpstreamAndDownstreamWithRegularObservables ");
// we can't restrict this ... see comment above
// assertTrue(generated1.get() >= Observable.bufferSize() && generated1.get() <= Observable.bufferSize() * 4);
}
@Test
@Ignore("Null values not permitted")
public void mergeWithNullValues() {
System.out.println("mergeWithNullValues");
TestObserver<String> ts = new TestObserver<String>();
Observable.merge(Observable.just(null, "one"), Observable.just("two", null)).subscribe(ts);
ts.assertTerminated();
ts.assertNoErrors();
ts.assertValues(null, "one", "two", null);
}
@Test
@Ignore("Null values are no longer permitted")
public void mergeWithTerminalEventAfterUnsubscribe() {
System.out.println("mergeWithTerminalEventAfterUnsubscribe");
TestObserver<String> ts = new TestObserver<String>();
Observable<String> bad = Observable.unsafeCreate(new ObservableSource<String>() {
@Override
public void subscribe(Observer<? super String> s) {
s.onNext("two");
// FIXME can't cancel downstream
// s.unsubscribe();
// s.onComplete();
}
});
Observable.merge(Observable.just(null, "one"), bad).subscribe(ts);
ts.assertNoErrors();
ts.assertValues(null, "one", "two");
}
@Test
@Ignore("Null values are not permitted")
public void mergingNullObservable() {
TestObserver<String> ts = new TestObserver<String>();
Observable.merge(Observable.just("one"), null).subscribe(ts);
ts.assertNoErrors();
ts.assertValue("one");
}
@Test
public void merge1AsyncStreamOf1() {
TestObserver<Integer> ts = new TestObserver<Integer>();
mergeNAsyncStreamsOfN(1, 1).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(1, ts.values().size());
}
@Test
public void merge1AsyncStreamOf1000() {
TestObserver<Integer> ts = new TestObserver<Integer>();
mergeNAsyncStreamsOfN(1, 1000).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(1000, ts.values().size());
}
@Test
public void merge10AsyncStreamOf1000() {
TestObserver<Integer> ts = new TestObserver<Integer>();
mergeNAsyncStreamsOfN(10, 1000).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(10000, ts.values().size());
}
@Test
public void merge1000AsyncStreamOf1000() {
TestObserver<Integer> ts = new TestObserver<Integer>();
mergeNAsyncStreamsOfN(1000, 1000).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(1000000, ts.values().size());
}
@Test
public void merge2000AsyncStreamOf100() {
TestObserver<Integer> ts = new TestObserver<Integer>();
mergeNAsyncStreamsOfN(2000, 100).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(200000, ts.values().size());
}
@Test
public void merge100AsyncStreamOf1() {
TestObserver<Integer> ts = new TestObserver<Integer>();
mergeNAsyncStreamsOfN(100, 1).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(100, ts.values().size());
}
private Observable<Integer> mergeNAsyncStreamsOfN(final int outerSize, final int innerSize) {
Observable<Observable<Integer>> os = Observable.range(1, outerSize)
.map(new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer i) {
return Observable.range(1, innerSize).subscribeOn(Schedulers.computation());
}
});
return Observable.merge(os);
}
@Test
public void merge1SyncStreamOf1() {
TestObserver<Integer> ts = new TestObserver<Integer>();
mergeNSyncStreamsOfN(1, 1).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(1, ts.values().size());
}
@Test
public void merge1SyncStreamOf1000000() {
TestObserver<Integer> ts = new TestObserver<Integer>();
mergeNSyncStreamsOfN(1, 1000000).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(1000000, ts.values().size());
}
@Test
public void merge1000SyncStreamOf1000() {
TestObserver<Integer> ts = new TestObserver<Integer>();
mergeNSyncStreamsOfN(1000, 1000).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(1000000, ts.values().size());
}
@Test
public void merge10000SyncStreamOf10() {
TestObserver<Integer> ts = new TestObserver<Integer>();
mergeNSyncStreamsOfN(10000, 10).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(100000, ts.values().size());
}
@Test
public void merge1000000SyncStreamOf1() {
TestObserver<Integer> ts = new TestObserver<Integer>();
mergeNSyncStreamsOfN(1000000, 1).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(1000000, ts.values().size());
}
private Observable<Integer> mergeNSyncStreamsOfN(final int outerSize, final int innerSize) {
Observable<Observable<Integer>> os = Observable.range(1, outerSize)
.map(new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer i) {
return Observable.range(1, innerSize);
}
});
return Observable.merge(os);
}
private Observable<Integer> createInfiniteObservable(final AtomicInteger generated) {
Observable<Integer> o = Observable.fromIterable(new Iterable<Integer>() {
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
@Override
public void remove() {
}
@Override
public Integer next() {
return generated.getAndIncrement();
}
@Override
public boolean hasNext() {
return true;
}
};
}
});
return o;
}
@Test
public void mergeManyAsyncSingle() {
TestObserver<Integer> ts = new TestObserver<Integer>();
Observable<Observable<Integer>> os = Observable.range(1, 10000)
.map(new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(final Integer i) {
return Observable.unsafeCreate(new ObservableSource<Integer>() {
@Override
public void subscribe(Observer<? super Integer> s) {
s.onSubscribe(Disposables.empty());
if (i < 500) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
s.onNext(i);
s.onComplete();
}
}).subscribeOn(Schedulers.computation()).cache();
}
});
Observable.merge(os).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(10000, ts.values().size());
}
Function<Integer, Observable<Integer>> toScalar = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer v) {
return Observable.just(v);
}
};
Function<Integer, Observable<Integer>> toHiddenScalar = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t) {
return Observable.just(t).hide();
}
};
;
void runMerge(Function<Integer, Observable<Integer>> func, TestObserver<Integer> ts) {
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 1000; i++) {
list.add(i);
}
Observable<Integer> source = Observable.fromIterable(list);
source.flatMap(func).subscribe(ts);
if (ts.values().size() != 1000) {
System.out.println(ts.values());
}
ts.assertTerminated();
ts.assertNoErrors();
ts.assertValueSequence(list);
}
@Test
public void testFastMergeFullScalar() {
runMerge(toScalar, new TestObserver<Integer>());
}
@Test
public void testFastMergeHiddenScalar() {
runMerge(toHiddenScalar, new TestObserver<Integer>());
}
@Test
public void testSlowMergeFullScalar() {
for (final int req : new int[] { 16, 32, 64, 128, 256 }) {
TestObserver<Integer> ts = new TestObserver<Integer>() {
int remaining = req;
@Override
public void onNext(Integer t) {
super.onNext(t);
if (--remaining == 0) {
remaining = req;
}
}
};
runMerge(toScalar, ts);
}
}
@Test
public void testSlowMergeHiddenScalar() {
for (final int req : new int[] { 16, 32, 64, 128, 256 }) {
TestObserver<Integer> ts = new TestObserver<Integer>() {
int remaining = req;
@Override
public void onNext(Integer t) {
super.onNext(t);
if (--remaining == 0) {
remaining = req;
}
}
};
runMerge(toHiddenScalar, ts);
}
}
@SuppressWarnings("unchecked")
@Test
public void mergeArray() {
Observable.mergeArray(Observable.just(1), Observable.just(2))
.test()
.assertResult(1, 2);
}
@Test
public void mergeErrors() {
List<Throwable> errors = TestHelper.trackPluginErrors();
try {
Observable<Integer> source1 = Observable.error(new TestException("First"));
Observable<Integer> source2 = Observable.error(new TestException("Second"));
Observable.merge(source1, source2)
.test()
.assertFailureAndMessage(TestException.class, "First");
assertTrue(errors.toString(), errors.isEmpty());
} finally {
RxJavaPlugins.reset();
}
}
}