/**
* 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.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.*;
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.Consumer;
import io.reactivex.observers.TestObserver;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.*;
import io.reactivex.subjects.PublishSubject;
public class ObservableAmbTest {
private TestScheduler scheduler;
private Scheduler.Worker innerScheduler;
@Before
public void setUp() {
scheduler = new TestScheduler();
innerScheduler = scheduler.createWorker();
}
private Observable<String> createObservable(final String[] values,
final long interval, final Throwable e) {
return Observable.unsafeCreate(new ObservableSource<String>() {
@Override
public void subscribe(final Observer<? super String> observer) {
CompositeDisposable parentSubscription = new CompositeDisposable();
observer.onSubscribe(parentSubscription);
long delay = interval;
for (final String value : values) {
parentSubscription.add(innerScheduler.schedule(new Runnable() {
@Override
public void run() {
observer.onNext(value);
}
}
, delay, TimeUnit.MILLISECONDS));
delay += interval;
}
parentSubscription.add(innerScheduler.schedule(new Runnable() {
@Override
public void run() {
if (e == null) {
observer.onComplete();
} else {
observer.onError(e);
}
}
}, delay, TimeUnit.MILLISECONDS));
}
});
}
@Test
public void testAmb() {
Observable<String> observable1 = createObservable(new String[] {
"1", "11", "111", "1111" }, 2000, null);
Observable<String> observable2 = createObservable(new String[] {
"2", "22", "222", "2222" }, 1000, null);
Observable<String> observable3 = createObservable(new String[] {
"3", "33", "333", "3333" }, 3000, null);
@SuppressWarnings("unchecked")
Observable<String> o = Observable.ambArray(observable1,
observable2, observable3);
Observer<String> observer = TestHelper.mockObserver();
o.subscribe(observer);
scheduler.advanceTimeBy(100000, TimeUnit.MILLISECONDS);
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, times(1)).onNext("2");
inOrder.verify(observer, times(1)).onNext("22");
inOrder.verify(observer, times(1)).onNext("222");
inOrder.verify(observer, times(1)).onNext("2222");
inOrder.verify(observer, times(1)).onComplete();
inOrder.verifyNoMoreInteractions();
}
@Test
public void testAmb2() {
IOException expectedException = new IOException(
"fake exception");
Observable<String> observable1 = createObservable(new String[] {},
2000, new IOException("fake exception"));
Observable<String> observable2 = createObservable(new String[] {
"2", "22", "222", "2222" }, 1000, expectedException);
Observable<String> observable3 = createObservable(new String[] {},
3000, new IOException("fake exception"));
@SuppressWarnings("unchecked")
Observable<String> o = Observable.ambArray(observable1,
observable2, observable3);
Observer<String> observer = TestHelper.mockObserver();
o.subscribe(observer);
scheduler.advanceTimeBy(100000, TimeUnit.MILLISECONDS);
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, times(1)).onNext("2");
inOrder.verify(observer, times(1)).onNext("22");
inOrder.verify(observer, times(1)).onNext("222");
inOrder.verify(observer, times(1)).onNext("2222");
inOrder.verify(observer, times(1)).onError(expectedException);
inOrder.verifyNoMoreInteractions();
}
@Test
public void testAmb3() {
Observable<String> observable1 = createObservable(new String[] {
"1" }, 2000, null);
Observable<String> observable2 = createObservable(new String[] {},
1000, null);
Observable<String> observable3 = createObservable(new String[] {
"3" }, 3000, null);
@SuppressWarnings("unchecked")
Observable<String> o = Observable.ambArray(observable1,
observable2, observable3);
Observer<String> observer = TestHelper.mockObserver();
o.subscribe(observer);
scheduler.advanceTimeBy(100000, TimeUnit.MILLISECONDS);
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, times(1)).onComplete();
inOrder.verifyNoMoreInteractions();
}
@SuppressWarnings("unchecked")
@Test
public void testSubscriptionOnlyHappensOnce() throws InterruptedException {
final AtomicLong count = new AtomicLong();
Consumer<Disposable> incrementer = new Consumer<Disposable>() {
@Override
public void accept(Disposable s) {
count.incrementAndGet();
}
};
//this aync stream should emit first
Observable<Integer> o1 = Observable.just(1).doOnSubscribe(incrementer)
.delay(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation());
//this stream emits second
Observable<Integer> o2 = Observable.just(1).doOnSubscribe(incrementer)
.delay(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation());
TestObserver<Integer> ts = new TestObserver<Integer>();
Observable.ambArray(o1, o2).subscribe(ts);
ts.awaitTerminalEvent(5, TimeUnit.SECONDS);
ts.assertNoErrors();
assertEquals(2, count.get());
}
@Test
public void testSynchronousSources() {
// under async subscription the second Observable would complete before
// the first but because this is a synchronous subscription to sources
// then second Observable does not get subscribed to before first
// subscription completes hence first Observable emits result through
// amb
int result = Observable.just(1).doOnNext(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//
}
}
}).ambWith(Observable.just(2)).blockingSingle();
assertEquals(1, result);
}
@SuppressWarnings("unchecked")
@Test
public void testAmbCancelsOthers() {
PublishSubject<Integer> source1 = PublishSubject.create();
PublishSubject<Integer> source2 = PublishSubject.create();
PublishSubject<Integer> source3 = PublishSubject.create();
TestObserver<Integer> ts = new TestObserver<Integer>();
Observable.ambArray(source1, source2, source3).subscribe(ts);
assertTrue("Source 1 doesn't have subscribers!", source1.hasObservers());
assertTrue("Source 2 doesn't have subscribers!", source2.hasObservers());
assertTrue("Source 3 doesn't have subscribers!", source3.hasObservers());
source1.onNext(1);
assertTrue("Source 1 doesn't have subscribers!", source1.hasObservers());
assertFalse("Source 2 still has subscribers!", source2.hasObservers());
assertFalse("Source 2 still has subscribers!", source3.hasObservers());
}
@SuppressWarnings("unchecked")
@Test
public void ambArrayEmpty() {
assertSame(Observable.empty(), Observable.ambArray());
}
@SuppressWarnings("unchecked")
@Test
public void ambArraySingleElement() {
assertSame(Observable.never(), Observable.ambArray(Observable.never()));
}
@Test
public void manySources() {
Observable<?>[] a = new Observable[32];
Arrays.fill(a, Observable.never());
a[31] = Observable.just(1);
Observable.amb(Arrays.asList(a))
.test()
.assertResult(1);
}
@Test
public void emptyIterable() {
Observable.amb(Collections.<Observable<Integer>>emptyList())
.test()
.assertResult();
}
@Test
public void singleIterable() {
Observable.amb(Collections.singletonList(Observable.just(1)))
.test()
.assertResult(1);
}
@SuppressWarnings("unchecked")
@Test
public void disposed() {
TestHelper.checkDisposed(Observable.ambArray(Observable.never(), Observable.never()));
}
@Test
public void onNextRace() {
for (int i = 0; i < 500; i++) {
final PublishSubject<Integer> ps1 = PublishSubject.create();
final PublishSubject<Integer> ps2 = PublishSubject.create();
@SuppressWarnings("unchecked")
TestObserver<Integer> to = Observable.ambArray(ps1, ps2).test();
Runnable r1 = new Runnable() {
@Override
public void run() {
ps1.onNext(1);
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
ps2.onNext(1);
}
};
TestHelper.race(r1, r2, Schedulers.single());
to.assertSubscribed().assertNoErrors()
.assertNotComplete().assertValueCount(1);
}
}
@Test
public void onCompleteRace() {
for (int i = 0; i < 500; i++) {
final PublishSubject<Integer> ps1 = PublishSubject.create();
final PublishSubject<Integer> ps2 = PublishSubject.create();
@SuppressWarnings("unchecked")
TestObserver<Integer> to = Observable.ambArray(ps1, ps2).test();
Runnable r1 = new Runnable() {
@Override
public void run() {
ps1.onComplete();
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
ps2.onComplete();
}
};
TestHelper.race(r1, r2, Schedulers.single());
to.assertResult();
}
}
@Test
public void onErrorRace() {
for (int i = 0; i < 500; i++) {
final PublishSubject<Integer> ps1 = PublishSubject.create();
final PublishSubject<Integer> ps2 = PublishSubject.create();
@SuppressWarnings("unchecked")
TestObserver<Integer> to = Observable.ambArray(ps1, ps2).test();
final Throwable ex = new TestException();
Runnable r1 = new Runnable() {
@Override
public void run() {
ps1.onError(ex);
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
ps2.onError(ex);
}
};
List<Throwable> errors = TestHelper.trackPluginErrors();
try {
TestHelper.race(r1, r2, Schedulers.single());
} finally {
RxJavaPlugins.reset();
}
to.assertFailure(TestException.class);
if (!errors.isEmpty()) {
TestHelper.assertUndeliverable(errors, 0, TestException.class);
}
}
}
@Test
public void ambWithOrder() {
Observable<Integer> error = Observable.error(new RuntimeException());
Observable.just(1).ambWith(error).test().assertValue(1).assertComplete();
}
@SuppressWarnings("unchecked")
@Test
public void ambIterableOrder() {
Observable<Integer> error = Observable.error(new RuntimeException());
Observable.amb(Arrays.asList(Observable.just(1), error)).test().assertValue(1).assertComplete();
}
@SuppressWarnings("unchecked")
@Test
public void ambArrayOrder() {
Observable<Integer> error = Observable.error(new RuntimeException());
Observable.ambArray(Observable.just(1), error).test().assertValue(1).assertComplete();
}
}