/**
* 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.observables;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
import rx.exceptions.TestException;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.schedulers.Schedulers;
import rx.subscriptions.Subscriptions;
public class BlockingObservableTest {
@Mock
Subscriber<Integer> w;
@Before
public void before() {
MockitoAnnotations.initMocks(this);
}
@Test
public void testLast() {
BlockingObservable<String> obs = BlockingObservable.from(Observable.just("one", "two", "three"));
assertEquals("three", obs.last());
}
@Test(expected = NoSuchElementException.class)
public void testLastEmptyObservable() {
BlockingObservable<Object> obs = BlockingObservable.from(Observable.empty());
obs.last();
}
@Test
public void testLastOrDefault() {
BlockingObservable<Integer> observable = BlockingObservable.from(Observable.just(1, 0, -1));
int last = observable.lastOrDefault(-100, new Func1<Integer, Boolean>() {
@Override
public Boolean call(Integer args) {
return args >= 0;
}
});
assertEquals(0, last);
}
@Test
public void testLastOrDefault1() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.just("one", "two", "three"));
assertEquals("three", observable.lastOrDefault("default"));
}
@Test
public void testLastOrDefault2() {
BlockingObservable<Object> observable = BlockingObservable.from(Observable.empty());
assertEquals("default", observable.lastOrDefault("default"));
}
@Test
public void testLastOrDefaultWithPredicate() {
BlockingObservable<Integer> observable = BlockingObservable.from(Observable.just(1, 0, -1));
int last = observable.lastOrDefault(0, new Func1<Integer, Boolean>() {
@Override
public Boolean call(Integer args) {
return args < 0;
}
});
assertEquals(-1, last);
}
@Test
public void testLastOrDefaultWrongPredicate() {
BlockingObservable<Integer> observable = BlockingObservable.from(Observable.just(-1, -2, -3));
int last = observable.lastOrDefault(0, new Func1<Integer, Boolean>() {
@Override
public Boolean call(Integer args) {
return args >= 0;
}
});
assertEquals(0, last);
}
@Test
public void testLastWithPredicate() {
BlockingObservable<String> obs = BlockingObservable.from(Observable.just("one", "two", "three"));
assertEquals("two", obs.last(new Func1<String, Boolean>() {
@Override
public Boolean call(String s) {
return s.length() == 3;
}
}));
}
@Test
public void testSingle() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.just("one"));
assertEquals("one", observable.single());
}
@Test
public void testSingleDefault() {
BlockingObservable<Object> observable = BlockingObservable.from(Observable.empty());
assertEquals("default", observable.singleOrDefault("default"));
}
@Test(expected = IllegalArgumentException.class)
public void testSingleDefaultPredicateMatchesMoreThanOne() {
BlockingObservable.from(Observable.just("one", "two")).singleOrDefault("default", new Func1<String, Boolean>() {
@Override
public Boolean call(String args) {
return args.length() == 3;
}
});
}
@Test
public void testSingleDefaultPredicateMatchesNothing() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.just("one", "two"));
String result = observable.singleOrDefault("default", new Func1<String, Boolean>() {
@Override
public Boolean call(String args) {
return args.length() == 4;
}
});
assertEquals("default", result);
}
@Test(expected = IllegalArgumentException.class)
public void testSingleDefaultWithMoreThanOne() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.just("one", "two", "three"));
observable.singleOrDefault("default");
}
@Test
public void testSingleWithPredicateDefault() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.just("one", "two", "four"));
assertEquals("four", observable.single(new Func1<String, Boolean>() {
@Override
public Boolean call(String s) {
return s.length() == 4;
}
}));
}
@Test(expected = IllegalArgumentException.class)
public void testSingleWrong() {
BlockingObservable<Integer> observable = BlockingObservable.from(Observable.just(1, 2));
observable.single();
}
@Test(expected = NoSuchElementException.class)
public void testSingleWrongPredicate() {
BlockingObservable<Integer> observable = BlockingObservable.from(Observable.just(-1));
observable.single(new Func1<Integer, Boolean>() {
@Override
public Boolean call(Integer args) {
return args > 0;
}
});
}
@Test
public void testToIterable() {
BlockingObservable<String> obs = BlockingObservable.from(Observable.just("one", "two", "three"));
Iterator<String> it = obs.toIterable().iterator();
assertEquals(true, it.hasNext());
assertEquals("one", it.next());
assertEquals(true, it.hasNext());
assertEquals("two", it.next());
assertEquals(true, it.hasNext());
assertEquals("three", it.next());
assertEquals(false, it.hasNext());
}
@Test(expected = NoSuchElementException.class)
public void testToIterableNextOnly() {
BlockingObservable<Integer> obs = BlockingObservable.from(Observable.just(1, 2, 3));
Iterator<Integer> it = obs.toIterable().iterator();
Assert.assertEquals((Integer) 1, it.next());
Assert.assertEquals((Integer) 2, it.next());
Assert.assertEquals((Integer) 3, it.next());
it.next();
}
@Test(expected = NoSuchElementException.class)
public void testToIterableNextOnlyTwice() {
BlockingObservable<Integer> obs = BlockingObservable.from(Observable.just(1, 2, 3));
Iterator<Integer> it = obs.toIterable().iterator();
Assert.assertEquals((Integer) 1, it.next());
Assert.assertEquals((Integer) 2, it.next());
Assert.assertEquals((Integer) 3, it.next());
boolean exc = false;
try {
it.next();
} catch (NoSuchElementException ex) {
exc = true;
}
Assert.assertEquals(true, exc);
it.next();
}
@Test
public void testToIterableManyTimes() {
BlockingObservable<Integer> obs = BlockingObservable.from(Observable.just(1, 2, 3));
Iterable<Integer> iter = obs.toIterable();
for (int j = 0; j < 3; j++) {
Iterator<Integer> it = iter.iterator();
Assert.assertTrue(it.hasNext());
Assert.assertEquals((Integer) 1, it.next());
Assert.assertTrue(it.hasNext());
Assert.assertEquals((Integer) 2, it.next());
Assert.assertTrue(it.hasNext());
Assert.assertEquals((Integer) 3, it.next());
Assert.assertFalse(it.hasNext());
}
}
@Test(expected = TestException.class)
public void testToIterableWithException() {
BlockingObservable<String> obs = BlockingObservable.from(Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
observer.onNext("one");
observer.onError(new TestException());
}
}));
Iterator<String> it = obs.toIterable().iterator();
assertEquals(true, it.hasNext());
assertEquals("one", it.next());
assertEquals(true, it.hasNext());
it.next();
}
@Test
public void testForEachWithError() {
try {
BlockingObservable.from(Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> observer) {
new Thread(new Runnable() {
@Override
public void run() {
observer.onNext("one");
observer.onNext("two");
observer.onNext("three");
observer.onCompleted();
}
}).start();
}
})).forEach(new Action1<String>() {
@Override
public void call(String t1) {
throw new RuntimeException("fail");
}
});
fail("we expect an exception to be thrown");
} catch (Throwable e) {
// do nothing as we expect this
}
}
@Test
public void testFirst() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.just("one", "two", "three"));
assertEquals("one", observable.first());
}
@Test(expected = NoSuchElementException.class)
public void testFirstWithEmpty() {
BlockingObservable.from(Observable.<String> empty()).first();
}
@Test
public void testFirstWithPredicate() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.just("one", "two", "three"));
String first = observable.first(new Func1<String, Boolean>() {
@Override
public Boolean call(String args) {
return args.length() > 3;
}
});
assertEquals("three", first);
}
@Test(expected = NoSuchElementException.class)
public void testFirstWithPredicateAndEmpty() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.just("one", "two", "three"));
observable.first(new Func1<String, Boolean>() {
@Override
public Boolean call(String args) {
return args.length() > 5;
}
});
}
@Test
public void testFirstOrDefault() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.just("one", "two", "three"));
assertEquals("one", observable.firstOrDefault("default"));
}
@Test
public void testFirstOrDefaultWithEmpty() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.<String> empty());
assertEquals("default", observable.firstOrDefault("default"));
}
@Test
public void testFirstOrDefaultWithPredicate() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.just("one", "two", "three"));
String first = observable.firstOrDefault("default", new Func1<String, Boolean>() {
@Override
public Boolean call(String args) {
return args.length() > 3;
}
});
assertEquals("three", first);
}
@Test
public void testFirstOrDefaultWithPredicateAndEmpty() {
BlockingObservable<String> observable = BlockingObservable.from(Observable.just("one", "two", "three"));
String first = observable.firstOrDefault("default", new Func1<String, Boolean>() {
@Override
public Boolean call(String args) {
return args.length() > 5;
}
});
assertEquals("default", first);
}
@Test
public void testSingleOrDefaultUnsubscribe() throws InterruptedException {
final CountDownLatch unsubscribe = new CountDownLatch(1);
Observable<Integer> o = Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> subscriber) {
subscriber.add(Subscriptions.create(new Action0() {
@Override
public void call() {
unsubscribe.countDown();
}
}));
subscriber.onNext(1);
subscriber.onNext(2);
// Don't call `onCompleted` to emulate an infinite stream
}
}).subscribeOn(Schedulers.newThread());
try {
o.toBlocking().singleOrDefault(-1);
fail("Expected IllegalArgumentException because there are 2 elements");
} catch (IllegalArgumentException e) {
// Expected
}
assertTrue("Timeout means `unsubscribe` is not called", unsubscribe.await(30, TimeUnit.SECONDS));
}
@Test
public void testUnsubscribeFromSingleWhenInterrupted() throws InterruptedException {
new InterruptionTests().assertUnsubscribeIsInvoked("single()", new Action1<BlockingObservable<Void>>() {
@Override
public void call(final BlockingObservable<Void> o) {
o.single();
}
});
}
@Test
public void testUnsubscribeFromForEachWhenInterrupted() throws InterruptedException {
new InterruptionTests().assertUnsubscribeIsInvoked("forEach()", new Action1<BlockingObservable<Void>>() {
@Override
public void call(final BlockingObservable<Void> o) {
o.forEach(new Action1<Void>() {
@Override
public void call(final Void aVoid) {
// nothing
}
});
}
});
}
@Test
public void testUnsubscribeFromFirstWhenInterrupted() throws InterruptedException {
new InterruptionTests().assertUnsubscribeIsInvoked("first()", new Action1<BlockingObservable<Void>>() {
@Override
public void call(final BlockingObservable<Void> o) {
o.first();
}
});
}
@Test
public void testUnsubscribeFromLastWhenInterrupted() throws InterruptedException {
new InterruptionTests().assertUnsubscribeIsInvoked("last()", new Action1<BlockingObservable<Void>>() {
@Override
public void call(final BlockingObservable<Void> o) {
o.last();
}
});
}
@Test
public void testUnsubscribeFromLatestWhenInterrupted() throws InterruptedException {
new InterruptionTests().assertUnsubscribeIsInvoked("latest()", new Action1<BlockingObservable<Void>>() {
@Override
public void call(final BlockingObservable<Void> o) {
o.latest().iterator().next();
}
});
}
@Test
public void testUnsubscribeFromNextWhenInterrupted() throws InterruptedException {
new InterruptionTests().assertUnsubscribeIsInvoked("next()", new Action1<BlockingObservable<Void>>() {
@Override
public void call(final BlockingObservable<Void> o) {
o.next().iterator().next();
}
});
}
@Test
public void testUnsubscribeFromGetIteratorWhenInterrupted() throws InterruptedException {
new InterruptionTests().assertUnsubscribeIsInvoked("getIterator()", new Action1<BlockingObservable<Void>>() {
@Override
public void call(final BlockingObservable<Void> o) {
o.getIterator().next();
}
});
}
@Test
public void testUnsubscribeFromToIterableWhenInterrupted() throws InterruptedException {
new InterruptionTests().assertUnsubscribeIsInvoked("toIterable()", new Action1<BlockingObservable<Void>>() {
@Override
public void call(final BlockingObservable<Void> o) {
o.toIterable().iterator().next();
}
});
}
/** Utilities set for interruption behaviour tests. */
private static class InterruptionTests {
private boolean isUnSubscribed;
private RuntimeException error;
private CountDownLatch latch = new CountDownLatch(1);
private Observable<Void> createObservable() {
return Observable.<Void>never().doOnUnsubscribe(new Action0() {
@Override
public void call() {
isUnSubscribed = true;
}
});
}
private void startBlockingAndInterrupt(final Action1<BlockingObservable<Void>> blockingAction) {
Thread subscriptionThread = new Thread() {
@Override
public void run() {
try {
blockingAction.call(createObservable().toBlocking());
} catch (RuntimeException e) {
if (!(e.getCause() instanceof InterruptedException)) {
error = e;
}
}
latch.countDown();
}
};
subscriptionThread.start();
subscriptionThread.interrupt();
}
void assertUnsubscribeIsInvoked(final String method, final Action1<BlockingObservable<Void>> blockingAction)
throws InterruptedException {
startBlockingAndInterrupt(blockingAction);
assertTrue("Timeout means interruption is not performed", latch.await(30, TimeUnit.SECONDS));
if (error != null) {
throw error;
}
assertTrue("'unsubscribe' is not invoked when thread is interrupted for " + method, isUnSubscribed);
}
}
}