/**
* 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 static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.*;
import org.junit.*;
import org.mockito.InOrder;
import io.reactivex.*;
import io.reactivex.disposables.*;
import io.reactivex.exceptions.TestException;
import io.reactivex.functions.*;
import io.reactivex.observers.TestObserver;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
public class ObservableTakeTest {
@Test
public void testTake1() {
Observable<String> w = Observable.fromIterable(Arrays.asList("one", "two", "three"));
Observable<String> take = w.take(2);
Observer<String> observer = TestHelper.mockObserver();
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)).onComplete();
}
@Test
public void testTake2() {
Observable<String> w = Observable.fromIterable(Arrays.asList("one", "two", "three"));
Observable<String> take = w.take(1);
Observer<String> observer = TestHelper.mockObserver();
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)).onComplete();
}
@Test(expected = IllegalArgumentException.class)
public void testTakeWithError() {
Observable.fromIterable(Arrays.asList(1, 2, 3)).take(1)
.map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t1) {
throw new IllegalArgumentException("some error");
}
}).blockingSingle();
}
@Test
public void testTakeWithErrorHappeningInOnNext() {
Observable<Integer> w = Observable.fromIterable(Arrays.asList(1, 2, 3))
.take(2).map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t1) {
throw new IllegalArgumentException("some error");
}
});
Observer<Integer> observer = TestHelper.mockObserver();
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.fromIterable(Arrays.asList(1, 2, 3)).take(1).map(new Function<Integer, Integer>() {
@Override
public Integer apply(Integer t1) {
throw new IllegalArgumentException("some error");
}
});
Observer<Integer> observer = TestHelper.mockObserver();
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.unsafeCreate(new ObservableSource<String>() {
@Override
public void subscribe(Observer<? super String> observer) {
observer.onSubscribe(Disposables.empty());
observer.onNext("one");
observer.onError(new Throwable("test failed"));
}
});
Observer<String> observer = TestHelper.mockObserver();
source.take(1).subscribe(observer);
verify(observer).onSubscribe((Disposable)notNull());
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)).onComplete();
verifyNoMoreInteractions(observer);
}
@Test
@Ignore("take(0) is now empty() and doesn't even subscribe to the original source")
public void testTakeZeroDoesntLeakError() {
final AtomicBoolean subscribed = new AtomicBoolean(false);
final Disposable bs = Disposables.empty();
Observable<String> source = Observable.unsafeCreate(new ObservableSource<String>() {
@Override
public void subscribe(Observer<? super String> observer) {
subscribed.set(true);
observer.onSubscribe(bs);
observer.onError(new Throwable("test failed"));
}
});
Observer<String> observer = TestHelper.mockObserver();
source.take(0).subscribe(observer);
assertTrue("source subscribed", subscribed.get());
assertTrue("source unsubscribed", bs.isDisposed());
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)).onComplete();
verifyNoMoreInteractions(observer);
}
@Test
public void testUnsubscribeAfterTake() {
TestObservableFunc f = new TestObservableFunc("one", "two", "three");
Observable<String> w = Observable.unsafeCreate(f);
Observer<String> observer = TestHelper.mockObserver();
Observable<String> take = w.take(1);
take.subscribe(observer);
// 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).onSubscribe((Disposable)notNull());
verify(observer, times(1)).onNext("one");
verify(observer, never()).onNext("two");
verify(observer, never()).onNext("three");
verify(observer, times(1)).onComplete();
// FIXME no longer assertable
// verify(s, times(1)).unsubscribe();
verifyNoMoreInteractions(observer);
}
@Test(timeout = 2000)
public void testUnsubscribeFromSynchronousInfiniteObservable() {
final AtomicLong count = new AtomicLong();
INFINITE_OBSERVABLE.take(10).subscribe(new Consumer<Long>() {
@Override
public void accept(Long l) {
count.set(l);
}
});
assertEquals(10, count.get());
}
@Test(timeout = 2000)
public void testMultiTake() {
final AtomicInteger count = new AtomicInteger();
Observable.unsafeCreate(new ObservableSource<Integer>() {
@Override
public void subscribe(Observer<? super Integer> s) {
Disposable bs = Disposables.empty();
s.onSubscribe(bs);
for (int i = 0; !bs.isDisposed(); i++) {
System.out.println("Emit: " + i);
count.incrementAndGet();
s.onNext(i);
}
}
}).take(100).take(1).blockingForEach(new Consumer<Integer>() {
@Override
public void accept(Integer t1) {
System.out.println("Receive: " + t1);
}
});
assertEquals(1, count.get());
}
static class TestObservableFunc implements ObservableSource<String> {
final String[] values;
Thread t;
TestObservableFunc(String... values) {
this.values = values;
}
@Override
public void subscribe(final Observer<? super String> observer) {
observer.onSubscribe(Disposables.empty());
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.onComplete();
} 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.unsafeCreate(new ObservableSource<Long>() {
@Override
public void subscribe(Observer<? super Long> op) {
Disposable d = Disposables.empty();
op.onSubscribe(d);
long l = 1;
while (!d.isDisposed()) {
op.onNext(l++);
}
op.onComplete();
}
});
@Test(timeout = 2000)
public void testTakeObserveOn() {
Observer<Object> o = TestHelper.mockObserver();
TestObserver<Object> ts = new TestObserver<Object>(o);
INFINITE_OBSERVABLE
.observeOn(Schedulers.newThread()).take(1).subscribe(ts);
ts.awaitTerminalEvent();
ts.assertNoErrors();
verify(o).onNext(1L);
verify(o, never()).onNext(2L);
verify(o).onComplete();
verify(o, never()).onError(any(Throwable.class));
}
@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 Consumer<Integer>() {
@Override
public void accept(Integer t1) {
try {
Thread.sleep(100);
} catch (Exception e) {
exception.set(e);
e.printStackTrace();
} finally {
latch.countDown();
}
}
});
latch.await();
assertNull(exception.get());
}
@Test
public void takeFinalValueThrows() {
Observable<Integer> source = Observable.just(1).take(1);
TestObserver<Integer> ts = new TestObserver<Integer>() {
@Override
public void onNext(Integer t) {
throw new TestException();
}
};
source.safeSubscribe(ts);
ts.assertNoValues();
ts.assertError(TestException.class);
ts.assertNotComplete();
}
@Test
public void testReentrantTake() {
final PublishSubject<Integer> source = PublishSubject.create();
TestObserver<Integer> ts = new TestObserver<Integer>();
source.take(1).doOnNext(new Consumer<Integer>() {
@Override
public void accept(Integer v) {
source.onNext(2);
}
}).subscribe(ts);
source.onNext(1);
ts.assertValue(1);
ts.assertNoErrors();
ts.assertComplete();
}
@Test
public void takeNegative() {
try {
Observable.just(1).take(-99);
fail("Should have thrown");
} catch (IllegalArgumentException ex) {
assertEquals("count >= 0 required but it was -99", ex.getMessage());
}
}
@Test
public void takeZero() {
Observable.just(1)
.take(0)
.test()
.assertResult();
}
@Test
public void dispose() {
TestHelper.checkDisposed(PublishSubject.create().take(2));
}
@Test
public void doubleOnSubscribe() {
TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Observable<Object> o) throws Exception {
return o.take(2);
}
});
}
}