/**
* 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.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import java.util.*;
import org.junit.*;
import org.mockito.MockitoAnnotations;
import io.reactivex.*;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.exceptions.*;
import io.reactivex.functions.*;
import io.reactivex.internal.functions.Functions;
import io.reactivex.observers.TestObserver;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
public class ObservableGroupJoinTest {
Observer<Object> observer = TestHelper.mockObserver();
BiFunction<Integer, Integer, Integer> add = new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer t1, Integer t2) {
return t1 + t2;
}
};
<T> Function<Integer, Observable<T>> just(final Observable<T> observable) {
return new Function<Integer, Observable<T>>() {
@Override
public Observable<T> apply(Integer t1) {
return observable;
}
};
}
<T, R> Function<T, Observable<R>> just2(final Observable<R> observable) {
return new Function<T, Observable<R>>() {
@Override
public Observable<R> apply(T t1) {
return observable;
}
};
}
BiFunction<Integer, Observable<Integer>, Observable<Integer>> add2 = new BiFunction<Integer, Observable<Integer>, Observable<Integer>>() {
@Override
public Observable<Integer> apply(final Integer leftValue, Observable<Integer> rightValues) {
return rightValues.map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer rightValue) throws Exception {
return add.apply(leftValue, rightValue);
}
});
}
};
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@Test
public void behaveAsJoin() {
PublishSubject<Integer> source1 = PublishSubject.create();
PublishSubject<Integer> source2 = PublishSubject.create();
Observable<Integer> m = Observable.merge(source1.groupJoin(source2,
just(Observable.never()),
just(Observable.never()), add2));
m.subscribe(observer);
source1.onNext(1);
source1.onNext(2);
source1.onNext(4);
source2.onNext(16);
source2.onNext(32);
source2.onNext(64);
source1.onComplete();
source2.onComplete();
verify(observer, times(1)).onNext(17);
verify(observer, times(1)).onNext(18);
verify(observer, times(1)).onNext(20);
verify(observer, times(1)).onNext(33);
verify(observer, times(1)).onNext(34);
verify(observer, times(1)).onNext(36);
verify(observer, times(1)).onNext(65);
verify(observer, times(1)).onNext(66);
verify(observer, times(1)).onNext(68);
verify(observer, times(1)).onComplete(); //Never emitted?
verify(observer, never()).onError(any(Throwable.class));
}
class Person {
final int id;
final String name;
Person(int id, String name) {
this.id = id;
this.name = name;
}
}
class PersonFruit {
final int personId;
final String fruit;
PersonFruit(int personId, String fruit) {
this.personId = personId;
this.fruit = fruit;
}
}
class PPF {
final Person person;
final Observable<PersonFruit> fruits;
PPF(Person person, Observable<PersonFruit> fruits) {
this.person = person;
this.fruits = fruits;
}
}
@Test
public void normal1() {
Observable<Person> source1 = Observable.fromIterable(Arrays.asList(
new Person(1, "Joe"),
new Person(2, "Mike"),
new Person(3, "Charlie")
));
Observable<PersonFruit> source2 = Observable.fromIterable(Arrays.asList(
new PersonFruit(1, "Strawberry"),
new PersonFruit(1, "Apple"),
new PersonFruit(3, "Peach")
));
Observable<PPF> q = source1.groupJoin(
source2,
just2(Observable.<Object> never()),
just2(Observable.<Object> never()),
new BiFunction<Person, Observable<PersonFruit>, PPF>() {
@Override
public PPF apply(Person t1, Observable<PersonFruit> t2) {
return new PPF(t1, t2);
}
});
q.subscribe(
new Observer<PPF>() {
@Override
public void onNext(final PPF ppf) {
ppf.fruits.filter(new Predicate<PersonFruit>() {
@Override
public boolean test(PersonFruit t1) {
return ppf.person.id == t1.personId;
}
}).subscribe(new Consumer<PersonFruit>() {
@Override
public void accept(PersonFruit t1) {
observer.onNext(Arrays.asList(ppf.person.name, t1.fruit));
}
});
}
@Override
public void onError(Throwable e) {
observer.onError(e);
}
@Override
public void onComplete() {
observer.onComplete();
}
@Override
public void onSubscribe(Disposable s) {
}
}
);
verify(observer, times(1)).onNext(Arrays.asList("Joe", "Strawberry"));
verify(observer, times(1)).onNext(Arrays.asList("Joe", "Apple"));
verify(observer, times(1)).onNext(Arrays.asList("Charlie", "Peach"));
verify(observer, times(1)).onComplete();
verify(observer, never()).onError(any(Throwable.class));
}
@Test
public void leftThrows() {
PublishSubject<Integer> source1 = PublishSubject.create();
PublishSubject<Integer> source2 = PublishSubject.create();
Observable<Observable<Integer>> m = source1.groupJoin(source2,
just(Observable.never()),
just(Observable.never()), add2);
m.subscribe(observer);
source2.onNext(1);
source1.onError(new RuntimeException("Forced failure"));
verify(observer, times(1)).onError(any(Throwable.class));
verify(observer, never()).onComplete();
verify(observer, never()).onNext(any());
}
@Test
public void rightThrows() {
PublishSubject<Integer> source1 = PublishSubject.create();
PublishSubject<Integer> source2 = PublishSubject.create();
Observable<Observable<Integer>> m = source1.groupJoin(source2,
just(Observable.never()),
just(Observable.never()), add2);
m.subscribe(observer);
source1.onNext(1);
source2.onError(new RuntimeException("Forced failure"));
verify(observer, times(1)).onNext(any(Observable.class));
verify(observer, times(1)).onError(any(Throwable.class));
verify(observer, never()).onComplete();
}
@Test
public void leftDurationThrows() {
PublishSubject<Integer> source1 = PublishSubject.create();
PublishSubject<Integer> source2 = PublishSubject.create();
Observable<Integer> duration1 = Observable.<Integer> error(new RuntimeException("Forced failure"));
Observable<Observable<Integer>> m = source1.groupJoin(source2,
just(duration1),
just(Observable.never()), add2);
m.subscribe(observer);
source1.onNext(1);
verify(observer, times(1)).onError(any(Throwable.class));
verify(observer, never()).onComplete();
verify(observer, never()).onNext(any());
}
@Test
public void rightDurationThrows() {
PublishSubject<Integer> source1 = PublishSubject.create();
PublishSubject<Integer> source2 = PublishSubject.create();
Observable<Integer> duration1 = Observable.<Integer> error(new RuntimeException("Forced failure"));
Observable<Observable<Integer>> m = source1.groupJoin(source2,
just(Observable.never()),
just(duration1), add2);
m.subscribe(observer);
source2.onNext(1);
verify(observer, times(1)).onError(any(Throwable.class));
verify(observer, never()).onComplete();
verify(observer, never()).onNext(any());
}
@Test
public void leftDurationSelectorThrows() {
PublishSubject<Integer> source1 = PublishSubject.create();
PublishSubject<Integer> source2 = PublishSubject.create();
Function<Integer, Observable<Integer>> fail = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
throw new RuntimeException("Forced failure");
}
};
Observable<Observable<Integer>> m = source1.groupJoin(source2,
fail,
just(Observable.never()), add2);
m.subscribe(observer);
source1.onNext(1);
verify(observer, times(1)).onError(any(Throwable.class));
verify(observer, never()).onComplete();
verify(observer, never()).onNext(any());
}
@Test
public void rightDurationSelectorThrows() {
PublishSubject<Integer> source1 = PublishSubject.create();
PublishSubject<Integer> source2 = PublishSubject.create();
Function<Integer, Observable<Integer>> fail = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
throw new RuntimeException("Forced failure");
}
};
Observable<Observable<Integer>> m = source1.groupJoin(source2,
just(Observable.never()),
fail, add2);
m.subscribe(observer);
source2.onNext(1);
verify(observer, times(1)).onError(any(Throwable.class));
verify(observer, never()).onComplete();
verify(observer, never()).onNext(any());
}
@Test
public void resultSelectorThrows() {
PublishSubject<Integer> source1 = PublishSubject.create();
PublishSubject<Integer> source2 = PublishSubject.create();
BiFunction<Integer, Observable<Integer>, Integer> fail = new BiFunction<Integer, Observable<Integer>, Integer>() {
@Override
public Integer apply(Integer t1, Observable<Integer> t2) {
throw new RuntimeException("Forced failure");
}
};
Observable<Integer> m = source1.groupJoin(source2,
just(Observable.never()),
just(Observable.never()), fail);
m.subscribe(observer);
source1.onNext(1);
source2.onNext(2);
verify(observer, times(1)).onError(any(Throwable.class));
verify(observer, never()).onComplete();
verify(observer, never()).onNext(any());
}
@Test
public void dispose() {
TestHelper.checkDisposed(Observable.just(1).groupJoin(
Observable.just(2),
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer left) throws Exception {
return Observable.never();
}
},
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer right) throws Exception {
return Observable.never();
}
},
new BiFunction<Integer, Observable<Integer>, Object>() {
@Override
public Object apply(Integer r, Observable<Integer> l) throws Exception {
return l;
}
}
));
}
@Test
public void innerCompleteLeft() {
Observable.just(1)
.groupJoin(
Observable.just(2),
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer left) throws Exception {
return Observable.empty();
}
},
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer right) throws Exception {
return Observable.never();
}
},
new BiFunction<Integer, Observable<Integer>, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer r, Observable<Integer> l) throws Exception {
return l;
}
}
)
.flatMap(Functions.<Observable<Integer>>identity())
.test()
.assertResult();
}
@Test
public void innerErrorLeft() {
Observable.just(1)
.groupJoin(
Observable.just(2),
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer left) throws Exception {
return Observable.error(new TestException());
}
},
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer right) throws Exception {
return Observable.never();
}
},
new BiFunction<Integer, Observable<Integer>, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer r, Observable<Integer> l) throws Exception {
return l;
}
}
)
.flatMap(Functions.<Observable<Integer>>identity())
.test()
.assertFailure(TestException.class);
}
@Test
public void innerCompleteRight() {
Observable.just(1)
.groupJoin(
Observable.just(2),
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer left) throws Exception {
return Observable.never();
}
},
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer right) throws Exception {
return Observable.empty();
}
},
new BiFunction<Integer, Observable<Integer>, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer r, Observable<Integer> l) throws Exception {
return l;
}
}
)
.flatMap(Functions.<Observable<Integer>>identity())
.test()
.assertResult(2);
}
@Test
public void innerErrorRight() {
Observable.just(1)
.groupJoin(
Observable.just(2),
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer left) throws Exception {
return Observable.never();
}
},
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer right) throws Exception {
return Observable.error(new TestException());
}
},
new BiFunction<Integer, Observable<Integer>, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer r, Observable<Integer> l) throws Exception {
return l;
}
}
)
.flatMap(Functions.<Observable<Integer>>identity())
.test()
.assertFailure(TestException.class);
}
@Test
public void innerErrorRace() {
for (int i = 0; i < 500; i++) {
final PublishSubject<Object> ps1 = PublishSubject.create();
final PublishSubject<Object> ps2 = PublishSubject.create();
List<Throwable> errors = TestHelper.trackPluginErrors();
try {
TestObserver<Observable<Integer>> to = Observable.just(1)
.groupJoin(
Observable.just(2).concatWith(Observable.<Integer>never()),
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer left) throws Exception {
return ps1;
}
},
new Function<Integer, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Integer right) throws Exception {
return ps2;
}
},
new BiFunction<Integer, Observable<Integer>, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer r, Observable<Integer> l) throws Exception {
return l;
}
}
)
.test();
final TestException ex1 = new TestException();
final TestException ex2 = new TestException();
Runnable r1 = new Runnable() {
@Override
public void run() {
ps1.onError(ex1);
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
ps2.onError(ex2);
}
};
TestHelper.race(r1, r2, Schedulers.single());
to.assertError(Throwable.class).assertSubscribed().assertNotComplete().assertValueCount(1);
Throwable exc = to.errors().get(0);
if (exc instanceof CompositeException) {
List<Throwable> es = TestHelper.compositeList(exc);
TestHelper.assertError(es, 0, TestException.class);
TestHelper.assertError(es, 1, TestException.class);
} else {
to.assertError(TestException.class);
}
if (!errors.isEmpty()) {
TestHelper.assertUndeliverable(errors, 0, TestException.class);
}
} finally {
RxJavaPlugins.reset();
}
}
}
@Test
public void outerErrorRace() {
for (int i = 0; i < 500; i++) {
final PublishSubject<Object> ps1 = PublishSubject.create();
final PublishSubject<Object> ps2 = PublishSubject.create();
List<Throwable> errors = TestHelper.trackPluginErrors();
try {
TestObserver<Object> to = ps1
.groupJoin(
ps2,
new Function<Object, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Object left) throws Exception {
return Observable.never();
}
},
new Function<Object, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Object right) throws Exception {
return Observable.never();
}
},
new BiFunction<Object, Observable<Object>, Observable<Object>>() {
@Override
public Observable<Object> apply(Object r, Observable<Object> l) throws Exception {
return l;
}
}
)
.flatMap(Functions.<Observable<Object>>identity())
.test();
final TestException ex1 = new TestException();
final TestException ex2 = new TestException();
Runnable r1 = new Runnable() {
@Override
public void run() {
ps1.onError(ex1);
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
ps2.onError(ex2);
}
};
TestHelper.race(r1, r2, Schedulers.single());
to.assertError(Throwable.class).assertSubscribed().assertNotComplete().assertNoValues();
Throwable exc = to.errors().get(0);
if (exc instanceof CompositeException) {
List<Throwable> es = TestHelper.compositeList(exc);
TestHelper.assertError(es, 0, TestException.class);
TestHelper.assertError(es, 1, TestException.class);
} else {
to.assertError(TestException.class);
}
if (!errors.isEmpty()) {
TestHelper.assertUndeliverable(errors, 0, TestException.class);
}
} finally {
RxJavaPlugins.reset();
}
}
}
@Test
public void rightEmission() {
final PublishSubject<Object> ps1 = PublishSubject.create();
final PublishSubject<Object> ps2 = PublishSubject.create();
TestObserver<Object> to = ps1
.groupJoin(
ps2,
new Function<Object, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Object left) throws Exception {
return Observable.never();
}
},
new Function<Object, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Object right) throws Exception {
return Observable.never();
}
},
new BiFunction<Object, Observable<Object>, Observable<Object>>() {
@Override
public Observable<Object> apply(Object r, Observable<Object> l) throws Exception {
return l;
}
}
)
.flatMap(Functions.<Observable<Object>>identity())
.test();
ps2.onNext(2);
ps1.onNext(1);
ps1.onComplete();
ps2.onComplete();
to.assertResult(2);
}
}