/**
* 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 java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import org.junit.Test;
import org.reactivestreams.*;
import io.reactivex.*;
import io.reactivex.disposables.Disposable;
import io.reactivex.exceptions.*;
import io.reactivex.flowables.ConnectableFlowable;
import io.reactivex.functions.*;
import io.reactivex.internal.functions.Functions;
import io.reactivex.internal.fuseable.HasUpstreamPublisher;
import io.reactivex.internal.schedulers.ImmediateThinScheduler;
import io.reactivex.internal.subscriptions.BooleanSubscription;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.processors.PublishProcessor;
import io.reactivex.schedulers.*;
import io.reactivex.subscribers.TestSubscriber;
public class FlowablePublishTest {
@Test
public void testPublish() throws InterruptedException {
final AtomicInteger counter = new AtomicInteger();
ConnectableFlowable<String> o = Flowable.unsafeCreate(new Publisher<String>() {
@Override
public void subscribe(final Subscriber<? super String> observer) {
observer.onSubscribe(new BooleanSubscription());
new Thread(new Runnable() {
@Override
public void run() {
counter.incrementAndGet();
observer.onNext("one");
observer.onComplete();
}
}).start();
}
}).publish();
final CountDownLatch latch = new CountDownLatch(2);
// subscribe once
o.subscribe(new Consumer<String>() {
@Override
public void accept(String v) {
assertEquals("one", v);
latch.countDown();
}
});
// subscribe again
o.subscribe(new Consumer<String>() {
@Override
public void accept(String v) {
assertEquals("one", v);
latch.countDown();
}
});
Disposable s = o.connect();
try {
if (!latch.await(1000, TimeUnit.MILLISECONDS)) {
fail("subscriptions did not receive values");
}
assertEquals(1, counter.get());
} finally {
s.dispose();
}
}
@Test
public void testBackpressureFastSlow() {
ConnectableFlowable<Integer> is = Flowable.range(1, Flowable.bufferSize() * 2).publish();
Flowable<Integer> fast = is.observeOn(Schedulers.computation())
.doOnComplete(new Action() {
@Override
public void run() {
System.out.println("^^^^^^^^^^^^^ completed FAST");
}
});
Flowable<Integer> slow = is.observeOn(Schedulers.computation()).map(new Function<Integer, Integer>() {
int c;
@Override
public Integer apply(Integer i) {
if (c == 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
c++;
return i;
}
}).doOnComplete(new Action() {
@Override
public void run() {
System.out.println("^^^^^^^^^^^^^ completed SLOW");
}
});
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
Flowable.merge(fast, slow).subscribe(ts);
is.connect();
ts.awaitTerminalEvent();
ts.assertNoErrors();
assertEquals(Flowable.bufferSize() * 4, ts.valueCount());
}
// use case from https://github.com/ReactiveX/RxJava/issues/1732
@Test
public void testTakeUntilWithPublishedStreamUsingSelector() {
final AtomicInteger emitted = new AtomicInteger();
Flowable<Integer> xs = Flowable.range(0, Flowable.bufferSize() * 2).doOnNext(new Consumer<Integer>() {
@Override
public void accept(Integer t1) {
emitted.incrementAndGet();
}
});
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
xs.publish(new Function<Flowable<Integer>, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Flowable<Integer> xs) {
return xs.takeUntil(xs.skipWhile(new Predicate<Integer>() {
@Override
public boolean test(Integer i) {
return i <= 3;
}
}));
}
}).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
ts.assertValues(0, 1, 2, 3);
assertEquals(5, emitted.get());
System.out.println(ts.values());
}
// use case from https://github.com/ReactiveX/RxJava/issues/1732
@Test
public void testTakeUntilWithPublishedStream() {
Flowable<Integer> xs = Flowable.range(0, Flowable.bufferSize() * 2);
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
ConnectableFlowable<Integer> xsp = xs.publish();
xsp.takeUntil(xsp.skipWhile(new Predicate<Integer>() {
@Override
public boolean test(Integer i) {
return i <= 3;
}
})).subscribe(ts);
xsp.connect();
System.out.println(ts.values());
}
@Test(timeout = 10000)
public void testBackpressureTwoConsumers() {
final AtomicInteger sourceEmission = new AtomicInteger();
final AtomicBoolean sourceUnsubscribed = new AtomicBoolean();
final Flowable<Integer> source = Flowable.range(1, 100)
.doOnNext(new Consumer<Integer>() {
@Override
public void accept(Integer t1) {
sourceEmission.incrementAndGet();
}
})
.doOnCancel(new Action() {
@Override
public void run() {
sourceUnsubscribed.set(true);
}
}).share();
;
final AtomicBoolean child1Unsubscribed = new AtomicBoolean();
final AtomicBoolean child2Unsubscribed = new AtomicBoolean();
final TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>();
final TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>() {
@Override
public void onNext(Integer t) {
if (valueCount() == 2) {
source.doOnCancel(new Action() {
@Override
public void run() {
child2Unsubscribed.set(true);
}
}).take(5).subscribe(ts2);
}
super.onNext(t);
}
};
source.doOnCancel(new Action() {
@Override
public void run() {
child1Unsubscribed.set(true);
}
}).take(5)
.subscribe(ts1);
ts1.awaitTerminalEvent();
ts2.awaitTerminalEvent();
ts1.assertNoErrors();
ts2.assertNoErrors();
assertTrue(sourceUnsubscribed.get());
assertTrue(child1Unsubscribed.get());
assertTrue(child2Unsubscribed.get());
ts1.assertValues(1, 2, 3, 4, 5);
ts2.assertValues(4, 5, 6, 7, 8);
assertEquals(8, sourceEmission.get());
}
@Test
public void testConnectWithNoSubscriber() {
TestScheduler scheduler = new TestScheduler();
ConnectableFlowable<Long> co = Flowable.interval(10, 10, TimeUnit.MILLISECONDS, scheduler).take(3).publish();
co.connect();
// Emit 0
scheduler.advanceTimeBy(15, TimeUnit.MILLISECONDS);
TestSubscriber<Long> subscriber = new TestSubscriber<Long>();
co.subscribe(subscriber);
// Emit 1 and 2
scheduler.advanceTimeBy(50, TimeUnit.MILLISECONDS);
subscriber.assertValues(1L, 2L);
subscriber.assertNoErrors();
subscriber.assertTerminated();
}
@Test
public void testSubscribeAfterDisconnectThenConnect() {
ConnectableFlowable<Integer> source = Flowable.just(1).publish();
TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>();
source.subscribe(ts1);
Disposable s = source.connect();
ts1.assertValue(1);
ts1.assertNoErrors();
ts1.assertTerminated();
TestSubscriber<Integer> ts2 = new TestSubscriber<Integer>();
source.subscribe(ts2);
Disposable s2 = source.connect();
ts2.assertValue(1);
ts2.assertNoErrors();
ts2.assertTerminated();
System.out.println(s);
System.out.println(s2);
}
@Test
public void testNoSubscriberRetentionOnCompleted() {
FlowablePublish<Integer> source = (FlowablePublish<Integer>)Flowable.just(1).publish();
TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>();
source.subscribe(ts1);
ts1.assertNoValues();
ts1.assertNoErrors();
ts1.assertNotComplete();
source.connect();
ts1.assertValue(1);
ts1.assertNoErrors();
ts1.assertTerminated();
assertNull(source.current.get());
}
@Test
public void testNonNullConnection() {
ConnectableFlowable<Object> source = Flowable.never().publish();
assertNotNull(source.connect());
assertNotNull(source.connect());
}
@Test
public void testNoDisconnectSomeoneElse() {
ConnectableFlowable<Object> source = Flowable.never().publish();
Disposable s1 = source.connect();
Disposable s2 = source.connect();
s1.dispose();
Disposable s3 = source.connect();
s2.dispose();
assertTrue(checkPublishDisposed(s1));
assertTrue(checkPublishDisposed(s2));
assertFalse(checkPublishDisposed(s3));
}
@SuppressWarnings("unchecked")
static boolean checkPublishDisposed(Disposable d) {
return ((FlowablePublish.PublishSubscriber<Object>)d).isDisposed();
}
@Test
public void testZeroRequested() {
ConnectableFlowable<Integer> source = Flowable.just(1).publish();
TestSubscriber<Integer> ts = new TestSubscriber<Integer>(0L);
source.subscribe(ts);
ts.assertNoValues();
ts.assertNoErrors();
ts.assertNotComplete();
source.connect();
ts.assertNoValues();
ts.assertNoErrors();
ts.assertNotComplete();
ts.request(5);
ts.assertValue(1);
ts.assertNoErrors();
ts.assertTerminated();
}
@Test
public void testConnectIsIdempotent() {
final AtomicInteger calls = new AtomicInteger();
Flowable<Integer> source = Flowable.unsafeCreate(new Publisher<Integer>() {
@Override
public void subscribe(Subscriber<? super Integer> t) {
t.onSubscribe(new BooleanSubscription());
calls.getAndIncrement();
}
});
ConnectableFlowable<Integer> conn = source.publish();
assertEquals(0, calls.get());
conn.connect();
conn.connect();
assertEquals(1, calls.get());
conn.connect().dispose();
conn.connect();
conn.connect();
assertEquals(2, calls.get());
}
@Test
public void syncFusedObserveOn() {
ConnectableFlowable<Integer> co = Flowable.range(0, 1000).publish();
Flowable<Integer> obs = co.observeOn(Schedulers.computation());
for (int i = 0; i < 1000; i++) {
for (int j = 1; j < 6; j++) {
List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>();
for (int k = 1; k < j; k++) {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
tss.add(ts);
obs.subscribe(ts);
}
Disposable s = co.connect();
for (TestSubscriber<Integer> ts : tss) {
ts.awaitDone(5, TimeUnit.SECONDS)
.assertSubscribed()
.assertValueCount(1000)
.assertNoErrors()
.assertComplete();
}
s.dispose();
}
}
}
@Test
public void syncFusedObserveOn2() {
ConnectableFlowable<Integer> co = Flowable.range(0, 1000).publish();
Flowable<Integer> obs = co.observeOn(ImmediateThinScheduler.INSTANCE);
for (int i = 0; i < 1000; i++) {
for (int j = 1; j < 6; j++) {
List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>();
for (int k = 1; k < j; k++) {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
tss.add(ts);
obs.subscribe(ts);
}
Disposable s = co.connect();
for (TestSubscriber<Integer> ts : tss) {
ts.awaitDone(5, TimeUnit.SECONDS)
.assertSubscribed()
.assertValueCount(1000)
.assertNoErrors()
.assertComplete();
}
s.dispose();
}
}
}
@Test
public void asyncFusedObserveOn() {
ConnectableFlowable<Integer> co = Flowable.range(0, 1000).observeOn(ImmediateThinScheduler.INSTANCE).publish();
for (int i = 0; i < 1000; i++) {
for (int j = 1; j < 6; j++) {
List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>();
for (int k = 1; k < j; k++) {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
tss.add(ts);
co.subscribe(ts);
}
Disposable s = co.connect();
for (TestSubscriber<Integer> ts : tss) {
ts.awaitDone(5, TimeUnit.SECONDS)
.assertSubscribed()
.assertValueCount(1000)
.assertNoErrors()
.assertComplete();
}
s.dispose();
}
}
}
@Test
public void testObserveOn() {
ConnectableFlowable<Integer> co = Flowable.range(0, 1000).hide().publish();
Flowable<Integer> obs = co.observeOn(Schedulers.computation());
for (int i = 0; i < 1000; i++) {
for (int j = 1; j < 6; j++) {
List<TestSubscriber<Integer>> tss = new ArrayList<TestSubscriber<Integer>>();
for (int k = 1; k < j; k++) {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
tss.add(ts);
obs.subscribe(ts);
}
Disposable s = co.connect();
for (TestSubscriber<Integer> ts : tss) {
ts.awaitDone(5, TimeUnit.SECONDS)
.assertSubscribed()
.assertValueCount(1000)
.assertNoErrors()
.assertComplete();
}
s.dispose();
}
}
}
@Test
public void source() {
Flowable<Integer> o = Flowable.never();
assertSame(o, (((HasUpstreamPublisher<?>)o.publish()).source()));
}
@Test
public void connectThrows() {
ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish();
try {
co.connect(new Consumer<Disposable>() {
@Override
public void accept(Disposable s) throws Exception {
throw new TestException();
}
});
} catch (TestException ex) {
// expected
}
}
@Test
public void addRemoveRace() {
for (int i = 0; i < 500; i++) {
final ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish();
final TestSubscriber<Integer> to = co.test();
final TestSubscriber<Integer> to2 = new TestSubscriber<Integer>();
Runnable r1 = new Runnable() {
@Override
public void run() {
co.subscribe(to2);
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
to.cancel();
}
};
TestHelper.race(r1, r2);
}
}
@Test
public void disposeOnArrival() {
ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish();
co.test(Long.MAX_VALUE, true).assertEmpty();
}
@Test
public void disposeOnArrival2() {
Flowable<Integer> co = Flowable.<Integer>never().publish().autoConnect();
co.test(Long.MAX_VALUE, true).assertEmpty();
}
@Test
public void dispose() {
TestHelper.checkDisposed(Flowable.never().publish());
TestHelper.checkDisposed(Flowable.never().publish(Functions.<Flowable<Object>>identity()));
}
@Test
public void empty() {
ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish();
co.connect();
}
@Test
public void take() {
ConnectableFlowable<Integer> co = Flowable.range(1, 2).publish();
TestSubscriber<Integer> to = co.take(1).test();
co.connect();
to.assertResult(1);
}
@Test
public void just() {
final PublishProcessor<Integer> ps = PublishProcessor.create();
ConnectableFlowable<Integer> co = ps.publish();
TestSubscriber<Integer> to = new TestSubscriber<Integer>() {
@Override
public void onNext(Integer t) {
super.onNext(t);
ps.onComplete();
}
};
co.subscribe(to);
co.connect();
ps.onNext(1);
to.assertResult(1);
}
@Test
public void nextCancelRace() {
for (int i = 0; i < 500; i++) {
final PublishProcessor<Integer> ps = PublishProcessor.create();
final ConnectableFlowable<Integer> co = ps.publish();
final TestSubscriber<Integer> to = co.test();
Runnable r1 = new Runnable() {
@Override
public void run() {
ps.onNext(1);
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
to.cancel();
}
};
TestHelper.race(r1, r2);
}
}
@Test
public void badSource() {
List<Throwable> errors = TestHelper.trackPluginErrors();
try {
new Flowable<Integer>() {
@Override
protected void subscribeActual(Subscriber<? super Integer> observer) {
observer.onSubscribe(new BooleanSubscription());
observer.onNext(1);
observer.onComplete();
observer.onNext(2);
observer.onError(new TestException());
observer.onComplete();
}
}
.publish()
.autoConnect()
.test()
.assertResult(1);
TestHelper.assertUndeliverable(errors, 0, TestException.class);
} finally {
RxJavaPlugins.reset();
}
}
@Test
public void noErrorLoss() {
List<Throwable> errors = TestHelper.trackPluginErrors();
try {
ConnectableFlowable<Object> co = Flowable.error(new TestException()).publish();
co.connect();
TestHelper.assertUndeliverable(errors, 0, TestException.class);
} finally {
RxJavaPlugins.reset();
}
}
@Test
public void subscribeDisconnectRace() {
for (int i = 0; i < 500; i++) {
final PublishProcessor<Integer> ps = PublishProcessor.create();
final ConnectableFlowable<Integer> co = ps.publish();
final Disposable d = co.connect();
final TestSubscriber<Integer> to = new TestSubscriber<Integer>();
Runnable r1 = new Runnable() {
@Override
public void run() {
d.dispose();
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
co.subscribe(to);
}
};
TestHelper.race(r1, r2);
}
}
@Test
public void selectorDisconnectsIndependentSource() {
PublishProcessor<Integer> ps = PublishProcessor.create();
ps.publish(new Function<Flowable<Integer>, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Flowable<Integer> v) throws Exception {
return Flowable.range(1, 2);
}
})
.test()
.assertResult(1, 2);
assertFalse(ps.hasSubscribers());
}
@Test(timeout = 5000)
public void selectorLatecommer() {
Flowable.range(1, 5)
.publish(new Function<Flowable<Integer>, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Flowable<Integer> v) throws Exception {
return v.concatWith(v);
}
})
.test()
.assertResult(1, 2, 3, 4, 5);
}
@Test
public void mainError() {
Flowable.error(new TestException())
.publish(Functions.<Flowable<Object>>identity())
.test()
.assertFailure(TestException.class);
}
@Test
public void selectorInnerError() {
PublishProcessor<Integer> ps = PublishProcessor.create();
ps.publish(new Function<Flowable<Integer>, Flowable<Integer>>() {
@Override
public Flowable<Integer> apply(Flowable<Integer> v) throws Exception {
return Flowable.error(new TestException());
}
})
.test()
.assertFailure(TestException.class);
assertFalse(ps.hasSubscribers());
}
@Test
public void preNextConnect() {
for (int i = 0; i < 500; i++) {
final ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish();
co.connect();
Runnable r1 = new Runnable() {
@Override
public void run() {
co.test();
}
};
TestHelper.race(r1, r1);
}
}
@Test
public void connectRace() {
for (int i = 0; i < 500; i++) {
final ConnectableFlowable<Integer> co = Flowable.<Integer>empty().publish();
Runnable r1 = new Runnable() {
@Override
public void run() {
co.connect();
}
};
TestHelper.race(r1, r1);
}
}
@Test
public void selectorCrash() {
Flowable.just(1).publish(new Function<Flowable<Integer>, Flowable<Object>>() {
@Override
public Flowable<Object> apply(Flowable<Integer> v) throws Exception {
throw new TestException();
}
})
.test()
.assertFailure(TestException.class);
}
@Test
public void pollThrows() {
Flowable.just(1)
.map(new Function<Integer, Object>() {
@Override
public Object apply(Integer v) throws Exception {
throw new TestException();
}
})
.publish()
.autoConnect()
.test()
.assertFailure(TestException.class);
}
@Test
public void dryRunCrash() {
final TestSubscriber<Object> ts = new TestSubscriber<Object>(1L) {
@Override
public void onNext(Object t) {
super.onNext(t);
onComplete();
cancel();
}
};
Flowable.range(1, 10)
.map(new Function<Integer, Object>() {
@Override
public Object apply(Integer v) throws Exception {
if (v == 2) {
throw new TestException();
}
return v;
}
})
.publish()
.autoConnect()
.subscribe(ts);
ts
.assertResult(1);
}
@Test
public void overflowQueue() {
Flowable.create(new FlowableOnSubscribe<Object>() {
@Override
public void subscribe(FlowableEmitter<Object> s) throws Exception {
for (int i = 0; i < 10; i++) {
s.onNext(i);
}
}
}, BackpressureStrategy.MISSING)
.publish(8)
.autoConnect()
.test(0L)
.assertFailure(MissingBackpressureException.class);
}
@Test
public void delayedUpstreamOnSubscribe() {
final Subscriber<?>[] sub = { null };
new Flowable<Integer>() {
@Override
protected void subscribeActual(Subscriber<? super Integer> s) {
sub[0] = s;
}
}
.publish()
.connect()
.dispose();
BooleanSubscription bs = new BooleanSubscription();
sub[0].onSubscribe(bs);
assertTrue(bs.isCancelled());
}
}