/** * Copyright 2016 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 io.reactivex.subjects; import static org.junit.Assert.*; import static org.mockito.Matchers.*; import static org.mockito.Mockito.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.junit.*; import org.mockito.*; import io.reactivex.*; import io.reactivex.exceptions.TestException; import io.reactivex.functions.Consumer; import io.reactivex.internal.fuseable.QueueSubscription; import io.reactivex.observers.*; public class AsyncSubjectTest { private final Throwable testException = new Throwable(); @Test public void testNeverCompleted() { AsyncSubject subject = AsyncSubject.create(); Observer observer = TestHelper.mockObserver(); subject.subscribe(observer); subject.onNext("one"); subject.onNext("two"); subject.onNext("three"); verify(observer, Mockito.never()).onNext(anyString()); verify(observer, Mockito.never()).onError(testException); verify(observer, Mockito.never()).onComplete(); } @Test public void testCompleted() { AsyncSubject subject = AsyncSubject.create(); Observer observer = TestHelper.mockObserver(); subject.subscribe(observer); subject.onNext("one"); subject.onNext("two"); subject.onNext("three"); subject.onComplete(); verify(observer, times(1)).onNext("three"); verify(observer, Mockito.never()).onError(any(Throwable.class)); verify(observer, times(1)).onComplete(); } @Test @Ignore("Null values not allowed") public void testNull() { AsyncSubject subject = AsyncSubject.create(); Observer observer = TestHelper.mockObserver(); subject.subscribe(observer); subject.onNext(null); subject.onComplete(); verify(observer, times(1)).onNext(null); verify(observer, Mockito.never()).onError(any(Throwable.class)); verify(observer, times(1)).onComplete(); } @Test public void testSubscribeAfterCompleted() { AsyncSubject subject = AsyncSubject.create(); Observer observer = TestHelper.mockObserver(); subject.onNext("one"); subject.onNext("two"); subject.onNext("three"); subject.onComplete(); subject.subscribe(observer); verify(observer, times(1)).onNext("three"); verify(observer, Mockito.never()).onError(any(Throwable.class)); verify(observer, times(1)).onComplete(); } @Test public void testSubscribeAfterError() { AsyncSubject subject = AsyncSubject.create(); Observer observer = TestHelper.mockObserver(); subject.onNext("one"); subject.onNext("two"); subject.onNext("three"); RuntimeException re = new RuntimeException("failed"); subject.onError(re); subject.subscribe(observer); verify(observer, times(1)).onError(re); verify(observer, Mockito.never()).onNext(any(String.class)); verify(observer, Mockito.never()).onComplete(); } @Test public void testError() { AsyncSubject subject = AsyncSubject.create(); Observer observer = TestHelper.mockObserver(); subject.subscribe(observer); subject.onNext("one"); subject.onNext("two"); subject.onNext("three"); subject.onError(testException); subject.onNext("four"); subject.onError(new Throwable()); subject.onComplete(); verify(observer, Mockito.never()).onNext(anyString()); verify(observer, times(1)).onError(testException); verify(observer, Mockito.never()).onComplete(); } @Test public void testUnsubscribeBeforeCompleted() { AsyncSubject subject = AsyncSubject.create(); Observer observer = TestHelper.mockObserver(); TestObserver ts = new TestObserver(observer); subject.subscribe(ts); subject.onNext("one"); subject.onNext("two"); ts.dispose(); verify(observer, Mockito.never()).onNext(anyString()); verify(observer, Mockito.never()).onError(any(Throwable.class)); verify(observer, Mockito.never()).onComplete(); subject.onNext("three"); subject.onComplete(); verify(observer, Mockito.never()).onNext(anyString()); verify(observer, Mockito.never()).onError(any(Throwable.class)); verify(observer, Mockito.never()).onComplete(); } @Test public void testEmptySubjectCompleted() { AsyncSubject subject = AsyncSubject.create(); Observer observer = TestHelper.mockObserver(); subject.subscribe(observer); subject.onComplete(); InOrder inOrder = inOrder(observer); inOrder.verify(observer, never()).onNext(null); inOrder.verify(observer, never()).onNext(any(String.class)); inOrder.verify(observer, times(1)).onComplete(); inOrder.verifyNoMoreInteractions(); } /** * Can receive timeout if subscribe never receives an onError/onCompleted ... which reveals a race condition. */ @Test(timeout = 10000) public void testSubscribeCompletionRaceCondition() { /* * With non-threadsafe code this fails most of the time on my dev laptop and is non-deterministic enough * to act as a unit test to the race conditions. * * With the synchronization code in place I can not get this to fail on my laptop. */ for (int i = 0; i < 50; i++) { final AsyncSubject subject = AsyncSubject.create(); final AtomicReference value1 = new AtomicReference(); subject.subscribe(new Consumer() { @Override public void accept(String t1) { try { // simulate a slow observer Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } value1.set(t1); } }); Thread t1 = new Thread(new Runnable() { @Override public void run() { subject.onNext("value"); subject.onComplete(); } }); SubjectSubscriberThread t2 = new SubjectSubscriberThread(subject); SubjectSubscriberThread t3 = new SubjectSubscriberThread(subject); SubjectSubscriberThread t4 = new SubjectSubscriberThread(subject); SubjectSubscriberThread t5 = new SubjectSubscriberThread(subject); t2.start(); t3.start(); t1.start(); t4.start(); t5.start(); try { t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } assertEquals("value", value1.get()); assertEquals("value", t2.value.get()); assertEquals("value", t3.value.get()); assertEquals("value", t4.value.get()); assertEquals("value", t5.value.get()); } } private static class SubjectSubscriberThread extends Thread { private final AsyncSubject subject; private final AtomicReference value = new AtomicReference(); public SubjectSubscriberThread(AsyncSubject subject) { this.subject = subject; } @Override public void run() { try { // a timeout exception will happen if we don't get a terminal state String v = subject.timeout(2000, TimeUnit.MILLISECONDS).blockingSingle(); value.set(v); } catch (Exception e) { e.printStackTrace(); } } } // FIXME subscriber methods are not allowed to throw // @Test // public void testOnErrorThrowsDoesntPreventDelivery() { // NbpAsyncSubject ps = NbpAsyncSubject.create(); // // ps.subscribe(); // TestObserver ts = new TestObserver(); // ps.subscribe(ts); // // try { // ps.onError(new RuntimeException("an exception")); // fail("expect OnErrorNotImplementedException"); // } catch (OnErrorNotImplementedException e) { // // ignore // } // // even though the onError above throws we should still receive it on the other subscriber // assertEquals(1, ts.getOnErrorEvents().size()); // } // FIXME subscriber methods are not allowed to throw // /** // * This one has multiple failures so should get a CompositeException // */ // @Test // public void testOnErrorThrowsDoesntPreventDelivery2() { // NbpAsyncSubject ps = NbpAsyncSubject.create(); // // ps.subscribe(); // ps.subscribe(); // TestObserver ts = new TestObserver(); // ps.subscribe(ts); // ps.subscribe(); // ps.subscribe(); // ps.subscribe(); // // try { // ps.onError(new RuntimeException("an exception")); // fail("expect OnErrorNotImplementedException"); // } catch (CompositeException e) { // // we should have 5 of them // assertEquals(5, e.getExceptions().size()); // } // // even though the onError above throws we should still receive it on the other subscriber // assertEquals(1, ts.getOnErrorEvents().size()); // } @Test public void testCurrentStateMethodsNormal() { AsyncSubject as = AsyncSubject.create(); assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasComplete()); assertNull(as.getValue()); assertNull(as.getThrowable()); as.onNext(1); assertFalse(as.hasValue()); // AS no longer reports a value until it has completed assertFalse(as.hasThrowable()); assertFalse(as.hasComplete()); assertNull(as.getValue()); // AS no longer reports a value until it has completed assertNull(as.getThrowable()); as.onComplete(); assertTrue(as.hasValue()); assertFalse(as.hasThrowable()); assertTrue(as.hasComplete()); assertEquals(1, as.getValue()); assertNull(as.getThrowable()); } @Test public void testCurrentStateMethodsEmpty() { AsyncSubject as = AsyncSubject.create(); assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasComplete()); assertNull(as.getValue()); assertNull(as.getThrowable()); as.onComplete(); assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertTrue(as.hasComplete()); assertNull(as.getValue()); assertNull(as.getThrowable()); } @Test public void testCurrentStateMethodsError() { AsyncSubject as = AsyncSubject.create(); assertFalse(as.hasValue()); assertFalse(as.hasThrowable()); assertFalse(as.hasComplete()); assertNull(as.getValue()); assertNull(as.getThrowable()); as.onError(new TestException()); assertFalse(as.hasValue()); assertTrue(as.hasThrowable()); assertFalse(as.hasComplete()); assertNull(as.getValue()); assertTrue(as.getThrowable() instanceof TestException); } @Test public void fusionLive() { AsyncSubject ap = new AsyncSubject(); TestObserver ts = ObserverFusion.newTest(QueueSubscription.ANY); ap.subscribe(ts); ts .assertOf(ObserverFusion.assertFuseable()) .assertOf(ObserverFusion.assertFusionMode(QueueSubscription.ASYNC)); ts.assertNoValues().assertNoErrors().assertNotComplete(); ap.onNext(1); ts.assertNoValues().assertNoErrors().assertNotComplete(); ap.onComplete(); ts.assertResult(1); } @Test public void fusionOfflie() { AsyncSubject ap = new AsyncSubject(); ap.onNext(1); ap.onComplete(); TestObserver ts = ObserverFusion.newTest(QueueSubscription.ANY); ap.subscribe(ts); ts .assertOf(ObserverFusion.assertFuseable()) .assertOf(ObserverFusion.assertFusionMode(QueueSubscription.ASYNC)) .assertResult(1); } }