/**
* 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.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import io.reactivex.*;
import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.disposables.*;
import io.reactivex.exceptions.TestException;
import io.reactivex.functions.Function;
import io.reactivex.internal.functions.Functions;
import io.reactivex.observers.TestObserver;
import io.reactivex.plugins.RxJavaPlugins;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
public class ObservableTimeoutWithSelectorTest {
@Test(timeout = 2000)
public void testTimeoutSelectorNormal1() {
PublishSubject<Integer> source = PublishSubject.create();
final PublishSubject<Integer> timeout = PublishSubject.create();
Function<Integer, Observable<Integer>> timeoutFunc = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
return timeout;
}
};
Observable<Integer> other = Observable.fromIterable(Arrays.asList(100));
Observer<Object> o = TestHelper.mockObserver();
InOrder inOrder = inOrder(o);
source.timeout(timeout, timeoutFunc, other).subscribe(o);
source.onNext(1);
source.onNext(2);
source.onNext(3);
timeout.onNext(1);
inOrder.verify(o).onNext(1);
inOrder.verify(o).onNext(2);
inOrder.verify(o).onNext(3);
inOrder.verify(o).onNext(100);
inOrder.verify(o).onComplete();
verify(o, never()).onError(any(Throwable.class));
}
@Test
public void testTimeoutSelectorTimeoutFirst() throws InterruptedException {
Observable<Integer> source = Observable.<Integer>never();
final PublishSubject<Integer> timeout = PublishSubject.create();
Function<Integer, Observable<Integer>> timeoutFunc = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
return timeout;
}
};
Observable<Integer> other = Observable.fromIterable(Arrays.asList(100));
Observer<Object> o = TestHelper.mockObserver();
InOrder inOrder = inOrder(o);
source.timeout(timeout, timeoutFunc, other).subscribe(o);
timeout.onNext(1);
inOrder.verify(o).onNext(100);
inOrder.verify(o).onComplete();
verify(o, never()).onError(any(Throwable.class));
}
@Test
public void testTimeoutSelectorFirstThrows() {
Observable<Integer> source = Observable.<Integer>never();
final PublishSubject<Integer> timeout = PublishSubject.create();
Function<Integer, Observable<Integer>> timeoutFunc = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
return timeout;
}
};
Callable<Observable<Integer>> firstTimeoutFunc = new Callable<Observable<Integer>>() {
@Override
public Observable<Integer> call() {
throw new TestException();
}
};
Observable<Integer> other = Observable.fromIterable(Arrays.asList(100));
Observer<Object> o = TestHelper.mockObserver();
source.timeout(Observable.defer(firstTimeoutFunc), timeoutFunc, other).subscribe(o);
verify(o).onError(any(TestException.class));
verify(o, never()).onNext(any());
verify(o, never()).onComplete();
}
@Test
public void testTimeoutSelectorSubsequentThrows() {
PublishSubject<Integer> source = PublishSubject.create();
final PublishSubject<Integer> timeout = PublishSubject.create();
Function<Integer, Observable<Integer>> timeoutFunc = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
throw new TestException();
}
};
Observable<Integer> other = Observable.fromIterable(Arrays.asList(100));
Observer<Object> o = TestHelper.mockObserver();
InOrder inOrder = inOrder(o);
source.timeout(timeout, timeoutFunc, other).subscribe(o);
source.onNext(1);
inOrder.verify(o).onNext(1);
inOrder.verify(o).onError(any(TestException.class));
verify(o, never()).onComplete();
}
@Test
public void testTimeoutSelectorFirstObservableThrows() {
PublishSubject<Integer> source = PublishSubject.create();
final PublishSubject<Integer> timeout = PublishSubject.create();
Function<Integer, Observable<Integer>> timeoutFunc = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
return timeout;
}
};
Observable<Integer> other = Observable.fromIterable(Arrays.asList(100));
Observer<Object> o = TestHelper.mockObserver();
source.timeout(Observable.<Integer> error(new TestException()), timeoutFunc, other).subscribe(o);
verify(o).onError(any(TestException.class));
verify(o, never()).onNext(any());
verify(o, never()).onComplete();
}
@Test
public void testTimeoutSelectorSubsequentObservableThrows() {
PublishSubject<Integer> source = PublishSubject.create();
final PublishSubject<Integer> timeout = PublishSubject.create();
Function<Integer, Observable<Integer>> timeoutFunc = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
return Observable.<Integer> error(new TestException());
}
};
Observable<Integer> other = Observable.fromIterable(Arrays.asList(100));
Observer<Object> o = TestHelper.mockObserver();
InOrder inOrder = inOrder(o);
source.timeout(timeout, timeoutFunc, other).subscribe(o);
source.onNext(1);
inOrder.verify(o).onNext(1);
inOrder.verify(o).onError(any(TestException.class));
verify(o, never()).onComplete();
}
@Test
public void testTimeoutSelectorWithFirstTimeoutFirstAndNoOtherObservable() {
PublishSubject<Integer> source = PublishSubject.create();
final PublishSubject<Integer> timeout = PublishSubject.create();
Function<Integer, Observable<Integer>> timeoutFunc = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
return PublishSubject.create();
}
};
Observer<Object> o = TestHelper.mockObserver();
source.timeout(timeout, timeoutFunc).subscribe(o);
timeout.onNext(1);
InOrder inOrder = inOrder(o);
inOrder.verify(o).onError(isA(TimeoutException.class));
inOrder.verifyNoMoreInteractions();
}
@Test
public void testTimeoutSelectorWithTimeoutFirstAndNoOtherObservable() {
PublishSubject<Integer> source = PublishSubject.create();
final PublishSubject<Integer> timeout = PublishSubject.create();
Function<Integer, Observable<Integer>> timeoutFunc = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
return timeout;
}
};
Observer<Object> o = TestHelper.mockObserver();
source.timeout(PublishSubject.create(), timeoutFunc).subscribe(o);
source.onNext(1);
timeout.onNext(1);
InOrder inOrder = inOrder(o);
inOrder.verify(o).onNext(1);
inOrder.verify(o).onError(isA(TimeoutException.class));
inOrder.verifyNoMoreInteractions();
}
@Test
public void testTimeoutSelectorWithTimeoutAndOnNextRaceCondition() throws InterruptedException {
// Thread 1 Thread 2
//
// observer.onNext(1)
// start timeout
// unsubscribe timeout in thread 2 start to do some long-time work in "unsubscribe"
// observer.onNext(2)
// timeout.onNext(1)
// "unsubscribe" done
//
//
// In the above case, the timeout operator should ignore "timeout.onNext(1)"
// since "observer" has already seen 2.
final CountDownLatch observerReceivedTwo = new CountDownLatch(1);
final CountDownLatch timeoutEmittedOne = new CountDownLatch(1);
final CountDownLatch observerCompleted = new CountDownLatch(1);
final CountDownLatch enteredTimeoutOne = new CountDownLatch(1);
final AtomicBoolean latchTimeout = new AtomicBoolean(false);
final Function<Integer, Observable<Integer>> timeoutFunc = new Function<Integer, Observable<Integer>>() {
@Override
public Observable<Integer> apply(Integer t1) {
if (t1 == 1) {
// Force "unsubscribe" run on another thread
return Observable.unsafeCreate(new ObservableSource<Integer>() {
@Override
public void subscribe(Observer<? super Integer> observer) {
observer.onSubscribe(Disposables.empty());
enteredTimeoutOne.countDown();
// force the timeout message be sent after observer.onNext(2)
while (true) {
try {
if (!observerReceivedTwo.await(30, TimeUnit.SECONDS)) {
// CountDownLatch timeout
// There should be something wrong
latchTimeout.set(true);
}
break;
} catch (InterruptedException e) {
// Since we just want to emulate a busy method,
// we ignore the interrupt signal from Scheduler.
}
}
observer.onNext(1);
timeoutEmittedOne.countDown();
}
}).subscribeOn(Schedulers.newThread());
} else {
return PublishSubject.create();
}
}
};
final Observer<Integer> o = TestHelper.mockObserver();
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
observerReceivedTwo.countDown();
return null;
}
}).when(o).onNext(2);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
observerCompleted.countDown();
return null;
}
}).when(o).onComplete();
final TestObserver<Integer> ts = new TestObserver<Integer>(o);
new Thread(new Runnable() {
@Override
public void run() {
PublishSubject<Integer> source = PublishSubject.create();
source.timeout(timeoutFunc, Observable.just(3)).subscribe(ts);
source.onNext(1); // start timeout
try {
if (!enteredTimeoutOne.await(30, TimeUnit.SECONDS)) {
latchTimeout.set(true);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
source.onNext(2); // disable timeout
try {
if (!timeoutEmittedOne.await(30, TimeUnit.SECONDS)) {
latchTimeout.set(true);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
source.onComplete();
}
}).start();
if (!observerCompleted.await(30, TimeUnit.SECONDS)) {
latchTimeout.set(true);
}
assertFalse("CoundDownLatch timeout", latchTimeout.get());
InOrder inOrder = inOrder(o);
inOrder.verify(o).onSubscribe((Disposable)notNull());
inOrder.verify(o).onNext(1);
inOrder.verify(o).onNext(2);
inOrder.verify(o, never()).onNext(3);
inOrder.verify(o).onComplete();
inOrder.verifyNoMoreInteractions();
}
@Test
public void dispose() {
TestHelper.checkDisposed(PublishSubject.create().timeout(Functions.justFunction(Observable.never())));
TestHelper.checkDisposed(PublishSubject.create().timeout(Functions.justFunction(Observable.never()), Observable.never()));
}
@Test
public void doubleOnSubscribe() {
TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Observable<Object> o) throws Exception {
return o.timeout(Functions.justFunction(Observable.never()));
}
});
TestHelper.checkDoubleOnSubscribeObservable(new Function<Observable<Object>, ObservableSource<Object>>() {
@Override
public ObservableSource<Object> apply(Observable<Object> o) throws Exception {
return o.timeout(Functions.justFunction(Observable.never()), Observable.never());
}
});
}
@Test
public void empty() {
Observable.empty()
.timeout(Functions.justFunction(Observable.never()))
.test()
.assertResult();
}
@Test
public void error() {
Observable.error(new TestException())
.timeout(Functions.justFunction(Observable.never()))
.test()
.assertFailure(TestException.class);
}
@Test
public void emptyInner() {
PublishSubject<Integer> ps = PublishSubject.create();
TestObserver<Integer> to = ps
.timeout(Functions.justFunction(Observable.empty()))
.test();
ps.onNext(1);
to.assertFailure(TimeoutException.class, 1);
}
@Test
public void badInnerSource() {
List<Throwable> errors = TestHelper.trackPluginErrors();
try {
PublishSubject<Integer> ps = PublishSubject.create();
TestObserver<Integer> to = ps
.timeout(Functions.justFunction(new Observable<Integer>() {
@Override
protected void subscribeActual(Observer<? super Integer> observer) {
observer.onSubscribe(Disposables.empty());
observer.onError(new TestException("First"));
observer.onNext(2);
observer.onError(new TestException("Second"));
observer.onComplete();
}
}))
.test();
ps.onNext(1);
to.assertFailureAndMessage(TestException.class, "First", 1);
TestHelper.assertUndeliverable(errors, 0, TestException.class, "Second");
} finally {
RxJavaPlugins.reset();
}
}
@Test
public void badInnerSourceOther() {
List<Throwable> errors = TestHelper.trackPluginErrors();
try {
PublishSubject<Integer> ps = PublishSubject.create();
TestObserver<Integer> to = ps
.timeout(Functions.justFunction(new Observable<Integer>() {
@Override
protected void subscribeActual(Observer<? super Integer> observer) {
observer.onSubscribe(Disposables.empty());
observer.onError(new TestException("First"));
observer.onNext(2);
observer.onError(new TestException("Second"));
observer.onComplete();
}
}), Observable.just(2))
.test();
ps.onNext(1);
to.assertFailureAndMessage(TestException.class, "First", 1);
TestHelper.assertUndeliverable(errors, 0, TestException.class, "Second");
} finally {
RxJavaPlugins.reset();
}
}
@Test
public void withOtherMainError() {
Observable.error(new TestException())
.timeout(Functions.justFunction(Observable.never()), Observable.never())
.test()
.assertFailure(TestException.class);
}
@Test
public void badSourceTimeout() {
new Observable<Integer>() {
@Override
protected void subscribeActual(Observer<? super Integer> observer) {
observer.onSubscribe(Disposables.empty());
observer.onNext(1);
observer.onNext(2);
observer.onError(new TestException("First"));
observer.onNext(3);
observer.onComplete();
observer.onError(new TestException("Second"));
}
}
.timeout(Functions.justFunction(Observable.never()), Observable.<Integer>never())
.take(1)
.test()
.assertResult(1);
}
@Test
public void selectorTake() {
PublishSubject<Integer> ps = PublishSubject.create();
TestObserver<Integer> to = ps
.timeout(Functions.justFunction(Observable.never()))
.take(1)
.test();
assertTrue(ps.hasObservers());
ps.onNext(1);
assertFalse(ps.hasObservers());
to.assertResult(1);
}
@Test
public void selectorFallbackTake() {
PublishSubject<Integer> ps = PublishSubject.create();
TestObserver<Integer> to = ps
.timeout(Functions.justFunction(Observable.never()), Observable.just(2))
.take(1)
.test();
assertTrue(ps.hasObservers());
ps.onNext(1);
assertFalse(ps.hasObservers());
to.assertResult(1);
}
}