/**
* Copyright 2014 Netflix, Inc.
*
* 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 rx.internal.operators;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import rx.Notification;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Observer;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action1;
import rx.functions.Func2;
import rx.functions.Func3;
import rx.functions.FuncN;
import rx.functions.Functions;
import rx.internal.util.RxRingBuffer;
import rx.observers.TestSubscriber;
import rx.schedulers.Schedulers;
import rx.subjects.PublishSubject;
public class OperatorZipTest {
Func2<String, String, String> concat2Strings;
PublishSubject<String> s1;
PublishSubject<String> s2;
Observable<String> zipped;
Observer<String> observer;
InOrder inOrder;
@Before
@SuppressWarnings("unchecked")
public void setUp() {
concat2Strings = new Func2<String, String, String>() {
@Override
public String call(String t1, String t2) {
return t1 + "-" + t2;
}
};
s1 = PublishSubject.create();
s2 = PublishSubject.create();
zipped = Observable.zip(s1, s2, concat2Strings);
observer = mock(Observer.class);
inOrder = inOrder(observer);
zipped.subscribe(observer);
}
@SuppressWarnings("unchecked")
@Test
public void testCollectionSizeDifferentThanFunction() {
FuncN<String> zipr = Functions.fromFunc(getConcatStringIntegerIntArrayZipr());
//Func3<String, Integer, int[], String>
/* define a Observer to receive aggregated events */
Observer<String> observer = mock(Observer.class);
@SuppressWarnings("rawtypes")
Collection ws = java.util.Collections.singleton(Observable.just("one", "two"));
Observable<String> w = Observable.zip(ws, zipr);
w.subscribe(observer);
verify(observer, times(1)).onError(any(Throwable.class));
verify(observer, never()).onCompleted();
verify(observer, never()).onNext(any(String.class));
}
@SuppressWarnings("unchecked")
/* mock calls don't do generics */
@Test
public void testStartpingDifferentLengthObservableSequences1() {
Observer<String> w = mock(Observer.class);
TestObservable w1 = new TestObservable();
TestObservable w2 = new TestObservable();
TestObservable w3 = new TestObservable();
Observable<String> zipW = Observable.zip(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsZipr());
zipW.subscribe(w);
/* simulate sending data */
// once for w1
w1.observer.onNext("1a");
w1.observer.onCompleted();
// twice for w2
w2.observer.onNext("2a");
w2.observer.onNext("2b");
w2.observer.onCompleted();
// 4 times for w3
w3.observer.onNext("3a");
w3.observer.onNext("3b");
w3.observer.onNext("3c");
w3.observer.onNext("3d");
w3.observer.onCompleted();
/* we should have been called 1 time on the Observer */
InOrder io = inOrder(w);
io.verify(w).onNext("1a2a3a");
io.verify(w, times(1)).onCompleted();
}
@Test
public void testStartpingDifferentLengthObservableSequences2() {
@SuppressWarnings("unchecked")
Observer<String> w = mock(Observer.class);
TestObservable w1 = new TestObservable();
TestObservable w2 = new TestObservable();
TestObservable w3 = new TestObservable();
Observable<String> zipW = Observable.zip(Observable.create(w1), Observable.create(w2), Observable.create(w3), getConcat3StringsZipr());
zipW.subscribe(w);
/* simulate sending data */
// 4 times for w1
w1.observer.onNext("1a");
w1.observer.onNext("1b");
w1.observer.onNext("1c");
w1.observer.onNext("1d");
w1.observer.onCompleted();
// twice for w2
w2.observer.onNext("2a");
w2.observer.onNext("2b");
w2.observer.onCompleted();
// 1 times for w3
w3.observer.onNext("3a");
w3.observer.onCompleted();
/* we should have been called 1 time on the Observer */
InOrder io = inOrder(w);
io.verify(w).onNext("1a2a3a");
io.verify(w, times(1)).onCompleted();
}
Func2<Object, Object, String> zipr2 = new Func2<Object, Object, String>() {
@Override
public String call(Object t1, Object t2) {
return "" + t1 + t2;
}
};
Func3<Object, Object, Object, String> zipr3 = new Func3<Object, Object, Object, String>() {
@Override
public String call(Object t1, Object t2, Object t3) {
return "" + t1 + t2 + t3;
}
};
/**
* Testing internal private logic due to the complexity so I want to use TDD to test as a I build it rather than relying purely on the overall functionality expected by the public methods.
*/
@SuppressWarnings("unchecked")
/* mock calls don't do generics */
@Test
public void testAggregatorSimple() {
PublishSubject<String> r1 = PublishSubject.create();
PublishSubject<String> r2 = PublishSubject.create();
/* define a Observer to receive aggregated events */
Observer<String> observer = mock(Observer.class);
Observable.zip(r1, r2, zipr2).subscribe(observer);
/* simulate the Observables pushing data into the aggregator */
r1.onNext("hello");
r2.onNext("world");
InOrder inOrder = inOrder(observer);
verify(observer, never()).onError(any(Throwable.class));
verify(observer, never()).onCompleted();
inOrder.verify(observer, times(1)).onNext("helloworld");
r1.onNext("hello ");
r2.onNext("again");
verify(observer, never()).onError(any(Throwable.class));
verify(observer, never()).onCompleted();
inOrder.verify(observer, times(1)).onNext("hello again");
r1.onCompleted();
r2.onCompleted();
inOrder.verify(observer, never()).onNext(anyString());
verify(observer, times(1)).onCompleted();
}
@SuppressWarnings("unchecked")
/* mock calls don't do generics */
@Test
public void testAggregatorDifferentSizedResultsWithOnComplete() {
/* create the aggregator which will execute the zip function when all Observables provide values */
/* define a Observer to receive aggregated events */
PublishSubject<String> r1 = PublishSubject.create();
PublishSubject<String> r2 = PublishSubject.create();
/* define a Observer to receive aggregated events */
Observer<String> observer = mock(Observer.class);
Observable.zip(r1, r2, zipr2).subscribe(observer);
/* simulate the Observables pushing data into the aggregator */
r1.onNext("hello");
r2.onNext("world");
r2.onCompleted();
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, never()).onError(any(Throwable.class));
inOrder.verify(observer, times(1)).onNext("helloworld");
inOrder.verify(observer, times(1)).onCompleted();
r1.onNext("hi");
r1.onCompleted();
inOrder.verify(observer, never()).onError(any(Throwable.class));
inOrder.verify(observer, never()).onCompleted();
inOrder.verify(observer, never()).onNext(anyString());
}
@SuppressWarnings("unchecked")
/* mock calls don't do generics */
@Test
public void testAggregateMultipleTypes() {
PublishSubject<String> r1 = PublishSubject.create();
PublishSubject<Integer> r2 = PublishSubject.create();
/* define a Observer to receive aggregated events */
Observer<String> observer = mock(Observer.class);
Observable.zip(r1, r2, zipr2).subscribe(observer);
/* simulate the Observables pushing data into the aggregator */
r1.onNext("hello");
r2.onNext(1);
r2.onCompleted();
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, never()).onError(any(Throwable.class));
inOrder.verify(observer, times(1)).onNext("hello1");
inOrder.verify(observer, times(1)).onCompleted();
r1.onNext("hi");
r1.onCompleted();
inOrder.verify(observer, never()).onError(any(Throwable.class));
inOrder.verify(observer, never()).onCompleted();
inOrder.verify(observer, never()).onNext(anyString());
}
@SuppressWarnings("unchecked")
/* mock calls don't do generics */
@Test
public void testAggregate3Types() {
PublishSubject<String> r1 = PublishSubject.create();
PublishSubject<Integer> r2 = PublishSubject.create();
PublishSubject<List<Integer>> r3 = PublishSubject.create();
/* define a Observer to receive aggregated events */
Observer<String> observer = mock(Observer.class);
Observable.zip(r1, r2, r3, zipr3).subscribe(observer);
/* simulate the Observables pushing data into the aggregator */
r1.onNext("hello");
r2.onNext(2);
r3.onNext(Arrays.asList(5, 6, 7));
verify(observer, never()).onError(any(Throwable.class));
verify(observer, never()).onCompleted();
verify(observer, times(1)).onNext("hello2[5, 6, 7]");
}
@SuppressWarnings("unchecked")
/* mock calls don't do generics */
@Test
public void testAggregatorsWithDifferentSizesAndTiming() {
PublishSubject<String> r1 = PublishSubject.create();
PublishSubject<String> r2 = PublishSubject.create();
/* define a Observer to receive aggregated events */
Observer<String> observer = mock(Observer.class);
Observable.zip(r1, r2, zipr2).subscribe(observer);
/* simulate the Observables pushing data into the aggregator */
r1.onNext("one");
r1.onNext("two");
r1.onNext("three");
r2.onNext("A");
verify(observer, never()).onError(any(Throwable.class));
verify(observer, never()).onCompleted();
verify(observer, times(1)).onNext("oneA");
r1.onNext("four");
r1.onCompleted();
r2.onNext("B");
verify(observer, times(1)).onNext("twoB");
r2.onNext("C");
verify(observer, times(1)).onNext("threeC");
r2.onNext("D");
verify(observer, times(1)).onNext("fourD");
r2.onNext("E");
verify(observer, never()).onNext("E");
r2.onCompleted();
verify(observer, never()).onError(any(Throwable.class));
verify(observer, times(1)).onCompleted();
}
@SuppressWarnings("unchecked")
/* mock calls don't do generics */
@Test
public void testAggregatorError() {
PublishSubject<String> r1 = PublishSubject.create();
PublishSubject<String> r2 = PublishSubject.create();
/* define a Observer to receive aggregated events */
Observer<String> observer = mock(Observer.class);
Observable.zip(r1, r2, zipr2).subscribe(observer);
/* simulate the Observables pushing data into the aggregator */
r1.onNext("hello");
r2.onNext("world");
verify(observer, never()).onError(any(Throwable.class));
verify(observer, never()).onCompleted();
verify(observer, times(1)).onNext("helloworld");
r1.onError(new RuntimeException(""));
r1.onNext("hello");
r2.onNext("again");
verify(observer, times(1)).onError(any(Throwable.class));
verify(observer, never()).onCompleted();
// we don't want to be called again after an error
verify(observer, times(0)).onNext("helloagain");
}
@SuppressWarnings("unchecked")
/* mock calls don't do generics */
@Test
public void testAggregatorUnsubscribe() {
PublishSubject<String> r1 = PublishSubject.create();
PublishSubject<String> r2 = PublishSubject.create();
/* define a Observer to receive aggregated events */
Observer<String> observer = mock(Observer.class);
Subscription subscription = Observable.zip(r1, r2, zipr2).subscribe(observer);
/* simulate the Observables pushing data into the aggregator */
r1.onNext("hello");
r2.onNext("world");
verify(observer, never()).onError(any(Throwable.class));
verify(observer, never()).onCompleted();
verify(observer, times(1)).onNext("helloworld");
subscription.unsubscribe();
r1.onNext("hello");
r2.onNext("again");
verify(observer, times(0)).onError(any(Throwable.class));
verify(observer, never()).onCompleted();
// we don't want to be called again after an error
verify(observer, times(0)).onNext("helloagain");
}
@SuppressWarnings("unchecked")
/* mock calls don't do generics */
@Test
public void testAggregatorEarlyCompletion() {
PublishSubject<String> r1 = PublishSubject.create();
PublishSubject<String> r2 = PublishSubject.create();
/* define a Observer to receive aggregated events */
Observer<String> observer = mock(Observer.class);
Observable.zip(r1, r2, zipr2).subscribe(observer);
/* simulate the Observables pushing data into the aggregator */
r1.onNext("one");
r1.onNext("two");
r1.onCompleted();
r2.onNext("A");
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, never()).onError(any(Throwable.class));
inOrder.verify(observer, never()).onCompleted();
inOrder.verify(observer, times(1)).onNext("oneA");
r2.onCompleted();
inOrder.verify(observer, never()).onError(any(Throwable.class));
inOrder.verify(observer, times(1)).onCompleted();
inOrder.verify(observer, never()).onNext(anyString());
}
@SuppressWarnings("unchecked")
/* mock calls don't do generics */
@Test
public void testStart2Types() {
Func2<String, Integer, String> zipr = getConcatStringIntegerZipr();
/* define a Observer to receive aggregated events */
Observer<String> observer = mock(Observer.class);
Observable<String> w = Observable.zip(Observable.just("one", "two"), Observable.just(2, 3, 4), zipr);
w.subscribe(observer);
verify(observer, never()).onError(any(Throwable.class));
verify(observer, times(1)).onCompleted();
verify(observer, times(1)).onNext("one2");
verify(observer, times(1)).onNext("two3");
verify(observer, never()).onNext("4");
}
@SuppressWarnings("unchecked")
/* mock calls don't do generics */
@Test
public void testStart3Types() {
Func3<String, Integer, int[], String> zipr = getConcatStringIntegerIntArrayZipr();
/* define a Observer to receive aggregated events */
Observer<String> observer = mock(Observer.class);
Observable<String> w = Observable.zip(Observable.just("one", "two"), Observable.just(2), Observable.just(new int[] { 4, 5, 6 }), zipr);
w.subscribe(observer);
verify(observer, never()).onError(any(Throwable.class));
verify(observer, times(1)).onCompleted();
verify(observer, times(1)).onNext("one2[4, 5, 6]");
verify(observer, never()).onNext("two");
}
@Test
public void testOnNextExceptionInvokesOnError() {
Func2<Integer, Integer, Integer> zipr = getDivideZipr();
@SuppressWarnings("unchecked")
Observer<Integer> observer = mock(Observer.class);
Observable<Integer> w = Observable.zip(Observable.just(10, 20, 30), Observable.just(0, 1, 2), zipr);
w.subscribe(observer);
verify(observer, times(1)).onError(any(Throwable.class));
}
@Test
public void testOnFirstCompletion() {
PublishSubject<String> oA = PublishSubject.create();
PublishSubject<String> oB = PublishSubject.create();
@SuppressWarnings("unchecked")
Observer<String> obs = mock(Observer.class);
Observable<String> o = Observable.zip(oA, oB, getConcat2Strings());
o.subscribe(obs);
InOrder io = inOrder(obs);
oA.onNext("a1");
io.verify(obs, never()).onNext(anyString());
oB.onNext("b1");
io.verify(obs, times(1)).onNext("a1-b1");
oB.onNext("b2");
io.verify(obs, never()).onNext(anyString());
oA.onNext("a2");
io.verify(obs, times(1)).onNext("a2-b2");
oA.onNext("a3");
oA.onNext("a4");
oA.onNext("a5");
oA.onCompleted();
// SHOULD ONCOMPLETE BE EMITTED HERE INSTEAD OF WAITING
// FOR B3, B4, B5 TO BE EMITTED?
oB.onNext("b3");
oB.onNext("b4");
oB.onNext("b5");
io.verify(obs, times(1)).onNext("a3-b3");
io.verify(obs, times(1)).onNext("a4-b4");
io.verify(obs, times(1)).onNext("a5-b5");
// WE RECEIVE THE ONCOMPLETE HERE
io.verify(obs, times(1)).onCompleted();
oB.onNext("b6");
oB.onNext("b7");
oB.onNext("b8");
oB.onNext("b9");
// never completes (infinite stream for example)
// we should receive nothing else despite oB continuing after oA completed
io.verifyNoMoreInteractions();
}
@Test
public void testOnErrorTermination() {
PublishSubject<String> oA = PublishSubject.create();
PublishSubject<String> oB = PublishSubject.create();
@SuppressWarnings("unchecked")
Observer<String> obs = mock(Observer.class);
Observable<String> o = Observable.zip(oA, oB, getConcat2Strings());
o.subscribe(obs);
InOrder io = inOrder(obs);
oA.onNext("a1");
io.verify(obs, never()).onNext(anyString());
oB.onNext("b1");
io.verify(obs, times(1)).onNext("a1-b1");
oB.onNext("b2");
io.verify(obs, never()).onNext(anyString());
oA.onNext("a2");
io.verify(obs, times(1)).onNext("a2-b2");
oA.onNext("a3");
oA.onNext("a4");
oA.onNext("a5");
oA.onError(new RuntimeException("forced failure"));
// it should emit failure immediately
io.verify(obs, times(1)).onError(any(RuntimeException.class));
oB.onNext("b3");
oB.onNext("b4");
oB.onNext("b5");
oB.onNext("b6");
oB.onNext("b7");
oB.onNext("b8");
oB.onNext("b9");
// never completes (infinite stream for example)
// we should receive nothing else despite oB continuing after oA completed
io.verifyNoMoreInteractions();
}
private Func2<String, String, String> getConcat2Strings() {
return new Func2<String, String, String>() {
@Override
public String call(String t1, String t2) {
return t1 + "-" + t2;
}
};
}
private Func2<Integer, Integer, Integer> getDivideZipr() {
Func2<Integer, Integer, Integer> zipr = new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer i1, Integer i2) {
return i1 / i2;
}
};
return zipr;
}
private Func3<String, String, String, String> getConcat3StringsZipr() {
Func3<String, String, String, String> zipr = new Func3<String, String, String, String>() {
@Override
public String call(String a1, String a2, String a3) {
if (a1 == null) {
a1 = "";
}
if (a2 == null) {
a2 = "";
}
if (a3 == null) {
a3 = "";
}
return a1 + a2 + a3;
}
};
return zipr;
}
private Func2<String, Integer, String> getConcatStringIntegerZipr() {
Func2<String, Integer, String> zipr = new Func2<String, Integer, String>() {
@Override
public String call(String s, Integer i) {
return getStringValue(s) + getStringValue(i);
}
};
return zipr;
}
private Func3<String, Integer, int[], String> getConcatStringIntegerIntArrayZipr() {
Func3<String, Integer, int[], String> zipr = new Func3<String, Integer, int[], String>() {
@Override
public String call(String s, Integer i, int[] iArray) {
return getStringValue(s) + getStringValue(i) + getStringValue(iArray);
}
};
return zipr;
}
private static String getStringValue(Object o) {
if (o == null) {
return "";
} else {
if (o instanceof int[]) {
return Arrays.toString((int[]) o);
} else {
return String.valueOf(o);
}
}
}
private static class TestObservable implements Observable.OnSubscribe<String> {
Observer<? super String> observer;
@Override
public void call(Subscriber<? super String> observer) {
// just store the variable where it can be accessed so we can manually trigger it
this.observer = observer;
}
}
@Test
public void testFirstCompletesThenSecondInfinite() {
s1.onNext("a");
s1.onNext("b");
s1.onCompleted();
s2.onNext("1");
inOrder.verify(observer, times(1)).onNext("a-1");
s2.onNext("2");
inOrder.verify(observer, times(1)).onNext("b-2");
inOrder.verify(observer, times(1)).onCompleted();
inOrder.verifyNoMoreInteractions();
}
@Test
public void testSecondInfiniteThenFirstCompletes() {
s2.onNext("1");
s2.onNext("2");
s1.onNext("a");
inOrder.verify(observer, times(1)).onNext("a-1");
s1.onNext("b");
inOrder.verify(observer, times(1)).onNext("b-2");
s1.onCompleted();
inOrder.verify(observer, times(1)).onCompleted();
inOrder.verifyNoMoreInteractions();
}
@Test
public void testSecondCompletesThenFirstInfinite() {
s2.onNext("1");
s2.onNext("2");
s2.onCompleted();
s1.onNext("a");
inOrder.verify(observer, times(1)).onNext("a-1");
s1.onNext("b");
inOrder.verify(observer, times(1)).onNext("b-2");
inOrder.verify(observer, times(1)).onCompleted();
inOrder.verifyNoMoreInteractions();
}
@Test
public void testFirstInfiniteThenSecondCompletes() {
s1.onNext("a");
s1.onNext("b");
s2.onNext("1");
inOrder.verify(observer, times(1)).onNext("a-1");
s2.onNext("2");
inOrder.verify(observer, times(1)).onNext("b-2");
s2.onCompleted();
inOrder.verify(observer, times(1)).onCompleted();
inOrder.verifyNoMoreInteractions();
}
@Test
public void testFirstFails() {
s2.onNext("a");
s1.onError(new RuntimeException("Forced failure"));
inOrder.verify(observer, times(1)).onError(any(RuntimeException.class));
s2.onNext("b");
s1.onNext("1");
s1.onNext("2");
inOrder.verify(observer, never()).onCompleted();
inOrder.verify(observer, never()).onNext(any(String.class));
inOrder.verifyNoMoreInteractions();
}
@Test
public void testSecondFails() {
s1.onNext("a");
s1.onNext("b");
s2.onError(new RuntimeException("Forced failure"));
inOrder.verify(observer, times(1)).onError(any(RuntimeException.class));
s2.onNext("1");
s2.onNext("2");
inOrder.verify(observer, never()).onCompleted();
inOrder.verify(observer, never()).onNext(any(String.class));
inOrder.verifyNoMoreInteractions();
}
@Test
public void testStartWithOnCompletedTwice() {
// issue: https://groups.google.com/forum/#!topic/rxjava/79cWTv3TFp0
// The problem is the original "zip" implementation does not wrap
// an internal observer with a SafeObserver. However, in the "zip",
// it may calls "onCompleted" twice. That breaks the Rx contract.
// This test tries to emulate this case.
// As "mock(Observer.class)" will create an instance in the package "rx",
// we need to wrap "mock(Observer.class)" with an observer instance
// which is in the package "rx.operators".
@SuppressWarnings("unchecked")
final Observer<Integer> observer = mock(Observer.class);
Observable.zip(Observable.just(1),
Observable.just(1), new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer a, Integer b) {
return a + b;
}
}).subscribe(new Observer<Integer>() {
@Override
public void onCompleted() {
observer.onCompleted();
}
@Override
public void onError(Throwable e) {
observer.onError(e);
}
@Override
public void onNext(Integer args) {
observer.onNext(args);
}
});
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, times(1)).onNext(2);
inOrder.verify(observer, times(1)).onCompleted();
inOrder.verifyNoMoreInteractions();
}
@Test
public void testStart() {
Observable<String> os = OBSERVABLE_OF_5_INTEGERS
.zipWith(OBSERVABLE_OF_5_INTEGERS, new Func2<Integer, Integer, String>() {
@Override
public String call(Integer a, Integer b) {
return a + "-" + b;
}
});
final ArrayList<String> list = new ArrayList<String>();
os.subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
list.add(s);
}
});
assertEquals(5, list.size());
assertEquals("1-1", list.get(0));
assertEquals("2-2", list.get(1));
assertEquals("5-5", list.get(4));
}
@Test
public void testStartAsync() throws InterruptedException {
Observable<String> os = ASYNC_OBSERVABLE_OF_INFINITE_INTEGERS(new CountDownLatch(1)).onBackpressureBuffer()
.zipWith(ASYNC_OBSERVABLE_OF_INFINITE_INTEGERS(new CountDownLatch(1)).onBackpressureBuffer(), new Func2<Integer, Integer, String>() {
@Override
public String call(Integer a, Integer b) {
return a + "-" + b;
}
}).take(5);
TestSubscriber<String> ts = new TestSubscriber<String>();
os.subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(5, ts.getOnNextEvents().size());
assertEquals("1-1", ts.getOnNextEvents().get(0));
assertEquals("2-2", ts.getOnNextEvents().get(1));
assertEquals("5-5", ts.getOnNextEvents().get(4));
}
@Test
public void testStartInfiniteAndFinite() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final CountDownLatch infiniteObservable = new CountDownLatch(1);
Observable<String> os = OBSERVABLE_OF_5_INTEGERS
.zipWith(ASYNC_OBSERVABLE_OF_INFINITE_INTEGERS(infiniteObservable), new Func2<Integer, Integer, String>() {
@Override
public String call(Integer a, Integer b) {
return a + "-" + b;
}
});
final ArrayList<String> list = new ArrayList<String>();
os.subscribe(new Observer<String>() {
@Override
public void onCompleted() {
latch.countDown();
}
@Override
public void onError(Throwable e) {
e.printStackTrace();
latch.countDown();
}
@Override
public void onNext(String s) {
System.out.println(s);
list.add(s);
}
});
latch.await(1000, TimeUnit.MILLISECONDS);
if (!infiniteObservable.await(2000, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("didn't unsubscribe");
}
assertEquals(5, list.size());
assertEquals("1-1", list.get(0));
assertEquals("2-2", list.get(1));
assertEquals("5-5", list.get(4));
}
@Test
public void testEmitNull() {
Observable<Integer> oi = Observable.just(1, null, 3);
Observable<String> os = Observable.just("a", "b", null);
Observable<String> o = Observable.zip(oi, os, new Func2<Integer, String, String>() {
@Override
public String call(Integer t1, String t2) {
return t1 + "-" + t2;
}
});
final ArrayList<String> list = new ArrayList<String>();
o.subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
list.add(s);
}
});
assertEquals(3, list.size());
assertEquals("1-a", list.get(0));
assertEquals("null-b", list.get(1));
assertEquals("3-null", list.get(2));
}
@Test
public void testEmitMaterializedNotifications() {
Observable<Notification<Integer>> oi = Observable.just(1, 2, 3).materialize();
Observable<Notification<String>> os = Observable.just("a", "b", "c").materialize();
Observable<String> o = Observable.zip(oi, os, new Func2<Notification<Integer>, Notification<String>, String>() {
@Override
public String call(Notification<Integer> t1, Notification<String> t2) {
return t1.getKind() + "_" + t1.getValue() + "-" + t2.getKind() + "_" + t2.getValue();
}
});
final ArrayList<String> list = new ArrayList<String>();
o.subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
list.add(s);
}
});
assertEquals(4, list.size());
assertEquals("OnNext_1-OnNext_a", list.get(0));
assertEquals("OnNext_2-OnNext_b", list.get(1));
assertEquals("OnNext_3-OnNext_c", list.get(2));
assertEquals("OnCompleted_null-OnCompleted_null", list.get(3));
}
@Test
public void testStartEmptyObservables() {
Observable<String> o = Observable.zip(Observable.<Integer> empty(), Observable.<String> empty(), new Func2<Integer, String, String>() {
@Override
public String call(Integer t1, String t2) {
return t1 + "-" + t2;
}
});
final ArrayList<String> list = new ArrayList<String>();
o.subscribe(new Action1<String>() {
@Override
public void call(String s) {
System.out.println(s);
list.add(s);
}
});
assertEquals(0, list.size());
}
@Test
public void testStartEmptyList() {
final Object invoked = new Object();
Collection<Observable<Object>> observables = Collections.emptyList();
Observable<Object> o = Observable.zip(observables, new FuncN<Object>() {
@Override
public Object call(final Object... args) {
assertEquals("No argument should have been passed", 0, args.length);
return invoked;
}
});
TestSubscriber<Object> ts = new TestSubscriber<Object>();
o.subscribe(ts);
ts.awaitTerminalEvent(200, TimeUnit.MILLISECONDS);
ts.assertReceivedOnNext(Collections.emptyList());
}
/**
* Expect NoSuchElementException instead of blocking forever as zip should emit onCompleted and no onNext
* and last() expects at least a single response.
*/
@Test(expected = NoSuchElementException.class)
public void testStartEmptyListBlocking() {
final Object invoked = new Object();
Collection<Observable<Object>> observables = Collections.emptyList();
Observable<Object> o = Observable.zip(observables, new FuncN<Object>() {
@Override
public Object call(final Object... args) {
assertEquals("No argument should have been passed", 0, args.length);
return invoked;
}
});
o.toBlocking().last();
}
@Test
public void testBackpressureSync() {
AtomicInteger generatedA = new AtomicInteger();
AtomicInteger generatedB = new AtomicInteger();
Observable<Integer> o1 = createInfiniteObservable(generatedA);
Observable<Integer> o2 = createInfiniteObservable(generatedB);
TestSubscriber<String> ts = new TestSubscriber<String>();
Observable.zip(o1, o2, new Func2<Integer, Integer, String>() {
@Override
public String call(Integer t1, Integer t2) {
return t1 + "-" + t2;
}
}).take(RxRingBuffer.SIZE * 2).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(RxRingBuffer.SIZE * 2, ts.getOnNextEvents().size());
assertTrue(generatedA.get() < (RxRingBuffer.SIZE * 3));
assertTrue(generatedB.get() < (RxRingBuffer.SIZE * 3));
}
@Test
public void testBackpressureAsync() {
AtomicInteger generatedA = new AtomicInteger();
AtomicInteger generatedB = new AtomicInteger();
Observable<Integer> o1 = createInfiniteObservable(generatedA).subscribeOn(Schedulers.computation());
Observable<Integer> o2 = createInfiniteObservable(generatedB).subscribeOn(Schedulers.computation());
TestSubscriber<String> ts = new TestSubscriber<String>();
Observable.zip(o1, o2, new Func2<Integer, Integer, String>() {
@Override
public String call(Integer t1, Integer t2) {
return t1 + "-" + t2;
}
}).take(RxRingBuffer.SIZE * 2).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(RxRingBuffer.SIZE * 2, ts.getOnNextEvents().size());
assertTrue(generatedA.get() < (RxRingBuffer.SIZE * 3));
assertTrue(generatedB.get() < (RxRingBuffer.SIZE * 3));
}
@Test
public void testDownstreamBackpressureRequestsWithFiniteSyncObservables() {
AtomicInteger generatedA = new AtomicInteger();
AtomicInteger generatedB = new AtomicInteger();
Observable<Integer> o1 = createInfiniteObservable(generatedA).take(RxRingBuffer.SIZE * 2);
Observable<Integer> o2 = createInfiniteObservable(generatedB).take(RxRingBuffer.SIZE * 2);
TestSubscriber<String> ts = new TestSubscriber<String>();
Observable.zip(o1, o2, new Func2<Integer, Integer, String>() {
@Override
public String call(Integer t1, Integer t2) {
return t1 + "-" + t2;
}
}).observeOn(Schedulers.computation()).take(RxRingBuffer.SIZE * 2).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(RxRingBuffer.SIZE * 2, ts.getOnNextEvents().size());
System.out.println("Generated => A: " + generatedA.get() + " B: " + generatedB.get());
assertTrue(generatedA.get() < (RxRingBuffer.SIZE * 3));
assertTrue(generatedB.get() < (RxRingBuffer.SIZE * 3));
}
@Test
public void testDownstreamBackpressureRequestsWithInfiniteAsyncObservables() {
AtomicInteger generatedA = new AtomicInteger();
AtomicInteger generatedB = new AtomicInteger();
Observable<Integer> o1 = createInfiniteObservable(generatedA).subscribeOn(Schedulers.computation());
Observable<Integer> o2 = createInfiniteObservable(generatedB).subscribeOn(Schedulers.computation());
TestSubscriber<String> ts = new TestSubscriber<String>();
Observable.zip(o1, o2, new Func2<Integer, Integer, String>() {
@Override
public String call(Integer t1, Integer t2) {
return t1 + "-" + t2;
}
}).observeOn(Schedulers.computation()).take(RxRingBuffer.SIZE * 2).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(RxRingBuffer.SIZE * 2, ts.getOnNextEvents().size());
System.out.println("Generated => A: " + generatedA.get() + " B: " + generatedB.get());
assertTrue(generatedA.get() < (RxRingBuffer.SIZE * 4));
assertTrue(generatedB.get() < (RxRingBuffer.SIZE * 4));
}
@Test
public void testDownstreamBackpressureRequestsWithInfiniteSyncObservables() {
AtomicInteger generatedA = new AtomicInteger();
AtomicInteger generatedB = new AtomicInteger();
Observable<Integer> o1 = createInfiniteObservable(generatedA);
Observable<Integer> o2 = createInfiniteObservable(generatedB);
TestSubscriber<String> ts = new TestSubscriber<String>();
Observable.zip(o1, o2, new Func2<Integer, Integer, String>() {
@Override
public String call(Integer t1, Integer t2) {
return t1 + "-" + t2;
}
}).observeOn(Schedulers.computation()).take(RxRingBuffer.SIZE * 2).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(RxRingBuffer.SIZE * 2, ts.getOnNextEvents().size());
System.out.println("Generated => A: " + generatedA.get() + " B: " + generatedB.get());
assertTrue(generatedA.get() < (RxRingBuffer.SIZE * 4));
assertTrue(generatedB.get() < (RxRingBuffer.SIZE * 4));
}
private Observable<Integer> createInfiniteObservable(final AtomicInteger generated) {
Observable<Integer> observable = Observable.from(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 observable;
}
Observable<Integer> OBSERVABLE_OF_5_INTEGERS = OBSERVABLE_OF_5_INTEGERS(new AtomicInteger());
Observable<Integer> OBSERVABLE_OF_5_INTEGERS(final AtomicInteger numEmitted) {
return Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(final Subscriber<? super Integer> o) {
for (int i = 1; i <= 5; i++) {
if (o.isUnsubscribed()) {
break;
}
numEmitted.incrementAndGet();
o.onNext(i);
Thread.yield();
}
o.onCompleted();
}
});
}
Observable<Integer> ASYNC_OBSERVABLE_OF_INFINITE_INTEGERS(final CountDownLatch latch) {
return Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(final Subscriber<? super Integer> o) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("-------> subscribe to infinite sequence");
System.out.println("Starting thread: " + Thread.currentThread());
int i = 1;
while (!o.isUnsubscribed()) {
o.onNext(i++);
Thread.yield();
}
o.onCompleted();
latch.countDown();
System.out.println("Ending thread: " + Thread.currentThread());
}
});
t.start();
}
});
}
@Test(timeout = 30000)
public void testIssue1812() {
// https://github.com/ReactiveX/RxJava/issues/1812
Observable<Integer> zip1 = Observable.zip(Observable.range(0, 1026), Observable.range(0, 1026),
new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer i1, Integer i2) {
return i1 + i2;
}
});
Observable<Integer> zip2 = Observable.zip(zip1, Observable.range(0, 1026),
new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer i1, Integer i2) {
return i1 + i2;
}
});
List<Integer> expected = new ArrayList<Integer>();
for (int i = 0; i < 1026; i++) {
expected.add(i * 3);
}
assertEquals(expected, zip2.toList().toBlocking().single());
}
@Test
public void testUnboundedDownstreamOverrequesting() {
Observable<Integer> source = Observable.range(1, 2).zipWith(Observable.range(1, 2), new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + 10 * t2;
}
});
TestSubscriber<Integer> ts = new TestSubscriber<Integer>() {
@Override
public void onNext(Integer t) {
super.onNext(t);
requestMore(5);
}
};
source.subscribe(ts);
ts.assertNoErrors();
ts.assertTerminalEvent();
ts.assertReceivedOnNext(Arrays.asList(11, 22));
}
@Test(timeout = 10000)
public void testZipRace() {
Observable<Integer> src = Observable.just(1).subscribeOn(Schedulers.computation());
for (int i = 0; i < 100000; i++) {
int value = Observable.zip(src, src, new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + t2 * 10;
}
}).toBlocking().singleOrDefault(0);
Assert.assertEquals(11, value);
}
}
/**
* Request only a single value and don't wait for another request just
* to emit an onCompleted.
*/
@Test
public void testZipRequest1() {
Observable<Integer> src = Observable.just(1).subscribeOn(Schedulers.computation());
TestSubscriber<Integer> ts = new TestSubscriber<Integer>() {
@Override
public void onStart() {
requestMore(1);
}
};
Observable.zip(src, src, new Func2<Integer, Integer, Integer>() {
@Override
public Integer call(Integer t1, Integer t2) {
return t1 + t2 * 10;
}
}).subscribe(ts);
ts.awaitTerminalEvent(1, TimeUnit.SECONDS);
ts.assertNoErrors();
ts.assertReceivedOnNext(Arrays.asList(11));
}
}