/** * 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.observers; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import org.junit.Test; import rx.Subscriber; import rx.exceptions.CompositeException; import rx.exceptions.OnErrorFailedException; import rx.exceptions.OnErrorNotImplementedException; import rx.functions.Action0; import rx.subscriptions.Subscriptions; public class SafeObserverTest { @Test public void onNextFailure() { AtomicReference<Throwable> onError = new AtomicReference<Throwable>(); try { OBSERVER_ONNEXT_FAIL(onError).onNext("one"); fail("expects exception to be thrown"); } catch (Exception e) { assertNull(onError.get()); assertTrue(e instanceof SafeObserverTestException); assertEquals("onNextFail", e.getMessage()); } } @Test public void onNextFailureSafe() { AtomicReference<Throwable> onError = new AtomicReference<Throwable>(); try { new SafeSubscriber<String>(OBSERVER_ONNEXT_FAIL(onError)).onNext("one"); assertNotNull(onError.get()); assertTrue(onError.get() instanceof SafeObserverTestException); assertEquals("onNextFail", onError.get().getMessage()); } catch (Exception e) { fail("expects exception to be passed to onError"); } } @Test public void onCompletedFailure() { AtomicReference<Throwable> onError = new AtomicReference<Throwable>(); try { OBSERVER_ONCOMPLETED_FAIL(onError).onCompleted(); fail("expects exception to be thrown"); } catch (Exception e) { assertNull(onError.get()); assertTrue(e instanceof SafeObserverTestException); assertEquals("onCompletedFail", e.getMessage()); } } @Test public void onCompletedFailureSafe() { AtomicReference<Throwable> onError = new AtomicReference<Throwable>(); try { new SafeSubscriber<String>(OBSERVER_ONCOMPLETED_FAIL(onError)).onCompleted(); assertNotNull(onError.get()); assertTrue(onError.get() instanceof SafeObserverTestException); assertEquals("onCompletedFail", onError.get().getMessage()); } catch (Exception e) { fail("expects exception to be passed to onError"); } } @Test public void onErrorFailure() { try { OBSERVER_ONERROR_FAIL().onError(new SafeObserverTestException("error!")); fail("expects exception to be thrown"); } catch (Exception e) { assertTrue(e instanceof SafeObserverTestException); assertEquals("onErrorFail", e.getMessage()); } } @Test public void onErrorFailureSafe() { try { new SafeSubscriber<String>(OBSERVER_ONERROR_FAIL()).onError(new SafeObserverTestException("error!")); fail("expects exception to be thrown"); } catch (Exception e) { e.printStackTrace(); assertTrue(e instanceof RuntimeException); assertEquals("Error occurred when trying to propagate error to Observer.onError", e.getMessage()); Throwable e2 = e.getCause(); assertTrue(e2 instanceof CompositeException); List<Throwable> innerExceptions = ((CompositeException) e2).getExceptions(); assertEquals(2, innerExceptions.size()); Throwable e3 = innerExceptions.get(0); assertTrue(e3 instanceof SafeObserverTestException); assertEquals("error!", e3.getMessage()); Throwable e4 = innerExceptions.get(1); assertTrue(e4 instanceof SafeObserverTestException); assertEquals("onErrorFail", e4.getMessage()); } } @Test public void onErrorNotImplementedFailureSafe() { try { new SafeSubscriber<String>(OBSERVER_ONERROR_NOTIMPLEMENTED()).onError(new SafeObserverTestException("error!")); fail("expects exception to be thrown"); } catch (Exception e) { assertTrue(e instanceof OnErrorNotImplementedException); assertTrue(e.getCause() instanceof SafeObserverTestException); assertEquals("error!", e.getCause().getMessage()); } } @Test public void onNextOnErrorFailure() { try { OBSERVER_ONNEXT_ONERROR_FAIL().onNext("one"); fail("expects exception to be thrown"); } catch (Exception e) { e.printStackTrace(); assertTrue(e instanceof SafeObserverTestException); assertEquals("onNextFail", e.getMessage()); } } @Test public void onNextOnErrorFailureSafe() { try { new SafeSubscriber<String>(OBSERVER_ONNEXT_ONERROR_FAIL()).onNext("one"); fail("expects exception to be thrown"); } catch (Exception e) { e.printStackTrace(); assertTrue(e instanceof RuntimeException); assertEquals("Error occurred when trying to propagate error to Observer.onError", e.getMessage()); Throwable e2 = e.getCause(); assertTrue(e2 instanceof CompositeException); List<Throwable> innerExceptions = ((CompositeException) e2).getExceptions(); assertEquals(2, innerExceptions.size()); Throwable e3 = innerExceptions.get(0); assertTrue(e3 instanceof SafeObserverTestException); assertEquals("onNextFail", e3.getMessage()); Throwable e4 = innerExceptions.get(1); assertTrue(e4 instanceof SafeObserverTestException); assertEquals("onErrorFail", e4.getMessage()); } } @Test public void onCompleteSuccessWithUnsubscribeFailure() { Subscriber<String> o = OBSERVER_SUCCESS(); try { o.add(Subscriptions.create(new Action0() { @Override public void call() { // break contract by throwing exception throw new SafeObserverTestException("failure from unsubscribe"); } })); new SafeSubscriber<String>(o).onCompleted(); fail("expects exception to be thrown"); } catch (Exception e) { e.printStackTrace(); assertTrue(o.isUnsubscribed()); assertTrue(e instanceof SafeObserverTestException); assertEquals("failure from unsubscribe", e.getMessage()); // expected since onError fails so SafeObserver can't help } } @Test public void onErrorSuccessWithUnsubscribeFailure() { AtomicReference<Throwable> onError = new AtomicReference<Throwable>(); Subscriber<String> o = OBSERVER_SUCCESS(onError); try { o.add(Subscriptions.create(new Action0() { @Override public void call() { // break contract by throwing exception throw new SafeObserverTestException("failure from unsubscribe"); } })); new SafeSubscriber<String>(o).onError(new SafeObserverTestException("failed")); fail("we expect the unsubscribe failure to cause an exception to be thrown"); } catch (Exception e) { e.printStackTrace(); assertTrue(o.isUnsubscribed()); // we still expect onError to have received something before unsubscribe blew up assertNotNull(onError.get()); assertTrue(onError.get() instanceof SafeObserverTestException); assertEquals("failed", onError.get().getMessage()); // now assert the exception that was thrown OnErrorFailedException onErrorFailedException = (OnErrorFailedException) e; assertTrue(onErrorFailedException.getCause() instanceof SafeObserverTestException); assertEquals("failure from unsubscribe", e.getMessage()); } } @Test public void onErrorFailureWithUnsubscribeFailure() { Subscriber<String> o = OBSERVER_ONERROR_FAIL(); try { o.add(Subscriptions.create(new Action0() { @Override public void call() { // break contract by throwing exception throw new SafeObserverTestException("failure from unsubscribe"); } })); new SafeSubscriber<String>(o).onError(new SafeObserverTestException("onError failure")); fail("expects exception to be thrown"); } catch (Exception e) { e.printStackTrace(); assertTrue(o.isUnsubscribed()); // assertions for what is expected for the actual failure propagated to onError which then fails assertTrue(e instanceof RuntimeException); assertEquals("Error occurred when trying to propagate error to Observer.onError and during unsubscription.", e.getMessage()); Throwable e2 = e.getCause(); assertTrue(e2 instanceof CompositeException); List<Throwable> innerExceptions = ((CompositeException) e2).getExceptions(); assertEquals(3, innerExceptions.size()); Throwable e3 = innerExceptions.get(0); assertTrue(e3 instanceof SafeObserverTestException); assertEquals("onError failure", e3.getMessage()); Throwable e4 = innerExceptions.get(1); assertTrue(e4 instanceof SafeObserverTestException); assertEquals("onErrorFail", e4.getMessage()); Throwable e5 = innerExceptions.get(2); assertTrue(e5 instanceof SafeObserverTestException); assertEquals("failure from unsubscribe", e5.getMessage()); } } @Test public void onErrorNotImplementedFailureWithUnsubscribeFailure() { Subscriber<String> o = OBSERVER_ONERROR_NOTIMPLEMENTED(); try { o.add(Subscriptions.create(new Action0() { @Override public void call() { // break contract by throwing exception throw new SafeObserverTestException("failure from unsubscribe"); } })); new SafeSubscriber<String>(o).onError(new SafeObserverTestException("error!")); fail("expects exception to be thrown"); } catch (Exception e) { e.printStackTrace(); assertTrue(o.isUnsubscribed()); // assertions for what is expected for the actual failure propagated to onError which then fails assertTrue(e instanceof RuntimeException); assertEquals("Observer.onError not implemented and error while unsubscribing.", e.getMessage()); Throwable e2 = e.getCause(); assertTrue(e2 instanceof CompositeException); List<Throwable> innerExceptions = ((CompositeException) e2).getExceptions(); assertEquals(2, innerExceptions.size()); Throwable e3 = innerExceptions.get(0); assertTrue(e3 instanceof SafeObserverTestException); assertEquals("error!", e3.getMessage()); Throwable e4 = innerExceptions.get(1); assertTrue(e4 instanceof SafeObserverTestException); assertEquals("failure from unsubscribe", e4.getMessage()); } } private static Subscriber<String> OBSERVER_SUCCESS() { return new Subscriber<String>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { } @Override public void onNext(String args) { } }; } private static Subscriber<String> OBSERVER_SUCCESS(final AtomicReference<Throwable> onError) { return new Subscriber<String>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { onError.set(e); } @Override public void onNext(String args) { } }; } private static Subscriber<String> OBSERVER_ONNEXT_FAIL(final AtomicReference<Throwable> onError) { return new Subscriber<String>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { onError.set(e); } @Override public void onNext(String args) { throw new SafeObserverTestException("onNextFail"); } }; } private static Subscriber<String> OBSERVER_ONNEXT_ONERROR_FAIL() { return new Subscriber<String>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { throw new SafeObserverTestException("onErrorFail"); } @Override public void onNext(String args) { throw new SafeObserverTestException("onNextFail"); } }; } private static Subscriber<String> OBSERVER_ONERROR_FAIL() { return new Subscriber<String>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { throw new SafeObserverTestException("onErrorFail"); } @Override public void onNext(String args) { } }; } private static Subscriber<String> OBSERVER_ONERROR_NOTIMPLEMENTED() { return new Subscriber<String>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { throw new OnErrorNotImplementedException(e); } @Override public void onNext(String args) { } }; } private static Subscriber<String> OBSERVER_ONCOMPLETED_FAIL(final AtomicReference<Throwable> onError) { return new Subscriber<String>() { @Override public void onCompleted() { throw new SafeObserverTestException("onCompletedFail"); } @Override public void onError(Throwable e) { onError.set(e); } @Override public void onNext(String args) { } }; } @SuppressWarnings("serial") private static class SafeObserverTestException extends RuntimeException { public SafeObserverTestException(String message) { super(message); } } }