/* * 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 com.appunite.rx.operators; import static com.appunite.rx.internal.Preconditions.checkState; import static com.google.common.truth.Truth.assert_; import static org.junit.Assert.*; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import java.lang.ref.WeakReference; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; import org.junit.*; import org.mockito.InOrder; import rx.*; import rx.Observable; import rx.Observer; import rx.exceptions.*; import rx.functions.*; import rx.internal.util.UtilityFunctions; import rx.observers.TestSubscriber; import rx.schedulers.*; import rx.subjects.PublishSubject; public class OperatorSwitchThenUnsubscribeTest { static class TestException extends Exception { } 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 = source.lift(OperatorSwitchThenUnsubscribe.<String>instance()); 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 = source.lift(OperatorSwitchThenUnsubscribe.<String>instance()); 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 = source.lift(OperatorSwitchThenUnsubscribe.<String>instance()); 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 = source.lift(OperatorSwitchThenUnsubscribe.<String>instance()); 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 = source.lift(OperatorSwitchThenUnsubscribe.<String>instance()); 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 = source.lift(OperatorSwitchThenUnsubscribe.<String>instance()); 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 = source.lift(OperatorSwitchThenUnsubscribe.<String>instance()); 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>(); o.lift(OperatorSwitchThenUnsubscribe.<String>instance()).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.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()); } }).lift(OperatorSwitchThenUnsubscribe.<Integer>instance()) .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.map(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); } }) .lift(OperatorSwitchThenUnsubscribe.<String>instance()) .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()); } @Test(timeout = 10000) public void testInitialRequestsAreAdditive() { TestSubscriber<Long> ts = new TestSubscriber<Long>(0); Observable.interval(100, TimeUnit.MILLISECONDS) .map( new Func1<Long, Observable<Long>>() { @Override public Observable<Long> call(Long t) { return Observable.just(1L, 2L, 3L); } } ) .take(3) .lift(OperatorSwitchThenUnsubscribe.<Long>instance()) .subscribe(ts); ts.requestMore(Long.MAX_VALUE - 100); ts.requestMore(1); ts.awaitTerminalEvent(); } @Test(timeout = 10000) public void testInitialRequestsDontOverflow() { TestSubscriber<Long> ts = new TestSubscriber<Long>(0); Observable.interval(100, TimeUnit.MILLISECONDS) .map(new Func1<Long, Observable<Long>>() { @Override public Observable<Long> call(Long t) { return Observable.from(Arrays.asList(1L, 2L, 3L)); } }) .take(3) .lift(OperatorSwitchThenUnsubscribe.<Long>instance()) .subscribe(ts); ts.requestMore(Long.MAX_VALUE - 1); ts.requestMore(2); ts.awaitTerminalEvent(); assertTrue(ts.getOnNextEvents().size() > 0); } @Test(timeout = 10000) public void testSecondaryRequestsDontOverflow() throws InterruptedException { TestSubscriber<Long> ts = new TestSubscriber<Long>(0); Observable.interval(100, TimeUnit.MILLISECONDS) .map(new Func1<Long, Observable<Long>>() { @Override public Observable<Long> call(Long t) { return Observable.from(Arrays.asList(1L, 2L, 3L)); } }) .take(3) .lift(OperatorSwitchThenUnsubscribe.<Long>instance()).subscribe(ts); ts.requestMore(1); //we will miss two of the first observable Thread.sleep(250); ts.requestMore(Long.MAX_VALUE - 1); ts.requestMore(Long.MAX_VALUE - 1); ts.awaitTerminalEvent(); ts.assertValueCount(7); } @Test(timeout = 10000) public void testSecondaryRequestsAdditivelyAreMoreThanLongMaxValueInducesMaxValueRequestFromUpstream() throws InterruptedException { final List<Long> requests = new CopyOnWriteArrayList<Long>(); final Action1<Long> addRequest = new Action1<Long>() { @Override public void call(Long n) { requests.add(n); } }; TestSubscriber<Long> ts = new TestSubscriber<Long>(1); Observable.interval(100, TimeUnit.MILLISECONDS) .map(new Func1<Long, Observable<Long>>() { @Override public Observable<Long> call(Long t) { return Observable.from(Arrays.asList(1L, 2L, 3L)).doOnRequest( addRequest); } }).take(3) .lift(OperatorSwitchThenUnsubscribe.<Long>instance()) .subscribe(ts); // we will miss two of the first observables Thread.sleep(250); ts.requestMore(Long.MAX_VALUE - 1); ts.requestMore(Long.MAX_VALUE - 1); ts.awaitTerminalEvent(); assertTrue(ts.getOnNextEvents().size() > 0); assertEquals(4, requests.size()); // depends on the request pattern assertEquals(Long.MAX_VALUE, (long) requests.get(requests.size()-1)); } @Test public void mainError() { TestSubscriber<Integer> ts = TestSubscriber.create(); PublishSubject<Integer> source = PublishSubject.create(); source .map(new Func1<Integer, Observable<Integer>>() { @Override public Observable<Integer> call(Integer v) { return Observable.range(v, 2); } }) .lift(OperatorSwitchThenUnsubscribe.<Integer>instance(true)) .subscribe(ts); source.onNext(1); source.onNext(2); source.onError(new TestException()); ts.assertValues(1, 2, 2, 3); ts.assertError(TestException.class); ts.assertNotCompleted(); } @Test public void innerError() { TestSubscriber<Integer> ts = TestSubscriber.create(); Observable.range(0, 3).map(new Func1<Integer, Observable<Integer>>() { @Override public Observable<Integer> call(Integer v) { return v == 1 ? Observable.<Integer>error(new TestException()) : Observable.range(v, 2); } }) .lift(OperatorSwitchThenUnsubscribe.<Integer>instance(true)) .subscribe(ts); ts.assertValues(0, 1, 2, 3); ts.assertError(TestException.class); ts.assertNotCompleted(); } @Test public void innerAllError() { TestSubscriber<Integer> ts = TestSubscriber.create(); Observable.range(0, 3).map(new Func1<Integer, Observable<Integer>>() { @Override public Observable<Integer> call(Integer v) { return Observable.range(v, 2).concatWith(Observable.<Integer>error(new TestException())); } }) .lift(OperatorSwitchThenUnsubscribe.<Integer>instance(true)) .subscribe(ts); ts.assertValues(0, 1, 1, 2, 2, 3); ts.assertError(CompositeException.class); ts.assertNotCompleted(); List<Throwable> exceptions = ((CompositeException)ts.getOnErrorEvents().get(0)).getExceptions(); assertEquals(3, exceptions.size()); for (Throwable ex : exceptions) { assertTrue(ex.toString(), ex instanceof TestException); } } @Test public void backpressure() { TestSubscriber<Integer> ts = TestSubscriber.create(0); Observable.range(0, 3).map(new Func1<Integer, Observable<Integer>>() { @Override public Observable<Integer> call(Integer v) { return Observable.range(v, 2); } }) .lift(OperatorSwitchThenUnsubscribe.<Integer>instance(true)) .subscribe(ts); ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); ts.requestMore(2); ts.assertValues(2, 3); ts.assertNoErrors(); ts.assertCompleted(); } @Test public void backpressureWithSwitch() { TestSubscriber<Integer> ts = TestSubscriber.create(0); PublishSubject<Integer> source = PublishSubject.create(); source.map(new Func1<Integer, Observable<Integer>>() { @Override public Observable<Integer> call(Integer v) { return Observable.range(v, 2); } }) .lift(OperatorSwitchThenUnsubscribe.<Integer>instance(true)) .subscribe(ts); ts.assertNoValues(); ts.assertNoErrors(); ts.assertNotCompleted(); ts.requestMore(1); source.onNext(0); ts.assertValues(0); ts.assertNoErrors(); ts.assertNotCompleted(); source.onNext(1); ts.assertValues(0); ts.assertNoErrors(); ts.assertNotCompleted(); ts.requestMore(1); ts.assertValues(0, 1); ts.assertNoErrors(); ts.assertNotCompleted(); source.onNext(2); ts.requestMore(2); source.onCompleted(); ts.assertValues(0, 1, 2, 3); ts.assertNoErrors(); ts.assertCompleted(); } Object ref; @Test public void producerIsNotRetained() throws Exception { ref = new Object(); WeakReference<Object> wr = new WeakReference<Object>(ref); PublishSubject<Observable<Object>> ps = PublishSubject.create(); Subscriber<Object> observer = new Subscriber<Object>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(Object t) { } }; ps.lift(OperatorSwitchThenUnsubscribe.instance()).subscribe(observer); ps.onNext(Observable.just(ref)); ref = null; System.gc(); Thread.sleep(500); Assert.assertNotNull(observer); // retain every other referenec in the pipeline Assert.assertNotNull(ps); Assert.assertNull("Object retained!", wr.get()); } @Test public void switchAsyncHeavily() { for (int i = 1; i < 1024; i *= 2) { System.out.println("switchAsyncHeavily >> " + i); final Queue<Throwable> q = new ConcurrentLinkedQueue<Throwable>(); final long[] lastSeen = { 0L }; final int j = i; TestSubscriber<Integer> ts = new TestSubscriber<Integer>(i) { int count; @Override public void onNext(Integer t) { super.onNext(t); lastSeen[0] = System.currentTimeMillis(); if (++count == j) { count = 0; requestMore(j); } } }; Observable.range(1, 10000) .observeOn(Schedulers.computation(), i) .map(new Func1<Integer, Observable<Integer>>() { @Override public Observable<Integer> call(Integer v) { return Observable.range(1, 1000).observeOn(Schedulers.computation(), j) .doOnError(new Action1<Throwable>() { @Override public void call(Throwable e) { q.add(e); } }); } }) .lift(OperatorSwitchThenUnsubscribe.<Integer>instance()) .timeout(30, TimeUnit.SECONDS) .subscribe(ts); ts.awaitTerminalEvent(60, TimeUnit.SECONDS); if (!q.isEmpty()) { AssertionError ae = new AssertionError("Dropped exceptions"); ae.initCause(new CompositeException(q)); throw ae; } ts.assertNoErrors(); if (ts.getCompletions() == 0) { fail("switchAsyncHeavily timed out @ " + j + " (" + ts.getOnNextEvents().size() + " onNexts received, last was " + (System.currentTimeMillis() - lastSeen[0]) + " ms ago"); } } } @Test public void asyncInner() throws Throwable { for (int i = 0; i < 100; i++) { final AtomicReference<Throwable> error = new AtomicReference<Throwable>(); Observable.just(Observable.range(1, 1000 * 1000).subscribeOn(Schedulers.computation())) .map(UtilityFunctions.<Observable<Integer>>identity()) .lift(OperatorSwitchThenUnsubscribe.<Integer>instance()) .observeOn(Schedulers.computation()) .ignoreElements() .timeout(5, TimeUnit.SECONDS) .toBlocking() .subscribe(Actions.empty(), new Action1<Throwable>() { @Override public void call(Throwable e) { error.set(e); } }); Throwable ex = error.get(); if (ex != null) { throw ex; } } } /** * This test is not passed by {@link rx.internal.operators.OperatorSwitch} */ @Test public void firstSubscribeThenUnsubscribePrevious() throws Exception { final AtomicInteger state = new AtomicInteger(); final int NONE = 0; final int FIRST_SUBSCRIBED = 1; final int FIRST_AND_SECOND_SUBSCRIBED = 2; final int SECOND_SUBSCRIBED = 3; final int WRONG = -1; final Observable<Object> observable1 = Observable.never() .doOnSubscribe(new Action0() { @Override public void call() { checkState(state.compareAndSet(NONE, FIRST_SUBSCRIBED)); } }) .doOnUnsubscribe(new Action0() { @Override public void call() { checkState(state.compareAndSet(FIRST_AND_SECOND_SUBSCRIBED, SECOND_SUBSCRIBED)); } }); final Observable<Object> observable2 = spy(Observable.never()) .doOnSubscribe(new Action0() { @Override public void call() { checkState(state.compareAndSet(FIRST_SUBSCRIBED, FIRST_AND_SECOND_SUBSCRIBED)); } }) .doOnUnsubscribe(new Action0() { @Override public void call() { state.set(WRONG); throw new IllegalStateException("Wrong state"); } }); Observable.just(1, 2) .map(new Func1<Integer, Observable<?>>() { @Override public Observable<?> call(Integer integer) { return integer == 1 ? observable1 : observable2; } }) .lift(OperatorSwitchThenUnsubscribe.instance()) .subscribe(); assert_().that(state.get()).isEqualTo(SECOND_SUBSCRIBED); } }