/**
* 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.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.mockito.InOrder;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Observer;
import rx.Producer;
import rx.Subscriber;
import rx.Subscription;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.observers.Subscribers;
import rx.observers.TestSubscriber;
import rx.schedulers.Schedulers;
public class OperatorTakeTest {
@Test
public void testTake1() {
Observable<String> w = Observable.from(Arrays.asList("one", "two", "three"));
Observable<String> take = w.lift(new OperatorTake<String>(2));
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
take.subscribe(observer);
verify(observer, times(1)).onNext("one");
verify(observer, times(1)).onNext("two");
verify(observer, never()).onNext("three");
verify(observer, never()).onError(any(Throwable.class));
verify(observer, times(1)).onCompleted();
}
@Test
public void testTake2() {
Observable<String> w = Observable.from(Arrays.asList("one", "two", "three"));
Observable<String> take = w.lift(new OperatorTake<String>(1));
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
take.subscribe(observer);
verify(observer, times(1)).onNext("one");
verify(observer, never()).onNext("two");
verify(observer, never()).onNext("three");
verify(observer, never()).onError(any(Throwable.class));
verify(observer, times(1)).onCompleted();
}
@Test(expected = IllegalArgumentException.class)
public void testTakeWithError() {
Observable.from(Arrays.asList(1, 2, 3)).take(1).map(new Func1<Integer, Integer>() {
@Override
public Integer call(Integer t1) {
throw new IllegalArgumentException("some error");
}
}).toBlocking().single();
}
@Test
public void testTakeWithErrorHappeningInOnNext() {
Observable<Integer> w = Observable.from(Arrays.asList(1, 2, 3)).take(2).map(new Func1<Integer, Integer>() {
@Override
public Integer call(Integer t1) {
throw new IllegalArgumentException("some error");
}
});
@SuppressWarnings("unchecked")
Observer<Integer> observer = mock(Observer.class);
w.subscribe(observer);
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, times(1)).onError(any(IllegalArgumentException.class));
inOrder.verifyNoMoreInteractions();
}
@Test
public void testTakeWithErrorHappeningInTheLastOnNext() {
Observable<Integer> w = Observable.from(Arrays.asList(1, 2, 3)).take(1).map(new Func1<Integer, Integer>() {
@Override
public Integer call(Integer t1) {
throw new IllegalArgumentException("some error");
}
});
@SuppressWarnings("unchecked")
Observer<Integer> observer = mock(Observer.class);
w.subscribe(observer);
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, times(1)).onError(any(IllegalArgumentException.class));
inOrder.verifyNoMoreInteractions();
}
@Test
public void testTakeDoesntLeakErrors() {
Observable<String> source = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
observer.onNext("one");
observer.onError(new Throwable("test failed"));
}
});
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
source.lift(new OperatorTake<String>(1)).subscribe(observer);
verify(observer, times(1)).onNext("one");
// even though onError is called we take(1) so shouldn't see it
verify(observer, never()).onError(any(Throwable.class));
verify(observer, times(1)).onCompleted();
verifyNoMoreInteractions(observer);
}
@Test
public void testTakeZeroDoesntLeakError() {
final AtomicBoolean subscribed = new AtomicBoolean(false);
final AtomicBoolean unSubscribed = new AtomicBoolean(false);
Observable<String> source = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
subscribed.set(true);
observer.add(new Subscription() {
@Override
public void unsubscribe() {
unSubscribed.set(true);
}
@Override
public boolean isUnsubscribed() {
return unSubscribed.get();
}
});
observer.onError(new Throwable("test failed"));
}
});
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
source.lift(new OperatorTake<String>(0)).subscribe(observer);
assertTrue("source subscribed", subscribed.get());
assertTrue("source unsubscribed", unSubscribed.get());
verify(observer, never()).onNext(anyString());
// even though onError is called we take(0) so shouldn't see it
verify(observer, never()).onError(any(Throwable.class));
verify(observer, times(1)).onCompleted();
verifyNoMoreInteractions(observer);
}
@Test
public void testUnsubscribeAfterTake() {
final Subscription s = mock(Subscription.class);
TestObservableFunc f = new TestObservableFunc("one", "two", "three");
Observable<String> w = Observable.create(f);
@SuppressWarnings("unchecked")
Observer<String> observer = mock(Observer.class);
Subscriber<String> subscriber = Subscribers.from(observer);
subscriber.add(s);
Observable<String> take = w.lift(new OperatorTake<String>(1));
take.subscribe(subscriber);
// wait for the Observable to complete
try {
f.t.join();
} catch (Throwable e) {
e.printStackTrace();
fail(e.getMessage());
}
System.out.println("TestObservable thread finished");
verify(observer, times(1)).onNext("one");
verify(observer, never()).onNext("two");
verify(observer, never()).onNext("three");
verify(observer, times(1)).onCompleted();
verify(s, times(1)).unsubscribe();
verifyNoMoreInteractions(observer);
}
@Test(timeout = 2000)
public void testUnsubscribeFromSynchronousInfiniteObservable() {
final AtomicLong count = new AtomicLong();
INFINITE_OBSERVABLE.take(10).subscribe(new Action1<Long>() {
@Override
public void call(Long l) {
count.set(l);
}
});
assertEquals(10, count.get());
}
@Test(timeout = 2000)
public void testMultiTake() {
final AtomicInteger count = new AtomicInteger();
Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> s) {
for (int i = 0; !s.isUnsubscribed(); i++) {
System.out.println("Emit: " + i);
count.incrementAndGet();
s.onNext(i);
}
}
}).take(100).take(1).toBlocking().forEach(new Action1<Integer>() {
@Override
public void call(Integer t1) {
System.out.println("Receive: " + t1);
}
});
assertEquals(1, count.get());
}
private static class TestObservableFunc implements Observable.OnSubscribe<String> {
final String[] values;
Thread t = null;
public TestObservableFunc(String... values) {
this.values = values;
}
@Override
public void call(final Subscriber<? super String> observer) {
System.out.println("TestObservable subscribed to ...");
t = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("running TestObservable thread");
for (String s : values) {
System.out.println("TestObservable onNext: " + s);
observer.onNext(s);
}
observer.onCompleted();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
});
System.out.println("starting TestObservable thread");
t.start();
System.out.println("done starting TestObservable thread");
}
}
private static Observable<Long> INFINITE_OBSERVABLE = Observable.create(new OnSubscribe<Long>() {
@Override
public void call(Subscriber<? super Long> op) {
long l = 1;
while (!op.isUnsubscribed()) {
op.onNext(l++);
}
op.onCompleted();
}
});
@Test(timeout = 2000)
public void testTakeObserveOn() {
@SuppressWarnings("unchecked")
Observer<Object> o = mock(Observer.class);
TestSubscriber<Object> ts = new TestSubscriber<Object>(o);
INFINITE_OBSERVABLE.onBackpressureDrop().observeOn(Schedulers.newThread()).take(1).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
verify(o).onNext(1L);
verify(o, never()).onNext(2L);
verify(o).onCompleted();
verify(o, never()).onError(any(Throwable.class));
}
@Test
public void testProducerRequestThroughTake() {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
ts.requestMore(3);
final AtomicLong requested = new AtomicLong();
Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> s) {
s.setProducer(new Producer() {
@Override
public void request(long n) {
requested.set(n);
}
});
}
}).take(3).subscribe(ts);
assertEquals(3, requested.get());
}
@Test
public void testProducerRequestThroughTakeIsModified() {
TestSubscriber<Integer> ts = new TestSubscriber<Integer>();
ts.requestMore(3);
final AtomicLong requested = new AtomicLong();
Observable.create(new OnSubscribe<Integer>() {
@Override
public void call(Subscriber<? super Integer> s) {
s.setProducer(new Producer() {
@Override
public void request(long n) {
requested.set(n);
}
});
}
}).take(1).subscribe(ts);
assertEquals(1, requested.get());
}
@Test
public void testInterrupt() throws InterruptedException {
final AtomicReference<Object> exception = new AtomicReference<Object>();
final CountDownLatch latch = new CountDownLatch(1);
Observable.just(1).subscribeOn(Schedulers.computation()).take(1).subscribe(new Action1<Integer>() {
@Override
public void call(Integer t1) {
try {
Thread.sleep(100);
} catch (Exception e) {
exception.set(e);
e.printStackTrace();
} finally {
latch.countDown();
}
}
});
latch.await();
assertNull(exception.get());
}
}