/**
* 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.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import io.reactivex.annotations.Nullable;
import org.junit.Test;
import org.mockito.InOrder;
import org.reactivestreams.*;
import io.reactivex.*;
import io.reactivex.exceptions.*;
import io.reactivex.functions.*;
import io.reactivex.internal.functions.Functions;
import io.reactivex.internal.fuseable.*;
import io.reactivex.internal.operators.flowable.FlowableObserveOn.BaseObserveOnSubscriber;
import io.reactivex.internal.schedulers.ImmediateThinScheduler;
import io.reactivex.internal.subscriptions.BooleanSubscription;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.processors.*;
import io.reactivex.schedulers.*;
import io.reactivex.subscribers.*;
public class FlowableObserveOnTest {
/**
* This is testing a no-op path since it uses Schedulers.immediate() which will not do scheduling.
*/
@Test
public void testObserveOn() {
Subscriber<Integer> observer = TestHelper.mockSubscriber();
Flowable.just(1, 2, 3).observeOn(ImmediateThinScheduler.INSTANCE).subscribe(observer);
verify(observer, times(1)).onNext(1);
verify(observer, times(1)).onNext(2);
verify(observer, times(1)).onNext(3);
verify(observer, times(1)).onComplete();
}
@Test
public void testOrdering() throws InterruptedException {
// Flowable<String> obs = Flowable.just("one", null, "two", "three", "four");
// FIXME null values not allowed
Flowable<String> obs = Flowable.just("one", "null", "two", "three", "four");
Subscriber<String> observer = TestHelper.mockSubscriber();
InOrder inOrder = inOrder(observer);
TestSubscriber<String> ts = new TestSubscriber<String>(observer);
obs.observeOn(Schedulers.computation()).subscribe(ts);
ts.awaitTerminalEvent(1000, TimeUnit.MILLISECONDS);
if (ts.errors().size() > 0) {
for (Throwable t : ts.errors()) {
t.printStackTrace();
}
fail("failed with exception");
}
inOrder.verify(observer, times(1)).onNext("one");
inOrder.verify(observer, times(1)).onNext("null");
inOrder.verify(observer, times(1)).onNext("two");
inOrder.verify(observer, times(1)).onNext("three");
inOrder.verify(observer, times(1)).onNext("four");
inOrder.verify(observer, times(1)).onComplete();
inOrder.verifyNoMoreInteractions();
}
@Test
public void testThreadName() throws InterruptedException {
System.out.println("Main Thread: " + Thread.currentThread().getName());
// FIXME null values not allowed
// Flowable<String> obs = Flowable.just("one", null, "two", "three", "four");
Flowable<String> obs = Flowable.just("one", "null", "two", "three", "four");
Subscriber<String> observer = TestHelper.mockSubscriber();
final String parentThreadName = Thread.currentThread().getName();
final CountDownLatch completedLatch = new CountDownLatch(1);
// assert subscribe is on main thread
obs = obs.doOnNext(new Consumer<String>() {
@Override
public void accept(String s) {
String threadName = Thread.currentThread().getName();
System.out.println("Source ThreadName: " + threadName + " Expected => " + parentThreadName);
assertEquals(parentThreadName, threadName);
}
});
// assert observe is on new thread
obs.observeOn(Schedulers.newThread()).doOnNext(new Consumer<String>() {
@Override
public void accept(String t1) {
String threadName = Thread.currentThread().getName();
boolean correctThreadName = threadName.startsWith("RxNewThreadScheduler");
System.out.println("ObserveOn ThreadName: " + threadName + " Correct => " + correctThreadName);
assertTrue(correctThreadName);
}
}).doAfterTerminate(new Action() {
@Override
public void run() {
completedLatch.countDown();
}
}).subscribe(observer);
if (!completedLatch.await(1000, TimeUnit.MILLISECONDS)) {
fail("timed out waiting");
}
verify(observer, never()).onError(any(Throwable.class));
verify(observer, times(5)).onNext(any(String.class));
verify(observer, times(1)).onComplete();
}
@Test
public void observeOnTheSameSchedulerTwice() {
Scheduler scheduler = ImmediateThinScheduler.INSTANCE;
Flowable<Integer> o = Flowable.just(1, 2, 3);
Flowable<Integer> o2 = o.observeOn(scheduler);
Subscriber<Object> observer1 = TestHelper.mockSubscriber();
Subscriber<Object> observer2 = TestHelper.mockSubscriber();
InOrder inOrder1 = inOrder(observer1);
InOrder inOrder2 = inOrder(observer2);
o2.subscribe(observer1);
o2.subscribe(observer2);
inOrder1.verify(observer1, times(1)).onNext(1);
inOrder1.verify(observer1, times(1)).onNext(2);
inOrder1.verify(observer1, times(1)).onNext(3);
inOrder1.verify(observer1, times(1)).onComplete();
verify(observer1, never()).onError(any(Throwable.class));
inOrder1.verifyNoMoreInteractions();
inOrder2.verify(observer2, times(1)).onNext(1);
inOrder2.verify(observer2, times(1)).onNext(2);
inOrder2.verify(observer2, times(1)).onNext(3);
inOrder2.verify(observer2, times(1)).onComplete();
verify(observer2, never()).onError(any(Throwable.class));
inOrder2.verifyNoMoreInteractions();
}
@Test
public void observeSameOnMultipleSchedulers() {
TestScheduler scheduler1 = new TestScheduler();
TestScheduler scheduler2 = new TestScheduler();
Flowable<Integer> o = Flowable.just(1, 2, 3);
Flowable<Integer> o1 = o.observeOn(scheduler1);
Flowable<Integer> o2 = o.observeOn(scheduler2);
Subscriber<Object> observer1 = TestHelper.mockSubscriber();
Subscriber<Object> observer2 = TestHelper.mockSubscriber();
InOrder inOrder1 = inOrder(observer1);
InOrder inOrder2 = inOrder(observer2);
o1.subscribe(observer1);
o2.subscribe(observer2);
scheduler1.advanceTimeBy(1, TimeUnit.SECONDS);
scheduler2.advanceTimeBy(1, TimeUnit.SECONDS);
inOrder1.verify(observer1, times(1)).onNext(1);
inOrder1.verify(observer1, times(1)).onNext(2);
inOrder1.verify(observer1, times(1)).onNext(3);
inOrder1.verify(observer1, times(1)).onComplete();
verify(observer1, never()).onError(any(Throwable.class));
inOrder1.verifyNoMoreInteractions();
inOrder2.verify(observer2, times(1)).onNext(1);
inOrder2.verify(observer2, times(1)).onNext(2);
inOrder2.verify(observer2, times(1)).onNext(3);
inOrder2.verify(observer2, times(1)).onComplete();
verify(observer2, never()).onError(any(Throwable.class));
inOrder2.verifyNoMoreInteractions();
}
/**
* Confirm that running on a NewThreadScheduler uses the same thread for the entire stream.
*/
@Test
public void testObserveOnWithNewThreadScheduler() {
final AtomicInteger count = new AtomicInteger();
final int _multiple = 99;
Flowable.range(1, 100000).map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t1) {
return t1 * _multiple;
}
}).observeOn(Schedulers.newThread())
.blockingForEach(new Consumer<Integer>() {
@Override
public void accept(Integer t1) {
assertEquals(count.incrementAndGet() * _multiple, t1.intValue());
// FIXME toBlocking methods run on the current thread
String name = Thread.currentThread().getName();
assertFalse("Wrong thread name: " + name, name.startsWith("Rx"));
}
});
}
/**
* Confirm that running on a ThreadPoolScheduler allows multiple threads but is still ordered.
*/
@Test
public void testObserveOnWithThreadPoolScheduler() {
final AtomicInteger count = new AtomicInteger();
final int _multiple = 99;
Flowable.range(1, 100000).map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t1) {
return t1 * _multiple;
}
}).observeOn(Schedulers.computation())
.blockingForEach(new Consumer<Integer>() {
@Override
public void accept(Integer t1) {
assertEquals(count.incrementAndGet() * _multiple, t1.intValue());
// FIXME toBlocking methods run on the caller's thread
String name = Thread.currentThread().getName();
assertFalse("Wrong thread name: " + name, name.startsWith("Rx"));
}
});
}
/**
* Attempts to confirm that when pauses exist between events, the ScheduledObserver
* does not lose or reorder any events since the scheduler will not block, but will
* be re-scheduled when it receives new events after each pause.
*
*
* This is non-deterministic in proving success, but if it ever fails (non-deterministically)
* it is a sign of potential issues as thread-races and scheduling should not affect output.
*/
@Test
public void testObserveOnOrderingConcurrency() {
final AtomicInteger count = new AtomicInteger();
final int _multiple = 99;
Flowable.range(1, 10000).map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t1) {
if (randomIntFrom0to100() > 98) {
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return t1 * _multiple;
}
}).observeOn(Schedulers.computation())
.blockingForEach(new Consumer<Integer>() {
@Override
public void accept(Integer t1) {
assertEquals(count.incrementAndGet() * _multiple, t1.intValue());
// assertTrue(name.startsWith("RxComputationThreadPool"));
// FIXME toBlocking now runs its methods on the caller thread
String name = Thread.currentThread().getName();
assertFalse("Wrong thread name: " + name, name.startsWith("Rx"));
}
});
}
@Test
public void testNonBlockingOuterWhileBlockingOnNext() throws InterruptedException {
final CountDownLatch completedLatch = new CountDownLatch(1);
final CountDownLatch nextLatch = new CountDownLatch(1);
final AtomicLong completeTime = new AtomicLong();
// use subscribeOn to make async, observeOn to move
Flowable.range(1, 2).subscribeOn(Schedulers.newThread()).observeOn(Schedulers.newThread()).subscribe(new DefaultSubscriber<Integer>() {
@Override
public void onComplete() {
System.out.println("onComplete");
completeTime.set(System.nanoTime());
completedLatch.countDown();
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Integer t) {
// don't let this thing finish yet
try {
if (!nextLatch.await(1000, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("it shouldn't have timed out");
}
} catch (InterruptedException e) {
throw new RuntimeException("it shouldn't have failed");
}
}
});
long afterSubscribeTime = System.nanoTime();
System.out.println("After subscribe: " + completedLatch.getCount());
assertEquals(1, completedLatch.getCount());
nextLatch.countDown();
completedLatch.await(1000, TimeUnit.MILLISECONDS);
assertTrue(completeTime.get() > afterSubscribeTime);
System.out.println("onComplete nanos after subscribe: " + (completeTime.get() - afterSubscribeTime));
}
private static int randomIntFrom0to100() {
// XORShift instead of Math.random http://javamex.com/tutorials/random_numbers/xorshift.shtml
long x = System.nanoTime();
x ^= (x << 21);
x ^= (x >>> 35);
x ^= (x << 4);
return Math.abs((int) x % 100);
}
@Test
public void testDelayedErrorDeliveryWhenSafeSubscriberUnsubscribes() {
TestScheduler testScheduler = new TestScheduler();
Flowable<Integer> source = Flowable.concat(Flowable.<Integer> error(new TestException()), Flowable.just(1));
@SuppressWarnings("unchecked")
DefaultSubscriber<Integer> o = mock(DefaultSubscriber.class);
InOrder inOrder = inOrder(o);
source.observeOn(testScheduler).subscribe(o);
inOrder.verify(o, never()).onError(any(TestException.class));
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS);
inOrder.verify(o).onError(any(TestException.class));
inOrder.verify(o, never()).onNext(anyInt());
inOrder.verify(o, never()).onComplete();
}
@Test
public void testAfterUnsubscribeCalledThenObserverOnNextNeverCalled() {
final TestScheduler testScheduler = new TestScheduler();
final Subscriber<Integer> observer = TestHelper.mockSubscriber();
TestSubscriber<Integer> ts = new TestSubscriber<Integer>(observer);
Flowable.just(1, 2, 3)
.observeOn(testScheduler)
.subscribe(ts);
ts.dispose();
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS);
final InOrder inOrder = inOrder(observer);
inOrder.verify(observer, never()).onNext(anyInt());
inOrder.verify(observer, never()).onError(any(Exception.class));
inOrder.verify(observer, never()).onComplete();
}
@Test
public void testBackpressureWithTakeAfter() {
final AtomicInteger generated = new AtomicInteger();
Flowable<Integer> flowable = Flowable.fromIterable(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;
}
};
}
});
TestSubscriber<Integer> testSubscriber = new TestSubscriber<Integer>() {
@Override
public void onNext(Integer t) {
System.err.println("c t = " + t + " thread " + Thread.currentThread());
super.onNext(t);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
};
flowable
.observeOn(Schedulers.newThread())
.take(3)
.subscribe(testSubscriber);
testSubscriber.awaitTerminalEvent();
System.err.println(testSubscriber.values());
testSubscriber.assertValues(0, 1, 2);
// it should be between the take num and requested batch size across the async boundary
System.out.println("Generated: " + generated.get());
assertTrue(generated.get() >= 3 && generated.get() <= Flowable.bufferSize());
}
@Test
public void testBackpressureWithTakeAfterAndMultipleBatches() {
int numForBatches = Flowable.bufferSize() * 3 + 1; // should be 4 batches == ((3*n)+1) items
final AtomicInteger generated = new AtomicInteger();
Flowable<Integer> flowable = Flowable.fromIterable(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;
}
};
}
});
TestSubscriber<Integer> testSubscriber = new TestSubscriber<Integer>() {
@Override
public void onNext(Integer t) {
// System.err.println("c t = " + t + " thread " + Thread.currentThread());
super.onNext(t);
}
};
flowable
.observeOn(Schedulers.newThread())
.take(numForBatches)
.subscribe(testSubscriber);
testSubscriber.awaitTerminalEvent();
System.err.println(testSubscriber.values());
// it should be between the take num and requested batch size across the async boundary
System.out.println("Generated: " + generated.get());
assertTrue(generated.get() >= numForBatches && generated.get() <= numForBatches + Flowable.bufferSize());
}
@Test
public void testBackpressureWithTakeBefore() {
final AtomicInteger generated = new AtomicInteger();
Flowable<Integer> flowable = Flowable.fromIterable(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;
}
};
}
});
TestSubscriber<Integer> testSubscriber = new TestSubscriber<Integer>();
flowable
.take(7)
.observeOn(Schedulers.newThread())
.subscribe(testSubscriber);
testSubscriber.awaitTerminalEvent();
testSubscriber.assertValues(0, 1, 2, 3, 4, 5, 6);
assertEquals(7, generated.get());
}
@Test
public void testQueueFullEmitsError() {
final CountDownLatch latch = new CountDownLatch(1);
Flowable<Integer> flowable = Flowable.unsafeCreate(new Publisher<Integer>() {
@Override
public void subscribe(Subscriber<? super Integer> o) {
o.onSubscribe(new BooleanSubscription());
for (int i = 0; i < Flowable.bufferSize() + 10; i++) {
o.onNext(i);
}
latch.countDown();
o.onComplete();
}
});
TestSubscriber<Integer> testSubscriber = new TestSubscriber<Integer>(new DefaultSubscriber<Integer>() {
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Integer t) {
// force it to be slow and wait until we have queued everything
try {
latch.await(500, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
flowable.observeOn(Schedulers.newThread()).subscribe(testSubscriber);
testSubscriber.awaitTerminalEvent();
List<Throwable> errors = testSubscriber.errors();
assertEquals(1, errors.size());
System.out.println("Errors: " + errors);
Throwable t = errors.get(0);
if (t instanceof MissingBackpressureException) {
// success, we expect this
} else {
if (t.getCause() instanceof MissingBackpressureException) {
// this is also okay
} else {
fail("Expecting MissingBackpressureException");
}
}
}
@Test
public void testAsyncChild() {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
Flowable.range(0, 100000).observeOn(Schedulers.newThread()).observeOn(Schedulers.newThread()).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
}
@Test
public void testOnErrorCutsAheadOfOnNext() {
for (int i = 0; i < 50; i++) {
final PublishProcessor<Long> subject = PublishProcessor.create();
final AtomicLong counter = new AtomicLong();
TestSubscriber<Long> ts = new TestSubscriber<Long>(new DefaultSubscriber<Long>() {
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Long t) {
// simulate slow consumer to force backpressure failure
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
}
});
subject.observeOn(Schedulers.computation()).subscribe(ts);
// this will blow up with backpressure
while (counter.get() < 102400) {
subject.onNext(counter.get());
counter.incrementAndGet();
}
ts.awaitTerminalEvent();
assertEquals(1, ts.errors().size());
ts.assertError(MissingBackpressureException.class);
// assert that the values are sequential, that cutting in didn't allow skipping some but emitting others.
// example [0, 1, 2] not [0, 1, 4]
List<Long> onNextEvents = ts.values();
assertTrue(onNextEvents.isEmpty() || onNextEvents.size() == onNextEvents.get(onNextEvents.size() - 1) + 1);
// we should emit the error without emitting the full buffer size
assertTrue(onNextEvents.size() < Flowable.bufferSize());
}
}
/**
* Make sure we get a MissingBackpressureException propagated through when we have a fast temporal (hot) producer.
*/
@Test
public void testHotOperatorBackpressure() {
TestSubscriber<String> ts = new TestSubscriber<String>();
Flowable.interval(0, 1, TimeUnit.MICROSECONDS)
.observeOn(Schedulers.computation())
.map(new Function<Long, String>() {
@Override
public String apply(Long t1) {
System.out.println(t1);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
return t1 + " slow value";
}
}).subscribe(ts);
ts.awaitTerminalEvent();
System.out.println("Errors: " + ts.errors());
assertEquals(1, ts.errors().size());
assertEquals(MissingBackpressureException.class, ts.errors().get(0).getClass());
}
@Test
public void testErrorPropagatesWhenNoOutstandingRequests() {
Flowable<Long> timer = Flowable.interval(0, 1, TimeUnit.MICROSECONDS)
.doOnEach(new Consumer<Notification<Long>>() {
@Override
public void accept(Notification<Long> n) {
// System.out.println("BEFORE " + n);
}
})
.observeOn(Schedulers.newThread())
.doOnEach(new Consumer<Notification<Long>>() {
@Override
public void accept(Notification<Long> n) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
// System.out.println("AFTER " + n);
}
});
TestSubscriber<Long> ts = new TestSubscriber<Long>();
Flowable.combineLatest(timer, Flowable.<Integer> never(), new BiFunction<Long, Integer, Long>() {
@Override
public Long apply(Long t1, Integer t2) {
return t1;
}
}).take(Flowable.bufferSize() * 2).subscribe(ts);
ts.awaitTerminalEvent();
assertEquals(1, ts.errors().size());
assertEquals(MissingBackpressureException.class, ts.errors().get(0).getClass());
}
@Test
public void testRequestOverflow() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicInteger count = new AtomicInteger();
Flowable.range(1, 100).observeOn(Schedulers.computation())
.subscribe(new DefaultSubscriber<Integer>() {
boolean first = true;
@Override
public void onStart() {
request(2);
}
@Override
public void onComplete() {
latch.countDown();
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Integer t) {
count.incrementAndGet();
if (first) {
request(Long.MAX_VALUE - 1);
request(Long.MAX_VALUE - 1);
request(10);
first = false;
}
}
});
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertEquals(100, count.get());
}
@Test
public void testNoMoreRequestsAfterUnsubscribe() throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
final List<Long> requests = Collections.synchronizedList(new ArrayList<Long>());
Flowable.range(1, 1000000)
.doOnRequest(new LongConsumer() {
@Override
public void accept(long n) {
requests.add(n);
}
})
.observeOn(Schedulers.io())
.subscribe(new DefaultSubscriber<Integer>() {
@Override
public void onStart() {
request(1);
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
}
@Override
public void onNext(Integer t) {
cancel();
latch.countDown();
}
});
assertTrue(latch.await(10, TimeUnit.SECONDS));
// FIXME observeOn requests bufferSize at first always
assertEquals(Arrays.asList(128L), requests);
}
@Test
public void testErrorDelayed() {
TestScheduler s = new TestScheduler();
Flowable<Integer> source = Flowable.just(1, 2 ,3)
.concatWith(Flowable.<Integer>error(new TestException()));
TestSubscriber<Integer> ts = TestSubscriber.create(0);
source.observeOn(s, true).subscribe(ts);
ts.assertNoValues();
ts.assertNoErrors();
ts.assertNotComplete();
s.advanceTimeBy(1, TimeUnit.SECONDS);
ts.assertNoValues();
ts.assertNoErrors();
ts.assertNotComplete();
ts.request(1);
s.advanceTimeBy(1, TimeUnit.SECONDS);
ts.assertValues(1);
ts.assertNoErrors();
ts.assertNotComplete();
ts.request(3); // requesting 2 doesn't switch to the error() source for some reason in concat.
s.advanceTimeBy(1, TimeUnit.SECONDS);
ts.assertValues(1, 2, 3);
ts.assertError(TestException.class);
ts.assertNotComplete();
}
@Test
public void testErrorDelayedAsync() {
Flowable<Integer> source = Flowable.just(1, 2 ,3)
.concatWith(Flowable.<Integer>error(new TestException()));
TestSubscriber<Integer> ts = TestSubscriber.create();
source.observeOn(Schedulers.computation(), true).subscribe(ts);
ts.awaitTerminalEvent(2, TimeUnit.SECONDS);
ts.assertValues(1, 2, 3);
ts.assertError(TestException.class);
ts.assertNotComplete();
}
@Test
public void requestExactCompletesImmediately() {
TestSubscriber<Integer> ts = TestSubscriber.create(0);
TestScheduler test = new TestScheduler();
Flowable.range(1, 10).observeOn(test).subscribe(ts);
test.advanceTimeBy(1, TimeUnit.SECONDS);
ts.assertNoValues();
ts.assertNoErrors();
ts.assertNotComplete();
ts.request(10);
test.advanceTimeBy(1, TimeUnit.SECONDS);
ts.assertValueCount(10);
ts.assertNoErrors();
ts.assertComplete();
}
@Test
public void fixedReplenishPattern() {
TestSubscriber<Integer> ts = TestSubscriber.create(0);
TestScheduler test = new TestScheduler();
final List<Long> requests = new ArrayList<Long>();
Flowable.range(1, 100)
.doOnRequest(new LongConsumer() {
@Override
public void accept(long v) {
requests.add(v);
}
})
.observeOn(test, false, 16).subscribe(ts);
test.advanceTimeBy(1, TimeUnit.SECONDS);
ts.request(20);
test.advanceTimeBy(1, TimeUnit.SECONDS);
ts.request(10);
test.advanceTimeBy(1, TimeUnit.SECONDS);
ts.request(50);
test.advanceTimeBy(1, TimeUnit.SECONDS);
ts.request(35);
test.advanceTimeBy(1, TimeUnit.SECONDS);
ts.assertValueCount(100);
ts.assertComplete();
ts.assertNoErrors();
assertEquals(Arrays.asList(16L, 12L, 12L, 12L, 12L, 12L, 12L, 12L, 12L), requests);
}
@Test
public void bufferSizesWork() {
for (int i = 1; i <= 1024; i = i * 2) {
TestSubscriber<Integer> ts = TestSubscriber.create();
Flowable.range(1, 1000 * 1000).observeOn(Schedulers.computation(), false, i)
.subscribe(ts);
ts.awaitTerminalEvent();
ts.assertValueCount(1000 * 1000);
ts.assertComplete();
ts.assertNoErrors();
}
}
@Test
public void synchronousRebatching() {
final List<Long> requests = new ArrayList<Long>();
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
Flowable.range(1, 50)
.doOnRequest(new LongConsumer() {
@Override
public void accept(long r) {
requests.add(r);
}
})
.rebatchRequests(20)
.subscribe(ts);
ts.assertValueCount(50);
ts.assertNoErrors();
ts.assertComplete();
assertEquals(Arrays.asList(20L, 15L, 15L, 15L), requests);
}
@Test
public void rebatchRequestsArgumentCheck() {
try {
Flowable.never().rebatchRequests(-99);
fail("Didn't throw IAE");
} catch (IllegalArgumentException ex) {
assertEquals("bufferSize > 0 required but it was -99", ex.getMessage());
}
}
@Test
public void delayError() {
Flowable.range(1, 5).concatWith(Flowable.<Integer>error(new TestException()))
.observeOn(Schedulers.computation(), true)
.doOnNext(new Consumer<Integer>() {
@Override
public void accept(Integer v) throws Exception {
if (v == 1) {
Thread.sleep(100);
}
}
})
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class, 1, 2, 3, 4, 5);
}
@Test
public void conditionalConsumer() {
Flowable.range(1, 5)
.observeOn(Schedulers.single())
.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer v) throws Exception {
return v % 2 == 0;
}
})
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(2, 4);
}
@Test
public void take() {
Flowable.range(1, 5)
.observeOn(Schedulers.single())
.take(3)
.take(3)
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2, 3);
}
@Test
public void cancelCleanup() {
TestSubscriber<Integer> ts = Flowable.range(1, 5)
.observeOn(Schedulers.single())
.test(0L);
ts.cancel();
}
@Test
public void conditionalConsumerFused() {
TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY);
Flowable.range(1, 5)
.observeOn(Schedulers.single())
.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer v) throws Exception {
return v % 2 == 0;
}
})
.subscribe(ts);
ts
.assertOf(SubscriberFusion.<Integer>assertFuseable())
.assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC))
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(2, 4);
}
@Test
public void conditionalConsumerFusedReject() {
TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.SYNC);
Flowable.range(1, 5)
.observeOn(Schedulers.single())
.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer v) throws Exception {
return v % 2 == 0;
}
})
.subscribe(ts);
ts
.assertOf(SubscriberFusion.<Integer>assertFuseable())
.assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.NONE))
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(2, 4);
}
@Test
public void requestOne() throws Exception {
TestSubscriber<Integer> ts = Flowable.range(1, 5)
.observeOn(Schedulers.single())
.test(1);
Thread.sleep(100);
ts.assertSubscribed().assertValue(1).assertNoErrors().assertNotComplete();
}
@Test
public void requestOneConditional() throws Exception {
TestSubscriber<Integer> ts = Flowable.range(1, 5)
.observeOn(Schedulers.single())
.filter(Functions.alwaysTrue())
.test(1);
Thread.sleep(100);
ts.assertSubscribed().assertValue(1).assertNoErrors().assertNotComplete();
}
@Test
public void conditionalConsumerFusedAsync() {
TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY);
UnicastProcessor<Integer> up = UnicastProcessor.create();
up
.observeOn(Schedulers.single())
.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer v) throws Exception {
return v % 2 == 0;
}
})
.subscribe(ts);
up.onNext(1);
up.onNext(2);
up.onNext(3);
up.onNext(4);
up.onNext(5);
up.onComplete();
ts
.assertOf(SubscriberFusion.<Integer>assertFuseable())
.assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC))
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(2, 4);
}
@Test
public void conditionalConsumerHidden() {
TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY);
Flowable.range(1, 5).hide()
.observeOn(Schedulers.single())
.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer v) throws Exception {
return v % 2 == 0;
}
})
.subscribe(ts);
ts
.assertOf(SubscriberFusion.<Integer>assertFuseable())
.assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC))
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(2, 4);
}
@Test
public void conditionalConsumerBarrier() {
TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY);
Flowable.range(1, 5)
.map(Functions.<Integer>identity())
.observeOn(Schedulers.single())
.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer v) throws Exception {
return v % 2 == 0;
}
})
.subscribe(ts);
ts
.assertOf(SubscriberFusion.<Integer>assertFuseable())
.assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC))
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(2, 4);
}
@Test
public void dispose() {
TestHelper.checkDisposed(PublishProcessor.create().observeOn(new TestScheduler()));
}
@Test
public void doubleOnSubscribe() {
TestHelper.checkDoubleOnSubscribeFlowable(new Function<Flowable<Object>, Flowable<Object>>() {
@Override
public Flowable<Object> apply(Flowable<Object> o) throws Exception {
return o.observeOn(new TestScheduler());
}
});
}
@Test
public void badSource() {
List<Throwable> errors = TestHelper.trackPluginErrors();
try {
TestScheduler scheduler = new TestScheduler();
TestSubscriber<Integer> to = new Flowable<Integer>() {
@Override
protected void subscribeActual(Subscriber<? super Integer> observer) {
observer.onSubscribe(new BooleanSubscription());
observer.onComplete();
observer.onNext(1);
observer.onError(new TestException());
observer.onComplete();
}
}
.observeOn(scheduler)
.test();
scheduler.triggerActions();
to.assertResult();
TestHelper.assertUndeliverable(errors, 0, TestException.class);
} finally {
RxJavaPlugins.reset();
}
}
@Test
public void inputSyncFused() {
Flowable.range(1, 5)
.observeOn(Schedulers.single())
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2, 3, 4, 5);
}
@Test
public void inputAsyncFused() {
UnicastProcessor<Integer> us = UnicastProcessor.create();
TestSubscriber<Integer> to = us.observeOn(Schedulers.single()).test();
TestHelper.emit(us, 1, 2, 3, 4, 5);
to
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2, 3, 4, 5);
}
@Test
public void inputAsyncFusedError() {
UnicastProcessor<Integer> us = UnicastProcessor.create();
TestSubscriber<Integer> to = us.observeOn(Schedulers.single()).test();
us.onError(new TestException());
to
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
}
@Test
public void inputAsyncFusedErrorDelayed() {
UnicastProcessor<Integer> us = UnicastProcessor.create();
TestSubscriber<Integer> to = us.observeOn(Schedulers.single(), true).test();
us.onError(new TestException());
to
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
}
@Test
public void outputFused() {
TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueSubscription.ANY);
Flowable.range(1, 5).hide()
.observeOn(Schedulers.single())
.subscribe(to);
SubscriberFusion.assertFusion(to, QueueSubscription.ASYNC)
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2, 3, 4, 5);
}
@Test
public void outputFusedReject() {
TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueSubscription.SYNC);
Flowable.range(1, 5).hide()
.observeOn(Schedulers.single())
.subscribe(to);
SubscriberFusion.assertFusion(to, QueueSubscription.NONE)
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2, 3, 4, 5);
}
@Test
public void inputOutputAsyncFusedError() {
TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueSubscription.ANY);
UnicastProcessor<Integer> us = UnicastProcessor.create();
us.observeOn(Schedulers.single())
.subscribe(to);
us.onError(new TestException());
to
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
SubscriberFusion.assertFusion(to, QueueSubscription.ASYNC)
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
}
@Test
public void inputOutputAsyncFusedErrorDelayed() {
TestSubscriber<Integer> to = SubscriberFusion.newTest(QueueSubscription.ANY);
UnicastProcessor<Integer> us = UnicastProcessor.create();
us.observeOn(Schedulers.single(), true)
.subscribe(to);
us.onError(new TestException());
to
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
SubscriberFusion.assertFusion(to, QueueSubscription.ASYNC)
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
}
@Test
public void outputFusedCancelReentrant() throws Exception {
final UnicastProcessor<Integer> us = UnicastProcessor.create();
final CountDownLatch cdl = new CountDownLatch(1);
us.observeOn(Schedulers.single())
.subscribe(new FlowableSubscriber<Integer>() {
Subscription d;
int count;
@Override
public void onSubscribe(Subscription d) {
this.d = d;
((QueueSubscription<?>)d).requestFusion(QueueSubscription.ANY);
}
@Override
public void onNext(Integer value) {
if (++count == 1) {
us.onNext(2);
d.cancel();
cdl.countDown();
}
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
us.onNext(1);
cdl.await();
}
@Test
public void nonFusedPollThrows() {
new Flowable<Integer>() {
@Override
protected void subscribeActual(Subscriber<? super Integer> observer) {
observer.onSubscribe(new BooleanSubscription());
@SuppressWarnings("unchecked")
BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)observer;
oo.sourceMode = QueueFuseable.SYNC;
oo.requested.lazySet(1);
oo.queue = new SimpleQueue<Integer>() {
@Override
public boolean offer(Integer value) {
return false;
}
@Override
public boolean offer(Integer v1, Integer v2) {
return false;
}
@Nullable
@Override
public Integer poll() throws Exception {
throw new TestException();
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public void clear() {
}
};
oo.clear();
oo.trySchedule();
}
}
.observeOn(Schedulers.single())
.test(0L)
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
}
@Test
public void conditionalNonFusedPollThrows() {
new Flowable<Integer>() {
@Override
protected void subscribeActual(Subscriber<? super Integer> observer) {
observer.onSubscribe(new BooleanSubscription());
@SuppressWarnings("unchecked")
BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)observer;
oo.sourceMode = QueueFuseable.SYNC;
oo.requested.lazySet(1);
oo.queue = new SimpleQueue<Integer>() {
@Override
public boolean offer(Integer value) {
return false;
}
@Override
public boolean offer(Integer v1, Integer v2) {
return false;
}
@Nullable
@Override
public Integer poll() throws Exception {
throw new TestException();
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public void clear() {
}
};
oo.clear();
oo.trySchedule();
}
}
.observeOn(Schedulers.single())
.filter(Functions.alwaysTrue())
.test(0L)
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
}
@Test
public void asycFusedPollThrows() {
new Flowable<Integer>() {
@Override
protected void subscribeActual(Subscriber<? super Integer> observer) {
observer.onSubscribe(new BooleanSubscription());
@SuppressWarnings("unchecked")
BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)observer;
oo.sourceMode = QueueFuseable.ASYNC;
oo.requested.lazySet(1);
oo.queue = new SimpleQueue<Integer>() {
@Override
public boolean offer(Integer value) {
return false;
}
@Override
public boolean offer(Integer v1, Integer v2) {
return false;
}
@Nullable
@Override
public Integer poll() throws Exception {
throw new TestException();
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public void clear() {
}
};
oo.clear();
oo.trySchedule();
}
}
.observeOn(Schedulers.single())
.test(0L)
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
}
@Test
public void conditionalAsyncFusedPollThrows() {
new Flowable<Integer>() {
@Override
protected void subscribeActual(Subscriber<? super Integer> observer) {
observer.onSubscribe(new BooleanSubscription());
@SuppressWarnings("unchecked")
BaseObserveOnSubscriber<Integer> oo = (BaseObserveOnSubscriber<Integer>)observer;
oo.sourceMode = QueueFuseable.ASYNC;
oo.requested.lazySet(1);
oo.queue = new SimpleQueue<Integer>() {
@Override
public boolean offer(Integer value) {
return false;
}
@Override
public boolean offer(Integer v1, Integer v2) {
return false;
}
@Nullable
@Override
public Integer poll() throws Exception {
throw new TestException();
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public void clear() {
}
};
oo.clear();
oo.trySchedule();
}
}
.observeOn(Schedulers.single())
.filter(Functions.alwaysTrue())
.test(0L)
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
}
@Test
public void trampolineScheduler() {
Flowable.just(1)
.observeOn(Schedulers.trampoline())
.test()
.assertResult(1);
}
@Test
public void conditionalNormal() {
Flowable.range(1, 1000).hide()
.observeOn(Schedulers.single())
.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer v) throws Exception {
return v % 2 == 0;
}
})
.take(250)
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertSubscribed()
.assertValueCount(250)
.assertNoErrors()
.assertComplete();
}
@Test
public void syncFusedCancelAfterRequest() {
final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(2L) {
@Override
public void onNext(Integer t) {
super.onNext(t);
if (t == 2) {
cancel();
onComplete();
}
}
};
Flowable.range(1, 3)
.observeOn(Schedulers.single())
.subscribe(ts);
ts
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2);
}
@Test
public void syncFusedCancelAfterRequest2() {
final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(2L);
Flowable.range(1, 2)
.observeOn(Schedulers.single())
.subscribe(ts);
ts
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2);
}
@Test
public void syncFusedCancelAfterRequestConditional() {
final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(2L) {
@Override
public void onNext(Integer t) {
super.onNext(t);
if (t == 2) {
cancel();
onComplete();
}
}
};
Flowable.range(1, 3)
.observeOn(Schedulers.single())
.filter(Functions.alwaysTrue())
.subscribe(ts);
ts
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2);
}
@Test
public void syncFusedCancelAfterRequestConditional2() {
final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(2L);
Flowable.range(1, 2)
.observeOn(Schedulers.single())
.filter(Functions.alwaysTrue())
.subscribe(ts);
ts
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2);
}
@Test
public void nonFusedCancelAfterRequestConditional2() {
final TestSubscriber<Integer> ts = new TestSubscriber<Integer>(2L);
Flowable.range(1, 2).hide()
.observeOn(Schedulers.single())
.filter(Functions.alwaysTrue())
.subscribe(ts);
ts
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1, 2);
}
@Test
public void doubleObserveOn() {
Flowable.just(1).hide()
.observeOn(Schedulers.computation())
.observeOn(Schedulers.single())
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1);
}
@Test
public void doubleObserveOnError() {
Flowable.error(new TestException())
.observeOn(Schedulers.computation())
.observeOn(Schedulers.single())
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
}
@Test
public void doubleObserveOnConditional() {
Flowable.just(1).hide()
.observeOn(Schedulers.computation())
.distinct()
.observeOn(Schedulers.single())
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertResult(1);
}
@Test
public void doubleObserveOnErrorConditional() {
Flowable.error(new TestException())
.observeOn(Schedulers.computation())
.distinct()
.observeOn(Schedulers.single())
.test()
.awaitDone(5, TimeUnit.SECONDS)
.assertFailure(TestException.class);
}
@Test
public void request1Conditional() {
Flowable.range(1, 10).hide()
.observeOn(ImmediateThinScheduler.INSTANCE)
.filter(Functions.alwaysTrue())
.test(1L)
.assertValue(1);
}
@Test
public void backFusedConditional() {
TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY);
Flowable.range(1, 100).hide()
.observeOn(ImmediateThinScheduler.INSTANCE)
.filter(Functions.alwaysTrue())
.subscribe(ts);
SubscriberFusion.assertFusion(ts, QueueSubscription.ASYNC)
.assertValueCount(100)
.assertComplete()
.assertNoErrors();
}
@Test
public void backFusedErrorConditional() {
TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY);
Flowable.<Integer>error(new TestException())
.observeOn(ImmediateThinScheduler.INSTANCE)
.filter(Functions.alwaysTrue())
.subscribe(ts);
SubscriberFusion.assertFusion(ts, QueueSubscription.ASYNC)
.assertFailure(TestException.class);
}
@Test
public void backFusedCancelConditional() {
for (int i = 0; i < 500; i++) {
final TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY);
final TestScheduler scheduler = new TestScheduler();
Flowable.just(1).hide()
.observeOn(scheduler)
.filter(Functions.alwaysTrue())
.subscribe(ts);
Runnable r1 = new Runnable() {
@Override
public void run() {
ts.cancel();
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
scheduler.triggerActions();
}
};
TestHelper.race(r1, r2);
SubscriberFusion.assertFusion(ts, QueueSubscription.ASYNC);
if (ts.valueCount() != 0) {
ts.assertResult(1);
}
}
}
@Test
public void syncFusedRequestOneByOneConditional() {
Flowable.range(1, 5)
.observeOn(ImmediateThinScheduler.INSTANCE)
.filter(Functions.alwaysTrue())
.rebatchRequests(1)
.test()
.assertResult(1, 2, 3, 4, 5);
}
}