/**
* 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.processors;
import io.reactivex.TestHelper;
import io.reactivex.exceptions.TestException;
import io.reactivex.functions.Consumer;
import io.reactivex.internal.fuseable.QueueSubscription;
import io.reactivex.internal.subscriptions.BooleanSubscription;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subscribers.SubscriberFusion;
import io.reactivex.subscribers.TestSubscriber;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.reactivestreams.Subscriber;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.Assert.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.*;
public class AsyncProcessorTest extends DelayedFlowableProcessorTest<Object> {
private final Throwable testException = new Throwable();
@Override
protected FlowableProcessor<Object> create() {
return AsyncProcessor.create();
}
@Test
public void testNeverCompleted() {
AsyncProcessor<String> subject = AsyncProcessor.create();
Subscriber<String> observer = TestHelper.mockSubscriber();
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() {
AsyncProcessor<String> subject = AsyncProcessor.create();
Subscriber<String> observer = TestHelper.mockSubscriber();
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() {
AsyncProcessor<String> subject = AsyncProcessor.create();
Subscriber<String> observer = TestHelper.mockSubscriber();
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() {
AsyncProcessor<String> subject = AsyncProcessor.create();
Subscriber<String> observer = TestHelper.mockSubscriber();
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() {
AsyncProcessor<String> subject = AsyncProcessor.create();
Subscriber<String> observer = TestHelper.mockSubscriber();
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() {
AsyncProcessor<String> subject = AsyncProcessor.create();
Subscriber<String> observer = TestHelper.mockSubscriber();
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() {
AsyncProcessor<String> subject = AsyncProcessor.create();
Subscriber<String> observer = TestHelper.mockSubscriber();
TestSubscriber<String> ts = new TestSubscriber<String>(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() {
AsyncProcessor<String> subject = AsyncProcessor.create();
Subscriber<String> observer = TestHelper.mockSubscriber();
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/onComplete ... 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 AsyncProcessor<String> subject = AsyncProcessor.create();
final AtomicReference<String> value1 = new AtomicReference<String>();
subject.subscribe(new Consumer<String>() {
@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 AsyncProcessor<String> subject;
private final AtomicReference<String> value = new AtomicReference<String>();
SubjectSubscriberThread(AsyncProcessor<String> 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() {
// AsyncSubject<String> ps = AsyncSubject.create();
//
// ps.subscribe();
// TestSubscriber<String> ts = new TestSubscriber<String>();
// 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() {
// AsyncSubject<String> ps = AsyncSubject.create();
//
// ps.subscribe();
// ps.subscribe();
// TestSubscriber<String> ts = new TestSubscriber<String>();
// 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() {
AsyncProcessor<Object> as = AsyncProcessor.create();
assertFalse(as.hasValue());
assertFalse(as.hasThrowable());
assertFalse(as.hasComplete());
assertNull(as.getValue());
assertNull(as.getThrowable());
as.onNext(1);
assertFalse(as.hasValue()); // AP no longer reports it has a value until it is terminated
assertFalse(as.hasThrowable());
assertFalse(as.hasComplete());
assertNull(as.getValue()); // AP no longer reports it has a value until it is terminated
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() {
AsyncProcessor<Object> as = AsyncProcessor.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() {
AsyncProcessor<Object> as = AsyncProcessor.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() {
AsyncProcessor<Integer> ap = new AsyncProcessor<Integer>();
TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY);
ap.subscribe(ts);
ts
.assertOf(SubscriberFusion.<Integer>assertFuseable())
.assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC));
ts.assertNoValues().assertNoErrors().assertNotComplete();
ap.onNext(1);
ts.assertNoValues().assertNoErrors().assertNotComplete();
ap.onComplete();
ts.assertResult(1);
}
@Test
public void fusionOfflie() {
AsyncProcessor<Integer> ap = new AsyncProcessor<Integer>();
ap.onNext(1);
ap.onComplete();
TestSubscriber<Integer> ts = SubscriberFusion.newTest(QueueSubscription.ANY);
ap.subscribe(ts);
ts
.assertOf(SubscriberFusion.<Integer>assertFuseable())
.assertOf(SubscriberFusion.<Integer>assertFusionMode(QueueSubscription.ASYNC))
.assertResult(1);
}
@Test
public void onSubscribeAfterDone() {
AsyncProcessor<Object> p = AsyncProcessor.create();
BooleanSubscription bs = new BooleanSubscription();
p.onSubscribe(bs);
assertFalse(bs.isCancelled());
p.onComplete();
bs = new BooleanSubscription();
p.onSubscribe(bs);
assertTrue(bs.isCancelled());
p.test().assertResult();
}
@Test
public void cancelUpfront() {
AsyncProcessor<Object> p = AsyncProcessor.create();
assertFalse(p.hasSubscribers());
p.test().assertEmpty();
p.test().assertEmpty();
p.test(0L, true).assertEmpty();
assertTrue(p.hasSubscribers());
}
@Test
public void cancelRace() {
AsyncProcessor<Object> p = AsyncProcessor.create();
for (int i = 0; i < 500; i++) {
final TestSubscriber<Object> ts1 = p.test();
final TestSubscriber<Object> ts2 = p.test();
Runnable r1 = new Runnable() {
@Override
public void run() {
ts1.cancel();
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
ts2.cancel();
}
};
TestHelper.race(r1, r2, Schedulers.single());
}
}
@Test
public void onErrorCancelRace() {
for (int i = 0; i < 500; i++) {
final AsyncProcessor<Object> p = AsyncProcessor.create();
final TestSubscriber<Object> ts1 = p.test();
Runnable r1 = new Runnable() {
@Override
public void run() {
ts1.cancel();
}
};
final TestException ex = new TestException();
Runnable r2 = new Runnable() {
@Override
public void run() {
p.onError(ex);
}
};
TestHelper.race(r1, r2, Schedulers.single());
if (ts1.errorCount() != 0) {
ts1.assertFailure(TestException.class);
} else {
ts1.assertEmpty();
}
}
}
@Test
public void onNextCrossCancel() {
AsyncProcessor<Object> p = AsyncProcessor.create();
final TestSubscriber<Object> ts2 = new TestSubscriber<Object>();
TestSubscriber<Object> ts1 = new TestSubscriber<Object>() {
@Override
public void onNext(Object t) {
ts2.cancel();
super.onNext(t);
}
};
p.subscribe(ts1);
p.subscribe(ts2);
p.onNext(1);
p.onComplete();
ts1.assertResult(1);
ts2.assertEmpty();
}
@Test
public void onErrorCrossCancel() {
AsyncProcessor<Object> p = AsyncProcessor.create();
final TestSubscriber<Object> ts2 = new TestSubscriber<Object>();
TestSubscriber<Object> ts1 = new TestSubscriber<Object>() {
@Override
public void onError(Throwable t) {
ts2.cancel();
super.onError(t);
}
};
p.subscribe(ts1);
p.subscribe(ts2);
p.onError(new TestException());
ts1.assertFailure(TestException.class);
ts2.assertEmpty();
}
@Test
public void onCompleteCrossCancel() {
AsyncProcessor<Object> p = AsyncProcessor.create();
final TestSubscriber<Object> ts2 = new TestSubscriber<Object>();
TestSubscriber<Object> ts1 = new TestSubscriber<Object>() {
@Override
public void onComplete() {
ts2.cancel();
super.onComplete();
}
};
p.subscribe(ts1);
p.subscribe(ts2);
p.onComplete();
ts1.assertResult();
ts2.assertEmpty();
}
}