/**
* 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 java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import org.junit.*;
import io.reactivex.*;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.disposables.*;
import io.reactivex.exceptions.TestException;
import io.reactivex.internal.operators.observable.BlockingObservableNext.NextObserver;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.processors.BehaviorProcessor;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.*;
public class BlockingObservableNextTest {
private void fireOnNextInNewThread(final Subject<String> o, final String value) {
new Thread() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// ignore
}
o.onNext(value);
}
}.start();
}
private void fireOnErrorInNewThread(final Subject<String> o) {
new Thread() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// ignore
}
o.onError(new TestException());
}
}.start();
}
static <T> Iterable<T> next(ObservableSource<T> source) {
return new BlockingObservableNext<T>(source);
}
@Test
public void testNext() {
Subject<String> obs = PublishSubject.create();
Iterator<String> it = next(obs).iterator();
fireOnNextInNewThread(obs, "one");
assertTrue(it.hasNext());
assertEquals("one", it.next());
fireOnNextInNewThread(obs, "two");
assertTrue(it.hasNext());
assertEquals("two", it.next());
fireOnNextInNewThread(obs, "three");
try {
assertEquals("three", it.next());
} catch (NoSuchElementException e) {
fail("Calling next() without hasNext() should wait for next fire");
}
obs.onComplete();
assertFalse(it.hasNext());
try {
it.next();
fail("At the end of an iterator should throw a NoSuchElementException");
} catch (NoSuchElementException e) {
}
// If the Observable is completed, hasNext always returns false and next always throw a NoSuchElementException.
assertFalse(it.hasNext());
try {
it.next();
fail("At the end of an iterator should throw a NoSuchElementException");
} catch (NoSuchElementException e) {
}
}
@Test
public void testNextWithError() {
Subject<String> obs = PublishSubject.create();
Iterator<String> it = next(obs).iterator();
fireOnNextInNewThread(obs, "one");
assertTrue(it.hasNext());
assertEquals("one", it.next());
fireOnErrorInNewThread(obs);
try {
it.hasNext();
fail("Expected an TestException");
} catch (TestException e) {
}
assertErrorAfterObservableFail(it);
}
@Test
public void testNextWithEmpty() {
Observable<String> obs = Observable.<String> empty().observeOn(Schedulers.newThread());
Iterator<String> it = next(obs).iterator();
assertFalse(it.hasNext());
try {
it.next();
fail("At the end of an iterator should throw a NoSuchElementException");
} catch (NoSuchElementException e) {
}
// If the Observable is completed, hasNext always returns false and next always throw a NoSuchElementException.
assertFalse(it.hasNext());
try {
it.next();
fail("At the end of an iterator should throw a NoSuchElementException");
} catch (NoSuchElementException e) {
}
}
@Test
public void testOnError() throws Throwable {
Subject<String> obs = PublishSubject.create();
Iterator<String> it = next(obs).iterator();
obs.onError(new TestException());
try {
it.hasNext();
fail("Expected an TestException");
} catch (TestException e) {
// successful
}
assertErrorAfterObservableFail(it);
}
@Test
public void testOnErrorInNewThread() {
Subject<String> obs = PublishSubject.create();
Iterator<String> it = next(obs).iterator();
fireOnErrorInNewThread(obs);
try {
it.hasNext();
fail("Expected an TestException");
} catch (TestException e) {
// successful
}
assertErrorAfterObservableFail(it);
}
private void assertErrorAfterObservableFail(Iterator<String> it) {
// After the Observable fails, hasNext and next always throw the exception.
try {
it.hasNext();
fail("hasNext should throw a TestException");
} catch (TestException e) {
}
try {
it.next();
fail("next should throw a TestException");
} catch (TestException e) {
}
}
@Test
public void testNextWithOnlyUsingNextMethod() {
Subject<String> obs = PublishSubject.create();
Iterator<String> it = next(obs).iterator();
fireOnNextInNewThread(obs, "one");
assertEquals("one", it.next());
fireOnNextInNewThread(obs, "two");
assertEquals("two", it.next());
obs.onComplete();
try {
it.next();
fail("At the end of an iterator should throw a NoSuchElementException");
} catch (NoSuchElementException e) {
}
}
@Test
public void testNextWithCallingHasNextMultipleTimes() {
Subject<String> obs = PublishSubject.create();
Iterator<String> it = next(obs).iterator();
fireOnNextInNewThread(obs, "one");
assertTrue(it.hasNext());
assertTrue(it.hasNext());
assertTrue(it.hasNext());
assertTrue(it.hasNext());
assertEquals("one", it.next());
obs.onComplete();
try {
it.next();
fail("At the end of an iterator should throw a NoSuchElementException");
} catch (NoSuchElementException e) {
}
}
/**
* Confirm that no buffering or blocking of the Observable onNext calls occurs and it just grabs the next emitted value.
* <p/>
* This results in output such as => a: 1 b: 2 c: 89
*
* @throws Throwable some method call is declared throws
*/
@Test
public void testNoBufferingOrBlockingOfSequence() throws Throwable {
int repeat = 0;
for (;;) {
final SerialDisposable task = new SerialDisposable();
try {
final CountDownLatch finished = new CountDownLatch(1);
final int COUNT = 30;
final CountDownLatch timeHasPassed = new CountDownLatch(COUNT);
final AtomicBoolean running = new AtomicBoolean(true);
final AtomicInteger count = new AtomicInteger(0);
final Observable<Integer> obs = Observable.unsafeCreate(new ObservableSource<Integer>() {
@Override
public void subscribe(final Observer<? super Integer> o) {
o.onSubscribe(Disposables.empty());
task.replace(Schedulers.single().scheduleDirect(new Runnable() {
@Override
public void run() {
try {
while (running.get() && !task.isDisposed()) {
o.onNext(count.incrementAndGet());
timeHasPassed.countDown();
}
o.onComplete();
} catch (Throwable e) {
o.onError(e);
} finally {
finished.countDown();
}
}
}));
}
});
Iterator<Integer> it = next(obs).iterator();
assertTrue(it.hasNext());
int a = it.next();
assertTrue(it.hasNext());
int b = it.next();
// we should have a different value
assertTrue("a and b should be different", a != b);
// wait for some time (if times out we are blocked somewhere so fail ... set very high for very slow, constrained machines)
timeHasPassed.await(8000, TimeUnit.MILLISECONDS);
assertTrue(it.hasNext());
int c = it.next();
assertTrue("c should not just be the next in sequence", c != (b + 1));
assertTrue("expected that c [" + c + "] is higher than or equal to " + COUNT, c >= COUNT);
assertTrue(it.hasNext());
int d = it.next();
assertTrue(d > c);
// shut down the thread
running.set(false);
finished.await();
assertFalse(it.hasNext());
System.out.println("a: " + a + " b: " + b + " c: " + c);
break;
} catch (AssertionError ex) {
if (++repeat == 3) {
throw ex;
}
Thread.sleep((int)(1000 * Math.pow(2, repeat - 1)));
} finally {
task.dispose();
}
}
}
@Test /* (timeout = 8000) */
public void testSingleSourceManyIterators() throws InterruptedException {
Observable<Long> o = Observable.interval(250, TimeUnit.MILLISECONDS);
PublishSubject<Integer> terminal = PublishSubject.create();
Observable<Long> source = o.takeUntil(terminal);
Iterable<Long> iter = source.blockingNext();
for (int j = 0; j < 3; j++) {
BlockingObservableNext.NextIterator<Long> it = (BlockingObservableNext.NextIterator<Long>)iter.iterator();
for (long i = 0; i < 10; i++) {
Assert.assertEquals(true, it.hasNext());
Assert.assertEquals(j + "th iteration next", Long.valueOf(i), it.next());
}
terminal.onNext(1);
}
}
@Test
public void testSynchronousNext() {
assertEquals(1, BehaviorProcessor.createDefault(1).take(1).blockingSingle().intValue());
assertEquals(2, BehaviorProcessor.createDefault(2).blockingIterable().iterator().next().intValue());
assertEquals(3, BehaviorProcessor.createDefault(3).blockingNext().iterator().next().intValue());
}
@Test
public void interrupt() {
Iterator<Object> it = Observable.never().blockingNext().iterator();
try {
Thread.currentThread().interrupt();
it.next();
} catch (RuntimeException ex) {
assertTrue(ex.toString(), ex.getCause() instanceof InterruptedException);
}
}
@Test(expected = UnsupportedOperationException.class)
public void remove() {
Observable.never().blockingNext().iterator().remove();
}
@Test
public void nextObserverError() {
NextObserver<Integer> no = new NextObserver<Integer>();
List<Throwable> errors = TestHelper.trackPluginErrors();
try {
no.onError(new TestException());
TestHelper.assertUndeliverable(errors, 0, TestException.class);
} finally {
RxJavaPlugins.reset();
}
}
@Test
public void nextObserverOnNext() throws Exception {
NextObserver<Integer> no = new NextObserver<Integer>();
no.setWaiting();
no.onNext(Notification.createOnNext(1));
no.setWaiting();
no.onNext(Notification.createOnNext(1));
assertEquals(1, no.takeNext().getValue().intValue());
}
@Test
public void nextObserverOnCompleteOnNext() throws Exception {
NextObserver<Integer> no = new NextObserver<Integer>();
no.setWaiting();
no.onNext(Notification.<Integer>createOnComplete());
no.setWaiting();
no.onNext(Notification.createOnNext(1));
assertTrue(no.takeNext().isOnComplete());
}
}