/**
* 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.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import rx.Observable;
import rx.Observer;
import rx.Producer;
import rx.Scheduler;
import rx.Subscriber;
import rx.exceptions.TestException;
import rx.functions.Action0;
import rx.functions.Func1;
import rx.observers.TestSubscriber;
import rx.schedulers.TestScheduler;
public class OperatorSwitchTest {
private TestScheduler scheduler;
private Scheduler.Worker innerScheduler;
private Observer<String> observer;
@Before
@SuppressWarnings("unchecked")
public void before() {
scheduler = new TestScheduler();
innerScheduler = scheduler.createWorker();
observer = mock(Observer.class);
}
@Test
public void testSwitchWhenOuterCompleteBeforeInner() {
Observable<Observable<String>> source = Observable.create(new Observable.OnSubscribe<Observable<String>>() {
@Override
public void call(Subscriber<? super Observable<String>> observer) {
publishNext(observer, 50, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishNext(observer, 70, "one");
publishNext(observer, 100, "two");
publishCompleted(observer, 200);
}
}));
publishCompleted(observer, 60);
}
});
Observable<String> sampled = Observable.switchOnNext(source);
sampled.subscribe(observer);
InOrder inOrder = inOrder(observer);
scheduler.advanceTimeTo(350, TimeUnit.MILLISECONDS);
inOrder.verify(observer, times(2)).onNext(anyString());
inOrder.verify(observer, times(1)).onCompleted();
}
@Test
public void testSwitchWhenInnerCompleteBeforeOuter() {
Observable<Observable<String>> source = Observable.create(new Observable.OnSubscribe<Observable<String>>() {
@Override
public void call(Subscriber<? super Observable<String>> observer) {
publishNext(observer, 10, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishNext(observer, 0, "one");
publishNext(observer, 10, "two");
publishCompleted(observer, 20);
}
}));
publishNext(observer, 100, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishNext(observer, 0, "three");
publishNext(observer, 10, "four");
publishCompleted(observer, 20);
}
}));
publishCompleted(observer, 200);
}
});
Observable<String> sampled = Observable.switchOnNext(source);
sampled.subscribe(observer);
InOrder inOrder = inOrder(observer);
scheduler.advanceTimeTo(150, TimeUnit.MILLISECONDS);
inOrder.verify(observer, never()).onCompleted();
inOrder.verify(observer, times(1)).onNext("one");
inOrder.verify(observer, times(1)).onNext("two");
inOrder.verify(observer, times(1)).onNext("three");
inOrder.verify(observer, times(1)).onNext("four");
scheduler.advanceTimeTo(250, TimeUnit.MILLISECONDS);
inOrder.verify(observer, never()).onNext(anyString());
inOrder.verify(observer, times(1)).onCompleted();
}
@Test
public void testSwitchWithComplete() {
Observable<Observable<String>> source = Observable.create(new Observable.OnSubscribe<Observable<String>>() {
@Override
public void call(Subscriber<? super Observable<String>> observer) {
publishNext(observer, 50, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> observer) {
publishNext(observer, 60, "one");
publishNext(observer, 100, "two");
}
}));
publishNext(observer, 200, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> observer) {
publishNext(observer, 0, "three");
publishNext(observer, 100, "four");
}
}));
publishCompleted(observer, 250);
}
});
Observable<String> sampled = Observable.switchOnNext(source);
sampled.subscribe(observer);
InOrder inOrder = inOrder(observer);
scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS);
inOrder.verify(observer, never()).onNext(anyString());
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS);
inOrder.verify(observer, times(1)).onNext("one");
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(175, TimeUnit.MILLISECONDS);
inOrder.verify(observer, times(1)).onNext("two");
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(225, TimeUnit.MILLISECONDS);
inOrder.verify(observer, times(1)).onNext("three");
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(350, TimeUnit.MILLISECONDS);
inOrder.verify(observer, times(1)).onNext("four");
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
}
@Test
public void testSwitchWithError() {
Observable<Observable<String>> source = Observable.create(new Observable.OnSubscribe<Observable<String>>() {
@Override
public void call(Subscriber<? super Observable<String>> observer) {
publishNext(observer, 50, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> observer) {
publishNext(observer, 50, "one");
publishNext(observer, 100, "two");
}
}));
publishNext(observer, 200, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishNext(observer, 0, "three");
publishNext(observer, 100, "four");
}
}));
publishError(observer, 250, new TestException());
}
});
Observable<String> sampled = Observable.switchOnNext(source);
sampled.subscribe(observer);
InOrder inOrder = inOrder(observer);
scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS);
inOrder.verify(observer, never()).onNext(anyString());
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS);
inOrder.verify(observer, times(1)).onNext("one");
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(175, TimeUnit.MILLISECONDS);
inOrder.verify(observer, times(1)).onNext("two");
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(225, TimeUnit.MILLISECONDS);
inOrder.verify(observer, times(1)).onNext("three");
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(350, TimeUnit.MILLISECONDS);
inOrder.verify(observer, never()).onNext(anyString());
verify(observer, never()).onCompleted();
verify(observer, times(1)).onError(any(TestException.class));
}
@Test
public void testSwitchWithSubsequenceComplete() {
Observable<Observable<String>> source = Observable.create(new Observable.OnSubscribe<Observable<String>>() {
@Override
public void call(Subscriber<? super Observable<String>> observer) {
publishNext(observer, 50, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishNext(observer, 50, "one");
publishNext(observer, 100, "two");
}
}));
publishNext(observer, 130, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishCompleted(observer, 0);
}
}));
publishNext(observer, 150, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishNext(observer, 50, "three");
}
}));
}
});
Observable<String> sampled = Observable.switchOnNext(source);
sampled.subscribe(observer);
InOrder inOrder = inOrder(observer);
scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS);
inOrder.verify(observer, never()).onNext(anyString());
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS);
inOrder.verify(observer, times(1)).onNext("one");
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(250, TimeUnit.MILLISECONDS);
inOrder.verify(observer, times(1)).onNext("three");
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
}
@Test
public void testSwitchWithSubsequenceError() {
Observable<Observable<String>> source = Observable.create(new Observable.OnSubscribe<Observable<String>>() {
@Override
public void call(Subscriber<? super Observable<String>> observer) {
publishNext(observer, 50, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishNext(observer, 50, "one");
publishNext(observer, 100, "two");
}
}));
publishNext(observer, 130, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishError(observer, 0, new TestException());
}
}));
publishNext(observer, 150, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishNext(observer, 50, "three");
}
}));
}
});
Observable<String> sampled = Observable.switchOnNext(source);
sampled.subscribe(observer);
InOrder inOrder = inOrder(observer);
scheduler.advanceTimeTo(90, TimeUnit.MILLISECONDS);
inOrder.verify(observer, never()).onNext(anyString());
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(125, TimeUnit.MILLISECONDS);
inOrder.verify(observer, times(1)).onNext("one");
verify(observer, never()).onCompleted();
verify(observer, never()).onError(any(Throwable.class));
scheduler.advanceTimeTo(250, TimeUnit.MILLISECONDS);
inOrder.verify(observer, never()).onNext("three");
verify(observer, never()).onCompleted();
verify(observer, times(1)).onError(any(TestException.class));
}
private <T> void publishCompleted(final Observer<T> observer, long delay) {
innerScheduler.schedule(new Action0() {
@Override
public void call() {
observer.onCompleted();
}
}, delay, TimeUnit.MILLISECONDS);
}
private <T> void publishError(final Observer<T> observer, long delay, final Throwable error) {
innerScheduler.schedule(new Action0() {
@Override
public void call() {
observer.onError(error);
}
}, delay, TimeUnit.MILLISECONDS);
}
private <T> void publishNext(final Observer<T> observer, long delay, final T value) {
innerScheduler.schedule(new Action0() {
@Override
public void call() {
observer.onNext(value);
}
}, delay, TimeUnit.MILLISECONDS);
}
@Test
public void testSwitchIssue737() {
// https://github.com/ReactiveX/RxJava/issues/737
Observable<Observable<String>> source = Observable.create(new Observable.OnSubscribe<Observable<String>>() {
@Override
public void call(Subscriber<? super Observable<String>> observer) {
publishNext(observer, 0, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishNext(observer, 10, "1-one");
publishNext(observer, 20, "1-two");
// The following events will be ignored
publishNext(observer, 30, "1-three");
publishCompleted(observer, 40);
}
}));
publishNext(observer, 25, Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> observer) {
publishNext(observer, 10, "2-one");
publishNext(observer, 20, "2-two");
publishNext(observer, 30, "2-three");
publishCompleted(observer, 40);
}
}));
publishCompleted(observer, 30);
}
});
Observable<String> sampled = Observable.switchOnNext(source);
sampled.subscribe(observer);
scheduler.advanceTimeTo(1000, TimeUnit.MILLISECONDS);
InOrder inOrder = inOrder(observer);
inOrder.verify(observer, times(1)).onNext("1-one");
inOrder.verify(observer, times(1)).onNext("1-two");
inOrder.verify(observer, times(1)).onNext("2-one");
inOrder.verify(observer, times(1)).onNext("2-two");
inOrder.verify(observer, times(1)).onNext("2-three");
inOrder.verify(observer, times(1)).onCompleted();
inOrder.verifyNoMoreInteractions();
}
@Test
public void testBackpressure() {
final Observable<String> o1 = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> observer) {
observer.setProducer(new Producer() {
private int emitted = 0;
@Override
public void request(long n) {
for(int i = 0; i < n && emitted < 10 && !observer.isUnsubscribed(); i++) {
scheduler.advanceTimeBy(5, TimeUnit.MILLISECONDS);
emitted++;
observer.onNext("a" + emitted);
}
if(emitted == 10) {
observer.onCompleted();
}
}
});
}
});
final Observable<String> o2 = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> observer) {
observer.setProducer(new Producer() {
private int emitted = 0;
@Override
public void request(long n) {
for(int i = 0; i < n && emitted < 10 && !observer.isUnsubscribed(); i++) {
scheduler.advanceTimeBy(5, TimeUnit.MILLISECONDS);
emitted++;
observer.onNext("b" + emitted);
}
if(emitted == 10) {
observer.onCompleted();
}
}
});
}
});
final Observable<String> o3 = Observable.create(new Observable.OnSubscribe<String>() {
@Override
public void call(final Subscriber<? super String> observer) {
observer.setProducer(new Producer() {
private int emitted = 0;
@Override
public void request(long n) {
for(int i = 0; i < n && emitted < 10 && !observer.isUnsubscribed(); i++) {
emitted++;
observer.onNext("c" + emitted);
}
if(emitted == 10) {
observer.onCompleted();
}
}
});
}
});
Observable<Observable<String>> o = Observable.create(new Observable.OnSubscribe<Observable<String>>() {
@Override
public void call(Subscriber<? super Observable<String>> observer) {
publishNext(observer, 10, o1);
publishNext(observer, 20, o2);
publishNext(observer, 30, o3);
publishCompleted(observer, 30);
}
});
final TestSubscriber<String> testSubscriber = new TestSubscriber<String>();
Observable.switchOnNext(o).subscribe(new Subscriber<String>() {
private int requested = 0;
@Override
public void onStart() {
requested = 3;
request(3);
}
@Override
public void onCompleted() {
testSubscriber.onCompleted();
}
@Override
public void onError(Throwable e) {
testSubscriber.onError(e);
}
@Override
public void onNext(String s) {
testSubscriber.onNext(s);
requested--;
if(requested == 0) {
requested = 3;
request(3);
}
}
});
scheduler.advanceTimeBy(10, TimeUnit.MILLISECONDS);
testSubscriber.assertReceivedOnNext(Arrays.asList("a1", "b1", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10"));
testSubscriber.assertNoErrors();
testSubscriber.assertTerminalEvent();
}
@Test
public void testUnsubscribe() {
final AtomicBoolean isUnsubscribed = new AtomicBoolean();
Observable.switchOnNext(
Observable.create(new Observable.OnSubscribe<Observable<Integer>>() {
@Override
public void call(final Subscriber<? super Observable<Integer>> subscriber) {
subscriber.onNext(Observable.just(1));
isUnsubscribed.set(subscriber.isUnsubscribed());
}
})
).take(1).subscribe();
assertTrue("Switch doesn't propagate 'unsubscribe'", isUnsubscribed.get());
}
/** The upstream producer hijacked the switch producer stopping the requests aimed at the inner observables. */
@Test
public void testIssue2654() {
Observable<String> oneItem = Observable.just("Hello").mergeWith(Observable.<String>never());
Observable<String> src = oneItem.switchMap(new Func1<String, Observable<String>>() {
@Override
public Observable<String> call(final String s) {
return Observable.just(s)
.mergeWith(Observable.interval(10, TimeUnit.MILLISECONDS)
.map(new Func1<Long, String>() {
@Override
public String call(Long i) {
return s + " " + i;
}
})).take(250);
}
})
.share()
;
TestSubscriber<String> ts = new TestSubscriber<String>() {
@Override
public void onNext(String t) {
super.onNext(t);
if (getOnNextEvents().size() == 250) {
onCompleted();
unsubscribe();
}
}
};
src.subscribe(ts);
ts.awaitTerminalEvent(10, TimeUnit.SECONDS);
System.out.println("> testIssue2654: " + ts.getOnNextEvents().size());
ts.assertTerminalEvent();
ts.assertNoErrors();
Assert.assertEquals(250, ts.getOnNextEvents().size());
}
}