/**
* Copyright 2014 Netflix, Inc.
*
* 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 rx.internal.operators;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static rx.internal.operators.BlockingOperatorNext.next;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Assert;
import org.junit.Test;
import rx.Observable;
import rx.Subscriber;
import rx.exceptions.TestException;
import rx.internal.operators.BlockingOperatorNext;
import rx.observables.BlockingObservable;
import rx.schedulers.Schedulers;
import rx.subjects.BehaviorSubject;
import rx.subjects.PublishSubject;
import rx.subjects.Subject;
public class BlockingOperatorNextTest {
private void fireOnNextInNewThread(final Subject<String, 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, String> o) {
new Thread() {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// ignore
}
o.onError(new TestException());
}
}.start();
}
@Test
public void testNext() {
Subject<String, 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());
obs.onCompleted();
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, 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, 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, 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, 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.onCompleted();
try {
it.next();
fail("At the end of an iterator should throw a NoSuchElementException");
} catch (NoSuchElementException e) {
}
}
@Test
public void testNextWithCallingHasNextMultipleTimes() {
Subject<String, 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.onCompleted();
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
*/
@Test
public void testNoBufferingOrBlockingOfSequence() throws Throwable {
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.create(new Observable.OnSubscribe<Integer>() {
@Override
public void call(final Subscriber<? super Integer> o) {
new Thread(new Runnable() {
@Override
public void run() {
try {
while (running.get()) {
o.onNext(count.incrementAndGet());
timeHasPassed.countDown();
}
o.onCompleted();
} catch (Throwable e) {
o.onError(e);
} finally {
finished.countDown();
}
}
}).start();
}
});
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);
}
@Test /* (timeout = 8000) */
public void testSingleSourceManyIterators() throws InterruptedException {
Observable<Long> o = Observable.interval(100, TimeUnit.MILLISECONDS);
PublishSubject<Void> terminal = PublishSubject.create();
BlockingObservable<Long> source = o.takeUntil(terminal).toBlocking();
Iterable<Long> iter = source.next();
for (int j = 0; j < 3; j++) {
BlockingOperatorNext.NextIterator<Long> it = (BlockingOperatorNext.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(null);
}
}
@Test
public void testSynchronousNext() {
assertEquals(1, BehaviorSubject.create(1).take(1).toBlocking().single().intValue());
assertEquals(2, BehaviorSubject.create(2).toBlocking().toIterable().iterator().next().intValue());
assertEquals(3, BehaviorSubject.create(3).toBlocking().next().iterator().next().intValue());
}
}