/**
* 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.flowable;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.*;
import org.mockito.InOrder;
import org.reactivestreams.*;
import io.reactivex.*;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.exceptions.TestException;
import io.reactivex.functions.*;
import io.reactivex.internal.util.CrashingMappedIterable;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.processors.PublishProcessor;
import io.reactivex.schedulers.*;
import io.reactivex.subscribers.*;
public class FlowableAmbTest {
private TestScheduler scheduler;
private Scheduler.Worker innerScheduler;
@Before
public void setUp() {
scheduler = new TestScheduler();
innerScheduler = scheduler.createWorker();
}
private Flowable<String> createFlowable(final String[] values,
final long interval, final Throwable e) {
return Flowable.unsafeCreate(new Publisher<String>() {
@Override
public void subscribe(final Subscriber<? super String> subscriber) {
final CompositeDisposable parentSubscription = new CompositeDisposable();
subscriber.onSubscribe(new Subscription() {
@Override
public void request(long n) {
}
@Override
public void cancel() {
parentSubscription.dispose();
}
});
long delay = interval;
for (final String value : values) {
parentSubscription.add(innerScheduler.schedule(new Runnable() {
@Override
public void run() {
subscriber.onNext(value);
}
}
, delay, TimeUnit.MILLISECONDS));
delay += interval;
}
parentSubscription.add(innerScheduler.schedule(new Runnable() {
@Override
public void run() {
if (e == null) {
subscriber.onComplete();
} else {
subscriber.onError(e);
}
}
}, delay, TimeUnit.MILLISECONDS));
}
});
}
@Test
public void testAmb() {
Flowable<String> Flowable1 = createFlowable(new String[] {
"1", "11", "111", "1111" }, 2000, null);
Flowable<String> Flowable2 = createFlowable(new String[] {
"2", "22", "222", "2222" }, 1000, null);
Flowable<String> Flowable3 = createFlowable(new String[] {
"3", "33", "333", "3333" }, 3000, null);
@SuppressWarnings("unchecked")
Flowable<String> o = Flowable.ambArray(Flowable1,
Flowable2, Flowable3);
@SuppressWarnings("unchecked")
DefaultSubscriber<String> observer = mock(DefaultSubscriber.class);
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");
Flowable<String> Flowable1 = createFlowable(new String[] {},
2000, new IOException("fake exception"));
Flowable<String> Flowable2 = createFlowable(new String[] {
"2", "22", "222", "2222" }, 1000, expectedException);
Flowable<String> Flowable3 = createFlowable(new String[] {},
3000, new IOException("fake exception"));
@SuppressWarnings("unchecked")
Flowable<String> o = Flowable.ambArray(Flowable1,
Flowable2, Flowable3);
@SuppressWarnings("unchecked")
DefaultSubscriber<String> observer = mock(DefaultSubscriber.class);
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() {
Flowable<String> Flowable1 = createFlowable(new String[] {
"1" }, 2000, null);
Flowable<String> Flowable2 = createFlowable(new String[] {},
1000, null);
Flowable<String> Flowable3 = createFlowable(new String[] {
"3" }, 3000, null);
@SuppressWarnings("unchecked")
Flowable<String> o = Flowable.ambArray(Flowable1,
Flowable2, Flowable3);
@SuppressWarnings("unchecked")
DefaultSubscriber<String> observer = mock(DefaultSubscriber.class);
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 testProducerRequestThroughAmb() {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L);
ts.request(3);
final AtomicLong requested1 = new AtomicLong();
final AtomicLong requested2 = new AtomicLong();
Flowable<Integer> o1 = Flowable.unsafeCreate(new Publisher<Integer>() {
@Override
public void subscribe(Subscriber<? super Integer> s) {
s.onSubscribe(new Subscription() {
@Override
public void request(long n) {
System.out.println("1-requested: " + n);
requested1.set(n);
}
@Override
public void cancel() {
}
});
}
});
Flowable<Integer> o2 = Flowable.unsafeCreate(new Publisher<Integer>() {
@Override
public void subscribe(Subscriber<? super Integer> s) {
s.onSubscribe(new Subscription() {
@Override
public void request(long n) {
System.out.println("2-requested: " + n);
requested2.set(n);
}
@Override
public void cancel() {
}
});
}
});
Flowable.ambArray(o1, o2).subscribe(ts);
assertEquals(3, requested1.get());
assertEquals(3, requested2.get());
}
@Test
public void testBackpressure() {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
Flowable.range(0, Flowable.bufferSize() * 2)
.ambWith(Flowable.range(0, Flowable.bufferSize() * 2))
.observeOn(Schedulers.computation()) // observeOn has a backpressured RxRingBuffer
.delay(1, TimeUnit.MICROSECONDS) // make it a slightly slow consumer
.subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(Flowable.bufferSize() * 2, ts.values().size());
}
@SuppressWarnings("unchecked")
@Test
public void testSubscriptionOnlyHappensOnce() throws InterruptedException {
final AtomicLong count = new AtomicLong();
Consumer<Subscription> incrementer = new Consumer<Subscription>() {
@Override
public void accept(Subscription s) {
count.incrementAndGet();
}
};
//this aync stream should emit first
Flowable<Integer> o1 = Flowable.just(1).doOnSubscribe(incrementer)
.delay(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation());
//this stream emits second
Flowable<Integer> o2 = Flowable.just(1).doOnSubscribe(incrementer)
.delay(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation());
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
Flowable.ambArray(o1, o2).subscribe(ts);
ts.request(1);
ts.awaitTerminalEvent(5, TimeUnit.SECONDS);
ts.assertNoErrors();
assertEquals(2, count.get());
}
@SuppressWarnings("unchecked")
@Test
public void testSecondaryRequestsPropagatedToChildren() throws InterruptedException {
//this aync stream should emit first
Flowable<Integer> o1 = Flowable.fromArray(1, 2, 3)
.delay(100, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation());
//this stream emits second
Flowable<Integer> o2 = Flowable.fromArray(4, 5, 6)
.delay(200, TimeUnit.MILLISECONDS).subscribeOn(Schedulers.computation());
TestSubscriber<Integer> ts = new TestSubscriber<Integer>(1L);
Flowable.ambArray(o1, o2).subscribe(ts);
// before first emission request 20 more
// this request should suffice to emit all
ts.request(20);
//ensure stream does not hang
ts.awaitTerminalEvent(5, TimeUnit.SECONDS);
ts.assertNoErrors();
}
@Test
public void testSynchronousSources() {
// under async subscription the second Flowable would complete before
// the first but because this is a synchronous subscription to sources
// then second Flowable does not get subscribed to before first
// subscription completes hence first Flowable emits result through
// amb
int result = Flowable.just(1).doOnNext(new Consumer<Integer>() {
@Override
public void accept(Integer t) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//
}
}
}).ambWith(Flowable.just(2)).blockingSingle();
assertEquals(1, result);
}
@SuppressWarnings("unchecked")
@Test
public void testAmbCancelsOthers() {
PublishProcessor<Integer> source1 = PublishProcessor.create();
PublishProcessor<Integer> source2 = PublishProcessor.create();
PublishProcessor<Integer> source3 = PublishProcessor.create();
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
Flowable.ambArray(source1, source2, source3).subscribe(ts);
assertTrue("Source 1 doesn't have subscribers!", source1.hasSubscribers());
assertTrue("Source 2 doesn't have subscribers!", source2.hasSubscribers());
assertTrue("Source 3 doesn't have subscribers!", source3.hasSubscribers());
source1.onNext(1);
assertTrue("Source 1 doesn't have subscribers!", source1.hasSubscribers());
assertFalse("Source 2 still has subscribers!", source2.hasSubscribers());
assertFalse("Source 2 still has subscribers!", source3.hasSubscribers());
}
@Test(timeout = 1000)
public void testMultipleUse() {
TestSubscriber<Long> ts1 = new TestSubscriber<Long>();
TestSubscriber<Long> ts2 = new TestSubscriber<Long>();
Flowable<Long> amb = Flowable.timer(100, TimeUnit.MILLISECONDS).ambWith(Flowable.timer(200, TimeUnit.MILLISECONDS));
amb.subscribe(ts1);
amb.subscribe(ts2);
ts1.awaitTerminalEvent();
ts2.awaitTerminalEvent();
ts1.assertValue(0L);
ts1.assertComplete();
ts1.assertNoErrors();
ts2.assertValue(0L);
ts2.assertComplete();
ts2.assertNoErrors();
}
@SuppressWarnings("unchecked")
@Test
public void ambIterable() {
PublishProcessor<Integer> ps1 = PublishProcessor.create();
PublishProcessor<Integer> ps2 = PublishProcessor.create();
TestSubscriber<Integer> ts = TestSubscriber.create();
Flowable.amb(Arrays.asList(ps1, ps2)).subscribe(ts);
ts.assertNoValues();
ps1.onNext(1);
ps1.onComplete();
assertFalse(ps1.hasSubscribers());
assertFalse(ps2.hasSubscribers());
ts.assertValue(1);
ts.assertNoErrors();
ts.assertComplete();
}
@SuppressWarnings("unchecked")
@Test
public void ambIterable2() {
PublishProcessor<Integer> ps1 = PublishProcessor.create();
PublishProcessor<Integer> ps2 = PublishProcessor.create();
TestSubscriber<Integer> ts = TestSubscriber.create();
Flowable.amb(Arrays.asList(ps1, ps2)).subscribe(ts);
ts.assertNoValues();
ps2.onNext(2);
ps2.onComplete();
assertFalse(ps1.hasSubscribers());
assertFalse(ps2.hasSubscribers());
ts.assertValue(2);
ts.assertNoErrors();
ts.assertComplete();
}
@Ignore("No 2-9 arg overloads")
@SuppressWarnings("unchecked")
@Test
public void ambMany() throws Exception {
for (int i = 2; i < 10; i++) {
Class<?>[] clazz = new Class[i];
Arrays.fill(clazz, Publisher.class);
PublishProcessor<Integer>[] ps = new PublishProcessor[i];
for (int j = 0; j < i; j++) {
for (int k = 0; k < i; k++) {
ps[k] = PublishProcessor.create();
}
Method m = Flowable.class.getMethod("amb", clazz);
Flowable<Integer> obs = (Flowable<Integer>)m.invoke(null, (Object[])ps);
TestSubscriber<Integer> ts = TestSubscriber.create();
obs.subscribe(ts);
for (int k = 0; k < i; k++) {
assertTrue("@" + i + "/" + k + " has no observers?", ps[k].hasSubscribers());
}
ps[j].onNext(j);
ps[j].onComplete();
for (int k = 0; k < i; k++) {
assertFalse("@" + i + "/" + k + " has observers?", ps[k].hasSubscribers());
}
ts.assertValue(j);
ts.assertNoErrors();
ts.assertComplete();
}
}
}
@Ignore("No 2-9 arg overloads")
@SuppressWarnings("unchecked")
@Test
public void ambManyError() throws Exception {
for (int i = 2; i < 10; i++) {
Class<?>[] clazz = new Class[i];
Arrays.fill(clazz, Publisher.class);
PublishProcessor<Integer>[] ps = new PublishProcessor[i];
for (int j = 0; j < i; j++) {
for (int k = 0; k < i; k++) {
ps[k] = PublishProcessor.create();
}
Method m = Flowable.class.getMethod("amb", clazz);
Flowable<Integer> obs = (Flowable<Integer>)m.invoke(null, (Object[])ps);
TestSubscriber<Integer> ts = TestSubscriber.create();
obs.subscribe(ts);
for (int k = 0; k < i; k++) {
assertTrue("@" + i + "/" + k + " has no observers?", ps[k].hasSubscribers());
}
ps[j].onError(new TestException(Integer.toString(j)));
for (int k = 0; k < i; k++) {
assertFalse("@" + i + "/" + k + " has observers?", ps[k].hasSubscribers());
}
ts.assertNoValues();
ts.assertError(TestException.class);
ts.assertNotComplete();
assertEquals(Integer.toString(j), ts.errors().get(0).getMessage());
}
}
}
@Ignore("No 2-9 arg overloads")
@SuppressWarnings("unchecked")
@Test
public void ambManyComplete() throws Exception {
for (int i = 2; i < 10; i++) {
Class<?>[] clazz = new Class[i];
Arrays.fill(clazz, Publisher.class);
PublishProcessor<Integer>[] ps = new PublishProcessor[i];
for (int j = 0; j < i; j++) {
for (int k = 0; k < i; k++) {
ps[k] = PublishProcessor.create();
}
Method m = Flowable.class.getMethod("amb", clazz);
Flowable<Integer> obs = (Flowable<Integer>)m.invoke(null, (Object[])ps);
TestSubscriber<Integer> ts = TestSubscriber.create();
obs.subscribe(ts);
for (int k = 0; k < i; k++) {
assertTrue("@" + i + "/" + k + " has no observers?", ps[k].hasSubscribers());
}
ps[j].onComplete();
for (int k = 0; k < i; k++) {
assertFalse("@" + i + "/" + k + " has observers?", ps[k].hasSubscribers());
}
ts.assertNoValues();
ts.assertNoErrors();
ts.assertComplete();
}
}
}
@SuppressWarnings("unchecked")
@Test
public void ambArrayEmpty() {
assertSame(Flowable.empty(), Flowable.ambArray());
}
@SuppressWarnings("unchecked")
@Test
public void ambArraySingleElement() {
assertSame(Flowable.never(), Flowable.ambArray(Flowable.never()));
}
@SuppressWarnings("unchecked")
@Test
public void disposed() {
TestHelper.checkDisposed(Flowable.ambArray(Flowable.never(), Flowable.never()));
}
@Test
public void manySources() {
Flowable<?>[] a = new Flowable[32];
Arrays.fill(a, Flowable.never());
a[31] = Flowable.just(1);
Flowable.amb(Arrays.asList(a))
.test()
.assertResult(1);
}
@Test
public void emptyIterable() {
Flowable.amb(Collections.<Flowable<Integer>>emptyList())
.test()
.assertResult();
}
@Test
public void singleIterable() {
Flowable.amb(Collections.singletonList(Flowable.just(1)))
.test()
.assertResult(1);
}
@Test
public void onNextRace() {
for (int i = 0; i < 500; i++) {
final PublishProcessor<Integer> ps1 = PublishProcessor.create();
final PublishProcessor<Integer> ps2 = PublishProcessor.create();
@SuppressWarnings("unchecked")
TestSubscriber<Integer> to = Flowable.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 PublishProcessor<Integer> ps1 = PublishProcessor.create();
final PublishProcessor<Integer> ps2 = PublishProcessor.create();
@SuppressWarnings("unchecked")
TestSubscriber<Integer> to = Flowable.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 PublishProcessor<Integer> ps1 = PublishProcessor.create();
final PublishProcessor<Integer> ps2 = PublishProcessor.create();
@SuppressWarnings("unchecked")
TestSubscriber<Integer> to = Flowable.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);
}
}
}
@SuppressWarnings("unchecked")
@Test
public void nullIterableElement() {
Flowable.amb(Arrays.asList(Flowable.never(), null, Flowable.never()))
.test()
.assertFailure(NullPointerException.class);
}
@Test
public void iteratorThrows() {
Flowable.amb(new CrashingMappedIterable<Flowable<Integer>>(1, 100, 100, new Function<Integer, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Integer v) throws Exception {
return Flowable.never();
}
}))
.test()
.assertFailureAndMessage(TestException.class, "iterator()");
Flowable.amb(new CrashingMappedIterable<Flowable<Integer>>(100, 1, 100, new Function<Integer, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Integer v) throws Exception {
return Flowable.never();
}
}))
.test()
.assertFailureAndMessage(TestException.class, "hasNext()");
Flowable.amb(new CrashingMappedIterable<Flowable<Integer>>(100, 100, 1, new Function<Integer, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Integer v) throws Exception {
return Flowable.never();
}
}))
.test()
.assertFailureAndMessage(TestException.class, "next()");
}
@Test
public void ambWithOrder() {
Flowable<Integer> error = Flowable.error(new RuntimeException());
Flowable.just(1).ambWith(error).test().assertValue(1).assertComplete();
}
@SuppressWarnings("unchecked")
@Test
public void ambIterableOrder() {
Flowable<Integer> error = Flowable.error(new RuntimeException());
Flowable.amb(Arrays.asList(Flowable.just(1), error)).test().assertValue(1).assertComplete();
}
@SuppressWarnings("unchecked")
@Test
public void ambArrayOrder() {
Flowable<Integer> error = Flowable.error(new RuntimeException());
Flowable.ambArray(Flowable.just(1), error).test().assertValue(1).assertComplete();
}
}